Files
Syntriax.Engine/Engine.Core/Transform3D.cs
2025-10-19 00:24:56 +03:00

286 lines
8.8 KiB
C#

using Engine.Core.Serialization;
namespace Engine.Core;
[System.Diagnostics.DebuggerDisplay("Name: {UniverseObject.Name, nq} Position: {Position.ToString(), nq}, Scale: {Scale.ToString(), nq}, Rotation: {Rotation}")]
public class Transform3D : Behaviour, ITransform3D
{
public Event<ITransform3D, ITransform3D.PositionChangedArguments> OnPositionChanged { get; } = new();
public Event<ITransform3D, ITransform3D.ScaleChangedArguments> OnScaleChanged { get; } = new();
public Event<ITransform3D, ITransform3D.RotationChangedArguments> OnRotationChanged { get; } = new();
public Event<ITransform3D> OnTransformUpdated { get; } = new();
private Vector3D _position = Vector3D.Zero;
private Vector3D _scale = Vector3D.One;
private Quaternion _rotation = Quaternion.Identity;
[Serialize] private Vector3D _localPosition = Vector3D.Zero;
[Serialize] private Vector3D _localScale = Vector3D.One;
[Serialize] private Quaternion _localRotation = Quaternion.Identity;
private ITransform3D? parentTransform = null;
public Vector3D Up => Quaternion.RotateVector(Vector3D.Up, Rotation);
public Vector3D Down => Quaternion.RotateVector(Vector3D.Down, Rotation);
public Vector3D Left => Quaternion.RotateVector(Vector3D.Left, Rotation);
public Vector3D Right => Quaternion.RotateVector(Vector3D.Right, Rotation);
public Vector3D Forward => Quaternion.RotateVector(Vector3D.Forward, Rotation);
public Vector3D Backward => Quaternion.RotateVector(Vector3D.Backward, Rotation);
public Vector3D Position
{
get => _position;
set
{
if (value == _position)
return;
Vector3D previousPosition = _position;
_position = value;
UpdateLocalPosition();
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
public Vector3D Scale
{
get => _scale;
set
{
if (value == _scale)
return;
Vector3D previousScale = _scale;
_scale = value;
UpdateLocalScale();
OnScaleChanged.Invoke(this, new(previousScale));
OnTransformUpdated.Invoke(this);
}
}
public Quaternion Rotation
{
get => _rotation;
set
{
if (value == _rotation)
return;
Quaternion previousRotation = _rotation;
_rotation = value;
UpdateLocalRotation();
OnRotationChanged.Invoke(this, new(previousRotation));
OnTransformUpdated.Invoke(this);
}
}
public Vector3D LocalPosition
{
get => _localPosition;
set
{
if (value == _localPosition)
return;
Vector3D previousPosition = _position;
_localPosition = value;
UpdatePosition();
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
public Vector3D LocalScale
{
get => _localScale;
set
{
if (value == _localScale)
return;
Vector3D previousScale = _scale;
Vector3D previousPosition = _position;
_localScale = value;
UpdateScale();
UpdatePosition();
OnScaleChanged.Invoke(this, new(previousScale));
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
public Quaternion LocalRotation
{
get => _localRotation;
set
{
if (value == _localRotation)
return;
Quaternion previousRotation = _rotation;
_localRotation = value;
UpdateRotation();
OnRotationChanged.Invoke(this, new(previousRotation));
OnTransformUpdated.Invoke(this);
}
}
private void RecalculatePosition(ITransform3D _, ITransform3D.PositionChangedArguments args)
{
if (parentTransform is null)
return;
UpdatePosition();
OnPositionChanged.Invoke(this, args);
OnTransformUpdated.Invoke(this);
}
private void RecalculateScale(ITransform3D _, ITransform3D.ScaleChangedArguments args)
{
if (parentTransform is null)
return;
Vector3D previousPosition = _position;
UpdateScale();
UpdatePosition();
OnScaleChanged.Invoke(this, args);
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
private void RecalculateRotation(ITransform3D _, ITransform3D.RotationChangedArguments args)
{
if (parentTransform is null)
return;
Vector3D previousPosition = Position;
UpdateRotation();
UpdatePosition();
OnRotationChanged.Invoke(this, args);
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
private void UpdateLocalPosition()
{
if (parentTransform is null)
_localPosition = Position;
else
_localPosition = parentTransform.Position.FromTo(Position).Scale(parentTransform.Scale);
}
private void UpdateLocalScale()
{
if (parentTransform is null)
_localScale = Scale;
else
_localScale = Scale.Scale(new(1f / parentTransform.Scale.X, 1f / parentTransform.Scale.Y, 1f / parentTransform.Scale.Z));
}
private void UpdateLocalRotation()
{
if (parentTransform is null)
_localRotation = Rotation;
else
_localRotation = Rotation * parentTransform.Rotation.Invert();
}
private void UpdatePosition()
{
if (parentTransform is null)
_position = LocalPosition;
else
_position = parentTransform.Position + Quaternion.RotateVector(LocalPosition.Scale(new(parentTransform.Scale.X, parentTransform.Scale.Y, parentTransform.Scale.Z)), parentTransform.Rotation);
}
private void UpdateScale()
{
if (parentTransform is null)
_scale = LocalScale;
else
_scale = (parentTransform?.Scale ?? Vector3D.One).Scale(_localScale);
}
private void UpdateRotation()
{
if (parentTransform is null)
_rotation = LocalRotation;
else
_rotation = parentTransform.Rotation * LocalRotation;
}
protected override void InitializeInternal()
{
UpdateReferences(UniverseObject.Parent);
UniverseObject.OnParentChanged.AddListener(OnParentChanged);
}
protected override void FinalizeInternal()
{
UniverseObject.OnParentChanged.RemoveListener(OnParentChanged);
}
private void UpdateReferences(IUniverseObject? parent)
{
ITransform3D? previousParent = parentTransform;
if (previousParent is not null)
{
previousParent.OnPositionChanged.RemoveListener(RecalculatePosition);
previousParent.OnScaleChanged.RemoveListener(RecalculateScale);
previousParent.OnRotationChanged.RemoveListener(RecalculateRotation);
previousParent.BehaviourController.UniverseObject.OnParentChanged.RemoveListener(OnParentChanged);
previousParent.BehaviourController.OnBehaviourAdded.RemoveListener(LookForTransform3D);
}
parentTransform = parent?.BehaviourController.GetBehaviour<ITransform3D>();
if (parentTransform is not null)
{
parentTransform.OnPositionChanged.AddListener(RecalculatePosition);
parentTransform.OnScaleChanged.AddListener(RecalculateScale);
parentTransform.OnRotationChanged.AddListener(RecalculateRotation);
parentTransform.BehaviourController.UniverseObject.OnParentChanged.AddListener(OnParentChanged);
UpdatePosition();
UpdateScale();
UpdateRotation();
}
else
UniverseObject.Parent?.BehaviourController.OnBehaviourAdded.AddListener(LookForTransform3D);
UpdateLocalPosition();
UpdateLocalScale();
UpdateLocalRotation();
OnPositionChanged.Invoke(this, new(Position));
OnScaleChanged.Invoke(this, new(Scale));
OnRotationChanged.Invoke(this, new(Rotation));
OnTransformUpdated.Invoke(this);
}
private void LookForTransform3D(IBehaviourController sender, IBehaviourController.BehaviourAddedArguments args)
{
if (args.BehaviourAdded is not ITransform3D)
return;
UpdateReferences(UniverseObject.Parent);
}
private void OnParentChanged(IUniverseObject sender, IUniverseObject.ParentChangedArguments args)
{
UpdateReferences(args.CurrentParent);
}
}