284 lines
7.5 KiB
C#
284 lines
7.5 KiB
C#
using System;
|
|
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 Action<IAssignableGameObject>? OnGameObjectAssigned { get; set; } = null;
|
|
|
|
public Action<IAssignable>? OnUnassigned { get; set; } = null;
|
|
|
|
public Action<ITransform>? OnPositionChanged { get; set; } = null;
|
|
public Action<ITransform>? OnScaleChanged { get; set; } = null;
|
|
public Action<ITransform>? OnRotationChanged { get; set; } = null;
|
|
|
|
public Action<ITransform, ITransform?>? OnParentChanged { get; set; } = null;
|
|
public Action<ITransform, ITransform>? OnChildrenAdded { get; set; } = null;
|
|
public Action<ITransform, ITransform>? 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<ITransform> _children = [];
|
|
|
|
public IGameObject GameObject { get; private set; } = null!;
|
|
public ITransform? Parent { get; private set; } = null;
|
|
|
|
public IReadOnlyList<ITransform> Children => _children;
|
|
|
|
public Vector2D Position
|
|
{
|
|
get => _position;
|
|
set
|
|
{
|
|
if (value == _position)
|
|
return;
|
|
|
|
_position = value;
|
|
UpdateLocalPosition();
|
|
OnPositionChanged?.Invoke(this);
|
|
}
|
|
}
|
|
|
|
public Vector2D Scale
|
|
{
|
|
get => _scale;
|
|
set
|
|
{
|
|
if (value == _scale)
|
|
return;
|
|
|
|
_scale = value;
|
|
UpdateLocalScale();
|
|
OnScaleChanged?.Invoke(this);
|
|
}
|
|
}
|
|
|
|
public float Rotation
|
|
{
|
|
get => _rotation;
|
|
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<ITransform> 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)
|
|
child.OnParentChanged?.Invoke(transform, previousParent);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public bool Assign(IGameObject gameObject)
|
|
{
|
|
if (GameObject is not null && GameObject.Initialized)
|
|
return false;
|
|
|
|
GameObject = gameObject;
|
|
OnGameObjectAssigned?.Invoke(this);
|
|
return true;
|
|
}
|
|
|
|
public bool Unassign()
|
|
{
|
|
if (GameObject is not null && GameObject.Initialized)
|
|
return false;
|
|
|
|
GameObject = null!;
|
|
OnUnassigned?.Invoke(this);
|
|
return true;
|
|
}
|
|
}
|