diff --git a/Nerfed.Editor/Systems/EditorInspectorWindow.cs b/Nerfed.Editor/Systems/EditorInspectorWindow.cs index de899a6..5451031 100644 --- a/Nerfed.Editor/Systems/EditorInspectorWindow.cs +++ b/Nerfed.Editor/Systems/EditorInspectorWindow.cs @@ -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,19 +31,40 @@ public override void Update(TimeSpan delta) private void DrawEntityComponents(Entity entity) { - World.ComponentTypeEnumerator components = World.Debug_GetAllComponentTypes(entity); - - foreach (Type type in components) + World.ComponentTypeEnumerator componentTypes = World.Debug_GetAllComponentTypes(entity); + + ImGui.Text("ComponentInspectorByType"); + foreach (Type componentType in componentTypes) { - ImGui.Text(type.Name); + if (ComponentHelper.ComponentInspectorByType.TryGetValue(componentType, out Action componentInspector)) + { + componentInspector(World, entity); + } + else + { + ImGui.Text(componentType.Name); + } + ImGui.Separator(); } + + ImGui.Dummy(new Vector2(16, 16)); + + ImGui.Text("GetComponentByType"); + foreach (Type componentType in componentTypes) + { + if (!ComponentHelper.GetComponentByType.TryGetValue(componentType, out Func componentGetter)) continue; + ValueType component = componentGetter.Invoke(World, entity); + ImGui.Text(component.ToString()); + ImGui.Separator(); + } + + ImGui.Dummy(new Vector2(16, 16)); - ImGui.Separator(); - + ImGui.Text("Reflection"); // TODO: explore something without reflection. // Maybe generate some look up dictionary> for 'custom editors'. // Look into serializing of entities and components. - foreach (Type component in components) + foreach (Type component in componentTypes) { System.Reflection.MethodInfo getMethodInfo = typeof(World).GetMethod("Get"); System.Reflection.MethodInfo getComponentMethod = getMethodInfo.MakeGenericMethod(component); diff --git a/Nerfed.Runtime/Components/LocalTransform.cs b/Nerfed.Runtime/Components/LocalTransform.cs index bb0b810..79b2cd4 100644 --- a/Nerfed.Runtime/Components/LocalTransform.cs +++ b/Nerfed.Runtime/Components/LocalTransform.cs @@ -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); } } \ No newline at end of file diff --git a/Nerfed.Runtime/Serialization/ComponentHelper.cs b/Nerfed.Runtime/Serialization/ComponentHelper.cs new file mode 100644 index 0000000..c6349f7 --- /dev/null +++ b/Nerfed.Runtime/Serialization/ComponentHelper.cs @@ -0,0 +1,57 @@ +using System.Numerics; +using ImGuiNET; +using MoonTools.ECS; +using Nerfed.Runtime.Components; + +namespace Nerfed.Runtime.Serialization; + +public static class ComponentHelper +{ + public static readonly Dictionary> GetComponentByType = new() + { + { typeof(LocalTransform), (world, entity) => world.Get(entity) }, + { typeof(Root), (world, entity) => world.Get(entity) }, + }; + + public static readonly Dictionary> SetComponentByType = new() + { + { typeof(LocalTransform), (world, entity, component) => world.Set(entity, (LocalTransform)component) }, + { typeof(Root), (world, entity, component) => world.Set(entity, (Root)component) }, + }; + + public static readonly Dictionary> ComponentInspectorByType = new() + { + { + typeof(LocalTransform), (world, entity) => + { + (Vector3 position, Quaternion rotation, Vector3 scale) = world.Get(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(); + } + }, + }; +} \ No newline at end of file diff --git a/Nerfed.Runtime/Util/MathEx.cs b/Nerfed.Runtime/Util/MathEx.cs index fd411c5..fcf5c82 100644 --- a/Nerfed.Runtime/Util/MathEx.cs +++ b/Nerfed.Runtime/Util/MathEx.cs @@ -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; + } +} \ No newline at end of file