Compare commits

..

No commits in common. "main" and "feat/physics2d" have entirely different histories.

49 changed files with 345 additions and 1908 deletions

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignable public interface IAssignable
{ {
/// <summary> /// <summary>
/// Event triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle. /// Callback triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle.
/// </summary> /// </summary>
Action<IAssignable>? OnUnassigned { get; set; } Action<IAssignable>? OnUnassigned { get; set; }

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableBehaviourController : IAssignable public interface IAssignableBehaviourController : IAssignable
{ {
/// <summary> /// <summary>
/// Event triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value. /// Callback triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableBehaviourController : IAssignable
IBehaviourController BehaviourController { get; } IBehaviourController BehaviourController { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="IBehaviourController"/> field of this object. /// Assign a value to the <see cref="IBehaviourController"/> field of this object
/// </summary> /// </summary>
/// <param name="behaviourController">New <see cref="IBehaviourController"/> to assign.</param> /// <param name="behaviourController">New <see cref="IBehaviourController"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableEntity : IAssignable public interface IAssignableEntity : IAssignable
{ {
/// <summary> /// <summary>
/// Event triggered when the <see cref="IEntity"/> value has has been assigned a new value. /// Callback triggered when the <see cref="IEntity"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableEntity>? OnEntityAssigned { get; set; } Action<IAssignableEntity>? OnEntityAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableEntity : IAssignable
IEntity Entity { get; } IEntity Entity { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="IEntity"/> field of this object. /// Assign a value to the <see cref="IEntity"/> field of this object
/// </summary> /// </summary>
/// <param name="entity">New <see cref="IEntity"/> to assign.</param> /// <param name="entity">New <see cref="IEntity"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -1,26 +0,0 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="ITransform"/> field.
/// </summary>
public interface IAssignableGameManager : IAssignable
{
/// <summary>
/// Event triggered when the <see cref="IGameManager"/> value has has been assigned a new value.
/// </summary>
Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; }
/// <inheritdoc cref="IGameManager" />
IGameManager GameManager { get; }
/// <summary>
/// Assign a value to the <see cref="IGameManager"/> field of this object.
/// </summary>
/// <param name="gameManager">New <see cref="IGameManager"/> to assign.</param>
/// <returns>
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IGameManager gameManager);
}

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableGameObject : IAssignable public interface IAssignableGameObject : IAssignable
{ {
/// <summary> /// <summary>
/// Event triggered when the <see cref="IGameObject"/> value has has been assigned a new value. /// Callback triggered when the <see cref="IGameObject"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableGameObject>? OnGameObjectAssigned { get; set; } Action<IAssignableGameObject>? OnGameObjectAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableGameObject : IAssignable
IGameObject GameObject { get; } IGameObject GameObject { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="IGameObject"/> field of this object. /// Assign a value to the <see cref="IGameObject"/> field of this object
/// </summary> /// </summary>
/// <param name="gameObject">New <see cref="IGameObject"/> to assign.</param> /// <param name="gameObject">New <see cref="IGameObject"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableStateEnable : IAssignable public interface IAssignableStateEnable : IAssignable
{ {
/// <summary> /// <summary>
/// Event triggered when the <see cref="IStateEnable"/> value has has been assigned a new value. /// Callback triggered when the <see cref="IStateEnable"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableStateEnable : IAssignable
IStateEnable StateEnable { get; } IStateEnable StateEnable { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="IStateEnable"/> field of this object. /// Assign a value to the <see cref="IStateEnable"/> field of this object
/// </summary> /// </summary>
/// <param name="stateEnable">New <see cref="IStateEnable"/> to assign.</param> /// <param name="stateEnable">New <see cref="IStateEnable"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableTransform : IAssignable public interface IAssignableTransform : IAssignable
{ {
/// <summary> /// <summary>
/// Event triggered when the <see cref="ITransform"/> value has has been assigned a new value. /// Callback triggered when the <see cref="ITransform"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableTransform>? OnTransformAssigned { get; set; } Action<IAssignableTransform>? OnTransformAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableTransform : IAssignable
ITransform Transform { get; } ITransform Transform { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="ITransform"/> field of this object. /// Assign a value to the <see cref="ITransform"/> field of this object
/// </summary> /// </summary>
/// <param name="transform">New <see cref="ITransform"/> to assign.</param> /// <param name="transform">New <see cref="ITransform"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -1,105 +0,0 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
public abstract class BaseEntity : IEntity
{
public Action<IEntity, string>? OnIdChanged { get; set; } = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
private IStateEnable _stateEnable = null!;
private bool _initialized = false;
private string _id = string.Empty;
public virtual IStateEnable StateEnable => _stateEnable;
public virtual bool IsActive => StateEnable.Enabled;
public string Id
{
get => _id;
set
{
if (value == _id)
return;
string previousId = _id;
_id = value;
OnIdChanged?.Invoke(this, previousId);
}
}
public bool Initialized
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
public bool Assign(IStateEnable stateEnable)
{
if (Initialized)
return false;
_stateEnable = stateEnable;
_stateEnable.Assign(this);
OnStateEnableAssigned?.Invoke(this);
return true;
}
protected virtual void UnassignInternal() { }
public bool Unassign()
{
if (Initialized)
return false;
UnassignInternal();
OnUnassigned?.Invoke(this);
return true;
}
protected virtual void InitializeInternal() { }
public bool Initialize()
{
if (Initialized)
return false;
InitializeInternal();
Initialized = true;
return true;
}
protected virtual void FinalizeInternal() { }
public bool Finalize()
{
if (!Initialized)
return false;
FinalizeInternal();
Initialized = false;
return true;
}
protected BaseEntity() => _id = Guid.NewGuid().ToString("D");
protected BaseEntity(string id) => _id = id;
}

View File

@ -3,22 +3,22 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
/// Represents a behaviour that any object in the game might use to interact with itself or other objects. /// Responsible for every behaviour an object in the game might have, controlled by <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignableStateEnable, IInitialize public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignableStateEnable, IInitialize
{ {
/// <summary> /// <summary>
/// Event triggered when the priority of the <see cref="IBehaviour"/> changes. /// Callback triggered when the <see cref="Priority"/> has changed.
/// </summary> /// </summary>
Action<IBehaviour>? OnPriorityChanged { get; set; } Action<IBehaviour>? OnPriorityChanged { get; set; }
/// <summary> /// <summary>
/// The priority of the <see cref="IBehaviour"/>. /// Call priority of the <see cref="IBehaviour"/>.
/// </summary> /// </summary>
int Priority { get; set; } int Priority { get; set; }
/// <summary> /// <summary>
/// The value indicating whether the <see cref="IBehaviour"/> is active. /// If the <see cref="IBehaviour"/> is active.
/// </summary> /// </summary>
bool IsActive { get; } bool IsActive { get; }
} }

View File

@ -5,101 +5,89 @@ using System.Diagnostics.CodeAnalysis;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
/// Represents a controller for managing <see cref="IBehaviour"/>s and notify them accordingly about the engine's updates. Connected to an <see cref="IGameObject"/>. /// Responsible for controlling <see cref="IBehaviour"/>s and notify them accordingly about the engine's updates. Connected to an <see cref="IGameObject"/>.
/// </summary> /// </summary>
public interface IBehaviourController : IAssignableGameObject, IEnumerable<IBehaviour> public interface IBehaviourController : IAssignableGameObject
{ {
/// <summary> /// <summary>
/// Event triggered before the update of <see cref="IBehaviour"/>s. /// Callback triggered when the <see cref="Update()"/> is called but right before the <see cref="OnUpdate"/> action is triggered.
/// </summary> /// </summary>
Action<IBehaviourController>? OnPreUpdate { get; set; } Action<IBehaviourController>? OnPreUpdate { get; set; }
/// <summary> /// <summary>
/// Event triggered during the update of <see cref="IBehaviour"/>s. /// Callback triggered when the <see cref="Update()"/> is called.
/// </summary> /// </summary>
Action<IBehaviourController>? OnUpdate { get; set; } Action<IBehaviourController>? OnUpdate { get; set; }
/// <summary> /// <summary>
/// Event triggered before the drawing phase. /// Callback triggered when the <see cref="OnPreDraw()"/> is called.
/// </summary> /// </summary>
Action<IBehaviourController>? OnPreDraw { get; set; } Action<IBehaviourController>? OnPreDraw { get; set; }
/// <summary> /// <summary>
/// Event triggered when a <see cref="IBehaviour"/> is added to the <see cref="IBehaviourController"/>. /// Callback triggered when the <see cref="IBehaviourController"/> has been registered a new <see cref="IBehaviour"/>.
/// </summary> /// </summary>
Action<IBehaviourController, IBehaviour>? OnBehaviourAdded { get; set; } Action<IBehaviourController, IBehaviour>? OnBehaviourAdded { get; set; }
/// <summary> /// <summary>
/// Event triggered when a <see cref="IBehaviour"/> is removed from the <see cref="IBehaviourController"/>. /// Callback triggered when the <see cref="IBehaviourController"/> has been removed an existing <see cref="IBehaviour"/>.
/// </summary> /// </summary>
Action<IBehaviourController, IBehaviour>? OnBehaviourRemoved { get; set; } Action<IBehaviourController, IBehaviour>? OnBehaviourRemoved { get; set; }
/// <summary> /// <summary>
/// Adds a <see cref="IBehaviour"/> to the <see cref="IBehaviourController"/>. /// Registers the provided <see cref="IBehaviour"/> to be controlled by the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to add.</typeparam> /// <param name="behaviour">Uninitialized <see cref="IBehaviour"/> to be registered.</param>
/// <param name="behaviour">The <see cref="IBehaviour"/> to add.</param> /// <typeparam name="T">An implemented class of <see cref="IBehaviour"/></typeparam>
/// <returns>The added <see cref="IBehaviour"/>.</returns> /// <returns>The provided <see cref="IBehaviour"/> class after initialization.</returns>
T AddBehaviour<T>(T behaviour) where T : class, IBehaviour; T AddBehaviour<T>(T behaviour) where T : class, IBehaviour;
/// <summary> /// <summary>
/// Adds a <see cref="IBehaviour"/> of the specified type to the <see cref="IBehaviourController"/>. /// Instantiates the provided <see cref="IBehaviour"/> type and registers it to the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to add.</typeparam> /// <param name="args">Constructor parameters for the given <see cref="IBehaviour"/> class.</param>
/// <param name="args">Construction parameters for the <see cref="IBehaviour"/>.</param> /// <typeparam name="T">An implemented class of <see cref="IBehaviour"/></typeparam>
/// <returns>The added <see cref="IBehaviour"/>.</returns> /// <returns>The instantiated <see cref="IBehaviour"/> class after initialization.</returns>
T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour; T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour;
/// <summary> /// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type. /// Looks up and tries to get the <see cref="IBehaviour"/> that is controlled by the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam> /// <param name="behaviour">If return value is <see cref="true"/> outputs the class found in the <see cref="IBehaviourController"/>. If the return value is falls, this parameter is <see cref="null"/></param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, <see cref="null"/>.</returns> /// <typeparam name="T">An implemented class or <see cref="interface"/></typeparam>
T? GetBehaviour<T>(); /// <returns>
/// <see cref="true"/>, if the type of <see cref="IBehaviour"/> is present in the <see cref="IBehaviourController"/>, <see cref="false"/> if not.
/// <summary> /// </returns>
/// Tries to get a <see cref="IBehaviour"/> of the specified type.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviour">When this method returns, contains the <see cref="IBehaviour"/> of the specified type, if found; otherwise, see.</param>
/// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found; otherwise, <see cref="false"/>.</returns>
bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour); bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour);
/// <summary> /// <typeparam name="T">An implemented class or <see cref="interface"/>.</typeparam>
/// Gets all <see cref="IBehaviour"/>s of the specified type. /// <returns>Returns a list of all the matching <see cref="IBehaviour"/>s found in the <see cref="IBehaviourController"/>.</returns>
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <returns>A list of <see cref="IBehaviour"/>s of the specified type.</returns>
IList<T> GetBehaviours<T>(); IList<T> GetBehaviours<T>();
/// <summary> /// <summary>
/// Gets all <see cref="IBehaviour"/>s of the specified type and stores them in the provided list. /// Removes the <see cref="IBehaviour"/> found in the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam> /// <param name="removeAll">If all of the instances of the given Type is to be removed or not.</param>
/// <param name="behaviours">The list to store the <see cref="IBehaviour"/>s.</param> /// <typeparam name="T">An implemented class or <see cref="interface"/> of <see cref="IBehaviour"/></typeparam>
void GetBehaviours<T>(List<T> behaviours);
/// <summary>
/// Removes <see cref="IBehaviour"/>s of the specified type from the <see cref="IBehaviourController"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to remove.</typeparam>
/// <param name="removeAll">A flag indicating whether to remove all <see cref="IBehaviour"/>s of the specified type.</param>
void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour; void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour;
/// <summary> /// <summary>
/// Removes the specified <see cref="IBehaviour"/> from the <see cref="IBehaviourController"/>. /// Removes the <see cref="IBehaviour"/> found in the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to remove.</typeparam> /// <param name="removeAll">If all of the instances of the given Type is to be removed or not.</param>
/// <param name="behaviour">The <see cref="IBehaviour"/> to remove.</param> /// <typeparam name="T">An implemented class or <see cref="interface"/> of <see cref="IBehaviour"/></typeparam>
void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour; void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour;
/// <summary> /// <summary>
/// Updates all <see cref="IBehaviour"/>s in the <see cref="IBehaviourController"/>. /// To be called in every frame of the engine. Responsible for notifying <see cref="IBehaviour"/>'s under the <see cref="IBehaviourController"/>'s control that a new frame is happening.
/// </summary> /// </summary>
/// <param name=""><see cref=""/> information from the game.</param>
void Update(); void Update();
/// <summary> /// <summary>
/// Performs pre-draw operations. /// To be called before every draw call from the engine. Responsible for notifying <see cref="IBehaviour"/>'s under the <see cref="IBehaviourController"/>'s control that the engine is about to start drawing into the screen.
/// </summary> /// </summary>
/// <param name=""><see cref=""/> information from the game.</param>
void UpdatePreDraw(); void UpdatePreDraw();
} }

View File

@ -1,26 +0,0 @@
namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents a 2D camera in the engine.
/// </summary>
public interface ICamera2D : IBehaviour, IAssignableTransform
{
/// <summary>
/// The zoom level of the camera.
/// </summary>
float Zoom { get; set; }
/// <summary>
/// Converts a position from screen coordinates to world coordinates.
/// </summary>
/// <param name="screenPosition">The position in screen coordinates.</param>
/// <returns>The position in world coordinates.</returns>
Vector2D ScreenToWorldPosition(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(Vector2D worldPosition);
}

View File

@ -1,20 +1,5 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents a basic entity in the engine.
/// </summary>
public interface IEntity : IInitialize, IAssignableStateEnable public interface IEntity : IInitialize, IAssignableStateEnable
{ {
/// <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>
Action<IEntity, string>? OnIdChanged { get; set; }
/// <summary>
/// The ID of the <see cref="IEntity"/>.
/// </summary>
string Id { get; set; }
} }

View File

@ -3,55 +3,19 @@ using System.Collections.Generic;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents a game world responsible for managing <see cref="IGameObject"/>s.
/// </summary>
public interface IGameManager : IEntity, IEnumerable<IGameObject> public interface IGameManager : IEntity, IEnumerable<IGameObject>
{ {
/// <summary> Action<GameManager, IGameObject>? OnGameObjectRegistered { get; set; }
/// Event triggered when a <see cref="IGameObject"/> is registered to the <see cref="IGameManager"/>. Action<GameManager, IGameObject>? OnGameObjectUnRegistered { get; set; }
/// </summary>
Action<IGameManager, IGameObject>? OnGameObjectRegistered { get; set; }
/// <summary>
/// Event triggered when a <see cref="IGameObject"/> is unregistered from the <see cref="IGameManager"/>.
/// </summary>
Action<IGameManager, IGameObject>? OnGameObjectUnRegistered { get; set; }
/// <summary>
/// Gets a read-only list of <see cref="IGameObject"/>s managed by the <see cref="IGameManager"/>.
/// </summary>
IReadOnlyList<IGameObject> GameObjects { get; } IReadOnlyList<IGameObject> GameObjects { get; }
/// <summary>
/// Registers a <see cref="IGameObject"/> to the <see cref="IGameManager"/>.
/// </summary>
/// <param name="gameObject">The <see cref="IGameObject"/> to register.</param>
void RegisterGameObject(IGameObject gameObject); void RegisterGameObject(IGameObject gameObject);
/// <summary>
/// Instantiates a <see cref="IGameObject"/> of type T with the given arguments and registers it to the <see cref="IGameManager"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="IGameObject"/> to instantiate.</typeparam>
/// <param name="args">Constructor parameters for the given type of <see cref="IGameObject"/>.</param>
/// <returns>The instantiated <see cref="IGameObject"/>.</returns>
T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject; T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject;
/// <summary>
/// Removes a <see cref="IGameObject"/> from the <see cref="IGameManager"/>.
/// </summary>
/// <param name="gameObject">The <see cref="IGameObject"/> to remove.</param>
/// <returns>The removed <see cref="IGameObject"/>.</returns>
IGameObject RemoveGameObject(IGameObject gameObject); IGameObject RemoveGameObject(IGameObject gameObject);
/// <summary>
/// Updates the <see cref="IGameManager"/> with the given engine time data.
/// </summary>
/// <param name="time">The engine time.</param>
void Update(EngineTime time); void Update(EngineTime time);
/// <summary>
/// Performs operations that should be done before the draw calls.
/// </summary>
void PreDraw(); void PreDraw();
} }

View File

@ -2,18 +2,9 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> public interface IGameObject : IEntity, IAssignableTransform, IAssignableBehaviourController, INameable, IInitialize
/// Represents a game object with various properties and functionalities.
/// </summary>
public interface IGameObject : IEntity, IAssignableGameManager, IAssignableTransform, IAssignableBehaviourController, INameable, IInitialize
{ {
/// <summary>
/// Event triggered when the <see cref="Update"/> method is called.
/// </summary>
Action<IGameObject>? OnUpdated { get; set; } Action<IGameObject>? OnUpdated { get; set; }
/// <summary>
/// Updates the game object.
/// </summary>
void Update(); void Update();
} }

View File

@ -2,35 +2,12 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents an entity that can be initialized and finalized. This information is useful for objects we know that are not in use and can be either recycled or dropped for garbage collection.
/// </summary>
public interface IInitialize public interface IInitialize
{ {
/// <summary>
/// Event triggered when the <see cref="Initialize"/> method is called successfully.
/// </summary>
Action<IInitialize>? OnInitialized { get; set; } Action<IInitialize>? OnInitialized { get; set; }
/// <summary>
/// Event triggered when the <see cref="Finalize"/> method is called successfully.
/// </summary>
Action<IInitialize>? OnFinalized { get; set; } Action<IInitialize>? OnFinalized { get; set; }
/// <summary>
/// The value indicating whether the entity has been initialized.
/// </summary>
bool Initialized { get; } bool Initialized { get; }
/// <summary>
/// Initializes the entity.
/// </summary>
/// <returns><see cref="true"/> if initialization is successful, otherwise <see cref="false"/>.</returns>
bool Initialize(); bool Initialize();
/// <summary>
/// Finalizes the entity so it can either be recycled or garbage collected.
/// </summary>
/// <returns><see cref="true"/> if finalization is successful, otherwise <see cref="false"/>.</returns>
bool Finalize(); bool Finalize();
} }

View File

@ -2,18 +2,8 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents an entity with a name.
/// </summary>
public interface INameable public interface INameable
{ {
/// <summary>
/// Event triggered when the name of the entity changes.
/// </summary>
Action<IEntity>? OnNameChanged { get; set; } Action<IEntity>? OnNameChanged { get; set; }
/// <summary>
/// The name of the entity.
/// </summary>
string Name { get; set; } string Name { get; set; }
} }

View File

@ -2,18 +2,8 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents an entity with an enable state that can be toggled.
/// </summary>
public interface IStateEnable : IAssignableEntity public interface IStateEnable : IAssignableEntity
{ {
/// <summary>
/// Event triggered when the <see cref="Enabled"/> state of the <see cref="IStateEnable"/> changes.
/// </summary>
Action<IStateEnable>? OnEnabledChanged { get; set; } Action<IStateEnable>? OnEnabledChanged { get; set; }
/// <summary>
/// The value indicating whether the <see cref="IStateEnable"/> is enabled.
/// </summary>
bool Enabled { get; set; } bool Enabled { get; set; }
} }

View File

@ -2,38 +2,14 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents the transformation properties of an object such as position, scale, and rotation.
/// </summary>
public interface ITransform public interface ITransform
{ {
/// <summary>
/// Event triggered when the <see cref="Position"/> of the <see cref="ITransform"/> changes.
/// </summary>
Action<ITransform>? OnPositionChanged { get; set; } Action<ITransform>? OnPositionChanged { get; set; }
/// <summary>
/// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform"/> changes.
/// </summary>
Action<ITransform>? OnScaleChanged { get; set; } Action<ITransform>? OnScaleChanged { get; set; }
/// <summary>
/// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform"/> changes.
/// </summary>
Action<ITransform>? OnRotationChanged { get; set; } Action<ITransform>? OnRotationChanged { get; set; }
/// <summary>
/// The position of the <see cref="ITransform"/> in 2D space.
/// </summary>
Vector2D Position { get; set; } Vector2D Position { get; set; }
/// <summary>
/// The scale of the <see cref="ITransform"/>.
/// </summary>
Vector2D Scale { get; set; } Vector2D Scale { get; set; }
/// <summary>
/// The rotation of the <see cref="ITransform"/> in degrees.
/// </summary>
float Rotation { get; set; } float Rotation { get; set; }
} }

View File

@ -6,20 +6,43 @@ using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")] [System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
public abstract class Behaviour : BaseEntity, IBehaviour public abstract class Behaviour : IBehaviour
{ {
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null; public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
public Action<IBehaviour>? OnPriorityChanged { get; set; } = null; public Action<IBehaviour>? OnPriorityChanged { get; set; } = null;
private IBehaviourController _behaviourController = null!; private IBehaviourController _behaviourController = null!;
private IStateEnable _stateEnable = null!;
private bool _initialized = false;
private int _priority = 0; private int _priority = 0;
public IStateEnable StateEnable => _stateEnable;
public IBehaviourController BehaviourController => _behaviourController; public IBehaviourController BehaviourController => _behaviourController;
public override bool IsActive => base.IsActive && BehaviourController.GameObject.StateEnable.Enabled; public bool IsActive => StateEnable.Enabled && BehaviourController.GameObject.StateEnable.Enabled;
public bool Initialized
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
public int Priority public int Priority
{ {
@ -34,6 +57,17 @@ public abstract class Behaviour : BaseEntity, IBehaviour
} }
} }
public bool Assign(IStateEnable stateEnable)
{
if (Initialized)
return false;
_stateEnable = stateEnable;
_stateEnable.Assign(this);
OnStateEnableAssigned?.Invoke(this);
return true;
}
public bool Assign(IBehaviourController behaviourController) public bool Assign(IBehaviourController behaviourController)
{ {
if (Initialized) if (Initialized)
@ -44,16 +78,36 @@ public abstract class Behaviour : BaseEntity, IBehaviour
return true; return true;
} }
protected override void UnassignInternal() public bool Unassign()
{ {
base.UnassignInternal(); if (Initialized)
return false;
_stateEnable = null!;
_behaviourController = null!; _behaviourController = null!;
OnUnassigned?.Invoke(this);
return true;
} }
protected override void InitializeInternal() public bool Initialize()
{ {
base.InitializeInternal(); if (Initialized)
return false;
NotAssignedException.Check(this, _behaviourController); NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, StateEnable); NotAssignedException.Check(this, _stateEnable);
Initialized = true;
return true;
}
public bool Finalize()
{
if (!Initialized)
return false;
Initialized = false;
return true;
} }
} }

View File

@ -1,102 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T>
{
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null;
public Action<BehaviourCacher<T>, T>? OnCached { get; set; } = null;
public Action<BehaviourCacher<T>, T>? OnUncached { get; set; } = null;
private readonly List<T> _behaviours = new(32);
public IReadOnlyList<T> Behaviours => _behaviours;
public IGameManager GameManager { get; private set; } = null!;
public T this[Index index] => _behaviours[index];
public BehaviourCacher() { }
public BehaviourCacher(IGameManager gameManager) => Assign(gameManager);
private void OnGameObjectRegistered(IGameManager manager, IGameObject gameObject)
{
gameObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
gameObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
}
private void OnGameObjectUnregistered(IGameManager manager, IGameObject gameObject)
{
gameObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded;
gameObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved;
}
private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not T tBehaviour)
return;
_behaviours.Add(tBehaviour);
OnCached?.Invoke(this, tBehaviour);
}
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not T tBehaviour)
return;
if (!_behaviours.Remove(tBehaviour))
return;
OnUncached?.Invoke(this, tBehaviour);
}
public bool Assign(IGameManager gameManager)
{
if (GameManager is not null)
return false;
foreach (IGameObject gameObject in gameManager)
{
OnGameObjectRegistered(gameManager, gameObject);
foreach (IBehaviour behaviour in gameObject.BehaviourController)
OnBehaviourAdded(gameObject.BehaviourController, behaviour);
}
gameManager.OnGameObjectRegistered += OnGameObjectRegistered;
gameManager.OnGameObjectUnRegistered += OnGameObjectUnregistered;
GameManager = gameManager;
OnGameManagerAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (GameManager is null)
return false;
foreach (IGameObject gameObject in GameManager)
{
OnGameObjectUnregistered(GameManager, gameObject);
foreach (IBehaviour behaviour in gameObject.BehaviourController)
OnBehaviourRemoved(gameObject.BehaviourController, behaviour);
}
GameManager.OnGameObjectRegistered -= OnGameObjectRegistered;
GameManager.OnGameObjectUnRegistered -= OnGameObjectUnregistered;
GameManager = null!;
OnUnassigned?.Invoke(this);
return true;
}
public IEnumerator<T> GetEnumerator() => _behaviours.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _behaviours.GetEnumerator();
}

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
@ -41,19 +40,19 @@ public class BehaviourController : IBehaviourController
public T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour public T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour
=> AddBehaviour(new Factory.BehaviourFactory().Instantiate<T>(_gameObject, args)); => AddBehaviour(new Factory.BehaviourFactory().Instantiate<T>(_gameObject, args));
public T? GetBehaviour<T>()
{
foreach (var behaviourItem in behaviours)
if (behaviourItem is T result)
return result;
return default;
}
public bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour) public bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour)
{ {
behaviour = GetBehaviour<T>(); foreach (var behaviourItem in behaviours)
return behaviour is not null; {
if (behaviourItem is not T result)
continue;
behaviour = result;
return true;
}
behaviour = default;
return false;
} }
public IList<T> GetBehaviours<T>() public IList<T> GetBehaviours<T>()
@ -71,18 +70,6 @@ public class BehaviourController : IBehaviourController
return behaviours ?? Enumerable.Empty<T>().ToList(); return behaviours ?? Enumerable.Empty<T>().ToList();
} }
public void GetBehaviours<T>(List<T> behaviors)
{
behaviors.Clear();
foreach (var behaviourItem in behaviours)
{
if (behaviourItem is not T _)
continue;
behaviours.Add(behaviourItem);
}
}
public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour
{ {
for (int i = behaviours.Count; i >= 0; i--) for (int i = behaviours.Count; i >= 0; i--)
@ -170,7 +157,4 @@ public class BehaviourController : IBehaviourController
behaviours.Remove(behaviour); behaviours.Remove(behaviour);
InsertBehaviourByPriority(behaviour); InsertBehaviourByPriority(behaviour);
} }
public IEnumerator<IBehaviour> GetEnumerator() => behaviours.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => behaviours.GetEnumerator();
} }

View File

@ -4,8 +4,6 @@ namespace Syntriax.Engine.Core;
public readonly struct EngineTime(TimeSpan Total, TimeSpan Elapsed) public readonly struct EngineTime(TimeSpan Total, TimeSpan Elapsed)
{ {
public readonly TimeSpan Total = Total; public readonly TimeSpan Total { get; init; } = Total;
public readonly TimeSpan Elapsed = Elapsed; public readonly TimeSpan Elapsed { get; init; } = Elapsed;
public readonly float DeltaTimeFrame = (float)Elapsed.TotalMilliseconds;
} }

View File

@ -1,32 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class BehaviourExtensions
{
public static bool TryFindBehaviour<T>(this IEnumerable<IGameObject> gameObjects, [NotNullWhen(returnValue: true)] out T? behaviour)
{
behaviour = default;
foreach (IGameObject gameObject in gameObjects)
if (gameObject.BehaviourController.TryGetBehaviour(out behaviour))
return true;
return false;
}
public static void FindBehaviours<T>(this IEnumerable<IGameObject> gameObjects, List<T> behaviours)
{
behaviours.Clear();
List<T> cache = [];
foreach (IGameObject gameObject in gameObjects)
{
gameObject.BehaviourController.GetBehaviours(cache);
behaviours.AddRange(cache);
}
}
}

View File

@ -1,9 +0,0 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class GameManagerExtensions
{
public static IGameObject InstantiateGameObject(this IGameManager gameManager, params object?[]? args)
=> gameManager.InstantiateGameObject<GameObject>(args);
}

View File

@ -1,12 +0,0 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class GameObjectExtensions
{
public static IGameObject SetGameObject(this IGameObject gameObject, string name)
{
gameObject.Name = name;
return gameObject;
}
}

View File

@ -1,14 +0,0 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class TransformExtensions
{
public static ITransform SetTransform(this ITransform transform, Vector2D? position = null, float? rotation = null, Vector2D? scale = null)
{
if (position.HasValue) transform.Position = position.Value;
if (rotation.HasValue) transform.Rotation = rotation.Value;
if (scale.HasValue) transform.Scale = scale.Value;
return transform;
}
}

View File

@ -1,188 +1,35 @@
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
/// <summary>
/// Provides extension methods for <see cref="Vector2D"/> type.
/// </summary>
public static class Vector2DExtensions public static class Vector2DExtensions
{ {
/// <summary>
/// Calculates the length of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The length of the <see cref="Vector2D"/>.</returns>
public static float Length(this Vector2D vector) => Vector2D.Length(vector); public static float Length(this Vector2D vector) => Vector2D.Length(vector);
/// <summary>
/// Calculates the squared length of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The squared length of the <see cref="Vector2D"/>.</returns>
public static float LengthSquared(this Vector2D vector) => Vector2D.LengthSquared(vector); public static float LengthSquared(this Vector2D vector) => Vector2D.LengthSquared(vector);
/// <summary>
/// Calculates the distance between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Vector2D"/>.</param>
/// <param name="to">The ending <see cref="Vector2D"/>.</param>
/// <returns>The distance between the two <see cref="Vector2D"/>s.</returns>
public static float Distance(this Vector2D from, Vector2D to) => Vector2D.Distance(from, to); public static float Distance(this Vector2D from, Vector2D to) => Vector2D.Distance(from, to);
/// <summary>
/// Returns the <see cref="Vector2D"/> with its components inverted.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The inverted <see cref="Vector2D"/>.</returns>
public static Vector2D Invert(this Vector2D vector) => Vector2D.Invert(vector); public static Vector2D Invert(this Vector2D vector) => Vector2D.Invert(vector);
/// <summary>
/// Adds two <see cref="Vector2D"/>s component-wise.
/// </summary>
/// <param name="vector">The first <see cref="Vector2D"/>.</param>
/// <param name="vectorToAdd">The vector <see cref="Vector2D"/> to be added.</param>
/// <returns>The result of the addition.</returns>
public static Vector2D Add(this Vector2D vector, Vector2D vectorToAdd) => Vector2D.Add(vector, vectorToAdd); public static Vector2D Add(this Vector2D vector, Vector2D vectorToAdd) => Vector2D.Add(vector, vectorToAdd);
/// <summary>
/// Subtracts one <see cref="Vector2D"/> from another component-wise.
/// </summary>
/// <param name="vector">The first <see cref="Vector2D"/>.</param>
/// <param name="vectorToSubtract">The <see cref="Vector2D"/> to be subtracted.</param>
/// <returns>The result of the subtraction.</returns>
public static Vector2D Subtract(this Vector2D vector, Vector2D vectorToSubtract) => Vector2D.Subtract(vector, vectorToSubtract); public static Vector2D Subtract(this Vector2D vector, Vector2D vectorToSubtract) => Vector2D.Subtract(vector, vectorToSubtract);
/// <summary>
/// Multiplies a <see cref="Vector2D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to multiply.</param>
/// <param name="value">The scalar value to multiply with.</param>
/// <returns>The result of the multiplication.</returns>
public static Vector2D Multiply(this Vector2D vector, float value) => Vector2D.Multiply(vector, value); public static Vector2D Multiply(this Vector2D vector, float value) => Vector2D.Multiply(vector, value);
public static Vector2D Subdivide(this Vector2D vector, float value) => Vector2D.Subdivide(vector, value);
/// <summary>
/// Divides a <see cref="Vector2D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to divide.</param>
/// <param name="value">The scalar value to divide with.</param>
/// <returns>The result of the division.</returns>
public static Vector2D Divide(this Vector2D vector, float value) => Vector2D.Divide(vector, value);
/// <summary>
/// Returns a <see cref="Vector2D"/> with the absolute values of each component.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> with absolute values.</returns>
public static Vector2D Abs(this Vector2D vector) => Vector2D.Abs(vector); public static Vector2D Abs(this Vector2D vector) => Vector2D.Abs(vector);
/// <summary>
/// Reflects a <see cref="Vector2D"/> off a surface with the specified normal.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to reflect.</param>
/// <param name="normal">The normal <see cref="Vector2D"/> of the reflecting surface.</param>
/// <returns>The reflected <see cref="Vector2D"/>.</returns>
public static Vector2D Reflect(this Vector2D vector, Vector2D normal) => Vector2D.Reflect(vector, normal); public static Vector2D Reflect(this Vector2D vector, Vector2D normal) => Vector2D.Reflect(vector, normal);
/// <summary>
/// Normalizes the <see cref="Vector2D"/> (creates a <see cref="Vector2D"/> with the same direction but with a length of 1).
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The normalized <see cref="Vector2D"/>.</returns>
public static Vector2D Normalize(this Vector2D vector) => Vector2D.Normalize(vector); public static Vector2D Normalize(this Vector2D vector) => Vector2D.Normalize(vector);
/// <summary>
/// Creates a <see cref="Vector2D"/> pointing from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="Vector2D"/> pointing from <paramref name="from"/> to <paramref name="to"/>.</returns>
public static Vector2D FromTo(this Vector2D from, Vector2D to) => Vector2D.FromTo(from, to); public static Vector2D FromTo(this Vector2D from, Vector2D to) => Vector2D.FromTo(from, to);
/// <summary>
/// Scales a <see cref="Vector2D"/> by another <see cref="Vector2D"/> component-wise.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to scale.</param>
/// <param name="scale">The <see cref="Vector2D"/> containing the scaling factors for each component.</param>
/// <returns>The scaled <see cref="Vector2D"/>.</returns>
public static Vector2D Scale(this Vector2D vector, Vector2D scale) => Vector2D.Scale(vector, scale); public static Vector2D Scale(this Vector2D vector, Vector2D scale) => Vector2D.Scale(vector, scale);
/// <summary>
/// Calculates the perpendicular <see cref="Vector2D"/> to the given <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>A <see cref="Vector2D"/> perpendicular to the input <see cref="Vector2D"/>.</returns>
public static Vector2D Perpendicular(this Vector2D vector) => Vector2D.Perpendicular(vector); public static Vector2D Perpendicular(this Vector2D vector) => Vector2D.Perpendicular(vector);
/// <summary>
/// Rotates a <see cref="Vector2D"/> by the specified angle (in radians).
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to rotate.</param>
/// <param name="angleInRadian">The angle to rotate by, in radians.</param>
/// <returns>The rotated <see cref="Vector2D"/>.</returns>
public static Vector2D Rotate(this Vector2D vector, float angleInRadian) => Vector2D.Rotate(vector, angleInRadian); public static Vector2D Rotate(this Vector2D vector, float angleInRadian) => Vector2D.Rotate(vector, angleInRadian);
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> containing the minimum components from both input <see cref="Vector2D"/>s.</returns>
public static Vector2D Min(this Vector2D left, Vector2D right) => Vector2D.Min(left, right); public static Vector2D Min(this Vector2D left, Vector2D right) => Vector2D.Min(left, right);
/// <summary>
/// Returns the component-wise maximum of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> containing the maximum components from both input <see cref="Vector2D"/>s.</returns>
public static Vector2D Max(this Vector2D left, Vector2D right) => Vector2D.Max(left, right); public static Vector2D Max(this Vector2D left, Vector2D right) => Vector2D.Max(left, right);
/// <summary>
/// Clamps each component of a <see cref="Vector2D"/> between the corresponding component of two other <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to clamp.</param>
/// <param name="min">The <see cref="Vector2D"/> representing the minimum values for each component.</param>
/// <param name="max">The <see cref="Vector2D"/> representing the maximum values for each component.</param>
/// <returns>The clamped <see cref="Vector2D"/>.</returns>
public static Vector2D Clamp(this Vector2D vector, Vector2D min, Vector2D max) => Vector2D.Clamp(vector, min, max); public static Vector2D Clamp(this Vector2D vector, Vector2D min, Vector2D max) => Vector2D.Clamp(vector, min, max);
/// <summary>
/// Linearly interpolates between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="from">The start <see cref="Vector2D"/>.</param>
/// <param name="to">The end <see cref="Vector2D"/>.</param>
/// <param name="t">The interpolation parameter (between 0 and 1).</param>
/// <returns>The interpolated <see cref="Vector2D"/>.</returns>
public static Vector2D Lerp(this Vector2D from, Vector2D to, float t) => Vector2D.Lerp(from, to, t); public static Vector2D Lerp(this Vector2D from, Vector2D to, float t) => Vector2D.Lerp(from, to, t);
/// <summary>
/// Calculates the cross product of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The cross product of the two <see cref="Vector2D"/>s.</returns>
public static float Cross(this Vector2D left, Vector2D right) => Vector2D.Cross(left, right); public static float Cross(this Vector2D left, Vector2D right) => Vector2D.Cross(left, right);
/// <summary>
/// Calculates the angle in radians between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The angle between the two <see cref="Vector2D"/>s in radians.</returns>
public static float AngleBetween(this Vector2D left, Vector2D right) => Vector2D.Angle(left, right); public static float AngleBetween(this Vector2D left, Vector2D right) => Vector2D.Angle(left, right);
/// <summary>
/// Calculates the dot product of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The dot product of the two <see cref="Vector2D"/>s.</returns>
public static float Dot(this Vector2D left, Vector2D right) => Vector2D.Dot(left, right); public static float Dot(this Vector2D left, Vector2D right) => Vector2D.Dot(left, right);
/// <summary>
/// Checks whether two <see cref="Vector2D"/>s are approximately equal within a certain epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <param name="epsilon">The maximum difference allowed between components.</param>
/// <returns>True if the <see cref="Vector2D"/>s are approximately equal, false otherwise.</returns>
public static bool ApproximatelyEquals(this Vector2D left, Vector2D right, float epsilon = float.Epsilon) => Vector2D.ApproximatelyEquals(left, right, epsilon); public static bool ApproximatelyEquals(this Vector2D left, Vector2D right, float epsilon = float.Epsilon) => Vector2D.ApproximatelyEquals(left, right, epsilon);
} }

View File

@ -9,15 +9,22 @@ using Syntriax.Engine.Core.Factory;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("GameObject Count: {_gameObjects.Count}")] [System.Diagnostics.DebuggerDisplay("GameObject Count: {_gameObjects.Count}")]
public class GameManager : BaseEntity, IGameManager public class GameManager : IGameManager
{ {
public Action<IGameManager, IGameObject>? OnGameObjectRegistered { get; set; } = null; public Action<GameManager, IGameObject>? OnGameObjectRegistered { get; set; } = null;
public Action<IGameManager, IGameObject>? OnGameObjectUnRegistered { get; set; } = null; public Action<GameManager, IGameObject>? OnGameObjectUnRegistered { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
private readonly List<IGameObject> _gameObjects = new(Constants.GAME_OBJECTS_SIZE_INITIAL); private readonly List<IGameObject> _gameObjects = new(Constants.GAME_OBJECTS_SIZE_INITIAL);
private IStateEnable _stateEnable = null!;
private GameObjectFactory _gameObjectFactory = null!; private GameObjectFactory _gameObjectFactory = null!;
private bool _initialized = false;
private GameObjectFactory GameObjectFactory private GameObjectFactory GameObjectFactory
{ {
@ -29,20 +36,21 @@ public class GameManager : BaseEntity, IGameManager
} }
} }
public bool Initialized => _initialized;
public IReadOnlyList<IGameObject> GameObjects => _gameObjects; public IReadOnlyList<IGameObject> GameObjects => _gameObjects;
public override IStateEnable StateEnable public IStateEnable StateEnable
{ {
get get
{ {
if (base.StateEnable is null) if (_stateEnable is null)
{ {
Assign(new StateEnableFactory().Instantiate(this)); Assign(new StateEnableFactory().Instantiate(this));
if (base.StateEnable is null) if (_stateEnable is null)
throw NotAssignedException.From(this, base.StateEnable); throw NotAssignedException.From(this, _stateEnable);
} }
return base.StateEnable; return _stateEnable;
} }
} }
@ -70,20 +78,52 @@ public class GameManager : BaseEntity, IGameManager
return gameObject; return gameObject;
} }
protected override void InitializeInternal() public bool Initialize()
{ {
base.InitializeInternal(); if (Initialized)
return false;
NotAssignedException.Check(this, StateEnable); NotAssignedException.Check(this, StateEnable);
foreach (var gameObject in GameObjects) foreach (var gameObject in GameObjects)
gameObject.Initialize(); gameObject.Initialize();
_initialized = true;
OnInitialized?.Invoke(this);
return true;
} }
protected override void FinalizeInternal() public bool Finalize()
{ {
base.FinalizeInternal(); if (!Initialized)
return false;
for (int i = GameObjects.Count; i >= 0; i--) for (int i = GameObjects.Count; i >= 0; i--)
GameObjects[i].Finalize(); GameObjects[i].Finalize();
OnFinalized?.Invoke(this);
_initialized = false;
return true;
}
public bool Assign(IStateEnable stateEnable)
{
if (Initialized)
return false;
_stateEnable = stateEnable;
OnStateEnableAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Initialized)
return false;
_stateEnable = null!;
OnUnassigned?.Invoke(this);
return true;
} }
public void Update(EngineTime time) public void Update(EngineTime time)
@ -103,8 +143,6 @@ public class GameManager : BaseEntity, IGameManager
private void Register(IGameObject gameObject) private void Register(IGameObject gameObject)
{ {
gameObject.Assign(this);
gameObject.OnFinalized += OnGameObjectFinalize; gameObject.OnFinalized += OnGameObjectFinalize;
_gameObjects.Add(gameObject); _gameObjects.Add(gameObject);

View File

@ -6,27 +6,47 @@ using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("Name: {Name}, Initialized: {Initialized}")] [System.Diagnostics.DebuggerDisplay("Name: {Name}, Initialized: {Initialized}")]
public class GameObject : BaseEntity, IGameObject public class GameObject : IGameObject
{ {
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
public Action<IAssignableTransform>? OnTransformAssigned { get; set; } = null; public Action<IAssignableTransform>? OnTransformAssigned { get; set; } = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null; public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null;
public Action<IEntity>? OnNameChanged { get; set; } = null; public Action<IEntity>? OnNameChanged { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
public Action<IGameObject>? OnUpdated { get; set; } = null; public Action<IGameObject>? OnUpdated { get; set; } = null;
private ITransform _transform = null!; private ITransform _transform = null!;
private IBehaviourController _behaviourController = null!; private IBehaviourController _behaviourController = null!;
private IStateEnable _stateEnable = null!; private IStateEnable _stateEnable = null!;
private IGameManager _gameManager = null!;
private string _name = nameof(GameObject); private string _name = nameof(GameObject);
private bool _initialized = false;
public ITransform Transform => _transform; public ITransform Transform => _transform;
public IBehaviourController BehaviourController => _behaviourController; public IBehaviourController BehaviourController => _behaviourController;
public IGameManager GameManager => _gameManager; public IStateEnable StateEnable => _stateEnable;
public bool Initialized
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
public string Name public string Name
{ {
@ -40,14 +60,17 @@ public class GameObject : BaseEntity, IGameObject
} }
} }
protected override void InitializeInternal() public bool Initialize()
{ {
base.InitializeInternal(); if (Initialized)
return false;
NotAssignedException.Check(this, _transform); NotAssignedException.Check(this, _transform);
NotAssignedException.Check(this, _behaviourController); NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, _stateEnable); NotAssignedException.Check(this, _stateEnable);
NotAssignedException.Check(this, _gameManager);
Initialized = true;
return true;
} }
public void Update() public void Update()
@ -58,12 +81,29 @@ public class GameObject : BaseEntity, IGameObject
OnUpdated?.Invoke(this); OnUpdated?.Invoke(this);
} }
protected override void FinalizeInternal() public bool Finalize()
{ {
base.FinalizeInternal(); if (!Initialized)
return false;
foreach (IBehaviour behaviour in _behaviourController.GetBehaviours<IBehaviour>())
behaviour.Finalize(); System.Threading.Tasks.Parallel.ForEach(
_behaviourController.GetBehaviours<IBehaviour>(),
behaviour => behaviour.Finalize()
);
Initialized = false;
return true;
}
public bool Assign(IStateEnable stateEnable)
{
if (Initialized)
return false;
_stateEnable = stateEnable;
OnStateEnableAssigned?.Invoke(this);
return true;
} }
public bool Assign(ITransform transform) public bool Assign(ITransform transform)
@ -86,24 +126,17 @@ public class GameObject : BaseEntity, IGameObject
return true; return true;
} }
public bool Assign(IGameManager gameManager) public bool Unassign()
{ {
if (Initialized) if (Initialized)
return false; return false;
_gameManager = gameManager;
OnGameManagerAssigned?.Invoke(this);
return true;
}
protected override void UnassignInternal()
{
base.UnassignInternal();
_stateEnable = null!; _stateEnable = null!;
_transform = null!; _transform = null!;
_behaviourController = null!; _behaviourController = null!;
_gameManager = null!;
OnUnassigned?.Invoke(this);
return true;
} }
public GameObject() { OnBehaviourControllerAssigned += ConnectBehaviourController; } public GameObject() { OnBehaviourControllerAssigned += ConnectBehaviourController; }

View File

@ -4,187 +4,32 @@ using System.Numerics;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
public static class Math public static class Math
{/// <summary> {
/// The value of Pi (π), a mathematical constant approximately equal to 3.14159.
/// </summary>
public const float PI = 3.1415926535897932f;
/// <summary>
/// The value of Tau (τ), a mathematical constant equal to 2π, approximately equal to 6.28319.
/// </summary>
public const float Tau = 2f * PI;
/// <summary>
/// The base of the natural logarithm, approximately equal to 2.71828.
/// </summary>
public const float E = 2.718281828459045f;
/// <summary>
/// The conversion factor from radians to degrees.
/// </summary>
public const float RadianToDegree = 180f / PI; public const float RadianToDegree = 180f / PI;
/// <summary>
/// The conversion factor from degrees to radians.
/// </summary>
public const float DegreeToRadian = PI / 180f; public const float DegreeToRadian = PI / 180f;
/// <summary> public const float E = 2.718281828459045f;
/// Returns the absolute value of a number. public const float PI = 3.1415926535897932f;
/// </summary> public const float Tau = 2f * PI;
/// <typeparam name="T">The type of the number.</typeparam>
/// <param name="x">The number.</param>
/// <returns>The absolute value of <paramref name="x"/>.</returns>
public static T Abs<T>(T x) where T : INumber<T> => x > T.Zero ? x : -x; public static T Abs<T>(T x) where T : INumber<T> => x > T.Zero ? x : -x;
/// <summary>
/// Returns the arccosine of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The arccosine of <paramref name="x"/>.</returns>
public static float Acos(float x) => MathF.Acos(x); public static float Acos(float x) => MathF.Acos(x);
/// <summary>
/// Returns the arcsine of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <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 quotient of two specified numbers.
/// </summary>
/// <param name="y">The y-coordinate of a point.</param>
/// <param name="x">The x-coordinate of a point.</param>
/// <returns>The angle, measured in radians.</returns>
public static float Atan2(float y, float x) => MathF.Atan2(y, x); public static float Atan2(float y, float x) => MathF.Atan2(y, x);
/// <summary>
/// Returns the hyperbolic arctangent of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The hyperbolic arctangent of <paramref name="x"/>.</returns>
public static float Atanh(float x) => MathF.Atanh(x); public static float Atanh(float x) => MathF.Atanh(x);
/// <summary>
/// Clamps a number between a minimum and maximum value.
/// </summary>
/// <typeparam name="T">The type of the number.</typeparam>
/// <param name="x">The number to clamp.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static T Clamp<T>(this T x, T min, T max) where T : INumber<T> => (x < min) ? min : (x > max) ? max : x; public static T Clamp<T>(this T x, T min, T max) where T : INumber<T> => (x < min) ? min : (x > max) ? max : x;
/// <summary>
/// Returns the smallest integral value that is greater than or equal to the specified number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The smallest integral value that is greater than or equal to <paramref name="x"/>.</returns>
public static float Ceiling(float x) => MathF.Ceiling(x); public static float Ceiling(float x) => MathF.Ceiling(x);
/// <summary>
/// Returns a value with the magnitude of <paramref name="x"/> and the sign of <paramref name="y"/>.
/// </summary>
/// <param name="x">The magnitude value.</param>
/// <param name="y">The sign value.</param>
/// <returns>A value with the magnitude of <paramref name="x"/> and the sign of <paramref name="y"/>.</returns>
public static float CopySign(float x, float y) => MathF.CopySign(x, y); public static float CopySign(float x, float y) => MathF.CopySign(x, y);
/// <summary>
/// Returns the largest integral value that is less than or equal to the specified number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The largest integral value that is less than or equal to <paramref name="x"/>.</returns>
public static float Floor(float x) => MathF.Floor(x); public static float Floor(float x) => MathF.Floor(x);
/// <summary>
/// Returns the remainder of the division of two specified numbers.
/// </summary>
/// <param name="x">The dividend.</param>
/// <param name="y">The divisor.</param>
/// <returns>The remainder of the division of <paramref name="x"/> by <paramref name="y"/>.</returns>
public static float IEEERemainder(float x, float y) => MathF.IEEERemainder(x, y); public static float IEEERemainder(float x, float y) => MathF.IEEERemainder(x, y);
/// <summary>
/// Returns the natural (base e) logarithm of a specified number.
/// </summary>
/// <param name="x">The number.</param>
/// <param name="y">The base.</param>
/// <returns>The natural logarithm of <paramref name="x"/> with base <paramref name="y"/>.</returns>
public static float Log(float x, float y) => MathF.Log(x, y); public static float Log(float x, float y) => MathF.Log(x, y);
/// <summary>
/// Returns the larger of two numbers.
/// </summary>
/// <typeparam name="T">The type of the numbers.</typeparam>
/// <param name="x">The first number.</param>
/// <param name="y">The second number.</param>
/// <returns>The larger of <paramref name="x"/> and <paramref name="y"/>.</returns>
public static T Max<T>(T x, T y) where T : INumber<T> => (x > y) ? x : y; public static T Max<T>(T x, T y) where T : INumber<T> => (x > y) ? x : y;
public static float MaxMagnitude(float x, float y) => MathF.MaxMagnitude(x, y);
/// <summary>
/// Returns the number whose absolute value is larger.
/// </summary>
/// <param name="x">The first number.</param>
/// <param name="y">The second number.</param>
/// <returns>The number whose absolute value is larger.</returns>
public static T AbsMax<T>(T x, T y) where T : INumber<T> => (Abs(x) > Abs(y)) ? x : y;
/// <summary>
/// Returns the smaller of two numbers.
/// </summary>
/// <typeparam name="T">The type of the numbers.</typeparam>
/// <param name="x">The first number.</param>
/// <param name="y">The second number.</param>
/// <returns>The smaller of <paramref name="x"/> and <paramref name="y"/>.</returns>
public static T Min<T>(T x, T y) where T : INumber<T> => (x < y) ? x : y; public static T Min<T>(T x, T y) where T : INumber<T> => (x < y) ? x : y;
public static float MinMagnitude(float x, float y) => MathF.MinMagnitude(x, y);
/// <summary>
/// Returns the number whose absolute value is smaller.
/// </summary>
/// <param name="x">The first number.</param>
/// <param name="y">The second number.</param>
/// <returns>The number whose absolute value is smaller.</returns>
public static T AbsMin<T>(T x, T y) where T : INumber<T> => (Abs(x) < Abs(y)) ? x : y;
/// <summary>
/// Returns a specified number raised to the specified power.
/// </summary>
/// <param name="x">The number to raise to a power.</param>
/// <param name="y">The power to raise <paramref name="x"/> to.</param>
/// <returns>The number <paramref name="x"/> raised to the power <paramref name="y"/>.</returns>
public static float Pow(float x, float y) => MathF.Pow(x, y); public static float Pow(float x, float y) => MathF.Pow(x, y);
/// <summary>
/// Rounds a number to a specified number of fractional digits.
/// </summary>
/// <param name="x">The number to round.</param>
/// <param name="digits">The number of fractional digits in the return value.</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 <paramref name="digits"/> fractional digits.</returns>
public static float Round(float x, int digits, MidpointRounding mode) => MathF.Round(x, digits, mode); public static float Round(float x, int digits, MidpointRounding mode) => MathF.Round(x, digits, mode);
/// <summary>
/// Returns the square of a number.
/// </summary>
/// <typeparam name="T">The type of the number.</typeparam>
/// <param name="x">The number to square.</param>
/// <returns>The square of <paramref name="x"/>.</returns>
public static T Sqr<T>(T x) where T : INumber<T> => x * x; public static T Sqr<T>(T x) where T : INumber<T> => x * x;
/// <summary>
/// Returns the square root of a specified number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The square root of <paramref name="x"/>.</returns>
public static float Sqrt(float x) => MathF.Sqrt(x); public static float Sqrt(float x) => MathF.Sqrt(x);
/// <summary>
/// Calculates the integral part of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The integral part of <paramref name="x"/>.</returns>
public static float Truncate(float x) => MathF.Truncate(x); public static float Truncate(float x) => MathF.Truncate(x);
} }

View File

@ -9,7 +9,5 @@ public static class Time
public static TimeSpan Total => _engineTime.Total; public static TimeSpan Total => _engineTime.Total;
public static TimeSpan Elapsed => _engineTime.Elapsed; public static TimeSpan Elapsed => _engineTime.Elapsed;
public static float DeltaTimeFrame => _engineTime.DeltaTimeFrame;
public static void SetTime(EngineTime engineTime) => _engineTime = engineTime; public static void SetTime(EngineTime engineTime) => _engineTime = engineTime;
} }

View File

@ -2,65 +2,21 @@ using System;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a two-dimensional vector.
/// </summary>
[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)
{ {
/// <summary> public readonly float X { get; init; } = X;
/// The X coordinate of the <see cref="Vector2D"/>. public readonly float Y { get; init; } = Y;
/// </summary>
public readonly float X = x;
/// <summary> public readonly float Magnitude => Length(this);
/// The Y coordinate of the <see cref="Vector2D"/>. public readonly float MagnitudeSquared => LengthSquared(this);
/// </summary> public readonly Vector2D Normalized => Normalize(this);
public readonly float Y = y;
/// <summary>
/// The magnitude (length) of the <see cref="Vector2D"/>.
/// </summary>
public float Magnitude => Length(this);
/// <summary>
/// The squared magnitude (length) of the <see cref="Vector2D"/>.
/// </summary>
public float MagnitudeSquared => LengthSquared(this);
/// <summary>
/// The normalized form of the <see cref="Vector2D"/> (a <see cref="Vector2D"/> with the same direction and a magnitude of 1).
/// </summary>
public Vector2D Normalized => Normalize(this);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing upwards.
/// </summary>
public readonly static Vector2D Up = new(0f, 1f); public readonly static Vector2D Up = new(0f, 1f);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing downwards.
/// </summary>
public readonly static Vector2D Down = new(0f, -1f); public readonly static Vector2D Down = new(0f, -1f);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing leftwards.
/// </summary>
public readonly static Vector2D Left = new(-1f, 0f); public readonly static Vector2D Left = new(-1f, 0f);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing rightwards.
/// </summary>
public readonly static Vector2D Right = new(1f, 0f); public readonly static Vector2D Right = new(1f, 0f);
/// <summary>
/// Represents the zero <see cref="Vector2D"/>.
/// </summary>
public readonly static Vector2D Zero = new(0f, 0f); public readonly static Vector2D Zero = new(0f, 0f);
/// <summary>
/// Represents the <see cref="Vector2D"/> with both components equal to 1.
/// </summary>
public readonly static Vector2D One = new(1f, 1f); public readonly static Vector2D One = new(1f, 1f);
public static Vector2D operator -(Vector2D vector) => new(0f - vector.X, 0f - vector.Y); public static Vector2D operator -(Vector2D vector) => new(0f - vector.X, 0f - vector.Y);
@ -72,189 +28,38 @@ public readonly struct Vector2D(float x, float y)
public static bool operator ==(Vector2D left, Vector2D right) => left.X == right.X && left.Y == right.Y; public static bool operator ==(Vector2D left, Vector2D right) => left.X == right.X && left.Y == right.Y;
public static bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y; public static bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y;
/// <summary>
/// Calculates the length of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <returns>The length of the <see cref="Vector2D"/>.</returns>
public static float Length(Vector2D vector) => MathF.Sqrt(LengthSquared(vector)); public static float Length(Vector2D vector) => MathF.Sqrt(LengthSquared(vector));
/// <summary>
/// Calculates the squared length of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <returns>The squared length of the <see cref="Vector2D"/>.</returns>
public static float LengthSquared(Vector2D vector) => vector.X * vector.X + vector.Y * vector.Y; public static float LengthSquared(Vector2D vector) => vector.X * vector.X + vector.Y * vector.Y;
/// <summary>
/// Calculates the distance between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="from">The start <see cref="Vector2D"/>.</param>
/// <param name="to">The end <see cref="Vector2D"/>.</param>
/// <returns>The distance between the two <see cref="Vector2D"/>s.</returns>
public static float Distance(Vector2D from, Vector2D to) => Length(FromTo(from, to)); public static float Distance(Vector2D from, Vector2D to) => Length(FromTo(from, to));
/// <summary>
/// Inverts the direction of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <returns>The inverted <see cref="Vector2D"/>.</returns>
public static Vector2D Invert(Vector2D vector) => -vector; public static Vector2D Invert(Vector2D vector) => -vector;
/// <summary>
/// Adds two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The sum of the two <see cref="Vector2D"/>s.</returns>
public static Vector2D Add(Vector2D left, Vector2D right) => left + right; public static Vector2D Add(Vector2D left, Vector2D right) => left + right;
/// <summary>
/// Subtracts one <see cref="Vector2D"/> from another.
/// </summary>
/// <param name="left">The <see cref="Vector2D"/> to subtract from.</param>
/// <param name="right">The <see cref="Vector2D"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="Vector2D"/> from the first.</returns>
public static Vector2D Subtract(Vector2D left, Vector2D right) => left - right; public static Vector2D Subtract(Vector2D left, Vector2D right) => left - right;
/// <summary>
/// Multiplies a <see cref="Vector2D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="Vector2D"/> by the scalar value.</returns>
public static Vector2D Multiply(Vector2D vector, float value) => vector * value; public static Vector2D Multiply(Vector2D vector, float value) => vector * value;
public static Vector2D Subdivide(Vector2D vector, float value) => vector / value;
/// <summary>
/// Divides a <see cref="Vector2D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="Vector2D"/> by the scalar value.</returns>
public static Vector2D Divide(Vector2D vector, float value) => vector / value;
/// <summary>
/// Calculates the absolute value of each component of the vector.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> with each component's absolute value.</returns>
public static Vector2D Abs(Vector2D vector) => new(Math.Abs(vector.X), Math.Abs(vector.Y)); public static Vector2D Abs(Vector2D vector) => new(Math.Abs(vector.X), Math.Abs(vector.Y));
/// <summary>
/// Normalizes the <see cref="Vector2D"/> (creates a unit <see cref="Vector2D"/> with the same direction).
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to normalize.</param>
/// <returns>The normalized <see cref="Vector2D"/>.</returns>
public static Vector2D Normalize(Vector2D vector) => vector / Length(vector); public static Vector2D Normalize(Vector2D vector) => vector / Length(vector);
/// <summary>
/// Reflects a <see cref="Vector2D"/> off a surface with the specified normal.
/// </summary>
/// <param name="vector">The incident <see cref="Vector2D"/>.</param>
/// <param name="normal">The normal <see cref="Vector2D"/> of the surface.</param>
/// <returns>The reflected <see cref="Vector2D"/>.</returns>
public static Vector2D Reflect(Vector2D vector, Vector2D normal) => vector - 2f * Dot(vector, normal) * normal; public static Vector2D Reflect(Vector2D vector, Vector2D normal) => vector - 2f * Dot(vector, normal) * normal;
/// <summary>
/// Calculates the <see cref="Vector2D"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="Vector2D"/> from the starting point to the ending point.</returns>
public static Vector2D FromTo(Vector2D from, Vector2D to) => to - from; public static Vector2D FromTo(Vector2D from, Vector2D to) => to - from;
/// <summary>
/// Scales a <see cref="Vector2D"/> by another <see cref="Vector2D"/> component-wise.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to scale.</param>
/// <param name="scale">The <see cref="Vector2D"/> containing the scaling factors for each component.</param>
/// <returns>The scaled <see cref="Vector2D"/>.</returns>
public static Vector2D Scale(Vector2D vector, Vector2D scale) => new(vector.X * scale.X, vector.Y * scale.Y); public static Vector2D Scale(Vector2D vector, Vector2D scale) => new(vector.X * scale.X, vector.Y * scale.Y);
/// <summary>
/// Calculates a perpendicular <see cref="Vector2D"/> to the given <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>A <see cref="Vector2D"/> perpendicular to the input <see cref="Vector2D"/>.</returns>
public static Vector2D Perpendicular(Vector2D vector) => new(-vector.Y, vector.X); public static Vector2D Perpendicular(Vector2D vector) => new(-vector.Y, vector.X);
/// <summary>
/// Rotates a <see cref="Vector2D"/> by the specified angle (in radians).
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to rotate.</param>
/// <param name="angleInRadian">The angle to rotate by, in radians.</param>
/// <returns>The rotated <see cref="Vector2D"/>.</returns>
public static Vector2D Rotate(Vector2D vector, float angleInRadian) => new(MathF.Cos(angleInRadian) * vector.X - MathF.Sin(angleInRadian) * vector.Y, MathF.Sin(angleInRadian) * vector.X + MathF.Cos(angleInRadian) * vector.Y); public static Vector2D Rotate(Vector2D vector, float angleInRadian) => new(MathF.Cos(angleInRadian) * vector.X - MathF.Sin(angleInRadian) * vector.Y, MathF.Sin(angleInRadian) * vector.X + MathF.Cos(angleInRadian) * vector.Y);
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> containing the minimum components from both input <see cref="Vector2D"/>s.</returns>
public static Vector2D Min(Vector2D left, Vector2D right) => new((left.X < right.X) ? left.X : right.X, (left.Y < right.Y) ? left.Y : right.Y); public static Vector2D Min(Vector2D left, Vector2D 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="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> containing the maximum components from both input <see cref="Vector2D"/>s.</returns>
public static Vector2D Max(Vector2D left, Vector2D right) => new((left.X > right.X) ? left.X : right.X, (left.Y > right.Y) ? left.Y : right.Y); public static Vector2D Max(Vector2D left, Vector2D 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="Vector2D"/> between the corresponding component of two other <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to clamp.</param>
/// <param name="min">The <see cref="Vector2D"/> representing the minimum values for each component.</param>
/// <param name="max">The <see cref="Vector2D"/> representing the maximum values for each component.</param>
/// <returns>A <see cref="Vector2D"/> with each component clamped between the corresponding components of the min and max <see cref="Vector2D"/>s.</returns>
public static Vector2D Clamp(Vector2D vector, Vector2D min, Vector2D max) => new(Math.Clamp(vector.X, min.X, max.X), Math.Clamp(vector.Y, min.Y, max.Y)); public static Vector2D Clamp(Vector2D vector, Vector2D min, Vector2D max) => new(Math.Clamp(vector.X, min.X, max.X), Math.Clamp(vector.Y, min.Y, max.Y));
/// <summary>
/// Performs linear interpolation between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Vector2D"/> (t = 0).</param>
/// <param name="to">The ending <see cref="Vector2D"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="Vector2D"/>.</returns>
public static Vector2D Lerp(Vector2D from, Vector2D to, float t) => from + FromTo(from, to) * t; public static Vector2D Lerp(Vector2D from, Vector2D to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Calculates the cross product of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The cross product of the two <see cref="Vector2D"/>s.</returns>
public static float Cross(Vector2D left, Vector2D right) => left.X * right.Y - left.Y * right.X; public static float Cross(Vector2D left, Vector2D right) => left.X * right.Y - left.Y * right.X;
/// <summary>
/// Calculates the angle between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The angle between the two <see cref="Vector2D"/>s in radians.</returns>
public static float Angle(Vector2D left, Vector2D right) => MathF.Acos(Dot(left, right) / (Length(left) * Length(right))); public static float Angle(Vector2D left, Vector2D right) => MathF.Acos(Dot(left, right) / (Length(left) * Length(right)));
/// <summary>
/// Calculates the dot product of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The dot product of the two <see cref="Vector2D"/>s.</returns>
public static float Dot(Vector2D left, Vector2D right) => left.X * right.X + left.Y * right.Y; public static float Dot(Vector2D left, Vector2D right) => left.X * right.X + left.Y * right.Y;
/// <summary> /// <summary>
/// Determines the orientation of three points represented by <see cref="Vector2D"/>s. /// Finds the Orientation of 3 <see cref="Vector2D"/>s
/// </summary> /// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param> /// <returns>0 -> Collinear, 1 -> Clockwise, 2 -> Counterclockwise</returns>
/// <param name="middle">The second <see cref="Vector2D"/>.</param>
/// <param name="right">The third <see cref="Vector2D"/>.</param>
/// <returns>
/// <para>0 - Collinear.</para>
/// <para>1 - Clockwise.</para>
/// <para>2 - Counterclockwise.</para>
/// </returns>
public static int Orientation(Vector2D left, Vector2D middle, Vector2D right) public static int Orientation(Vector2D left, Vector2D middle, Vector2D right)
{ {
Vector2D leftToMiddle = left.FromTo(middle); Vector2D leftToMiddle = left.FromTo(middle);
@ -268,32 +73,11 @@ public readonly struct Vector2D(float x, float y)
return 0; return 0;
} }
/// <summary>
/// Checks if two <see cref="Vector2D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Vector2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Vector2D left, Vector2D right, float epsilon = float.Epsilon) public static bool ApproximatelyEquals(Vector2D left, Vector2D right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon); => left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon);
/// <summary>
/// Converts the <see cref="Vector2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector2D"/>.</returns>
public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})"; public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector2D"/>.
/// </summary>
/// <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>
public override bool Equals(object? obj) => obj is Vector2D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y); public override bool Equals(object? obj) => obj is Vector2D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y);
/// <summary>
/// Generates a hash code for the <see cref="Vector2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Vector2D"/>.</returns>
public override int GetHashCode() => HashCode.Combine(X, Y); public override int GetHashCode() => HashCode.Combine(X, Y);
} }

View File

@ -2,18 +2,8 @@ using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D.Abstract; namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a <see cref="ICollider2D"/> with the shape of a <see cref="Circle"/>.
/// </summary>
public interface ICircleCollider2D : ICollider2D public interface ICircleCollider2D : ICollider2D
{ {
/// <summary>
/// The local <see cref="Circle"/> shape of the <see cref="ICollider2D"/>.
/// </summary>
Circle CircleLocal { get; set; } Circle CircleLocal { get; set; }
/// <summary>
/// The world space representation of the <see cref="Circle"/> shape.
/// </summary>
Circle CircleWorld { get; } Circle CircleWorld { get; }
} }

View File

@ -4,38 +4,15 @@ using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract; namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D collider.
/// </summary>
public interface ICollider2D : IBehaviour, IAssignableTransform public interface ICollider2D : IBehaviour, IAssignableTransform
{ {
/// <summary>
/// Event triggered when a collision is detected.
/// </summary>
Action<ICollider2D, CollisionDetectionInformation>? OnCollisionDetected { get; set; } Action<ICollider2D, CollisionDetectionInformation>? OnCollisionDetected { get; set; }
/// <summary>
/// Event triggered when a collision is resolved.
/// </summary>
Action<ICollider2D, CollisionDetectionInformation>? OnCollisionResolved { get; set; } Action<ICollider2D, CollisionDetectionInformation>? OnCollisionResolved { get; set; }
/// <summary>
/// Event triggered when another <see cref="ICollider2D"/> triggers this <see cref="ICollider2D"/>.
/// </summary>
Action<ICollider2D, ICollider2D>? OnTriggered { get; set; } Action<ICollider2D, ICollider2D>? OnTriggered { get; set; }
/// <summary>
/// The <see cref="IRigidBody2D"/> associated with the <see cref="ICollider2D"/>.
/// </summary>
IRigidBody2D? RigidBody2D { get; } IRigidBody2D? RigidBody2D { get; }
/// <summary>
/// The value indicating whether the <see cref="ICollider2D"/> is a trigger.
/// </summary>
bool IsTrigger { get; set; } bool IsTrigger { get; set; }
/// <summary>
/// Recalculates <see cref="ICollider2D"/> properties.
/// </summary>
void Recalculate(); void Recalculate();
} }

View File

@ -2,19 +2,7 @@ using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D; namespace Syntriax.Engine.Physics2D;
/// <summary>
/// Represents a 2D collision detector.
/// </summary>
public interface ICollisionDetector2D public interface ICollisionDetector2D
{ {
/// <summary>
/// Attempts to detect a collision between two <see cref="ICollider2D"/>s.
/// </summary>
/// <typeparam name="T1">Type of the first <see cref="ICollider2D"/>.</typeparam>
/// <typeparam name="T2">Type of the second <see cref="ICollider2D"/>.</typeparam>
/// <param name="left">The first <see cref="ICollider2D"/>.</param>
/// <param name="right">The second <see cref="ICollider2D"/>.</param>
/// <param name="collisionInformation">Information about the collision.</param>
/// <returns><see cref="true"/> if a collision is detected, otherwise <see cref="false"/>.</returns>
bool TryDetect<T1, T2>(T1 left, T2 right, out CollisionDetectionInformation collisionInformation) where T1 : ICollider2D where T2 : ICollider2D; bool TryDetect<T1, T2>(T1 left, T2 right, out CollisionDetectionInformation collisionInformation) where T1 : ICollider2D where T2 : ICollider2D;
} }

View File

@ -1,13 +1,6 @@
namespace Syntriax.Engine.Physics2D.Abstract; namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D collision resolver.
/// </summary>
public interface ICollisionResolver2D public interface ICollisionResolver2D
{ {
/// <summary>
/// Resolves collisions based on collision detection information provided.
/// </summary>
/// <param name="collisionInformation">Information about the collision.</param>
void Resolve(CollisionDetectionInformation collisionInformation); void Resolve(CollisionDetectionInformation collisionInformation);
} }

View File

@ -1,18 +1,11 @@
namespace Syntriax.Engine.Physics2D.Abstract; namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D physics engine.
/// </summary>
public interface IPhysicsEngine2D public interface IPhysicsEngine2D
{ {
/// <summary> int IterationCount { get; set; }
/// The number of iterations the <see cref="IPhysicsEngine2D"/> performs per step.
/// </summary> void AddRigidBody(IRigidBody2D rigidBody);
int IterationPerStep { get; set; } void RemoveRigidBody(IRigidBody2D rigidBody);
/// <summary>
/// Advances the physics simulation by the specified time.
/// </summary>
/// <param name="deltaTime">The time step.</param>
void Step(float deltaTime); void Step(float deltaTime);
} }

View File

@ -1,17 +1,7 @@
namespace Syntriax.Engine.Physics2D.Abstract; namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D physics object's responsive attributes.
/// </summary>
public interface IPhysicsMaterial2D public interface IPhysicsMaterial2D
{ {
/// <summary>
/// The friction coefficient of the physics object.
/// </summary>
float Friction { get; } float Friction { get; }
/// <summary>
/// The restitution (bounciness) coefficient of the physics object.
/// </summary>
float Restitution { get; } float Restitution { get; }
} }

View File

@ -3,33 +3,13 @@ using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract; namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D rigid body in the engine.
/// </summary>
public interface IRigidBody2D : IBehaviour, IAssignableTransform public interface IRigidBody2D : IBehaviour, IAssignableTransform
{ {
/// <summary>
/// The physics material of the <see cref="IRigidBody2D"/>.
/// </summary>
IPhysicsMaterial2D Material { get; set; } IPhysicsMaterial2D Material { get; set; }
/// <summary>
/// The velocity of the <see cref="IRigidBody2D"/>.
/// </summary>
Vector2D Velocity { get; set; } Vector2D Velocity { get; set; }
/// <summary>
/// The angular velocity (rotation rate) of the <see cref="IRigidBody2D"/>.
/// </summary>
float AngularVelocity { get; set; } float AngularVelocity { get; set; }
/// <summary>
/// The mass of the <see cref="IRigidBody2D"/>.
/// </summary>
float Mass { get; set; } float Mass { get; set; }
/// <summary>
/// The value indicating whether the <see cref="IRigidBody2D"/> is static/immovable.
/// </summary>
bool IsStatic { get; set; } bool IsStatic { get; set; }
} }

View File

@ -2,19 +2,8 @@ using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D.Abstract; namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a <see cref="ICollider2D"/> with a custom <see cref="Shape"/>.
/// </summary>
public interface IShapeCollider2D : ICollider2D public interface IShapeCollider2D : ICollider2D
{ {
/// <summary>
/// Gets or sets the local <see cref="Shape"/> of the <see cref="ICollider2D"/>.
/// </summary>
Shape ShapeLocal { get; set; } Shape ShapeLocal { get; set; }
/// <summary>
/// Gets the world space representation of the <see cref="Shape"/>.
/// </summary>
Shape ShapeWorld { get; } Shape ShapeWorld { get; }
} }

View File

@ -12,11 +12,10 @@ public class PhysicsEngine2D : IPhysicsEngine2D
private readonly List<ICollider2D> colliders = new(64); private readonly List<ICollider2D> colliders = new(64);
private int _iterationCount = 1; private int _iterationCount = 1;
private ICollisionDetector2D collisionDetector = new CollisionDetector2D();
private ICollisionResolver2D collisionResolver = new CollisionResolver2D();
private readonly ICollisionDetector2D collisionDetector = null!; public int IterationCount { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }
private readonly ICollisionResolver2D collisionResolver = null!;
public int IterationPerStep { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }
public void AddRigidBody(IRigidBody2D rigidBody) public void AddRigidBody(IRigidBody2D rigidBody)
{ {
@ -39,9 +38,9 @@ public class PhysicsEngine2D : IPhysicsEngine2D
public void Step(float deltaTime) public void Step(float deltaTime)
{ {
float intervalDeltaTime = deltaTime / IterationPerStep; float intervalDeltaTime = deltaTime / IterationCount;
for (int iterationIndex = 0; iterationIndex < IterationPerStep; iterationIndex++) for (int iterationIndex = 0; iterationIndex < IterationCount; iterationIndex++)
{ {
// Can Parallel // Can Parallel
for (int i = 0; i < rigidBodies.Count; i++) for (int i = 0; i < rigidBodies.Count; i++)
@ -101,7 +100,7 @@ public class PhysicsEngine2D : IPhysicsEngine2D
private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime) private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime)
{ {
if (rigidBody.IsStatic || !rigidBody.IsActive) if (rigidBody.IsStatic)
return; return;
rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime; rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime;
@ -123,16 +122,4 @@ public class PhysicsEngine2D : IPhysicsEngine2D
colliders.Remove(collider2D); colliders.Remove(collider2D);
} }
public PhysicsEngine2D()
{
collisionDetector = new CollisionDetector2D();
collisionResolver = new CollisionResolver2D();
}
public PhysicsEngine2D(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{
this.collisionDetector = collisionDetector;
this.collisionResolver = collisionResolver;
}
} }

View File

@ -1,150 +0,0 @@
using System;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D;
public class PhysicsEngine2DCacher : IPhysicsEngine2D, IAssignableGameManager
{
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null;
private int _iterationPerStep = 1;
protected readonly ICollisionDetector2D collisionDetector = null!;
protected readonly ICollisionResolver2D collisionResolver = null!;
protected BehaviourCacher<IRigidBody2D> rigidBodyCacher = new();
protected BehaviourCacher<ICollider2D> colliderCacher = new();
public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; }
public IGameManager GameManager { get; private set; } = null!;
public void Step(float deltaTime)
{
float intervalDeltaTime = deltaTime / IterationPerStep;
for (int iterationIndex = 0; iterationIndex < IterationPerStep; iterationIndex++)
{
// Can Parallel
foreach (var rigidBody in rigidBodyCacher)
StepRigidBody(rigidBody, intervalDeltaTime);
// Can Parallel
foreach (var collider in colliderCacher)
collider.Recalculate();
// Can Parallel
for (int x = 0; x < colliderCacher.Behaviours.Count; x++)
{
ICollider2D? colliderX = colliderCacher.Behaviours[x];
if (!colliderX.IsActive)
return;
for (int y = x + 1; y < colliderCacher.Behaviours.Count; y++)
{
ICollider2D? colliderY = colliderCacher.Behaviours[y];
if (!colliderY.IsActive)
return;
if (colliderX.RigidBody2D == colliderY.RigidBody2D)
continue;
bool bothCollidersAreTriggers = colliderX.IsTrigger && colliderX.IsTrigger == colliderY.IsTrigger;
if (bothCollidersAreTriggers)
continue;
bool bothCollidersAreStatic = colliderX.RigidBody2D?.IsStatic ?? true && colliderX.RigidBody2D?.IsStatic == colliderY.RigidBody2D?.IsStatic;
if (bothCollidersAreStatic)
continue;
if (collisionDetector.TryDetect(colliderX, colliderY, out CollisionDetectionInformation information))
{
if (colliderX.IsTrigger)
{
colliderX.OnTriggered?.Invoke(colliderX, colliderY);
continue;
}
else if (colliderY.IsTrigger)
{
colliderY.OnTriggered?.Invoke(colliderY, colliderY);
continue;
}
colliderX.OnCollisionDetected?.Invoke(colliderX, information);
colliderY.OnCollisionDetected?.Invoke(colliderY, information);
collisionResolver?.Resolve(information);
}
}
}
}
}
private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime)
{
if (rigidBody.IsStatic || !rigidBody.IsActive)
return;
rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime;
rigidBody.Transform.Rotation += rigidBody.AngularVelocity * intervalDeltaTime;
}
public bool Assign(IGameManager gameManager)
{
if (GameManager is not null)
return false;
colliderCacher.Assign(gameManager);
rigidBodyCacher.Assign(gameManager);
GameManager = gameManager;
OnGameManagerAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (GameManager is null)
return false;
colliderCacher.Unassign();
rigidBodyCacher.Unassign();
GameManager = null!;
OnUnassigned?.Invoke(this);
return true;
}
public PhysicsEngine2DCacher()
{
collisionDetector = new CollisionDetector2D();
collisionResolver = new CollisionResolver2D();
}
public PhysicsEngine2DCacher(IGameManager gameManager)
{
Assign(gameManager);
collisionDetector = new CollisionDetector2D();
collisionResolver = new CollisionResolver2D();
}
public PhysicsEngine2DCacher(IGameManager gameManager, ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{
Assign(gameManager);
this.collisionDetector = collisionDetector;
this.collisionResolver = collisionResolver;
}
public PhysicsEngine2DCacher(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{
this.collisionDetector = collisionDetector;
this.collisionResolver = collisionResolver;
}
}

View File

@ -4,47 +4,16 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
/// <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}")] [System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")]
public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary) public readonly struct AABB(Vector2D LowerBoundary, Vector2D UpperBoundary)
{ {
/// <summary> public readonly Vector2D LowerBoundary { get; init; } = LowerBoundary;
/// The lower boundary of the <see cref="AABB"/>. public readonly Vector2D UpperBoundary { get; init; } = UpperBoundary;
/// </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; 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(); 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 readonly Vector2D SizeHalf => Size * .5f;
/// <summary>
/// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
/// <returns>An <see cref="AABB"/> that bounds all the <see cref="Vector2D"/>s.</returns>
public static AABB FromVectors(IEnumerable<Vector2D> vectors) public static AABB FromVectors(IEnumerable<Vector2D> vectors)
{ {
int counter = 0; int counter = 0;
@ -65,33 +34,13 @@ public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
return new(lowerBoundary, upperBoundary); 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>
/// <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) public static bool ApproximatelyEquals(AABB left, AABB right)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary); => left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary);
} }
/// <summary>
/// Provides extension methods for the <see cref="AABB"/> struct.
/// </summary>
public static class AABBExtensions public static class AABBExtensions
{ {
/// <summary>
/// Converts a collection of <see cref="Vector2D"/>s to an <see cref="AABB"/>.
/// </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 ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors); public static AABB ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors);
/// <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>
/// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(this AABB left, AABB right) => AABB.ApproximatelyEquals(left, right); public static bool ApproximatelyEquals(this AABB left, AABB right) => AABB.ApproximatelyEquals(left, right);
} }

View File

@ -1,115 +1,47 @@
using System.Diagnostics;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary> [System.Diagnostics.DebuggerDisplay("Center: {Center.ToString(), nq}, Radius: {Radius}")]
/// Represents a 2D circle. public readonly struct Circle(Vector2D Center, float Radius)
/// </summary>
/// <param name="center">The center of the circle.</param>
/// <param name="radius">The radius of the circle.</param>
/// <remarks>
/// Initializes a new instance of the Circle struct with the specified center and radius.
/// </remarks>
[DebuggerDisplay("Center: {Center.ToString(),nq}, Radius: {Radius}")]
public readonly struct Circle(Vector2D center, float radius)
{ {
/// <summary>
/// The center of the circle.
/// </summary>
public readonly Vector2D Center = center;
/// <summary>
/// The radius of the <see cref="Circle"/>.
/// </summary>
public readonly float Radius = radius;
/// <summary>
/// Gets the squared radius of the <see cref="Circle"/>.
/// </summary>
public readonly float RadiusSquared => Radius * Radius;
/// <summary>
/// Gets the diameter of the <see cref="Circle"/>.
/// </summary>
public readonly float Diameter => 2f * Radius;
/// <summary>
/// A predefined unit <see cref="Circle"/> with a center at the origin and a radius of 1.
/// </summary>
public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f); public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f);
/// <summary> public readonly Vector2D Center { get; init; } = Center;
/// Sets the center of the <see cref="Circle"/>. public readonly float Radius { get; init; } = Radius;
/// </summary>
public static Circle SetCenter(Circle circle, Vector2D center) => new(center, circle.Radius);
/// <summary> public readonly float RadiusSquared => Radius * Radius;
/// Sets the radius of the <see cref="Circle"/>. public readonly float Diameter => 2f * Radius;
/// </summary>
public static Circle SetCenter(Circle circle, Vector2D center) => new(center, circle.Radius);
public static Circle SetRadius(Circle circle, float radius) => new(circle.Center, radius); public static Circle SetRadius(Circle circle, float radius) => new(circle.Center, radius);
/// <summary>
/// Displaces the <see cref="Circle"/> by the specified <see cref="Vector2D"/>.
/// </summary>
public static Circle Displace(Circle circle, Vector2D displaceVector) => new(circle.Center + displaceVector, circle.Radius); public static Circle Displace(Circle circle, Vector2D displaceVector) => new(circle.Center + displaceVector, circle.Radius);
/// <summary>
/// Projects the <see cref="Circle"/> onto the specified <see cref="Vector2D"/>.
/// </summary>
public static Projection Project(Circle circle, Vector2D projectionVector) public static Projection Project(Circle circle, Vector2D projectionVector)
{ {
float projectedCenter = circle.Center.Dot(projectionVector); float projectedCenter = circle.Center.Dot(projectionVector);
return new(projectedCenter - circle.Radius, projectedCenter + circle.Radius); return new(projectedCenter - circle.Radius, projectedCenter + circle.Radius);
} }
/// <summary>
/// Transforms the <see cref="Circle"/> by the specified <see cref="ITransform"/>.
/// </summary>
public static Circle TransformCircle(ITransform transform, Circle circle) public static Circle TransformCircle(ITransform transform, Circle circle)
=> new(transform.TransformVector2D(circle.Center), circle.Radius * transform.Scale.Magnitude); => new(transform.TransformVector2D(circle.Center), circle.Radius * transform.Scale.Magnitude);
/// <summary>
/// Checks if two <see cref="Circle"/>s are approximately equal.
/// </summary>
public static bool ApproximatelyEquals(Circle left, Circle right) public static bool ApproximatelyEquals(Circle left, Circle right)
=> left.Center.ApproximatelyEquals(right.Center) && left.Radius.ApproximatelyEquals(right.Radius); => left.Center.ApproximatelyEquals(right.Center) && left.Radius.ApproximatelyEquals(right.Radius);
} }
/// <summary>
/// Provides extension methods for the <see cref="Circle"/> struct.
/// </summary>
public static class CircleExtensions public static class CircleExtensions
{ {
/// <summary>
/// Sets the center of the <see cref="Circle"/>.
/// </summary>
public static Circle SetCenter(this Circle circle, Vector2D center) => Circle.SetCenter(circle, center); public static Circle SetCenter(this Circle circle, Vector2D center) => Circle.SetCenter(circle, center);
/// <summary>
/// Sets the radius of the <see cref="Circle"/>.
/// </summary>
public static Circle SetRadius(this Circle circle, float radius) => Circle.SetRadius(circle, radius); public static Circle SetRadius(this Circle circle, float radius) => Circle.SetRadius(circle, radius);
/// <summary>
/// Moves the <see cref="Circle"/> by the specified <see cref="Vector2D"/>.
/// </summary>
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);
/// <summary>
/// Projects the <see cref="Circle"/> onto the specified <see cref="Vector2D"/>.
/// </summary>
public static Projection ToProjection(this Circle circle, Vector2D projectionVector) => Circle.Project(circle, projectionVector); public static Projection ToProjection(this Circle circle, Vector2D projectionVector) => Circle.Project(circle, projectionVector);
/// <summary>
/// Transforms the <see cref="Circle"/> by the specified <see cref="ITransform"/>.
/// </summary>
public static Circle TransformCircle(this ITransform transform, Circle circle) => Circle.TransformCircle(transform, circle); public static Circle TransformCircle(this ITransform transform, Circle circle) => Circle.TransformCircle(transform, circle);
/// <summary>
/// Checks if two <see cref="Circle"/>s are approximately equal.
/// </summary>
public static bool ApproximatelyEquals(this Circle left, Circle right) => Circle.ApproximatelyEquals(left, right); public static bool ApproximatelyEquals(this Circle left, Circle right) => Circle.ApproximatelyEquals(left, right);
} }

View File

@ -1,54 +1,22 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary> [System.Diagnostics.DebuggerDisplay("From: {From.ToString(), nq}, To: {To.ToString(), nq}, Direction: {Direction.ToString(), nq}, Length: {Length}")]
/// Represents a 2D line segment defined by two endpoints. public readonly struct Line(Vector2D From, Vector2D To)
/// </summary>
/// <remarks>
/// Initializes a new instance of the Line struct with the specified endpoints.
/// </remarks>
/// <param name="from">The starting point of the <see cref="Line"/> segment.</param>
/// <param name="to">The ending point of the <see cref="Line"/> segment.</param>
[System.Diagnostics.DebuggerDisplay("From: {From.ToString(),nq}, To: {To.ToString(),nq}, Direction: {Direction.ToString(),nq}, Length: {Length}")]
public readonly struct Line(Vector2D from, Vector2D to)
{ {
/// <summary> public readonly Vector2D From { get; init; } = From;
/// The starting point of the <see cref="Line"/> segment. public readonly Vector2D To { get; init; } = To;
/// </summary>
public readonly Vector2D From = from;
/// <summary>
/// The ending point of the <see cref="Line"/> segment.
/// </summary>
public readonly Vector2D To = to;
/// <summary>
/// The reversed <see cref="Line"/> segment.
/// </summary>
public readonly Line Reversed => new(To, From); public readonly Line Reversed => new(To, From);
/// <summary>
/// The normalized direction <see cref="Vector2D"/> of the <see cref="Line"/> segment.
/// </summary>
public readonly Vector2D Direction => From.FromTo(To).Normalize(); public readonly Vector2D Direction => From.FromTo(To).Normalize();
/// <summary>
/// The length of the <see cref="Line"/> segment.
/// </summary>
public readonly float Length => From.FromTo(To).Length(); public readonly float Length => From.FromTo(To).Length();
/// <summary>
/// The squared length of the <see cref="Line"/> segment.
/// </summary>
public readonly float LengthSquared => From.FromTo(To).LengthSquared(); public readonly float LengthSquared => From.FromTo(To).LengthSquared();
/// <summary>
/// The equation of the <see cref="Line"/> defined by this <see cref="Line"/> segment.
/// </summary>
public static LineEquation GetLineEquation(Line line) public static LineEquation GetLineEquation(Line line)
{ {
Vector2D slopeVector = line.From.FromTo(line.To); Vector2D slopeVector = line.From.FromTo(line.To);
@ -59,15 +27,9 @@ public readonly struct Line(Vector2D from, Vector2D to)
return new LineEquation(slope, yOffset); return new LineEquation(slope, yOffset);
} }
/// <summary>
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line"/>.
/// </summary>
public static bool Intersects(Line line, Vector2D point) public static bool Intersects(Line line, Vector2D point)
=> LineEquation.Resolve(GetLineEquation(line), point.X).ApproximatelyEquals(point.Y); => LineEquation.Resolve(GetLineEquation(line), point.X).ApproximatelyEquals(point.Y);
/// <summary>
/// Calculates the parameter 't' representing the point's position on the <see cref="Line"/> segment.
/// </summary>
public static float GetT(Line line, Vector2D point) public static float GetT(Line line, Vector2D point)
{ {
float fromX = MathF.Abs(line.From.X); float fromX = MathF.Abs(line.From.X);
@ -90,63 +52,23 @@ public readonly struct Line(Vector2D from, Vector2D to)
return t; return t;
} }
/// <summary> public static bool Exist(Line line, List<Vector2D> vertices)
/// Checks if the <see cref="Line"/> segment intersects with another <see cref="Line"/> segment.
/// </summary>
public static bool Intersects(Line left, Line right)
{ {
int o1 = Vector2D.Orientation(left.From, left.To, right.From); for (int i = 0; i < vertices.Count - 1; i++)
int o2 = Vector2D.Orientation(left.From, left.To, right.To); {
int o3 = Vector2D.Orientation(right.From, right.To, left.From); Vector2D vertexCurrent = vertices[i];
int o4 = Vector2D.Orientation(right.From, right.To, left.To); Vector2D vertexNext = vertices[i];
if (line.From == vertexCurrent && line.To == vertexNext) return true;
if (o1 != o2 && o3 != o4) if (line.From == vertexNext && line.To == vertexCurrent) return true;
return true; }
if (o1 == 0 && OnSegment(left, right.From)) return true;
if (o2 == 0 && OnSegment(left, right.To)) return true;
if (o3 == 0 && OnSegment(right, left.From)) return true;
if (o4 == 0 && OnSegment(right, left.To)) return true;
Vector2D vertexFirst = vertices[0];
Vector2D vertexLast = vertices[^1];
if (line.From == vertexFirst && line.To == vertexLast) return true;
if (line.From == vertexLast && line.To == vertexFirst) return true;
return false; return false;
} }
/// <summary>
/// Checks if the point lies within the <see cref="Line"/> segment.
/// </summary>
public static bool OnSegment(Line line, Vector2D point)
{
if (point.X <= MathF.Max(line.From.X, line.To.X) && point.X >= MathF.Min(line.From.X, line.To.X) &&
point.Y <= MathF.Max(line.From.Y, line.To.Y) && point.Y >= MathF.Min(line.From.Y, line.To.Y))
return true;
return false;
}
/// <summary>
/// Determines whether two <see cref="Line"/> segments intersect.
/// </summary>
public static bool Intersects(Line left, Line right, [NotNullWhen(returnValue: true)] out Vector2D? point)
{
point = null;
bool result = Intersects(left, right);
if (result)
point = IntersectionPoint(left, right);
return result;
}
/// <summary>
/// Finds the point of intersection between two <see cref="Line"/> segments.
/// </summary>
public static Vector2D IntersectionPoint(Line left, Line right)
=> Vector2D.Lerp(left.From, left.To, IntersectionParameterT(left, right));
/// <summary>
/// Calculates the parameter 't' representing the intersection point's position on the <see cref="Line"/> segment.
/// </summary>
public static float IntersectionParameterT(Line left, Line right) public static float IntersectionParameterT(Line left, Line right)
{ {
float numerator = (left.From.X - right.From.X) * (right.From.Y - right.To.Y) - (left.From.Y - right.From.Y) * (right.From.X - right.To.X); float numerator = (left.From.X - right.From.X) * (right.From.Y - right.To.Y) - (left.From.Y - right.From.Y) * (right.From.X - right.To.X);
@ -159,18 +81,12 @@ public readonly struct Line(Vector2D from, Vector2D to)
return numerator / denominator; return numerator / denominator;
} }
/// <summary>
/// Linearly interpolates between the two endpoints of the <see cref="Line"/> segment using parameter 't'.
/// </summary>
public static Vector2D Lerp(Line line, float t) public static Vector2D Lerp(Line line, float t)
=> new( => new Vector2D(
line.From.X + (line.To.X - line.From.X) * t, line.From.X + (line.To.X - line.From.X) * t,
line.From.Y + (line.To.Y - line.From.Y) * t line.From.Y + (line.To.Y - line.From.Y) * t
); );
/// <summary>
/// Calculates the closest point on the <see cref="Line"/> segment to the specified point.
/// </summary>
public static Vector2D ClosestPointTo(Line line, Vector2D point) public static Vector2D ClosestPointTo(Line line, Vector2D point)
{ {
// Convert edge points to vectors // Convert edge points to vectors
@ -190,20 +106,53 @@ public readonly struct Line(Vector2D from, Vector2D to)
return new Vector2D((float)closestX, (float)closestY); return new Vector2D((float)closestX, (float)closestY);
} }
/// <summary> public static Vector2D IntersectionPoint(Line left, Line right)
/// Checks if two <see cref="Line"/> segments are approximately equal. => Vector2D.Lerp(left.From, left.To, IntersectionParameterT(left, right));
/// </summary>
public static bool Intersects(Line left, Line right)
{
int o1 = Vector2D.Orientation(left.From, left.To, right.From);
int o2 = Vector2D.Orientation(left.From, left.To, right.To);
int o3 = Vector2D.Orientation(right.From, right.To, left.From);
int o4 = Vector2D.Orientation(right.From, right.To, left.To);
if (o1 != o2 && o3 != o4)
return true;
if (o1 == 0 && OnSegment(left, right.From)) return true;
if (o2 == 0 && OnSegment(left, right.To)) return true;
if (o3 == 0 && OnSegment(right, left.From)) return true;
if (o4 == 0 && OnSegment(right, left.To)) return true;
return false;
}
public static bool OnSegment(Line line, Vector2D point)
{
if (point.X <= MathF.Max(line.From.X, line.To.X) && point.X >= MathF.Min(line.From.X, line.To.X) &&
point.Y <= MathF.Max(line.From.Y, line.To.Y) && point.Y >= MathF.Min(line.From.Y, line.To.Y))
return true;
return false;
}
public static bool Intersects(Line left, Line right, [NotNullWhen(returnValue: true)] out Vector2D? point)
{
point = null;
bool result = Intersects(left, right);
if (result)
point = IntersectionPoint(left, right);
return result;
}
public static bool ApproximatelyEquals(Line left, Line right) public static bool ApproximatelyEquals(Line left, Line right)
=> left.From.ApproximatelyEquals(right.From) && left.To.ApproximatelyEquals(right.To); => left.From.ApproximatelyEquals(right.From) && left.To.ApproximatelyEquals(right.To);
} }
/// <summary>
/// Provides extension methods for the Line struct.
/// </summary>
public static class LineExtensions public static class LineExtensions
{ {
/// <summary>
/// Checks if two <see cref="Line"/>s are approximately equal.
/// </summary>
public static bool ApproximatelyEquals(this Line left, Line right) => Line.ApproximatelyEquals(left, right); public static bool ApproximatelyEquals(this Line left, Line right) => Line.ApproximatelyEquals(left, right);
} }

View File

@ -2,63 +2,20 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents a line equation in the form y = mx + b.
/// </summary>
/// <param name="slope">The slope of the line.</param>
/// <param name="offsetY">The y-intercept of the line.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="LineEquation"/> struct with the specified slope and y-intercept.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")] [System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
public readonly struct LineEquation(float slope, float offsetY) public readonly struct LineEquation(float Slope, float OffsetY)
{ {
/// <summary> public readonly float Slope { get; init; } = Slope;
/// The slope of the line equation. public readonly float OffsetY { get; init; } = OffsetY;
/// </summary>
public readonly float Slope = slope;
/// <summary>
/// The y-intercept of the line equation.
/// </summary>
public readonly float OffsetY = offsetY;
/// <summary>
/// Resolves the y-coordinate for a given x-coordinate using the line equation.
/// </summary>
/// <param name="lineEquation">The line equation to resolve.</param>
/// <param name="x">The x-coordinate for which to resolve the y-coordinate.</param>
/// <returns>The y-coordinate resolved using the line equation.</returns>
public static float Resolve(LineEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b public static float Resolve(LineEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b
/// <summary>
/// Checks if two line equations are approximately equal.
/// </summary>
/// <param name="left">The first line equation to compare.</param>
/// <param name="right">The second line equation to compare.</param>
/// <returns>True if the line equations are approximately equal; otherwise, false.</returns>
public static bool ApproximatelyEquals(LineEquation left, LineEquation right) public static bool ApproximatelyEquals(LineEquation left, LineEquation right)
=> left.Slope.ApproximatelyEquals(right.Slope) && left.OffsetY.ApproximatelyEquals(right.OffsetY); => left.Slope.ApproximatelyEquals(right.Slope) && left.OffsetY.ApproximatelyEquals(right.OffsetY);
} }
/// <summary>
/// Provides extension methods for the LineEquation struct.
/// </summary>
public static class LineEquationExtensions public static class LineEquationExtensions
{ {
/// <summary>
/// Resolves the y-coordinate for a given x-coordinate using the line equation.
/// </summary>
/// <param name="lineEquation">The line equation to resolve.</param>
/// <param name="x">The x-coordinate for which to resolve the y-coordinate.</param>
/// <returns>The y-coordinate resolved using the line equation.</returns>
public static float Resolve(this LineEquation lineEquation, float x) => LineEquation.Resolve(lineEquation, x); public static float Resolve(this LineEquation lineEquation, float x) => LineEquation.Resolve(lineEquation, x);
/// <summary>
/// Checks if two line equations are approximately equal.
/// </summary>
/// <param name="left">The first line equation to compare.</param>
/// <param name="right">The second line equation to compare.</param>
/// <returns>True if the line equations are approximately equal; otherwise, false.</returns>
public static bool ApproximatelyEquals(this LineEquation left, LineEquation right) => LineEquation.ApproximatelyEquals(left, right); public static bool ApproximatelyEquals(this LineEquation left, LineEquation right) => LineEquation.ApproximatelyEquals(left, right);
} }

View File

@ -1,41 +1,12 @@
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents a range of values along a single axis.
/// </summary>
/// <param name="min">The minimum value of the projection.</param>
/// <param name="max">The maximum value of the projection.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Projection"/> struct with the specified minimum and maximum values.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("Min: {Min}, Max: {Max}")] [System.Diagnostics.DebuggerDisplay("Min: {Min}, Max: {Max}")]
public readonly struct Projection(float min, float max) public readonly struct Projection(float Min, float Max)
{ {
/// <summary> public readonly float Min { get; init; } = Min;
/// Gets the minimum value of the projection. public readonly float Max { get; init; } = Max;
/// </summary>
public readonly float Min = min;
/// <summary>
/// Gets the maximum value of the projection.
/// </summary>
public readonly float Max = max;
/// <summary>
/// Checks if two projections overlap.
/// </summary>
/// <param name="left">The first projection to check.</param>
/// <param name="right">The second projection to check.</param>
/// <returns><see cref="true"/> if the projections overlap; otherwise, <see cref="false"/>.</returns>
public static bool Overlaps(Projection left, Projection right) => Overlaps(left, right, out var _); public static bool Overlaps(Projection left, Projection right) => Overlaps(left, right, out var _);
/// <summary>
/// Checks if two projections overlap and calculates the depth of the overlap.
/// </summary>
/// <param name="left">The first projection to check.</param>
/// <param name="right">The second projection to check.</param>
/// <param name="depth">The depth of the overlap, if any.</param>
/// <returns><see cref="true"/> if the projections overlap; otherwise, <see cref="false"/>.</returns>
public static bool Overlaps(Projection left, Projection right, out float depth) public static bool Overlaps(Projection left, Projection right, out float depth)
{ {
// TODO Try to improve this // TODO Try to improve this
@ -71,26 +42,8 @@ public readonly struct Projection(float min, float max)
return false; return false;
} }
} }
/// <summary>
/// Provides extension methods for the <see cref="Projection"/> struct.
/// </summary>
public static class ProjectionExtensions public static class ProjectionExtensions
{ {
/// <summary>
/// Checks if two projections overlap.
/// </summary>
/// <param name="left">The first projection to check.</param>
/// <param name="right">The second projection to check.</param>
/// <returns><see cref="true"/> if the projections overlap; otherwise, <see cref="false"/>.</returns>
public static bool Overlaps(this Projection left, Projection right) => Projection.Overlaps(left, right); public static bool Overlaps(this Projection left, Projection right) => Projection.Overlaps(left, right);
/// <summary>
/// Checks if two projections overlap and calculates the depth of the overlap.
/// </summary>
/// <param name="left">The first projection to check.</param>
/// <param name="right">The second projection to check.</param>
/// <param name="depth">The depth of the overlap, if any.</param>
/// <returns><see cref="true"/> if the projections overlap; otherwise, <see cref="false"/>.</returns>
public static bool Overlaps(this Projection left, Projection right, out float depth) => Projection.Overlaps(left, right, out depth); public static bool Overlaps(this Projection left, Projection right, out float depth) => Projection.Overlaps(left, right, out depth);
} }

View File

@ -6,55 +6,22 @@ using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents a shape defined by a collection of vertices.
/// </summary>
/// <param name="vertices">The vertices of the shape.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Shape"/> struct with the specified vertices.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("Vertices Count: {Vertices.Count}")] [System.Diagnostics.DebuggerDisplay("Vertices Count: {Vertices.Count}")]
public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D> public readonly struct Shape(IList<Vector2D> Vertices) : IEnumerable<Vector2D>
{ {
public static readonly Shape Triangle = CreateNgon(3, Vector2D.Up); public static readonly Shape Triangle = CreateNgon(3, Vector2D.Up);
public static readonly Shape Box = CreateNgon(4, Vector2D.One); public static readonly Shape Box = CreateNgon(4, Vector2D.One);
public static readonly Shape Pentagon = CreateNgon(5, Vector2D.Up); public static readonly Shape Pentagon = CreateNgon(5, Vector2D.Up);
public static readonly Shape Hexagon = CreateNgon(6, Vector2D.Right); public static readonly Shape Hexagon = CreateNgon(6, Vector2D.Right);
private readonly List<Vector2D> _verticesList = vertices; public readonly IList<Vector2D> Vertices { get; init; } = Vertices;
/// <summary>
/// Gets the vertices of the shape.
/// </summary>
public IReadOnlyList<Vector2D> Vertices => _verticesList;
/// <summary>
/// The vertex at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the vertex to get or set.</param>
/// <returns>The vertex at the specified index.</returns>
public Vector2D this[System.Index index] => Vertices[index]; public Vector2D this[System.Index index] => Vertices[index];
/// <summary>
/// Returns a copy of the current shape.
/// </summary>
/// <param name="shape">The shape to copy.</param>
/// <returns>A copy of the input shape.</returns>
public static Shape CreateCopy(Shape shape) => new(new List<Vector2D>(shape.Vertices)); public static Shape CreateCopy(Shape shape) => new(new List<Vector2D>(shape.Vertices));
/// <summary>
/// Creates a regular polygon (ngon) with the specified number of vertices.
/// </summary>
/// <param name="vertexCount">The number of vertices in the polygon.</param>
/// <returns>A regular polygon with the specified number of vertices.</returns>
public static Shape CreateNgon(int vertexCount) => CreateNgon(vertexCount, Vector2D.Up); public static Shape CreateNgon(int vertexCount) => CreateNgon(vertexCount, Vector2D.Up);
/// <summary>
/// Creates a regular polygon (ngon) with the specified number of vertices and a rotation position.
/// </summary>
/// <param name="vertexCount">The number of vertices in the polygon.</param>
/// <param name="positionToRotate">The position to use for rotation.</param>
/// <returns>A regular polygon with the specified number of vertices and rotation position.</returns>
public static Shape CreateNgon(int vertexCount, Vector2D positionToRotate) public static Shape CreateNgon(int vertexCount, Vector2D positionToRotate)
{ {
if (vertexCount < 3) if (vertexCount < 3)
@ -70,11 +37,6 @@ public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D>
return new(vertices); return new(vertices);
} }
/// <summary>
/// Gets the super triangle that encloses the given shape.
/// </summary>
/// <param name="shape">The shape to enclose.</param>
/// <returns>The super triangle that encloses the given shape.</returns>
public static Triangle GetSuperTriangle(Shape shape) public static Triangle GetSuperTriangle(Shape shape)
{ {
float minX = float.MaxValue, minY = float.MaxValue; float minX = float.MaxValue, minY = float.MaxValue;
@ -101,11 +63,6 @@ public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D>
return new Triangle(p1, p2, p3); return new Triangle(p1, p2, p3);
} }
/// <summary>
/// Gets the lines that form the edges of the shape.
/// </summary>
/// <param name="shape">The shape to get lines from.</param>
/// <param name="lines">The list to populate with lines.</param>
public static void GetLines(Shape shape, IList<Line> lines) public static void GetLines(Shape shape, IList<Line> lines)
{ {
lines.Clear(); lines.Clear();
@ -114,11 +71,6 @@ public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D>
lines.Add(new(shape.Vertices[^1], shape.Vertices[0])); lines.Add(new(shape.Vertices[^1], shape.Vertices[0]));
} }
/// <summary>
/// Gets a list of lines that form the edges of the shape.
/// </summary>
/// <param name="shape">The shape to get lines from.</param>
/// <returns>A list of lines that form the edges of the shape.</returns>
public static List<Line> GetLines(Shape shape) public static List<Line> GetLines(Shape shape)
{ {
List<Line> lines = new(shape.Vertices.Count - 1); List<Line> lines = new(shape.Vertices.Count - 1);
@ -126,12 +78,6 @@ public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D>
return lines; return lines;
} }
/// <summary>
/// Projects the shape onto a vector.
/// </summary>
/// <param name="shape">The shape to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <param name="list">The list to populate with projected values.</param>
public static void Project(Shape shape, Vector2D projectionVector, IList<float> list) public static void Project(Shape shape, Vector2D projectionVector, IList<float> list)
{ {
list.Clear(); list.Clear();
@ -141,12 +87,6 @@ public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D>
list.Add(projectionVector.Dot(shape[i])); list.Add(projectionVector.Dot(shape[i]));
} }
/// <summary>
/// Projects the shape onto a vector.
/// </summary>
/// <param name="shape">The shape to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <returns>The projection of the shape onto the vector.</returns>
public static Projection Project(Shape shape, Vector2D projectionVector) public static Projection Project(Shape shape, Vector2D projectionVector)
{ {
float min = float.MaxValue; float min = float.MaxValue;
@ -162,12 +102,6 @@ public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D>
return new(min, max); return new(min, max);
} }
/// <summary>
/// Transforms the shape using the specified transform.
/// </summary>
/// <param name="shape">The shape to transform.</param>
/// <param name="transform">The transform to apply.</param>
/// <returns>The transformed shape.</returns>
public static Shape TransformShape(Shape shape, ITransform transform) public static Shape TransformShape(Shape shape, ITransform transform)
{ {
List<Vector2D> vertices = new(shape.Vertices.Count); List<Vector2D> vertices = new(shape.Vertices.Count);
@ -179,27 +113,15 @@ public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D>
return new Shape(vertices); return new Shape(vertices);
} }
/// <summary>
/// Transforms the shape using the specified transform.
/// </summary>
/// <param name="from">The shape to transform.</param>
/// <param name="transform">The transform to apply.</param>
/// <param name="to">The transformed shape.</param>
public static void TransformShape(Shape from, ITransform transform, ref Shape to) public static void TransformShape(Shape from, ITransform transform, ref Shape to)
{ {
to._verticesList.Clear(); to.Vertices.Clear();
int count = from._verticesList.Count; int count = from.Vertices.Count;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
to._verticesList.Add(transform.TransformVector2D(from[i])); to.Vertices.Add(transform.TransformVector2D(from[i]));
} }
/// <summary>
/// Determines whether two shapes are approximately equal.
/// </summary>
/// <param name="left">The first shape to compare.</param>
/// <param name="right">The second shape to compare.</param>
/// <returns><c>true</c> if the shapes are approximately equal; otherwise, <c>false</c>.</returns>
public static bool ApproximatelyEquals(Shape left, Shape right) public static bool ApproximatelyEquals(Shape left, Shape right)
{ {
if (left.Vertices.Count != right.Vertices.Count) if (left.Vertices.Count != right.Vertices.Count)
@ -212,83 +134,22 @@ public readonly struct Shape(List<Vector2D> vertices) : IEnumerable<Vector2D>
return true; return true;
} }
/// <inheritdoc/>
public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator(); public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator();
} }
/// <summary>
/// Provides extension methods for the <see cref="Shape"/> struct.
/// </summary>
public static class ShapeExtensions public static class ShapeExtensions
{ {
/// <summary>
/// Creates a copy of the shape.
/// </summary>
/// <param name="shape">The shape to copy.</param>
/// <returns>A copy of the input shape.</returns>
public static Shape CreateCopy(this Shape shape) => Shape.CreateCopy(shape); public static Shape CreateCopy(this Shape shape) => Shape.CreateCopy(shape);
/// <summary>
/// Gets the super triangle that encloses the shape.
/// </summary>
/// <param name="shape">The shape to enclose.</param>
/// <returns>The super triangle that encloses the shape.</returns>
public static Triangle ToSuperTriangle(this Shape shape) => Shape.GetSuperTriangle(shape); public static Triangle ToSuperTriangle(this Shape shape) => Shape.GetSuperTriangle(shape);
/// <summary>
/// Gets the lines that form the edges of the shape.
/// </summary>
/// <param name="shape">The shape to get lines from.</param>
/// <param name="lines">The list to populate with lines.</param>
public static void ToLines(this Shape shape, IList<Line> lines) => Shape.GetLines(shape, lines); public static void ToLines(this Shape shape, IList<Line> lines) => Shape.GetLines(shape, lines);
/// <summary>
/// Gets a list of lines that form the edges of the shape.
/// </summary>
/// <param name="shape">The shape to get lines from.</param>
/// <returns>A list of lines that form the edges of the shape.</returns>
public static List<Line> ToLines(this Shape shape) => Shape.GetLines(shape); public static List<Line> ToLines(this Shape shape) => Shape.GetLines(shape);
/// <summary>
/// Projects the shape onto a vector.
/// </summary>
/// <param name="shape">The shape to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <param name="list">The list to populate with projected values.</param>
public static void ToProjection(this Shape shape, Vector2D projectionVector, IList<float> list) => Shape.Project(shape, projectionVector, list); public static void ToProjection(this Shape shape, Vector2D projectionVector, IList<float> list) => Shape.Project(shape, projectionVector, list);
/// <summary>
/// Projects the shape onto a vector.
/// </summary>
/// <param name="shape">The shape to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <returns>The projection of the shape onto the vector.</returns>
public static Projection ToProjection(this Shape shape, Vector2D projectionVector) => Shape.Project(shape, projectionVector); public static Projection ToProjection(this Shape shape, Vector2D projectionVector) => Shape.Project(shape, projectionVector);
/// <summary>
/// Transforms the shape using the specified transform.
/// </summary>
/// <param name="transform">The transform to apply.</param>
/// <param name="shape">The shape to transform.</param>
/// <returns>The transformed shape.</returns>
public static Shape TransformShape(this ITransform transform, Shape shape) => Shape.TransformShape(shape, transform); public static Shape TransformShape(this ITransform transform, Shape shape) => Shape.TransformShape(shape, transform);
/// <summary>
/// Transforms the shape using the specified transform.
/// </summary>
/// <param name="transform">The transform to apply.</param>
/// <param name="from">The shape to transform.</param>
/// <param name="to">The transformed shape.</param>
public static void TransformShape(this ITransform transform, Shape from, ref Shape to) => Shape.TransformShape(from, transform, ref to); public static void TransformShape(this ITransform transform, Shape from, ref Shape to) => Shape.TransformShape(from, transform, ref to);
/// <summary>
/// Determines whether two shapes are approximately equal.
/// </summary>
/// <param name="left">The first shape to compare.</param>
/// <param name="right">The second shape to compare.</param>
/// <returns><c>true</c> if the shapes are approximately equal; otherwise, <c>false</c>.</returns>
public static bool ApproximatelyEquals(this Shape left, Shape right) => Shape.ApproximatelyEquals(left, right); public static bool ApproximatelyEquals(this Shape left, Shape right) => Shape.ApproximatelyEquals(left, right);
} }

View File

@ -1,17 +0,0 @@
# Work In Progress
This engine is still in development but the implemented features include:
- Modular Systems
- Behaviour System
- 2D Physics Engine(**Not Fully Completed, but usable**)
- Rigid Body Simulations
- Collision Detection (Convex Shape & Circle)
- Collision Resolution (**Not Fully Completed**)
- Vector2D, AABB, Circle, Line, LineEquation, Projection & Shape Data Types
- General Math
---
**A detailed README file will be written in the future. If you want to check out how to use this, please checkout this example Pong game made using this engine on top of [MonoGame](https://monogame.net/) from the link bellow.**
[Pong Source Code](https://git.syntriax.com/Syntriax/Engine-Pong)