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;
+ }
}