diff --git a/Nerfed.Editor/Editor/EditorGui.cs b/Nerfed.Editor/Editor/EditorGui.cs index cb14a5d..9c9cce0 100644 --- a/Nerfed.Editor/Editor/EditorGui.cs +++ b/Nerfed.Editor/Editor/EditorGui.cs @@ -66,6 +66,11 @@ private static void HandleOnGui() UpdateDock(); ImGui.ShowDemoWindow(); + + foreach (MoonTools.ECS.System system in Program.editorSystems) + { + system.Update(Engine.Timestep); + } } } } \ No newline at end of file diff --git a/Nerfed.Editor/Program.cs b/Nerfed.Editor/Program.cs index b9459e7..caefa0c 100644 --- a/Nerfed.Editor/Program.cs +++ b/Nerfed.Editor/Program.cs @@ -1,7 +1,9 @@ using MoonTools.ECS; +using Nerfed.Editor.Systems; using Nerfed.Runtime; using Nerfed.Runtime.Components; using Nerfed.Runtime.Systems; +using Nerfed.Runtime.Util; using System.Numerics; namespace Nerfed.Editor; @@ -10,6 +12,7 @@ internal class Program { private static readonly World world = new World(); private static List systems = new List(); + public static List editorSystems = new List(); private static void Main(string[] args) { @@ -23,15 +26,25 @@ private static void Main(string[] args) private static void HandleOnInitialize() { - systems.Add(new ParentSystem(world)); + //systems.Add(new ParentSystem(world)); systems.Add(new LocalToWorldSystem(world)); + editorSystems.Add(new EditorHierarchyWindow(world)); - Entity ent1 = world.CreateEntity(); + Entity ent1 = world.CreateEntity("parent"); world.Set(ent1, new LocalTransform(new Vector3(1, 0, 0), Quaternion.Identity, Vector3.One)); - Entity ent2 = world.CreateEntity(); + Entity ent2 = world.CreateEntity("child"); world.Set(ent2, new LocalTransform(new Vector3(0, 1, 0), Quaternion.Identity, Vector3.One)); - world.Set(ent2, new Parent(ent1)); + Transform.SetParent(world, ent2, ent1); + + Entity ent3 = world.CreateEntity("entity3"); + world.Set(ent3, new Root()); + Transform.SetParent(world, ent3, ent2); + + Entity ent4 = world.CreateEntity("entity4"); + world.Set(ent4, new Root()); + + Entity ent5 = world.CreateBaseEntity("entity5"); // Open project. // Setip EditorGui. @@ -50,6 +63,8 @@ private static void HandleOnUpdate() // Try Catch UserCode Update. + + world.FinishUpdate(); } private static void HandleOnRender() diff --git a/Nerfed.Editor/Systems/EditorHierarchyWindow.cs b/Nerfed.Editor/Systems/EditorHierarchyWindow.cs new file mode 100644 index 0000000..c44e513 --- /dev/null +++ b/Nerfed.Editor/Systems/EditorHierarchyWindow.cs @@ -0,0 +1,60 @@ +using ImGuiNET; +using MoonTools.ECS; +using Nerfed.Runtime.Components; + +namespace Nerfed.Editor.Systems +{ + internal class EditorHierarchyWindow : MoonTools.ECS.DebugSystem + { + private readonly Filter rootEntitiesWithTransformFilter; + private readonly Filter rootEntitiesFilterBroken; + private readonly Filter rootEntitiesFilter; + + public EditorHierarchyWindow(World world) : base(world) + { + rootEntitiesWithTransformFilter = FilterBuilder.Include().Exclude().Build(); + + // TODO: this doesn't work. + rootEntitiesFilterBroken = FilterBuilder.Exclude().Build(); + + // Maybe the parent/child functions should add a root component when not being a child. + rootEntitiesFilter = FilterBuilder.Include().Build(); + + // Maybe instead of a root, if we need a component that is always on an entity and has some use we could create something like a VersionComponent which only hold an int. + // The version would update each time something changes on the entity. + // + } + + public override void Update(TimeSpan delta) + { + ImGui.Begin("Hierarchy"); + + foreach (Entity entity in rootEntitiesWithTransformFilter.Entities) + { + DrawEntityAndChildren(entity); + } + + ImGui.NewLine(); + + foreach (Entity entity in rootEntitiesFilter.Entities) + { + DrawEntityAndChildren(entity); + } + + ImGui.End(); + } + + private void DrawEntityAndChildren(in Entity entity, int depth = 0) + { + string label = new string(' ', depth); + ImGui.Text($"{label}{entity.ID} | {GetTag(entity)}"); + + depth++; + ReverseSpanEnumerator childEntities = World.InRelations(entity); + foreach (Entity childEntity in childEntities) + { + DrawEntityAndChildren(childEntity, depth); + } + } + } +} diff --git a/Nerfed.Runtime/Components/Parent.cs b/Nerfed.Runtime/Components/Parent.cs index bc65061..37d222c 100644 --- a/Nerfed.Runtime/Components/Parent.cs +++ b/Nerfed.Runtime/Components/Parent.cs @@ -2,7 +2,10 @@ namespace Nerfed.Runtime.Components { - public readonly record struct Parent(Entity parentEntity); - public readonly record struct PreviousParent(Entity parentEntity); - public readonly record struct ChildRelation; + public readonly record struct Root; + //public readonly record struct Parent; + //public readonly record struct PreviousParent; + public readonly record struct Child; + // Describes a relation from the child to the parent. + public readonly record struct ChildParentRelation; } \ No newline at end of file diff --git a/Nerfed.Runtime/Components/Test.cs b/Nerfed.Runtime/Components/Test.cs new file mode 100644 index 0000000..9cd7e8d --- /dev/null +++ b/Nerfed.Runtime/Components/Test.cs @@ -0,0 +1,4 @@ +namespace Nerfed.Runtime.Components +{ + public readonly record struct Test(); +} diff --git a/Nerfed.Runtime/Systems/LocalToWorldSystem.cs b/Nerfed.Runtime/Systems/LocalToWorldSystem.cs index 741c703..ac48642 100644 --- a/Nerfed.Runtime/Systems/LocalToWorldSystem.cs +++ b/Nerfed.Runtime/Systems/LocalToWorldSystem.cs @@ -2,6 +2,7 @@ using Nerfed.Runtime.Components; using Nerfed.Runtime.Util; using System.Numerics; +using static System.Runtime.InteropServices.JavaScript.JSType; // TODO: // Explore if having a WorldTransform and LocalTransfom component each holding position, rotation, scale values and the matricies is useful. @@ -19,7 +20,7 @@ public class LocalToWorldSystem : MoonTools.ECS.System public LocalToWorldSystem(World world) : base(world) { - rootEntitiesFilter = FilterBuilder.Include().Exclude().Build(); + rootEntitiesFilter = FilterBuilder.Include().Exclude().Build(); } public override void Update(TimeSpan delta) @@ -32,20 +33,23 @@ public override void Update(TimeSpan delta) } } - private void UpdateWorldTransform(in Entity entity, in Matrix4x4 parentLocalToWorld) + private void UpdateWorldTransform(in Entity entity, Matrix4x4 localToWorldMatrix) { // TODO: Only update dirty transforms. // If a parent is dirty all the children need to update their localToWorld matrix. // How do we check if something is dirty? How do we know if a LocalTransform has been changed? - LocalTransform localTransform = Get(entity); - Matrix4x4 localToWorldMatrix = Matrix4x4.Multiply(parentLocalToWorld, localTransform.TRS()); - LocalToWorld localToWorld = new(localToWorldMatrix); - Set(entity, localToWorld); + if (Has(entity)) + { + LocalTransform localTransform = Get(entity); + localToWorldMatrix = Matrix4x4.Multiply(localToWorldMatrix, localTransform.TRS()); + LocalToWorld localToWorld = new(localToWorldMatrix); + Set(entity, localToWorld); - Log.Info($"Entity {entity} | local position {localTransform.position} | world position {localToWorldMatrix.Translation}"); + Log.Info($"Entity {entity} | local position {localTransform.position} | world position {localToWorldMatrix.Translation}"); + } - ReverseSpanEnumerator childEntities = World.OutRelations(entity); + ReverseSpanEnumerator childEntities = World.OutRelations(entity); foreach (Entity childEntity in childEntities) { UpdateWorldTransform(childEntity, localToWorldMatrix); diff --git a/Nerfed.Runtime/Systems/ParentSystem.cs b/Nerfed.Runtime/Systems/ParentSystem.cs index 0aed0ad..1cbf794 100644 --- a/Nerfed.Runtime/Systems/ParentSystem.cs +++ b/Nerfed.Runtime/Systems/ParentSystem.cs @@ -3,60 +3,65 @@ namespace Nerfed.Runtime.Systems { - public class ParentSystem : MoonTools.ECS.System - { - private readonly Filter parentsAddedFilter; - private readonly Filter parentsRemovedFilter; - private readonly Filter parentsFilter; + //public class ParentSystem : MoonTools.ECS.System + //{ + // private readonly Filter parentsAddedFilter; + // private readonly Filter parentsRemovedFilter; + // private readonly Filter parentsFilter; - public ParentSystem(World world) : base(world) - { - parentsAddedFilter = FilterBuilder.Include().Exclude().Build(); - parentsRemovedFilter = FilterBuilder.Include().Exclude().Build(); - parentsFilter = FilterBuilder.Include().Include().Build(); - } + // public ParentSystem(World world) : base(world) + // { + // parentsAddedFilter = FilterBuilder.Include().Exclude().Build(); + // parentsRemovedFilter = FilterBuilder.Include().Exclude().Build(); + // parentsFilter = FilterBuilder.Include().Include().Build(); + // } - public override void Update(TimeSpan delta) - { - // Update removed parents. - foreach (Entity entity in parentsRemovedFilter.Entities) - { - // Do stuff here to update/remove child relations etc. - PreviousParent previousParent = Get(entity); - World.Unrelate(previousParent.parentEntity, entity); - Remove(entity); - } + // public override void Update(TimeSpan delta) + // { + // // Update removed parents. + // foreach (Entity entity in parentsRemovedFilter.Entities) + // { + // // Do stuff here to update/remove child relations etc. + // //PreviousParent previousParent = Get(entity); + // //World.Unrelate(previousParent.parentEntity, entity); + // Remove(entity); + // } - // Update added parents. - foreach (Entity entity in parentsAddedFilter.Entities) - { - Parent parent = Get(entity); + // // Update added parents. + // foreach (Entity entity in parentsAddedFilter.Entities) + // { + // Parent parent = Get(entity); - if (Has(parent.parentEntity) && Get(parent.parentEntity).parentEntity == entity) - { - Log.Warning($"Entity {entity} cannot be a parent of entity {parent.parentEntity}, because {parent.parentEntity} is the parent of {entity}"); - Remove(entity); - continue; - } + // if (Has(parent.parentEntity) && Get(parent.parentEntity).parentEntity == entity) + // { + // Log.Warning($"Entity {entity} cannot be a parent of entity {parent.parentEntity}, because {parent.parentEntity} is the parent of {entity}"); + // Remove(entity); + // continue; + // } - PreviousParent previousParent = new(parent.parentEntity); - Set(entity, previousParent); - World.Relate(parent.parentEntity, entity, new ChildRelation()); - } + // PreviousParent previousParent = new(parent.parentEntity); + // Set(entity, previousParent); + // World.Relate(parent.parentEntity, entity, new ChildParentRelation()); + // } - // Update relations if the parent has changed. - foreach (Entity entity in parentsFilter.Entities) - { - Parent parent = Get(entity); - PreviousParent previousParent = Get(entity); + // // Update relations if the parent has changed. + // foreach (Entity entity in parentsFilter.Entities) + // { + // Parent parent = Get(entity); + // PreviousParent previousParent = Get(entity); - if(parent.parentEntity != previousParent.parentEntity) - { - World.Unrelate(previousParent.parentEntity, entity); - Set(entity, new PreviousParent(parent.parentEntity)); - World.Relate(parent.parentEntity, entity, new ChildRelation()); - } - } - } - } + // if(parent.parentEntity != previousParent.parentEntity) + // { + // World.Unrelate(previousParent.parentEntity, entity); + // Set(entity, new PreviousParent(parent.parentEntity)); + // World.Relate(parent.parentEntity, entity, new ChildParentRelation()); + // } + // } + + // // TODO: + // // What if an parent entity gets destroyed? + // // How does the child know if the parent is in valid. Also we need to remove the parent component. + // // Maybe if we also relate the other way around child -> parent via relations, and the relation is gone that means the parent is gone so we should remove the component. + // } + //} } \ No newline at end of file diff --git a/Nerfed.Runtime/Util/Transform.cs b/Nerfed.Runtime/Util/Transform.cs index d4d83be..2dc0764 100644 --- a/Nerfed.Runtime/Util/Transform.cs +++ b/Nerfed.Runtime/Util/Transform.cs @@ -6,7 +6,7 @@ namespace Nerfed.Runtime.Util { // https://github.com/needle-mirror/com.unity.entities/blob/master/Unity.Transforms/TransformHelpers.cs - internal static class Transform + public static class Transform { public static Vector3 Forward(in this Matrix4x4 matrix) => new Vector3(matrix.M31, matrix.M32, matrix.M33); public static Vector3 Back(in this Matrix4x4 matrix) => -matrix.Forward(); @@ -24,34 +24,76 @@ public static Matrix4x4 TRS(in this LocalTransform localTransform) Matrix4x4.CreateTranslation(localTransform.position); } + // Sets the parent child relation and adds a child component. + // Relation goes from child to parent. + public static void SetParent(in World world, in Entity child, in Entity parent) + { + if (world.Related(parent, child)) + { + RemoveParent(world, parent); + } + + world.Relate(child, parent, new ChildParentRelation()); + world.Set(child, new Child()); + world.Remove(child); + return; + } + + // Removes any parent child relation ship, thus making it a 'root' object. + public static void RemoveParent(in World world, in Entity child) + { + if (!world.HasOutRelation(child)) + { + return; + } + + // TODO: Check if Unrelate all also unrelates incomming relations..? + world.UnrelateAll(child); + world.Remove(child); + world.Set(child, new Root()); + } + + public static Entity CreateBaseEntity(this World world, string tag = "") + { + Entity entity = world.CreateEntity(tag); + world.Set(entity, new Root()); + return entity; + } + // Force update the transform data of an entity (and children). // Useful for when you need precise up to date transform data. public static void ForceUpdateLocalToWorld(in World world, in Entity entity) { Matrix4x4 parentLocalToWorldMatrix = Matrix4x4.Identity; - if (world.Has(entity)) - { - Entity parent = world.Get(entity).parentEntity; - parentLocalToWorldMatrix = world.Get(parent).localToWorldMatrix; + if (world.HasOutRelation(entity)) { + Entity parent = world.OutRelationSingleton(entity); + + if (world.Has(parent)) + { + parentLocalToWorldMatrix = world.Get(parent).localToWorldMatrix; + } } ForceUpdateLocalToWorld(world, entity, parentLocalToWorldMatrix); } - private static void ForceUpdateLocalToWorld(in World world, in Entity entity, in Matrix4x4 parentLocalToWorldMatrix) + private static void ForceUpdateLocalToWorld(in World world, in Entity entity, Matrix4x4 localToWorldMatrix) { - LocalTransform localTransform = world.Get(entity); - Matrix4x4 localToWorldMatrix = Matrix4x4.Multiply(parentLocalToWorldMatrix, localTransform.TRS()); - LocalToWorld localToWorld = new(localToWorldMatrix); - world.Set(entity, localToWorld); + if (world.Has(entity)) + { + LocalTransform localTransform = world.Get(entity); + localToWorldMatrix = Matrix4x4.Multiply(localToWorldMatrix, localTransform.TRS()); + LocalToWorld localToWorld = new(localToWorldMatrix); + world.Set(entity, localToWorld); - Log.Info($"Entity {entity} | local position {localTransform.position} | world position {localToWorldMatrix.Translation}"); + Log.Info($"Entity {entity} | local position {localTransform.position} | world position {localToWorldMatrix.Translation}"); + } - ReverseSpanEnumerator childEntities = world.OutRelations(entity); + ReverseSpanEnumerator childEntities = world.InRelations(entity); foreach (Entity childEntity in childEntities) { - ForceUpdateLocalToWorld(world, childEntity, parentLocalToWorldMatrix); + ForceUpdateLocalToWorld(world, childEntity, localToWorldMatrix); } } }