docs(physics2d): Primitives

This commit is contained in:
Syntriax 2024-02-01 12:32:58 +03:00
parent 2b19b24a26
commit 0257911018
6 changed files with 478 additions and 79 deletions

View File

@ -4,16 +4,47 @@ using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives; namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents an Axis-Aligned Bounding Box (AABB) in 2D space.
/// </summary>
/// <param name="lowerBoundary">The lower boundary of the <see cref="AABB"/>.</param>
/// <param name="upperBoundary">The upper boundary of the <see cref="AABB"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="AABB"/> struct with the specified lower and upper boundaries.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")] [System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")]
public readonly struct AABB(Vector2D LowerBoundary, Vector2D UpperBoundary) public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
{ {
public readonly Vector2D LowerBoundary { get; init; } = LowerBoundary; /// <summary>
public readonly Vector2D UpperBoundary { get; init; } = UpperBoundary; /// The lower boundary of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D LowerBoundary = lowerBoundary;
/// <summary>
/// The upper boundary of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D UpperBoundary = upperBoundary;
/// <summary>
/// Gets the center point of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D Center => (LowerBoundary + UpperBoundary) * .5f; public readonly Vector2D Center => (LowerBoundary + UpperBoundary) * .5f;
/// <summary>
/// Gets the size of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs(); public readonly Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs();
/// <summary>
/// Gets half the size of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D SizeHalf => Size * .5f; public readonly Vector2D SizeHalf => Size * .5f;
/// <summary>
/// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
/// <returns>An <see cref="AABB"/> that bounds all the <see cref="Vector2D"/>s.</returns>
public static AABB FromVectors(IEnumerable<Vector2D> vectors) public static AABB FromVectors(IEnumerable<Vector2D> vectors)
{ {
int counter = 0; int counter = 0;
@ -34,13 +65,33 @@ public readonly struct AABB(Vector2D LowerBoundary, Vector2D UpperBoundary)
return new(lowerBoundary, upperBoundary); return new(lowerBoundary, upperBoundary);
} }
/// <summary>
/// Checks if two <see cref="AABB"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="AABB"/>.</param>
/// <param name="right">The second <see cref="AABB"/>.</param>
/// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(AABB left, AABB right) public static bool ApproximatelyEquals(AABB left, AABB right)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary); => left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary);
} }
/// <summary>
/// Provides extension methods for the <see cref="AABB"/> struct.
/// </summary>
public static class AABBExtensions public static class AABBExtensions
{ {
/// <summary>
/// Converts a collection of <see cref="Vector2D"/>s to an <see cref="AABB"/>.
/// </summary>
/// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
/// <returns>An <see cref="AABB"/> that bounds all the <see cref="Vector2D"/>s.</returns>
public static AABB ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors); public static AABB ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors);
/// <summary>
/// Checks if two <see cref="AABB"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="AABB"/>.</param>
/// <param name="right">The second <see cref="AABB"/>.</param>
/// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(this AABB left, AABB right) => AABB.ApproximatelyEquals(left, right); public static bool ApproximatelyEquals(this AABB left, AABB right) => AABB.ApproximatelyEquals(left, right);
} }

View File

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

View File

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

View File

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

View File

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

View File

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