Compare commits
180 Commits
perf/event
...
7e07cd30db
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e07cd30db | |||
| e9ba7867ac | |||
| f28307be80 | |||
| 5f019892f1 | |||
| 0bd2b0618d | |||
| 0691d7092c | |||
| 4ed049573a | |||
| 599774ea8b | |||
| b17f880ecf | |||
| 98bc0693dd | |||
| ac2e160abb | |||
| 30a07dd034 | |||
| f180713f4b | |||
| 2d612ea0d4 | |||
| 5a6883a87f | |||
| a6eb67551d | |||
| 981732ff75 | |||
| 4bdd32b808 | |||
| cac69f5f35 | |||
| 1660915678 | |||
| 1d02b0eba2 | |||
| 2ef7fa6577 | |||
| 193b2c5af0 | |||
| a859c0cbff | |||
| fba64c3854 | |||
| a975cbb56b | |||
| 1664a9ccf7 | |||
| 988a6f67f2 | |||
| 2f32038f04 | |||
| 4b756fa232 | |||
| 0db2cae1bb | |||
| 896f7876c1 | |||
| 56d3112e35 | |||
| 32ec2325dc | |||
| b42f1f1881 | |||
| f8096377b2 | |||
| a9fc819268 | |||
| 1d63391975 | |||
| 61ff0887e2 | |||
| 16344dccc7 | |||
| dc4bea3eef | |||
| d1b2723a70 | |||
| 2f5c04e66b | |||
| f753da1f87 | |||
| 6901159106 | |||
| 7469c9ab0c | |||
| ede90adbdc | |||
| eeaca3a6c7 | |||
| 3b984a0a4b | |||
| c5afb70b18 | |||
| 9d2a192f04 | |||
| 598debc233 | |||
| ab05a89175 | |||
| dbd15cbbc2 | |||
| e051f5cfb4 | |||
| e70b7f112f | |||
| f55ba499b6 | |||
| b75f30f864 | |||
| 6f1f30bd53 | |||
| 92a5c276a4 | |||
| 69bc6573d1 | |||
| 28bc022587 | |||
| 25db60e436 | |||
| 7c62440bba | |||
| 4bec7bce6e | |||
| 8d31372c24 | |||
| a2e704916e | |||
| c7d170fad9 | |||
| 9ccf7b754d | |||
| e3d4899112 | |||
| 566c16d09c | |||
| ae9d4f02ef | |||
| e77772cbc2 | |||
| 4c542df401 | |||
| 28ca343b43 | |||
| 651b0614c4 | |||
| f47488c6f1 | |||
| 6d159330a1 | |||
| 8e314f3269 | |||
| f5a7077570 | |||
| 746d29fb7a | |||
| cf68f6ca6f | |||
| a4b83679b1 | |||
| a31b39fd1d | |||
| 0205354202 | |||
| 949dfeb3d9 | |||
| 620ef911fa | |||
| efed24de20 | |||
| 3912706d27 | |||
| d78c42a653 | |||
| b04e0f81cd | |||
| 65dcb0c564 | |||
| 3d183b21cd | |||
| 1644a751bb | |||
| 6631cae7b0 | |||
| 3452194941 | |||
| 11612ff0db | |||
| 63bc94c7a6 | |||
| e00319d7ff | |||
| 11719440dc | |||
| f246d68aa7 | |||
| 6e87c67096 | |||
| b8217f2106 | |||
| 9824980cbf | |||
| 93a79cd075 | |||
| f6e52abcc1 | |||
| 03232f72e8 | |||
| 37aca44e45 | |||
| 9f4d95a57b | |||
| 65eac57fce | |||
| 08311acc9a | |||
| f8fbae6130 | |||
| df06e8d134 | |||
| ad365dc722 | |||
| 200e8ae7da | |||
| 65cfaf1b4a | |||
| 83b155fc5e | |||
| 7db56e7f3e | |||
| 42064875a0 | |||
| 41245c0c1c | |||
| 0e5cc8f898 | |||
| c8bb991865 | |||
| bc1c76d746 | |||
| 8f03628bd6 | |||
| a1feb0bad3 | |||
| 978cba96c8 | |||
| 7212094a3d | |||
| 14843ddeba | |||
| 5315db0077 | |||
| 026f343d43 | |||
| da5f31f9d7 | |||
| fa1614f238 | |||
| 0c096d39db | |||
| dae6549bad | |||
| 767fc28488 | |||
| c3be8f60b7 | |||
| 33cb44bf36 | |||
| 4c1018ddec | |||
| cf7061fd58 | |||
| e6b7b9953f | |||
| 4a3775a0de | |||
| 4d353662a1 | |||
| ca0b2de917 | |||
| 2335c3ec62 | |||
| 30ccab1b93 | |||
| f56d6a7fc8 | |||
| 29a7f5880f | |||
| eee3056614 | |||
| 152b0e93db | |||
| 3f914fe46f | |||
| 62b54ee836 | |||
| 6a41407005 | |||
| adfa6c6ba0 | |||
| a53766f472 | |||
| 40735c713a | |||
| 2054ae3a35 | |||
| 9066e11c12 | |||
| f16a7e55c9 | |||
| e3b32b3c4a | |||
| a02584f3b6 | |||
| 45524e474e | |||
| fbdea47dc7 | |||
| f5fbd4e5ef | |||
| c7f63dc638 | |||
| beecefec1c | |||
| 24d1a1d764 | |||
| 9edf3b0aa6 | |||
| 8d49fb467c | |||
| 2caa042317 | |||
| fe8bde855d | |||
| ac620264b1 | |||
| f31b84f519 | |||
| efb7cc7452 | |||
| 7a3202a053 | |||
| 86c9ed2ba9 | |||
| 56321864fb | |||
| 6adc002f1a | |||
| 1acc8bdb8f | |||
| 61e2761580 | |||
| 996e61d0ad |
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
|||||||
[submodule "Engine.Serializers/YamlDotNet"]
|
[submodule "Engine.Integration/YamlDotNet"]
|
||||||
path = Engine.Serializers/YamlDotNet
|
path = Engine.Integration/YamlDotNet
|
||||||
url = git@github.com:Syntriax/YamlDotNet.git
|
url = https://github.com/Syntriax/YamlDotNet.git
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates the class implementing it has Assignable fields that are necessary for the engine to work properly.
|
/// Indicates the class implementing it has Assignable fields that are necessary for the engine to work properly.
|
||||||
@@ -8,7 +8,7 @@ public interface IAssignable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle.
|
/// Event triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UnassignEventHandler? OnUnassigned;
|
Event<IAssignable>? OnUnassigned { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unassign <see cref="IAssignable"/>'s all fields and make it ready to recycle.
|
/// Unassign <see cref="IAssignable"/>'s all fields and make it ready to recycle.
|
||||||
@@ -17,6 +17,4 @@ public interface IAssignable
|
|||||||
/// <see cref="true"/>, if the fields are unsigned successfully, <see cref="false"/> if not.
|
/// <see cref="true"/>, if the fields are unsigned successfully, <see cref="false"/> if not.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
bool Unassign();
|
bool Unassign();
|
||||||
|
|
||||||
delegate void UnassignEventHandler(IAssignable sender);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IBehaviourController"/> field.
|
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IBehaviourController"/> field.
|
||||||
@@ -8,7 +8,7 @@ public interface IHasBehaviourController : IAssignable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value.
|
/// Event triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event BehaviourControllerAssignedEventHandler? OnBehaviourControllerAssigned;
|
Event<IHasBehaviourController> OnBehaviourControllerAssigned { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IBehaviourController" />
|
/// <inheritdoc cref="IBehaviourController" />
|
||||||
IBehaviourController BehaviourController { get; }
|
IBehaviourController BehaviourController { get; }
|
||||||
@@ -21,6 +21,4 @@ public interface IHasBehaviourController : IAssignable
|
|||||||
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
bool Assign(IBehaviourController behaviourController);
|
bool Assign(IBehaviourController behaviourController);
|
||||||
|
|
||||||
delegate void BehaviourControllerAssignedEventHandler(IHasBehaviourController sender);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IEntity"/> field.
|
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IEntity"/> field.
|
||||||
@@ -8,7 +8,7 @@ public interface IHasEntity : IAssignable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IEntity"/> value has has been assigned a new value.
|
/// Event triggered when the <see cref="IEntity"/> value has has been assigned a new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EntityAssignedEventHandler? OnEntityAssigned;
|
Event<IHasEntity> OnEntityAssigned { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IEntity" />
|
/// <inheritdoc cref="IEntity" />
|
||||||
IEntity Entity { get; }
|
IEntity Entity { get; }
|
||||||
@@ -21,6 +21,4 @@ public interface IHasEntity : IAssignable
|
|||||||
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
bool Assign(IEntity entity);
|
bool Assign(IEntity entity);
|
||||||
|
|
||||||
delegate void EntityAssignedEventHandler(IHasEntity sender);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IStateEnable"/> field.
|
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IStateEnable"/> field.
|
||||||
@@ -8,7 +8,7 @@ public interface IHasStateEnable : IAssignable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IStateEnable"/> value has has been assigned a new value.
|
/// Event triggered when the <see cref="IStateEnable"/> value has has been assigned a new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event StateEnableAssignedEventHandler? OnStateEnableAssigned;
|
Event<IHasStateEnable> OnStateEnableAssigned { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IStateEnable" />
|
/// <inheritdoc cref="IStateEnable" />
|
||||||
IStateEnable StateEnable { get; }
|
IStateEnable StateEnable { get; }
|
||||||
@@ -21,6 +21,4 @@ public interface IHasStateEnable : IAssignable
|
|||||||
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
bool Assign(IStateEnable stateEnable);
|
bool Assign(IStateEnable stateEnable);
|
||||||
|
|
||||||
delegate void StateEnableAssignedEventHandler(IHasStateEnable sender);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IUniverse"/> field.
|
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IUniverse"/> field.
|
||||||
@@ -8,7 +8,7 @@ public interface IHasUniverse : IAssignable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IUniverse"/> value has has been assigned a new value.
|
/// Event triggered when the <see cref="IUniverse"/> value has has been assigned a new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UniverseAssignedEventHandler? OnUniverseAssigned;
|
Event<IHasUniverse> OnUniverseAssigned { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IUniverse" />
|
/// <inheritdoc cref="IUniverse" />
|
||||||
IUniverse Universe { get; }
|
IUniverse Universe { get; }
|
||||||
@@ -21,6 +21,4 @@ public interface IHasUniverse : IAssignable
|
|||||||
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
bool Assign(IUniverse universe);
|
bool Assign(IUniverse universe);
|
||||||
|
|
||||||
delegate void UniverseAssignedEventHandler(IHasUniverse sender);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IUniverseObject"/> field.
|
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IUniverseObject"/> field.
|
||||||
@@ -8,7 +8,7 @@ public interface IHasUniverseObject : IAssignable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IUniverseObject"/> value has has been assigned a new value.
|
/// Event triggered when the <see cref="IUniverseObject"/> value has has been assigned a new value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UniverseObjectAssignedEventHandler? OnUniverseObjectAssigned;
|
Event<IHasUniverseObject> OnUniverseObjectAssigned { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IUniverseObject" />
|
/// <inheritdoc cref="IUniverseObject" />
|
||||||
IUniverseObject UniverseObject { get; }
|
IUniverseObject UniverseObject { get; }
|
||||||
@@ -21,6 +21,4 @@ public interface IHasUniverseObject : IAssignable
|
|||||||
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
bool Assign(IUniverseObject universeObject);
|
bool Assign(IUniverseObject universeObject);
|
||||||
|
|
||||||
delegate void UniverseObjectAssignedEventHandler(IHasUniverseObject sender);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an entity which can be active or not.
|
/// Represents an entity which can be active or not.
|
||||||
@@ -8,12 +8,12 @@ public interface IActive
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IsActive"/> state of the <see cref="IActive"/> changes.
|
/// Event triggered when the <see cref="IsActive"/> state of the <see cref="IActive"/> changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ActiveChangedEventHandler? OnActiveChanged;
|
Event<IActive, ActiveChangedArguments> OnActiveChanged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The value indicating whether the <see cref="IActive"/> is enabled.
|
/// The value indicating whether the <see cref="IActive"/> is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsActive { get; }
|
bool IsActive { get; }
|
||||||
|
|
||||||
delegate void ActiveChangedEventHandler(IActive sender, bool previousState);
|
readonly record struct ActiveChangedArguments(bool PreviousState);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a behaviour that any object in the engine that might use to interact with itself or other objects.
|
/// Represents a behaviour that any object in the engine that might use to interact with itself or other objects.
|
||||||
@@ -8,12 +8,12 @@ public interface IBehaviour : IEntity, IActive, IHasBehaviourController, IHasSta
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the priority of the <see cref="IBehaviour"/> changes.
|
/// Event triggered when the priority of the <see cref="IBehaviour"/> changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event PriorityChangedEventHandler? OnPriorityChanged;
|
Event<IBehaviour, PriorityChangedArguments> OnPriorityChanged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The priority of the <see cref="IBehaviour"/>.
|
/// The priority of the <see cref="IBehaviour"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int Priority { get; set; }
|
int Priority { get; set; }
|
||||||
|
|
||||||
delegate void PriorityChangedEventHandler(IBehaviour sender, int previousPriority);
|
readonly record struct PriorityChangedArguments(int PreviousPriority);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public interface IBehaviour2D : IBehaviour
|
public interface IBehaviour2D : IBehaviour
|
||||||
{
|
{
|
||||||
|
|||||||
6
Engine.Core/Abstract/IBehaviour3D.cs
Normal file
6
Engine.Core/Abstract/IBehaviour3D.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public interface IBehaviour3D : IBehaviour
|
||||||
|
{
|
||||||
|
ITransform3D Transform { get; }
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a collector for the class type of <typeparamref name="T"/>.
|
/// Represents a collector for the class type of <typeparamref name="T"/>.
|
||||||
@@ -10,12 +10,12 @@ public interface IBehaviourCollector<T> : IHasUniverse where T : class
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when an object of type <typeparamref name="T"/> is added to the collector.
|
/// Event triggered when an object of type <typeparamref name="T"/> is added to the collector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event CollectedEventHandler? OnCollected;
|
Event<IBehaviourCollector<T>, BehaviourCollectedArguments> OnCollected { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when an object of type <typeparamref name="T"/> is removed from the collector.
|
/// Event triggered when an object of type <typeparamref name="T"/> is removed from the collector.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event RemovedEventHandler? OnRemoved;
|
Event<IBehaviourCollector<T>, BehaviourRemovedArguments> OnRemoved { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Amount of <typeparamref name="T"/> collected.
|
/// Amount of <typeparamref name="T"/> collected.
|
||||||
@@ -32,12 +32,11 @@ public interface IBehaviourCollector<T> : IHasUniverse where T : class
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The instance of the <see cref="IBehaviourCollector{T}"/> that triggered the event.</param>
|
/// <param name="sender">The instance of the <see cref="IBehaviourCollector{T}"/> that triggered the event.</param>
|
||||||
/// <param name="behaviourCollected">The object of type <typeparamref name="T"/> that was added to the collector.</param>
|
/// <param name="behaviourCollected">The object of type <typeparamref name="T"/> that was added to the collector.</param>
|
||||||
delegate void CollectedEventHandler(IBehaviourCollector<T> sender, T behaviourCollected);
|
readonly record struct BehaviourCollectedArguments(T BehaviourCollected);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for handling the <see cref="OnRemoved"/> event.
|
/// Delegate for handling the <see cref="OnRemoved"/> event.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The instance of the <see cref="IBehaviourCollector{T}"/> that triggered the event.</param>
|
/// <param name="BehaviourRemoved">The object of type <typeparamref name="T"/> that was removed from the collector.</param>
|
||||||
/// <param name="behaviourRemoved">The object of type <typeparamref name="T"/> that was removed from the collector.</param>
|
readonly record struct BehaviourRemovedArguments(T BehaviourRemoved);
|
||||||
delegate void RemovedEventHandler(IBehaviourCollector<T> sender, T behaviourRemoved);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a controller for managing <see cref="IBehaviour"/>s. Connected to an <see cref="IUniverseObject"/>.
|
/// Represents a controller for managing <see cref="IBehaviour"/>s. Connected to an <see cref="IUniverseObject"/>.
|
||||||
@@ -10,12 +10,12 @@ public interface IBehaviourController : IEntity, IHasUniverseObject
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when a <see cref="IBehaviour"/> is added to the <see cref="IBehaviourController"/>.
|
/// Event triggered when a <see cref="IBehaviour"/> is added to the <see cref="IBehaviourController"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event BehaviourAddedEventHandler? OnBehaviourAdded;
|
Event<IBehaviourController, BehaviourAddedArguments> OnBehaviourAdded { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when a <see cref="IBehaviour"/> is removed from the <see cref="IBehaviourController"/>.
|
/// Event triggered when a <see cref="IBehaviour"/> is removed from the <see cref="IBehaviourController"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event BehaviourRemovedEventHandler? OnBehaviourRemoved;
|
Event<IBehaviourController, BehaviourRemovedArguments> OnBehaviourRemoved { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Amount of <see cref="IBehaviour"/> collected.
|
/// Amount of <see cref="IBehaviour"/> collected.
|
||||||
@@ -62,7 +62,8 @@ public interface IBehaviourController : IEntity, IHasUniverseObject
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
|
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
|
||||||
/// <param name="results">The list to store the <see cref="IBehaviour"/>s.</param>
|
/// <param name="results">The list to store the <see cref="IBehaviour"/>s.</param>
|
||||||
void GetBehaviours<T>(IList<T> results);
|
/// <param name="collectionMethod">Whether to clear the <paramref name="results"/> before collection or append the results to the list.</param>
|
||||||
|
void GetBehaviours<T>(IList<T> results, CollectionMethod collectionMethod = CollectionMethod.Clear);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes <see cref="IBehaviour"/>s of the specified type from the <see cref="IBehaviourController"/>.
|
/// Removes <see cref="IBehaviour"/>s of the specified type from the <see cref="IBehaviourController"/>.
|
||||||
@@ -78,6 +79,8 @@ public interface IBehaviourController : IEntity, IHasUniverseObject
|
|||||||
/// <param name="behaviour">The <see cref="IBehaviour"/> to remove.</param>
|
/// <param name="behaviour">The <see cref="IBehaviour"/> to remove.</param>
|
||||||
void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour;
|
void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour;
|
||||||
|
|
||||||
delegate void BehaviourAddedEventHandler(IBehaviourController sender, IBehaviour behaviourAdded);
|
enum CollectionMethod { Clear, Append };
|
||||||
delegate void BehaviourRemovedEventHandler(IBehaviourController sender, IBehaviour behaviourRemoved);
|
|
||||||
|
readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded);
|
||||||
|
readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a 2D camera in the engine.
|
/// Represents a 2D camera in the engine.
|
||||||
|
|||||||
55
Engine.Core/Abstract/ICamera3D.cs
Normal file
55
Engine.Core/Abstract/ICamera3D.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a 3D camera in the engine.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICamera3D : IBehaviour3D
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when the near plane of the <see cref="ICamera3D"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
Event<ICamera3D, NearPlaneChangedArguments> OnNearPlaneChanged { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when the far plane of the <see cref="ICamera3D"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
Event<ICamera3D, FarPlaneChangedArguments> OnFarPlaneChanged { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when the field of view of the <see cref="ICamera3D"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
Event<ICamera3D, FieldOfViewChangedArguments> OnFieldOfViewChanged { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Near plane distance of the camera.
|
||||||
|
/// </summary>
|
||||||
|
float NearPlane { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Far plane distance of the camera.
|
||||||
|
/// </summary>
|
||||||
|
float FarPlane { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Field of View (FOV) value of the camera in degrees.
|
||||||
|
/// </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);
|
||||||
|
|
||||||
|
readonly record struct NearPlaneChangedArguments(float PreviousNearPlane);
|
||||||
|
readonly record struct FarPlaneChangedArguments(float PreviousFarPlane);
|
||||||
|
readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public interface ICoroutineYield
|
public interface ICoroutineYield
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,20 +1,6 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a basic entity in the engine.
|
/// Represents a basic entity in the engine.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IEntity : IInitializable, IHasStateEnable
|
public interface IEntity : IInitializable, IIdentifiable, IHasStateEnable;
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Event triggered when the <see cref="Id"/> of the <see cref="IEntity"/> changes.
|
|
||||||
/// The string action parameter is the previous <see cref="Id"/> of the <see cref="IEntity"/>.
|
|
||||||
/// </summary>
|
|
||||||
event IdChangedEventHandler? OnIdChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ID of the <see cref="IEntity"/>.
|
|
||||||
/// </summary>
|
|
||||||
string Id { get; set; }
|
|
||||||
|
|
||||||
delegate void IdChangedEventHandler(IEntity sender, string previousId);
|
|
||||||
}
|
|
||||||
|
|||||||
20
Engine.Core/Abstract/IHasId.cs
Normal file
20
Engine.Core/Abstract/IHasId.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents any instance in the engine with an id.
|
||||||
|
/// </summary>
|
||||||
|
public interface IIdentifiable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when the <see cref="Id"/> of the <see cref="IIdentifiable"/> changes.
|
||||||
|
/// The string action parameter is the previous <see cref="Id"/> of the <see cref="IIdentifiable"/>.
|
||||||
|
/// </summary>
|
||||||
|
Event<IIdentifiable, IdChangedArguments> OnIdChanged { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the <see cref="IIdentifiable"/>.
|
||||||
|
/// </summary>
|
||||||
|
string Id { get; set; }
|
||||||
|
|
||||||
|
readonly record struct IdChangedArguments(string PreviousId);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an entity that can be initialized and finalized. This information is useful for objects we know that are not in use and can be either recycled or dropped for garbage collection.
|
/// Represents an entity that can be initialized and finalized. This information is useful for objects we know that are not in use and can be either recycled or dropped for garbage collection.
|
||||||
@@ -8,12 +8,12 @@ public interface IInitializable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="Initialize"/> method is called successfully.
|
/// Event triggered when the <see cref="Initialize"/> method is called successfully.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event InitializedEventHandler? OnInitialized;
|
Event<IInitializable> OnInitialized { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IInitializable"/> method is called successfully.
|
/// Event triggered when the <see cref="IInitializable"/> method is called successfully.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event FinalizedEventHandler? OnFinalized;
|
Event<IInitializable> OnFinalized { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The value indicating whether the entity has been initialized.
|
/// The value indicating whether the entity has been initialized.
|
||||||
@@ -31,7 +31,4 @@ public interface IInitializable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns><see cref="true"/> if finalization is successful, otherwise <see cref="false"/>.</returns>
|
/// <returns><see cref="true"/> if finalization is successful, otherwise <see cref="false"/>.</returns>
|
||||||
bool Finalize();
|
bool Finalize();
|
||||||
|
|
||||||
delegate void InitializedEventHandler(IInitializable sender);
|
|
||||||
delegate void FinalizedEventHandler(IInitializable sender);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an entity with a name.
|
/// Represents an entity with a name.
|
||||||
@@ -8,12 +8,12 @@ public interface INameable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the name of the entity changes.
|
/// Event triggered when the name of the entity changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event NameChangedEventHandler? OnNameChanged;
|
Event<INameable, NameChangedArguments> OnNameChanged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the entity.
|
/// The name of the entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string Name { get; set; }
|
string Name { get; set; }
|
||||||
|
|
||||||
delegate void NameChangedEventHandler(INameable sender, string previousName);
|
readonly record struct NameChangedArguments(string PreviousName);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an entity with an enable state that can be toggled.
|
/// Represents an entity with an enable state that can be toggled.
|
||||||
@@ -8,12 +8,12 @@ public interface IStateEnable : IHasEntity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="Enabled"/> state of the <see cref="IStateEnable"/> changes.
|
/// Event triggered when the <see cref="Enabled"/> state of the <see cref="IStateEnable"/> changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EnabledChangedEventHandler? OnEnabledChanged;
|
Event<IStateEnable, EnabledChangedArguments> OnEnabledChanged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The value indicating whether the <see cref="IStateEnable"/> is enabled.
|
/// The value indicating whether the <see cref="IStateEnable"/> is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool Enabled { get; set; }
|
bool Enabled { get; set; }
|
||||||
|
|
||||||
delegate void EnabledChangedEventHandler(IStateEnable sender, bool previousState);
|
readonly record struct EnabledChangedArguments(bool PreviousState);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the transformation properties of an object such as position, scale, and rotation in 2D space.
|
/// Represents the transformation properties of an object such as position, scale, and rotation in 2D space.
|
||||||
@@ -8,17 +8,42 @@ public interface ITransform2D : IBehaviour
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="Position"/> of the <see cref="ITransform2D"/> changes.
|
/// Event triggered when the <see cref="Position"/> of the <see cref="ITransform2D"/> changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event PositionChangedEventHandler? OnPositionChanged;
|
Event<ITransform2D, PositionChangedArguments> OnPositionChanged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform2D"/> changes.
|
/// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform2D"/> changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ScaleChangedEventHandler? OnScaleChanged;
|
Event<ITransform2D, ScaleChangedArguments> OnScaleChanged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
event RotationChangedEventHandler? OnRotationChanged;
|
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>
|
||||||
|
Vector2D Up { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="Vector2D"/> pointing upwards in world space.
|
||||||
|
/// </summary>
|
||||||
|
Vector2D Down { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="Vector2D"/> pointing upwards in world space.
|
||||||
|
/// </summary>
|
||||||
|
Vector2D Left { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="Vector2D"/> pointing upwards in world space.
|
||||||
|
/// </summary>
|
||||||
|
Vector2D Right { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The world position of the <see cref="ITransform2D"/> in 2D space.
|
/// The world position of the <see cref="ITransform2D"/> in 2D space.
|
||||||
@@ -51,23 +76,20 @@ public interface ITransform2D : IBehaviour
|
|||||||
float LocalRotation { get; set; }
|
float LocalRotation { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
|
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The <see cref="ITransform2D"/> that the parent has changed.</param>
|
/// <param name="PreviousPosition">The previous <see cref="Position"/> of the <see cref="ITransform2D"/>.</param>
|
||||||
/// <param name="previousPosition">The previous <see cref="Position"/> of the <see cref="ITransform2D"/>.</param>
|
readonly record struct PositionChangedArguments(Vector2D PreviousPosition);
|
||||||
delegate void PositionChangedEventHandler(ITransform2D sender, Vector2D previousPosition);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
|
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The <see cref="ITransform2D"/> that the parent has changed.</param>
|
/// <param name="PreviousScale">The previous <see cref="Scale"/> of the <see cref="ITransform2D"/>.</param>
|
||||||
/// <param name="previousScale">The previous <see cref="Scale"/> of the <see cref="ITransform2D"/>.</param>
|
readonly record struct ScaleChangedArguments(Vector2D PreviousScale);
|
||||||
delegate void ScaleChangedEventHandler(ITransform2D sender, Vector2D previousScale);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
|
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The <see cref="ITransform2D"/> that the parent has changed.</param>
|
/// <param name="PreviousRotation">The previous <see cref="Rotation"/> of the <see cref="ITransform2D"/>.</param>
|
||||||
/// <param name="previousRotation">The previous <see cref="Rotation"/> of the <see cref="ITransform2D"/>.</param>
|
readonly record struct RotationChangedArguments(float PreviousRotation);
|
||||||
delegate void RotationChangedEventHandler(ITransform2D sender, float previousRotation);
|
|
||||||
}
|
}
|
||||||
|
|||||||
105
Engine.Core/Abstract/ITransform3D.cs
Normal file
105
Engine.Core/Abstract/ITransform3D.cs
Normal 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);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a universe responsible for managing <see cref="IUniverseObject"/>s.
|
/// Represents a universe responsible for managing <see cref="IUniverseObject"/>s.
|
||||||
@@ -10,47 +10,57 @@ public interface IUniverse : IEntity, IEnumerable<IUniverseObject>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when <see cref="Update(UniverseTime)"/> is about to be called called on the <see cref="IUniverse"/>.
|
/// Event triggered when <see cref="Update(UniverseTime)"/> is about to be called called on the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UpdateEventHandler? OnPreUpdate;
|
Event<IUniverse, UpdateArguments> OnPreUpdate { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when <see cref="Update(UniverseTime)"/> is called on the <see cref="IUniverse"/>.
|
/// Event triggered when <see cref="Update(UniverseTime)"/> is called on the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UpdateEventHandler? OnUpdate;
|
Event<IUniverse, UpdateArguments> OnUpdate { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered after <see cref="Update(UniverseTime)"/> is called on the <see cref="IUniverse"/>.
|
/// Event triggered after <see cref="Update(UniverseTime)"/> is called on the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UpdateEventHandler? OnPostUpdate;
|
Event<IUniverse, UpdateArguments> OnPostUpdate { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when <see cref="Draw"/> is about to be called called on the <see cref="IUniverse"/>.
|
/// Event triggered when <see cref="Draw"/> is about to be called called on the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event DrawEventHandler? OnPreDraw;
|
Event<IUniverse> OnPreDraw { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when <see cref="Draw"/> is called on the <see cref="IUniverse"/>.
|
/// Event triggered when <see cref="Draw"/> is called on the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event DrawEventHandler? OnDraw;
|
Event<IUniverse> OnDraw { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered after <see cref="Draw"/> is called on the <see cref="IUniverse"/>.
|
/// Event triggered after <see cref="Draw"/> is called on the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event DrawEventHandler? OnPostDraw;
|
Event<IUniverse> OnPostDraw { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when a <see cref="IUniverseObject"/> is about to be registered to the <see cref="IUniverse"/>.
|
||||||
|
/// </summary>
|
||||||
|
Event<IUniverse, UniverseObjectRegisteredArguments> OnPreUniverseObjectRegistered { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when a <see cref="IUniverseObject"/> is registered to the <see cref="IUniverse"/>.
|
/// Event triggered when a <see cref="IUniverseObject"/> is registered to the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UniverseObjectRegisteredEventHandler? OnUniverseObjectRegistered;
|
Event<IUniverse, UniverseObjectRegisteredArguments> OnUniverseObjectRegistered { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when a <see cref="IUniverseObject"/> is about to be unregistered from the <see cref="IUniverse"/>.
|
||||||
|
/// </summary>
|
||||||
|
Event<IUniverse, UniverseObjectUnRegisteredArguments> OnPreUniverseObjectUnRegistered { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when a <see cref="IUniverseObject"/> is unregistered from the <see cref="IUniverse"/>.
|
/// Event triggered when a <see cref="IUniverseObject"/> is unregistered from the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event UniverseObjectUnRegisteredEventHandler? OnUniverseObjectUnRegistered;
|
Event<IUniverse, UniverseObjectUnRegisteredArguments> OnUniverseObjectUnRegistered { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when <see cref="TimeScale"/> is changed on the <see cref="IUniverse"/>.
|
/// Event triggered when <see cref="TimeScale"/> is changed on the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event TimeScaleChangedEventHandler? OnTimeScaleChanged;
|
Event<IUniverse, TimeScaleChangedArguments> OnTimeScaleChanged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current time scale the <see cref="IUniverse"/> operates on.
|
/// Current time scale the <see cref="IUniverse"/> operates on.
|
||||||
@@ -68,9 +78,9 @@ public interface IUniverse : IEntity, IEnumerable<IUniverseObject>
|
|||||||
UniverseTime UnscaledTime { get; }
|
UniverseTime UnscaledTime { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a read-only list of <see cref="IUniverseObject"/>s managed by the <see cref="IUniverse"/>.
|
/// Gets the root <see cref="IUniverseObject"/> of the <see cref="IUniverse"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyList<IUniverseObject> UniverseObjects { get; }
|
IUniverseObject Root { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers an <see cref="IUniverseObject"/> to the <see cref="IUniverse"/>.
|
/// Registers an <see cref="IUniverseObject"/> to the <see cref="IUniverse"/>.
|
||||||
@@ -103,11 +113,8 @@ public interface IUniverse : IEntity, IEnumerable<IUniverseObject>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void Draw();
|
void Draw();
|
||||||
|
|
||||||
delegate void TimeScaleChangedEventHandler(IUniverse sender, float previousTimeScale);
|
readonly record struct TimeScaleChangedArguments(float PreviousTimeScale);
|
||||||
|
readonly record struct UpdateArguments(UniverseTime EngineTime);
|
||||||
delegate void UpdateEventHandler(IUniverse sender, UniverseTime engineTime);
|
readonly record struct UniverseObjectRegisteredArguments(IUniverseObject UniverseObjectRegistered);
|
||||||
delegate void DrawEventHandler(IUniverse sender);
|
readonly record struct UniverseObjectUnRegisteredArguments(IUniverseObject UniverseObjectUnregistered);
|
||||||
|
|
||||||
delegate void UniverseObjectRegisteredEventHandler(IUniverse sender, IUniverseObject universeObjectRegistered);
|
|
||||||
delegate void UniverseObjectUnRegisteredEventHandler(IUniverse sender, IUniverseObject universeObjectUnregistered);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an <see cref="IEntity"/> that can enter and exit a universe within the <see cref="IUniverse"/> system.
|
/// Represents an <see cref="IEntity"/> that can enter and exit a universe within the <see cref="IUniverse"/> system.
|
||||||
/// This interface allows for tracking the object's presence in the universe and provides events
|
/// This interface allows for tracking the object's presence in the universe and provides events
|
||||||
/// for notifying when the see enters or exits the universe.
|
/// for notifying when the see enters or exits the universe.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourController, IEnumerable<IUniverseObject>
|
public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourController
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IUniverseObject"/> enters the universe.
|
/// Event triggered when the <see cref="IUniverseObject"/> enters the universe.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event EnteredUniverseEventHandler? OnEnteredUniverse;
|
Event<IUniverseObject, EnteredUniverseArguments> OnEnteredUniverse { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="IUniverseObject"/> exits the universe.
|
/// Event triggered when the <see cref="IUniverseObject"/> exits the universe.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ExitedUniverseEventHandler? OnExitedUniverse;
|
Event<IUniverseObject, ExitedUniverseArguments> OnExitedUniverse { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when the <see cref="Parent"/> of the <see cref="IUniverseObject"/> changes. The second parameter is the old <see cref="IUniverseObject"/>.
|
/// Event triggered when the <see cref="Parent"/> of the <see cref="IUniverseObject"/> changes. The second parameter is the old <see cref="IUniverseObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ParentChangedEventHandler? OnParentChanged;
|
Event<IUniverseObject, ParentChangedArguments> OnParentChanged { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when a new <see cref="IUniverseObject"/> is added to the <see cref="Children"/>.
|
/// Event triggered when a new <see cref="IUniverseObject"/> is added to the <see cref="Children"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ChildrenAddedEventHandler? OnChildrenAdded;
|
Event<IUniverseObject, ChildrenAddedArguments> OnChildrenAdded { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event triggered when an <see cref="IUniverseObject"/> is removed from the <see cref="Children"/>.
|
/// Event triggered when an <see cref="IUniverseObject"/> is removed from the <see cref="Children"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event ChildrenRemovedEventHandler? OnChildrenRemoved;
|
Event<IUniverseObject, ChildrenRemovedArguments> OnChildrenRemoved { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="IUniverse"/> this <see cref="IUniverseObject"/> is connected to, if any.
|
/// Gets the <see cref="IUniverse"/> this <see cref="IUniverseObject"/> is connected to, if any.
|
||||||
@@ -47,7 +47,7 @@ public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourCon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The parent <see cref="IUniverseObject"/> of the <see cref="IUniverseObject"/>.
|
/// The parent <see cref="IUniverseObject"/> of the <see cref="IUniverseObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IUniverseObject? Parent { get; }
|
IUniverseObject? Parent { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="IUniverseObject"/>s that have this <see cref="IUniverseObject"/> as their <see cref="Parent"/>.
|
/// The <see cref="IUniverseObject"/>s that have this <see cref="IUniverseObject"/> as their <see cref="Parent"/>.
|
||||||
@@ -75,12 +75,6 @@ public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourCon
|
|||||||
/// </returns>
|
/// </returns>
|
||||||
internal bool ExitUniverse();
|
internal bool ExitUniverse();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the parent <see cref="IUniverseObject"/> of this <see cref="IUniverseObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="universeObject">The parent <see cref="IUniverseObject"/> to set.</param>
|
|
||||||
void SetParent(IUniverseObject? universeObject);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a child <see cref="IUniverseObject"/> to this <see cref="IUniverseObject"/>.
|
/// Adds a child <see cref="IUniverseObject"/> to this <see cref="IUniverseObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -94,38 +88,38 @@ public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourCon
|
|||||||
void RemoveChild(IUniverseObject universeObject);
|
void RemoveChild(IUniverseObject universeObject);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EventHandler delegate for the event triggered when the <see cref="IUniverseObject"/> enters the universe of a <see cref="IUniverse">.
|
/// Arguments for the event triggered when the <see cref="IUniverseObject"/> enters the universe of a <see cref="IUniverse">.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The <see cref="IUniverseObject"/> that entered the universe.</param>
|
/// <param name="sender">The <see cref="IUniverseObject"/> that entered the universe.</param>
|
||||||
/// <param name="universe">The <see cref="IUniverse"/> that the <see cref="IUniverseObject"/> has entered it's universe.</param>
|
/// <param name="universe">The <see cref="IUniverse"/> that the <see cref="IUniverseObject"/> has entered it's universe.</param>
|
||||||
delegate void EnteredUniverseEventHandler(IUniverseObject sender, IUniverse universe);
|
readonly record struct EnteredUniverseArguments(IUniverse Universe);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EventHandler delegate for the event triggered when the <see cref="IUniverseObject"/> exits the universe of a <see cref="IUniverse">.
|
/// Arguments for the event triggered when the <see cref="IUniverseObject"/> exits the universe of a <see cref="IUniverse">.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The <see cref="IUniverseObject"/> that exited the universe.</param>
|
/// <param name="sender">The <see cref="IUniverseObject"/> that exited the universe.</param>
|
||||||
/// <param name="universe">The <see cref="IUniverse"/> that the <see cref="IUniverseObject"/> has exited it's universe.</param>
|
/// <param name="universe">The <see cref="IUniverse"/> that the <see cref="IUniverseObject"/> has exited it's universe.</param>
|
||||||
delegate void ExitedUniverseEventHandler(IUniverseObject sender, IUniverse universe);
|
readonly record struct ExitedUniverseArguments(IUniverse Universe);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for the event triggered when the <see cref="IUniverseObject"/>'s parent changes.
|
/// Arguments for the event triggered when the <see cref="IUniverseObject"/>'s parent changes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The <see cref="IUniverseObject"/> that the parent has changed.</param>
|
/// <param name="sender">The <see cref="IUniverseObject"/> that the parent has changed.</param>
|
||||||
/// <param name="previousParent">The previous <see cref="IUniverseObject"/> the sender was a child of.</param>
|
/// <param name="previousParent">The previous <see cref="IUniverseObject"/> the sender was a child of.</param>
|
||||||
/// <param name="newParent">The new and current <see cref="IUniverseObject"/> the sender is a child of.</param>
|
/// <param name="newParent">The new and current <see cref="IUniverseObject"/> the sender is a child of.</param>
|
||||||
delegate void ParentChangedEventHandler(IUniverseObject sender, IUniverseObject? previousParent, IUniverseObject? newParent);
|
readonly record struct ParentChangedArguments(IUniverseObject? PreviousParent, IUniverseObject? CurrentParent);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for the event triggered when a new <see cref="IUniverseObject"/> added as a child.
|
/// Arguments for the event triggered when a new <see cref="IUniverseObject"/> added as a child.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The parent <see cref="IUniverseObject"/> this event is being called from.</param>
|
/// <param name="sender">The parent <see cref="IUniverseObject"/> this event is being called from.</param>
|
||||||
/// <param name="childrenAdded">The <see cref="IUniverseObject"/> that got removed as a children of the sender <see cref="IUniverseObject"/>.</param>
|
/// <param name="childrenAdded">The <see cref="IUniverseObject"/> that got removed as a children of the sender <see cref="IUniverseObject"/>.</param>
|
||||||
delegate void ChildrenAddedEventHandler(IUniverseObject sender, IUniverseObject childrenAdded);
|
readonly record struct ChildrenAddedArguments(IUniverseObject ChildrenAdded);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate for the event triggered when a new <see cref="IUniverseObject"/> removed from being a child.
|
/// Delegate for the event triggered when a new <see cref="IUniverseObject"/> removed from being a child.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The parent <see cref="IUniverseObject"/> this event is being called from.</param>
|
/// <param name="sender">The parent <see cref="IUniverseObject"/> this event is being called from.</param>
|
||||||
/// <param name="childrenAdded">The <see cref="IUniverseObject"/> that got removed as a children of the sender <see cref="IUniverseObject"/>.</param>
|
/// <param name="childrenAdded">The <see cref="IUniverseObject"/> that got removed as a children of the sender <see cref="IUniverseObject"/>.</param>
|
||||||
delegate void ChildrenRemovedEventHandler(IUniverseObject sender, IUniverseObject childrenRemoved);
|
readonly record struct ChildrenRemovedArguments(IUniverseObject ChildrenRemoved);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
|
||||||
|
|
||||||
public class ActiveBehaviourCollector<T> : IBehaviourCollector<T> where T : class, IBehaviour
|
|
||||||
{
|
|
||||||
public event IAssignable.UnassignEventHandler? OnUnassigned = null;
|
|
||||||
public event IHasUniverse.UniverseAssignedEventHandler? OnUniverseAssigned = null;
|
|
||||||
|
|
||||||
public event IBehaviourCollector<T>.CollectedEventHandler? OnCollected = null;
|
|
||||||
public event IBehaviourCollector<T>.RemovedEventHandler? OnRemoved = null;
|
|
||||||
|
|
||||||
private readonly List<T> monitoringBehaviours = new(32);
|
|
||||||
protected readonly List<T> activeBehaviours = new(32);
|
|
||||||
protected readonly Dictionary<IActive, T> monitoringActiveToBehaviour = new(32);
|
|
||||||
|
|
||||||
public IUniverse Universe { get; private set; } = null!;
|
|
||||||
|
|
||||||
public ActiveBehaviourCollector() { }
|
|
||||||
public ActiveBehaviourCollector(IUniverse universe) => Assign(universe);
|
|
||||||
|
|
||||||
private void OnUniverseObjectRegistered(IUniverse manager, IUniverseObject universeObject)
|
|
||||||
{
|
|
||||||
universeObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
|
|
||||||
universeObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
|
|
||||||
|
|
||||||
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
|
|
||||||
OnBehaviourAdded(universeObject.BehaviourController, universeObject.BehaviourController[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverseObject universeObject)
|
|
||||||
{
|
|
||||||
universeObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded;
|
|
||||||
universeObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved;
|
|
||||||
|
|
||||||
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
|
|
||||||
OnBehaviourRemoved(universeObject.BehaviourController, universeObject.BehaviourController[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
|
|
||||||
private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
|
|
||||||
{
|
|
||||||
if (behaviour is not T tBehaviour)
|
|
||||||
return;
|
|
||||||
|
|
||||||
monitoringBehaviours.Add(tBehaviour);
|
|
||||||
monitoringActiveToBehaviour.Add(tBehaviour, tBehaviour);
|
|
||||||
tBehaviour.OnActiveChanged += OnBehaviourStateChanged;
|
|
||||||
OnBehaviourStateChanged(tBehaviour, !tBehaviour.IsActive);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBehaviourStateChanged(IActive sender, bool previousState)
|
|
||||||
{
|
|
||||||
T behaviour = monitoringActiveToBehaviour[sender];
|
|
||||||
if (sender.IsActive)
|
|
||||||
{
|
|
||||||
activeBehaviours.Add(behaviour);
|
|
||||||
OnBehaviourAdd(behaviour);
|
|
||||||
OnCollected?.Invoke(this, behaviour);
|
|
||||||
}
|
|
||||||
else if (activeBehaviours.Remove(behaviour))
|
|
||||||
{
|
|
||||||
OnBehaviourRemove(behaviour);
|
|
||||||
OnRemoved?.Invoke(this, behaviour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
|
|
||||||
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
|
|
||||||
{
|
|
||||||
if (behaviour is not T tBehaviour)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!monitoringBehaviours.Remove(tBehaviour) || !monitoringActiveToBehaviour.Remove(tBehaviour))
|
|
||||||
return;
|
|
||||||
|
|
||||||
tBehaviour.OnActiveChanged -= OnBehaviourStateChanged;
|
|
||||||
if (activeBehaviours.Remove(tBehaviour))
|
|
||||||
{
|
|
||||||
OnBehaviourRemove(tBehaviour);
|
|
||||||
OnRemoved?.Invoke(this, tBehaviour);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Assign(IUniverse universe)
|
|
||||||
{
|
|
||||||
if (Universe is not null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
foreach (IUniverseObject universeObject in universe.UniverseObjects)
|
|
||||||
OnUniverseObjectRegistered(universe, universeObject);
|
|
||||||
|
|
||||||
universe.OnUniverseObjectRegistered += OnUniverseObjectRegistered;
|
|
||||||
universe.OnUniverseObjectUnRegistered += OnUniverseObjectUnregistered;
|
|
||||||
|
|
||||||
Universe = universe;
|
|
||||||
OnUniverseAssigned?.Invoke(this);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Unassign()
|
|
||||||
{
|
|
||||||
if (Universe is null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
|
|
||||||
OnUniverseObjectUnregistered(Universe, universeObject);
|
|
||||||
|
|
||||||
Universe.OnUniverseObjectRegistered -= OnUniverseObjectRegistered;
|
|
||||||
Universe.OnUniverseObjectUnRegistered -= OnUniverseObjectUnregistered;
|
|
||||||
|
|
||||||
Universe = null!;
|
|
||||||
OnUnassigned?.Invoke(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Count => activeBehaviours.Count;
|
|
||||||
public T this[System.Index index] => activeBehaviours[index];
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
|
||||||
|
|
||||||
public class ActiveBehaviourCollectorSorted<T> : ActiveBehaviourCollector<T> where T : class, IBehaviour
|
|
||||||
{
|
|
||||||
private Comparison<T>? _sortBy = null;
|
|
||||||
public Comparison<T>? SortBy
|
|
||||||
{
|
|
||||||
get => _sortBy;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_sortBy = value;
|
|
||||||
|
|
||||||
if (value is not null)
|
|
||||||
activeBehaviours.Sort(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnBehaviourAdd(IBehaviour behaviour)
|
|
||||||
{
|
|
||||||
if (SortBy is not null)
|
|
||||||
activeBehaviours.Sort(SortBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ActiveBehaviourCollectorSorted() { }
|
|
||||||
public ActiveBehaviourCollectorSorted(IUniverse universe, Comparison<T> sortBy) : base(universe) => SortBy = sortBy;
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public abstract class BaseEntity : IEntity
|
public abstract class BaseEntity : IEntity
|
||||||
{
|
{
|
||||||
public event IEntity.IdChangedEventHandler? OnIdChanged = null;
|
public Event<IIdentifiable, IIdentifiable.IdChangedArguments> OnIdChanged { get; } = new();
|
||||||
|
public Event<IInitializable> OnInitialized { get; } = new();
|
||||||
public event IInitializable.InitializedEventHandler? OnInitialized = null;
|
public Event<IInitializable> OnFinalized { get; } = new();
|
||||||
public event IInitializable.FinalizedEventHandler? OnFinalized = null;
|
public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new();
|
||||||
|
public Event<IAssignable> OnUnassigned { get; } = new();
|
||||||
public event IHasStateEnable.StateEnableAssignedEventHandler? OnStateEnableAssigned = null;
|
|
||||||
public event IAssignable.UnassignEventHandler? OnUnassigned = null;
|
|
||||||
|
|
||||||
private IStateEnable _stateEnable = null!;
|
private IStateEnable _stateEnable = null!;
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@ public abstract class BaseEntity : IEntity
|
|||||||
string previousId = _id;
|
string previousId = _id;
|
||||||
|
|
||||||
_id = value;
|
_id = value;
|
||||||
OnIdChanged?.Invoke(this, previousId);
|
OnIdChanged?.Invoke(this, new(previousId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,140 +1,103 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public abstract class Behaviour : BehaviourBase, IFirstFrameUpdate,
|
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
|
||||||
IPreUpdate, IUpdate, IPostUpdate,
|
public abstract class Behaviour : BaseEntity, IBehaviour
|
||||||
IPreDraw, IDraw, IPostDraw
|
|
||||||
{
|
{
|
||||||
protected IUniverse Universe => BehaviourController.UniverseObject.Universe;
|
public Event<IBehaviour, IBehaviour.PriorityChangedArguments> OnPriorityChanged { get; } = new();
|
||||||
protected IUniverseObject UniverseObject => BehaviourController.UniverseObject;
|
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 += OnInitialize;
|
get => _priority;
|
||||||
OnFinalized += OnFinalize;
|
set
|
||||||
OnUnassigned += OnUnassign;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnUnassign() { }
|
|
||||||
protected virtual void OnUnassign(IAssignable assignable) => OnUnassign();
|
|
||||||
|
|
||||||
protected virtual void OnInitialize() { }
|
|
||||||
protected virtual void OnInitialize(IInitializable _)
|
|
||||||
{
|
{
|
||||||
BehaviourController.UniverseObject.OnEnteredUniverse += EnteredUniverse;
|
if (value == _priority)
|
||||||
BehaviourController.UniverseObject.OnExitedUniverse += ExitedUniverse;
|
|
||||||
|
|
||||||
OnInitialize();
|
|
||||||
|
|
||||||
if (UniverseObject.IsInUniverse)
|
|
||||||
EnteredUniverse(UniverseObject, Universe);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnFinalize() { }
|
|
||||||
protected virtual void OnFinalize(IInitializable _)
|
|
||||||
{
|
|
||||||
BehaviourController.UniverseObject.OnEnteredUniverse -= EnteredUniverse;
|
|
||||||
BehaviourController.UniverseObject.OnExitedUniverse -= ExitedUniverse;
|
|
||||||
|
|
||||||
OnFinalize();
|
|
||||||
|
|
||||||
if (UniverseObject.IsInUniverse)
|
|
||||||
ExitedUniverse(UniverseObject, Universe);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnFirstActiveFrame() { }
|
|
||||||
void IFirstFrameUpdate.FirstActiveFrame()
|
|
||||||
{
|
|
||||||
Debug.Assert.AssertInitialized(this);
|
|
||||||
OnFirstActiveFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnPreUpdatePreActiveCheck() { }
|
|
||||||
protected virtual void OnPreUpdate() { }
|
|
||||||
void IPreUpdate.PreUpdate()
|
|
||||||
{
|
|
||||||
Debug.Assert.AssertInitialized(this);
|
|
||||||
|
|
||||||
OnPreUpdatePreActiveCheck();
|
|
||||||
|
|
||||||
if (!IsActive)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OnPreUpdate();
|
int previousPriority = _priority;
|
||||||
|
_priority = value;
|
||||||
|
OnPriorityChanged?.Invoke(this, new(previousPriority));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnUpdatePreActiveCheck() { }
|
private bool _isActive = false;
|
||||||
protected virtual void OnUpdate() { }
|
public bool IsActive => _isActive;
|
||||||
void IUpdate.Update()
|
|
||||||
|
protected virtual void OnAssign(IBehaviourController behaviourController) { }
|
||||||
|
public bool Assign(IBehaviourController behaviourController)
|
||||||
{
|
{
|
||||||
Debug.Assert.AssertInitialized(this);
|
if (IsInitialized)
|
||||||
|
return false;
|
||||||
|
|
||||||
OnUpdatePreActiveCheck();
|
_behaviourController = behaviourController;
|
||||||
|
OnAssign(behaviourController);
|
||||||
if (!IsActive)
|
behaviourController.OnUniverseObjectAssigned.AddListener(delegateOnUniverseObjectAssigned);
|
||||||
return;
|
behaviourController.StateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
|
||||||
|
if (behaviourController.UniverseObject is not null)
|
||||||
OnUpdate();
|
OnUniverseObjectAssigned(behaviourController);
|
||||||
|
OnBehaviourControllerAssigned?.Invoke(this);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnPostUpdatePreActiveCheck() { }
|
private void OnUniverseObjectAssigned(IHasUniverseObject sender)
|
||||||
protected virtual void OnPostUpdate() { }
|
|
||||||
void IPostUpdate.PostUpdate()
|
|
||||||
{
|
{
|
||||||
Debug.Assert.AssertInitialized(this);
|
sender.UniverseObject.OnActiveChanged.AddListener(delegateOnUniverseObjectActiveChanged);
|
||||||
|
UpdateActive();
|
||||||
OnPostUpdatePreActiveCheck();
|
|
||||||
|
|
||||||
if (!StateEnable.Enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
OnPostUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnPreDrawPreActiveCheck() { }
|
protected override void OnAssign(IStateEnable stateEnable)
|
||||||
protected virtual void OnPreDraw() { }
|
|
||||||
void IPreDraw.PreDraw()
|
|
||||||
{
|
{
|
||||||
Debug.Assert.AssertInitialized(this);
|
base.OnAssign(stateEnable);
|
||||||
|
|
||||||
OnPreDrawPreActiveCheck();
|
stateEnable.OnEnabledChanged.AddListener(delegateOnStateEnabledChanged);
|
||||||
|
|
||||||
if (!StateEnable.Enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
OnPreDraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnDrawPreActiveCheck() { }
|
protected override void UnassignInternal()
|
||||||
protected virtual void OnDraw() { }
|
|
||||||
void IDraw.Draw()
|
|
||||||
{
|
{
|
||||||
Debug.Assert.AssertInitialized(this);
|
BehaviourController.UniverseObject.OnActiveChanged.RemoveListener(delegateOnUniverseObjectActiveChanged);
|
||||||
|
StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
|
||||||
OnDrawPreActiveCheck();
|
BehaviourController.OnUniverseObjectAssigned.RemoveListener(delegateOnUniverseObjectAssigned);
|
||||||
|
BehaviourController.StateEnable.OnEnabledChanged.RemoveListener(delegateOnStateEnabledChanged);
|
||||||
if (!StateEnable.Enabled)
|
base.UnassignInternal();
|
||||||
return;
|
_behaviourController = null!;
|
||||||
|
|
||||||
OnDraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnPostDrawPreActiveCheck() { }
|
protected override void InitializeInternal()
|
||||||
protected virtual void OnPostDraw() { }
|
|
||||||
void IPostDraw.PostDraw()
|
|
||||||
{
|
{
|
||||||
Debug.Assert.AssertInitialized(this);
|
Debug.Assert.AssertBehaviourControllerAssigned(this);
|
||||||
|
Debug.Assert.AssertStateEnableAssigned(this);
|
||||||
|
|
||||||
OnPostDrawPreActiveCheck();
|
UpdateActive();
|
||||||
|
|
||||||
if (!StateEnable.Enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
OnPostDraw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnEnteredUniverse(IUniverse universe) { }
|
private void OnStateEnabledChanged(IStateEnable sender, IStateEnable.EnabledChangedArguments args) => UpdateActive();
|
||||||
protected virtual void EnteredUniverse(IUniverseObject sender, IUniverse universe) => OnEnteredUniverse(universe);
|
private void OnUniverseObjectActiveChanged(IActive sender, IActive.ActiveChangedArguments args) => UpdateActive();
|
||||||
|
|
||||||
protected virtual void OnExitedUniverse(IUniverse universe) { }
|
private void UpdateActive()
|
||||||
protected virtual void ExitedUniverse(IUniverseObject sender, IUniverse universe) => OnExitedUniverse(universe);
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
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!;
|
public ITransform2D Transform { get; private set; } = null!;
|
||||||
|
|
||||||
protected sealed override void OnInitialize(IInitializable _)
|
protected override void OnInitialize() => Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
|
||||||
{
|
protected override void OnFinalize() => Transform = null!;
|
||||||
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
|
|
||||||
base.OnInitialize(_);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sealed override void OnFinalize(IInitializable _)
|
|
||||||
{
|
|
||||||
Transform = null!;
|
|
||||||
base.OnFinalize(_);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sealed override void OnUnassign(IAssignable assignable) => base.OnUnassign(assignable);
|
|
||||||
protected sealed override void EnteredUniverse(IUniverseObject sender, IUniverse universe) => base.EnteredUniverse(sender, universe);
|
|
||||||
protected sealed override void ExitedUniverse(IUniverseObject sender, IUniverse universe) => base.ExitedUniverse(sender, universe);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
|
||||||
|
|
||||||
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
|
|
||||||
public abstract class BehaviourBase : BaseEntity, IBehaviour
|
|
||||||
{
|
|
||||||
public event IHasBehaviourController.BehaviourControllerAssignedEventHandler? OnBehaviourControllerAssigned = null;
|
|
||||||
public event IBehaviour.PriorityChangedEventHandler? OnPriorityChanged = null;
|
|
||||||
public event IActive.ActiveChangedEventHandler? OnActiveChanged = null;
|
|
||||||
|
|
||||||
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, 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 += OnUniverseObjectAssigned;
|
|
||||||
if (behaviourController.UniverseObject is not null)
|
|
||||||
OnUniverseObjectAssigned(behaviourController);
|
|
||||||
OnBehaviourControllerAssigned?.Invoke(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUniverseObjectAssigned(IHasUniverseObject sender)
|
|
||||||
{
|
|
||||||
sender.UniverseObject.OnActiveChanged += OnUniverseObjectActiveChanged;
|
|
||||||
UpdateActive();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAssign(IStateEnable stateEnable)
|
|
||||||
{
|
|
||||||
base.OnAssign(stateEnable);
|
|
||||||
|
|
||||||
stateEnable.OnEnabledChanged += OnStateEnabledChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UnassignInternal()
|
|
||||||
{
|
|
||||||
StateEnable.OnEnabledChanged -= OnStateEnabledChanged;
|
|
||||||
BehaviourController.OnUniverseObjectAssigned -= OnUniverseObjectAssigned;
|
|
||||||
base.UnassignInternal();
|
|
||||||
_behaviourController = null!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void InitializeInternal()
|
|
||||||
{
|
|
||||||
Debug.Assert.AssertBehaviourControllerAssigned(this);
|
|
||||||
Debug.Assert.AssertStateEnableAssigned(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnStateEnabledChanged(IStateEnable sender, bool previousState) => UpdateActive();
|
|
||||||
private void OnUniverseObjectActiveChanged(IActive sender, bool previousState) => UpdateActive();
|
|
||||||
|
|
||||||
private void UpdateActive()
|
|
||||||
{
|
|
||||||
bool previousActive = IsActive;
|
|
||||||
_isActive = StateEnable.Enabled && _behaviourController.UniverseObject.IsActive;
|
|
||||||
|
|
||||||
if (previousActive != IsActive)
|
|
||||||
OnActiveChanged?.Invoke(this, previousActive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
|
||||||
|
|
||||||
public class BehaviourCollector<T> : IBehaviourCollector<T> where T : class
|
|
||||||
{
|
|
||||||
public event IAssignable.UnassignEventHandler? OnUnassigned = null;
|
|
||||||
public event IHasUniverse.UniverseAssignedEventHandler? OnUniverseAssigned = null;
|
|
||||||
|
|
||||||
public event IBehaviourCollector<T>.CollectedEventHandler? OnCollected = null;
|
|
||||||
public event IBehaviourCollector<T>.RemovedEventHandler? OnRemoved = null;
|
|
||||||
|
|
||||||
protected readonly List<T> behaviours = new(32);
|
|
||||||
|
|
||||||
public IUniverse Universe { get; private set; } = null!;
|
|
||||||
|
|
||||||
public BehaviourCollector() { }
|
|
||||||
public BehaviourCollector(IUniverse universe) => Assign(universe);
|
|
||||||
|
|
||||||
private void OnUniverseObjectRegistered(IUniverse manager, IUniverseObject universeObject)
|
|
||||||
{
|
|
||||||
universeObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
|
|
||||||
universeObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
|
|
||||||
|
|
||||||
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
|
|
||||||
OnBehaviourAdded(universeObject.BehaviourController, universeObject.BehaviourController[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverseObject universeObject)
|
|
||||||
{
|
|
||||||
universeObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded;
|
|
||||||
universeObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved;
|
|
||||||
|
|
||||||
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
|
|
||||||
OnBehaviourRemoved(universeObject.BehaviourController, universeObject.BehaviourController[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
|
|
||||||
private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
|
|
||||||
{
|
|
||||||
if (behaviour is not T tBehaviour)
|
|
||||||
return;
|
|
||||||
|
|
||||||
behaviours.Add(tBehaviour);
|
|
||||||
OnBehaviourAdd(behaviour);
|
|
||||||
OnCollected?.Invoke(this, tBehaviour);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
|
|
||||||
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
|
|
||||||
{
|
|
||||||
if (behaviour is not T tBehaviour)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!behaviours.Remove(tBehaviour))
|
|
||||||
return;
|
|
||||||
|
|
||||||
OnBehaviourRemove(behaviour);
|
|
||||||
OnRemoved?.Invoke(this, tBehaviour);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnAssign(IUniverse universe) { }
|
|
||||||
public bool Assign(IUniverse universe)
|
|
||||||
{
|
|
||||||
if (Universe is not null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
foreach (IUniverseObject universeObject in universe.UniverseObjects)
|
|
||||||
OnUniverseObjectRegistered(universe, universeObject);
|
|
||||||
|
|
||||||
universe.OnUniverseObjectRegistered += OnUniverseObjectRegistered;
|
|
||||||
universe.OnUniverseObjectUnRegistered += OnUniverseObjectUnregistered;
|
|
||||||
|
|
||||||
Universe = universe;
|
|
||||||
OnAssign(universe);
|
|
||||||
OnUniverseAssigned?.Invoke(this);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Unassign()
|
|
||||||
{
|
|
||||||
if (Universe is null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
|
|
||||||
OnUniverseObjectUnregistered(Universe, universeObject);
|
|
||||||
|
|
||||||
Universe.OnUniverseObjectRegistered -= OnUniverseObjectRegistered;
|
|
||||||
Universe.OnUniverseObjectUnRegistered -= OnUniverseObjectUnregistered;
|
|
||||||
|
|
||||||
Universe = null!;
|
|
||||||
OnUnassigned?.Invoke(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Count => behaviours.Count;
|
|
||||||
public T this[System.Index index] => behaviours[index];
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
|
||||||
|
|
||||||
public class BehaviourCollectorSorted<T> : BehaviourCollector<T> where T : class
|
|
||||||
{
|
|
||||||
private Comparison<T>? _sortBy = null;
|
|
||||||
public Comparison<T>? SortBy
|
|
||||||
{
|
|
||||||
get => _sortBy;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_sortBy = value;
|
|
||||||
|
|
||||||
if (value is not null)
|
|
||||||
behaviours.Sort(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnBehaviourAdd(IBehaviour behaviour)
|
|
||||||
{
|
|
||||||
if (SortBy is not null)
|
|
||||||
behaviours.Sort(SortBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BehaviourCollectorSorted() { }
|
|
||||||
public BehaviourCollectorSorted(IUniverse universe, Comparison<T> sortBy) : base(universe) => SortBy = sortBy;
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
[System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")]
|
[System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")]
|
||||||
public class BehaviourController : BaseEntity, IBehaviourController
|
public class BehaviourController : BaseEntity, IBehaviourController
|
||||||
{
|
{
|
||||||
public event IBehaviourController.BehaviourAddedEventHandler? OnBehaviourAdded = null;
|
public Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments> OnBehaviourAdded { get; } = new();
|
||||||
public event IBehaviourController.BehaviourRemovedEventHandler? OnBehaviourRemoved = null;
|
public Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments> OnBehaviourRemoved { get; } = new();
|
||||||
public event IHasUniverseObject.UniverseObjectAssignedEventHandler? OnUniverseObjectAssigned = null;
|
public Event<IHasUniverseObject> OnUniverseObjectAssigned { get; } = new();
|
||||||
|
|
||||||
private readonly IList<IBehaviour> behaviours = new List<IBehaviour>(Constants.BEHAVIOURS_SIZE_INITIAL);
|
private readonly FastList<IBehaviour> behaviours = new(Constants.BEHAVIOURS_SIZE_INITIAL);
|
||||||
|
|
||||||
private IUniverseObject _universeObject = null!;
|
private IUniverseObject _universeObject = null!;
|
||||||
|
|
||||||
@@ -28,8 +26,9 @@ public class BehaviourController : BaseEntity, IBehaviourController
|
|||||||
|
|
||||||
if (IsInitialized)
|
if (IsInitialized)
|
||||||
behaviour.Initialize();
|
behaviour.Initialize();
|
||||||
behaviour.OnPriorityChanged += OnPriorityChange;
|
|
||||||
OnBehaviourAdded?.Invoke(this, behaviour);
|
behaviour.OnPriorityChanged.AddListener(OnPriorityChange);
|
||||||
|
OnBehaviourAdded?.Invoke(this, new(behaviour));
|
||||||
return behaviour;
|
return behaviour;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,22 +49,20 @@ public class BehaviourController : BaseEntity, IBehaviourController
|
|||||||
|
|
||||||
public IReadOnlyList<T> GetBehaviours<T>()
|
public IReadOnlyList<T> GetBehaviours<T>()
|
||||||
{
|
{
|
||||||
List<T>? behaviours = null;
|
List<T> behaviours = [];
|
||||||
|
|
||||||
foreach (IBehaviour behaviourItem in this.behaviours)
|
foreach (IBehaviour behaviourItem in this.behaviours)
|
||||||
{
|
if (behaviourItem is T behaviour)
|
||||||
if (behaviourItem is not T behaviour)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
behaviours ??= [];
|
|
||||||
behaviours.Add(behaviour);
|
behaviours.Add(behaviour);
|
||||||
|
|
||||||
|
return behaviours;
|
||||||
}
|
}
|
||||||
|
|
||||||
return behaviours ?? Enumerable.Empty<T>().ToList();
|
public void GetBehaviours<T>(IList<T> results, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear)
|
||||||
}
|
|
||||||
|
|
||||||
public void GetBehaviours<T>(IList<T> results)
|
|
||||||
{
|
{
|
||||||
|
if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
|
||||||
results.Clear();
|
results.Clear();
|
||||||
|
|
||||||
foreach (IBehaviour behaviourItem in behaviours)
|
foreach (IBehaviour behaviourItem in behaviours)
|
||||||
{
|
{
|
||||||
if (behaviourItem is not T behaviour)
|
if (behaviourItem is not T behaviour)
|
||||||
@@ -77,7 +74,7 @@ public class BehaviourController : BaseEntity, IBehaviourController
|
|||||||
|
|
||||||
public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour
|
public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour
|
||||||
{
|
{
|
||||||
for (int i = behaviours.Count; i >= 0; i--)
|
for (int i = behaviours.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (behaviours[i] is not T behaviour)
|
if (behaviours[i] is not T behaviour)
|
||||||
continue;
|
continue;
|
||||||
@@ -94,10 +91,10 @@ public class BehaviourController : BaseEntity, IBehaviourController
|
|||||||
if (!behaviours.Contains(behaviour))
|
if (!behaviours.Contains(behaviour))
|
||||||
throw new Exception($"{behaviour.GetType().Name} does not exist in {UniverseObject.Name}'s {nameof(IBehaviourController)}.");
|
throw new Exception($"{behaviour.GetType().Name} does not exist in {UniverseObject.Name}'s {nameof(IBehaviourController)}.");
|
||||||
|
|
||||||
behaviour.OnPriorityChanged -= OnPriorityChange;
|
behaviour.OnPriorityChanged.RemoveListener(OnPriorityChange);
|
||||||
behaviour.Finalize();
|
behaviour.Finalize();
|
||||||
behaviours.Remove(behaviour);
|
behaviours.Remove(behaviour);
|
||||||
OnBehaviourRemoved?.Invoke(this, behaviour);
|
OnBehaviourRemoved?.Invoke(this, new(behaviour));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnAssign(IUniverseObject universeObject) { }
|
protected virtual void OnAssign(IUniverseObject universeObject) { }
|
||||||
@@ -146,7 +143,7 @@ public class BehaviourController : BaseEntity, IBehaviourController
|
|||||||
behaviours.Add(behaviour);
|
behaviours.Add(behaviour);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPriorityChange(IBehaviour sender, int previousPriority)
|
private void OnPriorityChange(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
|
||||||
{
|
{
|
||||||
behaviours.Remove(sender);
|
behaviours.Remove(sender);
|
||||||
InsertBehaviourByPriority(sender);
|
InsertBehaviourByPriority(sender);
|
||||||
|
|||||||
56
Engine.Core/BehaviourInternal.cs
Normal file
56
Engine.Core/BehaviourInternal.cs
Normal 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);
|
||||||
|
}
|
||||||
16
Engine.Core/Collectors/ActiveBehaviourCollector.cs
Normal file
16
Engine.Core/Collectors/ActiveBehaviourCollector.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public class ActiveBehaviourCollector<T> : ActiveBehaviourCollectorBase<T> where T : class, IBehaviour
|
||||||
|
{
|
||||||
|
protected readonly FastList<T> activeBehaviours = new(32);
|
||||||
|
public override T this[Index index] => activeBehaviours[index];
|
||||||
|
public override int Count => activeBehaviours.Count;
|
||||||
|
|
||||||
|
public ActiveBehaviourCollector() { }
|
||||||
|
public ActiveBehaviourCollector(IUniverse universe) : base(universe) { }
|
||||||
|
|
||||||
|
protected override void AddBehaviour(T behaviour) => activeBehaviours.Add(behaviour);
|
||||||
|
protected override bool RemoveBehaviour(T tBehaviour) => activeBehaviours.Remove(tBehaviour);
|
||||||
|
}
|
||||||
147
Engine.Core/Collectors/ActiveBehaviourCollectorBase.cs
Normal file
147
Engine.Core/Collectors/ActiveBehaviourCollectorBase.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public abstract class ActiveBehaviourCollectorBase<T> : IBehaviourCollector<T> where T : class, IBehaviour
|
||||||
|
{
|
||||||
|
protected readonly Dictionary<IActive, T> monitoringActiveToBehaviour = new(32);
|
||||||
|
protected readonly FastList<T> monitoringBehaviours = new(32);
|
||||||
|
|
||||||
|
private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler delegateOnBehaviourAdded = null!;
|
||||||
|
private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler delegateOnBehaviourRemoved = null!;
|
||||||
|
private readonly Event<IActive, IActive.ActiveChangedArguments>.EventHandler delegateOnBehaviourStateChanged = null!;
|
||||||
|
private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler delegateOnUniverseObjectRegistered = null!;
|
||||||
|
private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler delegateOnUniverseObjectUnregistered = null!;
|
||||||
|
|
||||||
|
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourCollectedArguments> OnCollected { get; } = new();
|
||||||
|
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourRemovedArguments> OnRemoved { get; } = new();
|
||||||
|
public Event<IHasUniverse> OnUniverseAssigned { get; } = new();
|
||||||
|
public Event<IAssignable>? OnUnassigned { get; } = new();
|
||||||
|
|
||||||
|
public abstract int Count { get; }
|
||||||
|
|
||||||
|
public abstract T this[Index index] { get; }
|
||||||
|
public IUniverse Universe { get; private set; } = null!;
|
||||||
|
|
||||||
|
public bool Assign(IUniverse universe)
|
||||||
|
{
|
||||||
|
if (Universe is not null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (IUniverseObject universeObject in universe)
|
||||||
|
OnUniverseObjectRegistered(universe, new(universeObject));
|
||||||
|
|
||||||
|
universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered);
|
||||||
|
universe.OnUniverseObjectUnRegistered.AddListener(delegateOnUniverseObjectUnregistered);
|
||||||
|
|
||||||
|
Universe = universe;
|
||||||
|
OnUniverseAssigned?.Invoke(this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Unassign()
|
||||||
|
{
|
||||||
|
if (Universe is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (IUniverseObject universeObject in Universe)
|
||||||
|
OnUniverseObjectUnregistered(Universe, new(universeObject));
|
||||||
|
|
||||||
|
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered);
|
||||||
|
Universe.OnUniverseObjectUnRegistered.RemoveListener(delegateOnUniverseObjectUnregistered);
|
||||||
|
|
||||||
|
Universe = null!;
|
||||||
|
OnUnassigned?.Invoke(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void AddBehaviour(T behaviour);
|
||||||
|
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
|
||||||
|
private void OnBehaviourAdded(IBehaviourController controller, IBehaviourController.BehaviourAddedArguments args)
|
||||||
|
{
|
||||||
|
if (args.BehaviourAdded is not T tBehaviour)
|
||||||
|
return;
|
||||||
|
|
||||||
|
monitoringBehaviours.Add(tBehaviour);
|
||||||
|
monitoringActiveToBehaviour.Add(tBehaviour, tBehaviour);
|
||||||
|
tBehaviour.OnActiveChanged.AddListener(delegateOnBehaviourStateChanged);
|
||||||
|
OnBehaviourStateChanged(tBehaviour, new(!tBehaviour.IsActive));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool RemoveBehaviour(T behaviour);
|
||||||
|
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
|
||||||
|
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviourController.BehaviourRemovedArguments args)
|
||||||
|
{
|
||||||
|
if (args.BehaviourRemoved is not T tBehaviour)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!monitoringBehaviours.Remove(tBehaviour) || !monitoringActiveToBehaviour.Remove(tBehaviour))
|
||||||
|
return;
|
||||||
|
|
||||||
|
tBehaviour.OnActiveChanged.RemoveListener(delegateOnBehaviourStateChanged);
|
||||||
|
if (!RemoveBehaviour(tBehaviour))
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnBehaviourRemove(tBehaviour);
|
||||||
|
OnRemoved?.Invoke(this, new(tBehaviour));
|
||||||
|
}
|
||||||
|
private void OnBehaviourStateChanged(IActive sender, IActive.ActiveChangedArguments args)
|
||||||
|
{
|
||||||
|
T behaviour = monitoringActiveToBehaviour[sender];
|
||||||
|
if (sender.IsActive)
|
||||||
|
{
|
||||||
|
AddBehaviour(behaviour);
|
||||||
|
OnBehaviourAdd(behaviour);
|
||||||
|
OnCollected?.Invoke(this, new(behaviour));
|
||||||
|
}
|
||||||
|
else if (RemoveBehaviour(behaviour))
|
||||||
|
{
|
||||||
|
OnBehaviourRemove(behaviour);
|
||||||
|
OnRemoved?.Invoke(this, new(behaviour));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUniverseObjectRegistered(IUniverse manager, IUniverse.UniverseObjectRegisteredArguments args)
|
||||||
|
{
|
||||||
|
IUniverseObject universeObject = args.UniverseObjectRegistered;
|
||||||
|
|
||||||
|
universeObject.BehaviourController.OnBehaviourAdded.AddListener(delegateOnBehaviourAdded);
|
||||||
|
universeObject.BehaviourController.OnBehaviourRemoved.AddListener(delegateOnBehaviourRemoved);
|
||||||
|
|
||||||
|
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
|
||||||
|
OnBehaviourAdded(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverse.UniverseObjectUnRegisteredArguments args)
|
||||||
|
{
|
||||||
|
IUniverseObject universeObject = args.UniverseObjectUnregistered;
|
||||||
|
|
||||||
|
universeObject.BehaviourController.OnBehaviourAdded.RemoveListener(delegateOnBehaviourAdded);
|
||||||
|
universeObject.BehaviourController.OnBehaviourRemoved.RemoveListener(delegateOnBehaviourRemoved);
|
||||||
|
|
||||||
|
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
|
||||||
|
OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveBehaviourCollectorBase()
|
||||||
|
{
|
||||||
|
delegateOnBehaviourAdded = OnBehaviourAdded;
|
||||||
|
delegateOnBehaviourRemoved = OnBehaviourRemoved;
|
||||||
|
delegateOnBehaviourStateChanged = OnBehaviourStateChanged;
|
||||||
|
delegateOnUniverseObjectRegistered = OnUniverseObjectRegistered;
|
||||||
|
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveBehaviourCollectorBase(IUniverse universe)
|
||||||
|
{
|
||||||
|
delegateOnBehaviourAdded = OnBehaviourAdded;
|
||||||
|
delegateOnBehaviourRemoved = OnBehaviourRemoved;
|
||||||
|
delegateOnBehaviourStateChanged = OnBehaviourStateChanged;
|
||||||
|
delegateOnUniverseObjectRegistered = OnUniverseObjectRegistered;
|
||||||
|
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
|
||||||
|
|
||||||
|
Assign(universe);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Engine.Core/Collectors/ActiveBehaviourCollectorOrdered.cs
Normal file
89
Engine.Core/Collectors/ActiveBehaviourCollectorOrdered.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public class ActiveBehaviourCollectorOrdered<TIndex, TItem> : ActiveBehaviourCollector<TItem> where TItem : class, IBehaviour where TIndex : IComparable
|
||||||
|
{
|
||||||
|
private readonly SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
|
||||||
|
private readonly Dictionary<TItem, TIndex> indexCache = [];
|
||||||
|
|
||||||
|
private readonly Func<TItem, TIndex> getIndexFunc = null!;
|
||||||
|
private readonly IComparer<TIndex> sortBy = null!;
|
||||||
|
|
||||||
|
private int count = 0;
|
||||||
|
public override int Count => count;
|
||||||
|
|
||||||
|
public override TItem this[Index index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int actualIndex = index.IsFromEnd
|
||||||
|
? count - index.Value
|
||||||
|
: index.Value;
|
||||||
|
|
||||||
|
if (actualIndex < 0 || actualIndex >= count)
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
|
int leftIndex = actualIndex;
|
||||||
|
foreach ((TIndex i, FastList<TItem> list) in behaviours)
|
||||||
|
{
|
||||||
|
if (leftIndex < list.Count)
|
||||||
|
return list[leftIndex];
|
||||||
|
leftIndex -= list.Count;
|
||||||
|
}
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool RemoveBehaviour(TItem tBehaviour)
|
||||||
|
{
|
||||||
|
if (!indexCache.TryGetValue(tBehaviour, out TIndex? index) || !behaviours.TryGetValue(index, out FastList<TItem>? list))
|
||||||
|
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
|
||||||
|
|
||||||
|
if (!list.Remove(tBehaviour) || !indexCache.Remove(tBehaviour))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
count--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddBehaviour(TItem behaviour)
|
||||||
|
{
|
||||||
|
TIndex key = getIndexFunc(behaviour);
|
||||||
|
if (!behaviours.TryGetValue(key, out FastList<TItem>? list))
|
||||||
|
behaviours[key] = list = [];
|
||||||
|
|
||||||
|
count++;
|
||||||
|
list.Add(behaviour);
|
||||||
|
indexCache.Add(behaviour, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = Comparer<TIndex>.Create(sortBy);
|
||||||
|
behaviours = new(this.sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = Comparer<TIndex>.Create(sortBy);
|
||||||
|
behaviours = new(this.sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveBehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = sortBy;
|
||||||
|
behaviours = new(sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActiveBehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = sortBy;
|
||||||
|
behaviours = new(sortBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Engine.Core/Collectors/BehaviourCollector.cs
Normal file
17
Engine.Core/Collectors/BehaviourCollector.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public class BehaviourCollector<T> : BehaviourCollectorBase<T> where T : class
|
||||||
|
{
|
||||||
|
protected readonly FastList<T> behaviours = new(32);
|
||||||
|
|
||||||
|
public override T this[Index index] => behaviours[index];
|
||||||
|
public override int Count => behaviours.Count;
|
||||||
|
|
||||||
|
protected override void AddBehaviour(T behaviour) => behaviours.Add(behaviour);
|
||||||
|
protected override bool RemoveBehaviour(T tBehaviour) => behaviours.Remove(tBehaviour);
|
||||||
|
|
||||||
|
public BehaviourCollector() { }
|
||||||
|
public BehaviourCollector(IUniverse universe) : base(universe) { }
|
||||||
|
}
|
||||||
124
Engine.Core/Collectors/BehaviourCollectorBase.cs
Normal file
124
Engine.Core/Collectors/BehaviourCollectorBase.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public abstract class BehaviourCollectorBase<T> : IBehaviourCollector<T> where T : class
|
||||||
|
{
|
||||||
|
private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler delegateOnBehaviourAdded = null!;
|
||||||
|
private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler delegateOnBehaviourRemoved = null!;
|
||||||
|
private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler delegateOnUniverseObjectRegistered = null!;
|
||||||
|
private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler delegateOnUniverseObjectUnregistered = null!;
|
||||||
|
|
||||||
|
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourCollectedArguments> OnCollected { get; } = new();
|
||||||
|
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourRemovedArguments> OnRemoved { get; } = new();
|
||||||
|
public Event<IHasUniverse> OnUniverseAssigned { get; } = new();
|
||||||
|
public Event<IAssignable>? OnUnassigned { get; } = new();
|
||||||
|
|
||||||
|
public IUniverse Universe { get; private set; } = null!;
|
||||||
|
|
||||||
|
public abstract int Count { get; }
|
||||||
|
|
||||||
|
public abstract T this[Index index] { get; }
|
||||||
|
|
||||||
|
public bool Assign(IUniverse universe)
|
||||||
|
{
|
||||||
|
if (Universe is not null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (IUniverseObject universeObject in universe)
|
||||||
|
OnUniverseObjectRegistered(universe, new(universeObject));
|
||||||
|
|
||||||
|
universe.OnUniverseObjectRegistered.AddListener(delegateOnUniverseObjectRegistered);
|
||||||
|
universe.OnPreUniverseObjectUnRegistered.AddListener(delegateOnUniverseObjectUnregistered);
|
||||||
|
|
||||||
|
Universe = universe;
|
||||||
|
OnAssign(universe);
|
||||||
|
OnUniverseAssigned?.Invoke(this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Unassign()
|
||||||
|
{
|
||||||
|
if (Universe is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (IUniverseObject universeObject in Universe)
|
||||||
|
OnUniverseObjectUnregistered(Universe, new(universeObject));
|
||||||
|
|
||||||
|
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered);
|
||||||
|
Universe.OnPreUniverseObjectUnRegistered.RemoveListener(delegateOnUniverseObjectUnregistered);
|
||||||
|
|
||||||
|
Universe = null!;
|
||||||
|
OnUnassigned?.Invoke(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnAssign(IUniverse universe) { }
|
||||||
|
|
||||||
|
protected abstract void AddBehaviour(T behaviour);
|
||||||
|
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
|
||||||
|
private void OnBehaviourAdded(IBehaviourController controller, IBehaviourController.BehaviourAddedArguments args)
|
||||||
|
{
|
||||||
|
if (args.BehaviourAdded is not T tBehaviour)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddBehaviour(tBehaviour);
|
||||||
|
OnBehaviourAdd(args.BehaviourAdded);
|
||||||
|
OnCollected?.Invoke(this, new(tBehaviour));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool RemoveBehaviour(T tBehaviour);
|
||||||
|
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
|
||||||
|
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviourController.BehaviourRemovedArguments args)
|
||||||
|
{
|
||||||
|
if (args.BehaviourRemoved is not T tBehaviour)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!RemoveBehaviour(tBehaviour))
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnBehaviourRemove(args.BehaviourRemoved);
|
||||||
|
OnRemoved?.Invoke(this, new(tBehaviour));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUniverseObjectRegistered(IUniverse manager, IUniverse.UniverseObjectRegisteredArguments args)
|
||||||
|
{
|
||||||
|
IUniverseObject universeObject = args.UniverseObjectRegistered;
|
||||||
|
|
||||||
|
universeObject.BehaviourController.OnBehaviourAdded.AddListener(delegateOnBehaviourAdded);
|
||||||
|
universeObject.BehaviourController.OnBehaviourRemoved.AddListener(delegateOnBehaviourRemoved);
|
||||||
|
|
||||||
|
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
|
||||||
|
OnBehaviourAdded(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverse.UniverseObjectUnRegisteredArguments args)
|
||||||
|
{
|
||||||
|
IUniverseObject universeObject = args.UniverseObjectUnregistered;
|
||||||
|
|
||||||
|
universeObject.BehaviourController.OnBehaviourAdded.RemoveListener(delegateOnBehaviourAdded);
|
||||||
|
universeObject.BehaviourController.OnBehaviourRemoved.RemoveListener(delegateOnBehaviourRemoved);
|
||||||
|
|
||||||
|
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
|
||||||
|
OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehaviourCollectorBase()
|
||||||
|
{
|
||||||
|
delegateOnBehaviourAdded = OnBehaviourAdded;
|
||||||
|
delegateOnBehaviourRemoved = OnBehaviourRemoved;
|
||||||
|
delegateOnUniverseObjectRegistered = OnUniverseObjectRegistered;
|
||||||
|
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehaviourCollectorBase(IUniverse universe)
|
||||||
|
{
|
||||||
|
delegateOnBehaviourAdded = OnBehaviourAdded;
|
||||||
|
delegateOnBehaviourRemoved = OnBehaviourRemoved;
|
||||||
|
delegateOnUniverseObjectRegistered = OnUniverseObjectRegistered;
|
||||||
|
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
|
||||||
|
|
||||||
|
Assign(universe);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Engine.Core/Collectors/BehaviourCollectorOrdered.cs
Normal file
89
Engine.Core/Collectors/BehaviourCollectorOrdered.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public class BehaviourCollectorOrdered<TIndex, TItem> : BehaviourCollectorBase<TItem> where TItem : class where TIndex : IComparable
|
||||||
|
{
|
||||||
|
private readonly SortedDictionary<TIndex, FastList<TItem>> behaviours = null!;
|
||||||
|
private readonly Dictionary<TItem, TIndex> indexCache = null!;
|
||||||
|
|
||||||
|
private readonly Func<TItem, TIndex> getIndexFunc = null!;
|
||||||
|
private readonly IComparer<TIndex> sortBy = null!;
|
||||||
|
|
||||||
|
private int count = 0;
|
||||||
|
public override int Count => count;
|
||||||
|
|
||||||
|
public override TItem this[Index index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int actualIndex = index.IsFromEnd
|
||||||
|
? count - index.Value
|
||||||
|
: index.Value;
|
||||||
|
|
||||||
|
if (actualIndex < 0 || actualIndex >= count)
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
|
int leftIndex = actualIndex;
|
||||||
|
foreach ((TIndex i, FastList<TItem> list) in behaviours)
|
||||||
|
{
|
||||||
|
if (leftIndex < list.Count)
|
||||||
|
return list[leftIndex];
|
||||||
|
leftIndex -= list.Count;
|
||||||
|
}
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool RemoveBehaviour(TItem tBehaviour)
|
||||||
|
{
|
||||||
|
if (!indexCache.TryGetValue(tBehaviour, out TIndex? index) || !behaviours.TryGetValue(index, out FastList<TItem>? list))
|
||||||
|
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
|
||||||
|
|
||||||
|
if (!list.Remove(tBehaviour) || !indexCache.Remove(tBehaviour))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
count--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddBehaviour(TItem behaviour)
|
||||||
|
{
|
||||||
|
TIndex key = getIndexFunc(behaviour);
|
||||||
|
if (!behaviours.TryGetValue(key, out FastList<TItem>? list))
|
||||||
|
behaviours[key] = list = [];
|
||||||
|
|
||||||
|
count++;
|
||||||
|
list.Add(behaviour);
|
||||||
|
indexCache.Add(behaviour, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = Comparer<TIndex>.Create(sortBy);
|
||||||
|
behaviours = new(this.sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy) : base(universe)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = Comparer<TIndex>.Create(sortBy);
|
||||||
|
behaviours = new(this.sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehaviourCollectorOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = sortBy;
|
||||||
|
behaviours = new(sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BehaviourCollectorOrdered(IUniverse universe, Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy) : base(universe)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = sortBy;
|
||||||
|
behaviours = new(sortBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,46 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Debug;
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
public static class Assert
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void AssertInitialized(IInitializable initializable)
|
public static void AssertInitialized(IInitializable initializable)
|
||||||
=> System.Diagnostics.Debug.Assert(initializable.IsInitialized, $"{initializable.GetType().Name} must be initialized");
|
=> System.Diagnostics.Debug.Assert(initializable.IsInitialized, $"{initializable.GetType().Name} must be initialized");
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void AssertBehaviourControllerAssigned(IHasBehaviourController assignable)
|
public static void AssertBehaviourControllerAssigned(IHasBehaviourController assignable)
|
||||||
=> System.Diagnostics.Debug.Assert(assignable.BehaviourController is not null, $"{assignable.GetType().Name} must be initialized");
|
=> System.Diagnostics.Debug.Assert(assignable.BehaviourController is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IBehaviourController)}");
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void AssertEntityAssigned(IHasEntity assignable)
|
public static void AssertEntityAssigned(IHasEntity assignable)
|
||||||
=> System.Diagnostics.Debug.Assert(assignable.Entity is not null, $"{assignable.GetType().Name} must be initialized");
|
=> System.Diagnostics.Debug.Assert(assignable.Entity is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IEntity)}");
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void AssertUniverseAssigned(IHasUniverse assignable)
|
public static void AssertUniverseAssigned(IHasUniverse assignable)
|
||||||
=> System.Diagnostics.Debug.Assert(assignable.Universe is not null, $"{assignable.GetType().Name} must be initialized");
|
=> System.Diagnostics.Debug.Assert(assignable.Universe is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IUniverse)}");
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void AssertUniverseObjectAssigned(IHasUniverseObject assignable)
|
public static void AssertUniverseObjectAssigned(IHasUniverseObject assignable)
|
||||||
=> System.Diagnostics.Debug.Assert(assignable.UniverseObject is not null, $"{assignable.GetType().Name} must be initialized");
|
=> System.Diagnostics.Debug.Assert(assignable.UniverseObject is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IUniverseObject)}");
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void AssertStateEnableAssigned(IHasStateEnable assignable)
|
public static void AssertStateEnableAssigned(IHasStateEnable assignable)
|
||||||
=> System.Diagnostics.Debug.Assert(assignable.StateEnable is not null, $"{assignable.GetType().Name} must be initialized");
|
=> System.Diagnostics.Debug.Assert(assignable.StateEnable is not null, $"{assignable.GetType().Name} must be assigned an {nameof(IStateEnable)}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Debug;
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
public class ConsoleLogger : LoggerBase
|
public class ConsoleLogger : LoggerBase
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,20 +1,32 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Debug;
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
public class FileLogger : LoggerBase
|
public class FileLogger : LoggerBase
|
||||||
{
|
{
|
||||||
public readonly string FilePath;
|
public readonly string FilePath;
|
||||||
|
|
||||||
public FileLogger(string filePath)
|
|
||||||
{
|
|
||||||
FilePath = filePath;
|
|
||||||
File.Open(filePath, FileMode.Create).Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Write(string message)
|
protected override void Write(string message)
|
||||||
{
|
{
|
||||||
File.AppendAllTextAsync(FilePath, $"{message}{Environment.NewLine}");
|
File.AppendAllTextAsync(FilePath, $"{message}{Environment.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FileLogger(string filePath)
|
||||||
|
{
|
||||||
|
if (!filePath.EndsWith(".log"))
|
||||||
|
filePath += ".log";
|
||||||
|
|
||||||
|
FilePath = filePath;
|
||||||
|
|
||||||
|
bool isRelativePath = Path.GetFullPath(filePath).CompareTo(filePath) != 0;
|
||||||
|
|
||||||
|
if (isRelativePath)
|
||||||
|
FilePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath));
|
||||||
|
|
||||||
|
if (Path.GetDirectoryName(FilePath) is string directoryPath)
|
||||||
|
Directory.CreateDirectory(directoryPath);
|
||||||
|
|
||||||
|
File.Open(FilePath, FileMode.Create).Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
namespace Syntriax.Engine.Core.Debug;
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
public interface ILogger
|
public interface ILogger
|
||||||
{
|
{
|
||||||
|
static ILogger Shared { get; set; } = new ConsoleLogger();
|
||||||
|
|
||||||
Level FilterLevel { get; set; }
|
Level FilterLevel { get; set; }
|
||||||
|
|
||||||
void Log(string message, Level level = Level.Info, bool force = false);
|
void Log(string message, Level level = Level.Info, bool force = false);
|
||||||
|
|
||||||
enum Level
|
enum Level
|
||||||
{
|
{
|
||||||
|
Trace,
|
||||||
Info,
|
Info,
|
||||||
Warning,
|
Warning,
|
||||||
Error,
|
Error,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Debug;
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
public abstract class LoggerBase : ILogger
|
public abstract class LoggerBase : ILogger
|
||||||
{
|
{
|
||||||
public ILogger.Level FilterLevel { get; set; } = ILogger.Level.Info;
|
public ILogger.Level FilterLevel { get; set; } = ILogger.Level.Trace;
|
||||||
|
|
||||||
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false)
|
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false)
|
||||||
{
|
{
|
||||||
|
|||||||
9
Engine.Core/Debug/LoggerContainer.cs
Normal file
9
Engine.Core/Debug/LoggerContainer.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
|
public class LoggerContainer : Behaviour, ILogger
|
||||||
|
{
|
||||||
|
public ILogger Logger { get; set; } = ILogger.Shared;
|
||||||
|
|
||||||
|
public ILogger.Level FilterLevel { get => Logger.FilterLevel; set => Logger.FilterLevel = value; }
|
||||||
|
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false) => Logger.Log(message, level, force);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Debug;
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
public static class LoggerExtensions
|
public static class LoggerExtensions
|
||||||
{
|
{
|
||||||
@@ -16,13 +16,21 @@ public static class LoggerExtensions
|
|||||||
public static void LogError<T>(this ILogger logger, T caller, string message, bool force = false)
|
public static void LogError<T>(this ILogger logger, T caller, string message, bool force = false)
|
||||||
{
|
{
|
||||||
Log(logger, caller, message, ILogger.Level.Error, force);
|
Log(logger, caller, message, ILogger.Level.Error, force);
|
||||||
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{new StackTrace()}");
|
LogTrace(logger, caller, new StackTrace(), force);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LogException<T>(this ILogger logger, T caller, Exception exception, bool force = false)
|
public static void LogException<T>(this ILogger logger, T caller, Exception exception, bool force = false)
|
||||||
{
|
{
|
||||||
|
Log(logger, caller, $"Exception of type {exception.GetType().Name} occured", ILogger.Level.Error, force);
|
||||||
Log(logger, caller, $"Message: {exception.Message}", ILogger.Level.Error, force);
|
Log(logger, caller, $"Message: {exception.Message}", ILogger.Level.Error, force);
|
||||||
Log(logger, caller, $"InnerException: {exception.InnerException}", ILogger.Level.Error, force);
|
Log(logger, caller, $"InnerException: {exception.InnerException}", ILogger.Level.Error, force);
|
||||||
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{exception.StackTrace}");
|
|
||||||
|
// Not using LogTrace because exception.StackTrace is a type of string
|
||||||
|
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{exception.StackTrace}", ILogger.Level.Trace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LogTrace<T>(this ILogger logger, T caller, StackTrace? stackTrace = null, bool force = false)
|
||||||
|
{
|
||||||
|
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{stackTrace ?? new()}", ILogger.Level.Trace, force);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
Engine.Core/Debug/LoggerWrapper.cs
Normal file
23
Engine.Core/Debug/LoggerWrapper.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
|
public class LoggerWrapper(ILogger firstLogger, ILogger secondLogger) : ILogger
|
||||||
|
{
|
||||||
|
private readonly ILogger firstLogger = firstLogger;
|
||||||
|
private readonly ILogger secondLogger = secondLogger;
|
||||||
|
|
||||||
|
public ILogger.Level FilterLevel
|
||||||
|
{
|
||||||
|
get => firstLogger.FilterLevel;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
firstLogger.FilterLevel = value;
|
||||||
|
secondLogger.FilterLevel = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false)
|
||||||
|
{
|
||||||
|
firstLogger.Log(message, level, force);
|
||||||
|
secondLogger.Log(message, level, force);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Engine.Core/Debug/LoggerWrapperExtensions.cs
Normal file
6
Engine.Core/Debug/LoggerWrapperExtensions.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
|
public static class LoggerWrapperExtensions
|
||||||
|
{
|
||||||
|
public static ILogger WrapWith(this ILogger thisLogger, ILogger logger) => new LoggerWrapper(thisLogger, logger);
|
||||||
|
}
|
||||||
71
Engine.Core/Debug/RotatingFileLogger.cs
Normal file
71
Engine.Core/Debug/RotatingFileLogger.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Engine.Core.Debug;
|
||||||
|
|
||||||
|
public class RotatingFileLogger : ILogger
|
||||||
|
{
|
||||||
|
public readonly FileLogger FileLogger = null!;
|
||||||
|
public readonly string Directory = string.Empty;
|
||||||
|
public readonly int RotateLength = 3;
|
||||||
|
|
||||||
|
public RotatingFileLogger(string directory, string namePrefix, string nameSuffix = "", int rotateLength = 3)
|
||||||
|
{
|
||||||
|
RotateLength = rotateLength;
|
||||||
|
|
||||||
|
string fileName = Path.Combine(directory, namePrefix);
|
||||||
|
if (!string.IsNullOrWhiteSpace(nameSuffix))
|
||||||
|
fileName += $"_{nameSuffix}";
|
||||||
|
|
||||||
|
bool isRelativePath = Path.GetFullPath(fileName).CompareTo(fileName) != 0;
|
||||||
|
|
||||||
|
if (isRelativePath)
|
||||||
|
fileName = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName));
|
||||||
|
|
||||||
|
if (File.Exists($"{fileName}.log"))
|
||||||
|
RenameExistingLogs(fileName, RotateLength);
|
||||||
|
|
||||||
|
FileLogger = new(fileName);
|
||||||
|
|
||||||
|
Directory = Path.GetDirectoryName(fileName) ?? throw new("Unexpected error on getting directory of logger path");
|
||||||
|
RotateLastLogs(Directory, namePrefix, RotateLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenameExistingLogs(string filePath, int rotateLength)
|
||||||
|
{
|
||||||
|
for (int i = rotateLength - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
string source = i == 0
|
||||||
|
? $"{filePath}.log"
|
||||||
|
: $"{filePath}_{i}.log";
|
||||||
|
|
||||||
|
string dest = $"{filePath}_{i + 1}.log";
|
||||||
|
|
||||||
|
if (!File.Exists(source))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (File.Exists(dest))
|
||||||
|
File.Delete(dest);
|
||||||
|
|
||||||
|
File.Move(source, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RotateLastLogs(string directory, string prefix, int rotateLength)
|
||||||
|
{
|
||||||
|
IOrderedEnumerable<string> logs = System.IO.Directory.GetFiles(directory, $"{prefix}*.log")
|
||||||
|
.OrderBy(File.GetCreationTime);
|
||||||
|
|
||||||
|
foreach (string file in logs.Skip(rotateLength))
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ILogger.Shared.Log($"Removing log file located at \"{file}\" during rotation.");
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
catch (Exception e) { ILogger.Shared.LogException($"Failed to rotate log file at \"{file}\"", e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger.Level FilterLevel { get => FileLogger.FilterLevel; set => FileLogger.FilterLevel = value; }
|
||||||
|
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false) => FileLogger.Log(message, level, force);
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>false</ImplicitUsings>
|
<ImplicitUsings>false</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>Syntriax.Engine.Core</RootNamespace>
|
<RootNamespace>Engine.Core</RootNamespace>
|
||||||
|
<AssemblyName>Engine.Core</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Exceptions;
|
namespace Engine.Core.Exceptions;
|
||||||
|
|
||||||
public class AssignFailedException(string? message) : Exception(message)
|
public class AssignFailedException(string? message) : Exception(message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Syntriax.Engine.Core.Exceptions;
|
namespace Engine.Core.Exceptions;
|
||||||
|
|
||||||
public class BehaviourNotFoundException(string? message) : NotFoundException(message);
|
public class BehaviourNotFoundException(string? message) : NotFoundException(message);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Exceptions;
|
namespace Engine.Core.Exceptions;
|
||||||
|
|
||||||
public class NotAssignedException(string? message) : Exception(message)
|
public class NotAssignedException(string? message) : Exception(message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Exceptions;
|
namespace Engine.Core.Exceptions;
|
||||||
|
|
||||||
public class NotFoundException(string? message) : Exception(message)
|
public class NotFoundException(string? message) : Exception(message)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace Syntriax.Engine.Core.Exceptions;
|
namespace Engine.Core.Exceptions;
|
||||||
|
|
||||||
public class UniverseObjectNotFoundException(string? message) : NotFoundException(message);
|
public class UniverseObjectNotFoundException(string? message) : NotFoundException(message);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
using Syntriax.Engine.Core.Exceptions;
|
using Engine.Core.Exceptions;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public static class BehaviourControllerExtensions
|
public static class BehaviourControllerExtensions
|
||||||
{
|
{
|
||||||
@@ -27,7 +27,7 @@ public static class BehaviourControllerExtensions
|
|||||||
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
|
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
|
||||||
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
|
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
|
||||||
public static T GetRequiredBehaviour<T>(this IBehaviourController behaviourController) where T : class
|
public static T GetRequiredBehaviour<T>(this IBehaviourController behaviourController) where T : class
|
||||||
=> behaviourController.GetBehaviour<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName}");
|
=> behaviourController.GetBehaviour<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName}");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an existing <see cref="IBehaviour"/> of the specified type, or adds and returns a new one if it doesn't exist.
|
/// Gets an existing <see cref="IBehaviour"/> of the specified type, or adds and returns a new one if it doesn't exist.
|
||||||
@@ -93,25 +93,23 @@ public static class BehaviourControllerExtensions
|
|||||||
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
|
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
|
||||||
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
|
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
|
||||||
public static T GetRequiredBehaviourInParent<T>(this IBehaviourController behaviourController) where T : class
|
public static T GetRequiredBehaviourInParent<T>(this IBehaviourController behaviourController) where T : class
|
||||||
=> behaviourController.GetBehaviourInParent<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any parent");
|
=> behaviourController.GetBehaviourInParent<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any parent");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all <see cref="IBehaviour"/>s of the specified type in it's <see cref="IUniverseObject"/>'s parents recursively and stores them in the provided list.
|
/// Gets all <see cref="IBehaviour"/>s of the specified type in it's <see cref="IUniverseObject"/>'s parents recursively and stores them in the provided list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
|
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
|
||||||
/// <param name="behavioursInParent">The list to store the <see cref="IBehaviour"/>s.</param>
|
/// <param name="behavioursInParent">The list to store the <see cref="IBehaviour"/>s.</param>
|
||||||
public static void GetBehavioursInParent<T>(this IBehaviourController behaviourController, IList<T> behavioursInParent) where T : class
|
/// <param name="collectionMethod">Whether to clear the <paramref name="behavioursInParent"/> before collection or append the results to the list.</param>
|
||||||
|
public static void GetBehavioursInParent<T>(this IBehaviourController behaviourController, IList<T> behavioursInParent, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) where T : class
|
||||||
{
|
{
|
||||||
IBehaviourController? controller = behaviourController;
|
if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
|
||||||
List<T> cache = [];
|
|
||||||
behavioursInParent.Clear();
|
behavioursInParent.Clear();
|
||||||
|
|
||||||
|
IBehaviourController? controller = behaviourController;
|
||||||
while (controller is not null)
|
while (controller is not null)
|
||||||
{
|
{
|
||||||
controller.GetBehaviours(cache);
|
controller.GetBehaviours(behavioursInParent, IBehaviourController.CollectionMethod.Append);
|
||||||
foreach (T behaviour in cache)
|
|
||||||
behavioursInParent.Add(behaviour);
|
|
||||||
|
|
||||||
controller = controller.UniverseObject.Parent?.BehaviourController;
|
controller = controller.UniverseObject.Parent?.BehaviourController;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +138,7 @@ public static class BehaviourControllerExtensions
|
|||||||
if (behaviourController.GetBehaviour<T>() is T localBehaviour)
|
if (behaviourController.GetBehaviour<T>() is T localBehaviour)
|
||||||
return localBehaviour;
|
return localBehaviour;
|
||||||
|
|
||||||
foreach (IUniverseObject child in behaviourController.UniverseObject)
|
foreach (IUniverseObject child in behaviourController.UniverseObject.Children)
|
||||||
if (GetBehaviourInChildren<T>(child.BehaviourController) is T behaviour)
|
if (GetBehaviourInChildren<T>(child.BehaviourController) is T behaviour)
|
||||||
return behaviour;
|
return behaviour;
|
||||||
|
|
||||||
@@ -154,29 +152,27 @@ public static class BehaviourControllerExtensions
|
|||||||
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
|
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
|
||||||
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
|
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
|
||||||
public static T GetRequiredBehaviourInChildren<T>(this IBehaviourController behaviourController) where T : class
|
public static T GetRequiredBehaviourInChildren<T>(this IBehaviourController behaviourController) where T : class
|
||||||
=> behaviourController.GetBehaviourInChildren<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any children ");
|
=> behaviourController.GetBehaviourInChildren<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject?.Name ?? "NULL"}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any children ");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all <see cref="IBehaviour"/>s of the specified type in it's <see cref="IUniverseObject"/>'s children recursively and stores them in the provided list.
|
/// Gets all <see cref="IBehaviour"/>s of the specified type in it's <see cref="IUniverseObject"/>'s children recursively and stores them in the provided list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
|
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
|
||||||
/// <param name="behavioursInChildren">The list to store the <see cref="IBehaviour"/>s.</param>
|
/// <param name="behavioursInChildren">The list to store the <see cref="IBehaviour"/>s.</param>
|
||||||
public static void GetBehavioursInChildren<T>(this IBehaviourController behaviourController, IList<T> behavioursInChildren) where T : class
|
/// <param name="collectionMethod">Whether to clear the <paramref name="behavioursInChildren"/> before collection or append the results to the list.</param>
|
||||||
|
public static void GetBehavioursInChildren<T>(this IBehaviourController behaviourController, IList<T> behavioursInChildren, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear) where T : class
|
||||||
{
|
{
|
||||||
List<T> cache = [];
|
if (collectionMethod == IBehaviourController.CollectionMethod.Clear)
|
||||||
behavioursInChildren.Clear();
|
behavioursInChildren.Clear();
|
||||||
|
|
||||||
TraverseChildrenForBehaviour(behaviourController.UniverseObject, behavioursInChildren, cache);
|
TraverseChildrenForBehaviour(behaviourController.UniverseObject, behavioursInChildren);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void TraverseChildrenForBehaviour<T>(IUniverseObject universeObject, IList<T> behaviours, IList<T> cache) where T : class
|
private static void TraverseChildrenForBehaviour<T>(IUniverseObject universeObject, IList<T> behaviours) where T : class
|
||||||
{
|
{
|
||||||
universeObject.BehaviourController.GetBehaviours(cache);
|
universeObject.BehaviourController.GetBehaviours(behaviours, IBehaviourController.CollectionMethod.Append);
|
||||||
|
|
||||||
foreach (T behaviour in cache)
|
foreach (IUniverseObject child in universeObject.Children)
|
||||||
behaviours.Add(behaviour);
|
TraverseChildrenForBehaviour(child, behaviours);
|
||||||
|
|
||||||
foreach (IUniverseObject child in universeObject)
|
|
||||||
TraverseChildrenForBehaviour(child, behaviours, cache);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public static class EnumExtensions
|
public static class EnumExtensions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public static class FloatExtensions
|
public static class FloatExtensions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public static class TransformExtensions
|
public static class TransformExtensions
|
||||||
{
|
{
|
||||||
@@ -14,4 +14,17 @@ public static class TransformExtensions
|
|||||||
if (localScale.HasValue) transform.LocalScale = localScale.Value;
|
if (localScale.HasValue) transform.LocalScale = localScale.Value;
|
||||||
return transform;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Syntriax.Engine.Core.Exceptions;
|
using Engine.Core.Exceptions;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public static class UniverseExtensions
|
public static class UniverseExtensions
|
||||||
{
|
{
|
||||||
@@ -32,5 +32,5 @@ public static class UniverseExtensions
|
|||||||
/// <typeparam name="T">Type to be searched through the <see cref="IUniverse"/>.</typeparam>
|
/// <typeparam name="T">Type to be searched through the <see cref="IUniverse"/>.</typeparam>
|
||||||
/// <returns>The specified type if found; otherwise, throws <see cref="NotFoundException"/>.</returns>
|
/// <returns>The specified type if found; otherwise, throws <see cref="NotFoundException"/>.</returns>
|
||||||
public static T FindRequired<T>(this IUniverse universe) where T : class
|
public static T FindRequired<T>(this IUniverse universe) where T : class
|
||||||
=> universe.Find<T>() ?? throw new NotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} or {nameof(IBehaviour)} of type {typeof(T).FullName}");
|
=> universe.Root.BehaviourController.GetBehaviourInChildren<T>() ?? throw new NotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} or {nameof(IBehaviour)} of type {typeof(T).FullName}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
using Syntriax.Engine.Core.Exceptions;
|
using Engine.Core.Exceptions;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public static class UniverseObjectExtensions
|
public static class UniverseObjectExtensions
|
||||||
{
|
{
|
||||||
@@ -12,10 +12,25 @@ public static class UniverseObjectExtensions
|
|||||||
if (!string.IsNullOrWhiteSpace(name))
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
universeObject.Name = name;
|
universeObject.Name = name;
|
||||||
if (parent is not null)
|
if (parent is not null)
|
||||||
universeObject.SetParent(parent);
|
universeObject.Parent = parent;
|
||||||
return universeObject;
|
return universeObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerator<IUniverseObject> TraverseChildren(this IUniverseObject universeObject)
|
||||||
|
{
|
||||||
|
static IEnumerable<IUniverseObject> Traverse(IUniverseObject obj)
|
||||||
|
{
|
||||||
|
foreach (IUniverseObject child in obj.Children)
|
||||||
|
{
|
||||||
|
yield return child;
|
||||||
|
foreach (IUniverseObject descendant in Traverse(child))
|
||||||
|
yield return descendant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Traverse(universeObject).GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
#region Universe Object Search
|
#region Universe Object Search
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a <see cref="IUniverseObject"/> of the specified type.
|
/// Gets a <see cref="IUniverseObject"/> of the specified type.
|
||||||
@@ -81,7 +96,7 @@ public static class UniverseObjectExtensions
|
|||||||
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, null.</returns>
|
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, null.</returns>
|
||||||
public static T? GetUniverseObjectInParent<T>(this IUniverseObject universeObject) where T : class
|
public static T? GetUniverseObjectInParent<T>(this IUniverseObject universeObject) where T : class
|
||||||
{
|
{
|
||||||
if (universeObject.GetUniverseObject<T>() is T localUniverseObject)
|
if (universeObject.Children.GetUniverseObject<T>() is T localUniverseObject)
|
||||||
return localUniverseObject;
|
return localUniverseObject;
|
||||||
|
|
||||||
IUniverseObject? parent = universeObject;
|
IUniverseObject? parent = universeObject;
|
||||||
@@ -129,10 +144,10 @@ public static class UniverseObjectExtensions
|
|||||||
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, null.</returns>
|
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, null.</returns>
|
||||||
public static T? GetUniverseObjectInChildren<T>(this IUniverseObject universeObject) where T : class
|
public static T? GetUniverseObjectInChildren<T>(this IUniverseObject universeObject) where T : class
|
||||||
{
|
{
|
||||||
if (universeObject.GetUniverseObject<T>() is T localUniverseObject)
|
if (universeObject.Children.GetUniverseObject<T>() is T localUniverseObject)
|
||||||
return localUniverseObject;
|
return localUniverseObject;
|
||||||
|
|
||||||
foreach (IUniverseObject child in universeObject)
|
foreach (IUniverseObject child in universeObject.Children)
|
||||||
if (GetUniverseObjectInChildren<T>(child) is T behaviour)
|
if (GetUniverseObjectInChildren<T>(child) is T behaviour)
|
||||||
return behaviour;
|
return behaviour;
|
||||||
|
|
||||||
@@ -246,7 +261,7 @@ public static class UniverseObjectExtensions
|
|||||||
|
|
||||||
foreach (IUniverseObject universeObject in universeObjects)
|
foreach (IUniverseObject universeObject in universeObjects)
|
||||||
{
|
{
|
||||||
universeObject.Find(cache);
|
universeObject.Children.Find(cache);
|
||||||
foreach (T behaviour in cache)
|
foreach (T behaviour in cache)
|
||||||
instances.Add(behaviour);
|
instances.Add(behaviour);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core.Factory.Abstract;
|
namespace Engine.Core.Factory.Abstract;
|
||||||
|
|
||||||
public interface IFactory<TInterface> where TInterface : class
|
public interface IFactory<TInterface> where TInterface : class
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using Syntriax.Engine.Core.Exceptions;
|
using Engine.Core.Exceptions;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Factory;
|
namespace Engine.Core.Factory;
|
||||||
|
|
||||||
public class BehaviourControllerFactory
|
public class BehaviourControllerFactory
|
||||||
{
|
{
|
||||||
public static IBehaviourController Instantiate(IUniverseObject universeObject)
|
public static IBehaviourController Instantiate(IUniverseObject universeObject, IStateEnable? stateEnable = null)
|
||||||
=> Instantiate<BehaviourController>(universeObject);
|
=> Instantiate<BehaviourController>(universeObject, stateEnable);
|
||||||
|
|
||||||
public static T Instantiate<T>(IUniverseObject universeObject, params object?[]? args)
|
public static T Instantiate<T>(IUniverseObject universeObject, IStateEnable? stateEnable = null, params object?[]? args)
|
||||||
where T : class, IBehaviourController
|
where T : class, IBehaviourController
|
||||||
{
|
{
|
||||||
T behaviourController = TypeFactory.Get<T>(args);
|
T behaviourController = TypeFactory.Get<T>(args);
|
||||||
@@ -18,6 +18,17 @@ public class BehaviourControllerFactory
|
|||||||
if (!behaviourController.Assign(universeObject))
|
if (!behaviourController.Assign(universeObject))
|
||||||
throw AssignFailedException.From(behaviourController, universeObject);
|
throw AssignFailedException.From(behaviourController, universeObject);
|
||||||
|
|
||||||
|
if (stateEnable is not null)
|
||||||
|
{
|
||||||
|
if (!stateEnable.Assign(behaviourController))
|
||||||
|
throw AssignFailedException.From(stateEnable, behaviourController);
|
||||||
|
|
||||||
|
if (!behaviourController.Assign(stateEnable))
|
||||||
|
throw AssignFailedException.From(behaviourController, stateEnable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
StateEnableFactory.Instantiate(behaviourController);
|
||||||
|
|
||||||
return behaviourController;
|
return behaviourController;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Syntriax.Engine.Core.Exceptions;
|
using Engine.Core.Exceptions;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Factory;
|
namespace Engine.Core.Factory;
|
||||||
|
|
||||||
public class BehaviourFactory
|
public class BehaviourFactory
|
||||||
{
|
{
|
||||||
@@ -12,12 +12,15 @@ public class BehaviourFactory
|
|||||||
{
|
{
|
||||||
T behaviour = TypeFactory.Get<T>(args);
|
T behaviour = TypeFactory.Get<T>(args);
|
||||||
|
|
||||||
stateEnable ??= TypeFactory.Get<StateEnable>();
|
if (stateEnable is not null)
|
||||||
|
{
|
||||||
if (!stateEnable.Assign(behaviour))
|
if (!stateEnable.Assign(behaviour))
|
||||||
throw AssignFailedException.From(stateEnable, behaviour);
|
throw AssignFailedException.From(stateEnable, behaviour);
|
||||||
|
|
||||||
if (!behaviour.Assign(stateEnable))
|
if (!behaviour.Assign(stateEnable))
|
||||||
throw AssignFailedException.From(behaviour, stateEnable);
|
throw AssignFailedException.From(behaviour, stateEnable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
StateEnableFactory.Instantiate(behaviour);
|
||||||
|
|
||||||
return behaviour;
|
return behaviour;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using Syntriax.Engine.Core.Factory.Abstract;
|
using Engine.Core.Factory.Abstract;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Factory;
|
namespace Engine.Core.Factory;
|
||||||
|
|
||||||
public abstract class FactoryBase<TInterface> : IFactory<TInterface>
|
public abstract class FactoryBase<TInterface> : IFactory<TInterface>
|
||||||
where TInterface : class
|
where TInterface : class
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Syntriax.Engine.Core.Exceptions;
|
using Engine.Core.Exceptions;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Factory;
|
namespace Engine.Core.Factory;
|
||||||
|
|
||||||
public class StateEnableFactory
|
public class StateEnableFactory
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core.Factory;
|
namespace Engine.Core.Factory;
|
||||||
|
|
||||||
public class TransformFactory
|
public class TransformFactory
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Factory;
|
namespace Engine.Core.Factory;
|
||||||
|
|
||||||
public static class TypeFactory
|
public static class TypeFactory
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Syntriax.Engine.Core.Exceptions;
|
using Engine.Core.Exceptions;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core.Factory;
|
namespace Engine.Core.Factory;
|
||||||
|
|
||||||
public class UniverseObjectFactory
|
public class UniverseObjectFactory
|
||||||
{
|
{
|
||||||
@@ -18,18 +18,25 @@ public class UniverseObjectFactory
|
|||||||
{
|
{
|
||||||
T universeObject = TypeFactory.Get<T>(args);
|
T universeObject = TypeFactory.Get<T>(args);
|
||||||
|
|
||||||
behaviourController ??= TypeFactory.Get<BehaviourController>();
|
if (behaviourController is not null)
|
||||||
stateEnable ??= TypeFactory.Get<StateEnable>();
|
{
|
||||||
|
|
||||||
if (!behaviourController.Assign(universeObject))
|
if (!behaviourController.Assign(universeObject))
|
||||||
throw AssignFailedException.From(behaviourController, universeObject);
|
throw AssignFailedException.From(behaviourController, universeObject);
|
||||||
if (!stateEnable.Assign(universeObject))
|
|
||||||
throw AssignFailedException.From(stateEnable, universeObject);
|
|
||||||
|
|
||||||
if (!universeObject.Assign(behaviourController))
|
if (!universeObject.Assign(behaviourController))
|
||||||
throw AssignFailedException.From(universeObject, behaviourController);
|
throw AssignFailedException.From(universeObject, behaviourController);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
BehaviourControllerFactory.Instantiate(universeObject);
|
||||||
|
|
||||||
|
if (stateEnable is not null)
|
||||||
|
{
|
||||||
|
if (!stateEnable.Assign(universeObject))
|
||||||
|
throw AssignFailedException.From(stateEnable, universeObject);
|
||||||
if (!universeObject.Assign(stateEnable))
|
if (!universeObject.Assign(stateEnable))
|
||||||
throw AssignFailedException.From(universeObject, stateEnable);
|
throw AssignFailedException.From(universeObject, stateEnable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
StateEnableFactory.Instantiate(universeObject);
|
||||||
|
|
||||||
return universeObject;
|
return universeObject;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
|
||||||
|
|
||||||
public static class DelegateExtensions
|
|
||||||
{
|
|
||||||
[Obsolete($"{nameof(InvokeSafe)} causes memory allocation, please use Invoke() instead.")]
|
|
||||||
public static void InvokeSafe(this Delegate @delegate, params object?[] args)
|
|
||||||
{
|
|
||||||
foreach (Delegate invocation in Delegate.EnumerateInvocationList(@delegate))
|
|
||||||
try { invocation.DynamicInvoke(args); }
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
string methodCallRepresentation = $"{invocation.Method.DeclaringType?.FullName}.{invocation.Method.Name}({string.Join(", ", args)})";
|
|
||||||
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
506
Engine.Core/Helpers/Event.cs
Normal file
506
Engine.Core/Helpers/Event.cs
Normal file
@@ -0,0 +1,506 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Engine.Core.Debug;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
// TODO!: every reverse loop has a chance to have more than 1 unsubscription,
|
||||||
|
// for (int i = listeners.Count - 1; i >= 0; i--)
|
||||||
|
// can be replaced with
|
||||||
|
// for (int i = listeners.Count - 1; i >= 0; i = Math.Min(i - 1, listeners.Count - 1))
|
||||||
|
// but this would causes possible double calls on already called callbacks, find a better method.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a simple event with no parameters.
|
||||||
|
/// <para>Example usage:</para>
|
||||||
|
/// <code>
|
||||||
|
/// public class MyBehaviour : Behaviour, IUpdate
|
||||||
|
/// {
|
||||||
|
/// public readonly Event MyEvent = new();
|
||||||
|
///
|
||||||
|
/// public MyBehaviour()
|
||||||
|
/// {
|
||||||
|
/// MyEvent.AddListener(OnEventTriggered);
|
||||||
|
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// public void Update()
|
||||||
|
/// {
|
||||||
|
/// MyEvent.Invoke();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private void OnEventTriggered()
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"Event occurred!");
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private static void OnEventTriggeredOneTime()
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"Event called once!");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// The output of the example code above would be:
|
||||||
|
/// <code>
|
||||||
|
/// Event occurred!
|
||||||
|
/// Event called once!
|
||||||
|
/// Event occurred!
|
||||||
|
/// Event occurred!
|
||||||
|
/// Event occurred!
|
||||||
|
/// ...
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
public class Event
|
||||||
|
{
|
||||||
|
// We use Ascending order because draw calls are running from last to first
|
||||||
|
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
|
||||||
|
|
||||||
|
private ILogger _logger = ILogger.Shared;
|
||||||
|
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
|
||||||
|
|
||||||
|
private readonly List<ListenerData> listeners = null!;
|
||||||
|
private readonly List<ListenerData> onceListeners = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes the callback to be invoked whenever the event is triggered.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback to be called when the event is triggered.</param>
|
||||||
|
/// <param name="priority">Priority of the callback.</param>
|
||||||
|
public void AddListener(EventHandler listener, int priority = 0)
|
||||||
|
{
|
||||||
|
ListenerData listenerData = new(listener, priority);
|
||||||
|
|
||||||
|
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
|
||||||
|
if (insertIndex < 0)
|
||||||
|
insertIndex = ~insertIndex;
|
||||||
|
|
||||||
|
listeners.Insert(insertIndex, listenerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
|
||||||
|
/// <param name="priority">Priority of the callback.</param>
|
||||||
|
public void AddOneTimeListener(EventHandler listener, int priority = 0)
|
||||||
|
{
|
||||||
|
ListenerData listenerData = new(listener, priority);
|
||||||
|
|
||||||
|
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
|
||||||
|
if (insertIndex < 0)
|
||||||
|
insertIndex = ~insertIndex;
|
||||||
|
|
||||||
|
onceListeners.Insert(insertIndex, listenerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
|
||||||
|
public void RemoveListener(EventHandler listener)
|
||||||
|
{
|
||||||
|
for (int i = listeners.Count - 1; i >= 0; i--)
|
||||||
|
if (listeners[i].Callback == listener)
|
||||||
|
{
|
||||||
|
listeners.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
|
||||||
|
public void RemoveOneTimeListener(EventHandler listener)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < onceListeners.Count; i++)
|
||||||
|
if (onceListeners[i].Callback == listener)
|
||||||
|
{
|
||||||
|
onceListeners.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear() { listeners.Clear(); onceListeners.Clear(); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers the event.
|
||||||
|
/// </summary>
|
||||||
|
public void Invoke()
|
||||||
|
{
|
||||||
|
for (int i = listeners.Count - 1; i >= 0; i--)
|
||||||
|
try { listeners[i].Callback.Invoke(); }
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()";
|
||||||
|
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = onceListeners.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
try { onceListeners[i].Callback.Invoke(); }
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()";
|
||||||
|
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? this, Logger, exception, methodCallRepresentation);
|
||||||
|
}
|
||||||
|
onceListeners.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
|
||||||
|
{
|
||||||
|
listeners = new(initialListenerCount);
|
||||||
|
onceListeners = new(initialOnceListenerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event()
|
||||||
|
{
|
||||||
|
listeners = new(4);
|
||||||
|
onceListeners = new(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void EventHandler();
|
||||||
|
private record struct ListenerData(EventHandler Callback, int Priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an event with only sender parameters.
|
||||||
|
/// <para>Example usage:</para>
|
||||||
|
/// <code>
|
||||||
|
/// public class MyBehaviour : Behaviour, IUpdate
|
||||||
|
/// {
|
||||||
|
/// public readonly Event<MyBehaviour> MyEvent = new();
|
||||||
|
///
|
||||||
|
/// public MyBehaviour()
|
||||||
|
/// {
|
||||||
|
/// MyEvent.AddListener(OnEventTriggered);
|
||||||
|
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// public void Update()
|
||||||
|
/// {
|
||||||
|
/// MyEvent.Invoke(this);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private void OnEventTriggered(MyBehaviour sender)
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"{sender.Id}'s event occurred!");
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private static void OnEventTriggeredOneTime(MyBehaviour sender)
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"{sender.Id}'s event called once!");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// The output of the example code above would be:
|
||||||
|
/// <code>
|
||||||
|
/// [Id]'s event occurred!
|
||||||
|
/// [Id]'s event called once!
|
||||||
|
/// [Id]'s event occurred!
|
||||||
|
/// [Id]'s event occurred!
|
||||||
|
/// [Id]'s event occurred!
|
||||||
|
/// ...
|
||||||
|
/// </code>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TSender">Sender type</typeparam>
|
||||||
|
public class Event<TSender> where TSender : class
|
||||||
|
{
|
||||||
|
// We use Ascending order because draw calls are running from last to first
|
||||||
|
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
|
||||||
|
|
||||||
|
private ILogger _logger = ILogger.Shared;
|
||||||
|
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
|
||||||
|
|
||||||
|
private readonly List<ListenerData> listeners = null!;
|
||||||
|
private readonly List<ListenerData> onceListeners = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes the callback to be invoked whenever the event is triggered.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback to be called when the event is triggered.</param>
|
||||||
|
/// <param name="priority">Priority of the callback.</param>
|
||||||
|
public void AddListener(EventHandler listener, int priority = 0)
|
||||||
|
{
|
||||||
|
ListenerData listenerData = new(listener, priority);
|
||||||
|
|
||||||
|
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
|
||||||
|
if (insertIndex < 0)
|
||||||
|
insertIndex = ~insertIndex;
|
||||||
|
|
||||||
|
listeners.Insert(insertIndex, listenerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
|
||||||
|
/// <param name="priority">Priority of the callback.</param>
|
||||||
|
public void AddOneTimeListener(EventHandler listener, int priority = 0)
|
||||||
|
{
|
||||||
|
ListenerData listenerData = new(listener, priority);
|
||||||
|
|
||||||
|
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
|
||||||
|
if (insertIndex < 0)
|
||||||
|
insertIndex = ~insertIndex;
|
||||||
|
|
||||||
|
onceListeners.Insert(insertIndex, listenerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
|
||||||
|
public void RemoveListener(EventHandler listener)
|
||||||
|
{
|
||||||
|
for (int i = listeners.Count - 1; i >= 0; i--)
|
||||||
|
if (listeners[i].Callback == listener)
|
||||||
|
{
|
||||||
|
listeners.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
|
||||||
|
public void RemoveOneTimeListener(EventHandler listener)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < onceListeners.Count; i++)
|
||||||
|
if (onceListeners[i].Callback == listener)
|
||||||
|
{
|
||||||
|
onceListeners.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear() { listeners.Clear(); onceListeners.Clear(); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers the event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The caller that's triggering this event.</param>
|
||||||
|
public void Invoke(TSender sender)
|
||||||
|
{
|
||||||
|
for (int i = listeners.Count - 1; i >= 0; i--)
|
||||||
|
try { listeners[i].Callback.Invoke(sender); }
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})";
|
||||||
|
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = onceListeners.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
try { onceListeners[i].Callback.Invoke(sender); }
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})";
|
||||||
|
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
|
||||||
|
}
|
||||||
|
onceListeners.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
|
||||||
|
{
|
||||||
|
listeners = new(initialListenerCount);
|
||||||
|
onceListeners = new(initialOnceListenerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event()
|
||||||
|
{
|
||||||
|
listeners = new(4);
|
||||||
|
onceListeners = new(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void EventHandler(TSender sender);
|
||||||
|
private record struct ListenerData(EventHandler Callback, int Priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an event with sender and argument parameters.
|
||||||
|
/// <para>Example usage:</para>
|
||||||
|
/// <code>
|
||||||
|
/// public class MyBehaviour : Behaviour, IUpdate
|
||||||
|
/// {
|
||||||
|
/// public readonly Event<MyBehaviour, MyArguments> MyEvent = new();
|
||||||
|
///
|
||||||
|
/// private int myInt = 0;
|
||||||
|
/// private bool myBool = false;
|
||||||
|
///
|
||||||
|
/// public MyBehaviour()
|
||||||
|
/// {
|
||||||
|
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
|
||||||
|
/// MyEvent.AddListener(OnEventTriggered);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// public void Update()
|
||||||
|
/// {
|
||||||
|
/// MyEvent.Invoke(this, new MyArguments(myInt, myBool));
|
||||||
|
/// myInt++;
|
||||||
|
/// myBool = !myBool;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private void OnEventTriggered(MyBehaviour sender, MyArguments args)
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"{sender.Id}'s event occurred with MyInt: {args.MyInt} and MyBool {args.MyBool}!");
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// private static void OnEventTriggeredOneTime(MyBehaviour sender, MyArguments args)
|
||||||
|
/// {
|
||||||
|
/// Console.WriteLine($"{sender.Id}'s event called once with MyInt: {args.MyInt} and MyBool {args.MyBool}!");
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// public readonly record struct MyArguments(int MyInt, bool MyBool);
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// The output of the example code above would be:
|
||||||
|
/// <code>
|
||||||
|
/// [Id]'s event occurred with MyInt: 0 and MyBool False!
|
||||||
|
/// [Id]'s event called once with MyInt: 0 and MyBool False!
|
||||||
|
/// [Id]'s event occurred with MyInt: 1 and MyBool True!
|
||||||
|
/// [Id]'s event occurred with MyInt: 2 and MyBool False!
|
||||||
|
/// [Id]'s event occurred with MyInt: 3 and MyBool True!
|
||||||
|
/// ...
|
||||||
|
/// </code>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TSender">Sender type</typeparam>
|
||||||
|
public class Event<TSender, TArguments> where TSender : class
|
||||||
|
{
|
||||||
|
// We use Ascending order because draw calls are running from last to first
|
||||||
|
private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
|
||||||
|
|
||||||
|
private ILogger _logger = ILogger.Shared;
|
||||||
|
public ILogger Logger { get => _logger; set => _logger = value ?? ILogger.Shared; }
|
||||||
|
|
||||||
|
private readonly List<ListenerData> listeners = null!;
|
||||||
|
private readonly List<ListenerData> onceListeners = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes the callback to be invoked whenever the event is triggered.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback to be called when the event is triggered.</param>
|
||||||
|
/// <param name="priority">Priority of the callback.</param>
|
||||||
|
public void AddListener(EventHandler listener, int priority = 0)
|
||||||
|
{
|
||||||
|
ListenerData listenerData = new(listener, priority);
|
||||||
|
|
||||||
|
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
|
||||||
|
if (insertIndex < 0)
|
||||||
|
insertIndex = ~insertIndex;
|
||||||
|
|
||||||
|
listeners.Insert(insertIndex, listenerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
|
||||||
|
/// <param name="priority">Priority of the callback.</param>
|
||||||
|
public void AddOneTimeListener(EventHandler listener, int priority = 0)
|
||||||
|
{
|
||||||
|
ListenerData listenerData = new(listener, priority);
|
||||||
|
|
||||||
|
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
|
||||||
|
if (insertIndex < 0)
|
||||||
|
insertIndex = ~insertIndex;
|
||||||
|
|
||||||
|
onceListeners.Insert(insertIndex, listenerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
|
||||||
|
public void RemoveListener(EventHandler listener)
|
||||||
|
{
|
||||||
|
for (int i = listeners.Count - 1; i >= 0; i--)
|
||||||
|
if (listeners[i].Callback == listener)
|
||||||
|
{
|
||||||
|
listeners.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
|
||||||
|
public void RemoveOneTimeListener(EventHandler listener)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < onceListeners.Count; i++)
|
||||||
|
if (onceListeners[i].Callback == listener)
|
||||||
|
{
|
||||||
|
onceListeners.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear() { listeners.Clear(); onceListeners.Clear(); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers the event.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sender">The caller that's triggering this event.</param>
|
||||||
|
/// <param name="args">The arguments provided for this event.</param>
|
||||||
|
public void Invoke(TSender sender, TArguments args)
|
||||||
|
{
|
||||||
|
for (int i = listeners.Count - 1; i >= 0; i--)
|
||||||
|
try { listeners[i].Callback.Invoke(sender, args); }
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender}, {args})";
|
||||||
|
EventHelpers.LogInvocationException(listeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = onceListeners.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
try { onceListeners[i].Callback.Invoke(sender, args); }
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender}, {args})";
|
||||||
|
EventHelpers.LogInvocationException(onceListeners[i].Callback.Target ?? sender, Logger, exception, methodCallRepresentation);
|
||||||
|
}
|
||||||
|
onceListeners.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event(int initialListenerCount = 4, int initialOnceListenerCount = 2)
|
||||||
|
{
|
||||||
|
listeners = new(initialListenerCount);
|
||||||
|
onceListeners = new(initialOnceListenerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Event()
|
||||||
|
{
|
||||||
|
listeners = new(4);
|
||||||
|
onceListeners = new(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void EventHandler(TSender sender, TArguments args);
|
||||||
|
private record struct ListenerData(EventHandler Callback, int Priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class EventHelpers
|
||||||
|
{
|
||||||
|
public static void LogInvocationException(object sender, ILogger logger, Exception exception, string methodCallRepresentation)
|
||||||
|
{
|
||||||
|
logger.LogException(sender, exception);
|
||||||
|
logger.LogError(sender, $"Unexpected exception on invocation of method {methodCallRepresentation}");
|
||||||
|
}
|
||||||
|
}
|
||||||
102
Engine.Core/Helpers/FastList.cs
Normal file
102
Engine.Core/Helpers/FastList.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public class FastList<T> : IList<T>, IReadOnlyList<T>, IEnumerable<T> where T : notnull
|
||||||
|
{
|
||||||
|
private readonly List<T> items = [];
|
||||||
|
private readonly Dictionary<T, int> indexMap = [];
|
||||||
|
|
||||||
|
public bool IsReadOnly { get; set; } = false;
|
||||||
|
public int Count => items.Count;
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get => items[index];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
items[index] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
indexMap[item] = items.Count;
|
||||||
|
items.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int i) => Remove(items[i], i);
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
if (!indexMap.TryGetValue(item, out int index))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Remove(item, index);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Remove(T item, int index)
|
||||||
|
{
|
||||||
|
int lastIndex = items.Count - 1;
|
||||||
|
T lastItem = items[lastIndex];
|
||||||
|
|
||||||
|
items[index] = lastItem;
|
||||||
|
indexMap[lastItem] = index;
|
||||||
|
|
||||||
|
items.RemoveAt(lastIndex);
|
||||||
|
indexMap.Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, T item)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
items.Insert(index, item);
|
||||||
|
|
||||||
|
for (int i = index; i < items.Count; i++)
|
||||||
|
indexMap[items[i]] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
items.Clear();
|
||||||
|
indexMap.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(T item) => indexMap.ContainsKey(item);
|
||||||
|
public int IndexOf(T item) => items.IndexOf(item);
|
||||||
|
public int BinarySearch(T item, IComparer<T>? comparer = null) => items.BinarySearch(item, comparer);
|
||||||
|
|
||||||
|
public void Sort(IComparer<T> comparer)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
items.Sort(comparer);
|
||||||
|
|
||||||
|
for (int i = 0; i < items.Count; i++)
|
||||||
|
indexMap[items[i]] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(T[] array, int arrayIndex) => items.CopyTo(array, arrayIndex);
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() => items.GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
public FastList() { }
|
||||||
|
public FastList(int count) { items.Capacity = count; }
|
||||||
|
}
|
||||||
172
Engine.Core/Helpers/FastListOrdered.cs
Normal file
172
Engine.Core/Helpers/FastListOrdered.cs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// TODO This is VEERY experimental, and doesn't work well with the indices access. Use with caution
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TIndex"></typeparam>
|
||||||
|
/// <typeparam name="TItem"></typeparam>
|
||||||
|
public class FastListOrdered<TIndex, TItem> : IList<TItem>, IReadOnlyList<TItem>, IEnumerable<TItem> where TItem : notnull where TIndex : IComparable
|
||||||
|
{
|
||||||
|
private readonly SortedDictionary<TIndex, FastList<TItem>> items = null!;
|
||||||
|
|
||||||
|
private readonly Func<TItem, TIndex> getIndexFunc = null!;
|
||||||
|
private readonly IComparer<TIndex> sortBy = null!;
|
||||||
|
|
||||||
|
private int count = 0;
|
||||||
|
public int Count => count;
|
||||||
|
|
||||||
|
public bool IsReadOnly { get; set; } = false;
|
||||||
|
|
||||||
|
public TItem this[int index]
|
||||||
|
{
|
||||||
|
get { (TIndex tIndex, int i) = GetAt(index); return items[tIndex][i]; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
(TIndex tIndex, int i) = GetAt(index); items[tIndex][i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (TIndex TIndex, int i) GetAt(Index index)
|
||||||
|
{
|
||||||
|
int actualIndex = index.IsFromEnd
|
||||||
|
? count - index.Value
|
||||||
|
: index.Value;
|
||||||
|
|
||||||
|
if (actualIndex < 0 || actualIndex >= count)
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
|
int leftIndex = actualIndex;
|
||||||
|
foreach ((TIndex i, FastList<TItem> list) in items)
|
||||||
|
{
|
||||||
|
if (leftIndex < list.Count)
|
||||||
|
return (i, leftIndex);
|
||||||
|
leftIndex -= list.Count;
|
||||||
|
}
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int IndexOf(TItem item)
|
||||||
|
{
|
||||||
|
int indexCounter = 0;
|
||||||
|
foreach ((TIndex index, FastList<TItem> list) in items)
|
||||||
|
{
|
||||||
|
int i = list.IndexOf(item);
|
||||||
|
if (i != -1)
|
||||||
|
return indexCounter + i;
|
||||||
|
indexCounter += list.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(TItem item)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
TIndex key = getIndexFunc(item);
|
||||||
|
if (!items.TryGetValue(key, out FastList<TItem>? list))
|
||||||
|
items[key] = list = [];
|
||||||
|
|
||||||
|
list.Add(item);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, TItem item)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
TIndex tIndex = getIndexFunc(item);
|
||||||
|
if (!items.TryGetValue(tIndex, out FastList<TItem>? list))
|
||||||
|
items[tIndex] = list = [];
|
||||||
|
|
||||||
|
list.Insert(index, item);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(TItem item)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
TIndex index = getIndexFunc(item);
|
||||||
|
if (!items.TryGetValue(index, out FastList<TItem>? list))
|
||||||
|
throw new Exceptions.NotFoundException($"Index of '{index}' is not found in the collector");
|
||||||
|
|
||||||
|
if (!list.Remove(item))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
count--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
(TIndex tIndex, int i) = GetAt(index);
|
||||||
|
items[tIndex].RemoveAt(i);
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (IsReadOnly)
|
||||||
|
throw new System.Data.ReadOnlyException();
|
||||||
|
|
||||||
|
foreach ((TIndex index, FastList<TItem> list) in items)
|
||||||
|
list.Clear();
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(TItem item)
|
||||||
|
{
|
||||||
|
foreach ((TIndex index, FastList<TItem> list) in items)
|
||||||
|
if (list.Contains(item))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(TItem[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
int indexCounter = 0;
|
||||||
|
|
||||||
|
foreach ((TIndex index, FastList<TItem> list) in items)
|
||||||
|
{
|
||||||
|
list.CopyTo(array, indexCounter);
|
||||||
|
indexCounter += list.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<TItem> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach ((TIndex index, FastList<TItem> list) in items)
|
||||||
|
foreach (TItem item in list)
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
public FastListOrdered(Func<TItem, TIndex> getIndexFunc, Comparison<TIndex> sortBy)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = Comparer<TIndex>.Create(sortBy);
|
||||||
|
items = new(this.sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastListOrdered(Func<TItem, TIndex> getIndexFunc, IComparer<TIndex> sortBy)
|
||||||
|
{
|
||||||
|
this.getIndexFunc = getIndexFunc;
|
||||||
|
this.sortBy = sortBy;
|
||||||
|
items = new(sortBy);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Engine.Core/Helpers/IPool.cs
Normal file
10
Engine.Core/Helpers/IPool.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public interface IPool<T>
|
||||||
|
{
|
||||||
|
Event<IPool<T>, T> OnRemoved { get; }
|
||||||
|
Event<IPool<T>, T> OnReturned { get; }
|
||||||
|
|
||||||
|
T Get();
|
||||||
|
void Return(T item);
|
||||||
|
}
|
||||||
40
Engine.Core/Helpers/ListPool.cs
Normal file
40
Engine.Core/Helpers/ListPool.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public class ListPool<T> : IPool<List<T>>
|
||||||
|
{
|
||||||
|
public Event<IPool<List<T>>, List<T>> OnReturned { get; } = new();
|
||||||
|
public Event<IPool<List<T>>, List<T>> OnRemoved { get; } = new();
|
||||||
|
|
||||||
|
private readonly Func<List<T>> generator = null!;
|
||||||
|
private readonly Queue<List<T>> queue = new();
|
||||||
|
|
||||||
|
public List<T> Get()
|
||||||
|
{
|
||||||
|
if (!queue.TryDequeue(out List<T>? result))
|
||||||
|
result = generator();
|
||||||
|
|
||||||
|
result.Clear();
|
||||||
|
OnRemoved?.Invoke(this, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Return(List<T> list)
|
||||||
|
{
|
||||||
|
if (queue.Contains(list))
|
||||||
|
return;
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
queue.Enqueue(list);
|
||||||
|
OnReturned?.Invoke(this, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListPool(int initialListCount = 1, int initialListCapacity = 32)
|
||||||
|
{
|
||||||
|
generator = () => new(initialListCapacity);
|
||||||
|
for (int i = 0; i < initialListCount; i++)
|
||||||
|
queue.Enqueue(generator());
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Engine.Core/Helpers/Pool.cs
Normal file
41
Engine.Core/Helpers/Pool.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
public class Pool<T> : IPool<T>
|
||||||
|
{
|
||||||
|
public Event<IPool<T>, T> OnRemoved { get; } = new();
|
||||||
|
public Event<IPool<T>, T> OnReturned { get; } = new();
|
||||||
|
|
||||||
|
private readonly Func<T> generator = null!;
|
||||||
|
private readonly Queue<T> queue = new();
|
||||||
|
private readonly HashSet<T> queuedHashes = [];
|
||||||
|
|
||||||
|
public T Get()
|
||||||
|
{
|
||||||
|
if (!queue.TryDequeue(out T? result))
|
||||||
|
result = generator();
|
||||||
|
|
||||||
|
queuedHashes.Remove(result);
|
||||||
|
OnRemoved?.Invoke(this, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Return(T item)
|
||||||
|
{
|
||||||
|
if (queuedHashes.Contains(item))
|
||||||
|
return;
|
||||||
|
|
||||||
|
queue.Enqueue(item);
|
||||||
|
queuedHashes.Add(item);
|
||||||
|
OnReturned?.Invoke(this, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pool(Func<T> generator, int initialCapacity = 1)
|
||||||
|
{
|
||||||
|
this.generator = generator;
|
||||||
|
for (int i = 0; i < initialCapacity; i++)
|
||||||
|
queue.Enqueue(generator());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public interface IProgressionTracker : IReadOnlyProgressionTracker
|
public interface IProgressionTracker : IReadOnlyProgressionTracker
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public interface IReadOnlyProgressionTracker
|
public interface IReadOnlyProgressionTracker
|
||||||
{
|
{
|
||||||
event ProgressionUpdatedEventHandler? OnUpdated;
|
Event<IReadOnlyProgressionTracker, ProgressionUpdatedArguments> OnUpdated { get; }
|
||||||
event ProgressionEndedEventHandler? OnEnded;
|
Event<IReadOnlyProgressionTracker> OnEnded { get; }
|
||||||
|
|
||||||
float Progression { get; }
|
float Progression { get; }
|
||||||
string Status { get; }
|
string Status { get; }
|
||||||
|
|
||||||
delegate void ProgressionUpdatedEventHandler(IReadOnlyProgressionTracker sender, float previousProgression, string previousStatus);
|
readonly record struct ProgressionUpdatedArguments(float PreviousProgression, string PreviousStatus);
|
||||||
delegate void ProgressionEndedEventHandler(IReadOnlyProgressionTracker sender);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public class ProgressionTracker : IProgressionTracker
|
public class ProgressionTracker : IProgressionTracker
|
||||||
{
|
{
|
||||||
public event IReadOnlyProgressionTracker.ProgressionUpdatedEventHandler? OnUpdated = null;
|
public Event<IReadOnlyProgressionTracker, IReadOnlyProgressionTracker.ProgressionUpdatedArguments> OnUpdated { get; } = new();
|
||||||
public event IReadOnlyProgressionTracker.ProgressionEndedEventHandler? OnEnded = null;
|
public Event<IReadOnlyProgressionTracker> OnEnded { get; } = new();
|
||||||
|
|
||||||
public float Progression { get; private set; } = 0f;
|
public float Progression { get; private set; } = 0f;
|
||||||
public string Status { get; private set; } = "Default";
|
public string Status { get; private set; } = "Default";
|
||||||
@@ -19,7 +19,7 @@ public class ProgressionTracker : IProgressionTracker
|
|||||||
Progression = progression.Clamp(Progression, 1f);
|
Progression = progression.Clamp(Progression, 1f);
|
||||||
Status = status;
|
Status = status;
|
||||||
|
|
||||||
OnUpdated?.Invoke(this, previousProgression, previousStatus);
|
OnUpdated?.Invoke(this, new(previousProgression, previousStatus));
|
||||||
|
|
||||||
if (progression >= 1f)
|
if (progression >= 1f)
|
||||||
OnEnded?.Invoke(this);
|
OnEnded?.Invoke(this);
|
||||||
@@ -30,7 +30,7 @@ public class ProgressionTracker : IProgressionTracker
|
|||||||
Progression = 0f;
|
Progression = 0f;
|
||||||
Status = "Default";
|
Status = "Default";
|
||||||
|
|
||||||
OnUpdated = null;
|
OnUpdated.Clear();
|
||||||
OnEnded = null;
|
OnEnded.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public record struct ProgressiveTask<T>(IReadOnlyProgressionTracker ProgressionTracker, Task<T> Task)
|
public record struct ProgressiveTask<T>(IReadOnlyProgressionTracker ProgressionTracker, Task<T> Task)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public static class Math
|
public static class Math
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The value of Pi (π), a mathematical constant approximately equal to 3.14159.
|
/// The value of Pi (π).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float PI = 3.1415926535897932f;
|
public const float Pi = 3.1415926535897932f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The value of Tau (τ), a mathematical constant equal to 2π, approximately equal to 6.28319.
|
/// The value of Tau (τ), mathematical constant equal to 2π.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float Tau = 2f * PI;
|
public const float Tau = 2f * Pi;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base of the natural logarithm, approximately equal to 2.71828.
|
/// The base of the natural logarithm.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float E = 2.718281828459045f;
|
public const float E = 2.718281828459045f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The conversion factor from radians to degrees.
|
/// The conversion factor from radians to degrees.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float RadianToDegree = 180f / PI;
|
public const float RadianToDegree = 180f / Pi;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The conversion factor from degrees to radians.
|
/// The conversion factor from degrees to radians.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const float DegreeToRadian = PI / 180f;
|
public const float DegreeToRadian = Pi / 180f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets one minus of given <see cref="T"/>.
|
/// Gets one minus of given <see cref="T"/>.
|
||||||
@@ -110,6 +110,13 @@ public static class Math
|
|||||||
/// <returns>The sine of <paramref name="x"/>.</returns>
|
/// <returns>The sine of <paramref name="x"/>.</returns>
|
||||||
public static float Sin(float x) => MathF.Sin(x);
|
public static float Sin(float x) => MathF.Sin(x);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the tangent of a number.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The angle, in radians.</param>
|
||||||
|
/// <returns>The tangent of <paramref name="x"/>.</returns>
|
||||||
|
public static float Tan(float x) => MathF.Tan(x);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the arccosine of a number.
|
/// Returns the arccosine of a number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -124,6 +131,13 @@ public static class Math
|
|||||||
/// <returns>The arcsine of <paramref name="x"/>.</returns>
|
/// <returns>The arcsine of <paramref name="x"/>.</returns>
|
||||||
public static float Asin(float x) => MathF.Asin(x);
|
public static float Asin(float x) => MathF.Asin(x);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the angle whose tangent is the specified number.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The tangent value.</param>
|
||||||
|
/// <returns>The angle, in radians.</returns>
|
||||||
|
public static float Atan(float x) => MathF.Atan(x);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the angle whose tangent is the quotient of two specified numbers.
|
/// Returns the angle whose tangent is the quotient of two specified numbers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -240,21 +254,33 @@ public static class Math
|
|||||||
public static T Lerp<T>(T x, T y, T t) where T : IFloatingPoint<T> => x + (y - x) * t;
|
public static T Lerp<T>(T x, T y, T t) where T : IFloatingPoint<T> => x + (y - x) * t;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rounds a number to a specified number of fractional digits.
|
/// Rounds a number to the closest integer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="x">The number to round.</param>
|
/// <param name="x">The number to round.</param>
|
||||||
/// <param name="digits">The number of fractional digits in the return value.</param>
|
/// <param name="roundMode">Specification for how to round <paramref name="x"/> if it is midway between two other numbers.</param>
|
||||||
/// <param name="mode">Specification for how to round <paramref name="x"/> if it is midway between two other numbers.</param>
|
/// <returns>The number <paramref name="x"/> rounded to the closest integer.</returns>
|
||||||
/// <returns>The number <paramref name="x"/> rounded to <paramref name="digits"/> fractional digits.</returns>
|
public static float Round(float x, RoundMode roundMode) => RoundToInt(x, roundMode);
|
||||||
public static float Round(float x, int digits, MidpointRounding mode) => MathF.Round(x, digits, mode);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rounds a number to an integer.
|
/// Rounds a number to the closest integer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="x">The number to round.</param>
|
/// <param name="x">The number to round.</param>
|
||||||
/// <param name="roundMode">Specification for how to round <paramref name="x"/> if it's midway between two numbers</param>
|
/// <param name="roundMode">Specification for how to round <paramref name="x"/> if it's midway between two numbers</param>
|
||||||
/// <returns></returns>
|
/// <returns>The number <paramref name="x"/> rounded to the closest integer.</returns>
|
||||||
public static int RoundToInt(float x, RoundMode roundMode = RoundMode.Ceil) => (int)MathF.Round(x, 0, roundMode == RoundMode.Ceil ? MidpointRounding.ToPositiveInfinity : MidpointRounding.ToNegativeInfinity);
|
public static int RoundToInt(float x, RoundMode roundMode = RoundMode.Ceil)
|
||||||
|
{
|
||||||
|
float remainder = x.Mod(1f);
|
||||||
|
|
||||||
|
if (remainder == .5f)
|
||||||
|
if (roundMode == RoundMode.Floor)
|
||||||
|
return (int)x;
|
||||||
|
else
|
||||||
|
return (int)(x + .5f);
|
||||||
|
|
||||||
|
if (x < 0f)
|
||||||
|
return (int)(x - .5f);
|
||||||
|
return (int)(x + .5f);
|
||||||
|
}
|
||||||
public enum RoundMode { Ceil, Floor };
|
public enum RoundMode { Ceil, Floor };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
public static class MathExtensions
|
public static class MathExtensions
|
||||||
{
|
{
|
||||||
@@ -32,12 +32,18 @@ public static class MathExtensions
|
|||||||
/// <inheritdoc cref="Math.Sin(float)" />
|
/// <inheritdoc cref="Math.Sin(float)" />
|
||||||
public static float Sin(this float x) => Math.Sin(x);
|
public static float Sin(this float x) => Math.Sin(x);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Math.Tan(float)" />
|
||||||
|
public static float Tan(this float x) => Math.Tan(x);
|
||||||
|
|
||||||
/// <inheritdoc cref="Math.Acos(float)" />
|
/// <inheritdoc cref="Math.Acos(float)" />
|
||||||
public static float Acos(this float x) => Math.Acos(x);
|
public static float Acos(this float x) => Math.Acos(x);
|
||||||
|
|
||||||
/// <inheritdoc cref="Math.Asin(float)" />
|
/// <inheritdoc cref="Math.Asin(float)" />
|
||||||
public static float Asin(this float x) => Math.Asin(x);
|
public static float Asin(this float x) => Math.Asin(x);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Math.Atan(float)" />
|
||||||
|
public static float Atan(this float x) => Math.Atan(x);
|
||||||
|
|
||||||
/// <inheritdoc cref="Math.Atan2(float, float)" />
|
/// <inheritdoc cref="Math.Atan2(float, float)" />
|
||||||
public static float Atan2(this float y, float x) => Math.Atan2(y, x);
|
public static float Atan2(this float y, float x) => Math.Atan2(y, x);
|
||||||
|
|
||||||
@@ -81,7 +87,7 @@ public static class MathExtensions
|
|||||||
public static T Lerp<T>(this T x, T y, T t) where T : IFloatingPoint<T> => Math.Lerp(x, y, t);
|
public static T Lerp<T>(this T x, T y, T t) where T : IFloatingPoint<T> => Math.Lerp(x, y, t);
|
||||||
|
|
||||||
/// <inheritdoc cref="Math.Round(float, int, MidpointRounding)" />
|
/// <inheritdoc cref="Math.Round(float, int, MidpointRounding)" />
|
||||||
public static float Round(this float x, int digits, MidpointRounding mode) => Math.Round(x, digits, mode);
|
public static float Round(this float x, Math.RoundMode mode) => Math.Round(x, mode);
|
||||||
|
|
||||||
/// <inheritdoc cref="Math.RoundToInt(float, Math.RoundMode)" />
|
/// <inheritdoc cref="Math.RoundToInt(float, Math.RoundMode)" />
|
||||||
public static int RoundToInt(this float x, Math.RoundMode roundMode = Math.RoundMode.Ceil) => Math.RoundToInt(x, roundMode);
|
public static int RoundToInt(this float x, Math.RoundMode roundMode = Math.RoundMode.Ceil) => Math.RoundToInt(x, roundMode);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Syntriax.Engine.Core
|
namespace Engine.Core
|
||||||
{
|
{
|
||||||
// This is pretty much so the assembly gets loaded automatically because
|
// This is pretty much so the assembly gets loaded automatically because
|
||||||
// the builds include the assembly but sometimes doesn't link load it at startup.
|
// the builds include the assembly but sometimes doesn't link load it at startup.
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Syntriax.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)
|
|
||||||
{
|
|
||||||
/// <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;
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
/// 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>
|
|
||||||
/// 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>
|
|
||||||
/// 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);
|
|
||||||
}
|
|
||||||
114
Engine.Core/Primitives/AABB2D.cs
Normal file
114
Engine.Core/Primitives/AABB2D.cs
Normal 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);
|
||||||
|
}
|
||||||
113
Engine.Core/Primitives/AABB3D.cs
Normal file
113
Engine.Core/Primitives/AABB3D.cs
Normal 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);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a 2D circle.
|
/// Represents a 2D circle.
|
||||||
@@ -11,10 +12,10 @@ namespace Syntriax.Engine.Core;
|
|||||||
/// Initializes a new instance of the <see cref="Circle"/> struct with the specified center and radius.
|
/// Initializes a new instance of the <see cref="Circle"/> struct with the specified center and radius.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DebuggerDisplay("Center: {Center.ToString(),nq}, Radius: {Radius}")]
|
[DebuggerDisplay("Center: {Center.ToString(),nq}, Radius: {Radius}")]
|
||||||
public readonly struct Circle(Vector2D center, float radius)
|
public readonly struct Circle(Vector2D center, float radius) : IEquatable<Circle>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The center of the circle.
|
/// The center of the <see cref="Circle"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Vector2D Center = center;
|
public readonly Vector2D Center = center;
|
||||||
|
|
||||||
@@ -38,6 +39,11 @@ public readonly struct Circle(Vector2D center, float radius)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f);
|
public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f);
|
||||||
|
|
||||||
|
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>
|
/// <summary>
|
||||||
/// Sets the center of the <see cref="Circle"/>.
|
/// Sets the center of the <see cref="Circle"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -77,6 +83,26 @@ public readonly struct Circle(Vector2D center, float radius)
|
|||||||
/// <returns><see cref="true"/> if the <see cref="Circle"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
|
/// <returns><see cref="true"/> if the <see cref="Circle"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
|
||||||
public static bool ApproximatelyEquals(Circle left, Circle right, float epsilon = float.Epsilon)
|
public static bool ApproximatelyEquals(Circle left, Circle right, float epsilon = float.Epsilon)
|
||||||
=> left.Center.ApproximatelyEquals(right.Center, epsilon) && left.Radius.ApproximatelyEquals(right.Radius, 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="Circle"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to compare with the current <see cref="Circle"/>.</param>
|
||||||
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Circle"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public override bool Equals(object? obj) => obj is Circle circle && this == circle;
|
||||||
|
public bool Equals(Circle other) => this == other;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a hash code for the <see cref="Circle"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A hash code for the <see cref="Circle"/>.</returns>
|
||||||
|
public override int GetHashCode() => System.HashCode.Combine(Center, Radius);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="Circle"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="Circle"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(Circle)}({Center}, {Radius})";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -94,7 +120,7 @@ public static class CircleExtensions
|
|||||||
public static Circle Displace(this Circle circle, Vector2D displaceVector) => Circle.Displace(circle, displaceVector);
|
public static Circle Displace(this Circle circle, Vector2D displaceVector) => Circle.Displace(circle, displaceVector);
|
||||||
|
|
||||||
/// <inheritdoc cref="Circle.Project(Circle, Vector2D)" />
|
/// <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)" />
|
/// <inheritdoc cref="Circle.Transform(ITransform2D, Circle)" />
|
||||||
public static Circle Transform(this ITransform2D transform, Circle circle) => Circle.Transform(transform, circle);
|
public static Circle Transform(this ITransform2D transform, Circle circle) => Circle.Transform(transform, circle);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an HSV color.
|
/// Represents an HSV color.
|
||||||
@@ -10,7 +12,7 @@ namespace Syntriax.Engine.Core;
|
|||||||
/// Initializes a new instance of the <see cref="ColorHSV"/> struct with the specified values.
|
/// Initializes a new instance of the <see cref="ColorHSV"/> struct with the specified values.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
|
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
|
||||||
public readonly struct ColorHSV(float hue, float saturation, float value)
|
public readonly struct ColorHSV(float hue, float saturation, float value) : IEquatable<ColorHSV>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Hue value of the <see cref="ColorHSV"/>.
|
/// The Hue value of the <see cref="ColorHSV"/>.
|
||||||
@@ -27,49 +29,19 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float Value = value.Clamp(0f, 1f);
|
public readonly float Value = value.Clamp(0f, 1f);
|
||||||
|
|
||||||
public static ColorHSV operator -(ColorHSV color) => new(color.Hue.OneMinus().Clamp(0f, 1f), color.Saturation.OneMinus().Clamp(0f, 1f), color.Value.OneMinus().Clamp(0f, 1f));
|
public static ColorHSV operator -(ColorHSV color) => new(color.Hue.OneMinus(), color.Saturation.OneMinus(), color.Value.OneMinus());
|
||||||
public static ColorHSV operator +(ColorHSV left, ColorHSV right) => new((left.Hue + right.Hue).Clamp(0f, 1f), (left.Saturation + right.Saturation).Clamp(0f, 1f), (left.Value + right.Value).Clamp(0f, 1f));
|
public static ColorHSV operator +(ColorHSV left, ColorHSV right) => new(left.Hue + right.Hue, left.Saturation + right.Saturation, left.Value + right.Value);
|
||||||
public static ColorHSV operator -(ColorHSV left, ColorHSV right) => new((left.Hue - right.Hue).Clamp(0f, 1f), (left.Saturation - right.Saturation).Clamp(0f, 1f), (left.Value - right.Value).Clamp(0f, 1f));
|
public static ColorHSV operator -(ColorHSV left, ColorHSV right) => new(left.Hue - right.Hue, left.Saturation - right.Saturation, left.Value - right.Value);
|
||||||
public static ColorHSV operator *(ColorHSV left, ColorHSV right) => new((left.Hue * right.Hue).Clamp(0f, 1f), (left.Saturation * right.Saturation).Clamp(0f, 1f), (left.Value * right.Value).Clamp(0f, 1f));
|
public static ColorHSV operator *(ColorHSV left, ColorHSV right) => new(left.Hue * right.Hue, left.Saturation * right.Saturation, left.Value * right.Value);
|
||||||
public static ColorHSV operator *(ColorHSV color, float value) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f));
|
public static ColorHSV operator *(ColorHSV color, float value) => new(color.Hue * value, color.Saturation * value, color.Value * value);
|
||||||
public static ColorHSV operator *(float value, ColorHSV color) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f));
|
public static ColorHSV operator *(float value, ColorHSV color) => new(color.Hue * value, color.Saturation * value, color.Value * value);
|
||||||
public static ColorHSV operator /(ColorHSV color, float value) => new((color.Hue / value).Clamp(0f, 1f), (color.Saturation / value).Clamp(0f, 1f), (color.Value / value).Clamp(0f, 1f));
|
public static ColorHSV operator /(ColorHSV color, float value) => new(color.Hue / value, color.Saturation / value, color.Value / value);
|
||||||
public static bool operator ==(ColorHSV left, ColorHSV right) => left.Hue.ApproximatelyEquals(right.Hue) && left.Saturation.ApproximatelyEquals(right.Saturation) && left.Value.ApproximatelyEquals(right.Value);
|
public static bool operator ==(ColorHSV left, ColorHSV right) => left.Hue == right.Hue && left.Saturation == right.Saturation && left.Value == right.Value;
|
||||||
public static bool operator !=(ColorHSV left, ColorHSV right) => !left.Hue.ApproximatelyEquals(right.Hue) || !left.Saturation.ApproximatelyEquals(right.Saturation) || !left.Value.ApproximatelyEquals(right.Value);
|
public static bool operator !=(ColorHSV left, ColorHSV right) => left.Hue != right.Hue || left.Saturation != right.Saturation || left.Value != right.Value;
|
||||||
|
|
||||||
public static implicit operator ColorHSV(ColorRGBA rgba) => (ColorRGB)rgba;
|
public static implicit operator ColorHSV(ColorHSVA hsva) => new(hsva.Hue, hsva.Saturation, hsva.Value);
|
||||||
public static implicit operator ColorHSV(ColorRGB rgb)
|
public static implicit operator ColorHSV(ColorRGBA rgba) => (ColorHSVA)rgba;
|
||||||
{
|
public static implicit operator ColorHSV(ColorRGB rgb) => (ColorHSVA)rgb;
|
||||||
float hue;
|
|
||||||
float saturation;
|
|
||||||
float value;
|
|
||||||
|
|
||||||
float rd = rgb.R / 255f;
|
|
||||||
float gd = rgb.G / 255f;
|
|
||||||
float bd = rgb.B / 255f;
|
|
||||||
|
|
||||||
float max = Math.Max(rd, Math.Max(gd, bd));
|
|
||||||
float min = Math.Min(rd, Math.Min(gd, bd));
|
|
||||||
float delta = max - min;
|
|
||||||
|
|
||||||
if (delta.ApproximatelyEquals(0))
|
|
||||||
hue = 0f;
|
|
||||||
else if (max.ApproximatelyEquals(rd))
|
|
||||||
hue = 60f * ((gd - bd) / delta % 6f);
|
|
||||||
else if (max.ApproximatelyEquals(gd))
|
|
||||||
hue = 60f * (((bd - rd) / delta) + 2f);
|
|
||||||
else
|
|
||||||
hue = 60f * (((rd - gd) / delta) + 4f);
|
|
||||||
|
|
||||||
if (hue < 0f)
|
|
||||||
hue += 360f;
|
|
||||||
|
|
||||||
hue /= 360f;
|
|
||||||
saturation = max.ApproximatelyEquals(0f) ? 0f : delta / max;
|
|
||||||
value = max;
|
|
||||||
|
|
||||||
return new(hue, saturation, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inverts the given <see cref="ColorHSV"/>.
|
/// Inverts the given <see cref="ColorHSV"/>.
|
||||||
@@ -110,14 +82,6 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
|
|||||||
/// <returns>The result of dividing the <see cref="ColorHSV"/> by the scalar value.</returns>
|
/// <returns>The result of dividing the <see cref="ColorHSV"/> by the scalar value.</returns>
|
||||||
public static ColorHSV Divide(ColorHSV color, float value) => color / value;
|
public static ColorHSV Divide(ColorHSV color, float value) => color / value;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the <see cref="ColorHSV"/> from one point to another.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="from">The starting point.</param>
|
|
||||||
/// <param name="to">The ending point.</param>
|
|
||||||
/// <returns>The <see cref="ColorHSV"/> from the starting point to the ending point.</returns>
|
|
||||||
public static ColorHSV FromTo(ColorHSV from, ColorHSV to) => to - from;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs linear interpolation between two <see cref="ColorHSV"/>s.
|
/// Performs linear interpolation between two <see cref="ColorHSV"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -125,13 +89,14 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
|
|||||||
/// <param name="to">The ending <see cref="ColorHSV"/> (t = 1).</param>
|
/// <param name="to">The ending <see cref="ColorHSV"/> (t = 1).</param>
|
||||||
/// <param name="t">The interpolation parameter.</param>
|
/// <param name="t">The interpolation parameter.</param>
|
||||||
/// <returns>The interpolated <see cref="ColorHSV"/>.</returns>
|
/// <returns>The interpolated <see cref="ColorHSV"/>.</returns>
|
||||||
public static ColorHSV Lerp(ColorHSV from, ColorHSV to, float t) => from + FromTo(from, to) * t;
|
public static ColorHSV Lerp(ColorHSV from, ColorHSV to, float t)
|
||||||
|
{
|
||||||
|
float hueDiff = to.Hue - from.Hue;
|
||||||
|
float saturationDiff = to.Saturation - from.Saturation;
|
||||||
|
float valueDiff = to.Value - from.Value;
|
||||||
|
|
||||||
/// <summary>
|
return from + new ColorHSV(hueDiff * t, saturationDiff * t, valueDiff * t);
|
||||||
/// Converts the <see cref="ColorHSV"/> to its string representation.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string representation of the <see cref="ColorHSV"/>.</returns>
|
|
||||||
public override string ToString() => $"{nameof(ColorHSV)}({Hue}, {Saturation}, {Value})";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if two <see cref="ColorHSV"/>s are approximately equal within a specified epsilon range.
|
/// Checks if two <see cref="ColorHSV"/>s are approximately equal within a specified epsilon range.
|
||||||
@@ -148,13 +113,20 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The object to compare with the current <see cref="ColorHSV"/>.</param>
|
/// <param name="obj">The object to compare with the current <see cref="ColorHSV"/>.</param>
|
||||||
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorHSV"/>; otherwise, <see cref="false"/>.</returns>
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorHSV"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
public override bool Equals(object? obj) => obj is ColorHSV objVec && Hue.Equals(objVec.Hue) && Saturation.Equals(objVec.Saturation) && Value.Equals(objVec.Value);
|
public override bool Equals(object? obj) => obj is ColorHSV colorHSV && this == colorHSV;
|
||||||
|
public bool Equals(ColorHSV other) => this == other;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a hash code for the <see cref="ColorHSV"/>.
|
/// Generates a hash code for the <see cref="ColorHSV"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A hash code for the <see cref="ColorHSV"/>.</returns>
|
/// <returns>A hash code for the <see cref="ColorHSV"/>.</returns>
|
||||||
public override int GetHashCode() => System.HashCode.Combine(Hue, Saturation, Value);
|
public override int GetHashCode() => System.HashCode.Combine(Hue, Saturation, Value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="ColorHSV"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="ColorHSV"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(ColorHSV)}({Hue}, {Saturation}, {Value})";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -174,9 +146,6 @@ public static class ColorHSVExtensions
|
|||||||
/// <inheritdoc cref="ColorHSV.Divide(ColorHSV, ColorHSV)" />
|
/// <inheritdoc cref="ColorHSV.Divide(ColorHSV, ColorHSV)" />
|
||||||
public static ColorHSV Divide(this ColorHSV color, float value) => ColorHSV.Divide(color, value);
|
public static ColorHSV Divide(this ColorHSV color, float value) => ColorHSV.Divide(color, value);
|
||||||
|
|
||||||
/// <inheritdoc cref="ColorHSV.FromTo(ColorHSV, ColorHSV)" />
|
|
||||||
public static ColorHSV FromTo(this ColorHSV from, ColorHSV to) => ColorHSV.FromTo(from, to);
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ColorHSV.Lerp(ColorHSV, ColorHSV, float)" />
|
/// <inheritdoc cref="ColorHSV.Lerp(ColorHSV, ColorHSV, float)" />
|
||||||
public static ColorHSV Lerp(this ColorHSV from, ColorHSV to, float t) => ColorHSV.Lerp(from, to, t);
|
public static ColorHSV Lerp(this ColorHSV from, ColorHSV to, float t) => ColorHSV.Lerp(from, to, t);
|
||||||
|
|
||||||
|
|||||||
192
Engine.Core/Primitives/ColorHSVA.cs
Normal file
192
Engine.Core/Primitives/ColorHSVA.cs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an HSV color.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hue">Hue of the <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <param name="saturation">Saturation of the <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <param name="value">Value of the <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <param name="alpha">Alpha of the <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// Initializes a new instance of the <see cref="ColorHSVA"/> struct with the specified values.
|
||||||
|
/// </remarks>
|
||||||
|
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
|
||||||
|
public readonly struct ColorHSVA(float hue, float saturation, float value, float alpha = 1) : IEquatable<ColorHSVA>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Hue value of the <see cref="ColorHSVA"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float Hue = hue.Clamp(0f, 1f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Saturation value of the <see cref="ColorHSVA"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float Saturation = saturation.Clamp(0f, 1f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Value value of the <see cref="ColorHSVA"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float Value = value.Clamp(0f, 1f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Alpha value of the <see cref="ColorHSVA"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly float Alpha = alpha;
|
||||||
|
|
||||||
|
public static ColorHSVA operator -(ColorHSVA color) => new(color.Hue.OneMinus(), color.Saturation.OneMinus(), color.Value.OneMinus(), color.Alpha);
|
||||||
|
public static ColorHSVA operator +(ColorHSVA left, ColorHSVA right) => new(left.Hue + right.Hue, left.Saturation + right.Saturation, left.Value + right.Value, left.Alpha + right.Alpha);
|
||||||
|
public static ColorHSVA operator -(ColorHSVA left, ColorHSVA right) => new(left.Hue - right.Hue, left.Saturation - right.Saturation, left.Value - right.Value, left.Alpha - right.Alpha);
|
||||||
|
public static ColorHSVA operator *(ColorHSVA left, ColorHSVA right) => new(left.Hue * right.Hue, left.Saturation * right.Saturation, left.Value * right.Value, left.Alpha * right.Alpha);
|
||||||
|
public static ColorHSVA operator *(ColorHSVA color, float value) => new(color.Hue * value, color.Saturation * value, color.Value * value, color.Alpha * value);
|
||||||
|
public static ColorHSVA operator *(float value, ColorHSVA color) => new(color.Hue * value, color.Saturation * value, color.Value * value, color.Alpha * value);
|
||||||
|
public static ColorHSVA operator /(ColorHSVA color, float value) => new(color.Hue / value, color.Saturation / value, color.Value / value, color.Alpha / value);
|
||||||
|
public static bool operator ==(ColorHSVA left, ColorHSVA right) => left.Hue == right.Hue && left.Saturation == right.Saturation && left.Value == right.Value;
|
||||||
|
public static bool operator !=(ColorHSVA left, ColorHSVA right) => left.Hue != right.Hue || left.Saturation != right.Saturation || left.Value != right.Value;
|
||||||
|
|
||||||
|
public static implicit operator ColorHSVA(ColorHSV hsv) => new(hsv.Hue, hsv.Saturation, hsv.Value, 1f);
|
||||||
|
public static implicit operator ColorHSVA(ColorRGB rgb) => (ColorRGBA)rgb;
|
||||||
|
public static implicit operator ColorHSVA(ColorRGBA rgba)
|
||||||
|
{
|
||||||
|
float hue;
|
||||||
|
float saturation;
|
||||||
|
float value;
|
||||||
|
|
||||||
|
float rd = rgba.R / 255f;
|
||||||
|
float gd = rgba.G / 255f;
|
||||||
|
float bd = rgba.B / 255f;
|
||||||
|
|
||||||
|
float max = Math.Max(rd, Math.Max(gd, bd));
|
||||||
|
float min = Math.Min(rd, Math.Min(gd, bd));
|
||||||
|
float delta = max - min;
|
||||||
|
|
||||||
|
if (delta.ApproximatelyEquals(0))
|
||||||
|
hue = 0f;
|
||||||
|
else if (max.ApproximatelyEquals(rd))
|
||||||
|
hue = 60f * ((gd - bd) / delta % 6f);
|
||||||
|
else if (max.ApproximatelyEquals(gd))
|
||||||
|
hue = 60f * (((bd - rd) / delta) + 2f);
|
||||||
|
else
|
||||||
|
hue = 60f * (((rd - gd) / delta) + 4f);
|
||||||
|
|
||||||
|
if (hue < 0f)
|
||||||
|
hue += 360f;
|
||||||
|
|
||||||
|
hue /= 360f;
|
||||||
|
saturation = max.ApproximatelyEquals(0f) ? 0f : delta / max;
|
||||||
|
value = max;
|
||||||
|
|
||||||
|
return new(hue, saturation, value, rgba.A / 255f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inverts the given <see cref="ColorHSVA"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="color">The <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <returns>The inverted <see cref="ColorHSVA"/>.</returns>
|
||||||
|
public static ColorHSVA Invert(ColorHSVA color) => -color;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds two <see cref="ColorHSVA"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <param name="right">The second <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <returns>The sum of the two <see cref="ColorHSVA"/>s.</returns>
|
||||||
|
public static ColorHSVA Add(ColorHSVA left, ColorHSVA right) => left + right;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subtracts one <see cref="ColorHSVA"/> from another.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The <see cref="ColorHSVA"/> to subtract from.</param>
|
||||||
|
/// <param name="right">The <see cref="ColorHSVA"/> to subtract.</param>
|
||||||
|
/// <returns>The result of subtracting the second <see cref="ColorHSVA"/> from the first.</returns>
|
||||||
|
public static ColorHSVA Subtract(ColorHSVA left, ColorHSVA right) => left - right;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplies a <see cref="ColorHSVA"/> by a scalar value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="color">The <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <param name="value">The scalar value.</param>
|
||||||
|
/// <returns>The result of multiplying the <see cref="ColorHSVA"/> by the scalar value.</returns>
|
||||||
|
public static ColorHSVA Multiply(ColorHSVA color, float value) => color * value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Divides a <see cref="ColorHSVA"/> by a scalar value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="color">The <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <param name="value">The scalar value.</param>
|
||||||
|
/// <returns>The result of dividing the <see cref="ColorHSVA"/> by the scalar value.</returns>
|
||||||
|
public static ColorHSVA Divide(ColorHSVA color, float value) => color / value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs linear interpolation between two <see cref="ColorHSVA"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="from">The starting <see cref="ColorHSVA"/> (t = 0).</param>
|
||||||
|
/// <param name="to">The ending <see cref="ColorHSVA"/> (t = 1).</param>
|
||||||
|
/// <param name="t">The interpolation parameter.</param>
|
||||||
|
/// <returns>The interpolated <see cref="ColorHSVA"/>.</returns>
|
||||||
|
public static ColorHSVA Lerp(ColorHSVA from, ColorHSVA to, float t)
|
||||||
|
{
|
||||||
|
float hueDiff = to.Hue - from.Hue;
|
||||||
|
float saturationDiff = to.Saturation - from.Saturation;
|
||||||
|
float valueDiff = to.Value - from.Value;
|
||||||
|
float alphaDiff = to.Alpha - from.Alpha;
|
||||||
|
|
||||||
|
return from + new ColorHSVA(hueDiff * t, saturationDiff * t, valueDiff * t, alphaDiff * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two <see cref="ColorHSVA"/>s are approximately equal within a specified epsilon range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <param name="right">The second <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <param name="epsilon">The epsilon range.</param>
|
||||||
|
/// <returns><see cref="true"/> if the <see cref="ColorHSVA"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public static bool ApproximatelyEquals(ColorHSVA left, ColorHSVA right, float epsilon = float.Epsilon)
|
||||||
|
=> left.Hue.ApproximatelyEquals(right.Hue, epsilon) && left.Saturation.ApproximatelyEquals(right.Saturation, epsilon) && left.Value.ApproximatelyEquals(right.Value, epsilon);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified object is equal to the current <see cref="ColorHSVA"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to compare with the current <see cref="ColorHSVA"/>.</param>
|
||||||
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorHSVA"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public override bool Equals(object? obj) => obj is ColorHSVA colorHSVA && this == colorHSVA;
|
||||||
|
public bool Equals(ColorHSVA other) => this == other;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a hash code for the <see cref="ColorHSVA"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A hash code for the <see cref="ColorHSVA"/>.</returns>
|
||||||
|
public override int GetHashCode() => System.HashCode.Combine(Hue, Saturation, Value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="ColorHSVA"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="ColorHSVA"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(ColorHSVA)}({Hue}, {Saturation}, {Value})";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for <see cref="ColorHSVA"/> type.
|
||||||
|
/// </summary>
|
||||||
|
public static class ColorHSVAExtensions
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="ColorHSVA.Add(ColorHSVA, ColorHSVA)" />
|
||||||
|
public static ColorHSVA Add(this ColorHSVA color, ColorHSVA value) => ColorHSVA.Add(color, value);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ColorHSVA.Subtract(ColorHSVA, ColorHSVA)" />
|
||||||
|
public static ColorHSVA Subtract(this ColorHSVA color, ColorHSVA value) => ColorHSVA.Subtract(color, value);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ColorHSVA.Multiply(ColorHSVA, ColorHSVA)" />
|
||||||
|
public static ColorHSVA Multiply(this ColorHSVA color, float value) => ColorHSVA.Multiply(color, value);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ColorHSVA.Divide(ColorHSVA, ColorHSVA)" />
|
||||||
|
public static ColorHSVA Divide(this ColorHSVA color, float value) => ColorHSVA.Divide(color, value);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ColorHSVA.Lerp(ColorHSVA, ColorHSVA, float)" />
|
||||||
|
public static ColorHSVA Lerp(this ColorHSVA from, ColorHSVA to, float t) => ColorHSVA.Lerp(from, to, t);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ColorHSVA.ApproximatelyEquals(ColorHSVA, ColorHSVA, float) " />
|
||||||
|
public static bool ApproximatelyEquals(this ColorHSVA left, ColorHSVA right, float epsilon = float.Epsilon) => ColorHSVA.ApproximatelyEquals(left, right, epsilon);
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an RGB color.
|
/// Represents an RGB color.
|
||||||
@@ -10,7 +12,7 @@ namespace Syntriax.Engine.Core;
|
|||||||
/// Initializes a new instance of the <see cref="ColorRGB"/> struct with the specified values.
|
/// Initializes a new instance of the <see cref="ColorRGB"/> struct with the specified values.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
|
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
|
||||||
public readonly struct ColorRGB(byte r, byte g, byte b)
|
public readonly struct ColorRGB(byte r, byte g, byte b) : IEquatable<ColorRGB>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Red value of the <see cref="ColorRGB"/>.
|
/// The Red value of the <see cref="ColorRGB"/>.
|
||||||
@@ -38,30 +40,8 @@ public readonly struct ColorRGB(byte r, byte g, byte b)
|
|||||||
public static bool operator !=(ColorRGB left, ColorRGB right) => left.R != right.R || left.G != right.G || left.B != right.B;
|
public static bool operator !=(ColorRGB left, ColorRGB right) => left.R != right.R || left.G != right.G || left.B != right.B;
|
||||||
|
|
||||||
public static implicit operator ColorRGB(ColorRGBA rgba) => new(rgba.R, rgba.G, rgba.B);
|
public static implicit operator ColorRGB(ColorRGBA rgba) => new(rgba.R, rgba.G, rgba.B);
|
||||||
public static implicit operator ColorRGB(ColorHSV hsv)
|
public static implicit operator ColorRGB(ColorHSVA hsva) => (ColorRGBA)hsva;
|
||||||
{
|
public static implicit operator ColorRGB(ColorHSV hsv) => (ColorRGBA)hsv;
|
||||||
float hue = hsv.Hue * 360f;
|
|
||||||
float chroma = hsv.Value * hsv.Saturation;
|
|
||||||
float x = chroma * (1f - Math.Abs(hue / 60f % 2f - 1f));
|
|
||||||
float m = hsv.Value - chroma;
|
|
||||||
|
|
||||||
float r1 = 0f;
|
|
||||||
float g1 = 0f;
|
|
||||||
float b1 = 0f;
|
|
||||||
|
|
||||||
if (hue < 60) { r1 = chroma; g1 = x; b1 = 0; }
|
|
||||||
else if (hue < 120) { r1 = x; g1 = chroma; b1 = 0; }
|
|
||||||
else if (hue < 180) { r1 = 0; g1 = chroma; b1 = x; }
|
|
||||||
else if (hue < 240) { r1 = 0; g1 = x; b1 = chroma; }
|
|
||||||
else if (hue < 300) { r1 = x; g1 = 0; b1 = chroma; }
|
|
||||||
else if (hue <= 360) { r1 = chroma; g1 = 0; b1 = x; }
|
|
||||||
|
|
||||||
byte r = (byte)Math.RoundToInt((r1 + m) * 255);
|
|
||||||
byte g = (byte)Math.RoundToInt((g1 + m) * 255);
|
|
||||||
byte b = (byte)Math.RoundToInt((b1 + m) * 255);
|
|
||||||
|
|
||||||
return new(r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inverts the given <see cref="ColorRGB"/>.
|
/// Inverts the given <see cref="ColorRGB"/>.
|
||||||
@@ -102,14 +82,6 @@ public readonly struct ColorRGB(byte r, byte g, byte b)
|
|||||||
/// <returns>The result of dividing the <see cref="ColorRGB"/> by the scalar value.</returns>
|
/// <returns>The result of dividing the <see cref="ColorRGB"/> by the scalar value.</returns>
|
||||||
public static ColorRGB Divide(ColorRGB color, float value) => color / value;
|
public static ColorRGB Divide(ColorRGB color, float value) => color / value;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the <see cref="ColorRGB"/> from one point to another.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="from">The starting point.</param>
|
|
||||||
/// <param name="to">The ending point.</param>
|
|
||||||
/// <returns>The <see cref="ColorRGB"/> from the starting point to the ending point.</returns>
|
|
||||||
public static ColorRGB FromTo(ColorRGB from, ColorRGB to) => to - from;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs linear interpolation between two <see cref="ColorRGB"/>s.
|
/// Performs linear interpolation between two <see cref="ColorRGB"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -117,26 +89,34 @@ public readonly struct ColorRGB(byte r, byte g, byte b)
|
|||||||
/// <param name="to">The ending <see cref="ColorRGB"/> (t = 1).</param>
|
/// <param name="to">The ending <see cref="ColorRGB"/> (t = 1).</param>
|
||||||
/// <param name="t">The interpolation parameter.</param>
|
/// <param name="t">The interpolation parameter.</param>
|
||||||
/// <returns>The interpolated <see cref="ColorRGB"/>.</returns>
|
/// <returns>The interpolated <see cref="ColorRGB"/>.</returns>
|
||||||
public static ColorRGB Lerp(ColorRGB from, ColorRGB to, float t) => from + FromTo(from, to) * t;
|
public static ColorRGB Lerp(ColorRGB from, ColorRGB to, float t)
|
||||||
|
{
|
||||||
|
int redDiff = to.R - from.R;
|
||||||
|
int greenDiff = to.G - from.G;
|
||||||
|
int blueDiff = to.B - from.B;
|
||||||
|
|
||||||
/// <summary>
|
return from + new ColorRGB((byte)(redDiff * t), (byte)(greenDiff * t), (byte)(blueDiff * t));
|
||||||
/// Converts the <see cref="ColorRGB"/> to its string representation.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string representation of the <see cref="ColorRGB"/>.</returns>
|
|
||||||
public override string ToString() => $"{nameof(ColorRGB)}({R}, {G}, {B})";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the specified object is equal to the current <see cref="ColorRGB"/>.
|
/// Determines whether the specified object is equal to the current <see cref="ColorRGB"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The object to compare with the current <see cref="ColorRGB"/>.</param>
|
/// <param name="obj">The object to compare with the current <see cref="ColorRGB"/>.</param>
|
||||||
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGB"/>; otherwise, <see cref="false"/>.</returns>
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGB"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
public override bool Equals(object? obj) => obj is ColorRGB objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B);
|
public override bool Equals(object? obj) => obj is ColorRGB colorRGB && this == colorRGB;
|
||||||
|
public bool Equals(ColorRGB other) => this == other;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a hash code for the <see cref="ColorRGB"/>.
|
/// Generates a hash code for the <see cref="ColorRGB"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A hash code for the <see cref="ColorRGB"/>.</returns>
|
/// <returns>A hash code for the <see cref="ColorRGB"/>.</returns>
|
||||||
public override int GetHashCode() => System.HashCode.Combine(R, G, B);
|
public override int GetHashCode() => System.HashCode.Combine(R, G, B);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="ColorRGB"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="ColorRGB"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(ColorRGB)}({R}, {G}, {B})";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -156,9 +136,6 @@ public static class ColorRGBExtensions
|
|||||||
/// <inheritdoc cref="ColorRGB.Divide(ColorRGB, ColorRGB)" />
|
/// <inheritdoc cref="ColorRGB.Divide(ColorRGB, ColorRGB)" />
|
||||||
public static ColorRGB Divide(this ColorRGB color, float value) => ColorRGB.Divide(color, value);
|
public static ColorRGB Divide(this ColorRGB color, float value) => ColorRGB.Divide(color, value);
|
||||||
|
|
||||||
/// <inheritdoc cref="ColorRGB.FromTo(ColorRGB, ColorRGB)" />
|
|
||||||
public static ColorRGB FromTo(this ColorRGB from, ColorRGB to) => ColorRGB.FromTo(from, to);
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ColorRGB.Lerp(ColorRGB, ColorRGB, float)" />
|
/// <inheritdoc cref="ColorRGB.Lerp(ColorRGB, ColorRGB, float)" />
|
||||||
public static ColorRGB Lerp(this ColorRGB from, ColorRGB to, float t) => ColorRGB.Lerp(from, to, t);
|
public static ColorRGB Lerp(this ColorRGB from, ColorRGB to, float t) => ColorRGB.Lerp(from, to, t);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an RGBA color.
|
/// Represents an RGBA color.
|
||||||
@@ -11,7 +13,7 @@ namespace Syntriax.Engine.Core;
|
|||||||
/// Initializes a new instance of the <see cref="ColorRGBA"/> struct with the specified values.
|
/// Initializes a new instance of the <see cref="ColorRGBA"/> struct with the specified values.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
|
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
|
||||||
public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
|
public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255) : IEquatable<ColorRGBA>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Red value of the <see cref="ColorRGBA"/>.
|
/// The Red value of the <see cref="ColorRGBA"/>.
|
||||||
@@ -44,7 +46,31 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
|
|||||||
public static bool operator !=(ColorRGBA left, ColorRGBA right) => left.R != right.R || left.G != right.G || left.B != right.B || left.A != right.A;
|
public static bool operator !=(ColorRGBA left, ColorRGBA right) => left.R != right.R || left.G != right.G || left.B != right.B || left.A != right.A;
|
||||||
|
|
||||||
public static implicit operator ColorRGBA(ColorRGB rgb) => new(rgb.R, rgb.G, rgb.B, 255);
|
public static implicit operator ColorRGBA(ColorRGB rgb) => new(rgb.R, rgb.G, rgb.B, 255);
|
||||||
public static implicit operator ColorRGBA(ColorHSV hsv) => (ColorRGB)hsv;
|
public static implicit operator ColorRGBA(ColorHSV hsv) => (ColorHSVA)hsv;
|
||||||
|
public static implicit operator ColorRGBA(ColorHSVA hsva)
|
||||||
|
{
|
||||||
|
float hue = hsva.Hue * 360f;
|
||||||
|
float chroma = hsva.Value * hsva.Saturation;
|
||||||
|
float x = chroma * (1f - Math.Abs(hue / 60f % 2f - 1f));
|
||||||
|
float m = hsva.Value - chroma;
|
||||||
|
|
||||||
|
float r1 = 0f;
|
||||||
|
float g1 = 0f;
|
||||||
|
float b1 = 0f;
|
||||||
|
|
||||||
|
if (hue < 60) { r1 = chroma; g1 = x; b1 = 0; }
|
||||||
|
else if (hue < 120) { r1 = x; g1 = chroma; b1 = 0; }
|
||||||
|
else if (hue < 180) { r1 = 0; g1 = chroma; b1 = x; }
|
||||||
|
else if (hue < 240) { r1 = 0; g1 = x; b1 = chroma; }
|
||||||
|
else if (hue < 300) { r1 = x; g1 = 0; b1 = chroma; }
|
||||||
|
else if (hue <= 360) { r1 = chroma; g1 = 0; b1 = x; }
|
||||||
|
|
||||||
|
byte r = (byte)Math.RoundToInt((r1 + m) * 255);
|
||||||
|
byte g = (byte)Math.RoundToInt((g1 + m) * 255);
|
||||||
|
byte b = (byte)Math.RoundToInt((b1 + m) * 255);
|
||||||
|
|
||||||
|
return new(r, g, b, (byte)(hsva.Alpha * 255));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inverts the given <see cref="ColorRGBA"/>.
|
/// Inverts the given <see cref="ColorRGBA"/>.
|
||||||
@@ -85,14 +111,6 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
|
|||||||
/// <returns>The result of dividing the <see cref="ColorRGBA"/> by the scalar value.</returns>
|
/// <returns>The result of dividing the <see cref="ColorRGBA"/> by the scalar value.</returns>
|
||||||
public static ColorRGBA Divide(ColorRGBA color, float value) => color / value;
|
public static ColorRGBA Divide(ColorRGBA color, float value) => color / value;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the <see cref="ColorRGBA"/> from one point to another.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="from">The starting point.</param>
|
|
||||||
/// <param name="to">The ending point.</param>
|
|
||||||
/// <returns>The <see cref="ColorRGBA"/> from the starting point to the ending point.</returns>
|
|
||||||
public static ColorRGBA FromTo(ColorRGBA from, ColorRGBA to) => to - from;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs linear interpolation between two <see cref="ColorRGBA"/>s.
|
/// Performs linear interpolation between two <see cref="ColorRGBA"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -100,26 +118,35 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
|
|||||||
/// <param name="to">The ending <see cref="ColorRGBA"/> (t = 1).</param>
|
/// <param name="to">The ending <see cref="ColorRGBA"/> (t = 1).</param>
|
||||||
/// <param name="t">The interpolation parameter.</param>
|
/// <param name="t">The interpolation parameter.</param>
|
||||||
/// <returns>The interpolated <see cref="ColorRGBA"/>.</returns>
|
/// <returns>The interpolated <see cref="ColorRGBA"/>.</returns>
|
||||||
public static ColorRGBA Lerp(ColorRGBA from, ColorRGBA to, float t) => from + FromTo(from, to) * t;
|
public static ColorRGBA Lerp(ColorRGBA from, ColorRGBA to, float t)
|
||||||
|
{
|
||||||
|
int redDiff = to.R - from.R;
|
||||||
|
int greenDiff = to.G - from.G;
|
||||||
|
int blueDiff = to.B - from.B;
|
||||||
|
int alphaDiff = to.A - from.A;
|
||||||
|
|
||||||
/// <summary>
|
return from + new ColorRGBA((byte)(redDiff * t), (byte)(greenDiff * t), (byte)(blueDiff * t), (byte)(alphaDiff * t));
|
||||||
/// Converts the <see cref="ColorRGBA"/> to its string representation.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string representation of the <see cref="ColorRGBA"/>.</returns>
|
|
||||||
public override string ToString() => $"{nameof(ColorRGBA)}({R}, {G}, {B}, {A})";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the specified object is equal to the current <see cref="ColorRGBA"/>.
|
/// Determines whether the specified object is equal to the current <see cref="ColorRGBA"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The object to compare with the current <see cref="ColorRGBA"/>.</param>
|
/// <param name="obj">The object to compare with the current <see cref="ColorRGBA"/>.</param>
|
||||||
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGBA"/>; otherwise, <see cref="false"/>.</returns>
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGBA"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
public override bool Equals(object? obj) => obj is ColorRGBA objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B) && A.Equals(objVec.A);
|
public override bool Equals(object? obj) => obj is ColorRGBA colorRGBA && this == colorRGBA;
|
||||||
|
public bool Equals(ColorRGBA other) => this == other;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a hash code for the <see cref="ColorRGBA"/>.
|
/// Generates a hash code for the <see cref="ColorRGBA"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A hash code for the <see cref="ColorRGBA"/>.</returns>
|
/// <returns>A hash code for the <see cref="ColorRGBA"/>.</returns>
|
||||||
public override int GetHashCode() => System.HashCode.Combine(R, G, B, A);
|
public override int GetHashCode() => System.HashCode.Combine(R, G, B, A);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="ColorRGBA"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="ColorRGBA"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(ColorRGBA)}({R}, {G}, {B}, {A})";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -139,9 +166,6 @@ public static class ColorRGBAExtensions
|
|||||||
/// <inheritdoc cref="ColorRGBA.Divide(ColorRGBA, ColorRGBA)" />
|
/// <inheritdoc cref="ColorRGBA.Divide(ColorRGBA, ColorRGBA)" />
|
||||||
public static ColorRGBA Divide(this ColorRGBA color, float value) => ColorRGBA.Divide(color, value);
|
public static ColorRGBA Divide(this ColorRGBA color, float value) => ColorRGBA.Divide(color, value);
|
||||||
|
|
||||||
/// <inheritdoc cref="ColorRGBA.FromTo(ColorRGBA, ColorRGBA)" />
|
|
||||||
public static ColorRGBA FromTo(this ColorRGBA from, ColorRGBA to) => ColorRGBA.FromTo(from, to);
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ColorRGBA.Lerp(ColorRGBA, ColorRGBA, float)" />
|
/// <inheritdoc cref="ColorRGBA.Lerp(ColorRGBA, ColorRGBA, float)" />
|
||||||
public static ColorRGBA Lerp(this ColorRGBA from, ColorRGBA to, float t) => ColorRGBA.Lerp(from, to, t);
|
public static ColorRGBA Lerp(this ColorRGBA from, ColorRGBA to, float t) => ColorRGBA.Lerp(from, to, t);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Core;
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a 2D line segment defined by two endpoints.
|
/// Represents a 2D line segment defined by two endpoints.
|
||||||
@@ -11,7 +12,7 @@ namespace Syntriax.Engine.Core;
|
|||||||
/// Initializes a new instance of the <see cref="Line2D"/> struct with the specified endpoints.
|
/// Initializes a new instance of the <see cref="Line2D"/> struct with the specified endpoints.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[System.Diagnostics.DebuggerDisplay("From: {From.ToString(),nq}, To: {To.ToString(),nq}, Direction: {Direction.ToString(),nq}, Length: {Length}")]
|
[System.Diagnostics.DebuggerDisplay("From: {From.ToString(),nq}, To: {To.ToString(),nq}, Direction: {Direction.ToString(),nq}, Length: {Length}")]
|
||||||
public readonly struct Line2D(Vector2D from, Vector2D to)
|
public readonly struct Line2D(Vector2D from, Vector2D to) : IEquatable<Line2D>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The starting point of the <see cref="Line2D"/> segment.
|
/// The starting point of the <see cref="Line2D"/> segment.
|
||||||
@@ -43,18 +44,13 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float LengthSquared => From.FromTo(To).LengthSquared();
|
public readonly float LengthSquared => From.FromTo(To).LengthSquared();
|
||||||
|
|
||||||
|
public static bool operator ==(Line2D left, Line2D right) => left.From == right.From && left.To == right.To;
|
||||||
|
public static bool operator !=(Line2D left, Line2D right) => left.From != right.From || left.To != right.To;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment.
|
/// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Line2DEquation GetLineEquation(Line2D line)
|
public static Line2DEquation GetLineEquation(Line2D line) => 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>
|
/// <summary>
|
||||||
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line2D"/>.
|
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line2D"/>.
|
||||||
@@ -167,17 +163,14 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static Vector2D ClosestPointTo(Line2D line, Vector2D point)
|
public static Vector2D ClosestPointTo(Line2D line, Vector2D point)
|
||||||
{
|
{
|
||||||
Vector2D edgeVector = line.From.FromTo(line.To);
|
Vector2D lineRelativeVector = line.From.FromTo(line.To);
|
||||||
Vector2D pointVector = point - line.From;
|
|
||||||
|
|
||||||
float t = (pointVector.X * edgeVector.X + pointVector.Y * edgeVector.Y) / (edgeVector.X * edgeVector.X + edgeVector.Y * edgeVector.Y);
|
Vector2D lineDirection = lineRelativeVector.Normalized;
|
||||||
|
Vector2D pointVector = line.From.FromTo(point);
|
||||||
|
|
||||||
t = Math.Max(0, Math.Min(1, t));
|
float dot = lineDirection.Dot(pointVector).Clamp(0f, lineRelativeVector.Magnitude);
|
||||||
|
|
||||||
float closestX = line.From.X + t * edgeVector.X;
|
return lineDirection * dot;
|
||||||
float closestY = line.From.Y + t * edgeVector.Y;
|
|
||||||
|
|
||||||
return new Vector2D((float)closestX, (float)closestY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -189,6 +182,26 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
|
|||||||
/// <returns><see cref="true"/> if the <see cref="Line2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
|
/// <returns><see cref="true"/> if the <see cref="Line2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
|
||||||
public static bool ApproximatelyEquals(Line2D left, Line2D right, float epsilon = float.Epsilon)
|
public static bool ApproximatelyEquals(Line2D left, Line2D right, float epsilon = float.Epsilon)
|
||||||
=> left.From.ApproximatelyEquals(right.From, epsilon) && left.To.ApproximatelyEquals(right.To, 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="Line2D"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to compare with the current <see cref="Line2D"/>.</param>
|
||||||
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Line2D"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public override bool Equals(object? obj) => obj is Line2D line2D && this == line2D;
|
||||||
|
public bool Equals(Line2D other) => this == other;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a hash code for the <see cref="Line2D"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A hash code for the <see cref="Line2D"/>.</returns>
|
||||||
|
public override int GetHashCode() => System.HashCode.Combine(From, To);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="Line2D"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="Line2D"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(Line2D)}({From}, {To})";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -205,6 +218,9 @@ public static class Line2DExtensions
|
|||||||
/// <inheritdoc cref="Line2D.Intersects(Line2D, Vector2D)" />
|
/// <inheritdoc cref="Line2D.Intersects(Line2D, Vector2D)" />
|
||||||
public static bool Intersects(this Line2D line, Vector2D point) => Line2D.Intersects(line, point);
|
public static bool Intersects(this Line2D line, Vector2D point) => Line2D.Intersects(line, point);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Line2D.IntersectionPoint(Line2D, Line2D)" />
|
||||||
|
public static Vector2D IntersectionPoint(this Line2D left, Line2D right) => Line2D.IntersectionPoint(left, right);
|
||||||
|
|
||||||
/// <inheritdoc cref="Line2D.GetT(Line2D, Vector2D)" />
|
/// <inheritdoc cref="Line2D.GetT(Line2D, Vector2D)" />
|
||||||
public static float GetT(this Line2D line, Vector2D point) => Line2D.GetT(line, point);
|
public static float GetT(this Line2D line, Vector2D point) => Line2D.GetT(line, point);
|
||||||
|
|
||||||
@@ -214,6 +230,9 @@ public static class Line2DExtensions
|
|||||||
/// <inheritdoc cref="Line2D.Intersects(Line2D, Line2D, out Vector2D?)" />
|
/// <inheritdoc cref="Line2D.Intersects(Line2D, Line2D, out Vector2D?)" />
|
||||||
public static bool Intersects(this Line2D left, Line2D right, [NotNullWhen(returnValue: true)] out Vector2D? point) => Line2D.Intersects(left, right, out point);
|
public static bool Intersects(this Line2D left, Line2D right, [NotNullWhen(returnValue: true)] out Vector2D? point) => Line2D.Intersects(left, right, out point);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Line2D.ClosestPointTo(Line2D, Vector2D)" />
|
||||||
|
public static Vector2D ClosestPointTo(this Line2D line, Vector2D point) => Line2D.ClosestPointTo(line, point);
|
||||||
|
|
||||||
/// <inheritdoc cref="Line2D.ApproximatelyEquals(Line2D, Line2D, float)" />
|
/// <inheritdoc cref="Line2D.ApproximatelyEquals(Line2D, Line2D, float)" />
|
||||||
public static bool ApproximatelyEquals(this Line2D left, Line2D right, float epsilon = float.Epsilon) => Line2D.ApproximatelyEquals(left, right, epsilon);
|
public static bool ApproximatelyEquals(this Line2D left, Line2D right, float epsilon = float.Epsilon) => Line2D.ApproximatelyEquals(left, right, epsilon);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,82 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a line equation in the form y = mx + b.
|
/// Represents a <see cref="Line2DEquation"/> in the form y = mx + b.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="slope">The slope of the line.</param>
|
/// <param name="slope">The slope of the line.</param>
|
||||||
/// <param name="offsetY">The y-intercept of the line.</param>
|
/// <param name="offsetY">The Y intercept of the line.</param>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Initializes a new instance of the <see cref="Line2DEquation"/> struct with the specified slope and y-intercept.
|
/// Initializes a new instance of the <see cref="Line2DEquation"/> struct with the specified slope and Y intercept.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
|
[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
|
||||||
public readonly struct Line2DEquation(float slope, float offsetY)
|
public readonly struct Line2DEquation(float slope, float offsetY) : IEquatable<Line2DEquation>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The slope of the line equation.
|
/// The slope of the <see cref="Line2DEquation"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float Slope = slope;
|
public readonly float Slope = slope;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The y-intercept of the line equation.
|
/// The Y intercept of the <see cref="Line2DEquation"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float OffsetY = offsetY;
|
public readonly float OffsetY = offsetY;
|
||||||
|
|
||||||
/// <summary>
|
public static bool operator ==(Line2DEquation left, Line2DEquation right) => left.Slope == right.Slope && left.OffsetY == right.OffsetY;
|
||||||
/// Resolves the y-coordinate for a given x-coordinate using the line equation.
|
public static bool operator !=(Line2DEquation left, Line2DEquation right) => left.Slope != right.Slope || left.OffsetY != right.OffsetY;
|
||||||
/// </summary>
|
|
||||||
/// <param name="lineEquation">The line equation to resolve.</param>
|
|
||||||
/// <param name="x">The x-coordinate for which to resolve the y-coordinate.</param>
|
|
||||||
/// <returns>The y-coordinate resolved using the line equation.</returns>
|
|
||||||
public static float Resolve(Line2DEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b
|
|
||||||
|
|
||||||
/// <summary>
|
public static implicit operator Line2DEquation(Line2D line)
|
||||||
/// Checks if two line equations are approximately equal.
|
{
|
||||||
/// </summary>
|
Vector2D slopeVector = line.From.FromTo(line.To);
|
||||||
/// <param name="left">The first line equation to compare.</param>
|
float slope = slopeVector.Y / slopeVector.X;
|
||||||
/// <param name="right">The second line equation to compare.</param>
|
|
||||||
/// <param name="epsilon">The epsilon range.</param>
|
float yOffset = line.From.Y - (slope * line.From.X);
|
||||||
/// <returns>True if the line equations are approximately equal; otherwise, false.</returns>
|
|
||||||
public static bool ApproximatelyEquals(Line2DEquation left, Line2DEquation right, float epsilon = float.Epsilon)
|
return new Line2DEquation(slope, yOffset);
|
||||||
=> left.Slope.ApproximatelyEquals(right.Slope, epsilon) && left.OffsetY.ApproximatelyEquals(right.OffsetY, epsilon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides extension methods for the LineEquation struct.
|
/// Resolves the Y coordinate for a given X coordinate using the <see cref="Line2DEquation"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lineEquation">The <see cref="Line2DEquation"/> to resolve.</param>
|
||||||
|
/// <param name="x">The X coordinate for which to resolve the Y coordinate.</param>
|
||||||
|
/// <returns>The Y coordinate resolved using the <see cref="Line2DEquation"/>.</returns>
|
||||||
|
public static float Resolve(Line2DEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two <see cref="Line2DEquation"/> are approximately equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first <see cref="Line2DEquation"/> to compare.</param>
|
||||||
|
/// <param name="right">The second <see cref="Line2DEquation"/> to compare.</param>
|
||||||
|
/// <param name="epsilon">The epsilon range.</param>
|
||||||
|
/// <returns>True if the <see cref="Line2DEquation"/> are approximately equal; otherwise, false.</returns>
|
||||||
|
public static bool ApproximatelyEquals(Line2DEquation left, Line2DEquation right, float epsilon = float.Epsilon)
|
||||||
|
=> left.Slope.ApproximatelyEquals(right.Slope, epsilon) && left.OffsetY.ApproximatelyEquals(right.OffsetY, epsilon);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified object is equal to the current <see cref="Line2DEquation"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to compare with the current <see cref="Line2DEquation"/>.</param>
|
||||||
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Line2DEquation"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public override bool Equals(object? obj) => obj is Line2DEquation lineEquation && this == lineEquation;
|
||||||
|
public bool Equals(Line2DEquation other) => this == other;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a hash code for the <see cref="Line2DEquation"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A hash code for the <see cref="Line2DEquation"/>.</returns>
|
||||||
|
public override int GetHashCode() => System.HashCode.Combine(Slope, OffsetY);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="Line2DEquation"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="Line2DEquation"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(Line2DEquation)}({Slope}, {OffsetY})";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for the <see cref="Line2DEquation"/> struct.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class Line2DEquationExtensions
|
public static class Line2DEquationExtensions
|
||||||
{
|
{
|
||||||
|
|||||||
114
Engine.Core/Primitives/Line3D.cs
Normal file
114
Engine.Core/Primitives/Line3D.cs
Normal 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);
|
||||||
|
}
|
||||||
294
Engine.Core/Primitives/Matrix4x4.cs
Normal file
294
Engine.Core/Primitives/Matrix4x4.cs
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
// TODO Comments
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a 4D left handed space matrix.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Initializes a new instance of the <see cref="Matrix4x4"/> struct with the specified values.
|
||||||
|
/// </remarks>
|
||||||
|
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
|
||||||
|
public readonly struct Matrix4x4(
|
||||||
|
float m11, float m12, float m13, float m14,
|
||||||
|
float m21, float m22, float m23, float m24,
|
||||||
|
float m31, float m32, float m33, float m34,
|
||||||
|
float m41, float m42, float m43, float m44
|
||||||
|
) : IEquatable<Matrix4x4>
|
||||||
|
{
|
||||||
|
public readonly float M11 = m11, M12 = m12, M13 = m13, M14 = m14;
|
||||||
|
public readonly float M21 = m21, M22 = m22, M23 = m23, M24 = m24;
|
||||||
|
public readonly float M31 = m31, M32 = m32, M33 = m33, M34 = m34;
|
||||||
|
public readonly float M41 = m41, M42 = m42, M43 = m43, M44 = m44;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the position (translation) from the <see cref="Matrix4x4"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Vector3D Position => new(M41, M42, M43);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the scale from the <see cref="Matrix4x4"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Vector3D Scale
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
float scaleX = new Vector3D(M11, M12, M13).Length();
|
||||||
|
float scaleY = new Vector3D(M21, M22, M23).Length();
|
||||||
|
float scaleZ = new Vector3D(M31, M32, M33).Length();
|
||||||
|
|
||||||
|
if (Determinant(this) < 0)
|
||||||
|
scaleX *= -1;
|
||||||
|
|
||||||
|
return new(scaleX, scaleY, scaleZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the rotation from the <see cref="Matrix4x4"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Quaternion Rotation => Quaternion.FromRotationMatrix4x4(this).Normalized;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the identity <see cref="Matrix4x4"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static Matrix4x4 Identity => new(
|
||||||
|
1f, 0f, 0f, 0f,
|
||||||
|
0f, 1f, 0f, 0f,
|
||||||
|
0f, 0f, 1f, 0f,
|
||||||
|
0f, 0f, 0f, 1f
|
||||||
|
);
|
||||||
|
|
||||||
|
public static Matrix4x4 operator *(Matrix4x4 a, Matrix4x4 b) => new(
|
||||||
|
a.M11 * b.M11 + a.M12 * b.M21 + a.M13 * b.M31 + a.M14 * b.M41,
|
||||||
|
a.M11 * b.M12 + a.M12 * b.M22 + a.M13 * b.M32 + a.M14 * b.M42,
|
||||||
|
a.M11 * b.M13 + a.M12 * b.M23 + a.M13 * b.M33 + a.M14 * b.M43,
|
||||||
|
a.M11 * b.M14 + a.M12 * b.M24 + a.M13 * b.M34 + a.M14 * b.M44,
|
||||||
|
|
||||||
|
a.M21 * b.M11 + a.M22 * b.M21 + a.M23 * b.M31 + a.M24 * b.M41,
|
||||||
|
a.M21 * b.M12 + a.M22 * b.M22 + a.M23 * b.M32 + a.M24 * b.M42,
|
||||||
|
a.M21 * b.M13 + a.M22 * b.M23 + a.M23 * b.M33 + a.M24 * b.M43,
|
||||||
|
a.M21 * b.M14 + a.M22 * b.M24 + a.M23 * b.M34 + a.M24 * b.M44,
|
||||||
|
|
||||||
|
a.M31 * b.M11 + a.M32 * b.M21 + a.M33 * b.M31 + a.M34 * b.M41,
|
||||||
|
a.M31 * b.M12 + a.M32 * b.M22 + a.M33 * b.M32 + a.M34 * b.M42,
|
||||||
|
a.M31 * b.M13 + a.M32 * b.M23 + a.M33 * b.M33 + a.M34 * b.M43,
|
||||||
|
a.M31 * b.M14 + a.M32 * b.M24 + a.M33 * b.M34 + a.M34 * b.M44,
|
||||||
|
|
||||||
|
a.M41 * b.M11 + a.M42 * b.M21 + a.M43 * b.M31 + a.M44 * b.M41,
|
||||||
|
a.M41 * b.M12 + a.M42 * b.M22 + a.M43 * b.M32 + a.M44 * b.M42,
|
||||||
|
a.M41 * b.M13 + a.M42 * b.M23 + a.M43 * b.M33 + a.M44 * b.M43,
|
||||||
|
a.M41 * b.M14 + a.M42 * b.M24 + a.M43 * b.M34 + a.M44 * b.M44
|
||||||
|
);
|
||||||
|
|
||||||
|
public static bool operator ==(Matrix4x4 left, Matrix4x4 right) =>
|
||||||
|
left.M11 == right.M11 && left.M12 == right.M12 && left.M13 == right.M13 && left.M14 == right.M14 &&
|
||||||
|
left.M21 == right.M21 && left.M22 == right.M22 && left.M23 == right.M23 && left.M24 == right.M24 &&
|
||||||
|
left.M31 == right.M31 && left.M32 == right.M32 && left.M33 == right.M33 && left.M34 == right.M34 &&
|
||||||
|
left.M41 == right.M41 && left.M42 == right.M42 && left.M43 == right.M43 && left.M44 == right.M44;
|
||||||
|
|
||||||
|
public static bool operator !=(Matrix4x4 left, Matrix4x4 right) =>
|
||||||
|
left.M11 != right.M11 || left.M12 != right.M12 || left.M13 != right.M13 || left.M14 != right.M14 ||
|
||||||
|
left.M21 != right.M21 || left.M22 != right.M22 || left.M23 != right.M23 || left.M24 != right.M24 ||
|
||||||
|
left.M31 != right.M31 || left.M32 != right.M32 || left.M33 != right.M33 || left.M34 != right.M34 ||
|
||||||
|
left.M41 != right.M41 || left.M42 != right.M42 || left.M43 != right.M43 || left.M44 != right.M44;
|
||||||
|
|
||||||
|
public static implicit operator System.Numerics.Matrix4x4(Matrix4x4 m) => new(
|
||||||
|
m.M11, m.M12, m.M13, m.M14,
|
||||||
|
m.M21, m.M22, m.M23, m.M24,
|
||||||
|
m.M31, m.M32, m.M33, m.M34,
|
||||||
|
m.M41, m.M42, m.M43, m.M44
|
||||||
|
);
|
||||||
|
|
||||||
|
public static implicit operator Matrix4x4(System.Numerics.Matrix4x4 m) => new(
|
||||||
|
m.M11, m.M12, m.M13, m.M14,
|
||||||
|
m.M21, m.M22, m.M23, m.M24,
|
||||||
|
m.M31, m.M32, m.M33, m.M34,
|
||||||
|
m.M41, m.M42, m.M43, m.M44
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the determinant of the <see cref="Matrix4x4"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="m">The <see cref="Matrix4x4"/>.</param>
|
||||||
|
/// <returns>The determinant of the <see cref="Matrix4x4"/>.</returns>
|
||||||
|
public static float Determinant(Matrix4x4 m) => // https://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fourD/index.htm
|
||||||
|
m.M14 * m.M23 * m.M32 * m.M41 - m.M13 * m.M24 * m.M32 * m.M41 -
|
||||||
|
m.M14 * m.M22 * m.M33 * m.M41 + m.M12 * m.M24 * m.M33 * m.M41 +
|
||||||
|
m.M13 * m.M22 * m.M34 * m.M41 - m.M12 * m.M23 * m.M34 * m.M41 -
|
||||||
|
m.M14 * m.M23 * m.M31 * m.M42 + m.M13 * m.M24 * m.M31 * m.M42 +
|
||||||
|
m.M14 * m.M21 * m.M33 * m.M42 - m.M11 * m.M24 * m.M33 * m.M42 -
|
||||||
|
m.M13 * m.M21 * m.M34 * m.M42 + m.M11 * m.M23 * m.M34 * m.M42 +
|
||||||
|
m.M14 * m.M22 * m.M31 * m.M43 - m.M12 * m.M24 * m.M31 * m.M43 -
|
||||||
|
m.M14 * m.M21 * m.M32 * m.M43 + m.M11 * m.M24 * m.M32 * m.M43 +
|
||||||
|
m.M12 * m.M21 * m.M34 * m.M43 - m.M11 * m.M22 * m.M34 * m.M43 -
|
||||||
|
m.M13 * m.M22 * m.M31 * m.M44 + m.M12 * m.M23 * m.M31 * m.M44 +
|
||||||
|
m.M13 * m.M21 * m.M32 * m.M44 - m.M11 * m.M23 * m.M32 * m.M44 -
|
||||||
|
m.M12 * m.M21 * m.M33 * m.M44 + m.M11 * m.M22 * m.M33 * m.M44;
|
||||||
|
|
||||||
|
public static Matrix4x4 CreateTranslation(Vector3D position) => new(
|
||||||
|
1f, 0f, 0f, 0f,
|
||||||
|
0f, 1f, 0f, 0f,
|
||||||
|
0f, 0f, 1f, 0f,
|
||||||
|
position.X, position.Y, position.Z, 1
|
||||||
|
);
|
||||||
|
|
||||||
|
public static Matrix4x4 CreateScale(Vector3D scale) => new(
|
||||||
|
scale.X, 0f, 0f, 0f,
|
||||||
|
0f, scale.Y, 0f, 0f,
|
||||||
|
0f, 0f, scale.Z, 0f,
|
||||||
|
0f, 0f, 0f, 1f
|
||||||
|
);
|
||||||
|
|
||||||
|
public static Matrix4x4 CreateRotationX(float radians)
|
||||||
|
{
|
||||||
|
float c = Math.Cos(radians);
|
||||||
|
float s = Math.Sin(radians);
|
||||||
|
|
||||||
|
return new Matrix4x4(
|
||||||
|
1f, 0f, 0f, 0f,
|
||||||
|
0f, c, s, 0f,
|
||||||
|
0f, -s, c, 0f,
|
||||||
|
0f, 0f, 0f, 1f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matrix4x4 CreateRotationY(float radians)
|
||||||
|
{
|
||||||
|
float c = Math.Cos(radians);
|
||||||
|
float s = Math.Sin(radians);
|
||||||
|
|
||||||
|
return new Matrix4x4(
|
||||||
|
c, 0f, -s, 0f,
|
||||||
|
0f, 1f, 0f, 0f,
|
||||||
|
s, 0f, c, 0f,
|
||||||
|
0f, 0f, 0f, 1f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matrix4x4 CreateRotationZ(float radians)
|
||||||
|
{
|
||||||
|
float c = Math.Cos(radians);
|
||||||
|
float s = Math.Sin(radians);
|
||||||
|
|
||||||
|
return new Matrix4x4(
|
||||||
|
c, s, 0f, 0f,
|
||||||
|
-s, c, 0f, 0f,
|
||||||
|
0f, 0f, 1f, 0f,
|
||||||
|
0f, 0f, 0f, 1f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Find a better calculation for this
|
||||||
|
public static Matrix4x4 CreateRotation(Quaternion quaternion)
|
||||||
|
{
|
||||||
|
Vector3D angles = quaternion.ToAngles();
|
||||||
|
return Identity * CreateRotationX(angles.X) * CreateRotationY(angles.Y) * CreateRotationZ(angles.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matrix4x4 CreateLookMatrix(Vector3D forward, Vector3D up)
|
||||||
|
{
|
||||||
|
Vector3D z = forward.Normalized;
|
||||||
|
Vector3D x = up.Cross(z).Normalized;
|
||||||
|
Vector3D y = z.Cross(x);
|
||||||
|
|
||||||
|
return new Matrix4x4(
|
||||||
|
x.X, y.X, z.X, 0f,
|
||||||
|
x.Y, y.Y, z.Y, 0f,
|
||||||
|
x.Z, y.Z, z.Z, 0f,
|
||||||
|
0f, 0f, 0f, 1f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matrix4x4 CreateLookMatrix(Vector3D position, Vector3D target, Vector3D up)
|
||||||
|
{
|
||||||
|
Vector3D z = position.FromTo(target).Normalized;
|
||||||
|
Vector3D x = up.Cross(z).Normalized;
|
||||||
|
Vector3D y = z.Cross(x);
|
||||||
|
|
||||||
|
return new Matrix4x4(
|
||||||
|
x.X, y.X, z.X, 0f,
|
||||||
|
x.Y, y.Y, z.Y, 0f,
|
||||||
|
x.Z, y.Z, z.Z, 0f,
|
||||||
|
-x.Dot(position), -y.Dot(position), -z.Dot(position), 1f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matrix4x4 CreatePerspectiveFieldOfView(float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane)
|
||||||
|
{
|
||||||
|
float yScale = 1f / Math.Tan(fieldOfViewInRadians / 2f);
|
||||||
|
float xScale = yScale / aspectRatio;
|
||||||
|
|
||||||
|
return new Matrix4x4(
|
||||||
|
xScale, 0f, 0f, 0f,
|
||||||
|
0f, yScale, 0f, 0f,
|
||||||
|
0f, 0f, farPlane / (farPlane - nearPlane), 1f,
|
||||||
|
0f, 0f, -nearPlane * farPlane / (farPlane - nearPlane), 0f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matrix4x4 ToRightHanded(Matrix4x4 m) => new(
|
||||||
|
m.M11, m.M12, m.M13, m.M14,
|
||||||
|
m.M31, m.M32, m.M33, m.M34,
|
||||||
|
m.M21, m.M22, m.M23, m.M24,
|
||||||
|
m.M41, m.M42, m.M43, m.M44
|
||||||
|
);
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) => obj is Matrix4x4 matrix && this == matrix;
|
||||||
|
public bool Equals(Matrix4x4 other) => this == other;
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
HashCode hashCode = new();
|
||||||
|
hashCode.Add(M11); hashCode.Add(M12); hashCode.Add(M13); hashCode.Add(M14);
|
||||||
|
hashCode.Add(M21); hashCode.Add(M22); hashCode.Add(M23); hashCode.Add(M24);
|
||||||
|
hashCode.Add(M31); hashCode.Add(M32); hashCode.Add(M33); hashCode.Add(M34);
|
||||||
|
hashCode.Add(M41); hashCode.Add(M42); hashCode.Add(M43); hashCode.Add(M44);
|
||||||
|
return hashCode.ToHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"Matrix4x4({M11}, {M12}, {M13}, {M14},{M21}, {M22}, {M23}, {M24},{M31}, {M32}, {M33}, {M34},{M41}, {M42}, {M43}, {M44})";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for <see cref="Matrix4x4"/> type.
|
||||||
|
/// </summary>
|
||||||
|
public static class Matrix4x4Extensions
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="Matrix4x4.Determinant(Matrix4x4)" />
|
||||||
|
public static float Determinant(this Matrix4x4 matrix) => Matrix4x4.Determinant(matrix);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreateTranslation(Vector3D)" />
|
||||||
|
public static Matrix4x4 ApplyTranslation(this Matrix4x4 matrix, Vector3D translation) => matrix * Matrix4x4.CreateTranslation(translation);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreateScale(Vector3D)" />
|
||||||
|
public static Matrix4x4 ApplyScale(this Matrix4x4 matrix, Vector3 scale) => matrix * Matrix4x4.CreateScale(scale);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreateRotationZ(float)" />
|
||||||
|
public static Matrix4x4 ApplyRotationX(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationX(radians);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreateRotationY(float)" />
|
||||||
|
public static Matrix4x4 ApplyRotationY(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationY(radians);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreateRotationZ(float)" />
|
||||||
|
public static Matrix4x4 ApplyRotationZ(this Matrix4x4 matrix, float radians) => matrix * Matrix4x4.CreateRotationZ(radians);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreateRotation(Quater)" />
|
||||||
|
public static Matrix4x4 ApplyRotation(this Matrix4x4 matrix, Quaternion quaternion) => matrix * Matrix4x4.CreateRotation(quaternion);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreateLookMatrix( Vector3D, Vector3D)" />
|
||||||
|
public static Matrix4x4 ApplyLookRotationTo(this Matrix4x4 matrix, Vector3D forward, Vector3 up) => matrix * Matrix4x4.CreateLookMatrix(forward, up);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreateLookMatrix(Vector3D, Vector3D, Vector3D)" />
|
||||||
|
public static Matrix4x4 CreateLookMatrixTo(this Vector3D from, Vector3D to, Vector3 up) => Matrix4x4.CreateLookMatrix(from, to, up);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.CreatePerspectiveFieldOfView(float, float, float, float)" />
|
||||||
|
public static Matrix4x4 ApplyPerspectiveFieldOfView(this Matrix4x4 matrix, float fieldOfViewInRadians, float aspectRatio, float nearPlane, float farPlane)
|
||||||
|
=> matrix * Matrix4x4.CreatePerspectiveFieldOfView(fieldOfViewInRadians, aspectRatio, nearPlane, farPlane);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Matrix4x4.ToRightHanded(Matrix4x4) />
|
||||||
|
public static Matrix4x4 ToRightHanded(this Matrix4x4 matrix) => Matrix4x4.ToRightHanded(matrix);
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a range of values along a single axis.
|
/// Represents a range of values along a single axis.
|
||||||
@@ -9,7 +11,7 @@ namespace Syntriax.Engine.Core;
|
|||||||
/// Initializes a new instance of the <see cref="Projection1D"/> struct with the specified minimum and maximum values.
|
/// Initializes a new instance of the <see cref="Projection1D"/> struct with the specified minimum and maximum values.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[System.Diagnostics.DebuggerDisplay("Min: {Min}, Max: {Max}")]
|
[System.Diagnostics.DebuggerDisplay("Min: {Min}, Max: {Max}")]
|
||||||
public readonly struct Projection1D(float min, float max)
|
public readonly struct Projection1D(float min, float max) : IEquatable<Projection1D>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the minimum value of the projection.
|
/// Gets the minimum value of the projection.
|
||||||
@@ -21,6 +23,9 @@ public readonly struct Projection1D(float min, float max)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly float Max = max;
|
public readonly float Max = max;
|
||||||
|
|
||||||
|
public static bool operator ==(Projection1D left, Projection1D right) => left.Min == right.Min && left.Max == right.Max;
|
||||||
|
public static bool operator !=(Projection1D left, Projection1D right) => left.Min != right.Min || left.Max != right.Max;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if two projections overlap.
|
/// Checks if two projections overlap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -70,6 +75,36 @@ public readonly struct Projection1D(float min, float max)
|
|||||||
depth = 0f;
|
depth = 0f;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two <see cref="Projection1D"/>s are approximately equal within a specified epsilon range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first <see cref="Projection1D"/>.</param>
|
||||||
|
/// <param name="right">The second <see cref="Projection1D"/>.</param>
|
||||||
|
/// <param name="epsilon">The epsilon range.</param>
|
||||||
|
/// <returns><see cref="true"/> if the <see cref="Projection1D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public static bool ApproximatelyEquals(Projection1D left, Projection1D right, float epsilon = float.Epsilon)
|
||||||
|
=> left.Min.ApproximatelyEquals(right.Min, epsilon) && left.Max.ApproximatelyEquals(right.Max, epsilon);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified object is equal to the current <see cref="Projection1D"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to compare with the current <see cref="Projection1D"/>.</param>
|
||||||
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Projection1D"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public override bool Equals(object? obj) => obj is Projection1D projection1D && this == projection1D;
|
||||||
|
public bool Equals(Projection1D other) => this == other;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a hash code for the <see cref="Projection1D"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A hash code for the <see cref="Projection1D"/>.</returns>
|
||||||
|
public override int GetHashCode() => System.HashCode.Combine(Min, Max);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="Projection1D"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="Projection1D"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(Projection1D)}({Min}, {Max})";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,4 +117,7 @@ public static class Projection1DExtensions
|
|||||||
|
|
||||||
/// <inheritdoc cref="Projection1D.Overlaps(Projection1D, Projection1D, out float)" />
|
/// <inheritdoc cref="Projection1D.Overlaps(Projection1D, Projection1D, out float)" />
|
||||||
public static bool Overlaps(this Projection1D left, Projection1D right, out float depth) => Projection1D.Overlaps(left, right, out depth);
|
public static bool Overlaps(this Projection1D left, Projection1D right, out float depth) => Projection1D.Overlaps(left, right, out depth);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Projection1D.ApproximatelyEquals(Projection1D, Projection1D, float)" />
|
||||||
|
public static bool ApproximatelyEquals(this Projection1D left, Projection1D right, float epsilon = float.Epsilon) => Projection1D.ApproximatelyEquals(left, right, epsilon);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Syntriax.Engine.Core;
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a 3D space rotation.
|
/// Represents a 3D space rotation.
|
||||||
@@ -11,7 +13,7 @@ namespace Syntriax.Engine.Core;
|
|||||||
/// Initializes a new instance of the <see cref="Quaternion"/> struct with the specified positions.
|
/// Initializes a new instance of the <see cref="Quaternion"/> struct with the specified positions.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized.ToString(),nq}")]
|
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized.ToString(),nq}")]
|
||||||
public readonly struct Quaternion(float x, float y, float z, float w)
|
public readonly struct Quaternion(float x, float y, float z, float w) : IEquatable<Quaternion>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The X(i) imaginary of the <see cref="Quaternion"/>.
|
/// The X(i) imaginary of the <see cref="Quaternion"/>.
|
||||||
@@ -77,6 +79,30 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
public static implicit operator Quaternion(System.Numerics.Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
|
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);
|
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>
|
/// <summary>
|
||||||
/// Calculates the length of the <see cref="Quaternion"/>.
|
/// Calculates the length of the <see cref="Quaternion"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -130,6 +156,36 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
/// <returns>The normalized <see cref="Quaternion"/>.</returns>
|
/// <returns>The normalized <see cref="Quaternion"/>.</returns>
|
||||||
public static Quaternion Normalize(Quaternion quaternion) => quaternion / Length(quaternion);
|
public static Quaternion Normalize(Quaternion quaternion) => quaternion / Length(quaternion);
|
||||||
|
|
||||||
|
public static Quaternion LookAt(Vector3D origin, Vector3D target, Vector3D up) => LookAt(target - origin, up);
|
||||||
|
public static Quaternion LookAt(Vector3D target, Vector3D up)
|
||||||
|
{
|
||||||
|
Vector3D forward = target.Normalized;
|
||||||
|
|
||||||
|
if (forward.LengthSquared() < 1e-6f)
|
||||||
|
return Identity;
|
||||||
|
|
||||||
|
Vector3D right = up.Cross(forward).Normalized;
|
||||||
|
Vector3D newUp = forward.Cross(right);
|
||||||
|
|
||||||
|
Matrix4x4 rot = new(
|
||||||
|
right.X, right.Y, right.Z, 0f,
|
||||||
|
newUp.X, newUp.Y, newUp.Z, 0f,
|
||||||
|
forward.X, forward.Y, forward.Z, 0f,
|
||||||
|
0f, 0f, 0f, 1f
|
||||||
|
);
|
||||||
|
|
||||||
|
return FromRotationMatrix4x4(rot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
/// <summary>
|
||||||
/// Inverts the direction of the <see cref="Quaternion"/>.
|
/// Inverts the direction of the <see cref="Quaternion"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -152,8 +208,12 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
/// <returns>The rotated <see cref="Vector3D"/>.</returns>
|
/// <returns>The rotated <see cref="Vector3D"/>.</returns>
|
||||||
public static Vector3D RotateVector(Vector3D vector, Quaternion quaternion)
|
public static Vector3D RotateVector(Vector3D vector, Quaternion quaternion)
|
||||||
{
|
{
|
||||||
Quaternion rotation = quaternion * new Quaternion(vector.X, vector.Y, vector.Z, 0) * Invert(quaternion);
|
// https://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/
|
||||||
return new(rotation.X, rotation.Y, rotation.Z);
|
// 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>
|
/// <summary>
|
||||||
@@ -173,8 +233,8 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
dot = -dot;
|
dot = -dot;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dot > 0.9995f)
|
if (dot > 0.999999f)
|
||||||
return Lerp(from, to, t);
|
return to;
|
||||||
|
|
||||||
float angle = Math.Acos(dot);
|
float angle = Math.Acos(dot);
|
||||||
float sinAngle = Math.Sin(angle);
|
float sinAngle = Math.Sin(angle);
|
||||||
@@ -208,9 +268,9 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
/// <param name="axis">The axis of the rotation in <see cref="Vector3D"/>.</param>
|
/// <param name="axis">The axis of the rotation in <see cref="Vector3D"/>.</param>
|
||||||
/// <param name="angle">The angle in radians.</param>
|
/// <param name="angle">The angle in radians.</param>
|
||||||
/// <returns>The rotation <see cref="Quaternion"/> calculated by the given parameters.</returns>
|
/// <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);
|
float sinHalf = Math.Sin(halfAngle);
|
||||||
return new Quaternion(axis.X * sinHalf, axis.Y * sinHalf, axis.Z * sinHalf, Math.Cos(halfAngle));
|
return new Quaternion(axis.X * sinHalf, axis.Y * sinHalf, axis.Z * sinHalf, Math.Cos(halfAngle));
|
||||||
}
|
}
|
||||||
@@ -237,12 +297,11 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the <see cref="System.Numerics.Matrix4x4"/> from given <see cref="Quaternion"/>.
|
/// Calculates the <see cref="Matrix4x4"/> from given <see cref="Quaternion"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="axis">The axis of the rotation in <see cref="Vector3D"/>.</param>
|
/// <param name="quaternion">The rotation <see cref="Quaternion"/>.</param>
|
||||||
/// <param name="angle">The angle in radians.</param>
|
/// <returns>The rotation <see cref="Matrix4x4"/> calculated by the given <see cref="Quaternion"/>.</returns>
|
||||||
/// <returns>The rotation <see cref="System.Numerics.Matrix4x4"/> calculated by the given <see cref="Quaternion"/>.</returns>
|
public static Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion)
|
||||||
public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion)
|
|
||||||
{
|
{
|
||||||
float m00 = 1 - 2 * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z);
|
float m00 = 1 - 2 * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z);
|
||||||
float m01 = 2 * (quaternion.X * quaternion.Y - quaternion.W * quaternion.Z);
|
float m01 = 2 * (quaternion.X * quaternion.Y - quaternion.W * quaternion.Z);
|
||||||
@@ -272,6 +331,52 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the <see cref="Quaternion"/> from given <see cref="Matrix4x4"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="martix">The rotation <see cref="Matrix4x4"/>.</param>
|
||||||
|
/// <returns>The rotation <see cref="Quaternion"/> calculated by the given <see cref="Matrix4x4"/>.</returns>
|
||||||
|
public static Quaternion FromRotationMatrix4x4(Matrix4x4 martix)
|
||||||
|
{
|
||||||
|
float trace = martix.M11 + martix.M22 + martix.M33;
|
||||||
|
float w, x, y, z;
|
||||||
|
|
||||||
|
if (trace > 0)
|
||||||
|
{
|
||||||
|
float s = Math.Sqrt(trace + 1.0f) * 2f;
|
||||||
|
w = .25f * s;
|
||||||
|
x = (martix.M23 - martix.M32) / s;
|
||||||
|
y = (martix.M31 - martix.M13) / s;
|
||||||
|
z = (martix.M12 - martix.M21) / s;
|
||||||
|
}
|
||||||
|
else if ((martix.M11 > martix.M22) && (martix.M11 > martix.M33))
|
||||||
|
{
|
||||||
|
float s = Math.Sqrt(1.0f + martix.M11 - martix.M22 - martix.M33) * 2f;
|
||||||
|
w = (martix.M23 - martix.M32) / s;
|
||||||
|
x = .25f * s;
|
||||||
|
y = (martix.M12 + martix.M21) / s;
|
||||||
|
z = (martix.M31 + martix.M13) / s;
|
||||||
|
}
|
||||||
|
else if (martix.M22 > martix.M33)
|
||||||
|
{
|
||||||
|
float s = Math.Sqrt(1.0f + martix.M22 - martix.M11 - martix.M33) * 2f;
|
||||||
|
w = (martix.M31 - martix.M13) / s;
|
||||||
|
x = (martix.M12 + martix.M21) / s;
|
||||||
|
y = .25f * s;
|
||||||
|
z = (martix.M23 + martix.M32) / s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float s = Math.Sqrt(1.0f + martix.M33 - martix.M11 - martix.M22) * 2f;
|
||||||
|
w = (martix.M12 - martix.M21) / s;
|
||||||
|
x = (martix.M31 + martix.M13) / s;
|
||||||
|
y = (martix.M23 + martix.M32) / s;
|
||||||
|
z = .25f * s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(x, y, z, w);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if two <see cref="Quaternion"/>s are approximately equal within a specified epsilon range.
|
/// Checks if two <see cref="Quaternion"/>s are approximately equal within a specified epsilon range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -282,24 +387,25 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
public static bool ApproximatelyEquals(Quaternion left, Quaternion right, float epsilon = float.Epsilon)
|
public static bool ApproximatelyEquals(Quaternion left, Quaternion right, float epsilon = float.Epsilon)
|
||||||
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon) && left.W.ApproximatelyEquals(right.W, epsilon);
|
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon) && left.W.ApproximatelyEquals(right.W, epsilon);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts the <see cref="Quaternion"/> to its string representation.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
|
|
||||||
public override string ToString() => $"{nameof(Quaternion)}({W}, {X}, {Y}, {Z})";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines whether the specified object is equal to the current <see cref="Quaternion"/>.
|
/// Determines whether the specified object is equal to the current <see cref="Quaternion"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The object to compare with the current <see cref="Quaternion"/>.</param>
|
/// <param name="obj">The object to compare with the current <see cref="Quaternion"/>.</param>
|
||||||
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Quaternion"/>; otherwise, <see cref="false"/>.</returns>
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Quaternion"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
public override bool Equals(object? obj) => obj is Quaternion objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y) && Z.Equals(objVec.Z) && W.Equals(objVec.W);
|
public override bool Equals(object? obj) => obj is Quaternion quaternion && this == quaternion;
|
||||||
|
public bool Equals(Quaternion other) => this == other;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a hash code for the <see cref="Quaternion"/>.
|
/// Generates a hash code for the <see cref="Quaternion"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A hash code for the <see cref="Quaternion"/>.</returns>
|
/// <returns>A hash code for the <see cref="Quaternion"/>.</returns>
|
||||||
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z);
|
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z, W);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="Quaternion"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(Quaternion)}({X}, {Y}, {Z}, {W})";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -307,6 +413,9 @@ public readonly struct Quaternion(float x, float y, float z, float w)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class QuaternionExtensions
|
public static class QuaternionExtensions
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc cref="Quaternion.ToAngles(Quaternion)" />
|
||||||
|
public static Vector3D ToAngles(this Quaternion quaternion) => Quaternion.ToAngles(quaternion);
|
||||||
|
|
||||||
/// <inheritdoc cref="Quaternion.Length(Quaternion)" />
|
/// <inheritdoc cref="Quaternion.Length(Quaternion)" />
|
||||||
public static float Length(this Quaternion quaternion) => Quaternion.Length(quaternion);
|
public static float Length(this Quaternion quaternion) => Quaternion.Length(quaternion);
|
||||||
|
|
||||||
@@ -328,6 +437,9 @@ public static class QuaternionExtensions
|
|||||||
/// <inheritdoc cref="Quaternion.Normalize(Quaternion)" />
|
/// <inheritdoc cref="Quaternion.Normalize(Quaternion)" />
|
||||||
public static Quaternion Normalize(this Quaternion quaternion) => 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)" />
|
/// <inheritdoc cref="Quaternion.Invert(Quaternion)" />
|
||||||
public static Quaternion Invert(this Quaternion quaternion) => Quaternion.Invert(quaternion);
|
public static Quaternion Invert(this Quaternion quaternion) => Quaternion.Invert(quaternion);
|
||||||
|
|
||||||
@@ -347,7 +459,7 @@ public static class QuaternionExtensions
|
|||||||
public static float Dot(this Quaternion left, Quaternion right) => Quaternion.Dot(left, right);
|
public static float Dot(this Quaternion left, Quaternion right) => Quaternion.Dot(left, right);
|
||||||
|
|
||||||
/// <inheritdoc cref="Quaternion.ToRotationMatrix4x4(Quaternion, Quaternion)" />
|
/// <inheritdoc cref="Quaternion.ToRotationMatrix4x4(Quaternion, Quaternion)" />
|
||||||
public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(this Quaternion quaternion) => Quaternion.ToRotationMatrix4x4(quaternion);
|
public static Matrix4x4 ToRotationMatrix4x4(this Quaternion quaternion) => Quaternion.ToRotationMatrix4x4(quaternion);
|
||||||
|
|
||||||
/// <inheritdoc cref="Quaternion.FromAxisAngle(Vector3D, float)" />
|
/// <inheritdoc cref="Quaternion.FromAxisAngle(Vector3D, float)" />
|
||||||
public static Quaternion CreateRotation(this Vector3D axis, float angle) => Quaternion.FromAxisAngle(axis, angle);
|
public static Quaternion CreateRotation(this Vector3D axis, float angle) => Quaternion.FromAxisAngle(axis, angle);
|
||||||
|
|||||||
108
Engine.Core/Primitives/Ray2D.cs
Normal file
108
Engine.Core/Primitives/Ray2D.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Engine.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an infinite ray in 2D space.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Origin">The <see cref="Vector2D"/> in 2D space where the ray starts from.</param>
|
||||||
|
/// <param name="Direction">Normalized <see cref="Vector2D"/> indicating the ray's is direction.</param>
|
||||||
|
public readonly struct Ray2D(Vector2D Origin, Vector2D Direction) : IEquatable<Ray2D>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The starting point of the <see cref="Ray2D"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Vector2D Origin = Origin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The direction in which the <see cref="Ray2D"/> points. Should be a normalized vector.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Vector2D Direction = Direction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="Ray2D"/> with the same origin but with the direction reversed.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Ray2D Reversed => new(Origin, -Direction);
|
||||||
|
|
||||||
|
public static bool operator ==(Ray2D left, Ray2D right) => left.Origin == right.Origin && left.Direction == right.Direction;
|
||||||
|
public static bool operator !=(Ray2D left, Ray2D right) => left.Origin != right.Origin || left.Direction != right.Direction;
|
||||||
|
public static implicit operator Ray2D(Line2D line) => new(line.From, line.From.FromTo(line.To).Normalized);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a <see cref="Line2D"/> from a <see cref="Ray2D"/>, extending from its origin in the <see cref="Ray2D"/>'s direction for a given distance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ray">The source <see cref="Ray2D"/>.</param>
|
||||||
|
/// <param name="distance">The length of the line segment to create from the <see cref="Ray2D"/>.</param>
|
||||||
|
/// <returns>A <see cref="Line2D"/> representing the segment of the <see cref="Ray2D"/>.</returns>
|
||||||
|
public static Line2D GetLine(Ray2D ray, float distance)
|
||||||
|
=> new(ray.Origin, ray.Origin + ray.Direction * distance);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates the point on the <see cref="Ray2D"/> at a specified distance from its origin.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ray">The <see cref="Ray2D"/> to evaluate.</param>
|
||||||
|
/// <param name="distanceFromOrigin">The distance from the origin along the <see cref="Ray2D"/>'s direction.</param>
|
||||||
|
/// <returns>A <see cref="Vector2D"/> representing the point at the given distance on the <see cref="Ray2D"/>.</returns>
|
||||||
|
public static Vector2D Evaluate(Ray2D ray, float distanceFromOrigin)
|
||||||
|
=> ray.Origin + ray.Direction * distanceFromOrigin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the closest point on the <see cref="Ray2D"/> to the specified point.
|
||||||
|
/// </summary>
|
||||||
|
public static Vector2D ClosestPointTo(Ray2D ray, Vector2D point)
|
||||||
|
{
|
||||||
|
Vector2D originToPoint = ray.Origin.FromTo(point);
|
||||||
|
|
||||||
|
float dot = ray.Direction.Dot(originToPoint);
|
||||||
|
|
||||||
|
return ray.Origin + ray.Direction * dot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two <see cref="Ray2D"/>s are approximately equal within a specified epsilon range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="left">The first <see cref="Ray2D"/>.</param>
|
||||||
|
/// <param name="right">The second <see cref="Ray2D"/>.</param>
|
||||||
|
/// <param name="epsilon">The epsilon range.</param>
|
||||||
|
/// <returns><see cref="true"/> if the <see cref="Ray2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public static bool ApproximatelyEquals(Ray2D left, Ray2D 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="Ray2D"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to compare with the current <see cref="Ray2D"/>.</param>
|
||||||
|
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Ray2D"/>; otherwise, <see cref="false"/>.</returns>
|
||||||
|
public override bool Equals(object? obj) => obj is Ray2D ray2D && this == ray2D;
|
||||||
|
public bool Equals(Ray2D other) => this == other;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a hash code for the <see cref="Ray2D"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A hash code for the <see cref="Ray2D"/>.</returns>
|
||||||
|
public override int GetHashCode() => System.HashCode.Combine(Origin, Direction);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the <see cref="Ray2D"/> to its string representation.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A string representation of the <see cref="Ray2D"/>.</returns>
|
||||||
|
public override string ToString() => $"{nameof(Ray2D)}({Origin}, {Direction})";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for the <see cref="Ray2D"/> struct.
|
||||||
|
/// </summary>
|
||||||
|
public static class Ray2DExtensions
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="Ray2D.GetLine(Ray2D, float) />
|
||||||
|
public static Line2D ToLine(this Ray2D ray, float distance) => Ray2D.GetLine(ray, distance);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Ray2D.Evaluate(Ray2D, float) />
|
||||||
|
public static Vector2D Evaluate(this Ray2D ray, float distanceFromOrigin) => Ray2D.Evaluate(ray, distanceFromOrigin);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Ray2D.ClosestPointTo(Ray2D, Vector2D) />
|
||||||
|
public static Vector2D ClosestPointTo(this Ray2D ray, Vector2D point) => Ray2D.ClosestPointTo(ray, point);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Ray2D.ApproximatelyEquals(Ray2D, Ray2D, float)" />
|
||||||
|
public static bool ApproximatelyEquals(this Ray2D left, Ray2D right, float epsilon = float.Epsilon) => Ray2D.ApproximatelyEquals(left, right, epsilon);
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user