using System.Collections; using System.Collections.Generic; using Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core; [System.Diagnostics.DebuggerDisplay("Name: {GameObject.Name, nq} Position: {Position.ToString(), nq}, Scale: {Scale.ToString(), nq}, Rotation: {Rotation}")] public class Transform : ITransform { public event IAssignableGameObject.OnGameObjectAssignedDelegate? OnGameObjectAssigned = null; public event IAssignable.OnUnassignedDelegate? OnUnassigned = null; public event ITransform.OnPositionChangedDelegate? OnPositionChanged = null; public event ITransform.OnScaleChangedDelegate? OnScaleChanged = null; public event ITransform.OnRotationChangedDelegate? OnRotationChanged = null; public event ITransform.OnParentChangedDelegate? OnParentChanged = null; public event ITransform.OnChildrenAddedDelegate? OnChildrenAdded = null; public event ITransform.OnChildrenRemovedDelegate? OnChildrenRemoved = 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 IGameObject GameObject { get; private set; } = null!; public ITransform? Parent { get; private set; } = null; public IReadOnlyList Children => _children; public Vector2D Position { get => _position; set { if (value == _position) return; _position = value; UpdateLocalPosition(); OnPositionChanged?.Invoke(this); } } public Vector2D Scale { get => _localScale.Scale(Parent?.Scale ?? Vector2D.One); set { if (value == _scale) return; _scale = value; UpdateLocalScale(); OnScaleChanged?.Invoke(this); } } public float Rotation { get => _localRotation + (Parent?.Rotation ?? 0f); set { if (value == _rotation) 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 (previousParent is not null) { previousParent.RemoveChild(this); previousParent.OnPositionChanged -= RecalculatePosition; previousParent.OnScaleChanged -= RecalculateScale; previousParent.OnRotationChanged -= RecalculateRotation; previousParent.OnParentChanged -= NotifyChildrenOnParentChange; } Parent = transform; if (transform is not null) { transform.AddChild(this); transform.OnPositionChanged += RecalculatePosition; transform.OnScaleChanged += RecalculateScale; transform.OnRotationChanged += RecalculateRotation; transform.OnParentChanged += NotifyChildrenOnParentChange; } 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 NotifyChildrenOnParentChange(ITransform transform, ITransform? previousParent) { // TODO No idea how logical this is to propagate this to the children the way I'm doing right now. // I was originally gonna just call `child.OnParentChanged?.Invoke(child, child.Parent);` but seems an unnecessary call too? foreach (var child in Children) // TODO CHECK ERRORS child.SetParent(this); } 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.Rotate(0f * Math.DegreeToRadian); else _position = Parent.Position + LocalPosition.Scale(new(Parent.Scale.X, 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; } public bool Assign(IGameObject gameObject) { if (GameObject is not null && GameObject.IsInitialized) return false; GameObject = gameObject; OnGameObjectAssigned?.Invoke(this); return true; } public bool Unassign() { if (GameObject is not null && GameObject.IsInitialized) return false; GameObject = null!; OnUnassigned?.Invoke(this); return true; } }