16 Commits

12 changed files with 144 additions and 82 deletions

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
@@ -55,17 +56,17 @@ public class BehaviourController : IBehaviourController
public IList<T> GetBehaviours<T>() public IList<T> GetBehaviours<T>()
{ {
IList<T> behaviours = new List<T>(); List<T>? behaviours = null;
foreach (var behaviourItem in this.behaviours) foreach (var behaviourItem in this.behaviours)
{ {
if (behaviourItem is not T behaviour) if (behaviourItem is not T behaviour)
continue; continue;
behaviours ??= new List<T>(); behaviours ??= [];
behaviours.Add(behaviour); behaviours.Add(behaviour);
} }
return behaviours; return behaviours ?? Enumerable.Empty<T>().ToList();
} }
public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour

View File

@@ -0,0 +1,9 @@
namespace Syntriax.Engine.Core.Abstract;
public static class TransformExtensions
{
public static Vector2D TransformVector2D(this ITransform transform, Vector2D vector)
=> vector.Scale(transform.Scale)
.Rotate(transform.Rotation * Math.DegreeToRadian)
.Add(transform.Position);
}

View File

@@ -0,0 +1,22 @@
namespace Syntriax.Engine.Core;
public static class FloatExtensions
{
public static bool ApproximatelyEquals(this float a, float b)
=> ApproximatelyEquals(a, b, float.Epsilon);
public static bool ApproximatelyEquals(this float a, float b, float epsilon)
{
if (a == b)
return true;
const float floatNormal = (1 << 23) * float.Epsilon;
float absA = Math.Abs(a);
float absB = Math.Abs(b);
float diff = Math.Abs(a - b);
if (a == 0.0f || b == 0.0f || diff < floatNormal)
return diff < (epsilon * floatNormal);
return diff / Math.Min(absA + absB, float.MaxValue) < epsilon;
}
}

View File

@@ -13,6 +13,7 @@ public static class Vector2DExtensions
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); public static Vector2D Subdivide(this Vector2D vector, float value) => Vector2D.Subdivide(vector, value);
public static Vector2D Abs(this Vector2D vector) => Vector2D.Abs(vector);
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);
public static Vector2D Normalize(this Vector2D vector) => Vector2D.Normalize(vector); public static Vector2D Normalize(this Vector2D vector) => Vector2D.Normalize(vector);
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);
@@ -27,4 +28,7 @@ public static class Vector2DExtensions
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);
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);
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);
public static bool ApproximatelyEquals(this Vector2D left, Vector2D right, float epsilon = float.Epsilon) => Vector2D.ApproximatelyEquals(left, right, epsilon);
} }

32
Engine.Core/Math.cs Normal file
View File

@@ -0,0 +1,32 @@
using System;
namespace Syntriax.Engine.Core;
public static class Math
{
public const float RadianToDegree = 180f / PI;
public const float DegreeToRadian = PI / 180f;
public const float E = 2.718281828459045f;
public const float PI = 3.1415926535897932f;
public const float Tau = 2f * PI;
public static float Abs(float x) => MathF.Abs(x);
public static float Acos(float x) => MathF.Acos(x);
public static float Asin(float x) => MathF.Asin(x);
public static float Atan2(float y, float x) => MathF.Atan2(y, x);
public static float Atanh(float x) => MathF.Atanh(x);
public static float Clamp(float x, float min, float max) => (x < min) ? min : (x > max) ? max : x;
public static float Ceiling(float x) => MathF.Ceiling(x);
public static float CopySign(float x, float y) => MathF.CopySign(x, y);
public static float Floor(float x) => MathF.Floor(x);
public static float IEEERemainder(float x, float y) => MathF.IEEERemainder(x, y);
public static float Log(float x, float y) => MathF.Log(x, y);
public static float Max(float x, float y) => MathF.Max(x, y);
public static float MaxMagnitude(float x, float y) => MathF.MaxMagnitude(x, y);
public static float Min(float x, float y) => MathF.Min(x, y);
public static float MinMagnitude(float x, float y) => MathF.MinMagnitude(x, y);
public static float Pow(float x, float y) => MathF.Pow(x, y);
public static float Round(float x, int digits, MidpointRounding mode) => MathF.Round(x, digits, mode);
public static float Truncate(float x) => MathF.Truncate(x);
}

View File

@@ -34,6 +34,7 @@ public record Vector2D(float X, float Y)
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; public static Vector2D Subdivide(Vector2D vector, float value) => vector / value;
public static Vector2D Abs(Vector2D vector) => new(Math.Abs(vector.X), Math.Abs(vector.Y));
public static Vector2D Normalize(Vector2D vector) => vector / Length(vector); public static Vector2D Normalize(Vector2D vector) => vector / Length(vector);
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;
public static Vector2D FromTo(Vector2D from, Vector2D to) => to - from; public static Vector2D FromTo(Vector2D from, Vector2D to) => to - from;
@@ -49,5 +50,25 @@ public record Vector2D(float X, float Y)
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)));
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>
/// Finds the Orientation of 3 <see cref="Vector2D"/>s
/// </summary>
/// <returns>0 -> Collinear, 1 -> Clockwise, 2 -> Counterclockwise</returns>
public static int Orientation(Vector2D left, Vector2D middle, Vector2D right)
{
Vector2D leftToMiddle = left.FromTo(middle);
Vector2D middleToRight = middle.FromTo(right);
float value = leftToMiddle.Y * middleToRight.X -
leftToMiddle.X * middleToRight.Y;
if (value > 0) return 1;
if (value < 0) return 2;
return 0;
}
public static bool ApproximatelyEquals(Vector2D left, Vector2D right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon);
public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})"; public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})";
} }

View File

@@ -8,9 +8,6 @@ namespace Engine.Physics2D;
public static partial class Physics2D public static partial class Physics2D
{ {
public const float RadianToDegree = 57.29577866666166f;
public const float DegreeToRadian = 0.01745329277777778f;
public static bool Overlaps(this Circle left, Circle right) public static bool Overlaps(this Circle left, Circle right)
{ {
float distanceSquared = left.Position.FromTo(right.Position).LengthSquared(); float distanceSquared = left.Position.FromTo(right.Position).LengthSquared();

View File

@@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
using Syntriax.Engine.Core;
using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D;
public static class PhysicsMath
{
// Given three collinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
public static bool OnSegment(Vector2D p, Vector2D q, Vector2D r)
{
if (q.X <= MathF.Max(p.X, r.X) && q.X >= MathF.Min(p.X, r.X) &&
q.Y <= MathF.Max(p.Y, r.Y) && q.Y >= MathF.Min(p.Y, r.Y))
return true;
return false;
}
// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are collinear
// 1 --> Clockwise
// 2 --> Counterclockwise
public static int Orientation(Vector2D p, Vector2D q, Vector2D r)
{
// See https://www.geeksforgeeks.org/orientation-3-ordered-points/
// for details of below formula.
float val = (q.Y - p.Y) * (r.X - q.X) -
(q.X - p.X) * (r.Y - q.Y);
if (val == 0) return 0; // collinear
return (val > 0) ? 1 : 2; // clock or counterclock wise
}
public static float IntersectionParameterT(Vector2D p0, Vector2D p1, Vector2D q0, Vector2D q1)
=> ((q0.X - p0.X) * (p1.Y - p0.Y) - (q0.Y - p0.Y) * (p1.X - p0.X)) /
((q1.Y - q0.Y) * (p1.X - p0.X) - (q1.X - q0.X) * (p1.Y - p0.Y));
public static bool ApproximatelyEquals(this float a, float b)
=> ApproximatelyEquals(a, b, float.Epsilon);
public static bool ApproximatelyEquals(this Vector2D a, Vector2D b)
=> ApproximatelyEquals(a, b, float.Epsilon);
public static bool ApproximatelyEquals(this Vector2D a, Vector2D b, float epsilon)
=> ApproximatelyEquals(a.X, b.X, epsilon) && ApproximatelyEquals(a.Y, b.Y, epsilon);
public static bool ApproximatelyEquals(this float a, float b, float epsilon)
{
if (a == b)
return true;
const float floatNormal = (1 << 23) * float.Epsilon;
float absA = MathF.Abs(a);
float absB = MathF.Abs(b);
float diff = MathF.Abs(a - b);
if (a == 0.0f || b == 0.0f || diff < floatNormal)
return diff < (epsilon * floatNormal);
return diff / MathF.Min(absA + absB, float.MaxValue) < epsilon;
}
}

View File

@@ -1,14 +1,42 @@
using System.Collections.Generic;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
public record AABB(Vector2D LowerBoundary, Vector2D UpperBoundary) public record AABB(Vector2D LowerBoundary, Vector2D UpperBoundary)
{ {
public Vector2D Center => (LowerBoundary + UpperBoundary) * .5f;
public Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs();
public Vector2D SizeHalf => Size * .5f;
public static AABB FromVectors(IEnumerable<Vector2D> vectors)
{
int counter = 0;
Vector2D lowerBoundary = new(float.MaxValue, float.MaxValue);
Vector2D upperBoundary = new(float.MinValue, float.MinValue);
foreach (Vector2D vector in vectors)
{
lowerBoundary = Vector2D.Min(lowerBoundary, vector);
upperBoundary = Vector2D.Max(upperBoundary, vector);
counter++;
}
if (counter < 2)
throw new System.ArgumentException($"Parameter {nameof(vectors)} must have at least 2 items.");
return new(lowerBoundary, upperBoundary);
}
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);
} }
public static class AABBExtensions public static class AABBExtensions
{ {
public static AABB ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors);
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

@@ -107,18 +107,27 @@ public record Line(Vector2D From, Vector2D To)
public static bool Intersects(Line left, Line right) public static bool Intersects(Line left, Line right)
{ {
int o1 = PhysicsMath.Orientation(left.From, left.To, right.From); int o1 = Vector2D.Orientation(left.From, left.To, right.From);
int o2 = PhysicsMath.Orientation(left.From, left.To, right.To); int o2 = Vector2D.Orientation(left.From, left.To, right.To);
int o3 = PhysicsMath.Orientation(right.From, right.To, left.From); int o3 = Vector2D.Orientation(right.From, right.To, left.From);
int o4 = PhysicsMath.Orientation(right.From, right.To, left.To); int o4 = Vector2D.Orientation(right.From, right.To, left.To);
if (o1 != o2 && o3 != o4) if (o1 != o2 && o3 != o4)
return true; return true;
if (o1 == 0 && PhysicsMath.OnSegment(left.From, right.From, left.To)) return true; if (o1 == 0 && OnSegment(left, right.From)) return true;
if (o2 == 0 && PhysicsMath.OnSegment(left.From, right.To, left.To)) return true; if (o2 == 0 && OnSegment(left, right.To)) return true;
if (o3 == 0 && PhysicsMath.OnSegment(right.From, left.From, right.To)) return true; if (o3 == 0 && OnSegment(right, left.From)) return true;
if (o4 == 0 && PhysicsMath.OnSegment(right.From, left.To, right.To)) 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; return false;
} }

View File

@@ -1,3 +1,5 @@
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
public record LineEquation(float Slope, float OffsetY) public record LineEquation(float Slope, float OffsetY)

View File

@@ -1,11 +1,12 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Syntriax.Engine.Core; using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
public record Shape(IList<Vector2D> Vertices) public record Shape(IList<Vector2D> Vertices) : IEnumerable<Vector2D>
{ {
public Vector2D this[Index index] => Vertices[index]; public Vector2D this[Index index] => Vertices[index];
@@ -61,6 +62,9 @@ public record Shape(IList<Vector2D> Vertices)
return true; return true;
} }
public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator();
} }
public static class ShapeExtensions public static class ShapeExtensions