From 16344dccc799e41629519913f675092ebcf8251b Mon Sep 17 00:00:00 2001 From: Syntriax Date: Sun, 19 Oct 2025 00:24:56 +0300 Subject: [PATCH] feat: 3D transforms added --- Engine.Core/Abstract/IBehaviour3D.cs | 6 + Engine.Core/Abstract/ITransform3D.cs | 105 +++++++ Engine.Core/Extensions/TransformExtensions.cs | 13 + Engine.Core/Transform3D.cs | 285 ++++++++++++++++++ 4 files changed, 409 insertions(+) create mode 100644 Engine.Core/Abstract/IBehaviour3D.cs create mode 100644 Engine.Core/Abstract/ITransform3D.cs create mode 100644 Engine.Core/Transform3D.cs diff --git a/Engine.Core/Abstract/IBehaviour3D.cs b/Engine.Core/Abstract/IBehaviour3D.cs new file mode 100644 index 0000000..75985d6 --- /dev/null +++ b/Engine.Core/Abstract/IBehaviour3D.cs @@ -0,0 +1,6 @@ +namespace Engine.Core; + +public interface IBehaviour3D : IBehaviour +{ + ITransform3D Transform { get; } +} diff --git a/Engine.Core/Abstract/ITransform3D.cs b/Engine.Core/Abstract/ITransform3D.cs new file mode 100644 index 0000000..33a7a78 --- /dev/null +++ b/Engine.Core/Abstract/ITransform3D.cs @@ -0,0 +1,105 @@ +namespace Engine.Core; + +/// +/// Represents the transformation properties of an object such as position, scale, and rotation in 3D space. +/// +public interface ITransform3D : IBehaviour +{ + /// + /// Event triggered when the of the changes. + /// + Event OnPositionChanged { get; } + + /// + /// Event triggered when the of the changes. + /// + Event OnScaleChanged { get; } + + /// + /// Event triggered when the of the changes. + /// + Event OnRotationChanged { get; } + + /// + /// Event triggered when any of the properties of the gets updated. + /// + Event OnTransformUpdated { get; } + + /// + /// The pointing upwards in world space. + /// + Vector3D Up { get; } + + /// + /// The pointing upwards in world space. + /// + Vector3D Down { get; } + + /// + /// The pointing upwards in world space. + /// + Vector3D Left { get; } + + /// + /// The pointing upwards in world space. + /// + Vector3D Right { get; } + + /// + /// The pointing forwards in world space. + /// + Vector3D Forward { get; } + + /// + /// The pointing backwards in world space. + /// + Vector3D Backward { get; } + + /// + /// The world position of the in 3D space. + /// + Vector3D Position { get; set; } + + /// + /// The world scale of the . + /// + Vector3D Scale { get; set; } + + /// + /// The world rotation of the in degrees. + /// + Quaternion Rotation { get; set; } + + /// + /// The local position of the in 3D space. + /// + Vector3D LocalPosition { get; set; } + + /// + /// The local scale of the . + /// + Vector3D LocalScale { get; set; } + + /// + /// The local rotation of the in degrees. + /// + Quaternion LocalRotation { get; set; } + + /// + /// Arguments for the event triggered when the 's rotation changes. + /// + /// The previous of the . + readonly record struct PositionChangedArguments(Vector3D PreviousPosition); + + /// + /// Arguments for the event triggered when the 's rotation changes. + /// + /// The previous of the . + readonly record struct ScaleChangedArguments(Vector3D PreviousScale); + + /// + /// Arguments for the event triggered when the 's rotation changes. + /// + /// The previous of the . + readonly record struct RotationChangedArguments(Quaternion PreviousRotation); +} diff --git a/Engine.Core/Extensions/TransformExtensions.cs b/Engine.Core/Extensions/TransformExtensions.cs index 8300449..7ac43a3 100644 --- a/Engine.Core/Extensions/TransformExtensions.cs +++ b/Engine.Core/Extensions/TransformExtensions.cs @@ -14,4 +14,17 @@ public static class TransformExtensions if (localScale.HasValue) transform.LocalScale = localScale.Value; return transform; } + + public static ITransform3D SetTransform(this ITransform3D transform, + Vector3D? position = null, Quaternion? rotation = null, Vector3D? scale = null, + Vector3D? localPosition = null, Quaternion? localRotation = null, Vector3D? localScale = null) + { + if (position.HasValue) transform.Position = position.Value; + if (rotation.HasValue) transform.Rotation = rotation.Value; + if (scale.HasValue) transform.Scale = scale.Value; + if (localPosition.HasValue) transform.LocalPosition = localPosition.Value; + if (localRotation.HasValue) transform.LocalRotation = localRotation.Value; + if (localScale.HasValue) transform.LocalScale = localScale.Value; + return transform; + } } diff --git a/Engine.Core/Transform3D.cs b/Engine.Core/Transform3D.cs new file mode 100644 index 0000000..7340b19 --- /dev/null +++ b/Engine.Core/Transform3D.cs @@ -0,0 +1,285 @@ +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); + } +}