diff --git a/Engine.Core/Abstract/ITransform.cs b/Engine.Core/Abstract/ITransform.cs index f97cc17..724bc4a 100644 --- a/Engine.Core/Abstract/ITransform.cs +++ b/Engine.Core/Abstract/ITransform.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; namespace Syntriax.Engine.Core.Abstract; /// /// Represents the transformation properties of an object such as position, scale, and rotation. /// -public interface ITransform +public interface ITransform : IEnumerable { /// /// Event triggered when the of the changes. @@ -23,17 +24,61 @@ public interface ITransform Action? OnRotationChanged { get; set; } /// - /// The position of the in 2D space. + /// Event triggered when the of the changes. The second parameter is the old . + /// + Action? OnParentChanged { get; set; } + + /// + /// Event triggered when a new is added to the . + /// + Action? OnChildrenAdded { get; set; } + + /// + /// Event triggered when an is removed from the . + /// + Action? OnChildrenRemoved { get; set; } + + /// + /// The world position of the in 2D space. /// Vector2D Position { get; set; } /// - /// The scale of the . + /// The world scale of the . /// Vector2D Scale { get; set; } /// - /// The rotation of the in degrees. + /// The world rotation of the in degrees. /// float Rotation { get; set; } + + /// + /// The local position of the in 2D space. + /// + Vector2D LocalPosition { get; set; } + + /// + /// The local scale of the . + /// + Vector2D LocalScale { get; set; } + + /// + /// The local rotation of the in degrees. + /// + float LocalRotation { get; set; } + + /// + /// The parent of the . + /// + ITransform? Parent { get; } + + /// + /// The s that have this as their . + /// + IReadOnlyList Children { get; } + + void SetParent(ITransform? transform); + void AddChild(ITransform transform); + void RemoveChild(ITransform transform); } diff --git a/Engine.Core/Extensions/TransformExtensions.cs b/Engine.Core/Extensions/TransformExtensions.cs index 6925554..5c1da4f 100644 --- a/Engine.Core/Extensions/TransformExtensions.cs +++ b/Engine.Core/Extensions/TransformExtensions.cs @@ -4,11 +4,16 @@ namespace Syntriax.Engine.Core; public static class TransformExtensions { - public static ITransform SetTransform(this ITransform transform, Vector2D? position = null, float? rotation = null, Vector2D? scale = null) + public static ITransform SetTransform(this ITransform transform, + Vector2D? position = null, float? rotation = null, Vector2D? scale = null, + Vector2D? localPosition = null, float? localRotation = null, Vector2D? localScale = null) { if (position.HasValue) transform.Position = position.Value; if (rotation.HasValue) transform.Rotation = rotation.Value; if (scale.HasValue) transform.Scale = scale.Value; + if (localPosition.HasValue) transform.LocalPosition = localPosition.Value; + if (localRotation.HasValue) transform.LocalRotation = localRotation.Value; + if (localScale.HasValue) transform.LocalScale = localScale.Value; return transform; } } diff --git a/Engine.Core/Transform.cs b/Engine.Core/Transform.cs index cc0c32e..4a0c54d 100644 --- a/Engine.Core/Transform.cs +++ b/Engine.Core/Transform.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Collections.Generic; using Syntriax.Engine.Core.Abstract; @@ -11,10 +13,25 @@ public class Transform : ITransform public Action? OnScaleChanged { get; set; } = null; public Action? OnRotationChanged { get; set; } = null; + public Action? OnParentChanged { get; set; } = null; + public Action? OnChildrenAdded { get; set; } = null; + public Action? OnChildrenRemoved { get; set; } = null; + + private Vector2D _position = Vector2D.Zero; private Vector2D _scale = Vector2D.One; private float _rotation = 0f; + private Vector2D _localPosition = Vector2D.Zero; + private Vector2D _localScale = Vector2D.One; + private float _localRotation = 0f; + + private readonly List _children = []; + + public ITransform? Parent { get; private set; } = null; + + public IReadOnlyList Children => _children; + public Vector2D Position { get => _position; @@ -24,6 +41,7 @@ public class Transform : ITransform return; _position = value; + UpdateLocalPosition(); OnPositionChanged?.Invoke(this); } } @@ -37,6 +55,7 @@ public class Transform : ITransform return; _scale = value; + UpdateLocalScale(); OnScaleChanged?.Invoke(this); } } @@ -50,7 +69,180 @@ public class Transform : ITransform return; _rotation = value; + UpdateLocalPosition(); OnRotationChanged?.Invoke(this); } } + + public Vector2D LocalPosition + { + get => _localPosition; + set + { + if (value == _localPosition) + return; + + _localPosition = value; + UpdatePosition(); + OnPositionChanged?.Invoke(this); + } + } + + public Vector2D LocalScale + { + get => _localScale; + set + { + if (value == _localScale) + return; + + _localScale = value; + UpdateScale(); + OnScaleChanged?.Invoke(this); + } + } + + public float LocalRotation + { + get => _localRotation; + set + { + if (value == _localRotation) + return; + + _localRotation = value; + UpdateRotation(); + OnRotationChanged?.Invoke(this); + } + } + + public void SetParent(ITransform? transform) + { + if (transform == this || Parent == transform) + return; + + ITransform? previousParent = Parent; + if (Parent is not null) + { + Parent.RemoveChild(this); + Parent.OnPositionChanged -= RecalculatePosition; + Parent.OnScaleChanged -= RecalculateScale; + Parent.OnRotationChanged -= RecalculateRotation; + } + + Parent = transform; + + if (transform is not null) + { + transform.AddChild(this); + transform.OnPositionChanged += RecalculatePosition; + transform.OnScaleChanged += RecalculateScale; + transform.OnRotationChanged += RecalculateRotation; + } + + UpdateLocalPosition(); + UpdateLocalScale(); + UpdateLocalRotation(); + + OnParentChanged?.Invoke(this, previousParent); + } + + public void AddChild(ITransform transform) + { + if (_children.Contains(transform)) + return; + + _children.Add(transform); + transform.SetParent(this); + OnChildrenAdded?.Invoke(this, transform); + } + + public void RemoveChild(ITransform transform) + { + if (!_children.Remove(transform)) + return; + + transform.SetParent(null); + OnChildrenRemoved?.Invoke(this, transform); + } + + public IEnumerator GetEnumerator() => _children.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _children.GetEnumerator(); + + private void RecalculatePosition(ITransform _) + { + if (Parent is null) + return; + + UpdatePosition(); + OnPositionChanged?.Invoke(this); + } + + private void RecalculateScale(ITransform _) + { + if (Parent is null) + return; + + UpdateScale(); + OnScaleChanged?.Invoke(this); + } + + private void RecalculateRotation(ITransform _) + { + if (Parent is null) + return; + + UpdatePosition(); + UpdateRotation(); + OnPositionChanged?.Invoke(this); + OnRotationChanged?.Invoke(this); + } + + private void UpdateLocalPosition() + { + if (Parent is null) + _localPosition = Position; + else + _localPosition = Parent.Position.FromTo(Position).Scale(Parent.Scale); + } + + private void UpdateLocalScale() + { + if (Parent is null) + _localScale = Scale; + else + _localScale = Scale.Scale(new(1f / Parent.Scale.X, 1f / Parent.Scale.Y)); + } + + private void UpdateLocalRotation() + { + if (Parent is null) + _localRotation = Rotation; + else + _localRotation = Rotation - Parent.Rotation; + } + + private void UpdatePosition() + { + if (Parent is null) + _position = LocalPosition.Scale(Vector2D.One).Rotate(0f * Math.DegreeToRadian); + else + _position = Parent.Position + LocalPosition.Scale(new(1f / Parent.Scale.X, 1f / Parent.Scale.Y)).Rotate(Parent.Rotation * Math.DegreeToRadian); + } + + private void UpdateScale() + { + if (Parent is null) + _scale = LocalScale; + else + _scale = Vector2D.Scale(Parent.Scale, LocalScale); + } + + private void UpdateRotation() + { + if (Parent is null) + _rotation = LocalRotation; + else + _rotation = Parent.Rotation + LocalRotation; + } }