25 Commits

Author SHA1 Message Date
c8bb991865 refactor!: removed noise from class names
Renamed classes with names XBehaviour to X
2025-07-09 22:20:42 +03:00
bc1c76d746 feat: added priorities to events 2025-07-06 22:22:57 +03:00
8f03628bd6 fix: invocation loop inversed 2025-07-06 22:21:20 +03:00
a1feb0bad3 docs: added documentation for events 2025-07-06 21:04:22 +03:00
978cba96c8 refactor!: event methods renamed for better clarity 2025-07-06 20:39:45 +03:00
7212094a3d chore: updated misleading comment 2025-06-28 14:15:58 +03:00
14843ddeba refactor: removed unnecessary linq call 2025-06-28 12:50:03 +03:00
5315db0077 refactor!: renamed Math.PI to Math.Pi 2025-06-27 14:44:20 +03:00
026f343d43 docs: removed unnecessary comment lines from math constants 2025-06-27 14:42:45 +03:00
da5f31f9d7 refactor: made equality operators consistent in primitives & added missing ones 2025-06-27 12:00:50 +03:00
fa1614f238 feat: added approximately equals methods to projection 1D and ray 2D 2025-06-27 11:44:52 +03:00
0c096d39db docs: line equation XML comments updated 2025-06-27 11:43:54 +03:00
dae6549bad refactor: Equals methods to use equality operators on primitives 2025-06-27 11:37:20 +03:00
767fc28488 refactor: file logger relative path to full path conversion 2025-06-21 00:27:01 +03:00
c3be8f60b7 feat: added logger wrapper class 2025-06-18 17:39:23 +03:00
33cb44bf36 fix: file logger ensure directory exists 2025-06-18 17:39:11 +03:00
4c1018ddec feat: added logger container behaviour 2025-06-18 17:18:08 +03:00
cf7061fd58 fix: shape2D triangulation order changed 2025-06-15 15:14:06 +03:00
e6b7b9953f feat: ensured all primitives have ToString, GetHashCode & Equals methods 2025-06-15 14:44:50 +03:00
4a3775a0de perf: double copy in shape collider's world shape field 2025-06-15 14:34:52 +03:00
4d353662a1 feat: xna color to engine color rgba extension method 2025-06-15 13:32:13 +03:00
ca0b2de917 docs: fixed typo on Shape2D parameter 2025-06-15 13:29:53 +03:00
2335c3ec62 docs: added ray 2d comments 2025-06-13 22:17:39 +03:00
30ccab1b93 refactor: list pool initial count and capacity parameters added 2025-06-09 20:36:39 +03:00
f56d6a7fc8 chore: standalone physics engine not having pooled lists fixed 2025-06-09 20:27:29 +03:00
38 changed files with 761 additions and 169 deletions

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
@@ -50,17 +49,13 @@ public class BehaviourController : BaseEntity, IBehaviourController
public IReadOnlyList<T> GetBehaviours<T>() public IReadOnlyList<T> GetBehaviours<T>()
{ {
List<T>? behaviours = null; List<T> behaviours = [];
foreach (IBehaviour behaviourItem in this.behaviours) foreach (IBehaviour behaviourItem in this.behaviours)
{ if (behaviourItem is T behaviour)
if (behaviourItem is not T behaviour) behaviours.Add(behaviour);
continue;
behaviours ??= []; return behaviours;
behaviours.Add(behaviour);
}
return behaviours ?? Enumerable.Empty<T>().ToList();
} }
public void GetBehaviours<T>(IList<T> results) public void GetBehaviours<T>(IList<T> results)

View File

@@ -7,14 +7,23 @@ public class FileLogger : LoggerBase
{ {
public readonly string FilePath; public readonly string FilePath;
public FileLogger(string filePath)
{
FilePath = filePath;
File.Open(filePath, FileMode.Create).Close();
}
protected override void Write(string message) protected override void Write(string message)
{ {
File.AppendAllTextAsync(FilePath, $"{message}{Environment.NewLine}"); File.AppendAllTextAsync(FilePath, $"{message}{Environment.NewLine}");
} }
public FileLogger(string filePath)
{
FilePath = filePath;
bool isRelativePath = Path.GetFullPath(filePath).CompareTo(filePath) != 0;
if (isRelativePath)
FilePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filePath));
if (Path.GetDirectoryName(FilePath) is string directoryPath)
Directory.CreateDirectory(directoryPath);
File.Open(FilePath, FileMode.Create).Close();
}
} }

View File

@@ -0,0 +1,9 @@
namespace Syntriax.Engine.Core.Debug;
public class LoggerContainer : Behaviour, ILogger
{
public ILogger Logger { get; set; } = new ConsoleLogger();
public ILogger.Level FilterLevel { get => Logger.FilterLevel; set => Logger.FilterLevel = value; }
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false) => Logger.Log(message, level, force);
}

View File

@@ -0,0 +1,23 @@
namespace Syntriax.Engine.Core.Debug;
public class LoggerWrapper(ILogger firstLogger, ILogger secondLogger) : ILogger
{
private readonly ILogger firstLogger = firstLogger;
private readonly ILogger secondLogger = secondLogger;
public ILogger.Level FilterLevel
{
get => firstLogger.FilterLevel;
set
{
firstLogger.FilterLevel = value;
secondLogger.FilterLevel = value;
}
}
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false)
{
firstLogger.Log(message, level, force);
secondLogger.Log(message, level, force);
}
}

View File

@@ -3,32 +3,138 @@ using System.Collections.Generic;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a simple event with no parameters.
/// <para>Example usage:</para>
/// <code>
/// public class MyBehaviour : Behaviour, IUpdate
/// {
/// public readonly Event MyEvent = new();
///
/// public MyBehaviour()
/// {
/// MyEvent.AddListener(OnEventTriggered);
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
/// }
///
/// public void Update()
/// {
/// MyEvent.Invoke();
/// }
///
/// private void OnEventTriggered()
/// {
/// Console.WriteLine($"Event occurred!");
/// }
///
/// private static void OnEventTriggeredOneTime()
/// {
/// Console.WriteLine($"Event called once!");
/// }
/// }
/// </code>
/// The output of the example code above would be:
/// <code>
/// Event occurred!
/// Event called once!
/// Event occurred!
/// Event occurred!
/// Event occurred!
/// ...
/// </code>
/// </summary>
public class Event public class Event
{ {
private readonly List<EventHandler> listeners = null!; // We use Ascending order because draw calls are running from last to first
private readonly List<EventHandler> onceListeners = null!; private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
public void AddListener(EventHandler listener) => listeners.Add(listener); private readonly List<ListenerData> listeners = null!;
public void AddOnceListener(EventHandler listener) => onceListeners.Add(listener); private readonly List<ListenerData> onceListeners = null!;
public void RemoveListener(EventHandler listener) => listeners.Remove(listener);
public void RemoveOnceListener(EventHandler listener) => onceListeners.Remove(listener); /// <summary>
/// Subscribes the callback to be invoked whenever the event is triggered.
/// </summary>
/// <param name="listener">The callback to be called when the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
listeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
/// </summary>
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddOneTimeListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
onceListeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
public void RemoveListener(EventHandler listener)
{
for (int i = listeners.Count - 1; i >= 0; i--)
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
public void RemoveOneTimeListener(EventHandler listener)
{
for (int i = 0; i < onceListeners.Count; i++)
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
public void Clear() { listeners.Clear(); onceListeners.Clear(); } public void Clear() { listeners.Clear(); onceListeners.Clear(); }
/// <summary>
/// Triggers the event.
/// </summary>
public void Invoke() public void Invoke()
{ {
for (int i = 0; i < listeners.Count; i++) for (int i = listeners.Count - 1; i >= 0; i--)
try { listeners[i].Invoke(); } try { listeners[i].Callback.Invoke(); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Method.DeclaringType?.FullName}.{listeners[i].Method.Name}()"; string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}()";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (int i = onceListeners.Count - 1; i >= 0; i--)
{ {
try { onceListeners[i].Invoke(); } try { onceListeners[i].Callback.Invoke(); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Method.DeclaringType?.FullName}.{onceListeners[i].Method.Name}()"; string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}()";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
@@ -48,34 +154,144 @@ public class Event
} }
public delegate void EventHandler(); public delegate void EventHandler();
private record struct ListenerData(EventHandler Callback, int Priority);
} }
/// <summary>
/// Represents an event with only sender parameters.
/// <para>Example usage:</para>
/// <code>
/// public class MyBehaviour : Behaviour, IUpdate
/// {
/// public readonly Event&lt;MyBehaviour&gt; MyEvent = new();
///
/// public MyBehaviour()
/// {
/// MyEvent.AddListener(OnEventTriggered);
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
/// }
///
/// public void Update()
/// {
/// MyEvent.Invoke(this);
/// }
///
/// private void OnEventTriggered(MyBehaviour sender)
/// {
/// Console.WriteLine($"{sender.Id}'s event occurred!");
/// }
///
/// private static void OnEventTriggeredOneTime(MyBehaviour sender)
/// {
/// Console.WriteLine($"{sender.Id}'s event called once!");
/// }
/// }
/// </code>
/// The output of the example code above would be:
/// <code>
/// [Id]'s event occurred!
/// [Id]'s event called once!
/// [Id]'s event occurred!
/// [Id]'s event occurred!
/// [Id]'s event occurred!
/// ...
/// </code>
///
/// </summary>
/// <typeparam name="TSender">Sender type</typeparam>
public class Event<TSender> public class Event<TSender>
{ {
private readonly List<EventHandler> listeners = null!; // We use Ascending order because draw calls are running from last to first
private readonly List<EventHandler> onceListeners = null!; private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
public void AddListener(EventHandler listener) => listeners.Add(listener); private readonly List<ListenerData> listeners = null!;
public void AddOnceListener(EventHandler listener) => onceListeners.Add(listener); private readonly List<ListenerData> onceListeners = null!;
public void RemoveListener(EventHandler listener) => listeners.Remove(listener);
public void RemoveOnceListener(EventHandler listener) => onceListeners.Remove(listener); /// <summary>
/// Subscribes the callback to be invoked whenever the event is triggered.
/// </summary>
/// <param name="listener">The callback to be called when the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
listeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
/// </summary>
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddOneTimeListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
onceListeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
public void RemoveListener(EventHandler listener)
{
for (int i = listeners.Count - 1; i >= 0; i--)
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
public void RemoveOneTimeListener(EventHandler listener)
{
for (int i = 0; i < onceListeners.Count; i++)
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
public void Clear() { listeners.Clear(); onceListeners.Clear(); } public void Clear() { listeners.Clear(); onceListeners.Clear(); }
/// <summary>
/// Triggers the event.
/// </summary>
/// <param name="sender">The caller that's triggering this event.</param>
public void Invoke(TSender sender) public void Invoke(TSender sender)
{ {
for (int i = 0; i < listeners.Count; i++) for (int i = listeners.Count - 1; i >= 0; i--)
try { listeners[i].Invoke(sender); } try { listeners[i].Callback.Invoke(sender); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Method.DeclaringType?.FullName}.{listeners[i].Method.Name}({sender})"; string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({sender})";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (int i = onceListeners.Count - 1; i >= 0; i--)
{ {
try { onceListeners[i].Invoke(sender); } try { onceListeners[i].Callback.Invoke(sender); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Method.DeclaringType?.FullName}.{onceListeners[i].Method.Name}({sender})"; string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({sender})";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
@@ -95,34 +311,152 @@ public class Event<TSender>
} }
public delegate void EventHandler(TSender sender); public delegate void EventHandler(TSender sender);
private record struct ListenerData(EventHandler Callback, int Priority);
} }
/// <summary>
/// Represents an event with sender and argument parameters.
/// <para>Example usage:</para>
/// <code>
/// public class MyBehaviour : Behaviour, IUpdate
/// {
/// public readonly Event&lt;MyBehaviour, MyArguments&gt; MyEvent = new();
///
/// private int myInt = 0;
/// private bool myBool = false;
///
/// public MyBehaviour()
/// {
/// MyEvent.AddOneTimeListener(OnEventTriggeredOneTime);
/// MyEvent.AddListener(OnEventTriggered);
/// }
///
/// public void Update()
/// {
/// MyEvent.Invoke(this, new MyArguments(myInt, myBool));
/// myInt++;
/// myBool = !myBool;
/// }
///
/// private void OnEventTriggered(MyBehaviour sender, MyArguments args)
/// {
/// Console.WriteLine($"{sender.Id}'s event occurred with MyInt: {args.MyInt} and MyBool {args.MyBool}!");
/// }
///
/// private static void OnEventTriggeredOneTime(MyBehaviour sender, MyArguments args)
/// {
/// Console.WriteLine($"{sender.Id}'s event called once with MyInt: {args.MyInt} and MyBool {args.MyBool}!");
/// }
///
/// public readonly record struct MyArguments(int MyInt, bool MyBool);
/// }
/// </code>
/// The output of the example code above would be:
/// <code>
/// [Id]'s event occurred with MyInt: 0 and MyBool False!
/// [Id]'s event called once with MyInt: 0 and MyBool False!
/// [Id]'s event occurred with MyInt: 1 and MyBool True!
/// [Id]'s event occurred with MyInt: 2 and MyBool False!
/// [Id]'s event occurred with MyInt: 3 and MyBool True!
/// ...
/// </code>
///
/// </summary>
/// <typeparam name="TSender">Sender type</typeparam>
public class Event<TSender, TArguments> public class Event<TSender, TArguments>
{ {
private readonly List<EventHandler> listeners = null!; // We use Ascending order because draw calls are running from last to first
private readonly List<EventHandler> onceListeners = null!; private static readonly Comparer<ListenerData> SortByAscendingPriority = Comparer<ListenerData>.Create((x, y) => x.Priority.CompareTo(y.Priority));
public void AddListener(EventHandler listener) => listeners.Add(listener); private readonly List<ListenerData> listeners = null!;
public void AddOnceListener(EventHandler listener) => onceListeners.Add(listener); private readonly List<ListenerData> onceListeners = null!;
public void RemoveListener(EventHandler listener) => listeners.Remove(listener);
public void RemoveOnceListener(EventHandler listener) => onceListeners.Remove(listener); /// <summary>
/// Subscribes the callback to be invoked whenever the event is triggered.
/// </summary>
/// <param name="listener">The callback to be called when the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = listeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
listeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Subscribes the callback to be invoked the next time the event is triggered. The callback will be called only once.
/// </summary>
/// <param name="listener">The callback to be called the next time the event is triggered.</param>
/// <param name="priority">Priority of the callback.</param>
public void AddOneTimeListener(EventHandler listener, int priority = 0)
{
ListenerData listenerData = new(listener, priority);
int insertIndex = onceListeners.BinarySearch(listenerData, SortByAscendingPriority);
if (insertIndex < 0)
insertIndex = ~insertIndex;
onceListeners.Insert(insertIndex, listenerData);
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddListener(EventHandler)"/></param>
public void RemoveListener(EventHandler listener)
{
for (int i = listeners.Count - 1; i >= 0; i--)
if (listeners[i].Callback == listener)
{
listeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes the callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
/// <param name="listener">The callback that was previously registered by <see cref="AddOneTimeListener(EventHandler)"/></param>
public void RemoveOneTimeListener(EventHandler listener)
{
for (int i = 0; i < onceListeners.Count; i++)
if (onceListeners[i].Callback == listener)
{
onceListeners.RemoveAt(i);
return;
}
}
/// <summary>
/// Unsubscribes all listeners that was previously registered by either <see cref="AddListener(EventHandler)"/> or <see cref="AddOneTimeListener(EventHandler)"/>.
/// </summary>
public void Clear() { listeners.Clear(); onceListeners.Clear(); } public void Clear() { listeners.Clear(); onceListeners.Clear(); }
/// <summary>
/// Triggers the event.
/// </summary>
/// <param name="sender">The caller that's triggering this event.</param>
/// <param name="args">The arguments provided for this event.</param>
public void Invoke(TSender sender, TArguments args) public void Invoke(TSender sender, TArguments args)
{ {
for (int i = 0; i < listeners.Count; i++) for (int i = listeners.Count - 1; i >= 0; i--)
try { listeners[i].Invoke(sender, args); } try { listeners[i].Callback.Invoke(sender, args); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{listeners[i].Method.DeclaringType?.FullName}.{listeners[i].Method.Name}({string.Join(", ", sender, args)})"; string methodCallRepresentation = $"{listeners[i].Callback.Method.DeclaringType?.FullName}.{listeners[i].Callback.Method.Name}({string.Join(", ", sender, args)})";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
} }
for (int i = onceListeners.Count - 1; i >= 0; i--) for (int i = onceListeners.Count - 1; i >= 0; i--)
{ {
try { onceListeners[i].Invoke(sender, args); } try { onceListeners[i].Callback.Invoke(sender, args); }
catch (Exception exception) catch (Exception exception)
{ {
string methodCallRepresentation = $"{onceListeners[i].Method.DeclaringType?.FullName}.{onceListeners[i].Method.Name}({string.Join(", ", sender, args)})"; string methodCallRepresentation = $"{onceListeners[i].Callback.Method.DeclaringType?.FullName}.{onceListeners[i].Callback.Method.Name}({string.Join(", ", sender, args)})";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}"); Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
} }
onceListeners.RemoveAt(i); onceListeners.RemoveAt(i);
@@ -142,4 +476,5 @@ public class Event<TSender, TArguments>
} }
public delegate void EventHandler(TSender sender, TArguments args); public delegate void EventHandler(TSender sender, TArguments args);
private record struct ListenerData(EventHandler Callback, int Priority);
} }

View File

@@ -31,10 +31,10 @@ public class ListPool<T> : IPool<List<T>>
OnReturned?.Invoke(this, list); OnReturned?.Invoke(this, list);
} }
public ListPool(Func<List<T>> generator, int initialCapacity = 1) public ListPool(int initialListCount = 1, int initialListCapacity = 32)
{ {
this.generator = generator; generator = () => new(initialListCapacity);
for (int i = 0; i < initialCapacity; i++) for (int i = 0; i < initialListCount; i++)
queue.Enqueue(generator()); queue.Enqueue(generator());
} }
} }

View File

@@ -6,29 +6,29 @@ namespace Syntriax.Engine.Core;
public static class Math public static class Math
{ {
/// <summary> /// <summary>
/// The value of Pi (π), a mathematical constant approximately equal to 3.14159. /// The value of Pi (π).
/// </summary> /// </summary>
public const float PI = 3.1415926535897932f; public const float Pi = 3.1415926535897932f;
/// <summary> /// <summary>
/// The value of Tau (τ), a mathematical constant equal to 2π, approximately equal to 6.28319. /// The value of Tau (τ), mathematical constant equal to 2π.
/// </summary> /// </summary>
public const float Tau = 2f * PI; public const float Tau = 2f * Pi;
/// <summary> /// <summary>
/// The base of the natural logarithm, approximately equal to 2.71828. /// The base of the natural logarithm.
/// </summary> /// </summary>
public const float E = 2.718281828459045f; public const float E = 2.718281828459045f;
/// <summary> /// <summary>
/// The conversion factor from radians to degrees. /// The conversion factor from radians to degrees.
/// </summary> /// </summary>
public const float RadianToDegree = 180f / PI; public const float RadianToDegree = 180f / Pi;
/// <summary> /// <summary>
/// The conversion factor from degrees to radians. /// The conversion factor from degrees to radians.
/// </summary> /// </summary>
public const float DegreeToRadian = PI / 180f; public const float DegreeToRadian = Pi / 180f;
/// <summary> /// <summary>
/// Gets one minus of given <see cref="T"/>. /// Gets one minus of given <see cref="T"/>.

View File

@@ -38,6 +38,9 @@ public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
/// </summary> /// </summary>
public readonly Vector2D SizeHalf => Size * .5f; public readonly Vector2D SizeHalf => Size * .5f;
public static bool operator ==(AABB left, AABB right) => left.UpperBoundary == right.UpperBoundary && left.LowerBoundary == right.LowerBoundary;
public static bool operator !=(AABB left, AABB right) => left.UpperBoundary != right.UpperBoundary || left.LowerBoundary != right.LowerBoundary;
/// <summary> /// <summary>
/// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s. /// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s.
/// </summary> /// </summary>
@@ -63,12 +66,6 @@ public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
return new(lowerBoundary, upperBoundary); return new(lowerBoundary, upperBoundary);
} }
/// <summary>
/// Converts the <see cref="AABB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="AABB"/>.</returns>
public override string ToString() => $"{nameof(AABB)}({LowerBoundary}, {UpperBoundary})";
/// <summary> /// <summary>
/// Checks if two <see cref="AABB"/>s are approximately equal. /// Checks if two <see cref="AABB"/>s are approximately equal.
/// </summary> /// </summary>
@@ -78,6 +75,25 @@ public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
/// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(AABB left, AABB right, float epsilon = float.Epsilon) public static bool ApproximatelyEquals(AABB left, AABB right, float epsilon = float.Epsilon)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, epsilon); => left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="AABB"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="AABB"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="AABB"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is AABB aabb && this == aabb;
/// <summary>
/// Generates a hash code for the <see cref="AABB"/>.
/// </summary>
/// <returns>A hash code for the <see cref="AABB"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(LowerBoundary, UpperBoundary);
/// <summary>
/// Converts the <see cref="AABB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="AABB"/>.</returns>
public override string ToString() => $"{nameof(AABB)}({LowerBoundary}, {UpperBoundary})";
} }
/// <summary> /// <summary>

View File

@@ -38,6 +38,9 @@ public readonly struct Circle(Vector2D center, float radius)
/// </summary> /// </summary>
public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f); public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f);
public static bool operator ==(Circle left, Circle right) => left.Center == right.Center && left.Radius == right.Radius;
public static bool operator !=(Circle left, Circle right) => left.Center != right.Center || left.Radius != right.Radius;
/// <summary> /// <summary>
/// Sets the center of the <see cref="Circle"/>. /// Sets the center of the <see cref="Circle"/>.
/// </summary> /// </summary>
@@ -77,6 +80,25 @@ public readonly struct Circle(Vector2D center, float radius)
/// <returns><see cref="true"/> if the <see cref="Circle"/>s are approximately equal; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the <see cref="Circle"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Circle left, Circle right, float epsilon = float.Epsilon) public static bool ApproximatelyEquals(Circle left, Circle right, float epsilon = float.Epsilon)
=> left.Center.ApproximatelyEquals(right.Center, epsilon) && left.Radius.ApproximatelyEquals(right.Radius, epsilon); => left.Center.ApproximatelyEquals(right.Center, epsilon) && left.Radius.ApproximatelyEquals(right.Radius, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Circle"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Circle"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Circle"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Circle circle && this == circle;
/// <summary>
/// Generates a hash code for the <see cref="Circle"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Circle"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Center, Radius);
/// <summary>
/// Converts the <see cref="Circle"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Circle"/>.</returns>
public override string ToString() => $"{nameof(Circle)}({Center}, {Radius})";
} }
/// <summary> /// <summary>

View File

@@ -34,8 +34,8 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
public static ColorHSV operator *(ColorHSV color, float value) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f)); public static ColorHSV operator *(ColorHSV color, float value) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f));
public static ColorHSV operator *(float value, ColorHSV color) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f)); public static ColorHSV operator *(float value, ColorHSV color) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f));
public static ColorHSV operator /(ColorHSV color, float value) => new((color.Hue / value).Clamp(0f, 1f), (color.Saturation / value).Clamp(0f, 1f), (color.Value / value).Clamp(0f, 1f)); public static ColorHSV operator /(ColorHSV color, float value) => new((color.Hue / value).Clamp(0f, 1f), (color.Saturation / value).Clamp(0f, 1f), (color.Value / value).Clamp(0f, 1f));
public static bool operator ==(ColorHSV left, ColorHSV right) => left.Hue.ApproximatelyEquals(right.Hue) && left.Saturation.ApproximatelyEquals(right.Saturation) && left.Value.ApproximatelyEquals(right.Value); public static bool operator ==(ColorHSV left, ColorHSV right) => left.Hue == right.Hue && left.Saturation == right.Saturation && left.Value == right.Value;
public static bool operator !=(ColorHSV left, ColorHSV right) => !left.Hue.ApproximatelyEquals(right.Hue) || !left.Saturation.ApproximatelyEquals(right.Saturation) || !left.Value.ApproximatelyEquals(right.Value); public static bool operator !=(ColorHSV left, ColorHSV right) => left.Hue != right.Hue || left.Saturation != right.Saturation || left.Value != right.Value;
public static implicit operator ColorHSV(ColorRGBA rgba) => (ColorRGB)rgba; public static implicit operator ColorHSV(ColorRGBA rgba) => (ColorRGB)rgba;
public static implicit operator ColorHSV(ColorRGB rgb) public static implicit operator ColorHSV(ColorRGB rgb)
@@ -127,12 +127,6 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
/// <returns>The interpolated <see cref="ColorHSV"/>.</returns> /// <returns>The interpolated <see cref="ColorHSV"/>.</returns>
public static ColorHSV Lerp(ColorHSV from, ColorHSV to, float t) => from + FromTo(from, to) * t; public static ColorHSV Lerp(ColorHSV from, ColorHSV to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Converts the <see cref="ColorHSV"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorHSV"/>.</returns>
public override string ToString() => $"{nameof(ColorHSV)}({Hue}, {Saturation}, {Value})";
/// <summary> /// <summary>
/// Checks if two <see cref="ColorHSV"/>s are approximately equal within a specified epsilon range. /// Checks if two <see cref="ColorHSV"/>s are approximately equal within a specified epsilon range.
/// </summary> /// </summary>
@@ -148,13 +142,19 @@ public readonly struct ColorHSV(float hue, float saturation, float value)
/// </summary> /// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorHSV"/>.</param> /// <param name="obj">The object to compare with the current <see cref="ColorHSV"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorHSV"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorHSV"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorHSV objVec && Hue.Equals(objVec.Hue) && Saturation.Equals(objVec.Saturation) && Value.Equals(objVec.Value); public override bool Equals(object? obj) => obj is ColorHSV colorHSV && this == colorHSV;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="ColorHSV"/>. /// Generates a hash code for the <see cref="ColorHSV"/>.
/// </summary> /// </summary>
/// <returns>A hash code for the <see cref="ColorHSV"/>.</returns> /// <returns>A hash code for the <see cref="ColorHSV"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Hue, Saturation, Value); public override int GetHashCode() => System.HashCode.Combine(Hue, Saturation, Value);
/// <summary>
/// Converts the <see cref="ColorHSV"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorHSV"/>.</returns>
public override string ToString() => $"{nameof(ColorHSV)}({Hue}, {Saturation}, {Value})";
} }
/// <summary> /// <summary>

View File

@@ -119,24 +119,24 @@ public readonly struct ColorRGB(byte r, byte g, byte b)
/// <returns>The interpolated <see cref="ColorRGB"/>.</returns> /// <returns>The interpolated <see cref="ColorRGB"/>.</returns>
public static ColorRGB Lerp(ColorRGB from, ColorRGB to, float t) => from + FromTo(from, to) * t; public static ColorRGB Lerp(ColorRGB from, ColorRGB to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Converts the <see cref="ColorRGB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGB"/>.</returns>
public override string ToString() => $"{nameof(ColorRGB)}({R}, {G}, {B})";
/// <summary> /// <summary>
/// Determines whether the specified object is equal to the current <see cref="ColorRGB"/>. /// Determines whether the specified object is equal to the current <see cref="ColorRGB"/>.
/// </summary> /// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorRGB"/>.</param> /// <param name="obj">The object to compare with the current <see cref="ColorRGB"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGB"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGB"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorRGB objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B); public override bool Equals(object? obj) => obj is ColorRGB colorRGB && this == colorRGB;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="ColorRGB"/>. /// Generates a hash code for the <see cref="ColorRGB"/>.
/// </summary> /// </summary>
/// <returns>A hash code for the <see cref="ColorRGB"/>.</returns> /// <returns>A hash code for the <see cref="ColorRGB"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(R, G, B); public override int GetHashCode() => System.HashCode.Combine(R, G, B);
/// <summary>
/// Converts the <see cref="ColorRGB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGB"/>.</returns>
public override string ToString() => $"{nameof(ColorRGB)}({R}, {G}, {B})";
} }
/// <summary> /// <summary>

View File

@@ -102,24 +102,24 @@ public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
/// <returns>The interpolated <see cref="ColorRGBA"/>.</returns> /// <returns>The interpolated <see cref="ColorRGBA"/>.</returns>
public static ColorRGBA Lerp(ColorRGBA from, ColorRGBA to, float t) => from + FromTo(from, to) * t; public static ColorRGBA Lerp(ColorRGBA from, ColorRGBA to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Converts the <see cref="ColorRGBA"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGBA"/>.</returns>
public override string ToString() => $"{nameof(ColorRGBA)}({R}, {G}, {B}, {A})";
/// <summary> /// <summary>
/// Determines whether the specified object is equal to the current <see cref="ColorRGBA"/>. /// Determines whether the specified object is equal to the current <see cref="ColorRGBA"/>.
/// </summary> /// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorRGBA"/>.</param> /// <param name="obj">The object to compare with the current <see cref="ColorRGBA"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGBA"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGBA"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorRGBA objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B) && A.Equals(objVec.A); public override bool Equals(object? obj) => obj is ColorRGBA colorRGBA && this == colorRGBA;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="ColorRGBA"/>. /// Generates a hash code for the <see cref="ColorRGBA"/>.
/// </summary> /// </summary>
/// <returns>A hash code for the <see cref="ColorRGBA"/>.</returns> /// <returns>A hash code for the <see cref="ColorRGBA"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(R, G, B, A); public override int GetHashCode() => System.HashCode.Combine(R, G, B, A);
/// <summary>
/// Converts the <see cref="ColorRGBA"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGBA"/>.</returns>
public override string ToString() => $"{nameof(ColorRGBA)}({R}, {G}, {B}, {A})";
} }
/// <summary> /// <summary>

View File

@@ -43,6 +43,9 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
/// </summary> /// </summary>
public readonly float LengthSquared => From.FromTo(To).LengthSquared(); public readonly float LengthSquared => From.FromTo(To).LengthSquared();
public static bool operator ==(Line2D left, Line2D right) => left.From == right.From && left.To == right.To;
public static bool operator !=(Line2D left, Line2D right) => left.From != right.From || left.To != right.To;
/// <summary> /// <summary>
/// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment. /// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment.
/// </summary> /// </summary>
@@ -186,6 +189,25 @@ public readonly struct Line2D(Vector2D from, Vector2D to)
/// <returns><see cref="true"/> if the <see cref="Line2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the <see cref="Line2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Line2D left, Line2D right, float epsilon = float.Epsilon) public static bool ApproximatelyEquals(Line2D left, Line2D right, float epsilon = float.Epsilon)
=> left.From.ApproximatelyEquals(right.From, epsilon) && left.To.ApproximatelyEquals(right.To, epsilon); => left.From.ApproximatelyEquals(right.From, epsilon) && left.To.ApproximatelyEquals(right.To, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Line2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Line2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Line2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Line2D line2D && this == line2D;
/// <summary>
/// Generates a hash code for the <see cref="Line2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Line2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(From, To);
/// <summary>
/// Converts the <see cref="Line2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Line2D"/>.</returns>
public override string ToString() => $"{nameof(Line2D)}({From}, {To})";
} }
/// <summary> /// <summary>

View File

@@ -1,47 +1,69 @@
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
/// <summary> /// <summary>
/// Represents a line equation in the form y = mx + b. /// Represents a <see cref="Line2DEquation"/> in the form y = mx + b.
/// </summary> /// </summary>
/// <param name="slope">The slope of the line.</param> /// <param name="slope">The slope of the line.</param>
/// <param name="offsetY">The y-intercept of the line.</param> /// <param name="offsetY">The Y intercept of the line.</param>
/// <remarks> /// <remarks>
/// Initializes a new instance of the <see cref="Line2DEquation"/> struct with the specified slope and y-intercept. /// Initializes a new instance of the <see cref="Line2DEquation"/> struct with the specified slope and Y intercept.
/// </remarks> /// </remarks>
[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")] [System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
public readonly struct Line2DEquation(float slope, float offsetY) public readonly struct Line2DEquation(float slope, float offsetY)
{ {
/// <summary> /// <summary>
/// The slope of the line equation. /// The slope of the <see cref="Line2DEquation"/>.
/// </summary> /// </summary>
public readonly float Slope = slope; public readonly float Slope = slope;
/// <summary> /// <summary>
/// The y-intercept of the line equation. /// The Y intercept of the <see cref="Line2DEquation"/>.
/// </summary> /// </summary>
public readonly float OffsetY = offsetY; public readonly float OffsetY = offsetY;
public static bool operator ==(Line2DEquation left, Line2DEquation right) => left.Slope == right.Slope && left.OffsetY == right.OffsetY;
public static bool operator !=(Line2DEquation left, Line2DEquation right) => left.Slope != right.Slope || left.OffsetY != right.OffsetY;
/// <summary> /// <summary>
/// Resolves the y-coordinate for a given x-coordinate using the line equation. /// Resolves the Y coordinate for a given X coordinate using the <see cref="Line2DEquation"/>.
/// </summary> /// </summary>
/// <param name="lineEquation">The line equation to resolve.</param> /// <param name="lineEquation">The <see cref="Line2DEquation"/> to resolve.</param>
/// <param name="x">The x-coordinate for which to resolve the y-coordinate.</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> /// <returns>The Y coordinate resolved using the <see cref="Line2DEquation"/>.</returns>
public static float Resolve(Line2DEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b public static float Resolve(Line2DEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b
/// <summary> /// <summary>
/// Checks if two line equations are approximately equal. /// Checks if two <see cref="Line2DEquation"/> are approximately equal.
/// </summary> /// </summary>
/// <param name="left">The first line equation to compare.</param> /// <param name="left">The first <see cref="Line2DEquation"/> to compare.</param>
/// <param name="right">The second line equation to compare.</param> /// <param name="right">The second <see cref="Line2DEquation"/> to compare.</param>
/// <param name="epsilon">The epsilon range.</param> /// <param name="epsilon">The epsilon range.</param>
/// <returns>True if the line equations are approximately equal; otherwise, false.</returns> /// <returns>True if the <see cref="Line2DEquation"/> are approximately equal; otherwise, false.</returns>
public static bool ApproximatelyEquals(Line2DEquation left, Line2DEquation right, float epsilon = float.Epsilon) public static bool ApproximatelyEquals(Line2DEquation left, Line2DEquation right, float epsilon = float.Epsilon)
=> left.Slope.ApproximatelyEquals(right.Slope, epsilon) && left.OffsetY.ApproximatelyEquals(right.OffsetY, epsilon); => left.Slope.ApproximatelyEquals(right.Slope, epsilon) && left.OffsetY.ApproximatelyEquals(right.OffsetY, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Line2DEquation"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Line2DEquation"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Line2DEquation"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Line2DEquation lineEquation && this == lineEquation;
/// <summary>
/// Generates a hash code for the <see cref="Line2DEquation"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Line2DEquation"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Slope, OffsetY);
/// <summary>
/// Converts the <see cref="Line2DEquation"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Line2DEquation"/>.</returns>
public override string ToString() => $"{nameof(Line2DEquation)}({Slope}, {OffsetY})";
} }
/// <summary> /// <summary>
/// Provides extension methods for the LineEquation struct. /// Provides extension methods for the <see cref="Line2DEquation"/> struct.
/// </summary> /// </summary>
public static class Line2DEquationExtensions public static class Line2DEquationExtensions
{ {

View File

@@ -21,6 +21,9 @@ public readonly struct Projection1D(float min, float max)
/// </summary> /// </summary>
public readonly float Max = max; public readonly float Max = max;
public static bool operator ==(Projection1D left, Projection1D right) => left.Min == right.Min && left.Max == right.Max;
public static bool operator !=(Projection1D left, Projection1D right) => left.Min != right.Min || left.Max != right.Max;
/// <summary> /// <summary>
/// Checks if two projections overlap. /// Checks if two projections overlap.
/// </summary> /// </summary>
@@ -70,6 +73,35 @@ public readonly struct Projection1D(float min, float max)
depth = 0f; depth = 0f;
return false; return false;
} }
/// <summary>
/// Checks if two <see cref="Projection1D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Projection1D"/>.</param>
/// <param name="right">The second <see cref="Projection1D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Projection1D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Projection1D left, Projection1D right, float epsilon = float.Epsilon)
=> left.Min.ApproximatelyEquals(right.Min, epsilon) && left.Max.ApproximatelyEquals(right.Max, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Projection1D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Projection1D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Projection1D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Projection1D projection1D && this == projection1D;
/// <summary>
/// Generates a hash code for the <see cref="Projection1D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Projection1D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Min, Max);
/// <summary>
/// Converts the <see cref="Projection1D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Projection1D"/>.</returns>
public override string ToString() => $"{nameof(Projection1D)}({Min}, {Max})";
} }
/// <summary> /// <summary>
@@ -82,4 +114,7 @@ public static class Projection1DExtensions
/// <inheritdoc cref="Projection1D.Overlaps(Projection1D, Projection1D, out float)" /> /// <inheritdoc cref="Projection1D.Overlaps(Projection1D, Projection1D, out float)" />
public static bool Overlaps(this Projection1D left, Projection1D right, out float depth) => Projection1D.Overlaps(left, right, out depth); public static bool Overlaps(this Projection1D left, Projection1D right, out float depth) => Projection1D.Overlaps(left, right, out depth);
/// <inheritdoc cref="Projection1D.ApproximatelyEquals(Projection1D, Projection1D, float)" />
public static bool ApproximatelyEquals(this Projection1D left, Projection1D right, float epsilon = float.Epsilon) => Projection1D.ApproximatelyEquals(left, right, epsilon);
} }

View File

@@ -282,24 +282,24 @@ public readonly struct Quaternion(float x, float y, float z, float w)
public static bool ApproximatelyEquals(Quaternion left, Quaternion right, float epsilon = float.Epsilon) public static bool ApproximatelyEquals(Quaternion left, Quaternion right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon) && left.W.ApproximatelyEquals(right.W, epsilon); => left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon) && left.W.ApproximatelyEquals(right.W, epsilon);
/// <summary>
/// Converts the <see cref="Quaternion"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
public override string ToString() => $"{nameof(Quaternion)}({W}, {X}, {Y}, {Z})";
/// <summary> /// <summary>
/// Determines whether the specified object is equal to the current <see cref="Quaternion"/>. /// Determines whether the specified object is equal to the current <see cref="Quaternion"/>.
/// </summary> /// </summary>
/// <param name="obj">The object to compare with the current <see cref="Quaternion"/>.</param> /// <param name="obj">The object to compare with the current <see cref="Quaternion"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Quaternion"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Quaternion"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Quaternion objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y) && Z.Equals(objVec.Z) && W.Equals(objVec.W); public override bool Equals(object? obj) => obj is Quaternion quaternion && this == quaternion;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Quaternion"/>. /// Generates a hash code for the <see cref="Quaternion"/>.
/// </summary> /// </summary>
/// <returns>A hash code for the <see cref="Quaternion"/>.</returns> /// <returns>A hash code for the <see cref="Quaternion"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z); public override int GetHashCode() => System.HashCode.Combine(W, X, Y, Z);
/// <summary>
/// Converts the <see cref="Quaternion"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
public override string ToString() => $"{nameof(Quaternion)}({W}, {X}, {Y}, {Z})";
} }
/// <summary> /// <summary>

View File

@@ -1,5 +1,10 @@
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
/// <summary>
/// Represents an infinite ray in 2D space.
/// </summary>
/// <param name="Origin">The <see cref="Vector2D"/> in 2D space where the ray starts from.</param>
/// <param name="Direction">Normalized <see cref="Vector2D"/> indicating the ray's is direction.</param>
public readonly struct Ray2D(Vector2D Origin, Vector2D Direction) public readonly struct Ray2D(Vector2D Origin, Vector2D Direction)
{ {
/// <summary> /// <summary>
@@ -17,6 +22,8 @@ public readonly struct Ray2D(Vector2D Origin, Vector2D Direction)
/// </summary> /// </summary>
public readonly Ray2D Reversed => new(Origin, -Direction); public readonly Ray2D Reversed => new(Origin, -Direction);
public static bool operator ==(Ray2D left, Ray2D right) => left.Origin == right.Origin && left.Direction == right.Direction;
public static bool operator !=(Ray2D left, Ray2D right) => left.Origin != right.Origin || left.Direction != right.Direction;
public static implicit operator Ray2D(Line2D line) => new(line.From, line.From.FromTo(line.To).Normalized); public static implicit operator Ray2D(Line2D line) => new(line.From, line.From.FromTo(line.To).Normalized);
/// <summary> /// <summary>
@@ -48,6 +55,35 @@ public readonly struct Ray2D(Vector2D Origin, Vector2D Direction)
return ray.Origin + ray.Direction * dot; return ray.Origin + ray.Direction * dot;
} }
/// <summary>
/// Checks if two <see cref="Ray2D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Ray2D"/>.</param>
/// <param name="right">The second <see cref="Ray2D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Ray2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Ray2D left, Ray2D right, float epsilon = float.Epsilon)
=> left.Origin.ApproximatelyEquals(right.Origin, epsilon) && left.Direction.ApproximatelyEquals(right.Direction, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Ray2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Ray2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Ray2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Ray2D ray2D && this == ray2D;
/// <summary>
/// Generates a hash code for the <see cref="Ray2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Ray2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Origin, Direction);
/// <summary>
/// Converts the <see cref="Ray2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Ray2D"/>.</returns>
public override string ToString() => $"{nameof(Ray2D)}({Origin}, {Direction})";
} }
/// <summary> /// <summary>
@@ -63,4 +99,7 @@ public static class Ray2DExtensions
/// <inheritdoc cref="Ray2D.ClosestPointTo(Ray2D, Vector2D) /> /// <inheritdoc cref="Ray2D.ClosestPointTo(Ray2D, Vector2D) />
public static Vector2D ClosestPointTo(this Ray2D ray, Vector2D point) => Ray2D.ClosestPointTo(ray, point); public static Vector2D ClosestPointTo(this Ray2D ray, Vector2D point) => Ray2D.ClosestPointTo(ray, point);
/// <inheritdoc cref="Ray2D.ApproximatelyEquals(Ray2D, Ray2D, float)" />
public static bool ApproximatelyEquals(this Ray2D left, Ray2D right, float epsilon = float.Epsilon) => Ray2D.ApproximatelyEquals(left, right, epsilon);
} }

View File

@@ -122,7 +122,7 @@ public class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
triangles.Clear(); triangles.Clear();
for (int i = 2; i < shape.Vertices.Count; i++) for (int i = 2; i < shape.Vertices.Count; i++)
triangles.Add(new Triangle(shape[0], shape[i - 1], shape[i])); triangles.Add(new Triangle(shape[0], shape[i], shape[i - 1]));
} }
/// <summary> /// <summary>
@@ -251,6 +251,34 @@ public class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
return true; return true;
} }
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Shape2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Shape2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Shape2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Shape2D shape2D && _vertices.Equals(shape2D._vertices);
/// <summary>
/// Generates a hash code for the <see cref="Shape2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Shape2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Vertices);
/// <summary>
/// Converts the <see cref="Shape2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Shape2D"/>.</returns>
public override string ToString()
{
System.Text.StringBuilder stringBuilder = new(Vertices[0].ToString());
for (int i = 1; i < Vertices.Count; i++)
{
stringBuilder.Append(", ");
stringBuilder.Append(Vertices[i].ToString());
}
return $"{nameof(Shape2D)}({stringBuilder})";
}
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator(); public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator();
@@ -270,7 +298,7 @@ public static class Shape2DExtensions
public static Triangle ToSuperTriangle(this Shape2D shape) => Shape2D.GetSuperTriangle(shape); public static Triangle ToSuperTriangle(this Shape2D shape) => Shape2D.GetSuperTriangle(shape);
/// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D, IList{Triangle})" /> /// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D, IList{Triangle})" />
public static void ToTrianglesConvex(this Shape2D shape, IList<Triangle> lines) => Shape2D.TriangulateConvex(shape, lines); public static void ToTrianglesConvex(this Shape2D shape, IList<Triangle> triangles) => Shape2D.TriangulateConvex(shape, triangles);
/// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D)" /> /// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D)" />
public static List<Triangle> ToTrianglesConvex(this Shape2D shape) => Shape2D.TriangulateConvex(shape); public static List<Triangle> ToTrianglesConvex(this Shape2D shape) => Shape2D.TriangulateConvex(shape);

View File

@@ -7,6 +7,9 @@ public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C)
public readonly Vector2D B { get; init; } = B; public readonly Vector2D B { get; init; } = B;
public readonly Vector2D C { get; init; } = C; public readonly Vector2D C { get; init; } = C;
public static bool operator ==(Triangle left, Triangle right) => left.A == right.A && left.B == right.B && left.C == right.C;
public static bool operator !=(Triangle left, Triangle right) => left.A != right.A || left.B != right.B || left.C != right.C;
public readonly float Area public readonly float Area
=> .5f * Math.Abs( => .5f * Math.Abs(
A.X * (B.Y - C.Y) + A.X * (B.Y - C.Y) +
@@ -44,6 +47,25 @@ public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C)
/// <returns><c>true</c> if the <see cref="Triangle"/>s are approximately equal; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the <see cref="Triangle"/>s are approximately equal; otherwise, <c>false</c>.</returns>
public static bool ApproximatelyEquals(Triangle left, Triangle right, float epsilon = float.Epsilon) public static bool ApproximatelyEquals(Triangle left, Triangle right, float epsilon = float.Epsilon)
=> left.A.ApproximatelyEquals(right.A, epsilon) && left.B.ApproximatelyEquals(right.B, epsilon) && left.C.ApproximatelyEquals(right.C, epsilon); => left.A.ApproximatelyEquals(right.A, epsilon) && left.B.ApproximatelyEquals(right.B, epsilon) && left.C.ApproximatelyEquals(right.C, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Triangle"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Triangle"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Triangle"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Triangle triangle && this == triangle;
/// <summary>
/// Generates a hash code for the <see cref="Triangle"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Triangle"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(A, B, C);
/// <summary>
/// Converts the <see cref="Triangle"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Triangle"/>.</returns>
public override string ToString() => $"{nameof(Triangle)}({A}, {B}, {C})";
} }
public static class TriangleExtensions public static class TriangleExtensions

View File

@@ -302,24 +302,24 @@ public readonly struct Vector2D(float x, float y)
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})";
/// <summary> /// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector2D"/>. /// Determines whether the specified object is equal to the current <see cref="Vector2D"/>.
/// </summary> /// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector2D"/>.</param> /// <param name="obj">The object to compare with the current <see cref="Vector2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector2D"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector2D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y); public override bool Equals(object? obj) => obj is Vector2D vector2D && this == vector2D;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Vector2D"/>. /// Generates a hash code for the <see cref="Vector2D"/>.
/// </summary> /// </summary>
/// <returns>A hash code for the <see cref="Vector2D"/>.</returns> /// <returns>A hash code for the <see cref="Vector2D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y); public override int GetHashCode() => System.HashCode.Combine(X, Y);
/// <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})";
} }
/// <summary> /// <summary>

View File

@@ -271,24 +271,24 @@ public readonly struct Vector3D(float x, float y, float z)
public static bool ApproximatelyEquals(Vector3D left, Vector3D right, float epsilon = float.Epsilon) public static bool ApproximatelyEquals(Vector3D left, Vector3D right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon); => left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon);
/// <summary>
/// Converts the <see cref="Vector3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector3D"/>.</returns>
public override string ToString() => $"{nameof(Vector3D)}({X}, {Y}, {Z})";
/// <summary> /// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector3D"/>. /// Determines whether the specified object is equal to the current <see cref="Vector3D"/>.
/// </summary> /// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector3D"/>.</param> /// <param name="obj">The object to compare with the current <see cref="Vector3D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector3D"/>; otherwise, <see cref="false"/>.</returns> /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector3D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector3D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y) && Z.Equals(objVec.Z); public override bool Equals(object? obj) => obj is Vector3D vector3D && this == vector3D;
/// <summary> /// <summary>
/// Generates a hash code for the <see cref="Vector3D"/>. /// Generates a hash code for the <see cref="Vector3D"/>.
/// </summary> /// </summary>
/// <returns>A hash code for the <see cref="Vector3D"/>.</returns> /// <returns>A hash code for the <see cref="Vector3D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z); public override int GetHashCode() => System.HashCode.Combine(X, Y, Z);
/// <summary>
/// Converts the <see cref="Vector3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector3D"/>.</returns>
public override string ToString() => $"{nameof(Vector3D)}({X}, {Y}, {Z})";
} }
/// <summary> /// <summary>

View File

@@ -4,7 +4,7 @@ namespace Syntriax.Engine.Core;
public class UpdateManager : Behaviour public class UpdateManager : Behaviour
{ {
// We use Ascending order because draw calls are running from last to first // We use Ascending order because we are using reverse for loop to call them
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority));
private readonly ActiveBehaviourCollectorSorted<IFirstFrameUpdate> firstFrameUpdates = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorSorted<IFirstFrameUpdate> firstFrameUpdates = new() { SortBy = SortByAscendingPriority() };

View File

@@ -4,7 +4,7 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Integration.MonoGame; namespace Syntriax.Engine.Integration.MonoGame;
public class DrawableShapeBehaviour : Behaviour2D, IDrawableTriangle, IPreDraw public class DrawableShape : Behaviour2D, IDrawableTriangle, IPreDraw
{ {
private readonly Shape2D shape = new([]); private readonly Shape2D shape = new([]);
private readonly List<Triangle> worldTriangles = []; private readonly List<Triangle> worldTriangles = [];
@@ -23,7 +23,7 @@ public class DrawableShapeBehaviour : Behaviour2D, IDrawableTriangle, IPreDraw
protected void UpdateWorldShape() => shape.Transform(Transform, worldShape); protected void UpdateWorldShape() => shape.Transform(Transform, worldShape);
public DrawableShapeBehaviour() => shape = Shape2D.Triangle; public DrawableShape() => shape = Shape2D.Triangle;
public DrawableShapeBehaviour(Shape2D shape) => this.shape = shape; public DrawableShape(Shape2D shape) => this.shape = shape;
public DrawableShapeBehaviour(Shape2D shape, ColorRGB color) { this.shape = shape; this.color = color; } public DrawableShape(Shape2D shape, ColorRGB color) { this.shape = shape; this.color = color; }
} }

View File

@@ -8,7 +8,7 @@ using Syntriax.Engine.Systems.Input;
namespace Syntriax.Engine.Integration.MonoGame; namespace Syntriax.Engine.Integration.MonoGame;
public class KeyboardInputsBehaviour : Behaviour, IButtonInputs<Keys>, IUpdate public class KeyboardInputs : Behaviour, IButtonInputs<Keys>, IUpdate
{ {
public Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments> OnAnyButtonPressed { get; } = new(); public Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments> OnAnyButtonPressed { get; } = new();
public Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments> OnAnyButtonReleased { get; } = new(); public Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments> OnAnyButtonReleased { get; } = new();

View File

@@ -5,7 +5,7 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Integration.MonoGame; namespace Syntriax.Engine.Integration.MonoGame;
public class MonoGameCamera2DBehaviour : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPreDraw public class MonoGameCamera2D : BehaviourBase, ICamera2D, IFirstFrameUpdate, IPreDraw
{ {
public event MatrixTransformChangedArguments? OnMatrixTransformChanged = null; public event MatrixTransformChangedArguments? OnMatrixTransformChanged = null;
public event ViewportChangedArguments? OnViewportChanged = null; public event ViewportChangedArguments? OnViewportChanged = null;
@@ -103,7 +103,7 @@ public class MonoGameCamera2DBehaviour : BehaviourBase, ICamera2D, IFirstFrameUp
protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>(); protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
protected sealed override void FinalizeInternal() => Transform = null!; protected sealed override void FinalizeInternal() => Transform = null!;
public delegate void MatrixTransformChangedArguments(MonoGameCamera2DBehaviour sender); public delegate void MatrixTransformChangedArguments(MonoGameCamera2D sender);
public delegate void ViewportChangedArguments(MonoGameCamera2DBehaviour sender); public delegate void ViewportChangedArguments(MonoGameCamera2D sender);
public delegate void ZoomChangedArguments(MonoGameCamera2DBehaviour sender); public delegate void ZoomChangedArguments(MonoGameCamera2D sender);
} }

View File

@@ -9,14 +9,14 @@ public class SpriteBatcher : BehaviourBase, IFirstFrameUpdate, IDraw
private static Comparer<IBehaviour> SortByPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority)); private static Comparer<IBehaviour> SortByPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority));
private ISpriteBatch spriteBatch = null!; private ISpriteBatch spriteBatch = null!;
private MonoGameCamera2DBehaviour camera2D = null!; private MonoGameCamera2D camera2D = null!;
private readonly ActiveBehaviourCollectorSorted<IDrawableSprite> drawableSprites = new() { SortBy = SortByPriority() }; private readonly ActiveBehaviourCollectorSorted<IDrawableSprite> drawableSprites = new() { SortBy = SortByPriority() };
public void FirstActiveFrame() public void FirstActiveFrame()
{ {
MonoGameWindowContainer windowContainer = Universe.FindRequiredBehaviour<MonoGameWindowContainer>(); MonoGameWindowContainer windowContainer = Universe.FindRequiredBehaviour<MonoGameWindowContainer>();
camera2D = Universe.FindRequiredBehaviour<MonoGameCamera2DBehaviour>(); camera2D = Universe.FindRequiredBehaviour<MonoGameCamera2D>();
spriteBatch = new SpriteBatchWrapper(windowContainer.Window.GraphicsDevice); spriteBatch = new SpriteBatchWrapper(windowContainer.Window.GraphicsDevice);
drawableSprites.Unassign(); drawableSprites.Unassign();

View File

@@ -11,13 +11,13 @@ public class TriangleBatcher : BehaviourBase, ITriangleBatch, IFirstFrameUpdate,
private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority)); private static Comparer<IBehaviour> SortByAscendingPriority() => Comparer<IBehaviour>.Create((x, y) => x.Priority.CompareTo(y.Priority));
private TriangleBatch triangleBatch = null!; private TriangleBatch triangleBatch = null!;
private MonoGameCamera2DBehaviour camera2D = null!; private MonoGameCamera2D camera2D = null!;
private readonly ActiveBehaviourCollectorSorted<IDrawableTriangle> drawableShapes = new() { SortBy = SortByAscendingPriority() }; private readonly ActiveBehaviourCollectorSorted<IDrawableTriangle> drawableShapes = new() { SortBy = SortByAscendingPriority() };
public void FirstActiveFrame() public void FirstActiveFrame()
{ {
MonoGameWindowContainer windowContainer = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>(); MonoGameWindowContainer windowContainer = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>();
camera2D = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameCamera2DBehaviour>(); camera2D = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameCamera2D>();
triangleBatch = new(windowContainer.Window.GraphicsDevice); triangleBatch = new(windowContainer.Window.GraphicsDevice);
drawableShapes.Unassign(); drawableShapes.Unassign();

View File

@@ -18,7 +18,10 @@ public static class EngineConverterExtensions
public static Vector2D ToVector2D(this Vector2 vector) => new(vector.X, vector.Y); public static Vector2D ToVector2D(this Vector2 vector) => new(vector.X, vector.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color ToColor(this ColorRGBA rgba) => new(rgba.R, rgba.G, rgba.B, rgba.A); public static Color ToColor(this ColorRGBA color) => new(color.R, color.G, color.B, color.A);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ColorRGBA ToColorRGBA(this Color color) => new(color.R, color.G, color.B, color.A);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2D ToVector2D(this Point point) => new(point.X, point.Y); public static Vector2D ToVector2D(this Point point) => new(point.X, point.Y);

View File

@@ -2,7 +2,7 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D; namespace Syntriax.Engine.Physics2D;
public abstract class Collider2DBehaviourBase : Behaviour2D, ICollider2D public abstract class Collider2DBase : Behaviour2D, ICollider2D
{ {
public Event<ICollider2D, CollisionDetectionInformation> OnCollisionDetected { get; } = new(); public Event<ICollider2D, CollisionDetectionInformation> OnCollisionDetected { get; } = new();
public Event<ICollider2D, CollisionDetectionInformation> OnCollisionResolved { get; } = new(); public Event<ICollider2D, CollisionDetectionInformation> OnCollisionResolved { get; } = new();
@@ -18,7 +18,7 @@ public abstract class Collider2DBehaviourBase : Behaviour2D, ICollider2D
protected bool NeedsRecalculation { get; set; } = true; protected bool NeedsRecalculation { get; set; } = true;
protected IRigidBody2D? _rigidBody2D = null; protected IRigidBody2D? _rigidBody2D = null;
protected Collider2DBehaviourBase() protected Collider2DBase()
{ {
delegateOnBehaviourAddedToController = OnBehaviourAddedToController; delegateOnBehaviourAddedToController = OnBehaviourAddedToController;
delegateOnBehaviourRemovedFromController = OnBehaviourRemovedFromController; delegateOnBehaviourRemovedFromController = OnBehaviourRemovedFromController;

View File

@@ -2,7 +2,7 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D; namespace Syntriax.Engine.Physics2D;
public class Collider2DCircleBehaviour : Collider2DBehaviourBase, ICircleCollider2D public class Collider2DCircle : Collider2DBase, ICircleCollider2D
{ {
private Circle _circleLocal = Circle.UnitCircle; private Circle _circleLocal = Circle.UnitCircle;
@@ -19,6 +19,6 @@ public class Collider2DCircleBehaviour : Collider2DBehaviourBase, ICircleCollide
public override void CalculateCollider() => CircleWorld = Transform.Transform(_circleLocal); public override void CalculateCollider() => CircleWorld = Transform.Transform(_circleLocal);
public Collider2DCircleBehaviour() { } public Collider2DCircle() { }
public Collider2DCircleBehaviour(Circle circle) => CircleLocal = circle; public Collider2DCircle(Circle circle) => CircleLocal = circle;
} }

View File

@@ -2,7 +2,7 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D; namespace Syntriax.Engine.Physics2D;
public class Collider2DShapeBehaviour : Collider2DBehaviourBase, IShapeCollider2D public class Collider2DShape : Collider2DBase, IShapeCollider2D
{ {
public Shape2D ShapeWorld { get => _shapeWorld; protected set => _shapeWorld = value; } public Shape2D ShapeWorld { get => _shapeWorld; protected set => _shapeWorld = value; }
public Shape2D ShapeLocal public Shape2D ShapeLocal
@@ -15,13 +15,13 @@ public class Collider2DShapeBehaviour : Collider2DBehaviourBase, IShapeCollider2
} }
} }
private Shape2D _shapeWorld = Shape2D.Square.CreateCopy(); private Shape2D _shapeWorld = Shape2D.Square;
private Shape2D _shapeLocal = Shape2D.Square; private Shape2D _shapeLocal = Shape2D.Square;
public override void CalculateCollider() => ShapeLocal.Transform(Transform, _shapeWorld); public override void CalculateCollider() => ShapeLocal.Transform(Transform, _shapeWorld);
public Collider2DShapeBehaviour() { } public Collider2DShape() { }
public Collider2DShapeBehaviour(Shape2D shape) public Collider2DShape(Shape2D shape)
{ {
ShapeLocal = shape; ShapeLocal = shape;
} }

View File

@@ -25,11 +25,11 @@ public class PhysicsEngine2D : Behaviour, IPreUpdate, IPhysicsEngine2D
protected BehaviourCollector<IRigidBody2D> rigidBodyCollector = new(); protected BehaviourCollector<IRigidBody2D> rigidBodyCollector = new();
protected BehaviourCollector<ICollider2D> colliderCollector = new(); protected BehaviourCollector<ICollider2D> colliderCollector = new();
private readonly ListPool<ICollider2D> colliderPool = new(() => new(32)); private readonly ListPool<ICollider2D> colliderPool = new();
private readonly ListPool<IPrePhysicsUpdate> prePhysicsUpdatePool = new(() => new(32)); private readonly ListPool<IPrePhysicsUpdate> prePhysicsUpdatePool = new();
private readonly ListPool<IPhysicsUpdate> physicsUpdatePool = new(() => new(32)); private readonly ListPool<IPhysicsUpdate> physicsUpdatePool = new();
private readonly ListPool<IPhysicsIteration> physicsIterationPool = new(() => new(32)); private readonly ListPool<IPhysicsIteration> physicsIterationPool = new();
private readonly ListPool<IPostPhysicsUpdate> postPhysicsUpdatePool = new(() => new(32)); private readonly ListPool<IPostPhysicsUpdate> postPhysicsUpdatePool = new();
public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; } public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; }
public float IterationPeriod { get => _iterationPeriod; set => _iterationPeriod = value.Max(0.0001f); } public float IterationPeriod { get => _iterationPeriod; set => _iterationPeriod = value.Max(0.0001f); }

View File

@@ -21,6 +21,12 @@ public class PhysicsEngine2DStandalone : IPhysicsEngine2D
private readonly ICollisionResolver2D collisionResolver = null!; private readonly ICollisionResolver2D collisionResolver = null!;
private readonly IRaycastResolver2D raycastResolver = null!; private readonly IRaycastResolver2D raycastResolver = null!;
private readonly ListPool<ICollider2D> colliderPool = new();
private readonly ListPool<IPrePhysicsUpdate> prePhysicsUpdatePool = new();
private readonly ListPool<IPhysicsUpdate> physicsUpdatePool = new();
private readonly ListPool<IPhysicsIteration> physicsIterationPool = new();
private readonly ListPool<IPostPhysicsUpdate> postPhysicsUpdatePool = new();
public int IterationPerStep { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; } public int IterationPerStep { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }
public void AddRigidBody(IRigidBody2D rigidBody) public void AddRigidBody(IRigidBody2D rigidBody)
@@ -112,11 +118,11 @@ public class PhysicsEngine2DStandalone : IPhysicsEngine2D
{ {
float intervalDeltaTime = deltaTime / IterationPerStep; float intervalDeltaTime = deltaTime / IterationPerStep;
List<ICollider2D> childColliders = []; List<ICollider2D> childColliders = colliderPool.Get();
List<IPrePhysicsUpdate> physicsPreUpdates = []; List<IPrePhysicsUpdate> physicsPreUpdates = prePhysicsUpdatePool.Get();
List<IPhysicsUpdate> physicsUpdates = []; List<IPhysicsUpdate> physicsUpdates = physicsUpdatePool.Get();
List<IPhysicsIteration> physicsIterations = []; List<IPhysicsIteration> physicsIterations = physicsIterationPool.Get();
List<IPostPhysicsUpdate> physicsPostUpdates = []; List<IPostPhysicsUpdate> physicsPostUpdates = postPhysicsUpdatePool.Get();
rigidBody.BehaviourController.GetBehavioursInChildren(childColliders); rigidBody.BehaviourController.GetBehavioursInChildren(childColliders);
rigidBody.BehaviourController.GetBehavioursInChildren(physicsPreUpdates); rigidBody.BehaviourController.GetBehavioursInChildren(physicsPreUpdates);
@@ -160,6 +166,12 @@ public class PhysicsEngine2DStandalone : IPhysicsEngine2D
for (int i = physicsPostUpdates.Count - 1; i >= 0; i--) for (int i = physicsPostUpdates.Count - 1; i >= 0; i--)
physicsPostUpdates[i].PostPhysicsUpdate(deltaTime); physicsPostUpdates[i].PostPhysicsUpdate(deltaTime);
colliderPool.Return(childColliders);
prePhysicsUpdatePool.Return(physicsPreUpdates);
physicsUpdatePool.Return(physicsUpdates);
physicsIterationPool.Return(physicsIterations);
postPhysicsUpdatePool.Return(physicsPostUpdates);
} }
private void ResolveColliders(ICollider2D colliderX, ICollider2D colliderY) private void ResolveColliders(ICollider2D colliderX, ICollider2D colliderY)

View File

@@ -6,7 +6,7 @@ namespace Syntriax.Engine.Physics2D;
public class RaycastResolver2D : IRaycastResolver2D public class RaycastResolver2D : IRaycastResolver2D
{ {
private readonly ListPool<Line2D> lineCacheQueue = new(() => new(4)); private readonly ListPool<Line2D> lineCacheQueue = new(initialListCapacity: 4);
RaycastResult? IRaycastResolver2D.RaycastAgainst<T>(T shape, Ray2D ray, float length) RaycastResult? IRaycastResolver2D.RaycastAgainst<T>(T shape, Ray2D ray, float length)
{ {

View File

@@ -2,7 +2,7 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Systems.Time; namespace Syntriax.Engine.Systems.Time;
public class StopwatchBehaviour : Behaviour, IUpdate, IStopwatch public class Stopwatch : Behaviour, IUpdate, IStopwatch
{ {
public Event<IReadOnlyStopwatch> OnStarted { get; } = new(); public Event<IReadOnlyStopwatch> OnStarted { get; } = new();
public Event<IReadOnlyStopwatch, IReadOnlyStopwatch.StopwatchDeltaArguments> OnDelta { get; } = new(); public Event<IReadOnlyStopwatch, IReadOnlyStopwatch.StopwatchDeltaArguments> OnDelta { get; } = new();

View File

@@ -2,7 +2,7 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Systems.Time; namespace Syntriax.Engine.Systems.Time;
public class TickerBehaviour : StopwatchBehaviour, ITicker public class Ticker : Stopwatch, ITicker
{ {
public Event<ITicker> OnTick { get; } = new(); public Event<ITicker> OnTick { get; } = new();

View File

@@ -2,7 +2,7 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Systems.Time; namespace Syntriax.Engine.Systems.Time;
public class TimerBehaviour : Behaviour, IUpdate, ITimer public class Timer : Behaviour, IUpdate, ITimer
{ {
public Event<IReadOnlyTimer> OnStarted { get; } = new(); public Event<IReadOnlyTimer> OnStarted { get; } = new();
public Event<IReadOnlyTimer, IReadOnlyTimer.TimerDeltaArguments> OnDelta { get; } = new(); public Event<IReadOnlyTimer, IReadOnlyTimer.TimerDeltaArguments> OnDelta { get; } = new();

View File

@@ -9,8 +9,8 @@ internal static class EaseConstants
internal const float c1 = 1.70158f; internal const float c1 = 1.70158f;
internal const float c2 = c1 * 1.525f; internal const float c2 = c1 * 1.525f;
internal const float c3 = c1 + 1f; internal const float c3 = c1 + 1f;
internal const float c4 = 2f * Math.PI / 3f; internal const float c4 = 2f * Math.Pi / 3f;
internal const float c5 = 2f * Math.PI / 4.5f; internal const float c5 = 2f * Math.Pi / 4.5f;
} }
public abstract class EasingBase<T> where T : IEasing, new() { public static readonly T Instance = new(); } public abstract class EasingBase<T> where T : IEasing, new() { public static readonly T Instance = new(); }
@@ -33,9 +33,9 @@ public class EaseInQuint : EasingBase<EaseInQuint>, IEasing { public float Evalu
public class EaseOutQuint : EasingBase<EaseOutQuint>, IEasing { public float Evaluate(float x) => 1f - Math.Pow(1f - x, 5f); } public class EaseOutQuint : EasingBase<EaseOutQuint>, IEasing { public float Evaluate(float x) => 1f - Math.Pow(1f - x, 5f); }
public class EaseInOutQuint : EasingBase<EaseInOutQuint>, IEasing { public float Evaluate(float x) => x < .5f ? 16f * x * x * x * x * x : 1f - Math.Pow(-2f * x + 2f, 5f) * .5f; } public class EaseInOutQuint : EasingBase<EaseInOutQuint>, IEasing { public float Evaluate(float x) => x < .5f ? 16f * x * x * x * x * x : 1f - Math.Pow(-2f * x + 2f, 5f) * .5f; }
public class EaseInSine : EasingBase<EaseInSine>, IEasing { public float Evaluate(float x) => 1f - Math.Cos(x * Math.PI * .5f); } public class EaseInSine : EasingBase<EaseInSine>, IEasing { public float Evaluate(float x) => 1f - Math.Cos(x * Math.Pi * .5f); }
public class EaseOutSine : EasingBase<EaseOutSine>, IEasing { public float Evaluate(float x) => Math.Sin(x * Math.PI * .5f); } public class EaseOutSine : EasingBase<EaseOutSine>, IEasing { public float Evaluate(float x) => Math.Sin(x * Math.Pi * .5f); }
public class EaseInOutSine : EasingBase<EaseInOutSine>, IEasing { public float Evaluate(float x) => -(Math.Cos(Math.PI * x) - 1f) * .5f; } public class EaseInOutSine : EasingBase<EaseInOutSine>, IEasing { public float Evaluate(float x) => -(Math.Cos(Math.Pi * x) - 1f) * .5f; }
public class EaseInExpo : EasingBase<EaseInExpo>, IEasing { public float Evaluate(float x) => x == 0f ? 0f : Math.Pow(2f, 10f * x - 10f); } public class EaseInExpo : EasingBase<EaseInExpo>, IEasing { public float Evaluate(float x) => x == 0f ? 0f : Math.Pow(2f, 10f * x - 10f); }
public class EaseOutExpo : EasingBase<EaseOutExpo>, IEasing { public float Evaluate(float x) => x == 1f ? 1f : 1f - Math.Pow(2f, -10f * x); } public class EaseOutExpo : EasingBase<EaseOutExpo>, IEasing { public float Evaluate(float x) => x == 1f ? 1f : 1f - Math.Pow(2f, -10f * x); }