diff --git a/.gitmodules b/.gitmodules index 63fa691..4531d43 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "Nerfed.Runtime/Libraries/ImGui.NET"] path = Nerfed.Runtime/Libraries/ImGui.NET url = https://github.com/ImGuiNET/ImGui.NET.git +[submodule "Nerfed.Runtime/Libraries/ECS"] + path = Nerfed.Runtime/Libraries/ECS + url = https://github.com/MoonsideGames/MoonTools.ECS.git diff --git a/Nerfed.Editor/Program.cs b/Nerfed.Editor/Program.cs index 6b21d4a..28f6de0 100644 --- a/Nerfed.Editor/Program.cs +++ b/Nerfed.Editor/Program.cs @@ -1,9 +1,15 @@ -using Nerfed.Runtime; +using MoonTools.ECS; +using Nerfed.Runtime; +using Nerfed.Runtime.Systems; +using System.Numerics; namespace Nerfed.Editor; internal class Program { + private static readonly World world = new World(); + private static List systems = new List(); + private static void Main(string[] args) { Engine.OnInitialize += HandleOnInitialize; @@ -16,6 +22,16 @@ private static void Main(string[] args) private static void HandleOnInitialize() { + systems.Add(new ParentSystem(world)); + systems.Add(new TransformSystem(world)); + + Entity ent1 = world.CreateEntity(); + world.Set(ent1, new LocalTransform(new Vector3(1, 0, 0), Quaternion.Identity, Vector3.One)); + + Entity ent2 = world.CreateEntity(); + world.Set(ent2, new LocalTransform(new Vector3(0, 1, 0), Quaternion.Identity, Vector3.One)); + world.Set(ent2, new Parent(ent1)); + // Open project. // Setip EditorGui. EditorGui.Initialize(); @@ -23,6 +39,11 @@ private static void HandleOnInitialize() private static void HandleOnUpdate() { + foreach (MoonTools.ECS.System system in systems) + { + system.Update(Engine.Timestep); + } + // Editor Update. EditorGui.Update(); diff --git a/Nerfed.Runtime/Components/LocalToWorld.cs b/Nerfed.Runtime/Components/LocalToWorld.cs new file mode 100644 index 0000000..a3f3fea --- /dev/null +++ b/Nerfed.Runtime/Components/LocalToWorld.cs @@ -0,0 +1,6 @@ +using System.Numerics; + +namespace Nerfed.Runtime.Components +{ + public readonly record struct LocalToWorld(Matrix4x4 localToWorldMatrix); +} \ No newline at end of file diff --git a/Nerfed.Runtime/Components/LocalTransform.cs b/Nerfed.Runtime/Components/LocalTransform.cs new file mode 100644 index 0000000..f686c50 --- /dev/null +++ b/Nerfed.Runtime/Components/LocalTransform.cs @@ -0,0 +1,18 @@ +using System.Numerics; + +namespace Nerfed.Runtime.Components +{ + public struct LocalTransform + { + public Vector3 position; + public Quaternion rotation; + public Vector3 scale; + + public LocalTransform(Vector3 position, Quaternion rotation, Vector3 scale) + { + this.position = position; + this.rotation = rotation; + this.scale = scale; + } + } +} \ No newline at end of file diff --git a/Nerfed.Runtime/Components/Parent.cs b/Nerfed.Runtime/Components/Parent.cs new file mode 100644 index 0000000..bc65061 --- /dev/null +++ b/Nerfed.Runtime/Components/Parent.cs @@ -0,0 +1,8 @@ +using MoonTools.ECS; + +namespace Nerfed.Runtime.Components +{ + public readonly record struct Parent(Entity parentEntity); + public readonly record struct PreviousParent(Entity parentEntity); + public readonly record struct ChildRelation; +} \ No newline at end of file diff --git a/Nerfed.Runtime/Libraries/ECS b/Nerfed.Runtime/Libraries/ECS new file mode 160000 index 0000000..76b18a6 --- /dev/null +++ b/Nerfed.Runtime/Libraries/ECS @@ -0,0 +1 @@ +Subproject commit 76b18a6ba9a33f5a93022390be7ed805f9f722e8 diff --git a/Nerfed.Runtime/Nerfed.Runtime.csproj b/Nerfed.Runtime/Nerfed.Runtime.csproj index 01615fb..f493dad 100644 --- a/Nerfed.Runtime/Nerfed.Runtime.csproj +++ b/Nerfed.Runtime/Nerfed.Runtime.csproj @@ -32,12 +32,13 @@ - - - - - - + + + + + + + \ No newline at end of file diff --git a/Nerfed.Runtime/Systems/ParentSystem.cs b/Nerfed.Runtime/Systems/ParentSystem.cs new file mode 100644 index 0000000..0aed0ad --- /dev/null +++ b/Nerfed.Runtime/Systems/ParentSystem.cs @@ -0,0 +1,62 @@ +using MoonTools.ECS; +using Nerfed.Runtime.Components; + +namespace Nerfed.Runtime.Systems +{ + 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 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); + + 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()); + } + + // 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()); + } + } + } + } +} \ No newline at end of file diff --git a/Nerfed.Runtime/Systems/TransformSystem.cs b/Nerfed.Runtime/Systems/TransformSystem.cs new file mode 100644 index 0000000..4d5fc8b --- /dev/null +++ b/Nerfed.Runtime/Systems/TransformSystem.cs @@ -0,0 +1,47 @@ +using MoonTools.ECS; +using Nerfed.Runtime.Components; +using Nerfed.Runtime.Util; +using System.Numerics; + +namespace Nerfed.Runtime.Systems +{ + public class TransformSystem : MoonTools.ECS.System + { + private readonly Filter rootEntitiesFilter; + + public TransformSystem(World world) : base(world) + { + rootEntitiesFilter = FilterBuilder.Include().Exclude().Build(); + } + + public override void Update(TimeSpan delta) + { + Matrix4x4 rootMatrix = Matrix4x4.Identity; + + foreach (Entity entity in rootEntitiesFilter.Entities) + { + UpdateWorldTransform(entity, rootMatrix); + } + } + + private void UpdateWorldTransform(in Entity entity, in Matrix4x4 parentLocalToWorld) + { + // TODO: Only update dirty transforms. + // Maybe store the local transform matrix. + // If something is dirty all the children need to update their localToWorld matrix. + + LocalTransform localTransform = Get(entity); + Matrix4x4 localToWorldMatrix = Matrix4x4.Multiply(parentLocalToWorld, localTransform.TRS()); + LocalToWorld localToWorld = new(localToWorldMatrix); + Set(entity, localToWorld); + + Log.Info($"Entity {entity} | local position {localTransform.position} | world position {localToWorldMatrix.Translation}"); + + ReverseSpanEnumerator childEntities = World.OutRelations(entity); + foreach (Entity childEntity in childEntities) + { + UpdateWorldTransform(childEntity, localToWorldMatrix); + } + } + } +} \ No newline at end of file diff --git a/Nerfed.Runtime/Util/Transform.cs b/Nerfed.Runtime/Util/Transform.cs new file mode 100644 index 0000000..6885538 --- /dev/null +++ b/Nerfed.Runtime/Util/Transform.cs @@ -0,0 +1,25 @@ +using Nerfed.Runtime.Components; +using System.Numerics; + +namespace Nerfed.Runtime.Util +{ + // https://github.com/needle-mirror/com.unity.entities/blob/master/Unity.Transforms/TransformHelpers.cs + internal 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(); + public static Vector3 Up(in this Matrix4x4 matrix) => new Vector3(matrix.M21, matrix.M22, matrix.M23); + public static Vector3 Down(in this Matrix4x4 matrix) => -matrix.Up(); + public static Vector3 Right(in this Matrix4x4 matrix) => new Vector3(matrix.M11, matrix.M12, matrix.M13); + public static Vector3 Left(in this Matrix4x4 matrix) => -matrix.Right(); + //public static Vector3 Translation(in this Matrix4x4 matrix) => new Vector3(); + //public static Quaternion Rotation(in this Matrix4x4 matrix) => new Quaternion(); + + public static Matrix4x4 TRS(in this LocalTransform localTransform) + { + return Matrix4x4.CreateScale(localTransform.scale) * + Matrix4x4.CreateFromQuaternion(localTransform.rotation) * + Matrix4x4.CreateTranslation(localTransform.position); + } + } +} \ No newline at end of file