using MoonTools.ECS; using Nerfed.Runtime.Components; using Nerfed.Runtime.Util; using System.Numerics; // TODO: // Explore if having a WorldTransform and LocalTransfom component each holding position, rotation, scale values and the matricies is useful. // Often you need to either get or set these values. // If so, we probably need a utility funciton to do so. Since changing these values means that we need to update all the related data + children as well. // TODO: // When modifying transform all the children need to be updated as well. namespace Nerfed.Runtime.Systems { public class LocalToWorldSystem : MoonTools.ECS.System { private readonly bool useParallelFor = true; // When having a low amount of transforms or when in debug mode this might be slower. private readonly Filter rootEntitiesFilter; private readonly Filter transformEntitiesFilter; private readonly Action updateWorldTransform; public LocalToWorldSystem(World world) : base(world) { rootEntitiesFilter = FilterBuilder.Include().Exclude().Build(); if (useParallelFor) { transformEntitiesFilter = FilterBuilder.Include().Build(); updateWorldTransform = UpdateWorldTransformByIndex; } } public override void Update(TimeSpan delta) { if (rootEntitiesFilter.Empty) { return; } if (useParallelFor) { Profiler.BeginSample("ParallelFor.LocalToWorldCheck"); // This check is needed because some entities might not have a LocalToWorld component yet. // Adding this during the loop will break. foreach (Entity entity in transformEntitiesFilter.Entities) { if (Has(entity)) { continue; } Set(entity, new LocalToWorld(Matrix4x4.Identity)); } Profiler.EndSample(); Profiler.BeginSample("ParallelFor.LocalToWorldUpdate"); // This should only be used when the filter doesn't change by executing these functions! // So no entity deletion or setting/removing of components used by the filters in this loop. Parallel.For(0, rootEntitiesFilter.Count, updateWorldTransform); Profiler.EndSample(); } else { foreach (Entity entity in rootEntitiesFilter.Entities) { Profiler.BeginSample("UpdateWorldTransform"); UpdateWorldTransform(entity, Matrix4x4.Identity); Profiler.EndSample(); } } } private void UpdateWorldTransformByIndex(int entityFilterIndex) { Profiler.BeginSample("UpdateWorldTransformByIndex"); Entity entity = rootEntitiesFilter.NthEntity(entityFilterIndex); UpdateWorldTransform(entity, Matrix4x4.Identity); Profiler.EndSample(); } 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? if (Has(entity)) { LocalTransform localTransform = Get(entity); localToWorldMatrix = Matrix4x4.Multiply(localToWorldMatrix, localTransform.TRS()); LocalToWorld localToWorld = new(localToWorldMatrix); Set(entity, localToWorld); } ReverseSpanEnumerator childEntities = World.InRelations(entity); foreach (Entity childEntity in childEntities) { UpdateWorldTransform(childEntity, localToWorldMatrix); } } } }