26 Commits

Author SHA1 Message Date
988a6f67f2 BREAKING CHANGE: renamed original Behaviour class to BehaviourInternal, and replaced it with BehaviourBase
Original Behaviour was using old methods for detecting entering/exiting universe,
they are now all under the same hood and the original is kept for UniverseEntranceManager
because it needs to enter the universe without itself. The internal behaviour kept under
a subnamespace of "Core.Internal" for the purpose that it might come in handy for other use cases.
2025-10-22 16:50:19 +03:00
2f32038f04 refactor: moved packer into sub-namespace 2025-10-22 11:05:26 +03:00
4b756fa232 feat: added more assert methods 2025-10-22 11:04:44 +03:00
0db2cae1bb feat: added sphere primitive 2025-10-21 19:06:58 +03:00
896f7876c1 feat: implicit conversion from circle to aabb2d 2025-10-21 19:02:20 +03:00
56d3112e35 chore!: renamed extension method circle.ToProjection to ProjectTo 2025-10-21 18:43:21 +03:00
32ec2325dc fix: YamlDotNet being on the wrong branch fixed 2025-10-19 23:44:49 +03:00
b42f1f1881 fix: added missing types for new primitives 2025-10-19 19:03:30 +03:00
f8096377b2 chore: removed unsupported leftover methods from int vectors 2025-10-19 19:02:37 +03:00
a9fc819268 feat: added 3D AABB primitive 2025-10-19 18:45:57 +03:00
1d63391975 chore!: renamed AABB to AABB2D 2025-10-19 18:45:48 +03:00
61ff0887e2 feat: 3D camera added 2025-10-19 00:28:40 +03:00
16344dccc7 feat: 3D transforms added 2025-10-19 00:24:56 +03:00
dc4bea3eef feat: Vector4D added 2025-10-19 00:24:19 +03:00
d1b2723a70 feat: 3D ray and line primitives added 2025-10-19 00:24:06 +03:00
2f5c04e66b feat: ITransform.OnTransformUpdated event added 2025-10-19 00:22:30 +03:00
f753da1f87 chore: added XNA Vector3 and Quaternion conversions 2025-10-19 00:16:39 +03:00
6901159106 fix: Quaternion.RotateVector method not working properly on some angles fixed 2025-10-19 00:16:07 +03:00
7469c9ab0c docs: Vector3D.FromAxisAngle parameters updated 2025-10-19 00:15:09 +03:00
ede90adbdc feat: quaternion rotate method added 2025-10-19 00:14:39 +03:00
eeaca3a6c7 feat: quaternion to angles conversion added 2025-10-19 00:13:59 +03:00
3b984a0a4b feat: added Vector3D.Transform method 2025-10-19 00:13:01 +03:00
c5afb70b18 docs: vector3d rotate parameters updated 2025-10-19 00:12:35 +03:00
9d2a192f04 refactor: monogame 2D camera now use engine events 2025-10-19 00:11:51 +03:00
598debc233 perf: removed unnecessary null checks on events 2025-10-19 00:10:07 +03:00
ab05a89175 feat: line 2D to line 2D equation implicit operator added 2025-10-18 14:43:00 +03:00
91 changed files with 2585 additions and 490 deletions

View File

@@ -0,0 +1,6 @@
namespace Engine.Core;
public interface IBehaviour3D : IBehaviour
{
ITransform3D Transform { get; }
}

View File

@@ -0,0 +1,26 @@
namespace Engine.Core;
/// <summary>
/// Represents a 3D camera in the engine.
/// </summary>
public interface ICamera3D : IBehaviour3D
{
/// <summary>
/// Field of View (FOV) value of the camera
/// </summary>
float FieldOfView { get; set; }
/// <summary>
/// Converts a position from screen coordinates to a <see cref="Ray3D"/>.
/// </summary>
/// <param name="screenPosition">The position in screen coordinates.</param>
/// <returns>The <see cref="Ray3D"/> originating from the camera to the screen position in world coordinates.</returns>
Ray3D ScreenToWorldRay(Vector2D screenPosition);
/// <summary>
/// Converts a position from world coordinates to screen coordinates.
/// </summary>
/// <param name="worldPosition">The position in world coordinates.</param>
/// <returns>The position in screen coordinates.</returns>
Vector2D WorldToScreenPosition(Vector3D worldPosition);
}

View File

@@ -16,10 +16,15 @@ public interface ITransform2D : IBehaviour
Event<ITransform2D, ScaleChangedArguments> OnScaleChanged { get; }
/// <summary>
/// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform"/> changes.
/// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform2D"/> changes.
/// </summary>
Event<ITransform2D, RotationChangedArguments> OnRotationChanged { get; }
/// <summary>
/// Event triggered when any of the properties of the <see cref="ITransform2D"/> gets updated.
/// </summary>
Event<ITransform2D> OnTransformUpdated { get; }
/// <summary>
/// The <see cref="Vector2D"/> pointing upwards in world space.
/// </summary>

View File

@@ -0,0 +1,105 @@
namespace Engine.Core;
/// <summary>
/// Represents the transformation properties of an object such as position, scale, and rotation in 3D space.
/// </summary>
public interface ITransform3D : IBehaviour
{
/// <summary>
/// Event triggered when the <see cref="Position"/> of the <see cref="ITransform3D"/> changes.
/// </summary>
Event<ITransform3D, PositionChangedArguments> OnPositionChanged { get; }
/// <summary>
/// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform3D"/> changes.
/// </summary>
Event<ITransform3D, ScaleChangedArguments> OnScaleChanged { get; }
/// <summary>
/// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform3D"/> changes.
/// </summary>
Event<ITransform3D, RotationChangedArguments> OnRotationChanged { get; }
/// <summary>
/// Event triggered when any of the properties of the <see cref="ITransform3D"/> gets updated.
/// </summary>
Event<ITransform3D> OnTransformUpdated { get; }
/// <summary>
/// The <see cref="Vector3D"/> pointing upwards in world space.
/// </summary>
Vector3D Up { get; }
/// <summary>
/// The <see cref="Vector3D"/> pointing upwards in world space.
/// </summary>
Vector3D Down { get; }
/// <summary>
/// The <see cref="Vector3D"/> pointing upwards in world space.
/// </summary>
Vector3D Left { get; }
/// <summary>
/// The <see cref="Vector3D"/> pointing upwards in world space.
/// </summary>
Vector3D Right { get; }
/// <summary>
/// The <see cref="Vector3D"/> pointing forwards in world space.
/// </summary>
Vector3D Forward { get; }
/// <summary>
/// The <see cref="Vector3D"/> pointing backwards in world space.
/// </summary>
Vector3D Backward { get; }
/// <summary>
/// The world position of the <see cref="ITransform3D"/> in 3D space.
/// </summary>
Vector3D Position { get; set; }
/// <summary>
/// The world scale of the <see cref="ITransform3D"/>.
/// </summary>
Vector3D Scale { get; set; }
/// <summary>
/// The world rotation of the <see cref="ITransform3D"/> in degrees.
/// </summary>
Quaternion Rotation { get; set; }
/// <summary>
/// The local position of the <see cref="ITransform3D"/> in 3D space.
/// </summary>
Vector3D LocalPosition { get; set; }
/// <summary>
/// The local scale of the <see cref="ITransform3D"/>.
/// </summary>
Vector3D LocalScale { get; set; }
/// <summary>
/// The local rotation of the <see cref="ITransform3D"/> in degrees.
/// </summary>
Quaternion LocalRotation { get; set; }
/// <summary>
/// Arguments for the event triggered when the <see cref="ITransform3D"/>'s rotation changes.
/// </summary>
/// <param name="PreviousPosition">The previous <see cref="Position"/> of the <see cref="ITransform3D"/>.</param>
readonly record struct PositionChangedArguments(Vector3D PreviousPosition);
/// <summary>
/// Arguments for the event triggered when the <see cref="ITransform3D"/>'s rotation changes.
/// </summary>
/// <param name="PreviousScale">The previous <see cref="Scale"/> of the <see cref="ITransform3D"/>.</param>
readonly record struct ScaleChangedArguments(Vector3D PreviousScale);
/// <summary>
/// Arguments for the event triggered when the <see cref="ITransform3D"/>'s rotation changes.
/// </summary>
/// <param name="PreviousRotation">The previous <see cref="Rotation"/> of the <see cref="ITransform3D"/>.</param>
readonly record struct RotationChangedArguments(Quaternion PreviousRotation);
}

View File

@@ -1,50 +1,103 @@
namespace Engine.Core;
public abstract class Behaviour : BehaviourBase
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
public abstract class Behaviour : BaseEntity, IBehaviour
{
private readonly Event<IUniverseObject, IUniverseObject.EnteredUniverseArguments>.EventHandler delegateEnteredUniverse = null!;
private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler delegateExitedUniverse = null!;
public Event<IBehaviour, IBehaviour.PriorityChangedArguments> OnPriorityChanged { get; } = new();
public Event<IActive, IActive.ActiveChangedArguments> OnActiveChanged { get; } = new();
public Event<IHasBehaviourController> OnBehaviourControllerAssigned { get; } = new();
public Behaviour()
private readonly Event<IHasUniverseObject>.EventHandler delegateOnUniverseObjectAssigned = null!;
private readonly Event<IActive, IActive.ActiveChangedArguments>.EventHandler delegateOnUniverseObjectActiveChanged = null!;
private readonly Event<IStateEnable, IStateEnable.EnabledChangedArguments>.EventHandler delegateOnStateEnabledChanged = null!;
public IUniverse Universe => BehaviourController.UniverseObject.Universe;
public IUniverseObject UniverseObject => BehaviourController.UniverseObject;
private IBehaviourController _behaviourController = null!;
public IBehaviourController BehaviourController => _behaviourController;
private int _priority = 0;
public int Priority
{
OnInitialized.AddListener(OnInitialize);
OnFinalized.AddListener(OnFinalize);
OnUnassigned.AddListener(OnUnassign);
get => _priority;
set
{
if (value == _priority)
return;
delegateEnteredUniverse = EnteredUniverse;
delegateExitedUniverse = ExitedUniverse;
int previousPriority = _priority;
_priority = value;
OnPriorityChanged?.Invoke(this, new(previousPriority));
}
}
protected virtual void OnUnassign() { }
protected void OnUnassign(IAssignable assignable) => OnUnassign();
private bool _isActive = false;
public bool IsActive => _isActive;
protected virtual void OnInitialize() { }
protected void OnInitialize(IInitializable _)
protected virtual void OnAssign(IBehaviourController behaviourController) { }
public bool Assign(IBehaviourController behaviourController)
{
BehaviourController.UniverseObject.OnEnteredUniverse.AddListener(delegateEnteredUniverse);
BehaviourController.UniverseObject.OnExitedUniverse.AddListener(delegateExitedUniverse);
if (IsInitialized)
return false;
OnInitialize();
if (UniverseObject.IsInUniverse)
EnteredUniverse(UniverseObject, new(Universe));
_behaviourController = behaviourController;
OnAssign(behaviourController);
behaviourController.OnUniverseObjectAssigned.AddListener(delegateOnUniverseObjectAssigned);
behaviourController.StateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
if (behaviourController.UniverseObject is not null)
OnUniverseObjectAssigned(behaviourController);
OnBehaviourControllerAssigned?.Invoke(this);
return true;
}
protected virtual void OnFinalize() { }
protected void OnFinalize(IInitializable _)
private void OnUniverseObjectAssigned(IHasUniverseObject sender)
{
BehaviourController.UniverseObject.OnEnteredUniverse.RemoveListener(delegateEnteredUniverse);
BehaviourController.UniverseObject.OnExitedUniverse.RemoveListener(delegateExitedUniverse);
OnFinalize();
if (UniverseObject.IsInUniverse)
ExitedUniverse(UniverseObject, new(Universe));
sender.UniverseObject.OnActiveChanged.AddListener(delegateOnUniverseObjectActiveChanged);
UpdateActive();
}
protected virtual void OnEnteredUniverse(IUniverse universe) { }
protected void EnteredUniverse(IUniverseObject sender, IUniverseObject.EnteredUniverseArguments args) => OnEnteredUniverse(args.Universe);
protected override void OnAssign(IStateEnable stateEnable)
{
base.OnAssign(stateEnable);
protected virtual void OnExitedUniverse(IUniverse universe) { }
protected void ExitedUniverse(IUniverseObject sender, IUniverseObject.ExitedUniverseArguments args) => OnExitedUniverse(args.Universe);
stateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
}
protected override void UnassignInternal()
{
BehaviourController.UniverseObject.OnActiveChanged.RemoveListener(delegateOnUniverseObjectActiveChanged);
StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
BehaviourController.OnUniverseObjectAssigned.RemoveListener(delegateOnUniverseObjectAssigned);
BehaviourController.StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
base.UnassignInternal();
_behaviourController = null!;
}
protected override void InitializeInternal()
{
Debug.Assert.AssertBehaviourControllerAssigned(this);
Debug.Assert.AssertStateEnableAssigned(this);
UpdateActive();
}
private void OnStateEnabledChanged(IStateEnable sender, IStateEnable.EnabledChangedArguments args) => UpdateActive();
private void OnUniverseObjectActiveChanged(IActive sender, IActive.ActiveChangedArguments args) => UpdateActive();
private void UpdateActive()
{
bool previousActive = IsActive;
_isActive = StateEnable.Enabled && _behaviourController.StateEnable.Enabled && _behaviourController.UniverseObject.IsActive;
if (previousActive != IsActive)
OnActiveChanged?.Invoke(this, new(previousActive));
}
protected Behaviour()
{
delegateOnUniverseObjectAssigned = OnUniverseObjectAssigned;
delegateOnUniverseObjectActiveChanged = OnUniverseObjectActiveChanged;
delegateOnStateEnabledChanged = OnStateEnabledChanged;
}
}

View File

@@ -1,6 +1,7 @@
namespace Engine.Core;
public abstract class Behaviour2D : Behaviour, IBehaviour2D
// TODO this should not use independent behaviour, the OnInitialize usage for getting the transform can cause very unexpected issues
public abstract class Behaviour2D : Internal.BehaviourIndependent, IBehaviour2D
{
public ITransform2D Transform { get; private set; } = null!;

View File

@@ -1,103 +0,0 @@
namespace Engine.Core;
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
public abstract class BehaviourBase : BaseEntity, IBehaviour
{
public Event<IBehaviour, IBehaviour.PriorityChangedArguments> OnPriorityChanged { get; } = new();
public Event<IActive, IActive.ActiveChangedArguments> OnActiveChanged { get; } = new();
public Event<IHasBehaviourController> OnBehaviourControllerAssigned { get; } = new();
private readonly Event<IHasUniverseObject>.EventHandler delegateOnUniverseObjectAssigned = null!;
private readonly Event<IActive, IActive.ActiveChangedArguments>.EventHandler delegateOnUniverseObjectActiveChanged = null!;
private readonly Event<IStateEnable, IStateEnable.EnabledChangedArguments>.EventHandler delegateOnStateEnabledChanged = null!;
public IUniverse Universe => BehaviourController.UniverseObject.Universe;
public IUniverseObject UniverseObject => BehaviourController.UniverseObject;
private IBehaviourController _behaviourController = null!;
public IBehaviourController BehaviourController => _behaviourController;
private int _priority = 0;
public int Priority
{
get => _priority;
set
{
if (value == _priority)
return;
int previousPriority = _priority;
_priority = value;
OnPriorityChanged?.Invoke(this, new(previousPriority));
}
}
private bool _isActive = false;
public bool IsActive => _isActive;
protected virtual void OnAssign(IBehaviourController behaviourController) { }
public bool Assign(IBehaviourController behaviourController)
{
if (IsInitialized)
return false;
_behaviourController = behaviourController;
OnAssign(behaviourController);
behaviourController.OnUniverseObjectAssigned.AddListener(delegateOnUniverseObjectAssigned);
behaviourController.StateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
if (behaviourController.UniverseObject is not null)
OnUniverseObjectAssigned(behaviourController);
OnBehaviourControllerAssigned?.Invoke(this);
return true;
}
private void OnUniverseObjectAssigned(IHasUniverseObject sender)
{
sender.UniverseObject.OnActiveChanged.AddListener(delegateOnUniverseObjectActiveChanged);
UpdateActive();
}
protected override void OnAssign(IStateEnable stateEnable)
{
base.OnAssign(stateEnable);
stateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
}
protected override void UnassignInternal()
{
BehaviourController.UniverseObject.OnActiveChanged.RemoveListener(delegateOnUniverseObjectActiveChanged);
StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
BehaviourController.OnUniverseObjectAssigned.RemoveListener(delegateOnUniverseObjectAssigned);
BehaviourController.StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
base.UnassignInternal();
_behaviourController = null!;
}
protected override void InitializeInternal()
{
Debug.Assert.AssertBehaviourControllerAssigned(this);
Debug.Assert.AssertStateEnableAssigned(this);
UpdateActive();
}
private void OnStateEnabledChanged(IStateEnable sender, IStateEnable.EnabledChangedArguments args) => UpdateActive();
private void OnUniverseObjectActiveChanged(IActive sender, IActive.ActiveChangedArguments args) => UpdateActive();
private void UpdateActive()
{
bool previousActive = IsActive;
_isActive = StateEnable.Enabled && _behaviourController.StateEnable.Enabled && _behaviourController.UniverseObject.IsActive;
if (previousActive != IsActive)
OnActiveChanged?.Invoke(this, new(previousActive));
}
protected BehaviourBase()
{
delegateOnUniverseObjectAssigned = OnUniverseObjectAssigned;
delegateOnUniverseObjectActiveChanged = OnUniverseObjectActiveChanged;
delegateOnStateEnabledChanged = OnStateEnabledChanged;
}
}

View File

@@ -0,0 +1,56 @@
namespace Engine.Core.Internal;
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
/// <summary>
/// This behaviour can be used for core managers like <see cref="UpdateManager"/> etc. which need to be able to work even
/// in a very bare minimum setup without the presence of <see cref="UniverseEntranceManager"/> to set themselves up on universe entrance.
/// I recommend not using this unless you know what you are doing but it might come in handy for some use cases.
/// </summary>
public abstract class BehaviourIndependent : Behaviour
{
private readonly Event<IUniverseObject, IUniverseObject.EnteredUniverseArguments>.EventHandler delegateEnteredUniverse = null!;
private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler delegateExitedUniverse = null!;
public BehaviourIndependent()
{
OnInitialized.AddListener(OnInitialize);
OnFinalized.AddListener(OnFinalize);
OnUnassigned.AddListener(OnUnassign);
delegateEnteredUniverse = EnteredUniverse;
delegateExitedUniverse = ExitedUniverse;
}
protected virtual void OnUnassign() { }
protected void OnUnassign(IAssignable assignable) => OnUnassign();
protected virtual void OnInitialize() { }
protected void OnInitialize(IInitializable _)
{
BehaviourController.UniverseObject.OnEnteredUniverse.AddListener(delegateEnteredUniverse);
BehaviourController.UniverseObject.OnExitedUniverse.AddListener(delegateExitedUniverse);
OnInitialize();
if (UniverseObject.IsInUniverse)
EnteredUniverse(UniverseObject, new(Universe));
}
protected virtual void OnFinalize() { }
protected void OnFinalize(IInitializable _)
{
BehaviourController.UniverseObject.OnEnteredUniverse.RemoveListener(delegateEnteredUniverse);
BehaviourController.UniverseObject.OnExitedUniverse.RemoveListener(delegateExitedUniverse);
OnFinalize();
if (UniverseObject.IsInUniverse)
ExitedUniverse(UniverseObject, new(Universe));
}
protected virtual void OnEnteredUniverse(IUniverse universe) { }
protected void EnteredUniverse(IUniverseObject sender, IUniverseObject.EnteredUniverseArguments args) => OnEnteredUniverse(args.Universe);
protected virtual void OnExitedUniverse(IUniverse universe) { }
protected void ExitedUniverse(IUniverseObject sender, IUniverseObject.ExitedUniverseArguments args) => OnExitedUniverse(args.Universe);
}

View File

@@ -4,6 +4,22 @@ namespace Engine.Core.Debug;
public static class Assert
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertTrue(bool value, string errorMessage)
=> System.Diagnostics.Debug.Assert(value, errorMessage);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertFalse(bool value, string errorMessage)
=> System.Diagnostics.Debug.Assert(!value, errorMessage);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertNull(object? value, string errorMessage)
=> System.Diagnostics.Debug.Assert(value is null, errorMessage);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertNotNull(object? value, string errorMessage)
=> System.Diagnostics.Debug.Assert(value is not null, errorMessage);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertInitialized(IInitializable initializable)
=> System.Diagnostics.Debug.Assert(initializable.IsInitialized, $"{initializable.GetType().Name} must be initialized");

View File

@@ -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;
}
}

View File

@@ -1,111 +0,0 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
/// <summary>
/// Represents an Axis-Aligned Bounding Box (AABB) in 2D space.
/// </summary>
/// <param name="lowerBoundary">The lower boundary of the <see cref="AABB"/>.</param>
/// <param name="upperBoundary">The upper boundary of the <see cref="AABB"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="AABB"/> struct with the specified lower and upper boundaries.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")]
public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary) : IEquatable<AABB>
{
/// <summary>
/// The lower boundary of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D LowerBoundary = lowerBoundary;
/// <summary>
/// The upper boundary of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D UpperBoundary = upperBoundary;
/// <summary>
/// Gets the center point of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D Center => (LowerBoundary + UpperBoundary) * .5f;
/// <summary>
/// Gets the size of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs();
/// <summary>
/// Gets half the size of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D SizeHalf => Size * .5f;
public static bool operator ==(AABB left, AABB right) => left.UpperBoundary == right.UpperBoundary && left.LowerBoundary == right.LowerBoundary;
public static bool operator !=(AABB left, AABB right) => left.UpperBoundary != right.UpperBoundary || left.LowerBoundary != right.LowerBoundary;
/// <summary>
/// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
/// <returns>An <see cref="AABB"/> that bounds all the <see cref="Vector2D"/>s.</returns>
public static AABB FromVectors(IEnumerable<Vector2D> vectors)
{
int counter = 0;
Vector2D lowerBoundary = new(float.MaxValue, float.MaxValue);
Vector2D upperBoundary = new(float.MinValue, float.MinValue);
foreach (Vector2D vector in vectors)
{
lowerBoundary = Vector2D.Min(lowerBoundary, vector);
upperBoundary = Vector2D.Max(upperBoundary, vector);
counter++;
}
if (counter < 2)
throw new System.ArgumentException($"Parameter {nameof(vectors)} must have at least 2 items.");
return new(lowerBoundary, upperBoundary);
}
/// <summary>
/// Checks if two <see cref="AABB"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="AABB"/>.</param>
/// <param name="right">The second <see cref="AABB"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(AABB left, AABB right, float epsilon = float.Epsilon)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="AABB"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="AABB"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="AABB"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is AABB aabb && this == aabb;
public bool Equals(AABB other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="AABB"/>.
/// </summary>
/// <returns>A hash code for the <see cref="AABB"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(LowerBoundary, UpperBoundary);
/// <summary>
/// Converts the <see cref="AABB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="AABB"/>.</returns>
public override string ToString() => $"{nameof(AABB)}({LowerBoundary}, {UpperBoundary})";
}
/// <summary>
/// Provides extension methods for the <see cref="AABB"/> struct.
/// </summary>
public static class AABBExtensions
{
/// <inheritdoc cref="AABB.ToAABB" />
public static AABB ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors);
/// <inheritdoc cref="AABB.ApproximatelyEquals" />
public static bool ApproximatelyEquals(this AABB left, AABB right, float epsilon = float.Epsilon) => AABB.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
/// <summary>
/// Represents an Axis-Aligned Bounding Box (AABB) in 2D space.
/// </summary>
/// <param name="lowerBoundary">The lower boundary of the <see cref="AABB2D"/>.</param>
/// <param name="upperBoundary">The upper boundary of the <see cref="AABB2D"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="AABB2D"/> struct with the specified lower and upper boundaries.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")]
public readonly struct AABB2D(Vector2D lowerBoundary, Vector2D upperBoundary) : IEquatable<AABB2D>
{
/// <summary>
/// The lower boundary of the <see cref="AABB2D"/>.
/// </summary>
public readonly Vector2D LowerBoundary = lowerBoundary;
/// <summary>
/// The upper boundary of the <see cref="AABB2D"/>.
/// </summary>
public readonly Vector2D UpperBoundary = upperBoundary;
/// <summary>
/// Gets the center point of the <see cref="AABB2D"/>.
/// </summary>
public readonly Vector2D Center => (LowerBoundary + UpperBoundary) * .5f;
/// <summary>
/// Gets the size of the <see cref="AABB2D"/>.
/// </summary>
public readonly Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs();
/// <summary>
/// Gets half the size of the <see cref="AABB2D"/>.
/// </summary>
public readonly Vector2D SizeHalf => Size * .5f;
public static bool operator ==(AABB2D left, AABB2D right) => left.UpperBoundary == right.UpperBoundary && left.LowerBoundary == right.LowerBoundary;
public static bool operator !=(AABB2D left, AABB2D right) => left.UpperBoundary != right.UpperBoundary || left.LowerBoundary != right.LowerBoundary;
public static implicit operator AABB2D(Circle circle) => new(circle.Center - new Vector2D(circle.Radius, circle.Radius), circle.Center + new Vector2D(circle.Radius, circle.Radius));
public static implicit operator AABB2D(Shape2D shape) => FromVectors(shape.Vertices);
/// <summary>
/// Creates an <see cref="AABB2D"/> from a collection of <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
/// <returns>An <see cref="AABB2D"/> that bounds all the <see cref="Vector2D"/>s.</returns>
public static AABB2D FromVectors(IEnumerable<Vector2D> vectors)
{
int counter = 0;
Vector2D lowerBoundary = new(float.MaxValue, float.MaxValue);
Vector2D upperBoundary = new(float.MinValue, float.MinValue);
foreach (Vector2D vector in vectors)
{
lowerBoundary = Vector2D.Min(lowerBoundary, vector);
upperBoundary = Vector2D.Max(upperBoundary, vector);
counter++;
}
if (counter < 2)
throw new ArgumentException($"Parameter {nameof(vectors)} must have at least 2 items.");
return new(lowerBoundary, upperBoundary);
}
/// <summary>
/// Checks if two <see cref="AABB2D"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="AABB2D"/>.</param>
/// <param name="right">The second <see cref="AABB2D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="AABB2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(AABB2D left, AABB2D right, float epsilon = float.Epsilon)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="AABB2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="AABB2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="AABB2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is AABB2D aabb && this == aabb;
public bool Equals(AABB2D other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="AABB2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="AABB2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(LowerBoundary, UpperBoundary);
/// <summary>
/// Converts the <see cref="AABB2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="AABB2D"/>.</returns>
public override string ToString() => $"{nameof(AABB2D)}({LowerBoundary}, {UpperBoundary})";
}
/// <summary>
/// Provides extension methods for the <see cref="AABB2D"/> struct.
/// </summary>
public static class AABBExtensions
{
/// <inheritdoc cref="AABB2D.FromVectors" />
public static AABB2D ToAABB(this IEnumerable<Vector2D> vectors) => AABB2D.FromVectors(vectors);
/// <inheritdoc cref="AABB2D.ApproximatelyEquals" />
public static bool ApproximatelyEquals(this AABB2D left, AABB2D right, float epsilon = float.Epsilon) => AABB2D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
/// <summary>
/// Represents an Axis-Aligned Bounding Box (AABB) in 3D space.
/// </summary>
/// <param name="lowerBoundary">The lower boundary of the <see cref="AABB3D"/>.</param>
/// <param name="upperBoundary">The upper boundary of the <see cref="AABB3D"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="AABB3D"/> struct with the specified lower and upper boundaries.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")]
public readonly struct AABB3D(Vector3D lowerBoundary, Vector3D upperBoundary) : IEquatable<AABB3D>
{
/// <summary>
/// The lower boundary of the <see cref="AABB3D"/>.
/// </summary>
public readonly Vector3D LowerBoundary = lowerBoundary;
/// <summary>
/// The upper boundary of the <see cref="AABB3D"/>.
/// </summary>
public readonly Vector3D UpperBoundary = upperBoundary;
/// <summary>
/// Gets the center point of the <see cref="AABB3D"/>.
/// </summary>
public readonly Vector3D Center => (LowerBoundary + UpperBoundary) * .5f;
/// <summary>
/// Gets the size of the <see cref="AABB3D"/>.
/// </summary>
public readonly Vector3D Size => LowerBoundary.FromTo(UpperBoundary).Abs();
/// <summary>
/// Gets half the size of the <see cref="AABB3D"/>.
/// </summary>
public readonly Vector3D SizeHalf => Size * .5f;
public static bool operator ==(AABB3D left, AABB3D right) => left.UpperBoundary == right.UpperBoundary && left.LowerBoundary == right.LowerBoundary;
public static bool operator !=(AABB3D left, AABB3D right) => left.UpperBoundary != right.UpperBoundary || left.LowerBoundary != right.LowerBoundary;
public static implicit operator AABB3D(Sphere3D sphere) => new(sphere.Center - new Vector3D(sphere.Radius, sphere.Radius, sphere.Radius), sphere.Center + new Vector3D(sphere.Radius, sphere.Radius, sphere.Radius));
/// <summary>
/// Creates an <see cref="AABB3D"/> from a collection of <see cref="Vector3D"/>s.
/// </summary>
/// <param name="vectors">The collection of <see cref="Vector3D"/>s.</param>
/// <returns>An <see cref="AABB3D"/> that bounds all the <see cref="Vector3D"/>s.</returns>
public static AABB3D FromVectors(IEnumerable<Vector3D> vectors)
{
int counter = 0;
Vector3D lowerBoundary = new(float.MaxValue, float.MaxValue, float.MaxValue);
Vector3D upperBoundary = new(float.MinValue, float.MinValue, float.MinValue);
foreach (Vector3D vector in vectors)
{
lowerBoundary = Vector3D.Min(lowerBoundary, vector);
upperBoundary = Vector3D.Max(upperBoundary, vector);
counter++;
}
if (counter < 2)
throw new ArgumentException($"Parameter {nameof(vectors)} must have at least 2 items.");
return new(lowerBoundary, upperBoundary);
}
/// <summary>
/// Checks if two <see cref="AABB3D"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="AABB3D"/>.</param>
/// <param name="right">The second <see cref="AABB3D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="AABB3D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(AABB3D left, AABB3D right, float epsilon = float.Epsilon)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="AABB3D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="AABB3D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="AABB3D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is AABB3D aabb && this == aabb;
public bool Equals(AABB3D other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="AABB3D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="AABB3D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(LowerBoundary, UpperBoundary);
/// <summary>
/// Converts the <see cref="AABB3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="AABB3D"/>.</returns>
public override string ToString() => $"{nameof(AABB3D)}({LowerBoundary}, {UpperBoundary})";
}
/// <summary>
/// Provides extension methods for the <see cref="AABB3D"/> struct.
/// </summary>
public static class AABB3DExtensions
{
/// <inheritdoc cref="AABB3D.FromVectors" />
public static AABB3D ToAABB3D(this IEnumerable<Vector3D> vectors) => AABB3D.FromVectors(vectors);
/// <inheritdoc cref="AABB3D.ApproximatelyEquals" />
public static bool ApproximatelyEquals(this AABB3D left, AABB3D right, float epsilon = float.Epsilon) => AABB3D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -15,7 +15,7 @@ namespace Engine.Core;
public readonly struct Circle(Vector2D center, float radius) : IEquatable<Circle>
{
/// <summary>
/// The center of the circle.
/// The center of the <see cref="Circle"/>.
/// </summary>
public readonly Vector2D Center = center;
@@ -42,6 +42,8 @@ public readonly struct Circle(Vector2D center, float radius) : IEquatable<Circle
public static bool operator ==(Circle left, Circle right) => left.Center == right.Center && left.Radius == right.Radius;
public static bool operator !=(Circle left, Circle right) => left.Center != right.Center || left.Radius != right.Radius;
public static implicit operator Circle(Sphere3D sphere) => new(sphere.Center, sphere.Radius);
/// <summary>
/// Sets the center of the <see cref="Circle"/>.
/// </summary>
@@ -118,7 +120,7 @@ public static class CircleExtensions
public static Circle Displace(this Circle circle, Vector2D displaceVector) => Circle.Displace(circle, displaceVector);
/// <inheritdoc cref="Circle.Project(Circle, Vector2D)" />
public static Projection1D ToProjection(this Circle circle, Vector2D projectionVector) => Circle.Project(circle, projectionVector);
public static Projection1D ProjectTo(this Circle circle, Vector2D projectionVector) => Circle.Project(circle, projectionVector);
/// <inheritdoc cref="Circle.Transform(ITransform2D, Circle)" />
public static Circle Transform(this ITransform2D transform, Circle circle) => Circle.Transform(transform, circle);

View File

@@ -50,15 +50,7 @@ public readonly struct Line2D(Vector2D from, Vector2D to) : IEquatable<Line2D>
/// <summary>
/// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment.
/// </summary>
public static Line2DEquation GetLineEquation(Line2D line)
{
Vector2D slopeVector = line.From.FromTo(line.To);
float slope = slopeVector.Y / slopeVector.X;
float yOffset = line.From.Y - (slope * line.From.X);
return new Line2DEquation(slope, yOffset);
}
public static Line2DEquation GetLineEquation(Line2D line) => line;
/// <summary>
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line2D"/>.

View File

@@ -26,6 +26,16 @@ public readonly struct Line2DEquation(float slope, float offsetY) : IEquatable<L
public static bool operator ==(Line2DEquation left, Line2DEquation right) => left.Slope == right.Slope && left.OffsetY == right.OffsetY;
public static bool operator !=(Line2DEquation left, Line2DEquation right) => left.Slope != right.Slope || left.OffsetY != right.OffsetY;
public static implicit operator Line2DEquation(Line2D line)
{
Vector2D slopeVector = line.From.FromTo(line.To);
float slope = slopeVector.Y / slopeVector.X;
float yOffset = line.From.Y - (slope * line.From.X);
return new Line2DEquation(slope, yOffset);
}
/// <summary>
/// Resolves the Y coordinate for a given X coordinate using the <see cref="Line2DEquation"/>.
/// </summary>

View File

@@ -0,0 +1,114 @@
using System;
namespace Engine.Core;
/// <summary>
/// Represents a 3D line segment defined by two endpoints.
/// </summary>
/// <param name="from">The starting point of the <see cref="Line3D"/> segment.</param>
/// <param name="to">The ending point of the <see cref="Line3D"/> segment.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Line3D"/> struct with the specified endpoints.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("From: {From.ToString(),nq}, To: {To.ToString(),nq}, Direction: {Direction.ToString(),nq}, Length: {Length}")]
public readonly struct Line3D(Vector3D from, Vector3D to) : IEquatable<Line3D>
{
/// <summary>
/// The starting point of the <see cref="Line3D"/> segment.
/// </summary>
public readonly Vector3D From = from;
/// <summary>
/// The ending point of the <see cref="Line3D"/> segment.
/// </summary>
public readonly Vector3D To = to;
/// <summary>
/// The reversed <see cref="Line3D"/> segment.
/// </summary>
public readonly Line3D Reversed => new(To, From);
/// <summary>
/// The normalized direction <see cref="Vector3D"/> of the <see cref="Line3D"/> segment.
/// </summary>
public readonly Vector3D Direction => From.FromTo(To).Normalize();
/// <summary>
/// The length of the <see cref="Line3D"/> segment.
/// </summary>
public readonly float Length => From.FromTo(To).Length();
/// <summary>
/// The squared length of the <see cref="Line3D"/> segment.
/// </summary>
public readonly float LengthSquared => From.FromTo(To).LengthSquared();
public static bool operator ==(Line3D left, Line3D right) => left.From == right.From && left.To == right.To;
public static bool operator !=(Line3D left, Line3D right) => left.From != right.From || left.To != right.To;
/// <summary>
/// Linearly interpolates between the two endpoints of the <see cref="Line3D"/> segment using parameter 't'.
/// </summary>
public static Vector3D Lerp(Line3D line, float t)
=> Vector3D.Lerp(line.From, line.To, t);
/// <summary>
/// Calculates the closest point on the <see cref="Line3D"/> segment to the specified point.
/// </summary>
public static Vector3D ClosestPointTo(Line3D line, Vector3D point)
{
Vector3D lineRelativeVector = line.From.FromTo(line.To);
Vector3D lineDirection = lineRelativeVector.Normalized;
Vector3D pointVector = line.From.FromTo(point);
float dot = lineDirection.Dot(pointVector).Clamp(0f, lineRelativeVector.Magnitude);
return lineDirection * dot;
}
/// <summary>
/// Checks if two <see cref="Line3D"/> segments are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="Line3D"/>.</param>
/// <param name="right">The second <see cref="Line3D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Line3D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Line3D left, Line3D right, float epsilon = float.Epsilon)
=> left.From.ApproximatelyEquals(right.From, epsilon) && left.To.ApproximatelyEquals(right.To, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Line3D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Line3D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Line3D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Line3D line3D && this == line3D;
public bool Equals(Line3D other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="Line3D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Line3D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(From, To);
/// <summary>
/// Converts the <see cref="Line3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Line3D"/>.</returns>
public override string ToString() => $"{nameof(Line3D)}({From}, {To})";
}
/// <summary>
/// Provides extension methods for the <see cref="Line3D"/> struct.
/// </summary>
public static class Line3DExtensions
{
/// <inheritdoc cref="Line3D.Lerp(Line3D, float)" />
public static Vector3D Lerp(this Line3D line, float t) => Line3D.Lerp(line, t);
/// <inheritdoc cref="Line3D.ClosestPointTo(Line3D, Vector3D)" />
public static Vector3D ClosestPointTo(this Line3D line, Vector3D point) => Line3D.ClosestPointTo(line, point);
/// <inheritdoc cref="Line3D.ApproximatelyEquals(Line3D, Line3D, float)" />
public static bool ApproximatelyEquals(this Line3D left, Line3D right, float epsilon = float.Epsilon) => Line3D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -79,6 +79,30 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
public static implicit operator Quaternion(System.Numerics.Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
public static implicit operator System.Numerics.Quaternion(Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
/// <summary>
/// Get the Pitch, Yaw and Roll of the <see cref="Quaternion"/>.
/// </summary>
public static Vector3D ToAngles(Quaternion quaternion)
{
// Quaternion to Euler angles (in 3-2-1 sequence) conversion
float sinr_cosp = 2f * (quaternion.W * quaternion.X + quaternion.Y * quaternion.Z);
float cosr_cosp = 1f - 2f * (quaternion.X * quaternion.X + quaternion.Y * quaternion.Y);
float pitch = MathF.Atan2(sinr_cosp, cosr_cosp);
float sinp = 2f * (quaternion.W * quaternion.Y - quaternion.Z * quaternion.X);
float yaw;
if (MathF.Abs(sinp) >= 1f)
yaw = MathF.CopySign(MathF.PI / 2f, sinp);
else
yaw = MathF.Asin(sinp);
float siny_cosp = 2f * (quaternion.W * quaternion.Z + quaternion.X * quaternion.Y);
float cosy_cosp = 1f - 2f * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z);
float roll = MathF.Atan2(siny_cosp, cosy_cosp);
return new Vector3D(pitch, yaw, roll) * Math.RadianToDegree;
}
/// <summary>
/// Calculates the length of the <see cref="Quaternion"/>.
/// </summary>
@@ -132,6 +156,15 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
/// <returns>The normalized <see cref="Quaternion"/>.</returns>
public static Quaternion Normalize(Quaternion quaternion) => quaternion / Length(quaternion);
/// <summary>
/// Rotates a <see cref="Quaternion"/> around a axis by the specified angle (in radians).
/// </summary>
/// <param name="vector">The <see cref="Quaternion"/> to rotate.</param>
/// <param name="axis">The <see cref="Quaternion"/> to rotate around.</param>
/// <param name="angleInRadian">The angle to rotate by, in radians.</param>
/// <returns>The rotated <see cref="Quaternion"/>.</returns>
public static Quaternion Rotate(Quaternion vector, Vector3D axis, float angleInRadian) => vector * Quaternion.FromAxisAngle(axis, angleInRadian);
/// <summary>
/// Inverts the direction of the <see cref="Quaternion"/>.
/// </summary>
@@ -154,8 +187,12 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
/// <returns>The rotated <see cref="Vector3D"/>.</returns>
public static Vector3D RotateVector(Vector3D vector, Quaternion quaternion)
{
Quaternion rotation = quaternion * new Quaternion(vector.X, vector.Y, vector.Z, 0) * Invert(quaternion);
return new(rotation.X, rotation.Y, rotation.Z);
// https://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/
// t = 2 * cross(q.xyz, v)
// v' = v + q.w * t + cross(q.xyz, t)
Vector3D quaternionVector = new(quaternion.X, quaternion.Y, quaternion.Z);
Vector3D t = 2f * quaternionVector.Cross(vector);
return vector + quaternion.W * t + quaternionVector.Cross(t);
}
/// <summary>
@@ -210,9 +247,9 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
/// <param name="axis">The axis of the rotation in <see cref="Vector3D"/>.</param>
/// <param name="angle">The angle in radians.</param>
/// <returns>The rotation <see cref="Quaternion"/> calculated by the given parameters.</returns>
public static Quaternion FromAxisAngle(Vector3D axis, float angle)
public static Quaternion FromAxisAngle(Vector3D axis, float angleInRadian)
{
float halfAngle = angle * .5f;
float halfAngle = angleInRadian * .5f;
float sinHalf = Math.Sin(halfAngle);
return new Quaternion(axis.X * sinHalf, axis.Y * sinHalf, axis.Z * sinHalf, Math.Cos(halfAngle));
}
@@ -310,6 +347,9 @@ public readonly struct Quaternion(float x, float y, float z, float w) : IEquatab
/// </summary>
public static class QuaternionExtensions
{
/// <inheritdoc cref="Quaternion.ToAngles(Quaternion)" />
public static Vector3D ToAngles(this Quaternion quaternion) => Quaternion.ToAngles(quaternion);
/// <inheritdoc cref="Quaternion.Length(Quaternion)" />
public static float Length(this Quaternion quaternion) => Quaternion.Length(quaternion);
@@ -331,6 +371,9 @@ public static class QuaternionExtensions
/// <inheritdoc cref="Quaternion.Normalize(Quaternion)" />
public static Quaternion Normalize(this Quaternion quaternion) => Quaternion.Normalize(quaternion);
/// <inheritdoc cref="Quaternion.Rotate(Quaternion, Vector3D, float)" />
public static Quaternion Rotate(this Quaternion vector, Vector3D normal, float angleInRadian) => Quaternion.Rotate(vector, normal, angleInRadian);
/// <inheritdoc cref="Quaternion.Invert(Quaternion)" />
public static Quaternion Invert(this Quaternion quaternion) => Quaternion.Invert(quaternion);

View File

@@ -0,0 +1,109 @@
using System;
namespace Engine.Core;
/// <summary>
/// Represents an infinite ray in 3D space.
/// </summary>
/// <param name="Origin">The <see cref="Vector3D"/> in 3D space where the ray starts from.</param>
/// <param name="Direction">Normalized <see cref="Vector3D"/> indicating the ray's is direction.</param>
public readonly struct Ray3D(Vector3D Origin, Vector3D Direction) : IEquatable<Ray3D>
{
/// <summary>
/// The starting point of the <see cref="Ray3D"/>.
/// </summary>
public readonly Vector3D Origin = Origin;
/// <summary>
/// The direction in which the <see cref="Ray3D"/> points. Should be a normalized vector.
/// </summary>
public readonly Vector3D Direction = Direction;
/// <summary>
/// Gets a <see cref="Ray3D"/> with the same origin but with the direction reversed.
/// </summary>
public readonly Ray3D Reversed => new(Origin, -Direction);
public static bool operator ==(Ray3D left, Ray3D right) => left.Origin == right.Origin && left.Direction == right.Direction;
public static bool operator !=(Ray3D left, Ray3D right) => left.Origin != right.Origin || left.Direction != right.Direction;
public static implicit operator Ray3D(Line3D line) => new(line.From, line.From.FromTo(line.To).Normalized);
/// <summary>
/// Constructs a <see cref="Line3D"/> from a <see cref="Ray3D"/>, extending from its origin in the <see cref="Ray3D"/>'s direction for a given distance.
/// </summary>
/// <param name="ray">The source <see cref="Ray3D"/>.</param>
/// <param name="distance">The length of the line segment to create from the <see cref="Ray3D"/>.</param>
/// <returns>A <see cref="Line3D"/> representing the segment of the <see cref="Ray3D"/>.</returns>
public static Line3D GetLine(Ray3D ray, float distance)
=> new(ray.Origin, ray.Origin + ray.Direction * distance);
/// <summary>
/// Evaluates the point on the <see cref="Ray3D"/> at a specified distance from its origin.
/// </summary>
/// <param name="ray">The <see cref="Ray3D"/> to evaluate.</param>
/// <param name="distanceFromOrigin">The distance from the origin along the <see cref="Ray3D"/>'s direction.</param>
/// <returns>A <see cref="Vector3D"/> representing the point at the given distance on the <see cref="Ray3D"/>.</returns>
public static Vector3D Evaluate(Ray3D ray, float distanceFromOrigin)
=> ray.Origin + ray.Direction * distanceFromOrigin;
/// <summary>
/// Calculates the closest point on the <see cref="Ray3D"/> to the specified point.
/// </summary>
public static Vector3D ClosestPointTo(Ray3D ray, Vector3D point)
{
Vector3D originToPoint = ray.Origin.FromTo(point);
float dot = ray.Direction.Dot(originToPoint);
return ray.Origin + ray.Direction * dot;
}
/// <summary>
/// Checks if two <see cref="Ray3D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Ray3D"/>.</param>
/// <param name="right">The second <see cref="Ray3D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Ray3D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Ray3D left, Ray3D right, float epsilon = float.Epsilon)
=> left.Origin.ApproximatelyEquals(right.Origin, epsilon) && left.Direction.ApproximatelyEquals(right.Direction, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Ray3D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Ray3D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Ray3D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Ray3D ray3D && this == ray3D;
public bool Equals(Ray3D other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="Ray3D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Ray3D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Origin, Direction);
/// <summary>
/// Converts the <see cref="Ray3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Ray3D"/>.</returns>
public override string ToString() => $"{nameof(Ray3D)}({Origin}, {Direction})";
}
/// <summary>
/// Provides extension methods for the <see cref="Ray3D"/> struct.
/// </summary>
public static class Ray3DExtensions
{
/// <inheritdoc cref="Ray3D.GetLine(Ray3D, float) />
public static Line3D ToLine(this Ray3D ray, float distance) => Ray3D.GetLine(ray, distance);
/// <inheritdoc cref="Ray3D.Evaluate(Ray3D, float) />
public static Vector3D Evaluate(this Ray3D ray, float distanceFromOrigin) => Ray3D.Evaluate(ray, distanceFromOrigin);
/// <inheritdoc cref="Ray3D.ClosestPointTo(Ray3D, Vector3D) />
public static Vector3D ClosestPointTo(this Ray3D ray, Vector3D point) => Ray3D.ClosestPointTo(ray, point);
/// <inheritdoc cref="Ray3D.ApproximatelyEquals(Ray3D, Ray3D, float)" />
public static bool ApproximatelyEquals(this Ray3D left, Ray3D right, float epsilon = float.Epsilon) => Ray3D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,131 @@
using System;
using System.Diagnostics;
namespace Engine.Core;
/// <summary>
/// Represents a 3D sphere.
/// </summary>
/// <param name="center">The center of the sphere.</param>
/// <param name="radius">The radius of the sphere.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Sphere3D"/> struct with the specified center and radius.
/// </remarks>
[DebuggerDisplay("Center: {Center.ToString(),nq}, Radius: {Radius}")]
public readonly struct Sphere3D(Vector3D center, float radius) : IEquatable<Sphere3D>
{
/// <summary>
/// The center of the <see cref="Sphere3D"/>.
/// </summary>
public readonly Vector3D Center = center;
/// <summary>
/// The radius of the <see cref="Sphere3D"/>.
/// </summary>
public readonly float Radius = radius;
/// <summary>
/// Gets the squared radius of the <see cref="Sphere3D"/>.
/// </summary>
public readonly float RadiusSquared => Radius * Radius;
/// <summary>
/// Gets the diameter of the <see cref="Sphere3D"/>.
/// </summary>
public readonly float Diameter => 2f * Radius;
/// <summary>
/// A predefined unit <see cref="Sphere3D"/> with a center at the origin and a radius of 1.
/// </summary>
public static readonly Sphere3D UnitSphere = new(Vector3D.Zero, 1f);
public static bool operator ==(Sphere3D left, Sphere3D right) => left.Center == right.Center && left.Radius == right.Radius;
public static bool operator !=(Sphere3D left, Sphere3D right) => left.Center != right.Center || left.Radius != right.Radius;
/// <summary>
/// Sets the center of the <see cref="Sphere3D"/>.
/// </summary>
public static Sphere3D SetCenter(Sphere3D sphere, Vector3D center) => new(center, sphere.Radius);
/// <summary>
/// Sets the radius of the <see cref="Sphere3D"/>.
/// </summary>
public static Sphere3D SetRadius(Sphere3D sphere, float radius) => new(sphere.Center, radius);
/// <summary>
/// Displaces the <see cref="Sphere3D"/> by the specified <see cref="Vector3D"/>.
/// </summary>
public static Sphere3D Displace(Sphere3D sphere, Vector3D displaceVector) => new(sphere.Center + displaceVector, sphere.Radius);
/// <summary>
/// Projects the <see cref="Sphere3D"/> onto the specified <see cref="Vector3D"/>.
/// </summary>
public static Projection1D Project(Sphere3D sphere, Vector3D projectionVector)
{
float projectedCenter = sphere.Center.Dot(projectionVector);
return new(projectedCenter - sphere.Radius, projectedCenter + sphere.Radius);
}
/// <summary>
/// Transforms the <see cref="Sphere3D"/> by the specified <see cref="ITransform3D"/>.
/// </summary>
public static Sphere3D Transform(ITransform3D transform, Sphere3D sphere)
=> new(transform.Transform(sphere.Center), sphere.Radius * (transform.Scale.Magnitude / Vector3D.One.Magnitude));
/// <summary>
/// Checks if two <see cref="Sphere3D"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="Sphere3D"/>.</param>
/// <param name="right">The second <see cref="Sphere3D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Sphere3D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Sphere3D left, Sphere3D right, float epsilon = float.Epsilon)
=> left.Center.ApproximatelyEquals(right.Center, epsilon) && left.Radius.ApproximatelyEquals(right.Radius, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Sphere3D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Sphere3D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Sphere3D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Sphere3D sphere && this == sphere;
public bool Equals(Sphere3D other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="Sphere3D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Sphere3D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Center, Radius);
/// <summary>
/// Converts the <see cref="Sphere3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Sphere3D"/>.</returns>
public override string ToString() => $"{nameof(Sphere3D)}({Center}, {Radius})";
}
/// <summary>
/// Provides extension methods for the <see cref="Sphere3D"/> struct.
/// </summary>
public static class SphereExtensions
{
/// <inheritdoc cref="Sphere3D.SetCenter(Sphere3D, Vector3D)" />
public static Sphere3D SetCenter(this Sphere3D sphere, Vector3D center) => Sphere3D.SetCenter(sphere, center);
/// <inheritdoc cref="Sphere3D.SetRadius(Sphere3D, float)" />
public static Sphere3D SetRadius(this Sphere3D sphere, float radius) => Sphere3D.SetRadius(sphere, radius);
/// <inheritdoc cref="Sphere3D.Displace(Sphere3D, Vector3D)" />
public static Sphere3D Displace(this Sphere3D sphere, Vector3D displaceVector) => Sphere3D.Displace(sphere, displaceVector);
/// <inheritdoc cref="Sphere3D.Project(Sphere3D, Vector3D)" />
public static Projection1D ProjectTo(this Sphere3D sphere, Vector3D projectionVector) => Sphere3D.Project(sphere, projectionVector);
/// <inheritdoc cref="Sphere3D.Transform(ITransform3D, Sphere3D)" />
public static Sphere3D Transform(this ITransform3D transform, Sphere3D sphere) => Sphere3D.Transform(transform, sphere);
/// <inheritdoc cref="Sphere3D.Transform(ITransform3D, Sphere3D)" />
public static Sphere3D Transform(this Sphere3D sphere, ITransform3D transform) => Sphere3D.Transform(transform, sphere);
/// <inheritdoc cref="Sphere3D.ApproximatelyEquals(Sphere3D, Sphere3D, float)" />
public static bool ApproximatelyEquals(this Sphere3D left, Sphere3D right, float epsilon = float.Epsilon) => Sphere3D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -71,9 +71,6 @@ public readonly struct Vector2DInt(int x, int y) : IEquatable<Vector2DInt>
public static Vector2DInt operator -(Vector2DInt vector) => new(0 - vector.X, 0 - vector.Y);
public static Vector2DInt operator +(Vector2DInt left, Vector2DInt right) => new(left.X + right.X, left.Y + right.Y);
public static Vector2DInt operator -(Vector2DInt left, Vector2DInt right) => new(left.X - right.X, left.Y - right.Y);
public static Vector2DInt operator *(Vector2DInt vector, int value) => new(vector.X * value, vector.Y * value);
public static Vector2DInt operator *(int value, Vector2DInt vector) => new(vector.X * value, vector.Y * value);
public static Vector2DInt operator /(Vector2DInt vector, int value) => new(vector.X / value, vector.Y / value);
public static bool operator ==(Vector2DInt left, Vector2DInt right) => left.X == right.X && left.Y == right.Y;
public static bool operator !=(Vector2DInt left, Vector2DInt right) => left.X != right.X || left.Y != right.Y;
@@ -125,22 +122,6 @@ public readonly struct Vector2DInt(int x, int y) : IEquatable<Vector2DInt>
/// <returns>The result of subtracting the second <see cref="Vector2DInt"/> from the first.</returns>
public static Vector2DInt Subtract(Vector2DInt left, Vector2DInt right) => left - right;
/// <summary>
/// Multiplies a <see cref="Vector2DInt"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2DInt"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="Vector2DInt"/> by the scalar value.</returns>
public static Vector2DInt Multiply(Vector2DInt vector, int value) => vector * value;
/// <summary>
/// Divides a <see cref="Vector2DInt"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2DInt"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="Vector2DInt"/> by the scalar value.</returns>
public static Vector2DInt Divide(Vector2DInt vector, int value) => vector / value;
/// <summary>
/// Calculates the absolute value of each component of the vector.
/// </summary>
@@ -196,15 +177,6 @@ public readonly struct Vector2DInt(int x, int y) : IEquatable<Vector2DInt>
/// <returns>A <see cref="Vector2DInt"/> with each component clamped between the corresponding components of the min and max <see cref="Vector2DInt"/>s.</returns>
public static Vector2DInt Clamp(Vector2DInt vector, Vector2DInt min, Vector2DInt max) => new(Engine.Core.Math.Clamp(vector.X, min.X, max.X), Engine.Core.Math.Clamp(vector.Y, min.Y, max.Y));
/// <summary>
/// Performs linear interpolation between two <see cref="Vector2DInt"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Vector2DInt"/> (t = 0).</param>
/// <param name="to">The ending <see cref="Vector2DInt"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="Vector2DInt"/>.</returns>
public static Vector2DInt Lerp(Vector2DInt from, Vector2DInt to, int t) => from + FromTo(from, to) * t;
/// <summary>
/// Calculates the cross product of two <see cref="Vector2DInt"/>s.
/// </summary>
@@ -265,12 +237,6 @@ public static class Vector2DIntExtensions
/// <inheritdoc cref="Vector2DInt.Subtract(Vector2DInt, Vector2DInt)" />
public static Vector2DInt Subtract(this Vector2DInt vector, Vector2DInt vectorToSubtract) => Vector2DInt.Subtract(vector, vectorToSubtract);
/// <inheritdoc cref="Vector2DInt.Multiply(Vector2DInt, int)" />
public static Vector2DInt Multiply(this Vector2DInt vector, int value) => Vector2DInt.Multiply(vector, value);
/// <inheritdoc cref="Vector2DInt.Divide(Vector2DInt, int)" />
public static Vector2DInt Divide(this Vector2DInt vector, int value) => Vector2DInt.Divide(vector, value);
/// <inheritdoc cref="Vector2DInt.Abs(Vector2DInt)" />
public static Vector2DInt Abs(this Vector2DInt vector) => Vector2DInt.Abs(vector);
@@ -292,9 +258,6 @@ public static class Vector2DIntExtensions
/// <inheritdoc cref="Vector2DInt.Clamp(Vector2DInt, Vector2DInt,Vector2DInt)" />
public static Vector2DInt Clamp(this Vector2DInt vector, Vector2DInt min, Vector2DInt max) => Vector2DInt.Clamp(vector, min, max);
/// <inheritdoc cref="Vector2DInt.Lerp(Vector2DInt, Vector2DInt," />
public static Vector2DInt Lerp(this Vector2DInt from, Vector2DInt to, int t) => Vector2DInt.Lerp(from, to, t);
/// <inheritdoc cref="Vector2DInt.Cross(Vector2DInt, Vector2DInt)" />
public static int Cross(this Vector2DInt left, Vector2DInt right) => Vector2DInt.Cross(left, right);

View File

@@ -198,13 +198,13 @@ public readonly struct Vector3D(float x, float y, float z) : IEquatable<Vector3D
public static Vector3D Scale(Vector3D vector, Vector3D scale) => new(vector.X * scale.X, vector.Y * scale.Y, vector.Z * scale.Z);
/// <summary>
/// Rotates a <see cref="Vector3D"/> around a normal by the specified angle (in radians).
/// Rotates a <see cref="Vector3D"/> around a axis by the specified angle (in radians).
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to rotate.</param>
/// <param name="normal">The <see cref="Vector3D"/> to rotate around.</param>
/// <param name="axis">The <see cref="Vector3D"/> to rotate around.</param>
/// <param name="angleInRadian">The angle to rotate by, in radians.</param>
/// <returns>The rotated <see cref="Vector3D"/>.</returns>
public static Vector3D Rotate(Vector3D vector, Vector3D normal, float angleInRadian) => vector * Math.Cos(angleInRadian) + Cross(normal, vector) * Math.Sin(angleInRadian) + normal * Dot(normal, vector) * (1f - Math.Cos(angleInRadian));
public static Vector3D Rotate(Vector3D vector, Vector3D axis, float angleInRadian) => vector * Math.Cos(angleInRadian) + Cross(axis, vector) * Math.Sin(angleInRadian) + axis * Dot(axis, vector) * (1f - Math.Cos(angleInRadian));
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector3D"/>s.
@@ -264,6 +264,15 @@ public readonly struct Vector3D(float x, float y, float z) : IEquatable<Vector3D
/// <returns>The dot product of the two <see cref="Vector3D"/>s.</returns>
public static float Dot(Vector3D left, Vector3D right) => left.X * right.X + left.Y * right.Y + left.Z * right.Z;
/// <summary>
/// Transforms the <see cref="Vector3D"/> using the specified <see cref="ITransform3D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to transform.</param>
/// <param name="transform">The <see cref="ITransform3D"/> to apply.</param>
/// <returns>The transformed <see cref="Vector3D"/>.</returns>
public static Vector3D Transform(Vector3D vector, ITransform3D transform)
=> Quaternion.RotateVector(vector, transform.Rotation).Add(transform.Position).Scale(transform.Scale);
/// <summary>
/// Checks if two <see cref="Vector3D"/>s are approximately equal within a specified epsilon range.
/// </summary>
@@ -363,6 +372,12 @@ public static class Vector3DExtensions
/// <inheritdoc cref="Vector3D.Dot(Vector3D, Vector3D)" />
public static float Dot(this Vector3D left, Vector3D right) => Vector3D.Dot(left, right);
/// <inheritdoc cref="Vector3D.Transform(Vector3D, ITransform3D)" />
public static Vector3D Transform(this Vector3D vector, ITransform3D transform) => Vector3D.Transform(vector, transform);
/// <inheritdoc cref="Vector3D.Transform(Vector3D, ITransform3D)" />
public static Vector3D Transform(this ITransform3D transform, Vector3D vector) => Vector3D.Transform(vector, transform);
/// <inheritdoc cref="Vector3D.ApproximatelyEquals(Vector3D, Vector3D, float)" />
public static bool ApproximatelyEquals(this Vector3D left, Vector3D right, float epsilon = float.Epsilon) => Vector3D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -81,9 +81,6 @@ public readonly struct Vector3DInt(int x, int y, int z) : IEquatable<Vector3DInt
public static Vector3DInt operator -(Vector3DInt vector) => new(0 - vector.X, 0 - vector.Y, 0 - vector.Z);
public static Vector3DInt operator +(Vector3DInt left, Vector3DInt right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
public static Vector3DInt operator -(Vector3DInt left, Vector3DInt right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
public static Vector3DInt operator *(Vector3DInt vector, int value) => new(vector.X * value, vector.Y * value, vector.Z * value);
public static Vector3DInt operator *(int value, Vector3DInt vector) => new(vector.X * value, vector.Y * value, vector.Z * value);
public static Vector3DInt operator /(Vector3DInt vector, int value) => new(vector.X / value, vector.Y / value, vector.Z / value);
public static bool operator ==(Vector3DInt left, Vector3DInt right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z;
public static bool operator !=(Vector3DInt left, Vector3DInt right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z;
@@ -135,22 +132,6 @@ public readonly struct Vector3DInt(int x, int y, int z) : IEquatable<Vector3DInt
/// <returns>The result of subtracting the second <see cref="Vector3DInt"/> from the first.</returns>
public static Vector3DInt Subtract(Vector3DInt left, Vector3DInt right) => left - right;
/// <summary>
/// Multiplies a <see cref="Vector3DInt"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector3DInt"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="Vector3DInt"/> by the scalar value.</returns>
public static Vector3DInt Multiply(Vector3DInt vector, int value) => vector * value;
/// <summary>
/// Divides a <see cref="Vector3DInt"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector3DInt"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="Vector3DInt"/> by the scalar value.</returns>
public static Vector3DInt Divide(Vector3DInt vector, int value) => vector / value;
/// <summary>
/// Calculates the absolute value of each component of the vector.
/// </summary>
@@ -199,15 +180,6 @@ public readonly struct Vector3DInt(int x, int y, int z) : IEquatable<Vector3DInt
/// <returns>A <see cref="Vector3DInt"/> with each component clamped between the corresponding components of the min and max <see cref="Vector3DInt"/>s.</returns>
public static Vector3DInt Clamp(Vector3DInt vector, Vector3DInt min, Vector3DInt max) => new(Math.Clamp(vector.X, min.X, max.X), Math.Clamp(vector.Y, min.Y, max.Y), Math.Clamp(vector.Z, min.Z, max.Z));
/// <summary>
/// Performs linear interpolation between two <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Vector3DInt"/> (t = 0).</param>
/// <param name="to">The ending <see cref="Vector3DInt"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="Vector3DInt"/>.</returns>
public static Vector3DInt Lerp(Vector3DInt from, Vector3DInt to, int t) => from + FromTo(from, to) * t;
/// <summary>
/// Calculates the cross product of two <see cref="Vector3DInt"/>s.
/// </summary>
@@ -276,12 +248,6 @@ public static class Vector3DIntExtensions
/// <inheritdoc cref="Vector3DInt.Subtract(Vector3DInt, Vector3DInt)" />
public static Vector3DInt Subtract(this Vector3DInt vector, Vector3DInt vectorToSubtract) => Vector3DInt.Subtract(vector, vectorToSubtract);
/// <inheritdoc cref="Vector3DInt.Multiply(Vector3DInt, int)" />
public static Vector3DInt Multiply(this Vector3DInt vector, int value) => Vector3DInt.Multiply(vector, value);
/// <inheritdoc cref="Vector3DInt.Divide(Vector3DInt, int)" />
public static Vector3DInt Divide(this Vector3DInt vector, int value) => Vector3DInt.Divide(vector, value);
/// <inheritdoc cref="Vector3DInt.Abs(Vector3DInt)" />
public static Vector3DInt Abs(this Vector3DInt vector) => Vector3DInt.Abs(vector);
@@ -300,9 +266,6 @@ public static class Vector3DIntExtensions
/// <inheritdoc cref="Vector3DInt.Clamp(Vector3DInt, Vector3DInt, Vector3DInt)" />
public static Vector3DInt Clamp(this Vector3DInt vector, Vector3DInt min, Vector3DInt max) => Vector3DInt.Clamp(vector, min, max);
/// <inheritdoc cref="Vector3DInt.Lerp(Vector3DInt, Vector3DInt, int)" />
public static Vector3DInt Lerp(this Vector3DInt from, Vector3DInt to, int t) => Vector3DInt.Lerp(from, to, t);
/// <inheritdoc cref="Vector3DInt.Cross(Vector3DInt, Vector3DInt)" />
public static Vector3DInt Cross(this Vector3DInt left, Vector3DInt right) => Vector3DInt.Cross(left, right);

View File

@@ -0,0 +1,318 @@
using System;
namespace Engine.Core;
/// <summary>
/// Represents a four-dimensional vector.
/// </summary>
/// <param name="x">X position of the <see cref="Vector4D"/>.</param>
/// <param name="y">Y position of the <see cref="Vector4D"/>.</param>
/// <param name="z">Z position of the <see cref="Vector4D"/>.</param>
/// <param name="w">W position of the <see cref="Vector4D"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Vector4D"/> struct with the specified positions.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized.ToString(),nq}")]
public readonly struct Vector4D(float x, float y, float z, float w) : IEquatable<Vector4D>
{
/// <summary>
/// The X coordinate of the <see cref="Vector4D"/>.
/// </summary>
public readonly float X = x;
/// <summary>
/// The Y coordinate of the <see cref="Vector4D"/>.
/// </summary>
public readonly float Y = y;
/// <summary>
/// The Z coordinate of the <see cref="Vector4D"/>.
/// </summary>
public readonly float Z = z;
/// <summary>
/// The W coordinate of the <see cref="Vector4D"/>.
/// </summary>
public readonly float W = w;
/// <summary>
/// The magnitude (length) of the <see cref="Vector4D"/>.
/// </summary>
public float Magnitude => Length(this);
/// <summary>
/// The squared magnitude (length) of the <see cref="Vector4D"/>.
/// </summary>
public float MagnitudeSquared => LengthSquared(this);
/// <summary>
/// The normalized form of the <see cref="Vector4D"/> (a <see cref="Vector4D"/> with the same direction and a magnitude of 1).
/// </summary>
public Vector4D Normalized => Normalize(this);
/// <summary>
/// Represents the zero <see cref="Vector4D"/>.
/// </summary>
public readonly static Vector4D Zero = new(0f, 0f, 0f, 0f);
/// <summary>
/// Represents the one <see cref="Vector4D"/>.
/// </summary>
public readonly static Vector4D One = new(1f, 1f, 1f, 1f);
/// <summary>
/// Represents the unit X <see cref="Vector4D"/>.
/// </summary>
public readonly static Vector4D UnitX = new(1f, 0f, 0f, 0f);
/// <summary>
/// Represents the unit Y <see cref="Vector4D"/>.
/// </summary>
public readonly static Vector4D UnitY = new(0f, 1f, 0f, 0f);
/// <summary>
/// Represents the unit Z <see cref="Vector4D"/>.
/// </summary>
public readonly static Vector4D UnitZ = new(0f, 0f, 1f, 0f);
/// <summary>
/// Represents the unit W <see cref="Vector4D"/>.
/// </summary>
public readonly static Vector4D UnitW = new(0f, 0f, 0f, 1f);
public static Vector4D operator -(Vector4D vector) => new(0f - vector.X, 0f - vector.Y, 0f - vector.Z, 0f - vector.W);
public static Vector4D operator +(Vector4D left, Vector4D right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W);
public static Vector4D operator -(Vector4D left, Vector4D right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W);
public static Vector4D operator *(Vector4D vector, float value) => new(vector.X * value, vector.Y * value, vector.Z * value, vector.W * value);
public static Vector4D operator *(float value, Vector4D vector) => new(vector.X * value, vector.Y * value, vector.Z * value, vector.W * value);
public static Vector4D operator /(Vector4D vector, float value) => new(vector.X / value, vector.Y / value, vector.Z / value, vector.W / value);
public static bool operator ==(Vector4D left, Vector4D right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z && left.W == right.W;
public static bool operator !=(Vector4D left, Vector4D right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z || left.W != right.W;
public static implicit operator System.Numerics.Vector4(Vector4D vector) => new(vector.X, vector.Y, vector.Z, vector.W);
public static implicit operator Vector4D(System.Numerics.Vector4 vector) => new(vector.X, vector.Y, vector.Z, vector.W);
/// <summary>
/// Calculates the length of the <see cref="Vector4D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/>.</param>
/// <returns>The length of the <see cref="Vector4D"/>.</returns>
public static float Length(Vector4D vector) => Math.Sqrt(LengthSquared(vector));
/// <summary>
/// Calculates the squared length of the <see cref="Vector4D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/>.</param>
/// <returns>The squared length of the <see cref="Vector4D"/>.</returns>
public static float LengthSquared(Vector4D vector) => vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z + vector.W * vector.W;
/// <summary>
/// Calculates the distance between two <see cref="Vector4D"/>s.
/// </summary>
/// <param name="from">The start <see cref="Vector4D"/>.</param>
/// <param name="to">The end <see cref="Vector4D"/>.</param>
/// <returns>The distance between the two <see cref="Vector4D"/>s.</returns>
public static float Distance(Vector4D from, Vector4D to) => Length(FromTo(from, to));
/// <summary>
/// Inverts the direction of the <see cref="Vector4D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/>.</param>
/// <returns>The inverted <see cref="Vector4D"/>.</returns>
public static Vector4D Invert(Vector4D vector) => -vector;
/// <summary>
/// Adds two <see cref="Vector4D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector4D"/>.</param>
/// <param name="right">The second <see cref="Vector4D"/>.</param>
/// <returns>The sum of the two <see cref="Vector4D"/>s.</returns>
public static Vector4D Add(Vector4D left, Vector4D right) => left + right;
/// <summary>
/// Subtracts one <see cref="Vector4D"/> from another.
/// </summary>
/// <param name="left">The <see cref="Vector4D"/> to subtract from.</param>
/// <param name="right">The <see cref="Vector4D"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="Vector4D"/> from the first.</returns>
public static Vector4D Subtract(Vector4D left, Vector4D right) => left - right;
/// <summary>
/// Multiplies a <see cref="Vector4D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="Vector4D"/> by the scalar value.</returns>
public static Vector4D Multiply(Vector4D vector, float value) => vector * value;
/// <summary>
/// Divides a <see cref="Vector4D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="Vector4D"/> by the scalar value.</returns>
public static Vector4D Divide(Vector4D vector, float value) => vector / value;
/// <summary>
/// Calculates the absolute value of each component of the vector.
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/>.</param>
/// <returns>The <see cref="Vector4D"/> with each component's absolute value.</returns>
public static Vector4D Abs(Vector4D vector) => new(Math.Abs(vector.X), Math.Abs(vector.Y), Math.Abs(vector.Z), Math.Abs(vector.W));
/// <summary>
/// Normalizes the <see cref="Vector4D"/> (creates a unit <see cref="Vector4D"/> with the same direction).
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/> to normalize.</param>
/// <returns>The normalized <see cref="Vector4D"/>.</returns>
public static Vector4D Normalize(Vector4D vector) => vector / Length(vector);
/// <summary>
/// Calculates the <see cref="Vector4D"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="Vector4D"/> from the starting point to the ending point.</returns>
public static Vector4D FromTo(Vector4D from, Vector4D to) => to - from;
/// <summary>
/// Scales a <see cref="Vector4D"/> by another <see cref="Vector4D"/> component-wise.
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/> to scale.</param>
/// <param name="scale">The <see cref="Vector4D"/> containing the scaling factors for each component.</param>
/// <returns>The scaled <see cref="Vector4D"/>.</returns>
public static Vector4D Scale(Vector4D vector, Vector4D scale) => new(vector.X * scale.X, vector.Y * scale.Y, vector.Z * scale.Z, vector.W * scale.W);
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector4D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector4D"/>.</param>
/// <param name="right">The second <see cref="Vector4D"/>.</param>
/// <returns>The <see cref="Vector4D"/> containing the minimum components from both input <see cref="Vector4D"/>s.</returns>
public static Vector4D Min(Vector4D left, Vector4D right) => new((left.X < right.X) ? left.X : right.X, (left.Y < right.Y) ? left.Y : right.Y, (left.Z < right.Z) ? left.Z : right.Z, (left.W < right.W) ? left.W : right.W);
/// <summary>
/// Returns the component-wise maximum of two <see cref="Vector4D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector4D"/>.</param>
/// <param name="right">The second <see cref="Vector4D"/>.</param>
/// <returns>The <see cref="Vector4D"/> containing the maximum components from both input <see cref="Vector4D"/>s.</returns>
public static Vector4D Max(Vector4D left, Vector4D right) => new((left.X > right.X) ? left.X : right.X, (left.Y > right.Y) ? left.Y : right.Y, (left.Z > right.Z) ? left.Z : right.Z, (left.W > right.W) ? left.W : right.W);
/// <summary>
/// Clamps each component of a <see cref="Vector4D"/> between the corresponding component of two other <see cref="Vector4D"/>s.
/// </summary>
/// <param name="vector">The <see cref="Vector4D"/> to clamp.</param>
/// <param name="min">The <see cref="Vector4D"/> representing the minimum values for each component.</param>
/// <param name="max">The <see cref="Vector4D"/> representing the maximum values for each component.</param>
/// <returns>A <see cref="Vector4D"/> with each component clamped between the corresponding components of the min and max <see cref="Vector4D"/>s.</returns>
public static Vector4D Clamp(Vector4D vector, Vector4D min, Vector4D max) => new(Math.Clamp(vector.X, min.X, max.X), Math.Clamp(vector.Y, min.Y, max.Y), Math.Clamp(vector.Z, min.Z, max.Z), Math.Clamp(vector.W, min.W, max.W));
/// <summary>
/// Performs linear interpolation between two <see cref="Vector4D"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Vector4D"/> (t = 0).</param>
/// <param name="to">The ending <see cref="Vector4D"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="Vector4D"/>.</returns>
public static Vector4D Lerp(Vector4D from, Vector4D to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Calculates the dot product of two <see cref="Vector4D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector4D"/>.</param>
/// <param name="right">The second <see cref="Vector4D"/>.</param>
/// <returns>The dot product of the two <see cref="Vector4D"/>s.</returns>
public static float Dot(Vector4D left, Vector4D right) => left.X * right.X + left.Y * right.Y + left.Z * right.Z + left.W * right.W;
/// <summary>
/// Checks if two <see cref="Vector4D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Vector4D"/>.</param>
/// <param name="right">The second <see cref="Vector4D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Vector4D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Vector4D left, Vector4D right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon) && left.Z.ApproximatelyEquals(right.W, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector4D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector4D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector4D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector4D vector4D && this == vector4D;
public bool Equals(Vector4D other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="Vector4D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Vector4D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z, W);
/// <summary>
/// Converts the <see cref="Vector4D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector4D"/>.</returns>
public override string ToString() => $"{nameof(Vector4D)}({X}, {Y}, {Z}, {W})";
}
/// <summary>
/// Provides extension methods for <see cref="Vector4D"/> type.
/// </summary>
public static class Vector4DExtensions
{
/// <inheritdoc cref="Vector4D.Length(Vector4D)" />
public static float Length(this Vector4D vector) => Vector4D.Length(vector);
/// <inheritdoc cref="Vector4D.LengthSquared(Vector4D)" />
public static float LengthSquared(this Vector4D vector) => Vector4D.LengthSquared(vector);
/// <inheritdoc cref="Vector4D.Distance(Vector4D, Vector4D)" />
public static float Distance(this Vector4D from, Vector4D to) => Vector4D.Distance(from, to);
/// <inheritdoc cref="Vector4D.Invert(Vector4D)" />
public static Vector4D Invert(this Vector4D vector) => Vector4D.Invert(vector);
/// <inheritdoc cref="Vector4D.Add(Vector4D, Vector4D)" />
public static Vector4D Add(this Vector4D vector, Vector4D vectorToAdd) => Vector4D.Add(vector, vectorToAdd);
/// <inheritdoc cref="Vector4D.Subtract(Vector4D, Vector4D)" />
public static Vector4D Subtract(this Vector4D vector, Vector4D vectorToSubtract) => Vector4D.Subtract(vector, vectorToSubtract);
/// <inheritdoc cref="Vector4D.Multiply(Vector4D, float)" />
public static Vector4D Multiply(this Vector4D vector, float value) => Vector4D.Multiply(vector, value);
/// <inheritdoc cref="Vector4D.Divide(Vector4D, float)" />
public static Vector4D Divide(this Vector4D vector, float value) => Vector4D.Divide(vector, value);
/// <inheritdoc cref="Vector4D.Abs(Vector4D)" />
public static Vector4D Abs(this Vector4D vector) => Vector4D.Abs(vector);
/// <inheritdoc cref="Vector4D.Normalize(Vector4D)" />
public static Vector4D Normalize(this Vector4D vector) => Vector4D.Normalize(vector);
/// <inheritdoc cref="Vector4D.FromTo(Vector4D, Vector4D)" />
public static Vector4D FromTo(this Vector4D from, Vector4D to) => Vector4D.FromTo(from, to);
/// <inheritdoc cref="Vector4D.Scale(Vector4D, Vector4D)" />
public static Vector4D Scale(this Vector4D vector, Vector4D scale) => Vector4D.Scale(vector, scale);
/// <inheritdoc cref="Vector4D.Min(Vector4D, Vector4D)" />
public static Vector4D Min(this Vector4D left, Vector4D right) => Vector4D.Min(left, right);
/// <inheritdoc cref="Vector4D.Max(Vector4D, Vector4D)" />
public static Vector4D Max(this Vector4D left, Vector4D right) => Vector4D.Max(left, right);
/// <inheritdoc cref="Vector4D.Clamp(Vector4D, Vector4D, Vector4D)" />
public static Vector4D Clamp(this Vector4D vector, Vector4D min, Vector4D max) => Vector4D.Clamp(vector, min, max);
/// <inheritdoc cref="Vector4D.Lerp(Vector4D, Vector4D, float)" />
public static Vector4D Lerp(this Vector4D from, Vector4D to, float t) => Vector4D.Lerp(from, to, t);
/// <inheritdoc cref="Vector4D.Dot(Vector4D, Vector4D)" />
public static float Dot(this Vector4D left, Vector4D right) => Vector4D.Dot(left, right);
/// <inheritdoc cref="Vector4D.ApproximatelyEquals(Vector4D, Vector4D, float)" />
public static bool ApproximatelyEquals(this Vector4D left, Vector4D right, float epsilon = float.Epsilon) => Vector4D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -2,7 +2,7 @@ using System.Collections.Generic;
namespace Engine.Core;
public class DrawManager : Behaviour
public class DrawManager : Behaviour, IEnterUniverse, IExitUniverse
{
// We use Descending order because draw calls are running from last to first
private static Comparer<int> SortByDescendingPriority() => Comparer<int>.Create((x, y) => y.CompareTo(x));
@@ -30,7 +30,7 @@ public class DrawManager : Behaviour
postDrawEntities[i].PostDraw();
}
protected override void OnEnteredUniverse(IUniverse universe)
public void EnterUniverse(IUniverse universe)
{
preDrawEntities.Assign(universe);
drawEntities.Assign(universe);
@@ -41,7 +41,7 @@ public class DrawManager : Behaviour
universe.OnPostDraw.AddListener(OnPostDraw);
}
protected override void OnExitedUniverse(IUniverse universe)
public void ExitUniverse(IUniverse universe)
{
preDrawEntities.Unassign();
drawEntities.Unassign();

View File

@@ -2,7 +2,7 @@ using System.Collections.Generic;
namespace Engine.Core;
public class UniverseEntranceManager : Behaviour
public class UniverseEntranceManager : Internal.BehaviourIndependent
{
// We use Ascending order because we are using reverse for loop to call them
private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));

View File

@@ -2,7 +2,7 @@ using System.Collections.Generic;
namespace Engine.Core;
public class UpdateManager : Behaviour
public class UpdateManager : Behaviour, IEnterUniverse, IExitUniverse
{
// We use Ascending order because we are using reverse for loop to call them
private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
@@ -16,7 +16,7 @@ public class UpdateManager : Behaviour
private readonly List<IFirstFrameUpdate> toCallFirstFrameUpdates = new(32);
protected override void OnEnteredUniverse(IUniverse universe)
public void EnterUniverse(IUniverse universe)
{
firstFrameUpdates.Assign(universe);
lastFrameUpdates.Assign(universe);
@@ -30,7 +30,7 @@ public class UpdateManager : Behaviour
universe.OnPostUpdate.AddListener(OnPostUpdate);
}
protected override void OnExitedUniverse(IUniverse universe)
public void ExitUniverse(IUniverse universe)
{
firstFrameUpdates.Unassign();
lastFrameUpdates.Unassign();

View File

@@ -8,6 +8,7 @@ public class Transform2D : Behaviour, ITransform2D
public Event<ITransform2D, ITransform2D.PositionChangedArguments> OnPositionChanged { get; } = new();
public Event<ITransform2D, ITransform2D.ScaleChangedArguments> OnScaleChanged { get; } = new();
public Event<ITransform2D, ITransform2D.RotationChangedArguments> OnRotationChanged { get; } = new();
public Event<ITransform2D> OnTransformUpdated { get; } = new();
private Vector2D _position = Vector2D.Zero;
private Vector2D _scale = Vector2D.One;
@@ -36,7 +37,8 @@ public class Transform2D : Behaviour, ITransform2D
_position = value;
UpdateLocalPosition();
OnPositionChanged?.Invoke(this, new(previousPosition));
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
@@ -52,7 +54,8 @@ public class Transform2D : Behaviour, ITransform2D
_scale = value;
UpdateLocalScale();
OnScaleChanged?.Invoke(this, new(previousScale));
OnScaleChanged.Invoke(this, new(previousScale));
OnTransformUpdated.Invoke(this);
}
}
@@ -68,7 +71,8 @@ public class Transform2D : Behaviour, ITransform2D
_rotation = value;
UpdateLocalRotation();
OnRotationChanged?.Invoke(this, new(previousRotation));
OnRotationChanged.Invoke(this, new(previousRotation));
OnTransformUpdated.Invoke(this);
}
}
@@ -84,7 +88,8 @@ public class Transform2D : Behaviour, ITransform2D
_localPosition = value;
UpdatePosition();
OnPositionChanged?.Invoke(this, new(previousPosition));
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
@@ -102,8 +107,9 @@ public class Transform2D : Behaviour, ITransform2D
UpdateScale();
UpdatePosition();
OnScaleChanged?.Invoke(this, new(previousScale));
OnPositionChanged?.Invoke(this, new(previousPosition));
OnScaleChanged.Invoke(this, new(previousScale));
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
@@ -119,7 +125,8 @@ public class Transform2D : Behaviour, ITransform2D
_localRotation = value;
UpdateRotation();
OnRotationChanged?.Invoke(this, new(previousRotation));
OnRotationChanged.Invoke(this, new(previousRotation));
OnTransformUpdated.Invoke(this);
}
}
@@ -130,7 +137,8 @@ public class Transform2D : Behaviour, ITransform2D
UpdatePosition();
OnPositionChanged?.Invoke(this, args);
OnPositionChanged.Invoke(this, args);
OnTransformUpdated.Invoke(this);
}
private void RecalculateScale(ITransform2D _, ITransform2D.ScaleChangedArguments args)
@@ -143,8 +151,9 @@ public class Transform2D : Behaviour, ITransform2D
UpdateScale();
UpdatePosition();
OnScaleChanged?.Invoke(this, args);
OnPositionChanged?.Invoke(this, new(previousPosition));
OnScaleChanged.Invoke(this, args);
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
private void RecalculateRotation(ITransform2D _, ITransform2D.RotationChangedArguments args)
@@ -157,8 +166,9 @@ public class Transform2D : Behaviour, ITransform2D
UpdateRotation();
UpdatePosition();
OnRotationChanged?.Invoke(this, args);
OnPositionChanged?.Invoke(this, new(previousPosition));
OnRotationChanged.Invoke(this, args);
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
private void UpdateLocalPosition()
@@ -252,9 +262,10 @@ public class Transform2D : Behaviour, ITransform2D
UpdateLocalScale();
UpdateLocalRotation();
OnPositionChanged?.Invoke(this, new(Position));
OnScaleChanged?.Invoke(this, new(Scale));
OnRotationChanged?.Invoke(this, new(Rotation));
OnPositionChanged.Invoke(this, new(Position));
OnScaleChanged.Invoke(this, new(Scale));
OnRotationChanged.Invoke(this, new(Rotation));
OnTransformUpdated.Invoke(this);
}
private void LookForTransform2D(IBehaviourController sender, IBehaviourController.BehaviourAddedArguments args)

285
Engine.Core/Transform3D.cs Normal file
View File

@@ -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<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);
}
}

View File

@@ -56,17 +56,17 @@ public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicator
return this;
}
protected override void OnEnteredUniverse(IUniverse universe)
public override void EnterUniverse(IUniverse universe)
{
base.OnEnteredUniverse(universe);
base.EnterUniverse(universe);
cancellationTokenSource = new CancellationTokenSource();
PollEvents(cancellationTokenSource.Token);
}
protected override void OnExitedUniverse(IUniverse universe)
public override void ExitUniverse(IUniverse universe)
{
base.OnExitedUniverse(universe);
base.ExitUniverse(universe);
cancellationTokenSource?.Cancel();
}

View File

@@ -1,14 +1,15 @@
using System.Reflection;
using Engine.Core;
using Engine.Core.Debug;
using Engine.Systems.Network.Packers;
using LiteNetLib;
using LiteNetLib.Utils;
using Engine.Core;
using Engine.Core.Debug;
namespace Engine.Systems.Network;
public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicator
public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IExitUniverse, INetworkCommunicator
{
protected readonly NetPacketProcessor netPacketProcessor = new();
@@ -32,15 +33,13 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicat
return this;
}
protected override void OnEnteredUniverse(IUniverse universe)
public virtual void EnterUniverse(IUniverse universe)
{
base.OnEnteredUniverse(universe);
logger = universe.FindBehaviour<ILogger>();
}
protected override void OnExitedUniverse(IUniverse universe)
public virtual void ExitUniverse(IUniverse universe)
{
base.OnExitedUniverse(universe);
logger = null;
Stop();
}
@@ -143,19 +142,28 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicat
private void SetupEnginePackets()
{
// I know, ugly af. I need to find a better way
netPacketProcessor.RegisterNestedType(AABBNetPacker.Write, AABBNetPacker.Read);
netPacketProcessor.RegisterNestedType(AABB2DNetPacker.Write, AABB2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(AABB3DNetPacker.Write, AABB3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(CircleNetPacker.Write, CircleNetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorHSVNetPacker.Write, ColorHSVNetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorRGBANetPacker.Write, ColorRGBANetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorHSVANetPacker.Write, ColorHSVANetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorRGBNetPacker.Write, ColorRGBNetPacker.Read);
netPacketProcessor.RegisterNestedType(Line2DEquationNetPacker.Write, Line2DEquationNetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorRGBANetPacker.Write, ColorRGBANetPacker.Read);
netPacketProcessor.RegisterNestedType(Line2DNetPacker.Write, Line2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Line2DEquationNetPacker.Write, Line2DEquationNetPacker.Read);
netPacketProcessor.RegisterNestedType(Line3DNetPacker.Write, Line3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Projection1DNetPacker.Write, Projection1DNetPacker.Read);
netPacketProcessor.RegisterNestedType(QuaternionNetPacker.Write, QuaternionNetPacker.Read);
netPacketProcessor.RegisterNestedType(Ray2DNetPacker.Write, Ray2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Ray3DNetPacker.Write, Ray3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Shape2DNetPacker.Write, Shape2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Sphere3DNetPacker.Write, Sphere3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(TriangleNetPacker.Write, TriangleNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector2DNetPacker.Write, Vector2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector2DIntNetPacker.Write, Vector2DIntNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector3DIntNetPacker.Write, Vector3DIntNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector4DNetPacker.Write, Vector4DNetPacker.Read);
}
public INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback)

View File

@@ -88,15 +88,15 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
private void PollEvents(IUniverse sender, IUniverse.UpdateArguments args) => Manager.PollEvents();
protected override void OnEnteredUniverse(IUniverse universe)
public override void EnterUniverse(IUniverse universe)
{
base.OnEnteredUniverse(universe);
base.EnterUniverse(universe);
universe.OnPostUpdate.AddListener(PollEvents);
}
protected override void OnExitedUniverse(IUniverse universe)
public override void ExitUniverse(IUniverse universe)
{
base.OnExitedUniverse(universe);
base.ExitUniverse(universe);
universe.OnPostUpdate.RemoveListener(PollEvents);
}
}

View File

@@ -2,21 +2,21 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class AABBNetPacker
internal static class AABB2DNetPacker
{
internal static void Write(NetDataWriter writer, AABB data)
internal static void Write(NetDataWriter writer, AABB2D data)
{
Vector2DNetPacker.Write(writer, data.LowerBoundary);
Vector2DNetPacker.Write(writer, data.UpperBoundary);
}
internal static AABB Read(NetDataReader reader)
internal static AABB2D Read(NetDataReader reader)
{
Vector2D lowerBoundary = Vector2DNetPacker.Read(reader);
Vector2D upperBoundary = Vector2DNetPacker.Read(reader);
return new AABB(lowerBoundary, upperBoundary);
return new AABB2D(lowerBoundary, upperBoundary);
}
}

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class AABB3DNetPacker
{
internal static void Write(NetDataWriter writer, AABB3D data)
{
Vector3DNetPacker.Write(writer, data.LowerBoundary);
Vector3DNetPacker.Write(writer, data.UpperBoundary);
}
internal static AABB3D Read(NetDataReader reader)
{
Vector3D lowerBoundary = Vector3DNetPacker.Read(reader);
Vector3D upperBoundary = Vector3DNetPacker.Read(reader);
return new AABB3D(lowerBoundary, upperBoundary);
}
}

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class CircleNetPacker
{

View File

@@ -0,0 +1,26 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class ColorHSVANetPacker
{
internal static void Write(NetDataWriter writer, ColorHSVA data)
{
writer.Put(data.Hue);
writer.Put(data.Saturation);
writer.Put(data.Value);
writer.Put(data.Alpha);
}
internal static ColorHSVA Read(NetDataReader reader)
{
float hue = reader.GetFloat();
float saturation = reader.GetFloat();
float value = reader.GetFloat();
float alpha = reader.GetFloat();
return new ColorHSVA(hue, saturation, value, alpha);
}
}

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class ColorHSVNetPacker
{

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class ColorRGBANetPacker
{

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class ColorRGBNetPacker
{

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class Line2DEquationNetPacker
{

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class Line2DNetPacker
{

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class Line3DNetPacker
{
internal static void Write(NetDataWriter writer, Line3D data)
{
Vector3DNetPacker.Write(writer, data.From);
Vector3DNetPacker.Write(writer, data.To);
}
internal static Line3D Read(NetDataReader reader)
{
Vector3D from = Vector3DNetPacker.Read(reader);
Vector3D to = Vector3DNetPacker.Read(reader);
return new Line3D(from, to);
}
}

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class Projection1DNetPacker
{

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class QuaternionNetPacker
{

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class Ray2DNetPacker
{
internal static void Write(NetDataWriter writer, Ray2D data)
{
Vector2DNetPacker.Write(writer, data.Origin);
Vector2DNetPacker.Write(writer, data.Direction);
}
internal static Ray2D Read(NetDataReader reader)
{
Vector2D from = Vector2DNetPacker.Read(reader);
Vector2D direction = Vector2DNetPacker.Read(reader);
return new Ray2D(from, direction);
}
}

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class Ray3DNetPacker
{
internal static void Write(NetDataWriter writer, Ray3D data)
{
Vector3DNetPacker.Write(writer, data.Origin);
Vector3DNetPacker.Write(writer, data.Direction);
}
internal static Ray3D Read(NetDataReader reader)
{
Vector3D from = Vector3DNetPacker.Read(reader);
Vector3D direction = Vector3DNetPacker.Read(reader);
return new Ray3D(from, direction);
}
}

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class Shape2DNetPacker
{

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class Sphere3DNetPacker
{
internal static void Write(NetDataWriter writer, Sphere3D data)
{
Vector3DNetPacker.Write(writer, data.Center);
writer.Put(data.Radius);
}
internal static Sphere3D Read(NetDataReader reader)
{
Vector3D center = Vector3DNetPacker.Read(reader);
float radius = reader.GetFloat();
return new Sphere3D(center, radius);
}
}

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class TriangleNetPacker
{

View File

@@ -0,0 +1,22 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class Vector2DIntNetPacker
{
internal static void Write(NetDataWriter writer, Vector2DInt data)
{
writer.Put(data.X);
writer.Put(data.Y);
}
internal static Vector2DInt Read(NetDataReader reader)
{
int x = reader.GetInt();
int y = reader.GetInt();
return new Vector2DInt(x, y);
}
}

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class Vector2DNetPacker
{

View File

@@ -0,0 +1,24 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class Vector3DIntNetPacker
{
internal static void Write(NetDataWriter writer, Vector3DInt data)
{
writer.Put(data.X);
writer.Put(data.Y);
writer.Put(data.Z);
}
internal static Vector3DInt Read(NetDataReader reader)
{
int x = reader.GetInt();
int y = reader.GetInt();
int z = reader.GetInt();
return new Vector3DInt(x, y, z);
}
}

View File

@@ -2,7 +2,7 @@ using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network;
namespace Engine.Systems.Network.Packers;
internal static class Vector3DNetPacker
{

View File

@@ -0,0 +1,26 @@
using LiteNetLib.Utils;
using Engine.Core;
namespace Engine.Systems.Network.Packers;
internal static class Vector4DNetPacker
{
internal static void Write(NetDataWriter writer, Vector4D data)
{
writer.Put(data.X);
writer.Put(data.Y);
writer.Put(data.Z);
writer.Put(data.W);
}
internal static Vector4D Read(NetDataReader reader)
{
float x = reader.GetFloat();
float y = reader.GetFloat();
float z = reader.GetFloat();
float w = reader.GetFloat();
return new Vector4D(x, y, z, w);
}
}

View File

@@ -10,13 +10,13 @@ namespace Engine.Integration.MonoGame;
public interface ISpriteBatch
{
void Begin(SpriteSortMode sortMode = SpriteSortMode.Deferred, BlendState? blendState = null, SamplerState? samplerState = null, DepthStencilState? depthStencilState = null, RasterizerState? rasterizerState = null, Effect? effect = null, Matrix? transformMatrix = null);
void Draw(Texture2D texture, Vector2D position, AABB? sourceAABB, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth);
void Draw(Texture2D texture, Vector2D position, AABB? sourceAABB, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth);
void Draw(Texture2D texture, AABB destinationAABB, AABB? sourceAABB, Color color, float rotation, Vector2D origin, SpriteEffects effects, float layerDepth);
void Draw(Texture2D texture, Vector2D position, AABB? sourceAABB, Color color);
void Draw(Texture2D texture, AABB destinationAABB, AABB? sourceAABB, Color color);
void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth);
void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth);
void Draw(Texture2D texture, AABB2D destinationAABB, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, SpriteEffects effects, float layerDepth);
void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color);
void Draw(Texture2D texture, AABB2D destinationAABB, AABB2D? sourceAABB, Color color);
void Draw(Texture2D texture, Vector2D position, Color color);
void Draw(Texture2D texture, AABB destinationAABB, Color color);
void Draw(Texture2D texture, AABB2D destinationAABB, Color color);
void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color);
void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth);
void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth);

View File

@@ -4,7 +4,7 @@ using Engine.Core;
namespace Engine.Integration.MonoGame;
public class LoadContentManager : Behaviour, IFirstFrameUpdate
public class LoadContentManager : Behaviour, IEnterUniverse, IExitUniverse, IFirstFrameUpdate
{
// We use Ascending order because we are using reverse for loop to call them
private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
@@ -20,14 +20,14 @@ public class LoadContentManager : Behaviour, IFirstFrameUpdate
monoGameWindowContainer = Universe.FindRequiredBehaviour<MonoGameWindowContainer>();
}
protected override void OnEnteredUniverse(IUniverse universe)
public void EnterUniverse(IUniverse universe)
{
loadContents.Assign(universe);
universe.OnPreUpdate.AddListener(OnPreUpdate);
}
protected override void OnExitedUniverse(IUniverse universe)
public void ExitUniverse(IUniverse universe)
{
loadContents.Unassign();

View File

@@ -5,11 +5,11 @@ using Engine.Core;
namespace Engine.Integration.MonoGame;
public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPreDraw
public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, IPreDraw
{
public event MatrixTransformChangedArguments? OnMatrixTransformChanged = null;
public event ViewportChangedArguments? OnViewportChanged = null;
public event ZoomChangedArguments? OnZoomChanged = null;
public Event<MonoGameCamera2D> OnMatrixTransformChanged { get; } = new();
public Event<MonoGameCamera2D> OnViewportChanged { get; } = new();
public Event<MonoGameCamera2D> OnZoomChanged { get; } = new();
private Matrix _matrixTransform = Matrix.Identity;
@@ -28,7 +28,7 @@ public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPr
return;
_matrixTransform = value;
OnMatrixTransformChanged?.Invoke(this);
OnMatrixTransformChanged.Invoke(this);
}
}
@@ -47,7 +47,7 @@ public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPr
return;
_viewport = value;
OnViewportChanged?.Invoke(this);
OnViewportChanged.Invoke(this);
}
}
@@ -62,7 +62,7 @@ public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPr
return;
_zoom = newValue;
OnZoomChanged?.Invoke(this);
OnZoomChanged.Invoke(this);
}
}
@@ -102,8 +102,4 @@ public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPr
protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
protected sealed override void FinalizeInternal() => Transform = null!;
public delegate void MatrixTransformChangedArguments(MonoGameCamera2D sender);
public delegate void ViewportChangedArguments(MonoGameCamera2D sender);
public delegate void ZoomChangedArguments(MonoGameCamera2D sender);
}

View File

@@ -0,0 +1,131 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Engine.Core;
namespace Engine.Integration.MonoGame;
public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDraw
{
public Event<MonoGameCamera3D, ViewChangedArguments> OnViewChanged { get; } = new();
public Event<MonoGameCamera3D, ProjectionChangedArguments> OnProjectionChanged { get; } = new();
public Event<MonoGameCamera3D, ViewportChangedArguments> OnViewportChanged { get; } = new();
public Event<MonoGameCamera3D, FieldOfViewChangedArguments> OnFieldOfViewChanged { get; } = new();
private Matrix _view = Matrix.Identity;
private Matrix _projection = Matrix.Identity;
private Viewport _viewport = default;
private float _fieldOfView = 1f;
public GraphicsDeviceManager Graphics { get; private set; } = null!;
public ITransform3D Transform { get; private set; } = null!;
public Matrix View
{
get => _view;
set
{
if (_view == value)
return;
Matrix previousView = _view;
_view = value;
OnViewChanged.Invoke(this, new(previousView));
}
}
public Matrix Projection
{
get => _projection;
set
{
if (_projection == value)
return;
Matrix previousProjection = _projection;
_projection = value;
OnProjectionChanged.Invoke(this, new(previousProjection));
}
}
public Vector3D Position
{
get => Transform.Position;
set => Transform.Position = value;
}
public Viewport Viewport
{
get => _viewport;
set
{
if (_viewport.Equals(value))
return;
Viewport previousViewport = _viewport;
_viewport = value;
OnViewportChanged.Invoke(this, new(previousViewport));
}
}
public float FieldOfView
{
get => _fieldOfView;
set
{
float newValue = Math.Max(0.1f, value);
if (_fieldOfView == newValue)
return;
float previousFieldOfView = _fieldOfView;
_fieldOfView = newValue;
OnFieldOfViewChanged.Invoke(this, new(previousFieldOfView));
}
}
public Engine.Core.Quaternion Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
// TODO This causes delay since OnPreDraw calls assuming this is called in in Update
public Ray3D ScreenToWorldRay(Vector2D screenPosition)
{
Vector3 nearPoint = new(screenPosition.X, screenPosition.Y, 0f);
Vector3 farPoint = new(screenPosition.X, screenPosition.Y, 1f);
Vector3 worldNear = Viewport.Unproject(nearPoint, _projection, _view, Matrix.Identity);
Vector3 worldFar = Viewport.Unproject(farPoint, _projection, _view, Matrix.Identity);
Vector3 direction = Vector3.Normalize(worldFar - worldNear);
return new(worldNear.ToVector3D(), direction.ToVector3D());
}
public Vector2D WorldToScreenPosition(Vector3D worldPosition) => Viewport.Project(worldPosition.ToVector3(), _projection, _view, Matrix.Identity).ToVector3D();
public void FirstActiveFrame()
{
Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics;
Viewport = Graphics.GraphicsDevice.Viewport;
}
public void PreDraw()
{
Vector3 cameraPosition = Position.ToVector3();
View = Matrix.CreateLookAt(
cameraPosition,
Transform.Forward.ToVector3() + cameraPosition,
Transform.Up.ToVector3()
);
Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Viewport.AspectRatio, 0.1f, 100f);
}
protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform3D>();
protected sealed override void FinalizeInternal() => Transform = null!;
public readonly record struct ViewChangedArguments(Matrix PreviousView);
public readonly record struct ProjectionChangedArguments(Matrix PreviousProjection);
public readonly record struct ViewportChangedArguments(Viewport PreviousViewport);
public readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView);
}

View File

@@ -14,25 +14,25 @@ public class SpriteBatchWrapper(GraphicsDevice graphicsDevice) : ISpriteBatch
public void Begin(SpriteSortMode sortMode = SpriteSortMode.Deferred, BlendState? blendState = null, SamplerState? samplerState = null, DepthStencilState? depthStencilState = null, RasterizerState? rasterizerState = null, Effect? effect = null, Matrix? transformMatrix = null)
=> spriteBatch.Begin(sortMode, blendState, samplerState, depthStencilState, rasterizerState, effect, transformMatrix);
public void Draw(Texture2D texture, Vector2D position, AABB? sourceAABB, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth)
public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, Vector2D scale, SpriteEffects effects, float layerDepth)
=> spriteBatch.Draw(texture, position.ToDisplayVector2(), sourceAABB?.ToRectangle(), color, rotation, origin.ToDisplayVector2(), scale.ToDisplayVector2(), effects, layerDepth);
public void Draw(Texture2D texture, Vector2D position, AABB? sourceAABB, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth)
public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, float scale, SpriteEffects effects, float layerDepth)
=> spriteBatch.Draw(texture, position.ToDisplayVector2(), sourceAABB?.ToRectangle(), color, rotation, origin.ToDisplayVector2(), scale, effects, layerDepth);
public void Draw(Texture2D texture, AABB destinationAABB, AABB? sourceAABB, Color color, float rotation, Vector2D origin, SpriteEffects effects, float layerDepth)
public void Draw(Texture2D texture, AABB2D destinationAABB, AABB2D? sourceAABB, Color color, float rotation, Vector2D origin, SpriteEffects effects, float layerDepth)
=> spriteBatch.Draw(texture, destinationAABB.ToRectangle(), sourceAABB?.ToRectangle(), color, rotation, origin.ToDisplayVector2(), effects, layerDepth);
public void Draw(Texture2D texture, Vector2D position, AABB? sourceAABB, Color color)
public void Draw(Texture2D texture, Vector2D position, AABB2D? sourceAABB, Color color)
=> spriteBatch.Draw(texture, position.ToDisplayVector2(), sourceAABB?.ToRectangle(), color);
public void Draw(Texture2D texture, AABB destinationAABB, AABB? sourceAABB, Color color)
public void Draw(Texture2D texture, AABB2D destinationAABB, AABB2D? sourceAABB, Color color)
=> spriteBatch.Draw(texture, destinationAABB.ToRectangle(), sourceAABB?.ToRectangle(), color);
public void Draw(Texture2D texture, Vector2D position, Color color)
=> spriteBatch.Draw(texture, position.ToDisplayVector2(), color);
public void Draw(Texture2D texture, AABB destinationAABB, Color color)
public void Draw(Texture2D texture, AABB2D destinationAABB, Color color)
=> spriteBatch.Draw(texture, destinationAABB.ToRectangle(), color);
public void DrawString(SpriteFont spriteFont, string text, Vector2D position, Color color)

View File

@@ -4,7 +4,7 @@ using Engine.Core;
namespace Engine.Integration.MonoGame;
public class SpriteBatcher : BehaviourBase, IFirstFrameUpdate, IDraw
public class SpriteBatcher : Behaviour, IFirstFrameUpdate, IDraw
{
private static Comparer<int> SortByPriority() => Comparer<int>.Create((x, y) => y.CompareTo(x));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;

View File

@@ -6,7 +6,7 @@ using Engine.Core;
namespace Engine.Integration.MonoGame;
public class TriangleBatcher : BehaviourBase, ITriangleBatch, IFirstFrameUpdate, IDraw
public class TriangleBatcher : Behaviour, ITriangleBatch, IFirstFrameUpdate, IDraw
{
private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;

View File

@@ -35,6 +35,15 @@ public static class EngineConverterExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ToVector2(this Vector2D vector) => new(vector.X, vector.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3D ToVector3D(this Vector3 vector) => new(vector.X, vector.Y, vector.Z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 ToVector3(this Vector3D vector) => new(vector.X, vector.Y, vector.Z);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Microsoft.Xna.Framework.Quaternion ToXnaQuaternion(this Core.Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ToDisplayVector2(this Vector2D vector) => vector.Scale(screenScale).ToVector2();
@@ -51,7 +60,7 @@ public static class EngineConverterExtensions
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle ToRectangle(this AABB aabb) => new()
public static Rectangle ToRectangle(this AABB2D aabb) => new()
{
X = (int)(aabb.LowerBoundary.X * screenScale.X),
Y = (int)(aabb.LowerBoundary.Y * screenScale.Y),

View File

@@ -4,7 +4,7 @@ using Engine.Core.Serialization;
namespace Engine.Integration.MonoGame;
[IgnoreSerialization]
public class MonoGameWindowContainer(MonoGameWindow GameWindow) : BehaviourBase
public class MonoGameWindowContainer(MonoGameWindow GameWindow) : Behaviour
{
public MonoGameWindow Window { get; } = GameWindow;
}

View File

@@ -8,33 +8,33 @@ using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class AABBConverter : EngineTypeYamlSerializerBase<AABB>
public class AABB2DConverter : EngineTypeYamlSerializerBase<AABB2D>
{
public override AABB Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
public override AABB2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(AABB.LowerBoundary)) != 0)
throw new ArgumentException($"{nameof(AABB)} mapping must start with {nameof(AABB.LowerBoundary)}");
if (parser.Consume<Scalar>().Value.CompareTo(nameof(AABB2D.LowerBoundary)) != 0)
throw new ArgumentException($"{nameof(AABB2D)} mapping must start with {nameof(AABB2D.LowerBoundary)}");
Vector2D lowerBoundary = (Vector2D)rootDeserializer(typeof(Vector2D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(AABB.UpperBoundary)) != 0)
throw new ArgumentException($"{nameof(AABB)} mapping must end with {nameof(AABB.UpperBoundary)}");
if (parser.Consume<Scalar>().Value.CompareTo(nameof(AABB2D.UpperBoundary)) != 0)
throw new ArgumentException($"{nameof(AABB2D)} mapping must end with {nameof(AABB2D.UpperBoundary)}");
Vector2D upperBoundary = (Vector2D)rootDeserializer(typeof(Vector2D))!;
parser.Consume<MappingEnd>();
return new AABB(lowerBoundary, upperBoundary);
return new AABB2D(lowerBoundary, upperBoundary);
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
AABB aabb = (AABB)value!;
AABB2D aabb = (AABB2D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(AABB.LowerBoundary)));
emitter.Emit(new Scalar(nameof(AABB2D.LowerBoundary)));
serializer(aabb.LowerBoundary, typeof(Vector2D));
emitter.Emit(new Scalar(nameof(AABB.UpperBoundary)));
emitter.Emit(new Scalar(nameof(AABB2D.UpperBoundary)));
serializer(aabb.UpperBoundary, typeof(Vector2D));
emitter.Emit(new MappingEnd());
}

View File

@@ -0,0 +1,41 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class AABB3DConverter : EngineTypeYamlSerializerBase<AABB3D>
{
public override AABB3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(AABB3D.LowerBoundary)) != 0)
throw new ArgumentException($"{nameof(AABB3D)} mapping must start with {nameof(AABB3D.LowerBoundary)}");
Vector3D lowerBoundary = (Vector3D)rootDeserializer(typeof(Vector3D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(AABB3D.UpperBoundary)) != 0)
throw new ArgumentException($"{nameof(AABB3D)} mapping must end with {nameof(AABB3D.UpperBoundary)}");
Vector3D upperBoundary = (Vector3D)rootDeserializer(typeof(Vector3D))!;
parser.Consume<MappingEnd>();
return new AABB3D(lowerBoundary, upperBoundary);
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
AABB3D aabb = (AABB3D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(AABB3D.LowerBoundary)));
serializer(aabb.LowerBoundary, typeof(Vector3D));
emitter.Emit(new Scalar(nameof(AABB3D.UpperBoundary)));
serializer(aabb.UpperBoundary, typeof(Vector3D));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,28 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class ColorHSVAConverter : EngineTypeYamlSerializerBase<ColorHSVA>
{
private static readonly int SUBSTRING_START_LENGTH = nameof(ColorHSVA).Length + 1;
public override ColorHSVA Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string value = parser.Consume<Scalar>().Value;
string insideParenthesis = value[SUBSTRING_START_LENGTH..^1];
string[] values = insideParenthesis.Split(", ");
return new ColorHSVA(float.Parse(values[0]), float.Parse(values[1]), float.Parse(values[2]), float.Parse(values[3]));
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
ColorHSVA hsva = (ColorHSVA)value!;
emitter.Emit(new Scalar($"{nameof(ColorHSVA)}({hsva.Hue}, {hsva.Saturation}, {hsva.Value}, {hsva.Alpha})"));
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class Line3DConverter : EngineTypeYamlSerializerBase<Line3D>
{
public override Line3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Line3D.From)) != 0)
throw new ArgumentException($"{nameof(Line3D)} mapping must start with {nameof(Line3D.From)}");
Vector3D from = (Vector3D)rootDeserializer(typeof(Vector3D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Line3D.To)) != 0)
throw new ArgumentException($"{nameof(Line3D)} mapping must end with {nameof(Line3D.To)}");
Vector3D to = (Vector3D)rootDeserializer(typeof(Vector3D))!;
parser.Consume<MappingEnd>();
return new Line3D(from, to);
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Line3D line3D = (Line3D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(Line3D.From)));
serializer(line3D.From, typeof(Vector3D));
emitter.Emit(new Scalar(nameof(Line3D.To)));
serializer(line3D.To, typeof(Vector3D));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class Ray2DConverter : EngineTypeYamlSerializerBase<Ray2D>
{
public override Ray2D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Ray2D.Origin)) != 0)
throw new ArgumentException($"{nameof(Ray2D)} mapping must start with {nameof(Ray2D.Origin)}");
Vector2D origin = (Vector2D)rootDeserializer(typeof(Vector2D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Ray2D.Direction)) != 0)
throw new ArgumentException($"{nameof(Ray2D)} mapping must end with {nameof(Ray2D.Direction)}");
Vector2D direction = (Vector2D)rootDeserializer(typeof(Vector2D))!;
parser.Consume<MappingEnd>();
return new Ray2D(origin, direction);
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Ray2D ray2D = (Ray2D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(ray2D.Origin)));
serializer(ray2D.Origin, typeof(Vector2D));
emitter.Emit(new Scalar(nameof(ray2D.Direction)));
serializer(ray2D.Direction, typeof(Vector2D));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class Ray3DConverter : EngineTypeYamlSerializerBase<Ray3D>
{
public override Ray3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Ray3D.Origin)) != 0)
throw new ArgumentException($"{nameof(Ray3D)} mapping must start with {nameof(Ray3D.Origin)}");
Vector3D origin = (Vector3D)rootDeserializer(typeof(Vector3D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Ray3D.Direction)) != 0)
throw new ArgumentException($"{nameof(Ray3D)} mapping must end with {nameof(Ray3D.Direction)}");
Vector3D direction = (Vector3D)rootDeserializer(typeof(Vector3D))!;
parser.Consume<MappingEnd>();
return new Ray3D(origin, direction);
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Ray3D ray3D = (Ray3D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(ray3D.Origin)));
serializer(ray3D.Origin, typeof(Vector3D));
emitter.Emit(new Scalar(nameof(ray3D.Direction)));
serializer(ray3D.Direction, typeof(Vector3D));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class Sphere3DConverter : EngineTypeYamlSerializerBase<Sphere3D>
{
public override Sphere3D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
parser.Consume<MappingStart>();
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Sphere3D.Center)) != 0)
throw new ArgumentException($"{nameof(Sphere3D)} mapping must start with {nameof(Sphere3D.Center)}");
Vector3D lowerBoundary = (Vector3D)rootDeserializer(typeof(Vector3D))!;
if (parser.Consume<Scalar>().Value.CompareTo(nameof(Sphere3D.Radius)) != 0)
throw new ArgumentException($"{nameof(Sphere3D)} mapping must end with {nameof(Sphere3D.Radius)}");
float radius = (float)rootDeserializer(typeof(float))!;
parser.Consume<MappingEnd>();
return new Sphere3D(lowerBoundary, radius);
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Sphere3D sphere = (Sphere3D)value!;
emitter.Emit(new MappingStart());
emitter.Emit(new Scalar(nameof(Sphere3D.Center)));
serializer(sphere.Center, typeof(Vector3D));
emitter.Emit(new Scalar(nameof(Sphere3D.Radius)));
serializer(sphere.Radius, typeof(float));
emitter.Emit(new MappingEnd());
}
}

View File

@@ -0,0 +1,28 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class Vector2DIntConverter : EngineTypeYamlSerializerBase<Vector2DInt>
{
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector2DInt).Length + 1;
public override Vector2DInt Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string value = parser.Consume<Scalar>().Value;
string insideParenthesis = value[SUBSTRING_START_LENGTH..^1];
string[] values = insideParenthesis.Split(", ");
return new Vector2DInt(int.Parse(values[0]), int.Parse(values[1]));
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Vector2DInt vector2DInt = (Vector2DInt)value!;
emitter.Emit(new Scalar($"{nameof(Vector2DInt)}({vector2DInt.X}, {vector2DInt.Y})"));
}
}

View File

@@ -0,0 +1,28 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class Vector3DIntConverter : EngineTypeYamlSerializerBase<Vector3DInt>
{
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector3DInt).Length + 1;
public override Vector3DInt Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string value = parser.Consume<Scalar>().Value;
string insideParenthesis = value[SUBSTRING_START_LENGTH..^1];
string[] values = insideParenthesis.Split(", ");
return new Vector3DInt(int.Parse(values[0]), int.Parse(values[1]), int.Parse(values[2]));
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Vector3DInt vector3DInt = (Vector3DInt)value!;
emitter.Emit(new Scalar($"{nameof(Vector3DInt)}({vector3DInt.X}, {vector3DInt.Y}, {vector3DInt.Z})"));
}
}

View File

@@ -0,0 +1,28 @@
using System;
using Engine.Core;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
namespace Engine.Serializers.Yaml;
public class Vector4DConverter : EngineTypeYamlSerializerBase<Vector4D>
{
private static readonly int SUBSTRING_START_LENGTH = nameof(Vector4D).Length + 1;
public override Vector4D Read(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
string value = parser.Consume<Scalar>().Value;
string insideParenthesis = value[SUBSTRING_START_LENGTH..^1];
string[] values = insideParenthesis.Split(", ");
return new Vector4D(float.Parse(values[0]), float.Parse(values[1]), float.Parse(values[2]), float.Parse(values[3]));
}
public override void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
Vector4D vector4D = (Vector4D)value!;
emitter.Emit(new Scalar($"{nameof(Vector4D)}({vector4D.X}, {vector4D.Y}, {vector4D.Z}, {vector4D.W})"));
}
}

View File

@@ -73,7 +73,7 @@ public class CollisionDetector2D : ICollisionDetector2D
Vector2D projectionVector = vertices[indexProjection].FromTo(vertices[(indexProjection + 1) % count]).Perpendicular().Normalized;
Projection1D shapeProjection = shapeCollider.ShapeWorld.ToProjection(projectionVector);
Projection1D circleProjection = circleCollider.CircleWorld.ToProjection(projectionVector);
Projection1D circleProjection = circleCollider.CircleWorld.ProjectTo(projectionVector);
if (!shapeProjection.Overlaps(circleProjection, out float depth))
return false;
@@ -86,7 +86,7 @@ public class CollisionDetector2D : ICollisionDetector2D
Vector2D shapeToCircleProjectionVector = shapeCollider.Transform.Position.FromTo(circleCollider.CircleWorld.Center).Normalized;
Projection1D shapeProjection = shapeCollider.ShapeWorld.ToProjection(shapeToCircleProjectionVector);
Projection1D circleProjection = circleCollider.CircleWorld.ToProjection(shapeToCircleProjectionVector);
Projection1D circleProjection = circleCollider.CircleWorld.ProjectTo(shapeToCircleProjectionVector);
if (!shapeProjection.Overlaps(circleProjection, out float depth))
return false;
@@ -104,8 +104,8 @@ public class CollisionDetector2D : ICollisionDetector2D
Vector2D leftToRightCenterProjectionVector = left.CircleWorld.Center.FromTo(right.CircleWorld.Center).Normalized;
Projection1D leftProjection = left.CircleWorld.ToProjection(leftToRightCenterProjectionVector);
Projection1D rightProjection = right.CircleWorld.ToProjection(leftToRightCenterProjectionVector);
Projection1D leftProjection = left.CircleWorld.ProjectTo(leftToRightCenterProjectionVector);
Projection1D rightProjection = right.CircleWorld.ProjectTo(leftToRightCenterProjectionVector);
bool collision = leftProjection.Overlaps(rightProjection, out float depth);

View File

@@ -68,11 +68,11 @@ public static class Physics2D
return isOverlapping;
}
public static bool Overlaps(this AABB aabb, Vector2D point)
public static bool Overlaps(this AABB2D aabb, Vector2D point)
=> point.X >= aabb.LowerBoundary.X && point.X <= aabb.UpperBoundary.X &&
point.Y >= aabb.LowerBoundary.Y && point.Y <= aabb.UpperBoundary.Y;
public static bool Overlaps(this AABB left, AABB right)
public static bool Overlaps(this AABB2D left, AABB2D right)
=> left.LowerBoundary.X <= right.UpperBoundary.X && left.UpperBoundary.X >= right.LowerBoundary.X &&
left.LowerBoundary.Y <= right.UpperBoundary.Y && left.UpperBoundary.Y >= right.LowerBoundary.Y;

View File

@@ -4,7 +4,7 @@ using Engine.Core;
namespace Engine.Physics2D;
public class PhysicsEngine2D : Behaviour, IPreUpdate, IPhysicsEngine2D
public class PhysicsEngine2D : Behaviour, IEnterUniverse, IExitUniverse, IPreUpdate, IPhysicsEngine2D
{
public Event<IPhysicsEngine2D, float> OnPhysicsIteration { get; } = new();
public Event<IPhysicsEngine2D, float> OnPhysicsStep { get; } = new();
@@ -224,7 +224,7 @@ public class PhysicsEngine2D : Behaviour, IPreUpdate, IPhysicsEngine2D
rigidBody.Transform.Rotation += rigidBody.AngularVelocity * intervalDeltaTime;
}
protected override void OnEnteredUniverse(IUniverse universe)
public void EnterUniverse(IUniverse universe)
{
physicsPreUpdateCollector.Assign(universe);
physicsUpdateCollector.Assign(universe);
@@ -234,7 +234,7 @@ public class PhysicsEngine2D : Behaviour, IPreUpdate, IPhysicsEngine2D
rigidBodyCollector.Assign(universe);
}
protected override void OnExitedUniverse(IUniverse universe)
public void ExitUniverse(IUniverse universe)
{
physicsPreUpdateCollector.Unassign();
physicsUpdateCollector.Unassign();

View File

@@ -10,7 +10,7 @@ namespace Engine.Systems.Network;
/// <summary>
/// Intermediary manager that looks up in it's hierarchy for a <see cref="INetworkCommunicator"/> to route/broadcast it's received packets to their destinations.
/// </summary>
public class NetworkManager : Behaviour, INetworkManager
public class NetworkManager : Behaviour, IEnterUniverse, IExitUniverse, INetworkManager
{
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> clientPacketArrivalMethods = [];
private readonly Dictionary<Type, Dictionary<Type, List<MethodInfo>>> serverPacketArrivalMethods = [];
@@ -230,8 +230,8 @@ public class NetworkManager : Behaviour, INetworkManager
UnregisterPacketRoutersFor(removedBehaviour, serverPacketRouters, serverPacketArrivalMethods);
}
protected override void OnExitedUniverse(IUniverse universe) => _networkEntityCollector.Unassign();
protected override void OnEnteredUniverse(IUniverse universe)
public void ExitUniverse(IUniverse universe) => _networkEntityCollector.Unassign();
public void EnterUniverse(IUniverse universe)
{
_networkEntityCollector.Assign(universe);
NetworkCommunicator = BehaviourController.GetRequiredBehaviourInParent<INetworkCommunicator>();

View File

@@ -2,7 +2,7 @@ using Engine.Core;
namespace Engine.Systems.Time;
public class Stopwatch : Behaviour, IUpdate, IStopwatch
public class Stopwatch : Behaviour, IUpdate, IEnterUniverse, IExitUniverse, IStopwatch
{
public Event<IReadOnlyStopwatch> OnStarted { get; } = new();
public Event<IReadOnlyStopwatch, IReadOnlyStopwatch.StopwatchDeltaArguments> OnDelta { get; } = new();
@@ -49,7 +49,7 @@ public class Stopwatch : Behaviour, IUpdate, IStopwatch
OnDelta?.Invoke(this, new(delta));
}
protected override void OnEnteredUniverse(IUniverse universe)
public void EnterUniverse(IUniverse universe)
{
if (!shouldBeTicking || State is TimerState.Ticking)
return;
@@ -60,7 +60,7 @@ public class Stopwatch : Behaviour, IUpdate, IStopwatch
StartStopwatch();
}
protected override void OnExitedUniverse(IUniverse universe)
public void ExitUniverse(IUniverse universe)
{
if (!shouldBeTicking || State is not TimerState.Ticking)
return;
@@ -88,8 +88,10 @@ public class Stopwatch : Behaviour, IUpdate, IStopwatch
OnStarted?.Invoke(this);
}
protected override void OnFinalize()
protected override void FinalizeInternal()
{
base.FinalizeInternal();
Time = 0f;
State = TimerState.Idle;
shouldBeTicking = false;

View File

@@ -30,9 +30,9 @@ public class TickerStopwatch : Stopwatch, ITicker
}
}
protected override void OnFinalize()
protected override void FinalizeInternal()
{
base.OnFinalize();
base.FinalizeInternal();
TickCounter = 0;
nextTick = 0f;

View File

@@ -30,9 +30,9 @@ public class TickerTimer : Timer, ITicker
}
}
protected override void OnFinalize()
protected override void FinalizeInternal()
{
base.OnFinalize();
base.FinalizeInternal();
TickCounter = 0;
nextTick = 0f;

View File

@@ -2,7 +2,7 @@ using Engine.Core;
namespace Engine.Systems.Time;
public class Timer : Behaviour, IUpdate, ITimer
public class Timer : Behaviour, IUpdate, IEnterUniverse, IExitUniverse, ITimer
{
public Event<IReadOnlyTimer> OnStarted { get; } = new();
public Event<IReadOnlyTimer, IReadOnlyTimer.TimerDeltaArguments> OnDelta { get; } = new();
@@ -67,7 +67,7 @@ public class Timer : Behaviour, IUpdate, ITimer
Stop();
}
protected override void OnEnteredUniverse(IUniverse universe)
public void EnterUniverse(IUniverse universe)
{
if (!shouldBeTicking || State is TimerState.Ticking)
return;
@@ -78,7 +78,7 @@ public class Timer : Behaviour, IUpdate, ITimer
StartTimer();
}
protected override void OnExitedUniverse(IUniverse universe)
public void ExitUniverse(IUniverse universe)
{
if (!shouldBeTicking || State is not TimerState.Ticking)
return;
@@ -106,8 +106,10 @@ public class Timer : Behaviour, IUpdate, ITimer
OnStarted?.Invoke(this);
}
protected override void OnFinalize()
protected override void FinalizeInternal()
{
base.FinalizeInternal();
StartTime = 0f;
Remaining = 0f;
State = TimerState.Idle;

View File

@@ -0,0 +1,24 @@
using Engine.Core;
namespace Engine.Systems.Tween;
public static class TweenAABB2DExtensions
{
private static readonly BoxedPool<AABB2D> boxedAABBPool = new(2);
public static ITween TweenAABB(this AABB2D initialAABB, ITweenManager tweenManager, float duration, AABB2D targetAABB, System.Action<AABB2D> setMethod)
{
Boxed<AABB2D> boxedInitial = boxedAABBPool.Get(initialAABB);
Boxed<AABB2D> boxedTarget = boxedAABBPool.Get(targetAABB);
ITween tween = tweenManager.StartTween(duration, t => setMethod?.Invoke(new AABB2D(boxedInitial.Value.LowerBoundary.Lerp(boxedTarget.Value.LowerBoundary, t), boxedInitial.Value.UpperBoundary.Lerp(boxedTarget.Value.UpperBoundary, t))));
tween.OnComplete(() =>
{
boxedAABBPool.Return(boxedInitial);
boxedAABBPool.Return(boxedTarget);
});
return tween;
}
}

View File

@@ -0,0 +1,24 @@
using Engine.Core;
namespace Engine.Systems.Tween;
public static class TweenAABB3DExtensions
{
private static readonly BoxedPool<AABB3D> boxedAABBPool = new(2);
public static ITween TweenAABB(this AABB3D initialAABB, ITweenManager tweenManager, float duration, AABB3D targetAABB, System.Action<AABB3D> setMethod)
{
Boxed<AABB3D> boxedInitial = boxedAABBPool.Get(initialAABB);
Boxed<AABB3D> boxedTarget = boxedAABBPool.Get(targetAABB);
ITween tween = tweenManager.StartTween(duration, t => setMethod?.Invoke(new AABB3D(boxedInitial.Value.LowerBoundary.Lerp(boxedTarget.Value.LowerBoundary, t), boxedInitial.Value.UpperBoundary.Lerp(boxedTarget.Value.UpperBoundary, t))));
tween.OnComplete(() =>
{
boxedAABBPool.Return(boxedInitial);
boxedAABBPool.Return(boxedTarget);
});
return tween;
}
}

View File

@@ -1,24 +0,0 @@
using Engine.Core;
namespace Engine.Systems.Tween;
public static class TweenAABBExtensions
{
private static readonly BoxedPool<AABB> boxedAABBPool = new(2);
public static ITween TweenAABB(this AABB initialAABB, ITweenManager tweenManager, float duration, AABB targetAABB, System.Action<AABB> setMethod)
{
Boxed<AABB> boxedInitial = boxedAABBPool.Get(initialAABB);
Boxed<AABB> boxedTarget = boxedAABBPool.Get(targetAABB);
ITween tween = tweenManager.StartTween(duration, t => setMethod?.Invoke(new AABB(boxedInitial.Value.LowerBoundary.Lerp(boxedTarget.Value.LowerBoundary, t), boxedInitial.Value.UpperBoundary.Lerp(boxedTarget.Value.UpperBoundary, t))));
tween.OnComplete(() =>
{
boxedAABBPool.Return(boxedInitial);
boxedAABBPool.Return(boxedTarget);
});
return tween;
}
}

View File

@@ -0,0 +1,31 @@
using Engine.Core;
namespace Engine.Systems.Tween;
public static class TweenLine3DExtensions
{
private static readonly BoxedPool<Line3D> boxedLine3DPool = new(2);
public static ITween TweenLine3D(this Line3D initialLine3D, ITweenManager tweenManager, float duration, Line3D targetLine3D, System.Action<Line3D> setMethod)
{
Boxed<Line3D> boxedInitial = boxedLine3DPool.Get(initialLine3D);
Boxed<Line3D> boxedTarget = boxedLine3DPool.Get(targetLine3D);
ITween tween = tweenManager.StartTween(duration,
t => setMethod?.Invoke(
new Line3D(
boxedInitial.Value.From.Lerp(boxedTarget.Value.From, t),
boxedInitial.Value.To.Lerp(boxedTarget.Value.To, t)
)
)
);
tween.OnComplete(() =>
{
boxedLine3DPool.Return(boxedInitial);
boxedLine3DPool.Return(boxedTarget);
});
return tween;
}
}

View File

@@ -0,0 +1,31 @@
using Engine.Core;
namespace Engine.Systems.Tween;
public static class TweenRay2DExtensions
{
private static readonly BoxedPool<Ray2D> boxedRay2DPool = new(2);
public static ITween TweenRay2D(this Ray2D initialRay2D, ITweenManager tweenManager, float duration, Ray2D targetRay2D, System.Action<Ray2D> setMethod)
{
Boxed<Ray2D> boxedInitial = boxedRay2DPool.Get(initialRay2D);
Boxed<Ray2D> boxedTarget = boxedRay2DPool.Get(targetRay2D);
ITween tween = tweenManager.StartTween(duration,
t => setMethod?.Invoke(
new Ray2D(
boxedInitial.Value.Origin.Lerp(boxedTarget.Value.Origin, t),
boxedInitial.Value.Direction.Lerp(boxedTarget.Value.Direction, t).Normalized
)
)
);
tween.OnComplete(() =>
{
boxedRay2DPool.Return(boxedInitial);
boxedRay2DPool.Return(boxedTarget);
});
return tween;
}
}

View File

@@ -0,0 +1,31 @@
using Engine.Core;
namespace Engine.Systems.Tween;
public static class TweenRay3DExtensions
{
private static readonly BoxedPool<Ray3D> boxedRay3DPool = new(2);
public static ITween TweenRay3D(this Ray3D initialRay3D, ITweenManager tweenManager, float duration, Ray3D targetRay3D, System.Action<Ray3D> setMethod)
{
Boxed<Ray3D> boxedInitial = boxedRay3DPool.Get(initialRay3D);
Boxed<Ray3D> boxedTarget = boxedRay3DPool.Get(targetRay3D);
ITween tween = tweenManager.StartTween(duration,
t => setMethod?.Invoke(
new Ray3D(
boxedInitial.Value.Origin.Lerp(boxedTarget.Value.Origin, t),
boxedInitial.Value.Direction.Lerp(boxedTarget.Value.Direction, t).Normalized
)
)
);
tween.OnComplete(() =>
{
boxedRay3DPool.Return(boxedInitial);
boxedRay3DPool.Return(boxedTarget);
});
return tween;
}
}

View File

@@ -0,0 +1,31 @@
using Engine.Core;
namespace Engine.Systems.Tween;
public static class TweenSphere3DExtensions
{
private static readonly BoxedPool<Sphere3D> boxedSphere3DPool = new(2);
public static ITween TweenSphere3D(this Sphere3D initialSphere3D, ITweenManager tweenManager, float duration, Sphere3D targetSphere3D, System.Action<Sphere3D> setMethod)
{
Boxed<Sphere3D> boxedInitial = boxedSphere3DPool.Get(initialSphere3D);
Boxed<Sphere3D> boxedTarget = boxedSphere3DPool.Get(targetSphere3D);
ITween tween = tweenManager.StartTween(duration,
t => setMethod?.Invoke(
new Sphere3D(
boxedInitial.Value.Center.Lerp(boxedTarget.Value.Center, t),
boxedInitial.Value.Diameter.Lerp(boxedTarget.Value.Diameter, t)
)
)
);
tween.OnComplete(() =>
{
boxedSphere3DPool.Return(boxedInitial);
boxedSphere3DPool.Return(boxedTarget);
});
return tween;
}
}

View File

@@ -0,0 +1,24 @@
using Engine.Core;
namespace Engine.Systems.Tween;
public static class TweenVector4DExtensions
{
private static readonly BoxedPool<Vector4D> boxedVector4DPool = new(2);
public static ITween TweenVector4D(this Vector4D initialVector4D, ITweenManager tweenManager, float duration, Vector4D targetVector4D, System.Action<Vector4D> setMethod)
{
Boxed<Vector4D> boxedInitial = boxedVector4DPool.Get(initialVector4D);
Boxed<Vector4D> boxedTarget = boxedVector4DPool.Get(targetVector4D);
ITween tween = tweenManager.StartTween(duration, t => setMethod?.Invoke(boxedInitial.Value.Lerp(boxedTarget.Value, t)));
tween.OnComplete(() =>
{
boxedVector4DPool.Return(boxedInitial);
boxedVector4DPool.Return(boxedTarget);
});
return tween;
}
}

View File

@@ -5,7 +5,7 @@ using Engine.Core;
namespace Engine.Systems.Tween;
public class TweenManager : Behaviour, ITweenManager
public class TweenManager : Behaviour, IEnterUniverse, IExitUniverse, ITweenManager
{
private CoroutineManager coroutineManager = null!;
@@ -73,12 +73,12 @@ public class TweenManager : Behaviour, ITweenManager
Return((Tween)tween);
}
protected override void OnEnteredUniverse(IUniverse universe)
public void EnterUniverse(IUniverse universe)
{
coroutineManager = universe.FindRequiredBehaviour<CoroutineManager>();
}
protected override void OnExitedUniverse(IUniverse universe)
public void ExitUniverse(IUniverse universe)
{
coroutineManager = null!;
}