Compare commits

..

4 Commits

Author SHA1 Message Date
max
5eaf3547dc Random uint util 2024-11-16 14:54:34 +01:00
max
d80e1177b9 vcs 2024-11-16 13:22:38 +01:00
max
cf6cd080c6 Add component button 2024-11-13 21:11:50 +01:00
max
87ee6df46f Inspector tests 2024-11-02 21:59:35 +01:00
6 changed files with 192 additions and 21 deletions

View File

@ -4,6 +4,7 @@
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/FAudio" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/ImGui.NET" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/MoonTools.ECS" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/RefreshCS" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/SDL2CS" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/WellspringCS" vcs="Git" />

View File

@ -1,6 +1,8 @@
using System.Numerics;
using ImGuiNET;
using MoonTools.ECS;
using Nerfed.Editor.Components;
using Nerfed.Runtime.Serialization;
#if DEBUG
namespace Nerfed.Editor.Systems
@ -29,27 +31,69 @@ public override void Update(TimeSpan delta)
private void DrawEntityComponents(Entity entity)
{
World.ComponentTypeEnumerator components = World.Debug_GetAllComponentTypes(entity);
World.ComponentTypeEnumerator componentTypes = World.Debug_GetAllComponentTypes(entity);
foreach (Type type in components)
// Add button of all types that we can add. Also filter out types we already have.
List<Type> componentTypesToAdd = ComponentHelper.AddComponentByType.Keys.ToList();
foreach (Type componentType in componentTypes)
{
ImGui.Text(type.Name);
componentTypesToAdd.Remove(componentType);
}
const string popupId = "AddComponentPopup";
if (ImGui.Button("Add Component"))
{
ImGui.OpenPopup(popupId);
}
if (ImGui.BeginPopup(popupId))
{
foreach (Type componentType in componentTypesToAdd)
{
if (ImGui.Selectable(componentType.Name))
{
if (ComponentHelper.AddComponentByType.TryGetValue(componentType, out Action<World, Entity> componentSetter))
{
componentSetter.Invoke(World, entity);
}
}
}
ImGui.EndPopup();
}
ImGui.Dummy(new Vector2(16, 16));
ImGui.Text("ComponentInspectorByType");
foreach (Type componentType in componentTypes)
{
if (ComponentHelper.ComponentInspectorByType.TryGetValue(componentType, out Action<World, Entity> componentInspector))
{
componentInspector(World, entity);
}
else if (ComponentHelper.GetComponentByType.TryGetValue(componentType, out Func<World, Entity, ValueType> componentGetter))
{
ValueType component = componentGetter.Invoke(World, entity);
ImGui.Text(component.ToString());
}
else
{
ImGui.Text(componentType.Name);
}
ImGui.Separator();
// TODO: explore something without reflection.
// Maybe generate some look up dictionary<type, Action<world, entity>> for 'custom editors'.
// Look into serializing of entities and components.
foreach (Type component in components)
{
System.Reflection.MethodInfo getMethodInfo = typeof(World).GetMethod("Get");
System.Reflection.MethodInfo getComponentMethod = getMethodInfo.MakeGenericMethod(component);
object result = getComponentMethod.Invoke(World, [entity]);
// process here
ImGui.Text(result.ToString());
}
ImGui.Dummy(new Vector2(16, 16));
// ImGui.Text("Reflection");
// foreach (Type component in componentTypes)
// {
// System.Reflection.MethodInfo getMethodInfo = typeof(World).GetMethod("Get");
// System.Reflection.MethodInfo getComponentMethod = getMethodInfo.MakeGenericMethod(component);
// object result = getComponentMethod.Invoke(World, [entity]);
//
// // process here
// ImGui.Text(result.ToString());
// }
}
}
}

View File

@ -4,10 +4,6 @@ namespace Nerfed.Runtime.Components
{
public readonly record struct LocalTransform(Vector3 position, Quaternion rotation, Vector3 scale)
{
public readonly Vector3 position = position;
public readonly Quaternion rotation = rotation;
public readonly Vector3 scale = scale;
public static readonly LocalTransform Identity = new(Vector3.Zero, Quaternion.Identity, Vector3.One);
}
}

View File

@ -0,0 +1,68 @@
using System.Numerics;
using ImGuiNET;
using MoonTools.ECS;
using Nerfed.Runtime.Components;
namespace Nerfed.Runtime.Serialization;
public static class ComponentHelper
{
// Auto generate this.
public static readonly Dictionary<Type, Func<World, Entity, ValueType>> GetComponentByType = new()
{
{ typeof(LocalTransform), (world, entity) => world.Get<LocalTransform>(entity) },
{ typeof(Root), (world, entity) => world.Get<Root>(entity) },
};
// Auto generate this.
public static readonly Dictionary<Type, Action<World, Entity, ValueType>> SetComponentByType = new()
{
{ typeof(LocalTransform), (world, entity, component) => world.Set(entity, (LocalTransform)component) },
{ typeof(Root), (world, entity, component) => world.Set(entity, (Root)component) },
};
// Auto generate this, but it should only contain user assignable components (so something like 'root' should be excluded).
// Maybe use an attribute for this.
public static readonly Dictionary<Type, Action<World, Entity>> AddComponentByType = new()
{
{ typeof(LocalTransform), (world, entity) => world.Set(entity, LocalTransform.Identity) },
};
// Auto generate this, but also keep the option for 'custom inspectors'.
// Maybe via attribute?
public static readonly Dictionary<Type, Action<World, Entity>> ComponentInspectorByType = new()
{
{
typeof(LocalTransform), (world, entity) =>
{
(Vector3 position, Quaternion rotation, Vector3 scale) = world.Get<LocalTransform>(entity);
Vector3 eulerAngles = MathEx.ToEulerAngles(rotation);
eulerAngles = new Vector3(float.RadiansToDegrees(eulerAngles.X), float.RadiansToDegrees(eulerAngles.Y), float.RadiansToDegrees(eulerAngles.Z));
bool isDirty = false;
ImGui.BeginGroup();
ImGui.Text($"{nameof(LocalTransform)}");
isDirty |= ImGui.DragFloat3("Position", ref position, 0.2f, float.MinValue, float.MaxValue /*, "%f0 m" */); // TODO: right format.
isDirty |= ImGui.DragFloat3("Rotation", ref eulerAngles);
isDirty |= ImGui.DragFloat3("Scale", ref scale);
ImGui.EndGroup();
if (!isDirty)
{
return;
}
eulerAngles = new Vector3(float.DegreesToRadians(eulerAngles.X), float.DegreesToRadians(eulerAngles.Y), float.DegreesToRadians(eulerAngles.Z));
world.Set(entity, new LocalTransform(position, MathEx.ToQuaternion(eulerAngles), scale));
}
},
{
typeof(Root), (world, entity) =>
{
ImGui.BeginGroup();
ImGui.Text($"{nameof(Root)}");
ImGui.EndGroup();
}
},
};
}

View File

@ -1,3 +1,5 @@
using System.Numerics;
namespace Nerfed.Runtime;
public static class MathEx
@ -17,4 +19,51 @@ public static float Denormalize(float value, float newMin, float newMax) {
public static float Remap(float value, float oldMin, float oldMax, float newMin, float newMax) {
return (value - oldMin) / (oldMax - oldMin) * (newMax - newMin) + newMin;
}
// https://stackoverflow.com/questions/70462758/c-sharp-how-to-convert-quaternions-to-euler-angles-xyz
public static Quaternion ToQuaternion(Vector3 v)
{
float cy = (float)Math.Cos(v.Z * 0.5);
float sy = (float)Math.Sin(v.Z * 0.5);
float cp = (float)Math.Cos(v.Y * 0.5);
float sp = (float)Math.Sin(v.Y * 0.5);
float cr = (float)Math.Cos(v.X * 0.5);
float sr = (float)Math.Sin(v.X * 0.5);
return new Quaternion
{
W = (cr * cp * cy + sr * sp * sy),
X = (sr * cp * cy - cr * sp * sy),
Y = (cr * sp * cy + sr * cp * sy),
Z = (cr * cp * sy - sr * sp * cy),
};
}
public static Vector3 ToEulerAngles(Quaternion q)
{
Vector3 angles = new();
// roll / x
double sinrCosp = 2 * (q.W * q.X + q.Y * q.Z);
double cosrCosp = 1 - 2 * (q.X * q.X + q.Y * q.Y);
angles.X = (float)Math.Atan2(sinrCosp, cosrCosp);
// pitch / y
double sinp = 2 * (q.W * q.Y - q.Z * q.X);
if (Math.Abs(sinp) >= 1)
{
angles.Y = (float)Math.CopySign(Math.PI / 2, sinp);
}
else
{
angles.Y = (float)Math.Asin(sinp);
}
// yaw / z
double sinyCosp = 2 * (q.W * q.Z + q.X * q.Y);
double cosyCosp = 1 - 2 * (q.Y * q.Y + q.Z * q.Z);
angles.Z = (float)Math.Atan2(sinyCosp, cosyCosp);
return angles;
}
}

View File

@ -0,0 +1,13 @@
using System.Security.Cryptography;
namespace Nerfed.Runtime.Util;
public static class RandomId
{
public static uint GenerateSecureRandomUInt()
{
byte[] buffer = new byte[4];
RandomNumberGenerator.Fill(buffer);
return BitConverter.ToUInt32(buffer, 0);
}
}