286 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			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);
 | 
						|
    }
 | 
						|
}
 |