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 OnPositionChanged { get; } = new(); public Event OnScaleChanged { get; } = new(); public Event OnRotationChanged { get; } = new(); public Event 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(); 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); } }