87 Commits

Author SHA1 Message Date
7e07cd30db refactor: monogame camera transform caching 2025-11-03 15:05:32 +03:00
e9ba7867ac fix: ordered behaviour controllers not removing behaviours properly because of index getter function time dependence 2025-11-02 10:54:13 +03:00
f28307be80 fix: entrance manager exits not being managed properly 2025-11-01 23:27:55 +03:00
5f019892f1 feat: IBehaviourController.CollectionMethod added 2025-11-01 23:27:40 +03:00
0bd2b0618d feat: wip network packet encryption 2025-10-28 19:47:54 +03:00
0691d7092c fix: half of the universe objects not getting deserialized because of for loop 2025-10-28 10:32:01 +03:00
4ed049573a feat: client & server behaviours added 2025-10-28 10:04:25 +03:00
599774ea8b fix: litenetlib server throwing an exception on sending packets 2025-10-28 10:04:25 +03:00
b17f880ecf feat: non-generic FNV1a hasher added 2025-10-28 10:04:25 +03:00
98bc0693dd fix: possible hash code collisions on Matrix4x4 2025-10-28 10:04:25 +03:00
ac2e160abb refactor!: Identifiable interface extracted from IEntity 2025-10-28 10:04:25 +03:00
30a07dd034 chore: added to xna matrix extension method 2025-10-28 10:04:25 +03:00
f180713f4b feat: added basic 4d matrix 2025-10-28 10:04:25 +03:00
2d612ea0d4 fix: universe object registration logic order fixed 2025-10-28 10:04:25 +03:00
5a6883a87f BREAKING CHANGE: replaced universe objects with root universe object 2025-10-28 10:02:59 +03:00
a6eb67551d chore: put monogame behaviours under a parent universe object 2025-10-23 22:45:57 +03:00
981732ff75 fix: Quaternion.SLerp snapping issue 2025-10-23 12:51:36 +03:00
4bdd32b808 feat: missing extension methods for Math.Tan and Atan 2025-10-23 12:45:00 +03:00
cac69f5f35 chore: Camera3D now uses left handed matrices 2025-10-23 10:05:43 +03:00
1660915678 feat: added near & far planes to camera3D 2025-10-23 10:04:56 +03:00
1d02b0eba2 feat: added missing Math.Tan & Atan methods 2025-10-23 10:00:52 +03:00
2ef7fa6577 chore: added assert for universe entrance manager issue 2025-10-22 23:44:29 +03:00
193b2c5af0 fix: yaml serialization issues caused by class converter treating primitives as classes 2025-10-22 23:35:33 +03:00
a859c0cbff fix: quaternion string format order fixed 2025-10-22 22:24:48 +03:00
fba64c3854 fix: behaviours being serialized in reverse 2025-10-22 22:13:46 +03:00
a975cbb56b chore: added fixme comment 2025-10-22 21:30:09 +03:00
1664a9ccf7 fix: entrance manager not calling exits 2025-10-22 20:21:58 +03:00
988a6f67f2 BREAKING CHANGE: renamed original Behaviour class to BehaviourInternal, and replaced it with BehaviourBase
Original Behaviour was using old methods for detecting entering/exiting universe,
they are now all under the same hood and the original is kept for UniverseEntranceManager
because it needs to enter the universe without itself. The internal behaviour kept under
a subnamespace of "Core.Internal" for the purpose that it might come in handy for other use cases.
2025-10-22 16:50:19 +03:00
2f32038f04 refactor: moved packer into sub-namespace 2025-10-22 11:05:26 +03:00
4b756fa232 feat: added more assert methods 2025-10-22 11:04:44 +03:00
0db2cae1bb feat: added sphere primitive 2025-10-21 19:06:58 +03:00
896f7876c1 feat: implicit conversion from circle to aabb2d 2025-10-21 19:02:20 +03:00
56d3112e35 chore!: renamed extension method circle.ToProjection to ProjectTo 2025-10-21 18:43:21 +03:00
32ec2325dc fix: YamlDotNet being on the wrong branch fixed 2025-10-19 23:44:49 +03:00
b42f1f1881 fix: added missing types for new primitives 2025-10-19 19:03:30 +03:00
f8096377b2 chore: removed unsupported leftover methods from int vectors 2025-10-19 19:02:37 +03:00
a9fc819268 feat: added 3D AABB primitive 2025-10-19 18:45:57 +03:00
1d63391975 chore!: renamed AABB to AABB2D 2025-10-19 18:45:48 +03:00
61ff0887e2 feat: 3D camera added 2025-10-19 00:28:40 +03:00
16344dccc7 feat: 3D transforms added 2025-10-19 00:24:56 +03:00
dc4bea3eef feat: Vector4D added 2025-10-19 00:24:19 +03:00
d1b2723a70 feat: 3D ray and line primitives added 2025-10-19 00:24:06 +03:00
2f5c04e66b feat: ITransform.OnTransformUpdated event added 2025-10-19 00:22:30 +03:00
f753da1f87 chore: added XNA Vector3 and Quaternion conversions 2025-10-19 00:16:39 +03:00
6901159106 fix: Quaternion.RotateVector method not working properly on some angles fixed 2025-10-19 00:16:07 +03:00
7469c9ab0c docs: Vector3D.FromAxisAngle parameters updated 2025-10-19 00:15:09 +03:00
ede90adbdc feat: quaternion rotate method added 2025-10-19 00:14:39 +03:00
eeaca3a6c7 feat: quaternion to angles conversion added 2025-10-19 00:13:59 +03:00
3b984a0a4b feat: added Vector3D.Transform method 2025-10-19 00:13:01 +03:00
c5afb70b18 docs: vector3d rotate parameters updated 2025-10-19 00:12:35 +03:00
9d2a192f04 refactor: monogame 2D camera now use engine events 2025-10-19 00:11:51 +03:00
598debc233 perf: removed unnecessary null checks on events 2025-10-19 00:10:07 +03:00
ab05a89175 feat: line 2D to line 2D equation implicit operator added 2025-10-18 14:43:00 +03:00
dbd15cbbc2 chore: updated submodule link from ssh to https 2025-10-18 11:18:54 +03:00
e051f5cfb4 fix: removed git issue caused by submodule issue 2025-10-18 11:11:07 +03:00
e70b7f112f chore: coroutine manager moved to correct directory 2025-10-17 21:27:49 +03:00
f55ba499b6 fix: int vectors not rounding float values on regular vector conversions 2025-10-16 15:37:31 +03:00
b75f30f864 fix: math round methods not working properly 2025-10-16 15:37:03 +03:00
6f1f30bd53 feat: intervectoral implicit conversions added 2025-10-16 14:38:27 +03:00
92a5c276a4 feat: integer vector 2d & 3d added 2025-10-16 14:12:24 +03:00
69bc6573d1 feat: added IEquatable interfaces to primitives 2025-10-16 13:59:49 +03:00
28bc022587 perf: forgotten memory allocation on triangle batch 2025-10-16 08:43:40 +03:00
25db60e436 perf: memory allocations reduced on universe update 2025-10-16 08:25:02 +03:00
7c62440bba chore: added an experimental ordered fast list class 2025-10-14 12:06:47 +03:00
4bec7bce6e fix: fast list readonly mode not throwing exceptions 2025-10-14 11:42:05 +03:00
8d31372c24 refactor: universe and objects now use fast list 2025-10-13 12:40:43 +03:00
a2e704916e feat: fast list now implements IList 2025-10-13 12:39:49 +03:00
c7d170fad9 perf: significant performance optimizations on ordered behaviour collectors by using a sorted dictionary 2025-10-13 09:58:58 +03:00
9ccf7b754d perf: ordered behaviour collectors now use linked lists for performance 2025-10-11 16:07:26 +03:00
e3d4899112 refactor: renamed behaviour collectors from sorted to ordered 2025-10-11 16:05:47 +03:00
566c16d09c refactor: active behaviour collector base added 2025-10-11 15:36:58 +03:00
ae9d4f02ef chore: moved behaviour collectors into subdirectory 2025-10-11 15:36:06 +03:00
e77772cbc2 refactor: behaviour collector base added 2025-10-11 15:08:02 +03:00
4c542df401 perf: implemented fast list with index mapping 2025-10-10 14:58:40 +03:00
28ca343b43 perf: improved pool return method by using a hashset for searching if the returning item is already queued 2025-10-10 14:21:54 +03:00
651b0614c4 fix: index check on triangle batch flush 2025-10-10 11:43:04 +03:00
f47488c6f1 fix: registering/unregistering objects during universe enter/exit causing stack overflows 2025-10-10 10:59:39 +03:00
6d159330a1 refactor: moved client and server interfaces into their files 2025-08-31 23:09:02 +03:00
8e314f3269 feat: networking type hasher added 2025-08-19 21:17:47 +03:00
f5a7077570 perf: improved garbage created by tweens slightly
They still do generate a lot of garbage but with boxed value pools I made the boxes reusable, it still does generate garbage through the delegate creation, gotta find a solution for them later
2025-08-14 20:31:46 +03:00
746d29fb7a refactor: shortened IButtonInputs event declaration 2025-08-10 14:42:47 +03:00
cf68f6ca6f fix: first frame updates not calling first, they are now set to be a high priority 2025-08-09 22:29:44 +03:00
a4b83679b1 chore: added todo for a rare bug 2025-08-09 21:41:24 +03:00
a31b39fd1d fix: universe finalize not working properly 2025-08-09 21:36:28 +03:00
0205354202 fix: universe entrance manager using the wrong reference on universe exit 2025-08-09 21:09:36 +03:00
949dfeb3d9 fix: universe reverse for loop index doesn't start with count - 1 2025-08-09 21:03:45 +03:00
620ef911fa fix: parameter name typo 2025-08-09 21:01:25 +03:00
168 changed files with 5070 additions and 1032 deletions

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "Engine.Integration/YamlDotNet"] [submodule "Engine.Integration/YamlDotNet"]
path = Engine.Integration/YamlDotNet path = Engine.Integration/YamlDotNet
url = git@github.com:Syntriax/YamlDotNet.git url = https://github.com/Syntriax/YamlDotNet.git

View File

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

View File

@@ -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;
enum CollectionMethod { Clear, Append };
readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded); readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded);
readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved); readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved);
} }

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

View File

@@ -3,18 +3,4 @@ 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<IEntity, IdChangedArguments> OnIdChanged { get; }
/// <summary>
/// The ID of the <see cref="IEntity"/>.
/// </summary>
string Id { get; set; }
readonly record struct IdChangedArguments(string PreviousId);
}

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

View File

@@ -16,10 +16,15 @@ public interface ITransform2D : IBehaviour
Event<ITransform2D, ScaleChangedArguments> OnScaleChanged { get; } 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<ITransform2D, RotationChangedArguments> OnRotationChanged { get; } 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> /// <summary>
/// The <see cref="Vector2D"/> pointing upwards in world space. /// The <see cref="Vector2D"/> pointing upwards in world space.
/// </summary> /// </summary>

View File

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

View File

@@ -78,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"/>.

View File

@@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
public class ActiveBehaviourCollectorSorted<T> : ActiveBehaviourCollector<T> where T : class, IBehaviour
{
private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!;
private IComparer<T>? _sortBy = null;
public IComparer<T>? SortBy
{
get => _sortBy;
set
{
_sortBy = value;
if (value is not null)
activeBehaviours.Sort(value);
}
}
protected override void AddBehaviour(T behaviour)
{
if (SortBy is null)
{
activeBehaviours.Add(behaviour);
return;
}
int insertionIndex = activeBehaviours.BinarySearch(behaviour, SortBy);
if (insertionIndex < 0)
insertionIndex = ~insertionIndex;
activeBehaviours.Insert(insertionIndex, behaviour);
}
protected override void OnBehaviourAdd(IBehaviour behaviour)
{
behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged);
}
protected override void OnBehaviourRemove(IBehaviour behaviour)
{
behaviour.OnPriorityChanged.RemoveListener(delegateOnPriorityChanged);
}
private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{
T behaviour = (T)sender;
activeBehaviours.Remove(behaviour);
AddBehaviour(behaviour);
}
public ActiveBehaviourCollectorSorted()
{
delegateOnPriorityChanged = OnPriorityChanged;
}
public ActiveBehaviourCollectorSorted(IUniverse universe, Comparison<T> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
SortBy = Comparer<T>.Create(sortBy);
}
}

View File

@@ -4,7 +4,7 @@ namespace Engine.Core;
public abstract class BaseEntity : IEntity public abstract class BaseEntity : IEntity
{ {
public Event<IEntity, IEntity.IdChangedArguments> OnIdChanged { get; } = new(); public Event<IIdentifiable, IIdentifiable.IdChangedArguments> OnIdChanged { get; } = new();
public Event<IInitializable> OnInitialized { get; } = new(); public Event<IInitializable> OnInitialized { get; } = new();
public Event<IInitializable> OnFinalized { get; } = new(); public Event<IInitializable> OnFinalized { get; } = new();
public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new(); public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new();

View File

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

View File

@@ -1,6 +1,7 @@
namespace Engine.Core; 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!;

View File

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

View File

@@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
namespace Engine.Core;
public class BehaviourCollectorSorted<T> : BehaviourCollector<T> where T : class
{
private readonly Event<IBehaviour, IBehaviour.PriorityChangedArguments>.EventHandler delegateOnPriorityChanged = null!;
private IComparer<T>? _sortBy = null;
public IComparer<T>? SortBy
{
get => _sortBy;
set
{
_sortBy = value;
if (value is not null)
behaviours.Sort(value);
}
}
protected override void AddBehaviour(T behaviour)
{
if (SortBy is null)
{
behaviours.Add(behaviour);
return;
}
int insertionIndex = behaviours.BinarySearch(behaviour, SortBy);
if (insertionIndex < 0)
insertionIndex = ~insertionIndex;
behaviours.Insert(insertionIndex, behaviour);
}
protected override void OnBehaviourAdd(IBehaviour behaviour)
{
behaviour.OnPriorityChanged.AddListener(delegateOnPriorityChanged);
}
protected override void OnBehaviourRemove(IBehaviour behaviour)
{
behaviour.OnPriorityChanged.RemoveListener(delegateOnPriorityChanged);
}
private void OnPriorityChanged(IBehaviour sender, IBehaviour.PriorityChangedArguments args)
{
T behaviour = (T)sender;
behaviours.Remove(behaviour);
AddBehaviour(behaviour);
}
public BehaviourCollectorSorted()
{
delegateOnPriorityChanged = OnPriorityChanged;
}
public BehaviourCollectorSorted(IUniverse universe, Comparison<T> sortBy) : base(universe)
{
delegateOnPriorityChanged = OnPriorityChanged;
SortBy = Comparer<T>.Create(sortBy);
}
}

View File

@@ -10,7 +10,7 @@ public class BehaviourController : BaseEntity, IBehaviourController
public Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments> OnBehaviourRemoved { get; } = new(); public Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments> OnBehaviourRemoved { get; } = new();
public Event<IHasUniverseObject> OnUniverseObjectAssigned { get; } = new(); public Event<IHasUniverseObject> OnUniverseObjectAssigned { get; } = new();
private readonly List<IBehaviour> behaviours = new(Constants.BEHAVIOURS_SIZE_INITIAL); private readonly FastList<IBehaviour> behaviours = new(Constants.BEHAVIOURS_SIZE_INITIAL);
private IUniverseObject _universeObject = null!; private IUniverseObject _universeObject = null!;
@@ -58,9 +58,11 @@ public class BehaviourController : BaseEntity, IBehaviourController
return behaviours; return behaviours;
} }
public void GetBehaviours<T>(IList<T> results) public void GetBehaviours<T>(IList<T> results, IBehaviourController.CollectionMethod collectionMethod = IBehaviourController.CollectionMethod.Clear)
{ {
results.Clear(); if (collectionMethod == IBehaviourController.CollectionMethod.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)

View File

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

View File

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

View File

@@ -3,12 +3,10 @@ using System.Collections.Generic;
namespace Engine.Core; namespace Engine.Core;
public class ActiveBehaviourCollector<T> : IBehaviourCollector<T> where T : class, IBehaviour public abstract class ActiveBehaviourCollectorBase<T> : IBehaviourCollector<T> where T : class, IBehaviour
{ {
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourCollectedArguments> OnCollected { get; } = new(); protected readonly Dictionary<IActive, T> monitoringActiveToBehaviour = new(32);
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourRemovedArguments> OnRemoved { get; } = new(); protected readonly FastList<T> monitoringBehaviours = new(32);
public Event<IHasUniverse> OnUniverseAssigned { get; } = new();
public Event<IAssignable>? OnUnassigned { get; } = new();
private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler delegateOnBehaviourAdded = null!; private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler delegateOnBehaviourAdded = null!;
private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler delegateOnBehaviourRemoved = null!; private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler delegateOnBehaviourRemoved = null!;
@@ -16,12 +14,95 @@ public class ActiveBehaviourCollector<T> : IBehaviourCollector<T> where T : clas
private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler delegateOnUniverseObjectRegistered = null!; private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler delegateOnUniverseObjectRegistered = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler delegateOnUniverseObjectUnregistered = null!; private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler delegateOnUniverseObjectUnregistered = null!;
private readonly List<T> monitoringBehaviours = new(32); public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourCollectedArguments> OnCollected { get; } = new();
protected readonly List<T> activeBehaviours = new(32); public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourRemovedArguments> OnRemoved { get; } = new();
protected readonly Dictionary<IActive, T> monitoringActiveToBehaviour = new(32); 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 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) private void OnUniverseObjectRegistered(IUniverse manager, IUniverse.UniverseObjectRegisteredArguments args)
{ {
IUniverseObject universeObject = args.UniverseObjectRegistered; IUniverseObject universeObject = args.UniverseObjectRegistered;
@@ -44,89 +125,7 @@ public class ActiveBehaviourCollector<T> : IBehaviourCollector<T> where T : clas
OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i])); OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
} }
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { } public ActiveBehaviourCollectorBase()
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 virtual void AddBehaviour(T behaviour) => activeBehaviours.Add(behaviour);
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 (activeBehaviours.Remove(behaviour))
{
OnBehaviourRemove(behaviour);
OnRemoved?.Invoke(this, new(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 (activeBehaviours.Remove(tBehaviour))
{
OnBehaviourRemove(tBehaviour);
OnRemoved?.Invoke(this, new(tBehaviour));
}
}
public bool Assign(IUniverse universe)
{
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
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.UniverseObjects)
OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered);
Universe.OnUniverseObjectUnRegistered.RemoveListener(delegateOnUniverseObjectUnregistered);
Universe = null!;
OnUnassigned?.Invoke(this);
return true;
}
public int Count => activeBehaviours.Count;
public T this[Index index] => activeBehaviours[index];
public ActiveBehaviourCollector()
{ {
delegateOnBehaviourAdded = OnBehaviourAdded; delegateOnBehaviourAdded = OnBehaviourAdded;
delegateOnBehaviourRemoved = OnBehaviourRemoved; delegateOnBehaviourRemoved = OnBehaviourRemoved;
@@ -135,7 +134,7 @@ public class ActiveBehaviourCollector<T> : IBehaviourCollector<T> where T : clas
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered; delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
} }
public ActiveBehaviourCollector(IUniverse universe) public ActiveBehaviourCollectorBase(IUniverse universe)
{ {
delegateOnBehaviourAdded = OnBehaviourAdded; delegateOnBehaviourAdded = OnBehaviourAdded;
delegateOnBehaviourRemoved = OnBehaviourRemoved; delegateOnBehaviourRemoved = OnBehaviourRemoved;

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

View 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) { }
}

View File

@@ -1,24 +1,87 @@
using System; using System;
using System.Collections.Generic;
namespace Engine.Core; namespace Engine.Core;
public class BehaviourCollector<T> : IBehaviourCollector<T> where T : class public abstract class BehaviourCollectorBase<T> : IBehaviourCollector<T> where T : class
{ {
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();
private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler delegateOnBehaviourAdded = null!; private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler delegateOnBehaviourAdded = null!;
private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler delegateOnBehaviourRemoved = null!; private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler delegateOnBehaviourRemoved = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler delegateOnUniverseObjectRegistered = null!; private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler delegateOnUniverseObjectRegistered = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler delegateOnUniverseObjectUnregistered = null!; private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler delegateOnUniverseObjectUnregistered = null!;
protected readonly List<T> behaviours = new(32); 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 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) private void OnUniverseObjectRegistered(IUniverse manager, IUniverse.UniverseObjectRegisteredArguments args)
{ {
IUniverseObject universeObject = args.UniverseObjectRegistered; IUniverseObject universeObject = args.UniverseObjectRegistered;
@@ -41,70 +104,7 @@ public class BehaviourCollector<T> : IBehaviourCollector<T> where T : class
OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i])); OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
} }
protected virtual void AddBehaviour(T behaviour) => behaviours.Add(behaviour); public BehaviourCollectorBase()
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 virtual void OnBehaviourRemove(IBehaviour behaviour) { }
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviourController.BehaviourRemovedArguments args)
{
if (args.BehaviourRemoved is not T tBehaviour)
return;
if (!behaviours.Remove(tBehaviour))
return;
OnBehaviourRemove(args.BehaviourRemoved);
OnRemoved?.Invoke(this, new(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, 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.UniverseObjects)
OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(delegateOnUniverseObjectRegistered);
Universe.OnPreUniverseObjectUnRegistered.RemoveListener(delegateOnUniverseObjectUnregistered);
Universe = null!;
OnUnassigned?.Invoke(this);
return true;
}
public int Count => behaviours.Count;
public T this[Index index] => behaviours[index];
public BehaviourCollector()
{ {
delegateOnBehaviourAdded = OnBehaviourAdded; delegateOnBehaviourAdded = OnBehaviourAdded;
delegateOnBehaviourRemoved = OnBehaviourRemoved; delegateOnBehaviourRemoved = OnBehaviourRemoved;
@@ -112,7 +112,7 @@ public class BehaviourCollector<T> : IBehaviourCollector<T> where T : class
delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered; delegateOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
} }
public BehaviourCollector(IUniverse universe) public BehaviourCollectorBase(IUniverse universe)
{ {
delegateOnBehaviourAdded = OnBehaviourAdded; delegateOnBehaviourAdded = OnBehaviourAdded;
delegateOnBehaviourRemoved = OnBehaviourRemoved; delegateOnBehaviourRemoved = OnBehaviourRemoved;

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

View File

@@ -4,6 +4,22 @@ 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");

View File

@@ -100,18 +100,16 @@ public static class BehaviourControllerExtensions
/// </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;
} }
} }
@@ -161,22 +159,20 @@ public static class BehaviourControllerExtensions
/// </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)
behaviours.Add(behaviour);
foreach (IUniverseObject child in universeObject.Children) foreach (IUniverseObject child in universeObject.Children)
TraverseChildrenForBehaviour(child, behaviours, cache); TraverseChildrenForBehaviour(child, behaviours);
} }
} }

View File

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

View File

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

View File

@@ -16,6 +16,21 @@ public static class UniverseObjectExtensions
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.

View File

@@ -5,6 +5,12 @@ using Engine.Core.Debug;
namespace Engine.Core; 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> /// <summary>
/// Represents a simple event with no parameters. /// Represents a simple event with no parameters.
/// <para>Example usage:</para> /// <para>Example usage:</para>

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

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

View File

@@ -10,22 +10,25 @@ public class Pool<T> : IPool<T>
private readonly Func<T> generator = null!; private readonly Func<T> generator = null!;
private readonly Queue<T> queue = new(); private readonly Queue<T> queue = new();
private readonly HashSet<T> queuedHashes = [];
public T Get() public T Get()
{ {
if (!queue.TryDequeue(out T? result)) if (!queue.TryDequeue(out T? result))
result = generator(); result = generator();
queuedHashes.Remove(result);
OnRemoved?.Invoke(this, result); OnRemoved?.Invoke(this, result);
return result; return result;
} }
public void Return(T item) public void Return(T item)
{ {
if (queue.Contains(item)) if (queuedHashes.Contains(item))
return; return;
queue.Enqueue(item); queue.Enqueue(item);
queuedHashes.Add(item);
OnReturned?.Invoke(this, item); OnReturned?.Invoke(this, item);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
using System;
using System.Diagnostics; using System.Diagnostics;
namespace Engine.Core; namespace Engine.Core;
@@ -11,10 +12,10 @@ namespace 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;
@@ -41,6 +42,8 @@ public readonly struct Circle(Vector2D center, float radius)
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 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>
@@ -87,6 +90,7 @@ public readonly struct Circle(Vector2D center, float radius)
/// <param name="obj">The object to compare with the current <see cref="Circle"/>.</param> /// <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> /// <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 override bool Equals(object? obj) => obj is Circle circle && this == circle;
public bool Equals(Circle other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Circle"/>. /// Generates a hash code for the <see cref="Circle"/>.
@@ -116,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);

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -10,7 +12,7 @@ namespace 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"/>.
@@ -112,6 +114,7 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
/// <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 colorHSV && this == colorHSV; 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"/>.

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -11,7 +13,7 @@ namespace Engine.Core;
/// Initializes a new instance of the <see cref="ColorHSVA"/> struct with the specified values. /// Initializes a new instance of the <see cref="ColorHSVA"/> struct with the specified values.
/// </remarks> /// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")] [System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct ColorHSVA(float hue, float saturation, float value, float alpha = 1) public readonly struct ColorHSVA(float hue, float saturation, float value, float alpha = 1) : IEquatable<ColorHSVA>
{ {
/// <summary> /// <summary>
/// The Hue value of the <see cref="ColorHSVA"/>. /// The Hue value of the <see cref="ColorHSVA"/>.
@@ -150,6 +152,7 @@ public readonly struct ColorHSVA(float hue, float saturation, float value, float
/// <param name="obj">The object to compare with the current <see cref="ColorHSVA"/>.</param> /// <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> /// <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 override bool Equals(object? obj) => obj is ColorHSVA colorHSVA && this == colorHSVA;
public bool Equals(ColorHSVA other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="ColorHSVA"/>. /// Generates a hash code for the <see cref="ColorHSVA"/>.

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -10,7 +12,7 @@ namespace 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"/>.
@@ -102,6 +104,7 @@ public readonly struct ColorRGB(byte r, byte g, byte b)
/// <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 colorRGB && this == colorRGB; 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"/>.

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -11,7 +13,7 @@ namespace 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"/>.
@@ -132,6 +134,7 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
/// <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 colorRGBA && this == colorRGBA; 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"/>.

View File

@@ -1,3 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace Engine.Core; namespace Engine.Core;
@@ -11,7 +12,7 @@ namespace 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.
@@ -49,15 +50,7 @@ public readonly struct Line2D(Vector2D from, Vector2D 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"/>.
@@ -196,6 +189,7 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
/// <param name="obj">The object to compare with the current <see cref="Line2D"/>.</param> /// <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> /// <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 override bool Equals(object? obj) => obj is Line2D line2D && this == line2D;
public bool Equals(Line2D other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Line2D"/>. /// Generates a hash code for the <see cref="Line2D"/>.

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -9,7 +11,7 @@ namespace Engine.Core;
/// 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 <see cref="Line2DEquation"/>. /// The slope of the <see cref="Line2DEquation"/>.
@@ -24,6 +26,16 @@ public readonly struct Line2DEquation(float slope, float offsetY)
public static bool operator ==(Line2DEquation left, Line2DEquation right) => left.Slope == right.Slope && left.OffsetY == right.OffsetY; public static bool operator ==(Line2DEquation left, Line2DEquation right) => left.Slope == right.Slope && left.OffsetY == right.OffsetY;
public static bool operator !=(Line2DEquation left, Line2DEquation right) => left.Slope != right.Slope || left.OffsetY != right.OffsetY; public static bool operator !=(Line2DEquation left, Line2DEquation right) => left.Slope != right.Slope || left.OffsetY != right.OffsetY;
public static implicit operator Line2DEquation(Line2D line)
{
Vector2D slopeVector = line.From.FromTo(line.To);
float slope = slopeVector.Y / slopeVector.X;
float yOffset = line.From.Y - (slope * line.From.X);
return new Line2DEquation(slope, yOffset);
}
/// <summary> /// <summary>
/// Resolves the Y coordinate for a given X coordinate using the <see cref="Line2DEquation"/>. /// Resolves the Y coordinate for a given X coordinate using the <see cref="Line2DEquation"/>.
/// </summary> /// </summary>
@@ -48,6 +60,7 @@ public readonly struct Line2DEquation(float slope, float offsetY)
/// <param name="obj">The object to compare with the current <see cref="Line2DEquation"/>.</param> /// <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> /// <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 override bool Equals(object? obj) => obj is Line2DEquation lineEquation && this == lineEquation;
public bool Equals(Line2DEquation other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Line2DEquation"/>. /// Generates a hash code for the <see cref="Line2DEquation"/>.

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -9,7 +11,7 @@ namespace 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.
@@ -90,6 +92,7 @@ public readonly struct Projection1D(float min, float max)
/// <param name="obj">The object to compare with the current <see cref="Projection1D"/>.</param> /// <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> /// <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 override bool Equals(object? obj) => obj is Projection1D projection1D && this == projection1D;
public bool Equals(Projection1D other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Projection1D"/>. /// Generates a hash code for the <see cref="Projection1D"/>.

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -11,7 +13,7 @@ namespace 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>
@@ -288,18 +393,19 @@ public readonly struct Quaternion(float x, float y, float z, float w)
/// <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 quaternion && this == quaternion; 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(W, X, Y, Z); public override int GetHashCode() => System.HashCode.Combine(X, Y, Z, W);
/// <summary> /// <summary>
/// Converts the <see cref="Quaternion"/> to its string representation. /// Converts the <see cref="Quaternion"/> to its string representation.
/// </summary> /// </summary>
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns> /// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
public override string ToString() => $"{nameof(Quaternion)}({W}, {X}, {Y}, {Z})"; 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);

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -5,7 +7,7 @@ namespace Engine.Core;
/// </summary> /// </summary>
/// <param name="Origin">The <see cref="Vector2D"/> in 2D space where the ray starts from.</param> /// <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> /// <param name="Direction">Normalized <see cref="Vector2D"/> indicating the ray's is direction.</param>
public readonly struct Ray2D(Vector2D Origin, Vector2D Direction) public readonly struct Ray2D(Vector2D Origin, Vector2D Direction) : IEquatable<Ray2D>
{ {
/// <summary> /// <summary>
/// The starting point of the <see cref="Ray2D"/>. /// The starting point of the <see cref="Ray2D"/>.
@@ -72,6 +74,7 @@ public readonly struct Ray2D(Vector2D Origin, Vector2D Direction)
/// <param name="obj">The object to compare with the current <see cref="Ray2D"/>.</param> /// <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> /// <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 override bool Equals(object? obj) => obj is Ray2D ray2D && this == ray2D;
public bool Equals(Ray2D other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Ray2D"/>. /// Generates a hash code for the <see cref="Ray2D"/>.

View File

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

View File

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

View File

@@ -1,7 +1,9 @@
using System;
namespace Engine.Core; namespace Engine.Core;
[System.Diagnostics.DebuggerDisplay("A: {A.ToString(), nq}, B: {B.ToString(), nq}, B: {C.ToString(), nq}")] [System.Diagnostics.DebuggerDisplay("A: {A.ToString(), nq}, B: {B.ToString(), nq}, B: {C.ToString(), nq}")]
public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C) public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C) : IEquatable<Triangle>
{ {
public readonly Vector2D A { get; init; } = A; public readonly Vector2D A { get; init; } = A;
public readonly Vector2D B { get; init; } = B; public readonly Vector2D B { get; init; } = B;
@@ -54,6 +56,7 @@ public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C)
/// <param name="obj">The object to compare with the current <see cref="Triangle"/>.</param> /// <param name="obj">The object to compare with the current <see cref="Triangle"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Triangle"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Triangle"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Triangle triangle && this == triangle; public override bool Equals(object? obj) => obj is Triangle triangle && this == triangle;
public bool Equals(Triangle other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Triangle"/>. /// Generates a hash code for the <see cref="Triangle"/>.

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -9,7 +11,7 @@ namespace Engine.Core;
/// Initializes a new instance of the <see cref="Vector2D"/> struct with the specified positions. /// Initializes a new instance of the <see cref="Vector2D"/> 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 Vector2D(float x, float y) public readonly struct Vector2D(float x, float y) : IEquatable<Vector2D>
{ {
/// <summary> /// <summary>
/// The X coordinate of the <see cref="Vector2D"/>. /// The X coordinate of the <see cref="Vector2D"/>.
@@ -82,6 +84,7 @@ public readonly struct Vector2D(float x, float y)
public static implicit operator System.Numerics.Vector2(Vector2D vector) => new(vector.X, vector.Y); public static implicit operator System.Numerics.Vector2(Vector2D vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector2DInt vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector3D vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(Vector3D vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y); public static implicit operator Vector2D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y);
@@ -308,6 +311,7 @@ public readonly struct Vector2D(float x, float y)
/// <param name="obj">The object to compare with the current <see cref="Vector2D"/>.</param> /// <param name="obj">The object to compare with the current <see cref="Vector2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector2D"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector2D vector2D && this == vector2D; public override bool Equals(object? obj) => obj is Vector2D vector2D && this == vector2D;
public bool Equals(Vector2D other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Vector2D"/>. /// Generates a hash code for the <see cref="Vector2D"/>.

View File

@@ -0,0 +1,269 @@
using System;
namespace Engine.Core;
/// <summary>
/// Represents a two-dimensional integer vector.
/// </summary>
/// <param name="x">X position of the <see cref="Vector2DInt"/>.</param>
/// <param name="y">Y position of the <see cref="Vector2DInt"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Vector2DInt"/> struct with the specified positions.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct Vector2DInt(int x, int y) : IEquatable<Vector2DInt>
{
/// <summary>
/// The X coordinate of the <see cref="Vector2DInt"/>.
/// </summary>
public readonly int X = x;
/// <summary>
/// The Y coordinate of the <see cref="Vector2DInt"/>.
/// </summary>
public readonly int Y = y;
/// <summary>
/// The magnitude (length) of the <see cref="Vector2DInt"/>.
/// </summary>
public float Magnitude => Length(this);
/// <summary>
/// The squared magnitude (length) of the <see cref="Vector2DInt"/>.
/// </summary>
public float MagnitudeSquared => LengthSquared(this);
/// <summary>
/// Gets a <see cref="Vector2DInt"/> with the direction reversed.
/// </summary>
public readonly Vector2DInt Reversed => -this;
/// <summary>
/// Represents the unit <see cref="Vector2DInt"/> pointing upwards.
/// </summary>
public readonly static Vector2DInt Up = new(0, 1);
/// <summary>
/// Represents the unit <see cref="Vector2DInt"/> pointing downwards.
/// </summary>
public readonly static Vector2DInt Down = new(0, -1);
/// <summary>
/// Represents the unit <see cref="Vector2DInt"/> pointing leftwards.
/// </summary>
public readonly static Vector2DInt Left = new(-1, 0);
/// <summary>
/// Represents the unit <see cref="Vector2DInt"/> pointing rightwards.
/// </summary>
public readonly static Vector2DInt Right = new(1, 0);
/// <summary>
/// Represents the zero <see cref="Vector2DInt"/>.
/// </summary>
public readonly static Vector2DInt Zero = new(0, 0);
/// <summary>
/// Represents the <see cref="Vector2DInt"/> with both components equal to 1.
/// </summary>
public readonly static Vector2DInt One = new(1, 1);
public static Vector2DInt operator -(Vector2DInt vector) => new(0 - vector.X, 0 - vector.Y);
public static Vector2DInt operator +(Vector2DInt left, Vector2DInt right) => new(left.X + right.X, left.Y + right.Y);
public static Vector2DInt operator -(Vector2DInt left, Vector2DInt right) => new(left.X - right.X, left.Y - right.Y);
public static bool operator ==(Vector2DInt left, Vector2DInt right) => left.X == right.X && left.Y == right.Y;
public static bool operator !=(Vector2DInt left, Vector2DInt right) => left.X != right.X || left.Y != right.Y;
public static implicit operator Vector2DInt(Vector2D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt());
public static implicit operator Vector2DInt(Vector3DInt vector) => new(vector.X, vector.Y);
/// <summary>
/// Calculates the length of the <see cref="Vector2DInt"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2DInt"/>.</param>
/// <returns>The length of the <see cref="Vector2DInt"/>.</returns>
public static float Length(Vector2DInt vector) => Engine.Core.Math.Sqrt(LengthSquared(vector));
/// <summary>
/// Calculates the squared length of the <see cref="Vector2DInt"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2DInt"/>.</param>
/// <returns>The squared length of the <see cref="Vector2DInt"/>.</returns>
public static float LengthSquared(Vector2DInt vector) => vector.X * vector.X + vector.Y * vector.Y;
/// <summary>
/// Calculates the distance between two <see cref="Vector2DInt"/>s.
/// </summary>
/// <param name="from">The start <see cref="Vector2DInt"/>.</param>
/// <param name="to">The end <see cref="Vector2DInt"/>.</param>
/// <returns>The distance between the two <see cref="Vector2DInt"/>s.</returns>
public static float Distance(Vector2DInt from, Vector2DInt to) => Length(FromTo(from, to));
/// <summary>
/// Inverts the direction of the <see cref="Vector2DInt"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2DInt"/>.</param>
/// <returns>The inverted <see cref="Vector2DInt"/>.</returns>
public static Vector2DInt Invert(Vector2DInt vector) => -vector;
/// <summary>
/// Adds two <see cref="Vector2DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2DInt"/>.</param>
/// <param name="right">The second <see cref="Vector2DInt"/>.</param>
/// <returns>The sum of the two <see cref="Vector2DInt"/>s.</returns>
public static Vector2DInt Add(Vector2DInt left, Vector2DInt right) => left + right;
/// <summary>
/// Subtracts one <see cref="Vector2DInt"/> from another.
/// </summary>
/// <param name="left">The <see cref="Vector2DInt"/> to subtract from.</param>
/// <param name="right">The <see cref="Vector2DInt"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="Vector2DInt"/> from the first.</returns>
public static Vector2DInt Subtract(Vector2DInt left, Vector2DInt right) => left - right;
/// <summary>
/// Calculates the absolute value of each component of the vector.
/// </summary>
/// <param name="vector">The <see cref="Vector2DInt"/>.</param>
/// <returns>The <see cref="Vector2DInt"/> with each component's absolute value.</returns>
public static Vector2DInt Abs(Vector2DInt vector) => new(Engine.Core.Math.Abs(vector.X), Engine.Core.Math.Abs(vector.Y));
/// <summary>
/// Calculates the <see cref="Vector2DInt"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="Vector2DInt"/> from the starting point to the ending point.</returns>
public static Vector2DInt FromTo(Vector2DInt from, Vector2DInt to) => to - from;
/// <summary>
/// Scales a <see cref="Vector2DInt"/> by another <see cref="Vector2DInt"/> component-wise.
/// </summary>
/// <param name="vector">The <see cref="Vector2DInt"/> to scale.</param>
/// <param name="scale">The <see cref="Vector2DInt"/> containing the scaling factors for each component.</param>
/// <returns>The scaled <see cref="Vector2DInt"/>.</returns>
public static Vector2DInt Scale(Vector2DInt vector, Vector2DInt scale) => new(vector.X * scale.X, vector.Y * scale.Y);
/// <summary>
/// Calculates a perpendicular <see cref="Vector2DInt"/> to the given <see cref="Vector2DInt"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2DInt"/>.</param>
/// <returns>A <see cref="Vector2DInt"/> perpendicular to the input <see cref="Vector2DInt"/>.</returns>
public static Vector2DInt Perpendicular(Vector2DInt vector) => new(-vector.Y, vector.X);
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector2DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2DInt"/>.</param>
/// <param name="right">The second <see cref="Vector2DInt"/>.</param>
/// <returns>The <see cref="Vector2DInt"/> containing the minimum components from both input <see cref="Vector2DInt"/>s.</returns>
public static Vector2DInt Min(Vector2DInt left, Vector2DInt right) => new((left.X < right.X) ? left.X : right.X, (left.Y < right.Y) ? left.Y : right.Y);
/// <summary>
/// Returns the component-wise maximum of two <see cref="Vector2DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2DInt"/>.</param>
/// <param name="right">The second <see cref="Vector2DInt"/>.</param>
/// <returns>The <see cref="Vector2DInt"/> containing the maximum components from both input <see cref="Vector2DInt"/>s.</returns>
public static Vector2DInt Max(Vector2DInt left, Vector2DInt right) => new((left.X > right.X) ? left.X : right.X, (left.Y > right.Y) ? left.Y : right.Y);
/// <summary>
/// Clamps each component of a <see cref="Vector2DInt"/> between the corresponding component of two other <see cref="Vector2DInt"/>s.
/// </summary>
/// <param name="vector">The <see cref="Vector2DInt"/> to clamp.</param>
/// <param name="min">The <see cref="Vector2DInt"/> representing the minimum values for each component.</param>
/// <param name="max">The <see cref="Vector2DInt"/> representing the maximum values for each component.</param>
/// <returns>A <see cref="Vector2DInt"/> with each component clamped between the corresponding components of the min and max <see cref="Vector2DInt"/>s.</returns>
public static Vector2DInt Clamp(Vector2DInt vector, Vector2DInt min, Vector2DInt max) => new(Engine.Core.Math.Clamp(vector.X, min.X, max.X), Engine.Core.Math.Clamp(vector.Y, min.Y, max.Y));
/// <summary>
/// Calculates the cross product of two <see cref="Vector2DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2DInt"/>.</param>
/// <param name="right">The second <see cref="Vector2DInt"/>.</param>
/// <returns>The cross product of the two <see cref="Vector2DInt"/>s.</returns>
public static int Cross(Vector2DInt left, Vector2DInt right) => left.X * right.Y - left.Y * right.X;
/// <summary>
/// Calculates the dot product of two <see cref="Vector2DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2DInt"/>.</param>
/// <param name="right">The second <see cref="Vector2DInt"/>.</param>
/// <returns>The dot product of the two <see cref="Vector2DInt"/>s.</returns>
public static int Dot(Vector2DInt left, Vector2DInt right) => left.X * right.X + left.Y * right.Y;
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector2DInt"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector2DInt"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector2DInt"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector2DInt vector2DInt && this == vector2DInt;
public bool Equals(Vector2DInt other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="Vector2DInt"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Vector2DInt"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y);
/// <summary>
/// Converts the <see cref="Vector2DInt"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector2DInt"/>.</returns>
public override string ToString() => $"{nameof(Vector2DInt)}({X}, {Y})";
}
/// <summary>
/// Provides extension methods for <see cref="Vector2DInt"/> type.
/// </summary>
public static class Vector2DIntExtensions
{
/// <inheritdoc cref="Vector2DInt.Length(Vector2DInt)" />
public static float Length(this Vector2DInt vector) => Vector2DInt.Length(vector);
/// <inheritdoc cref="Vector2DInt.LengthSquared(this vector) => Vector2DInt/>
public static float LengthSquared(this Vector2DInt vector) => Vector2DInt.LengthSquared(vector);
/// <inheritdoc cref="Vector2DInt.Distance(Vector2DInt, Vector2DInt)" />
public static float Distance(this Vector2DInt from, Vector2DInt to) => Vector2DInt.Distance(from, to);
/// <inheritdoc cref="Vector2DInt.Invert(this vector) => Vector2DInt/>
public static Vector2DInt Invert(this Vector2DInt vector) => Vector2DInt.Invert(vector);
/// <inheritdoc cref="Vector2DInt.Add(Vector2DInt, Vector2DInt)" />
public static Vector2DInt Add(this Vector2DInt vector, Vector2DInt vectorToAdd) => Vector2DInt.Add(vector, vectorToAdd);
/// <inheritdoc cref="Vector2DInt.Subtract(Vector2DInt, Vector2DInt)" />
public static Vector2DInt Subtract(this Vector2DInt vector, Vector2DInt vectorToSubtract) => Vector2DInt.Subtract(vector, vectorToSubtract);
/// <inheritdoc cref="Vector2DInt.Abs(Vector2DInt)" />
public static Vector2DInt Abs(this Vector2DInt vector) => Vector2DInt.Abs(vector);
/// <inheritdoc cref="Vector2DInt.FromTo(Vector2DInt, Vector2DInt)" />
public static Vector2DInt FromTo(this Vector2DInt from, Vector2DInt to) => Vector2DInt.FromTo(from, to);
/// <inheritdoc cref="Vector2DInt.Scale(Vector2DInt, Vector2DInt)" />
public static Vector2DInt Scale(this Vector2DInt vector, Vector2DInt scale) => Vector2DInt.Scale(vector, scale);
/// <inheritdoc cref="Vector2DInt.Perpendicular(Vector2DInt)" />
public static Vector2DInt Perpendicular(this Vector2DInt vector) => Vector2DInt.Perpendicular(vector);
/// <inheritdoc cref="Vector2DInt.Min(Vector2DInt, Vector2DInt)" />
public static Vector2DInt Min(this Vector2DInt left, Vector2DInt right) => Vector2DInt.Min(left, right);
/// <inheritdoc cref="Vector2DInt.Max(Vector2DInt, Vector2DInt)" />
public static Vector2DInt Max(this Vector2DInt left, Vector2DInt right) => Vector2DInt.Max(left, right);
/// <inheritdoc cref="Vector2DInt.Clamp(Vector2DInt, Vector2DInt,Vector2DInt)" />
public static Vector2DInt Clamp(this Vector2DInt vector, Vector2DInt min, Vector2DInt max) => Vector2DInt.Clamp(vector, min, max);
/// <inheritdoc cref="Vector2DInt.Cross(Vector2DInt, Vector2DInt)" />
public static int Cross(this Vector2DInt left, Vector2DInt right) => Vector2DInt.Cross(left, right);
/// <inheritdoc cref="Vector2D.Angle(Vector2D, Vector2D)" />
public static float AngleBetween(this Vector2D left, Vector2D right) => Vector2D.Angle(left, right);
/// <inheritdoc cref="Vector2DInt.Dot(Vector2DInt, Vector2DInt)" />
public static int Dot(this Vector2DInt left, Vector2DInt right) => Vector2DInt.Dot(left, right);
}

View File

@@ -1,3 +1,5 @@
using System;
namespace Engine.Core; namespace Engine.Core;
/// <summary> /// <summary>
@@ -10,7 +12,7 @@ namespace Engine.Core;
/// Initializes a new instance of the <see cref="Vector3D"/> struct with the specified positions. /// Initializes a new instance of the <see cref="Vector3D"/> 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 Vector3D(float x, float y, float z) public readonly struct Vector3D(float x, float y, float z) : IEquatable<Vector3D>
{ {
/// <summary> /// <summary>
/// The X coordinate of the <see cref="Vector3D"/>. /// The X coordinate of the <see cref="Vector3D"/>.
@@ -92,6 +94,7 @@ public readonly struct Vector3D(float x, float y, float z)
public static implicit operator System.Numerics.Vector3(Vector3D vector) => new(vector.X, vector.Y, vector.Z); public static implicit operator System.Numerics.Vector3(Vector3D vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y, vector.Z); public static implicit operator Vector3D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(Vector3DInt vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(Vector2D vector) => new(vector.X, vector.Y, 0f); public static implicit operator Vector3D(Vector2D vector) => new(vector.X, vector.Y, 0f);
public static implicit operator Vector3D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y, 0f); public static implicit operator Vector3D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y, 0f);
@@ -195,13 +198,13 @@ public readonly struct Vector3D(float x, float y, float z)
public static Vector3D Scale(Vector3D vector, Vector3D scale) => new(vector.X * scale.X, vector.Y * scale.Y, vector.Z * scale.Z); public static Vector3D Scale(Vector3D vector, Vector3D scale) => new(vector.X * scale.X, vector.Y * scale.Y, vector.Z * scale.Z);
/// <summary> /// <summary>
/// Rotates a <see cref="Vector3D"/> around a normal by the specified angle (in radians). /// Rotates a <see cref="Vector3D"/> around a axis by the specified angle (in radians).
/// </summary> /// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to rotate.</param> /// <param name="vector">The <see cref="Vector3D"/> to rotate.</param>
/// <param name="normal">The <see cref="Vector3D"/> to rotate around.</param> /// <param name="axis">The <see cref="Vector3D"/> to rotate around.</param>
/// <param name="angleInRadian">The angle to rotate by, in radians.</param> /// <param name="angleInRadian">The angle to rotate by, in radians.</param>
/// <returns>The rotated <see cref="Vector3D"/>.</returns> /// <returns>The rotated <see cref="Vector3D"/>.</returns>
public static Vector3D Rotate(Vector3D vector, Vector3D normal, float angleInRadian) => vector * Math.Cos(angleInRadian) + Cross(normal, vector) * Math.Sin(angleInRadian) + normal * Dot(normal, vector) * (1f - Math.Cos(angleInRadian)); public static Vector3D Rotate(Vector3D vector, Vector3D axis, float angleInRadian) => vector * Math.Cos(angleInRadian) + Cross(axis, vector) * Math.Sin(angleInRadian) + axis * Dot(axis, vector) * (1f - Math.Cos(angleInRadian));
/// <summary> /// <summary>
/// Returns the component-wise minimum of two <see cref="Vector3D"/>s. /// Returns the component-wise minimum of two <see cref="Vector3D"/>s.
@@ -261,6 +264,15 @@ public readonly struct Vector3D(float x, float y, float z)
/// <returns>The dot product of the two <see cref="Vector3D"/>s.</returns> /// <returns>The dot product of the two <see cref="Vector3D"/>s.</returns>
public static float Dot(Vector3D left, Vector3D right) => left.X * right.X + left.Y * right.Y + left.Z * right.Z; public static float Dot(Vector3D left, Vector3D right) => left.X * right.X + left.Y * right.Y + left.Z * right.Z;
/// <summary>
/// Transforms the <see cref="Vector3D"/> using the specified <see cref="ITransform3D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to transform.</param>
/// <param name="transform">The <see cref="ITransform3D"/> to apply.</param>
/// <returns>The transformed <see cref="Vector3D"/>.</returns>
public static Vector3D Transform(Vector3D vector, ITransform3D transform)
=> Quaternion.RotateVector(vector, transform.Rotation).Add(transform.Position).Scale(transform.Scale);
/// <summary> /// <summary>
/// Checks if two <see cref="Vector3D"/>s are approximately equal within a specified epsilon range. /// Checks if two <see cref="Vector3D"/>s are approximately equal within a specified epsilon range.
/// </summary> /// </summary>
@@ -277,6 +289,7 @@ public readonly struct Vector3D(float x, float y, float z)
/// <param name="obj">The object to compare with the current <see cref="Vector3D"/>.</param> /// <param name="obj">The object to compare with the current <see cref="Vector3D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector3D"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector3D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector3D vector3D && this == vector3D; public override bool Equals(object? obj) => obj is Vector3D vector3D && this == vector3D;
public bool Equals(Vector3D other) => this == other;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Vector3D"/>. /// Generates a hash code for the <see cref="Vector3D"/>.
@@ -359,6 +372,12 @@ public static class Vector3DExtensions
/// <inheritdoc cref="Vector3D.Dot(Vector3D, Vector3D)" /> /// <inheritdoc cref="Vector3D.Dot(Vector3D, Vector3D)" />
public static float Dot(this Vector3D left, Vector3D right) => Vector3D.Dot(left, right); public static float Dot(this Vector3D left, Vector3D right) => Vector3D.Dot(left, right);
/// <inheritdoc cref="Vector3D.Transform(Vector3D, ITransform3D)" />
public static Vector3D Transform(this Vector3D vector, ITransform3D transform) => Vector3D.Transform(vector, transform);
/// <inheritdoc cref="Vector3D.Transform(Vector3D, ITransform3D)" />
public static Vector3D Transform(this ITransform3D transform, Vector3D vector) => Vector3D.Transform(vector, transform);
/// <inheritdoc cref="Vector3D.ApproximatelyEquals(Vector3D, Vector3D, float)" /> /// <inheritdoc cref="Vector3D.ApproximatelyEquals(Vector3D, Vector3D, float)" />
public static bool ApproximatelyEquals(this Vector3D left, Vector3D right, float epsilon = float.Epsilon) => Vector3D.ApproximatelyEquals(left, right, epsilon); public static bool ApproximatelyEquals(this Vector3D left, Vector3D right, float epsilon = float.Epsilon) => Vector3D.ApproximatelyEquals(left, right, epsilon);
} }

View File

@@ -0,0 +1,277 @@
using System;
namespace Engine.Core;
/// <summary>
/// Represents a three-dimensional integer vector.
/// </summary>
/// <param name="x">X position of the <see cref="Vector3DInt"/>.</param>
/// <param name="y">Y position of the <see cref="Vector3DInt"/>.</param>
/// <param name="z">Z position of the <see cref="Vector3DInt"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Vector3DInt"/> struct with the specified positions.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct Vector3DInt(int x, int y, int z) : IEquatable<Vector3DInt>
{
/// <summary>
/// The X coordinate of the <see cref="Vector3DInt"/>.
/// </summary>
public readonly int X = x;
/// <summary>
/// The Y coordinate of the <see cref="Vector3DInt"/>.
/// </summary>
public readonly int Y = y;
/// <summary>
/// The Z coordinate of the <see cref="Vector3DInt"/>.
/// </summary>
public readonly int Z = z;
/// <summary>
/// The magnitude (length) of the <see cref="Vector3DInt"/>.
/// </summary>
public float Magnitude => Length(this);
/// <summary>
/// The squared magnitude (length) of the <see cref="Vector3DInt"/>.
/// </summary>
public float MagnitudeSquared => LengthSquared(this);
/// <summary>
/// Represents the unit <see cref="Vector3DInt"/> pointing upwards.
/// </summary>
public readonly static Vector3DInt Up = new(0, 1, 0);
/// <summary>
/// Represents the unit <see cref="Vector3DInt"/> pointing downwards.
/// </summary>
public readonly static Vector3DInt Down = new(0, -1, 0);
/// <summary>
/// Represents the unit <see cref="Vector3DInt"/> pointing leftwards.
/// </summary>
public readonly static Vector3DInt Left = new(-1, 0, 0);
/// <summary>
/// Represents the unit <see cref="Vector3DInt"/> pointing rightwards.
/// </summary>
public readonly static Vector3DInt Right = new(1, 0, 0);
/// <summary>
/// Represents the unit <see cref="Vector3DInt"/> pointing forwards.
/// </summary>
public readonly static Vector3DInt Forward = new(0, 0, 1);
/// <summary>
/// Represents the unit <see cref="Vector3DInt"/> pointing backwards.
public readonly static Vector3DInt Backward = new(0, 0, -1);
/// <summary>
/// Represents the zero <see cref="Vector3DInt"/>.
/// </summary>
public readonly static Vector3DInt Zero = new(0, 0, 0);
/// <summary>
/// Represents the <see cref="Vector3DInt"/> with both components equal to 1.
/// </summary>
public readonly static Vector3DInt One = new(1, 1, 1);
public static Vector3DInt operator -(Vector3DInt vector) => new(0 - vector.X, 0 - vector.Y, 0 - vector.Z);
public static Vector3DInt operator +(Vector3DInt left, Vector3DInt right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
public static Vector3DInt operator -(Vector3DInt left, Vector3DInt right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
public static bool operator ==(Vector3DInt left, Vector3DInt right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z;
public static bool operator !=(Vector3DInt left, Vector3DInt right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z;
public static implicit operator Vector3DInt(Vector3D vector) => new(vector.X.RoundToInt(), vector.Y.RoundToInt(), vector.Z.RoundToInt());
public static implicit operator Vector3DInt(Vector2DInt vector) => new(vector.X, vector.Y, 0);
/// <summary>
/// Calculates the length of the <see cref="Vector3DInt"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3DInt"/>.</param>
/// <returns>The length of the <see cref="Vector3DInt"/>.</returns>
public static float Length(Vector3DInt vector) => Math.Sqrt(LengthSquared(vector));
/// <summary>
/// Calculates the squared length of the <see cref="Vector3DInt"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3DInt"/>.</param>
/// <returns>The squared length of the <see cref="Vector3DInt"/>.</returns>
public static float LengthSquared(Vector3DInt vector) => vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z;
/// <summary>
/// Calculates the distance between two <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="from">The start <see cref="Vector3DInt"/>.</param>
/// <param name="to">The end <see cref="Vector3DInt"/>.</param>
/// <returns>The distance between the two <see cref="Vector3DInt"/>s.</returns>
public static float Distance(Vector3DInt from, Vector3DInt to) => Length(FromTo(from, to));
/// <summary>
/// Inverts the direction of the <see cref="Vector3DInt"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3DInt"/>.</param>
/// <returns>The inverted <see cref="Vector3DInt"/>.</returns>
public static Vector3DInt Invert(Vector3DInt vector) => -vector;
/// <summary>
/// Adds two <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3DInt"/>.</param>
/// <param name="right">The second <see cref="Vector3DInt"/>.</param>
/// <returns>The sum of the two <see cref="Vector3DInt"/>s.</returns>
public static Vector3DInt Add(Vector3DInt left, Vector3DInt right) => left + right;
/// <summary>
/// Subtracts one <see cref="Vector3DInt"/> from another.
/// </summary>
/// <param name="left">The <see cref="Vector3DInt"/> to subtract from.</param>
/// <param name="right">The <see cref="Vector3DInt"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="Vector3DInt"/> from the first.</returns>
public static Vector3DInt Subtract(Vector3DInt left, Vector3DInt right) => left - right;
/// <summary>
/// Calculates the absolute value of each component of the vector.
/// </summary>
/// <param name="vector">The <see cref="Vector3DInt"/>.</param>
/// <returns>The <see cref="Vector3DInt"/> with each component's absolute value.</returns>
public static Vector3DInt Abs(Vector3DInt vector) => new(Math.Abs(vector.X), Math.Abs(vector.Y), Math.Abs(vector.Z));
/// <summary>
/// Calculates the <see cref="Vector3DInt"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="Vector3DInt"/> from the starting point to the ending point.</returns>
public static Vector3DInt FromTo(Vector3DInt from, Vector3DInt to) => to - from;
/// <summary>
/// Scales a <see cref="Vector3DInt"/> by another <see cref="Vector3DInt"/> component-wise.
/// </summary>
/// <param name="vector">The <see cref="Vector3DInt"/> to scale.</param>
/// <param name="scale">The <see cref="Vector3DInt"/> containing the scaling factors for each component.</param>
/// <returns>The scaled <see cref="Vector3DInt"/>.</returns>
public static Vector3DInt Scale(Vector3DInt vector, Vector3DInt scale) => new(vector.X * scale.X, vector.Y * scale.Y, vector.Z * scale.Z);
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3DInt"/>.</param>
/// <param name="right">The second <see cref="Vector3DInt"/>.</param>
/// <returns>The <see cref="Vector3DInt"/> containing the minimum components from both input <see cref="Vector3DInt"/>s.</returns>
public static Vector3DInt Min(Vector3DInt left, Vector3DInt right) => new((left.X < right.X) ? left.X : right.X, (left.Y < right.Y) ? left.Y : right.Y, (left.Z < right.Z) ? left.Z : right.Z);
/// <summary>
/// Returns the component-wise maximum of two <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3DInt"/>.</param>
/// <param name="right">The second <see cref="Vector3DInt"/>.</param>
/// <returns>The <see cref="Vector3DInt"/> containing the maximum components from both input <see cref="Vector3DInt"/>s.</returns>
public static Vector3DInt Max(Vector3DInt left, Vector3DInt right) => new((left.X > right.X) ? left.X : right.X, (left.Y > right.Y) ? left.Y : right.Y, (left.Z > right.Z) ? left.Z : right.Z);
/// <summary>
/// Clamps each component of a <see cref="Vector3DInt"/> between the corresponding component of two other <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="vector">The <see cref="Vector3DInt"/> to clamp.</param>
/// <param name="min">The <see cref="Vector3DInt"/> representing the minimum values for each component.</param>
/// <param name="max">The <see cref="Vector3DInt"/> representing the maximum values for each component.</param>
/// <returns>A <see cref="Vector3DInt"/> with each component clamped between the corresponding components of the min and max <see cref="Vector3DInt"/>s.</returns>
public static Vector3DInt Clamp(Vector3DInt vector, Vector3DInt min, Vector3DInt max) => new(Math.Clamp(vector.X, min.X, max.X), Math.Clamp(vector.Y, min.Y, max.Y), Math.Clamp(vector.Z, min.Z, max.Z));
/// <summary>
/// Calculates the cross product of two <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3DInt"/>.</param>
/// <param name="right">The second <see cref="Vector3DInt"/>.</param>
/// <returns>The cross product of the two <see cref="Vector3DInt"/>s.</returns>
public static Vector3DInt Cross(Vector3DInt left, Vector3DInt right) => new(left.Y * right.Z - left.Z * right.Y, left.Z * right.X - left.X * right.Z, left.X * right.Y - left.Y * right.X);
/// <summary>
/// Calculates the angle between two <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3DInt"/>.</param>
/// <param name="right">The second <see cref="Vector3DInt"/>.</param>
/// <returns>The angle between the two <see cref="Vector3DInt"/>s in radians.</returns>
public static float Angle(Vector3DInt left, Vector3DInt right) => Math.Acos(Dot(left, right) / (Length(left) * Length(right)));
/// <summary>
/// Calculates the dot product of two <see cref="Vector3DInt"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3DInt"/>.</param>
/// <param name="right">The second <see cref="Vector3DInt"/>.</param>
/// <returns>The dot product of the two <see cref="Vector3DInt"/>s.</returns>
public static int Dot(Vector3DInt left, Vector3DInt right) => left.X * right.X + left.Y * right.Y + left.Z * right.Z;
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector3DInt"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector3DInt"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector3DInt"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector3DInt vector3D && this == vector3D;
public bool Equals(Vector3DInt other) => this == other;
/// <summary>
/// Generates a hash code for the <see cref="Vector3DInt"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Vector3DInt"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z);
/// <summary>
/// Converts the <see cref="Vector3DInt"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector3DInt"/>.</returns>
public override string ToString() => $"{nameof(Vector3DInt)}({X}, {Y}, {Z})";
}
/// <summary>
/// Provides extension methods for <see cref="Vector3DInt"/> type.
/// </summary>
public static class Vector3DIntExtensions
{
/// <inheritdoc cref="Vector3DInt.Length(Vector3DInt)" />
public static float Length(this Vector3DInt vector) => Vector3DInt.Length(vector);
/// <inheritdoc cref="Vector3DInt.LengthSquared(Vector3DInt)" />
public static float LengthSquared(this Vector3DInt vector) => Vector3DInt.LengthSquared(vector);
/// <inheritdoc cref="Vector3DInt.Distance(Vector3DInt, Vector3DInt)" />
public static float Distance(this Vector3DInt from, Vector3DInt to) => Vector3DInt.Distance(from, to);
/// <inheritdoc cref="Vector3DInt.Invert(Vector3DInt)" />
public static Vector3DInt Invert(this Vector3DInt vector) => Vector3DInt.Invert(vector);
/// <inheritdoc cref="Vector3DInt.Add(Vector3DInt, Vector3DInt)" />
public static Vector3DInt Add(this Vector3DInt vector, Vector3DInt vectorToAdd) => Vector3DInt.Add(vector, vectorToAdd);
/// <inheritdoc cref="Vector3DInt.Subtract(Vector3DInt, Vector3DInt)" />
public static Vector3DInt Subtract(this Vector3DInt vector, Vector3DInt vectorToSubtract) => Vector3DInt.Subtract(vector, vectorToSubtract);
/// <inheritdoc cref="Vector3DInt.Abs(Vector3DInt)" />
public static Vector3DInt Abs(this Vector3DInt vector) => Vector3DInt.Abs(vector);
/// <inheritdoc cref="Vector3DInt.FromTo(Vector3DInt, Vector3DInt)" />
public static Vector3DInt FromTo(this Vector3DInt from, Vector3DInt to) => Vector3DInt.FromTo(from, to);
/// <inheritdoc cref="Vector3DInt.Scale(Vector3DInt, Vector3DInt)" />
public static Vector3DInt Scale(this Vector3DInt vector, Vector3DInt scale) => Vector3DInt.Scale(vector, scale);
/// <inheritdoc cref="Vector3DInt.Min(Vector3DInt, Vector3DInt)" />
public static Vector3DInt Min(this Vector3DInt left, Vector3DInt right) => Vector3DInt.Min(left, right);
/// <inheritdoc cref="Vector3DInt.Max(Vector3DInt, Vector3DInt)" />
public static Vector3DInt Max(this Vector3DInt left, Vector3DInt right) => Vector3DInt.Max(left, right);
/// <inheritdoc cref="Vector3DInt.Clamp(Vector3DInt, Vector3DInt, Vector3DInt)" />
public static Vector3DInt Clamp(this Vector3DInt vector, Vector3DInt min, Vector3DInt max) => Vector3DInt.Clamp(vector, min, max);
/// <inheritdoc cref="Vector3DInt.Cross(Vector3DInt, Vector3DInt)" />
public static Vector3DInt Cross(this Vector3DInt left, Vector3DInt right) => Vector3DInt.Cross(left, right);
/// <inheritdoc cref="Vector3DInt.Angle(Vector3DInt, Vector3DInt)" />
public static float AngleBetween(this Vector3DInt left, Vector3DInt right) => Vector3DInt.Angle(left, right);
/// <inheritdoc cref="Vector3DInt.Dot(Vector3DInt, Vector3DInt)" />
public static int Dot(this Vector3DInt left, Vector3DInt right) => Vector3DInt.Dot(left, right);
}

View File

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

View File

@@ -1,39 +0,0 @@
using System;
using System.Collections.Generic;
namespace Engine.Core.Serialization;
public class EntityRegistry
{
public Event<EntityRegistry, EntityRegisteredArguments> OnEntityRegistered = null!;
private readonly Dictionary<string, Action<IEntity>?> assignCallbacks = [];
private readonly Dictionary<string, IEntity> registeredEntities = [];
public IReadOnlyDictionary<string, IEntity> RegisteredEntities => registeredEntities;
public void Add(IEntity entity)
{
if (registeredEntities.TryAdd(entity.Id, entity))
OnEntityRegistered?.Invoke(this, new(entity));
}
public void QueueAssign(string id, Action<IEntity> setMethod)
{
assignCallbacks.TryAdd(id, null);
assignCallbacks[id] = assignCallbacks[id] + setMethod;
}
public void AssignAll()
{
foreach ((string id, Action<IEntity>? action) in assignCallbacks)
action?.Invoke(registeredEntities[id]);
}
public void Reset()
{
assignCallbacks.Clear();
registeredEntities.Clear();
}
public readonly record struct EntityRegisteredArguments(IEntity Entity);
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
namespace Engine.Core.Serialization;
public class IdentifiableRegistry
{
public Event<IdentifiableRegistry, EntityRegisteredArguments> OnEntityRegistered = null!;
private readonly Dictionary<string, Action<IIdentifiable>?> assignCallbacks = [];
private readonly Dictionary<string, IIdentifiable> registeredEntities = [];
public IReadOnlyDictionary<string, IIdentifiable> RegisteredEntities => registeredEntities;
public void Add(IIdentifiable identifiable)
{
if (registeredEntities.TryAdd(identifiable.Id, identifiable))
OnEntityRegistered?.Invoke(this, new(identifiable));
}
public void QueueAssign(string id, Action<IIdentifiable> setMethod)
{
assignCallbacks.TryAdd(id, null);
assignCallbacks[id] = assignCallbacks[id] + setMethod;
}
public void AssignAll()
{
foreach ((string id, Action<IIdentifiable>? action) in assignCallbacks)
action?.Invoke(registeredEntities[id]);
}
public void Reset()
{
assignCallbacks.Clear();
registeredEntities.Clear();
}
public readonly record struct EntityRegisteredArguments(IIdentifiable Entity);
}

View File

@@ -43,8 +43,8 @@ public class SerializedClass
continue; continue;
object? value = privatePropertyInfo.GetValue(@class); object? value = privatePropertyInfo.GetValue(@class);
if (value is IEntity entity) if (value is IIdentifiable identifiable)
Private.Add(privatePropertyInfo.Name, entity.Id); Private.Add(privatePropertyInfo.Name, identifiable.Id);
else else
Private.Add(privatePropertyInfo.Name, value); Private.Add(privatePropertyInfo.Name, value);
} }
@@ -61,8 +61,8 @@ public class SerializedClass
continue; continue;
object? value = publicPropertyInfo.GetValue(@class); object? value = publicPropertyInfo.GetValue(@class);
if (value is IEntity entity) if (value is IIdentifiable identifiable)
Public.Add(publicPropertyInfo.Name, entity.Id); Public.Add(publicPropertyInfo.Name, identifiable.Id);
else else
Public.Add(publicPropertyInfo.Name, value); Public.Add(publicPropertyInfo.Name, value);
} }
@@ -76,8 +76,8 @@ public class SerializedClass
continue; continue;
object? value = privateFieldInfo.GetValue(@class); object? value = privateFieldInfo.GetValue(@class);
if (value is IEntity entity) if (value is IIdentifiable identifiable)
Private.Add(privateFieldInfo.Name, entity.Id); Private.Add(privateFieldInfo.Name, identifiable.Id);
else else
Private.Add(privateFieldInfo.Name, value); Private.Add(privateFieldInfo.Name, value);
} }
@@ -91,8 +91,8 @@ public class SerializedClass
continue; continue;
object? value = publicFieldInfo.GetValue(@class); object? value = publicFieldInfo.GetValue(@class);
if (value is IEntity entity) if (value is IIdentifiable identifiable)
Public.Add(publicFieldInfo.Name, entity.Id); Public.Add(publicFieldInfo.Name, identifiable.Id);
else else
Public.Add(publicFieldInfo.Name, value); Public.Add(publicFieldInfo.Name, value);
} }
@@ -112,36 +112,36 @@ public class SerializedClass
return instance; return instance;
} }
public object CreateInstance(EntityRegistry? entityRegistry) public object CreateInstance(IdentifiableRegistry? identifiableRegistry)
{ {
if (entityRegistry is null) if (identifiableRegistry is null)
return CreateInstance(); return CreateInstance();
Type type = TypeFactory.GetType(Type); Type type = TypeFactory.GetType(Type);
object instance = TypeFactory.Get(type); object instance = TypeFactory.Get(type);
foreach ((string key, object? value) in Private) foreach ((string key, object? value) in Private)
AssignVariable(key, type, instance, value, PRIVATE_BINDING_FLAGS, entityRegistry); AssignVariable(key, type, instance, value, PRIVATE_BINDING_FLAGS, identifiableRegistry);
foreach ((string key, object? value) in Public) foreach ((string key, object? value) in Public)
AssignVariable(key, type, instance, value, PUBLIC_BINDING_FLAGS, entityRegistry); AssignVariable(key, type, instance, value, PUBLIC_BINDING_FLAGS, identifiableRegistry);
return instance; return instance;
} }
private static void AssignVariable(string key, Type type, object instance, object? value, BindingFlags bindingFlags, EntityRegistry entityRegistry) private static void AssignVariable(string key, Type type, object instance, object? value, BindingFlags bindingFlags, IdentifiableRegistry identifiableRegistry)
{ {
if (type.GetField(key, bindingFlags) is FieldInfo fieldInfo) if (type.GetField(key, bindingFlags) is FieldInfo fieldInfo)
{ {
if (typeof(IEntity).IsAssignableFrom(fieldInfo.FieldType)) if (typeof(IIdentifiable).IsAssignableFrom(fieldInfo.FieldType))
entityRegistry.QueueAssign(value?.ToString() ?? "", (entity) => fieldInfo.SetValue(instance, entity)); identifiableRegistry.QueueAssign(value?.ToString() ?? "", (entity) => fieldInfo.SetValue(instance, entity));
else else
fieldInfo.SetValue(instance, value); fieldInfo.SetValue(instance, value);
} }
else if (type.GetProperty(key, bindingFlags) is PropertyInfo propertyInfo) else if (type.GetProperty(key, bindingFlags) is PropertyInfo propertyInfo)
{ {
if (typeof(IEntity).IsAssignableFrom(propertyInfo.PropertyType)) if (typeof(IIdentifiable).IsAssignableFrom(propertyInfo.PropertyType))
entityRegistry.QueueAssign(value?.ToString() ?? "", (entity) => propertyInfo.SetValue(instance, entity)); identifiableRegistry.QueueAssign(value?.ToString() ?? "", (entity) => propertyInfo.SetValue(instance, entity));
else else
propertyInfo.SetValue(instance, value); propertyInfo.SetValue(instance, value);
} }

View File

@@ -2,14 +2,15 @@ using System.Collections.Generic;
namespace Engine.Core; namespace Engine.Core;
public class DrawManager : Behaviour public class DrawManager : Behaviour, IEnterUniverse, IExitUniverse
{ {
// We use Descending order because draw calls are running from last to first // We use Descending order because draw calls are running from last to first
private static Comparer<IBehaviour> SortByDescendingPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority)); private static Comparer<int> SortByDescendingPriority() => Comparer<int>.Create((x, y) => y.CompareTo(x));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorSorted<IPreDraw> preDrawEntities = new() { SortBy = SortByDescendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IPreDraw> preDrawEntities = new(GetPriority(), SortByDescendingPriority());
private readonly ActiveBehaviourCollectorSorted<IDraw> drawEntities = new() { SortBy = SortByDescendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IDraw> drawEntities = new(GetPriority(), SortByDescendingPriority());
private readonly ActiveBehaviourCollectorSorted<IPostDraw> postDrawEntities = new() { SortBy = SortByDescendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IPostDraw> postDrawEntities = new(GetPriority(), SortByDescendingPriority());
private void OnPreDraw(IUniverse sender) private void OnPreDraw(IUniverse sender)
{ {
@@ -29,7 +30,7 @@ public class DrawManager : Behaviour
postDrawEntities[i].PostDraw(); postDrawEntities[i].PostDraw();
} }
protected override void OnEnteredUniverse(IUniverse universe) public void EnterUniverse(IUniverse universe)
{ {
preDrawEntities.Assign(universe); preDrawEntities.Assign(universe);
drawEntities.Assign(universe); drawEntities.Assign(universe);
@@ -40,7 +41,7 @@ public class DrawManager : Behaviour
universe.OnPostDraw.AddListener(OnPostDraw); universe.OnPostDraw.AddListener(OnPostDraw);
} }
protected override void OnExitedUniverse(IUniverse universe) public void ExitUniverse(IUniverse universe)
{ {
preDrawEntities.Unassign(); preDrawEntities.Unassign();
drawEntities.Unassign(); drawEntities.Unassign();

View File

@@ -2,23 +2,27 @@ using System.Collections.Generic;
namespace Engine.Core; namespace Engine.Core;
public class UniverseEntranceManager : Behaviour public class UniverseEntranceManager : Internal.BehaviourIndependent
{ {
// We use Ascending order because we are using reverse for loop to call them // We use Ascending order because we are using reverse for loop to call them
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorSorted<IEnterUniverse> enterUniverses = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IEnterUniverse> enterUniverses = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollectorSorted<IExitUniverse> exitUniverses = new() { SortBy = SortByAscendingPriority() };
private readonly List<IEnterUniverse> toCallEnterUniverses = new(32); private readonly List<IEnterUniverse> toCallEnterUniverses = new(32);
private readonly List<IExitUniverse> toCallExitUniverses = new(32); private readonly List<IExitUniverse> toCallExitUniverses = new(32);
protected override void OnEnteredUniverse(IUniverse universe) protected override void OnEnteredUniverse(IUniverse universe)
{ {
// FIXME: This causes an issue when the UniverseEntranceManager is already attached to a UniverseObject then registered into a Universe,
// the enter/exit universe collectors call OnUniverseObjectRegistered internally on Assign, but since the Universe calls the OnUniverseObjectRegistered
// event it tries to call OnUniverseObjectRegistered again on the same object, causing a duplicate entry error.
Debug.Assert.AssertTrue(BehaviourController.Count == 1, $"{nameof(UniverseEntranceManager)} must be in it's own {nameof(IUniverseObject)} with no other {nameof(IBehaviour)}s attached at the moment. Failing to do so might cause instantiation or serialization issues.");
enterUniverses.Assign(universe); enterUniverses.Assign(universe);
foreach (IUniverseObject universeObject in Universe.UniverseObjects) foreach (IUniverseObject universeObject in universe)
OnUniverseObjectRegistered(Universe, new(universeObject)); OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(OnUniverseObjectRegistered); universe.OnUniverseObjectRegistered.AddListener(OnUniverseObjectRegistered);
universe.OnUniverseObjectUnRegistered.AddListener(OnUniverseObjectUnRegistered); universe.OnUniverseObjectUnRegistered.AddListener(OnUniverseObjectUnRegistered);
@@ -28,8 +32,8 @@ public class UniverseEntranceManager : Behaviour
{ {
enterUniverses.Unassign(); enterUniverses.Unassign();
foreach (IUniverseObject universeObject in Universe.UniverseObjects) foreach (IUniverseObject universeObject in universe)
OnUniverseObjectUnRegistered(Universe, new(universeObject)); OnUniverseObjectUnRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.RemoveListener(OnUniverseObjectRegistered); universe.OnUniverseObjectRegistered.RemoveListener(OnUniverseObjectRegistered);
universe.OnUniverseObjectUnRegistered.RemoveListener(OnUniverseObjectUnRegistered); universe.OnUniverseObjectUnRegistered.RemoveListener(OnUniverseObjectUnRegistered);
@@ -37,10 +41,13 @@ public class UniverseEntranceManager : Behaviour
private void OnUniverseObjectUnRegistered(IUniverse sender, IUniverse.UniverseObjectUnRegisteredArguments args) private void OnUniverseObjectUnRegistered(IUniverse sender, IUniverse.UniverseObjectUnRegisteredArguments args)
{ {
args.UniverseObjectUnregistered.BehaviourController.GetBehavioursInChildren(toCallExitUniverses);
for (int i = toCallExitUniverses.Count - 1; i >= 0; i--) for (int i = toCallExitUniverses.Count - 1; i >= 0; i--)
{ {
toCallExitUniverses[i].ExitUniverse(Universe); IExitUniverse exitUniverse = toCallExitUniverses[i];
toCallExitUniverses.RemoveAt(i); toCallExitUniverses.RemoveAt(i);
exitUniverse.ExitUniverse(Universe);
} }
} }
@@ -48,8 +55,9 @@ public class UniverseEntranceManager : Behaviour
{ {
for (int i = toCallEnterUniverses.Count - 1; i >= 0; i--) for (int i = toCallEnterUniverses.Count - 1; i >= 0; i--)
{ {
toCallEnterUniverses[i].EnterUniverse(Universe); IEnterUniverse enterUniverse = toCallEnterUniverses[i];
toCallEnterUniverses.RemoveAt(i); toCallEnterUniverses.RemoveAt(i);
enterUniverse.EnterUniverse(Universe);
} }
} }
@@ -58,14 +66,8 @@ public class UniverseEntranceManager : Behaviour
toCallEnterUniverses.Add(args.BehaviourCollected); toCallEnterUniverses.Add(args.BehaviourCollected);
} }
private void OnExitUniverseCollected(IBehaviourCollector<IExitUniverse> sender, IBehaviourCollector<IExitUniverse>.BehaviourCollectedArguments args)
{
toCallExitUniverses.Add(args.BehaviourCollected);
}
public UniverseEntranceManager() public UniverseEntranceManager()
{ {
enterUniverses.OnCollected.AddListener(OnEnterUniverseCollected); enterUniverses.OnCollected.AddListener(OnEnterUniverseCollected);
exitUniverses.OnCollected.AddListener(OnExitUniverseCollected);
} }
} }

View File

@@ -2,20 +2,21 @@ using System.Collections.Generic;
namespace Engine.Core; namespace Engine.Core;
public class UpdateManager : Behaviour public class UpdateManager : Behaviour, IEnterUniverse, IExitUniverse
{ {
// We use Ascending order because we are using reverse for loop to call them // We use Ascending order because we are using reverse for loop to call them
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorSorted<IFirstFrameUpdate> firstFrameUpdates = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IFirstFrameUpdate> firstFrameUpdates = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollector<ILastFrameUpdate> lastFrameUpdates = new(); private readonly ActiveBehaviourCollector<ILastFrameUpdate> lastFrameUpdates = new();
private readonly ActiveBehaviourCollectorSorted<IPreUpdate> preUpdateEntities = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IPreUpdate> preUpdateEntities = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollectorSorted<IUpdate> updateEntities = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IUpdate> updateEntities = new(GetPriority(), SortByAscendingPriority());
private readonly ActiveBehaviourCollectorSorted<IPostUpdate> postUpdateEntities = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, IPostUpdate> postUpdateEntities = new(GetPriority(), SortByAscendingPriority());
private readonly List<IFirstFrameUpdate> toCallFirstFrameUpdates = new(32); private readonly List<IFirstFrameUpdate> toCallFirstFrameUpdates = new(32);
protected override void OnEnteredUniverse(IUniverse universe) public void EnterUniverse(IUniverse universe)
{ {
firstFrameUpdates.Assign(universe); firstFrameUpdates.Assign(universe);
lastFrameUpdates.Assign(universe); lastFrameUpdates.Assign(universe);
@@ -23,12 +24,13 @@ public class UpdateManager : Behaviour
updateEntities.Assign(universe); updateEntities.Assign(universe);
postUpdateEntities.Assign(universe); postUpdateEntities.Assign(universe);
universe.OnPreUpdate.AddListener(OnFirstUpdate, int.MaxValue);
universe.OnPreUpdate.AddListener(OnPreUpdate); universe.OnPreUpdate.AddListener(OnPreUpdate);
universe.OnUpdate.AddListener(OnUpdate); universe.OnUpdate.AddListener(OnUpdate);
universe.OnPostUpdate.AddListener(OnPostUpdate); universe.OnPostUpdate.AddListener(OnPostUpdate);
} }
protected override void OnExitedUniverse(IUniverse universe) public void ExitUniverse(IUniverse universe)
{ {
firstFrameUpdates.Unassign(); firstFrameUpdates.Unassign();
lastFrameUpdates.Unassign(); lastFrameUpdates.Unassign();
@@ -36,19 +38,23 @@ public class UpdateManager : Behaviour
updateEntities.Unassign(); updateEntities.Unassign();
postUpdateEntities.Unassign(); postUpdateEntities.Unassign();
universe.OnPreUpdate.RemoveListener(OnFirstUpdate);
universe.OnPreUpdate.RemoveListener(OnPreUpdate); universe.OnPreUpdate.RemoveListener(OnPreUpdate);
universe.OnUpdate.RemoveListener(OnUpdate); universe.OnUpdate.RemoveListener(OnUpdate);
universe.OnPostUpdate.RemoveListener(OnPostUpdate); universe.OnPostUpdate.RemoveListener(OnPostUpdate);
} }
private void OnPreUpdate(IUniverse sender, IUniverse.UpdateArguments args) private void OnFirstUpdate(IUniverse sender, IUniverse.UpdateArguments args)
{ {
for (int i = toCallFirstFrameUpdates.Count - 1; i >= 0; i--) for (int i = toCallFirstFrameUpdates.Count - 1; i >= 0; i--)
{ {
toCallFirstFrameUpdates[i].FirstActiveFrame(); toCallFirstFrameUpdates[i].FirstActiveFrame();
toCallFirstFrameUpdates.RemoveAt(i); toCallFirstFrameUpdates.RemoveAt(i);
} }
}
private void OnPreUpdate(IUniverse sender, IUniverse.UpdateArguments args)
{
for (int i = preUpdateEntities.Count - 1; i >= 0; i--) for (int i = preUpdateEntities.Count - 1; i >= 0; i--)
preUpdateEntities[i].PreUpdate(); preUpdateEntities[i].PreUpdate();
} }

View File

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

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

@@ -0,0 +1,285 @@
using Engine.Core.Serialization;
namespace Engine.Core;
[System.Diagnostics.DebuggerDisplay("Name: {UniverseObject.Name, nq} Position: {Position.ToString(), nq}, Scale: {Scale.ToString(), nq}, Rotation: {Rotation}")]
public class Transform3D : Behaviour, ITransform3D
{
public Event<ITransform3D, ITransform3D.PositionChangedArguments> OnPositionChanged { get; } = new();
public Event<ITransform3D, ITransform3D.ScaleChangedArguments> OnScaleChanged { get; } = new();
public Event<ITransform3D, ITransform3D.RotationChangedArguments> OnRotationChanged { get; } = new();
public Event<ITransform3D> OnTransformUpdated { get; } = new();
private Vector3D _position = Vector3D.Zero;
private Vector3D _scale = Vector3D.One;
private Quaternion _rotation = Quaternion.Identity;
[Serialize] private Vector3D _localPosition = Vector3D.Zero;
[Serialize] private Vector3D _localScale = Vector3D.One;
[Serialize] private Quaternion _localRotation = Quaternion.Identity;
private ITransform3D? parentTransform = null;
public Vector3D Up => Quaternion.RotateVector(Vector3D.Up, Rotation);
public Vector3D Down => Quaternion.RotateVector(Vector3D.Down, Rotation);
public Vector3D Left => Quaternion.RotateVector(Vector3D.Left, Rotation);
public Vector3D Right => Quaternion.RotateVector(Vector3D.Right, Rotation);
public Vector3D Forward => Quaternion.RotateVector(Vector3D.Forward, Rotation);
public Vector3D Backward => Quaternion.RotateVector(Vector3D.Backward, Rotation);
public Vector3D Position
{
get => _position;
set
{
if (value == _position)
return;
Vector3D previousPosition = _position;
_position = value;
UpdateLocalPosition();
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
public Vector3D Scale
{
get => _scale;
set
{
if (value == _scale)
return;
Vector3D previousScale = _scale;
_scale = value;
UpdateLocalScale();
OnScaleChanged.Invoke(this, new(previousScale));
OnTransformUpdated.Invoke(this);
}
}
public Quaternion Rotation
{
get => _rotation;
set
{
if (value == _rotation)
return;
Quaternion previousRotation = _rotation;
_rotation = value;
UpdateLocalRotation();
OnRotationChanged.Invoke(this, new(previousRotation));
OnTransformUpdated.Invoke(this);
}
}
public Vector3D LocalPosition
{
get => _localPosition;
set
{
if (value == _localPosition)
return;
Vector3D previousPosition = _position;
_localPosition = value;
UpdatePosition();
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
public Vector3D LocalScale
{
get => _localScale;
set
{
if (value == _localScale)
return;
Vector3D previousScale = _scale;
Vector3D previousPosition = _position;
_localScale = value;
UpdateScale();
UpdatePosition();
OnScaleChanged.Invoke(this, new(previousScale));
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
}
public Quaternion LocalRotation
{
get => _localRotation;
set
{
if (value == _localRotation)
return;
Quaternion previousRotation = _rotation;
_localRotation = value;
UpdateRotation();
OnRotationChanged.Invoke(this, new(previousRotation));
OnTransformUpdated.Invoke(this);
}
}
private void RecalculatePosition(ITransform3D _, ITransform3D.PositionChangedArguments args)
{
if (parentTransform is null)
return;
UpdatePosition();
OnPositionChanged.Invoke(this, args);
OnTransformUpdated.Invoke(this);
}
private void RecalculateScale(ITransform3D _, ITransform3D.ScaleChangedArguments args)
{
if (parentTransform is null)
return;
Vector3D previousPosition = _position;
UpdateScale();
UpdatePosition();
OnScaleChanged.Invoke(this, args);
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
private void RecalculateRotation(ITransform3D _, ITransform3D.RotationChangedArguments args)
{
if (parentTransform is null)
return;
Vector3D previousPosition = Position;
UpdateRotation();
UpdatePosition();
OnRotationChanged.Invoke(this, args);
OnPositionChanged.Invoke(this, new(previousPosition));
OnTransformUpdated.Invoke(this);
}
private void UpdateLocalPosition()
{
if (parentTransform is null)
_localPosition = Position;
else
_localPosition = parentTransform.Position.FromTo(Position).Scale(parentTransform.Scale);
}
private void UpdateLocalScale()
{
if (parentTransform is null)
_localScale = Scale;
else
_localScale = Scale.Scale(new(1f / parentTransform.Scale.X, 1f / parentTransform.Scale.Y, 1f / parentTransform.Scale.Z));
}
private void UpdateLocalRotation()
{
if (parentTransform is null)
_localRotation = Rotation;
else
_localRotation = Rotation * parentTransform.Rotation.Invert();
}
private void UpdatePosition()
{
if (parentTransform is null)
_position = LocalPosition;
else
_position = parentTransform.Position + Quaternion.RotateVector(LocalPosition.Scale(new(parentTransform.Scale.X, parentTransform.Scale.Y, parentTransform.Scale.Z)), parentTransform.Rotation);
}
private void UpdateScale()
{
if (parentTransform is null)
_scale = LocalScale;
else
_scale = (parentTransform?.Scale ?? Vector3D.One).Scale(_localScale);
}
private void UpdateRotation()
{
if (parentTransform is null)
_rotation = LocalRotation;
else
_rotation = parentTransform.Rotation * LocalRotation;
}
protected override void InitializeInternal()
{
UpdateReferences(UniverseObject.Parent);
UniverseObject.OnParentChanged.AddListener(OnParentChanged);
}
protected override void FinalizeInternal()
{
UniverseObject.OnParentChanged.RemoveListener(OnParentChanged);
}
private void UpdateReferences(IUniverseObject? parent)
{
ITransform3D? previousParent = parentTransform;
if (previousParent is not null)
{
previousParent.OnPositionChanged.RemoveListener(RecalculatePosition);
previousParent.OnScaleChanged.RemoveListener(RecalculateScale);
previousParent.OnRotationChanged.RemoveListener(RecalculateRotation);
previousParent.BehaviourController.UniverseObject.OnParentChanged.RemoveListener(OnParentChanged);
previousParent.BehaviourController.OnBehaviourAdded.RemoveListener(LookForTransform3D);
}
parentTransform = parent?.BehaviourController.GetBehaviour<ITransform3D>();
if (parentTransform is not null)
{
parentTransform.OnPositionChanged.AddListener(RecalculatePosition);
parentTransform.OnScaleChanged.AddListener(RecalculateScale);
parentTransform.OnRotationChanged.AddListener(RecalculateRotation);
parentTransform.BehaviourController.UniverseObject.OnParentChanged.AddListener(OnParentChanged);
UpdatePosition();
UpdateScale();
UpdateRotation();
}
else
UniverseObject.Parent?.BehaviourController.OnBehaviourAdded.AddListener(LookForTransform3D);
UpdateLocalPosition();
UpdateLocalScale();
UpdateLocalRotation();
OnPositionChanged.Invoke(this, new(Position));
OnScaleChanged.Invoke(this, new(Scale));
OnRotationChanged.Invoke(this, new(Rotation));
OnTransformUpdated.Invoke(this);
}
private void LookForTransform3D(IBehaviourController sender, IBehaviourController.BehaviourAddedArguments args)
{
if (args.BehaviourAdded is not ITransform3D)
return;
UpdateReferences(UniverseObject.Parent);
}
private void OnParentChanged(IUniverseObject sender, IUniverseObject.ParentChangedArguments args)
{
UpdateReferences(args.CurrentParent);
}
}

View File

@@ -22,7 +22,7 @@ public class Universe : BaseEntity, IUniverse
private readonly Event<IInitializable>.EventHandler delegateOnUniverseObjectFinalize = null!; private readonly Event<IInitializable>.EventHandler delegateOnUniverseObjectFinalize = null!;
private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler delegateOnUniverseObjectExitedUniverse = null!; private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler delegateOnUniverseObjectExitedUniverse = null!;
private readonly List<IUniverseObject> _universeObjects = new(Constants.UNIVERSE_OBJECTS_SIZE_INITIAL); private readonly FastList<IUniverseObject> _universeObjects = new(Constants.UNIVERSE_OBJECTS_SIZE_INITIAL);
private float _timeScale = 1f; private float _timeScale = 1f;
public Universe() public Universe()
@@ -31,7 +31,7 @@ public class Universe : BaseEntity, IUniverse
delegateOnUniverseObjectExitedUniverse = OnUniverseObjectExitedUniverse; delegateOnUniverseObjectExitedUniverse = OnUniverseObjectExitedUniverse;
} }
public IReadOnlyList<IUniverseObject> UniverseObjects => _universeObjects; public IUniverseObject Root { get; private set; } = Factory.UniverseObjectFactory.Instantiate().SetUniverseObject("Root");
public UniverseTime Time { get; private set; } = new(); public UniverseTime Time { get; private set; } = new();
public UniverseTime UnscaledTime { get; private set; } = new(); public UniverseTime UnscaledTime { get; private set; } = new();
@@ -63,14 +63,17 @@ public class Universe : BaseEntity, IUniverse
if (!universeObject.Initialize()) if (!universeObject.Initialize())
throw new Exception($"{universeObject.Name} can't be initialized"); throw new Exception($"{universeObject.Name} can't be initialized");
for (int i = 0; i < universeObject.Children.Count; i++) if (universeObject.Parent == null)
Register(universeObject.Children[i]); universeObject.Parent = Root;
_universeObjects.Add(universeObject); _universeObjects.Add(universeObject);
if (!universeObject.EnterUniverse(this)) if (!universeObject.EnterUniverse(this))
throw new Exception($"{universeObject.Name} can't enter the universe"); throw new Exception($"{universeObject.Name} can't enter the universe");
for (int i = 0; i < universeObject.Children.Count; i++)
Register(universeObject.Children[i]);
OnUniverseObjectRegistered?.Invoke(this, new(universeObject)); OnUniverseObjectRegistered?.Invoke(this, new(universeObject));
} }
@@ -113,15 +116,15 @@ public class Universe : BaseEntity, IUniverse
protected override void InitializeInternal() protected override void InitializeInternal()
{ {
foreach (IUniverseObject universeObject in UniverseObjects) foreach (IUniverseObject universeObject in _universeObjects)
universeObject.Initialize(); universeObject.Initialize();
} }
protected override void FinalizeInternal() protected override void FinalizeInternal()
{ {
base.FinalizeInternal(); base.FinalizeInternal();
for (int i = UniverseObjects.Count; i >= 0; i--) for (int i = _universeObjects.Count - 1; i >= 0; i--)
UniverseObjects[i].Finalize(); Remove(_universeObjects[i]);
} }
public void Update(UniverseTime engineTime) public void Update(UniverseTime engineTime)
@@ -131,9 +134,10 @@ public class Universe : BaseEntity, IUniverse
UnscaledTime = engineTime; UnscaledTime = engineTime;
Time = new(TimeSpan.FromTicks((long)(Time.TimeSinceStart.Ticks + engineTime.DeltaSpan.Ticks * TimeScale)), TimeSpan.FromTicks((long)(engineTime.DeltaSpan.Ticks * TimeScale))); Time = new(TimeSpan.FromTicks((long)(Time.TimeSinceStart.Ticks + engineTime.DeltaSpan.Ticks * TimeScale)), TimeSpan.FromTicks((long)(engineTime.DeltaSpan.Ticks * TimeScale)));
OnPreUpdate?.Invoke(this, new(Time)); IUniverse.UpdateArguments args = new(Time);
OnUpdate?.Invoke(this, new(Time)); OnPreUpdate?.Invoke(this, args);
OnPostUpdate?.Invoke(this, new(Time)); OnUpdate?.Invoke(this, args);
OnPostUpdate?.Invoke(this, args);
} }
public void Draw() public void Draw()
@@ -157,6 +161,6 @@ public class Universe : BaseEntity, IUniverse
Remove(universeObject); Remove(universeObject);
} }
public IEnumerator<IUniverseObject> GetEnumerator() => _universeObjects.GetEnumerator(); public IEnumerator<IUniverseObject> GetEnumerator() => Root.TraverseChildren();
IEnumerator IEnumerable.GetEnumerator() => _universeObjects.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => Root.TraverseChildren();
} }

View File

@@ -19,7 +19,7 @@ public class UniverseObject : BaseEntity, IUniverseObject
private IUniverse _universe = null!; private IUniverse _universe = null!;
private IBehaviourController _behaviourController = null!; private IBehaviourController _behaviourController = null!;
private bool _isActive = false; private bool _isActive = false;
private readonly List<IUniverseObject> _children = []; private readonly FastList<IUniverseObject> _children = [];
private IUniverseObject? _parent = null; private IUniverseObject? _parent = null;
public IReadOnlyList<IUniverseObject> Children => _children; public IReadOnlyList<IUniverseObject> Children => _children;
@@ -100,14 +100,14 @@ public class UniverseObject : BaseEntity, IUniverseObject
return true; return true;
} }
public void AddChild(IUniverseObject parent) public void AddChild(IUniverseObject child)
{ {
if (_children.Contains(parent)) if (_children.Contains(child))
return; return;
_children.Add(parent); _children.Add(child);
parent.Parent = this; child.Parent = this;
OnChildrenAdded?.Invoke(this, new(parent)); OnChildrenAdded?.Invoke(this, new(child));
} }
public void RemoveChild(IUniverseObject child) public void RemoveChild(IUniverseObject child)

View File

@@ -2,7 +2,7 @@ using System;
namespace Engine.Core; namespace Engine.Core;
public readonly struct UniverseTime(TimeSpan TimeSinceStart, TimeSpan TimeDelta) public readonly record struct UniverseTime(TimeSpan TimeSinceStart, TimeSpan TimeDelta)
{ {
public readonly TimeSpan TimeSinceStart = TimeSinceStart; public readonly TimeSpan TimeSinceStart = TimeSinceStart;
public readonly TimeSpan DeltaSpan = TimeDelta; public readonly TimeSpan DeltaSpan = TimeDelta;

View File

@@ -11,6 +11,7 @@ namespace Engine.Systems.Network;
public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicatorClient
{ {
private readonly NetDataWriter netDataWriter = new(); private readonly NetDataWriter netDataWriter = new();
private readonly NetDataWriter netDataWriterEncrypted = new();
private CancellationTokenSource? cancellationTokenSource = null; private CancellationTokenSource? cancellationTokenSource = null;
@@ -42,7 +43,20 @@ public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicator
public INetworkCommunicatorClient SendToServer<T>(T packet, PacketDelivery packetDelivery) where T : class, new() public INetworkCommunicatorClient SendToServer<T>(T packet, PacketDelivery packetDelivery) where T : class, new()
{ {
netDataWriter.Reset(); netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet); netDataWriterEncrypted.Reset();
if (packet is INetworkPacketEncrypted)
{
netPacketProcessor.Write(netDataWriterEncrypted, packet);
byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData());
netDataWriter.Put(true);
netDataWriter.PutBytesWithLength(encryptedData);
}
else
{
netDataWriter.Put(false);
netPacketProcessor.Write(netDataWriter, packet);
}
switch (packetDelivery) switch (packetDelivery)
{ {
@@ -56,17 +70,17 @@ public class LiteNetLibClient : LiteNetLibCommunicatorBase, INetworkCommunicator
return this; return this;
} }
protected override void OnEnteredUniverse(IUniverse universe) public override void EnterUniverse(IUniverse universe)
{ {
base.OnEnteredUniverse(universe); base.EnterUniverse(universe);
cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource = new CancellationTokenSource();
PollEvents(cancellationTokenSource.Token); PollEvents(cancellationTokenSource.Token);
} }
protected override void OnExitedUniverse(IUniverse universe) public override void ExitUniverse(IUniverse universe)
{ {
base.OnExitedUniverse(universe); base.ExitUniverse(universe);
cancellationTokenSource?.Cancel(); cancellationTokenSource?.Cancel();
} }

View File

@@ -1,16 +1,18 @@
using System.Reflection; using System.Reflection;
using Engine.Core;
using Engine.Core.Debug;
using Engine.Systems.Network.Packers;
using LiteNetLib; using LiteNetLib;
using LiteNetLib.Utils; using LiteNetLib.Utils;
using Engine.Core;
using Engine.Core.Debug;
namespace Engine.Systems.Network; namespace Engine.Systems.Network;
public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicator public abstract class LiteNetLibCommunicatorBase : Behaviour, IEnterUniverse, IExitUniverse, INetworkCommunicator
{ {
protected readonly NetPacketProcessor netPacketProcessor = new(); protected readonly NetPacketProcessor netPacketProcessor = new();
protected readonly PacketCryptor cryptor = new("At4ywW9PGoWH3g==", "NmpMFTvd3pvUbA=="); // TODO implement public key exchange
private readonly Dictionary<Type, Event<IConnection, object>> listeners = []; private readonly Dictionary<Type, Event<IConnection, object>> listeners = [];
private readonly Dictionary<string, IConnection> _connections = []; private readonly Dictionary<string, IConnection> _connections = [];
@@ -32,15 +34,13 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicat
return this; return this;
} }
protected override void OnEnteredUniverse(IUniverse universe) public virtual void EnterUniverse(IUniverse universe)
{ {
base.OnEnteredUniverse(universe);
logger = universe.FindBehaviour<ILogger>(); logger = universe.FindBehaviour<ILogger>();
} }
protected override void OnExitedUniverse(IUniverse universe) public virtual void ExitUniverse(IUniverse universe)
{ {
base.OnExitedUniverse(universe);
logger = null; logger = null;
Stop(); Stop();
} }
@@ -55,7 +55,20 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicat
private void NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod) private void NetworkReceiveEvent(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod)
{ {
try { netPacketProcessor.ReadAllPackets(reader, peer); } try
{
bool isEncrypted = reader.GetBool();
if (isEncrypted) // TODO performance improvements
{
byte[] encryptedData = reader.GetBytesWithLength();
byte[] decryptedData = cryptor.Decrypt(encryptedData);
NetDataReader innerReader = new(decryptedData);
netPacketProcessor.ReadAllPackets(innerReader, peer);
return;
}
netPacketProcessor.ReadAllPackets(reader, peer);
}
catch (Exception exception) { logger?.LogException(this, exception, force: true); } catch (Exception exception) { logger?.LogException(this, exception, force: true); }
} }
@@ -143,19 +156,28 @@ public abstract class LiteNetLibCommunicatorBase : Behaviour, INetworkCommunicat
private void SetupEnginePackets() private void SetupEnginePackets()
{ {
// I know, ugly af. I need to find a better way // I know, ugly af. I need to find a better way
netPacketProcessor.RegisterNestedType(AABBNetPacker.Write, AABBNetPacker.Read); netPacketProcessor.RegisterNestedType(AABB2DNetPacker.Write, AABB2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(AABB3DNetPacker.Write, AABB3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(CircleNetPacker.Write, CircleNetPacker.Read); netPacketProcessor.RegisterNestedType(CircleNetPacker.Write, CircleNetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorHSVNetPacker.Write, ColorHSVNetPacker.Read); netPacketProcessor.RegisterNestedType(ColorHSVNetPacker.Write, ColorHSVNetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorRGBANetPacker.Write, ColorRGBANetPacker.Read); netPacketProcessor.RegisterNestedType(ColorHSVANetPacker.Write, ColorHSVANetPacker.Read);
netPacketProcessor.RegisterNestedType(ColorRGBNetPacker.Write, ColorRGBNetPacker.Read); netPacketProcessor.RegisterNestedType(ColorRGBNetPacker.Write, ColorRGBNetPacker.Read);
netPacketProcessor.RegisterNestedType(Line2DEquationNetPacker.Write, Line2DEquationNetPacker.Read); netPacketProcessor.RegisterNestedType(ColorRGBANetPacker.Write, ColorRGBANetPacker.Read);
netPacketProcessor.RegisterNestedType(Line2DNetPacker.Write, Line2DNetPacker.Read); netPacketProcessor.RegisterNestedType(Line2DNetPacker.Write, Line2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Line2DEquationNetPacker.Write, Line2DEquationNetPacker.Read);
netPacketProcessor.RegisterNestedType(Line3DNetPacker.Write, Line3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Projection1DNetPacker.Write, Projection1DNetPacker.Read); netPacketProcessor.RegisterNestedType(Projection1DNetPacker.Write, Projection1DNetPacker.Read);
netPacketProcessor.RegisterNestedType(QuaternionNetPacker.Write, QuaternionNetPacker.Read); netPacketProcessor.RegisterNestedType(QuaternionNetPacker.Write, QuaternionNetPacker.Read);
netPacketProcessor.RegisterNestedType(Ray2DNetPacker.Write, Ray2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Ray3DNetPacker.Write, Ray3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Shape2DNetPacker.Write, Shape2DNetPacker.Read); netPacketProcessor.RegisterNestedType(Shape2DNetPacker.Write, Shape2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Sphere3DNetPacker.Write, Sphere3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(TriangleNetPacker.Write, TriangleNetPacker.Read); netPacketProcessor.RegisterNestedType(TriangleNetPacker.Write, TriangleNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector2DNetPacker.Write, Vector2DNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector2DNetPacker.Write, Vector2DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector2DIntNetPacker.Write, Vector2DIntNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read); netPacketProcessor.RegisterNestedType(Vector3DNetPacker.Write, Vector3DNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector3DIntNetPacker.Write, Vector3DIntNetPacker.Read);
netPacketProcessor.RegisterNestedType(Vector4DNetPacker.Write, Vector4DNetPacker.Read);
} }
public INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback) public INetworkCommunicator SubscribeToPackets<T>(Event<IConnection, T>.EventHandler callback)

View File

@@ -13,6 +13,7 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
public int Port { get; private set; } = 8888; public int Port { get; private set; } = 8888;
private readonly NetDataWriter netDataWriter = new(); private readonly NetDataWriter netDataWriter = new();
private readonly NetDataWriter netDataWriterEncrypted = new();
public LiteNetLibServer() : this(8888, 2) { } public LiteNetLibServer() : this(8888, 2) { }
public LiteNetLibServer(int port, int maxConnectionCount) : base() public LiteNetLibServer(int port, int maxConnectionCount) : base()
@@ -53,11 +54,25 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
public INetworkCommunicatorServer SendToClient<T>(IConnection connection, T packet, PacketDelivery packetDelivery) where T : class, new() public INetworkCommunicatorServer SendToClient<T>(IConnection connection, T packet, PacketDelivery packetDelivery) where T : class, new()
{ {
netDataWriter.Reset(); netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet);
if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.CompareTo(connection.Id) == 0) is not NetPeer netPeer) if (Manager.ConnectedPeerList.FirstOrDefault(p => p.Id.ToString().CompareTo(connection.Id) == 0) is not NetPeer netPeer)
throw new($"Peer {connection} couldn't be found."); throw new($"Peer {connection} couldn't be found.");
if (packet is INetworkPacketEncrypted) // TODO performance improvements
{
netDataWriterEncrypted.Reset();
netPacketProcessor.Write(netDataWriterEncrypted, packet);
byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData());
netDataWriter.Put(true);
netDataWriter.PutBytesWithLength(encryptedData);
}
else
{
netDataWriter.Put(false);
netPacketProcessor.Write(netDataWriter, packet);
}
switch (packetDelivery) switch (packetDelivery)
{ {
case PacketDelivery.ReliableInOrder: netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break; case PacketDelivery.ReliableInOrder: netPeer.Send(netDataWriter, DeliveryMethod.ReliableOrdered); break;
@@ -75,6 +90,23 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
netDataWriter.Reset(); netDataWriter.Reset();
netPacketProcessor.Write(netDataWriter, packet); netPacketProcessor.Write(netDataWriter, packet);
if (packet is INetworkPacketEncrypted)
{
netDataWriterEncrypted.Reset();
logger?.Log($"Encrypted Packet Sending");
netPacketProcessor.Write(netDataWriterEncrypted, packet);
byte[] encryptedData = cryptor.Encrypt(netDataWriterEncrypted.CopyData());
netDataWriter.PutBytesWithLength(encryptedData);
netDataWriter.Put(true);
}
else
{
logger?.Log($"Regular Packet Sending");
netPacketProcessor.Write(netDataWriter, packet);
netDataWriter.Put(false);
}
switch (packetDelivery) switch (packetDelivery)
{ {
case PacketDelivery.ReliableInOrder: Manager.SendToAll(netDataWriter, DeliveryMethod.ReliableOrdered); break; case PacketDelivery.ReliableInOrder: Manager.SendToAll(netDataWriter, DeliveryMethod.ReliableOrdered); break;
@@ -88,15 +120,15 @@ public class LiteNetLibServer : LiteNetLibCommunicatorBase, INetworkCommunicator
private void PollEvents(IUniverse sender, IUniverse.UpdateArguments args) => Manager.PollEvents(); private void PollEvents(IUniverse sender, IUniverse.UpdateArguments args) => Manager.PollEvents();
protected override void OnEnteredUniverse(IUniverse universe) public override void EnterUniverse(IUniverse universe)
{ {
base.OnEnteredUniverse(universe); base.EnterUniverse(universe);
universe.OnPostUpdate.AddListener(PollEvents); universe.OnPostUpdate.AddListener(PollEvents);
} }
protected override void OnExitedUniverse(IUniverse universe) public override void ExitUniverse(IUniverse universe)
{ {
base.OnExitedUniverse(universe); base.ExitUniverse(universe);
universe.OnPostUpdate.RemoveListener(PollEvents); universe.OnPostUpdate.RemoveListener(PollEvents);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
using System.Security.Cryptography;
using System.Text;
namespace Engine.Systems.Network;
public class PacketCryptor // TODO performance improvements
{
private readonly Aes aes = null!;
private readonly ICryptoTransform encrpytor = null!;
private readonly ICryptoTransform decryptor = null!;
public byte[] Encrypt(byte[] data) => encrpytor.TransformFinalBlock(data, 0, data.Length);
public byte[] Decrypt(byte[] data) => decryptor.TransformFinalBlock(data, 0, data.Length);
public PacketCryptor(string key, string initializationVector)
{
aes = Aes.Create();
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = Encoding.UTF8.GetBytes(initializationVector);
encrpytor = aes.CreateEncryptor();
decryptor = aes.CreateDecryptor();
}
}

View File

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

View File

@@ -10,11 +10,11 @@ namespace Engine.Integration.MonoGame;
public class KeyboardInputs : Behaviour, IButtonInputs<Keys>, IUpdate public class KeyboardInputs : Behaviour, IButtonInputs<Keys>, IUpdate
{ {
public Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments> OnAnyButtonPressed { get; } = new(); public IButtonInputs<Keys>.InputEvent OnAnyButtonPressed { get; } = new();
public Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments> OnAnyButtonReleased { get; } = new(); public IButtonInputs<Keys>.InputEvent OnAnyButtonReleased { get; } = new();
private readonly Dictionary<Keys, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>> OnPressed = new(256); private readonly Dictionary<Keys, IButtonInputs<Keys>.InputEvent> OnPressed = new(256);
private readonly Dictionary<Keys, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>> OnReleased = new(256); private readonly Dictionary<Keys, IButtonInputs<Keys>.InputEvent> OnReleased = new(256);
private int cachePressedCurrentlyCount = 0; private int cachePressedCurrentlyCount = 0;
private readonly Keys[] cachePressedCurrently = new Keys[256]; private readonly Keys[] cachePressedCurrently = new Keys[256];
@@ -22,9 +22,9 @@ public class KeyboardInputs : Behaviour, IButtonInputs<Keys>, IUpdate
private int cachePressedPreviouslyCount = 0; private int cachePressedPreviouslyCount = 0;
private readonly Keys[] cachePressedPreviously = new Keys[256]; private readonly Keys[] cachePressedPreviously = new Keys[256];
public void RegisterOnPress(Keys key, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>.EventHandler callback) public void RegisterOnPress(Keys key, IButtonInputs<Keys>.InputEvent.EventHandler callback)
{ {
if (!OnPressed.TryGetValue(key, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? delegateCallback)) if (!OnPressed.TryGetValue(key, out IButtonInputs<Keys>.InputEvent? delegateCallback))
{ {
delegateCallback = new(); delegateCallback = new();
OnPressed.Add(key, delegateCallback); OnPressed.Add(key, delegateCallback);
@@ -33,15 +33,15 @@ public class KeyboardInputs : Behaviour, IButtonInputs<Keys>, IUpdate
delegateCallback.AddListener(callback); delegateCallback.AddListener(callback);
} }
public void UnregisterOnPress(Keys key, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>.EventHandler callback) public void UnregisterOnPress(Keys key, IButtonInputs<Keys>.InputEvent.EventHandler callback)
{ {
if (OnPressed.TryGetValue(key, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? delegateCallback)) if (OnPressed.TryGetValue(key, out IButtonInputs<Keys>.InputEvent? delegateCallback))
delegateCallback.RemoveListener(callback); delegateCallback.RemoveListener(callback);
} }
public void RegisterOnRelease(Keys key, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>.EventHandler callback) public void RegisterOnRelease(Keys key, IButtonInputs<Keys>.InputEvent.EventHandler callback)
{ {
if (!OnReleased.TryGetValue(key, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? delegateCallback)) if (!OnReleased.TryGetValue(key, out IButtonInputs<Keys>.InputEvent? delegateCallback))
{ {
delegateCallback = new(); delegateCallback = new();
OnReleased.Add(key, delegateCallback); OnReleased.Add(key, delegateCallback);
@@ -50,9 +50,9 @@ public class KeyboardInputs : Behaviour, IButtonInputs<Keys>, IUpdate
delegateCallback.AddListener(callback); delegateCallback.AddListener(callback);
} }
public void UnregisterOnRelease(Keys key, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>.EventHandler callback) public void UnregisterOnRelease(Keys key, IButtonInputs<Keys>.InputEvent.EventHandler callback)
{ {
if (OnReleased.TryGetValue(key, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? delegateCallback)) if (OnReleased.TryGetValue(key, out IButtonInputs<Keys>.InputEvent? delegateCallback))
delegateCallback.RemoveListener(callback); delegateCallback.RemoveListener(callback);
} }
@@ -69,7 +69,7 @@ public class KeyboardInputs : Behaviour, IButtonInputs<Keys>, IUpdate
if (WasPressed(currentlyPressedKey)) if (WasPressed(currentlyPressedKey))
continue; continue;
if (OnPressed.TryGetValue(currentlyPressedKey, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? callback)) if (OnPressed.TryGetValue(currentlyPressedKey, out IButtonInputs<Keys>.InputEvent? callback))
callback?.Invoke(this, new(currentlyPressedKey)); callback?.Invoke(this, new(currentlyPressedKey));
OnAnyButtonPressed?.Invoke(this, new(currentlyPressedKey)); OnAnyButtonPressed?.Invoke(this, new(currentlyPressedKey));
@@ -82,7 +82,7 @@ public class KeyboardInputs : Behaviour, IButtonInputs<Keys>, IUpdate
if (IsPressed(previouslyPressedKey)) if (IsPressed(previouslyPressedKey))
continue; continue;
if (OnReleased.TryGetValue(previouslyPressedKey, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? callback)) if (OnReleased.TryGetValue(previouslyPressedKey, out IButtonInputs<Keys>.InputEvent? callback))
callback?.Invoke(this, new(previouslyPressedKey)); callback?.Invoke(this, new(previouslyPressedKey));
OnAnyButtonReleased?.Invoke(this, new(previouslyPressedKey)); OnAnyButtonReleased?.Invoke(this, new(previouslyPressedKey));

View File

@@ -1,14 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using Engine.Core; using Engine.Core;
namespace Engine.Integration.MonoGame; namespace Engine.Integration.MonoGame;
public class LoadContentManager : Behaviour, IFirstFrameUpdate public class LoadContentManager : Behaviour, IEnterUniverse, IExitUniverse, IFirstFrameUpdate
{ {
// We use Ascending order because we are using reverse for loop to call them // We use Ascending order because we are using reverse for loop to call them
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<int> SortByAscendingPriority() => Comparer<int>.Create((x, y) => x.CompareTo(y));
private static System.Func<IBehaviour, int> GetPriority() => (b) => b.Priority;
private readonly ActiveBehaviourCollectorSorted<ILoadContent> loadContents = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorOrdered<int, ILoadContent> loadContents = new(GetPriority(), SortByAscendingPriority());
private readonly List<ILoadContent> toCallLoadContents = new(32); private readonly List<ILoadContent> toCallLoadContents = new(32);
private MonoGameWindowContainer monoGameWindowContainer = null!; private MonoGameWindowContainer monoGameWindowContainer = null!;
@@ -18,14 +20,14 @@ public class LoadContentManager : Behaviour, IFirstFrameUpdate
monoGameWindowContainer = Universe.FindRequiredBehaviour<MonoGameWindowContainer>(); monoGameWindowContainer = Universe.FindRequiredBehaviour<MonoGameWindowContainer>();
} }
protected override void OnEnteredUniverse(IUniverse universe) public void EnterUniverse(IUniverse universe)
{ {
loadContents.Assign(universe); loadContents.Assign(universe);
universe.OnPreUpdate.AddListener(OnPreUpdate); universe.OnPreUpdate.AddListener(OnPreUpdate);
} }
protected override void OnExitedUniverse(IUniverse universe) public void ExitUniverse(IUniverse universe)
{ {
loadContents.Unassign(); loadContents.Unassign();

View File

@@ -5,11 +5,11 @@ using Engine.Core;
namespace Engine.Integration.MonoGame; namespace Engine.Integration.MonoGame;
public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPreDraw public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw
{ {
public event MatrixTransformChangedArguments? OnMatrixTransformChanged = null; public Event<MonoGameCamera2D> OnMatrixTransformChanged { get; } = new();
public event ViewportChangedArguments? OnViewportChanged = null; public Event<MonoGameCamera2D> OnViewportChanged { get; } = new();
public event ZoomChangedArguments? OnZoomChanged = null; public Event<MonoGameCamera2D> OnZoomChanged { get; } = new();
private Matrix _matrixTransform = Matrix.Identity; private Matrix _matrixTransform = Matrix.Identity;
@@ -28,7 +28,7 @@ public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPr
return; return;
_matrixTransform = value; _matrixTransform = value;
OnMatrixTransformChanged?.Invoke(this); OnMatrixTransformChanged.Invoke(this);
} }
} }
@@ -47,7 +47,7 @@ public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPr
return; return;
_viewport = value; _viewport = value;
OnViewportChanged?.Invoke(this); OnViewportChanged.Invoke(this);
} }
} }
@@ -56,13 +56,13 @@ public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPr
get => _zoom; get => _zoom;
set set
{ {
float newValue = Engine.Core.Math.Max(0.1f, value); float newValue = Math.Max(0.1f, value);
if (_zoom == newValue) if (_zoom == newValue)
return; return;
_zoom = newValue; _zoom = newValue;
OnZoomChanged?.Invoke(this); OnZoomChanged.Invoke(this);
} }
} }
@@ -84,26 +84,21 @@ public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPr
return screenPosition.Scale(EngineConverterExtensions.screenScale); return screenPosition.Scale(EngineConverterExtensions.screenScale);
} }
public void LastActiveFrame() => Transform = null!;
public void FirstActiveFrame() public void FirstActiveFrame()
{ {
Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics; Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics;
Viewport = Graphics.GraphicsDevice.Viewport; Viewport = Graphics.GraphicsDevice.Viewport;
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
} }
public void PreDraw() public void PreDraw()
{ {
MatrixTransform = MatrixTransform =
Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) * Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) *
Matrix.CreateRotationZ(Rotation * Engine.Core.Math.DegreeToRadian) * Matrix.CreateRotationZ(Rotation * Math.DegreeToRadian) *
Matrix.CreateScale(Transform.Scale.X.Max(Transform.Scale.Y)) * Matrix.CreateScale(Transform.Scale.X.Max(Transform.Scale.Y)) *
Matrix.CreateScale(Zoom) * Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f)); Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f));
} }
protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
protected sealed override void FinalizeInternal() => Transform = null!;
public delegate void MatrixTransformChangedArguments(MonoGameCamera2D sender);
public delegate void ViewportChangedArguments(MonoGameCamera2D sender);
public delegate void ZoomChangedArguments(MonoGameCamera2D sender);
} }

Some files were not shown because too many files have changed in this diff Show More