refactor: moved 2D primitives from Physics2D to Core
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using Syntriax.Engine.Physics2D.Primitives;
|
||||
using Syntriax.Engine.Core;
|
||||
|
||||
namespace Syntriax.Engine.Physics2D.Abstract;
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
using Syntriax.Engine.Physics2D.Primitives;
|
||||
using Syntriax.Engine.Core;
|
||||
|
||||
namespace Syntriax.Engine.Physics2D.Abstract;
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
using Syntriax.Engine.Core;
|
||||
using Syntriax.Engine.Physics2D.Abstract;
|
||||
using Syntriax.Engine.Physics2D.Primitives;
|
||||
|
||||
namespace Syntriax.Engine.Physics2D;
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
using Syntriax.Engine.Core;
|
||||
using Syntriax.Engine.Physics2D.Abstract;
|
||||
using Syntriax.Engine.Physics2D.Primitives;
|
||||
|
||||
namespace Syntriax.Engine.Physics2D;
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
using Syntriax.Engine.Core;
|
||||
using Syntriax.Engine.Physics2D.Abstract;
|
||||
using Syntriax.Engine.Physics2D.Primitives;
|
||||
|
||||
namespace Syntriax.Engine.Physics2D;
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using Syntriax.Engine.Core;
|
||||
using Syntriax.Engine.Physics2D.Primitives;
|
||||
|
||||
namespace Syntriax.Engine.Physics2D;
|
||||
|
||||
|
@@ -1,103 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Syntriax.Engine.Core;
|
||||
|
||||
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}")]
|
||||
public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
|
||||
{
|
||||
/// <summary>
|
||||
/// The lower boundary of the <see cref="AABB"/>.
|
||||
/// </summary>
|
||||
public readonly Vector2D LowerBoundary = lowerBoundary;
|
||||
|
||||
/// <summary>
|
||||
/// The upper boundary of the <see cref="AABB"/>.
|
||||
/// </summary>
|
||||
public readonly Vector2D UpperBoundary = upperBoundary;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the center point of the <see cref="AABB"/>.
|
||||
/// </summary>
|
||||
public readonly Vector2D Center => (LowerBoundary + UpperBoundary) * .5f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the <see cref="AABB"/>.
|
||||
/// </summary>
|
||||
public readonly Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs();
|
||||
|
||||
/// <summary>
|
||||
/// Gets half the size of the <see cref="AABB"/>.
|
||||
/// </summary>
|
||||
public readonly Vector2D SizeHalf => Size * .5f;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s.
|
||||
/// </summary>
|
||||
/// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
|
||||
/// <returns>An <see cref="AABB"/> that bounds all the <see cref="Vector2D"/>s.</returns>
|
||||
public static AABB FromVectors(IEnumerable<Vector2D> vectors)
|
||||
{
|
||||
int counter = 0;
|
||||
|
||||
Vector2D lowerBoundary = new(float.MaxValue, float.MaxValue);
|
||||
Vector2D upperBoundary = new(float.MinValue, float.MinValue);
|
||||
|
||||
foreach (Vector2D vector in vectors)
|
||||
{
|
||||
lowerBoundary = Vector2D.Min(lowerBoundary, vector);
|
||||
upperBoundary = Vector2D.Max(upperBoundary, vector);
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (counter < 2)
|
||||
throw new System.ArgumentException($"Parameter {nameof(vectors)} must have at least 2 items.");
|
||||
|
||||
return new(lowerBoundary, upperBoundary);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// 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)
|
||||
=> 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
|
||||
{
|
||||
/// <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);
|
||||
|
||||
/// <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);
|
||||
}
|
@@ -1,115 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
using Syntriax.Engine.Core;
|
||||
using Syntriax.Engine.Core.Abstract;
|
||||
|
||||
namespace Syntriax.Engine.Physics2D.Primitives;
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
{
|
||||
/// <summary>
|
||||
/// The center of the circle.
|
||||
/// </summary>
|
||||
public readonly Vector2D Center = center;
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the <see cref="Circle"/>.
|
||||
/// </summary>
|
||||
public readonly float Radius = radius;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the squared radius of the <see cref="Circle"/>.
|
||||
/// </summary>
|
||||
public readonly float RadiusSquared => Radius * Radius;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the diameter of the <see cref="Circle"/>.
|
||||
/// </summary>
|
||||
public readonly float Diameter => 2f * Radius;
|
||||
|
||||
/// <summary>
|
||||
/// A predefined unit <see cref="Circle"/> with a center at the origin and a radius of 1.
|
||||
/// </summary>
|
||||
public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the center of the <see cref="Circle"/>.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Projects the <see cref="Circle"/> onto the specified <see cref="Vector2D"/>.
|
||||
/// </summary>
|
||||
public static Projection1D Project(Circle circle, Vector2D projectionVector)
|
||||
{
|
||||
float projectedCenter = circle.Center.Dot(projectionVector);
|
||||
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)
|
||||
=> new(transform.TransformVector2D(circle.Center), circle.Radius * (transform.Scale.Magnitude / Vector2D.One.Magnitude));
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Circle"/>s are approximately equal.
|
||||
/// </summary>
|
||||
public static bool ApproximatelyEquals(Circle left, Circle right)
|
||||
=> 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the center of the <see cref="Circle"/>.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <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);
|
||||
|
||||
/// <summary>
|
||||
/// Projects the <see cref="Circle"/> onto the specified <see cref="Vector2D"/>.
|
||||
/// </summary>
|
||||
public static Projection1D 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);
|
||||
|
||||
/// <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);
|
||||
}
|
@@ -1,232 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using Syntriax.Engine.Core;
|
||||
|
||||
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="Line2D"/> segment.</param>
|
||||
/// <param name="to">The ending point of the <see cref="Line2D"/> segment.</param>
|
||||
[System.Diagnostics.DebuggerDisplay("From: {From.ToString(),nq}, To: {To.ToString(),nq}, Direction: {Direction.ToString(),nq}, Length: {Length}")]
|
||||
public readonly struct Line2D(Vector2D from, Vector2D to)
|
||||
{
|
||||
/// <summary>
|
||||
/// The starting point of the <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public readonly Vector2D From = from;
|
||||
|
||||
/// <summary>
|
||||
/// The ending point of the <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public readonly Vector2D To = to;
|
||||
|
||||
/// <summary>
|
||||
/// The reversed <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public readonly Line2D Reversed => new(To, From);
|
||||
|
||||
/// <summary>
|
||||
/// The normalized direction <see cref="Vector2D"/> of the <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public readonly Vector2D Direction => From.FromTo(To).Normalize();
|
||||
|
||||
/// <summary>
|
||||
/// The length of the <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public readonly float Length => From.FromTo(To).Length();
|
||||
|
||||
/// <summary>
|
||||
/// The squared length of the <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public readonly float LengthSquared => From.FromTo(To).LengthSquared();
|
||||
|
||||
/// <summary>
|
||||
/// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public static Line2DEquation GetLineEquation(Line2D line)
|
||||
{
|
||||
Vector2D slopeVector = line.From.FromTo(line.To);
|
||||
float slope = slopeVector.Y / slopeVector.X;
|
||||
|
||||
float yOffset = line.From.Y - (slope * line.From.X);
|
||||
|
||||
return new Line2DEquation(slope, yOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line2D"/>.
|
||||
/// </summary>
|
||||
public static bool Intersects(Line2D line, Vector2D point)
|
||||
=> Line2DEquation.Resolve(GetLineEquation(line), point.X).ApproximatelyEquals(point.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the parameter 't' representing the point's position on the <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public static float GetT(Line2D line, Vector2D point)
|
||||
{
|
||||
float fromX = MathF.Abs(line.From.X);
|
||||
float toX = MathF.Abs(line.To.X);
|
||||
float pointX = MathF.Abs(point.X);
|
||||
|
||||
float min = MathF.Min(fromX, toX);
|
||||
float max = MathF.Max(fromX, toX) - min;
|
||||
|
||||
pointX -= min;
|
||||
|
||||
float t = pointX / max;
|
||||
|
||||
// FIXME
|
||||
// I don't even know, apparently whatever I wrote up there doesn't take into account of the direction of the line
|
||||
// Which... I can see how, but I am also not sure how I can make it take into account. Or actually I'm for some reason
|
||||
// too unmotivated to find a solution. Future me, find a better way if possible, please.
|
||||
if (!Lerp(line, t).ApproximatelyEquals(point))
|
||||
return 1f - t;
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="Line2D"/> segment intersects with another <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public static bool Intersects(Line2D left, Line2D 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the point lies within the <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public static bool OnSegment(Line2D 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="Line2D"/> segments intersect.
|
||||
/// </summary>
|
||||
public static bool Intersects(Line2D left, Line2D 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="Line2D"/> segments.
|
||||
/// </summary>
|
||||
public static Vector2D IntersectionPoint(Line2D left, Line2D 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="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public static float IntersectionParameterT(Line2D left, Line2D 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 denominator = (left.From.X - left.To.X) * (right.From.Y - right.To.Y) - (left.From.Y - left.To.Y) * (right.From.X - right.To.X);
|
||||
|
||||
// Lines are parallel
|
||||
if (denominator == 0)
|
||||
return float.NaN;
|
||||
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between the two endpoints of the <see cref="Line2D"/> segment using parameter 't'.
|
||||
/// </summary>
|
||||
public static Vector2D Lerp(Line2D line, float t)
|
||||
=> Vector2D.Lerp(line.From, line.To, t);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the closest point on the <see cref="Line2D"/> segment to the specified point.
|
||||
/// </summary>
|
||||
public static Vector2D ClosestPointTo(Line2D line, Vector2D point)
|
||||
{
|
||||
Vector2D edgeVector = line.From.FromTo(line.To);
|
||||
Vector2D pointVector = point - line.From;
|
||||
|
||||
float t = (pointVector.X * edgeVector.X + pointVector.Y * edgeVector.Y) / (edgeVector.X * edgeVector.X + edgeVector.Y * edgeVector.Y);
|
||||
|
||||
t = MathF.Max(0, MathF.Min(1, t));
|
||||
|
||||
float closestX = line.From.X + t * edgeVector.X;
|
||||
float closestY = line.From.Y + t * edgeVector.Y;
|
||||
|
||||
return new Vector2D((float)closestX, (float)closestY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Line2D"/> segments are approximately equal.
|
||||
/// </summary>
|
||||
public static bool ApproximatelyEquals(Line2D left, Line2D right)
|
||||
=> left.From.ApproximatelyEquals(right.From) && left.To.ApproximatelyEquals(right.To);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the Line struct.
|
||||
/// </summary>
|
||||
public static class Line2DExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Linearly interpolates between the two endpoints of the <see cref="Line2D"/> segment using parameter 't'.
|
||||
/// </summary>
|
||||
public static Vector2D Lerp(this Line2D line, float t) => Line2D.Lerp(line, t);
|
||||
|
||||
/// <summary>
|
||||
/// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public static Line2DEquation ToLineEquation(this Line2D line) => Line2D.GetLineEquation(line);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line2D"/>.
|
||||
/// </summary>
|
||||
public static bool Intersects(this Line2D line, Vector2D point) => Line2D.Intersects(line, point);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the parameter 't' representing the point's position on the <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public static float GetT(this Line2D line, Vector2D point) => Line2D.GetT(line, point);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="Line2D"/> segment intersects with another <see cref="Line2D"/> segment.
|
||||
/// </summary>
|
||||
public static bool Intersects(this Line2D left, Line2D right) => Line2D.Intersects(left, right);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether two <see cref="Line2D"/> segments intersect.
|
||||
/// </summary>
|
||||
public static bool Intersects(this Line2D left, Line2D right, [NotNullWhen(returnValue: true)] out Vector2D? point) => Line2D.Intersects(left, right, out point);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two <see cref="Line2D"/>s are approximately equal.
|
||||
/// </summary>
|
||||
public static bool ApproximatelyEquals(this Line2D left, Line2D right) => Line2D.ApproximatelyEquals(left, right);
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
using Syntriax.Engine.Core;
|
||||
|
||||
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="Line2DEquation"/> struct with the specified slope and y-intercept.
|
||||
/// </remarks>
|
||||
[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
|
||||
public readonly struct Line2DEquation(float slope, float offsetY)
|
||||
{
|
||||
/// <summary>
|
||||
/// 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(Line2DEquation 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(Line2DEquation left, Line2DEquation right)
|
||||
=> left.Slope.ApproximatelyEquals(right.Slope) && left.OffsetY.ApproximatelyEquals(right.OffsetY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the LineEquation struct.
|
||||
/// </summary>
|
||||
public static class Line2DEquationExtensions
|
||||
{
|
||||
/// <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 Line2DEquation lineEquation, float x) => Line2DEquation.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 Line2DEquation left, Line2DEquation right) => Line2DEquation.ApproximatelyEquals(left, right);
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
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="Projection1D"/> struct with the specified minimum and maximum values.
|
||||
/// </remarks>
|
||||
[System.Diagnostics.DebuggerDisplay("Min: {Min}, Max: {Max}")]
|
||||
public readonly struct Projection1D(float min, float max)
|
||||
{
|
||||
/// <summary>
|
||||
/// 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(Projection1D left, Projection1D right) => Overlaps(left, right, out float _);
|
||||
|
||||
/// <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(Projection1D left, Projection1D right, out float depth)
|
||||
{
|
||||
// TODO Try to improve this
|
||||
bool rightMinInLeft = right.Min > left.Min && right.Min < left.Max;
|
||||
if (rightMinInLeft)
|
||||
{
|
||||
depth = left.Max - right.Min;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rightMaxInLeft = right.Max < left.Max && right.Max > left.Min;
|
||||
if (rightMaxInLeft)
|
||||
{
|
||||
depth = left.Min - right.Max;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool leftMinInRight = left.Min > right.Min && left.Min < right.Max;
|
||||
if (leftMinInRight)
|
||||
{
|
||||
depth = right.Max - left.Min;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool leftMaxInRight = left.Max < right.Max && left.Max > right.Min;
|
||||
if (leftMaxInRight)
|
||||
{
|
||||
depth = right.Min - left.Max;
|
||||
return true;
|
||||
}
|
||||
|
||||
depth = 0f;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="Projection1D"/> struct.
|
||||
/// </summary>
|
||||
public static class Projection1DExtensions
|
||||
{
|
||||
/// <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 Projection1D left, Projection1D right) => Projection1D.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 Projection1D left, Projection1D right, out float depth) => Projection1D.Overlaps(left, right, out depth);
|
||||
}
|
@@ -1,294 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Syntriax.Engine.Core;
|
||||
using Syntriax.Engine.Core.Abstract;
|
||||
|
||||
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="Shape2D"/> struct with the specified vertices.
|
||||
/// </remarks>
|
||||
[System.Diagnostics.DebuggerDisplay("Vertices Count: {Vertices.Count}")]
|
||||
public readonly struct Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
|
||||
{
|
||||
public static readonly Shape2D Triangle = CreateNgon(3, Vector2D.Up);
|
||||
public static readonly Shape2D Box = CreateNgon(4, Vector2D.One);
|
||||
public static readonly Shape2D Pentagon = CreateNgon(5, Vector2D.Up);
|
||||
public static readonly Shape2D Hexagon = CreateNgon(6, Vector2D.Right);
|
||||
|
||||
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];
|
||||
|
||||
/// <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 Shape2D CreateCopy(Shape2D 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 Shape2D 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 Shape2D CreateNgon(int vertexCount, Vector2D positionToRotate)
|
||||
{
|
||||
if (vertexCount < 3)
|
||||
throw new System.ArgumentException($"{nameof(vertexCount)} must have a value of more than 2.");
|
||||
|
||||
List<Vector2D> vertices = new(vertexCount);
|
||||
|
||||
float radiansPerVertex = 360f / vertexCount * Math.DegreeToRadian;
|
||||
|
||||
for (int i = 0; i < vertexCount; i++)
|
||||
vertices.Add(positionToRotate.Rotate(i * radiansPerVertex));
|
||||
|
||||
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(Shape2D shape)
|
||||
{
|
||||
float minX = float.MaxValue, minY = float.MaxValue;
|
||||
float maxX = float.MinValue, maxY = float.MinValue;
|
||||
|
||||
foreach (Vector2D point in shape.Vertices)
|
||||
{
|
||||
minX = Math.Min(minX, point.X);
|
||||
minY = Math.Min(minY, point.Y);
|
||||
maxX = Math.Max(maxX, point.X);
|
||||
maxY = Math.Max(maxY, point.Y);
|
||||
}
|
||||
|
||||
float dx = maxX - minX;
|
||||
float dy = maxY - minY;
|
||||
float deltaMax = Math.Max(dx, dy);
|
||||
float midX = (minX + maxX) / 2;
|
||||
float midY = (minY + maxY) / 2;
|
||||
|
||||
Vector2D p1 = new((float)midX - 20f * (float)deltaMax, (float)midY - (float)deltaMax);
|
||||
Vector2D p2 = new((float)midX, (float)midY + 20 * (float)deltaMax);
|
||||
Vector2D p3 = new((float)midX + 20 * (float)deltaMax, (float)midY - (float)deltaMax);
|
||||
|
||||
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(Shape2D shape, IList<Line2D> lines)
|
||||
{
|
||||
lines.Clear();
|
||||
for (int i = 0; i < shape.Vertices.Count - 1; i++)
|
||||
lines.Add(new(shape.Vertices[i], shape.Vertices[i + 1]));
|
||||
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<Line2D> GetLines(Shape2D shape)
|
||||
{
|
||||
List<Line2D> lines = new(shape.Vertices.Count - 1);
|
||||
GetLines(shape, 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(Shape2D shape, Vector2D projectionVector, IList<float> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
int count = shape.Vertices.Count;
|
||||
for (int i = 0; i < count; 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 Projection1D Project(Shape2D shape, Vector2D projectionVector)
|
||||
{
|
||||
float min = float.MaxValue;
|
||||
float max = float.MinValue;
|
||||
|
||||
for (int i = 0; i < shape.Vertices.Count; i++)
|
||||
{
|
||||
float projectedLength = projectionVector.Dot(shape.Vertices[i]);
|
||||
min = Math.Min(projectedLength, min);
|
||||
max = Math.Max(projectedLength, 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 Shape2D TransformShape(Shape2D shape, ITransform transform)
|
||||
{
|
||||
List<Vector2D> vertices = new(shape.Vertices.Count);
|
||||
|
||||
int count = shape.Vertices.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
vertices.Add(transform.TransformVector2D(shape[i]));
|
||||
|
||||
return new Shape2D(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(Shape2D from, ITransform transform, ref Shape2D to)
|
||||
{
|
||||
to._verticesList.Clear();
|
||||
|
||||
int count = from._verticesList.Count;
|
||||
for (int i = 0; i < count; 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(Shape2D left, Shape2D right)
|
||||
{
|
||||
if (left.Vertices.Count != right.Vertices.Count)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < left.Vertices.Count; i++)
|
||||
if (!left.Vertices[i].ApproximatelyEquals(right.Vertices[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="Shape2D"/> struct.
|
||||
/// </summary>
|
||||
public static class Shape2DExtensions
|
||||
{
|
||||
/// <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 Shape2D CreateCopy(this Shape2D shape) => Shape2D.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 Shape2D shape) => Shape2D.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 Shape2D shape, IList<Line2D> lines) => Shape2D.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<Line2D> ToLines(this Shape2D shape) => Shape2D.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 Shape2D shape, Vector2D projectionVector, IList<float> list) => Shape2D.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 Projection1D ToProjection(this Shape2D shape, Vector2D projectionVector) => Shape2D.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 Shape2D TransformShape(this ITransform transform, Shape2D shape) => Shape2D.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, Shape2D from, ref Shape2D to) => Shape2D.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 Shape2D left, Shape2D right) => Shape2D.ApproximatelyEquals(left, right);
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
using System;
|
||||
|
||||
using Syntriax.Engine.Core;
|
||||
|
||||
namespace Syntriax.Engine.Physics2D.Primitives;
|
||||
|
||||
[System.Diagnostics.DebuggerDisplay("A: {A.ToString(), nq}, B: {B.ToString(), nq}, B: {C.ToString(), nq}")]
|
||||
public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C)
|
||||
{
|
||||
public readonly Vector2D A { get; init; } = A;
|
||||
public readonly Vector2D B { get; init; } = B;
|
||||
public readonly Vector2D C { get; init; } = C;
|
||||
|
||||
public readonly float Area
|
||||
=> .5f * MathF.Abs(
|
||||
A.X * (B.Y - C.Y) +
|
||||
B.X * (C.Y - A.Y) +
|
||||
C.X * (A.Y - B.Y)
|
||||
);
|
||||
|
||||
public static Circle GetCircumCircle(Triangle triangle)
|
||||
{
|
||||
Vector2D midAB = (triangle.A + triangle.B) / 2f;
|
||||
Vector2D midBC = (triangle.B + triangle.C) / 2f;
|
||||
|
||||
float slopeAB = (triangle.B.Y - triangle.A.Y) / (triangle.B.X - triangle.A.X);
|
||||
float slopeBC = (triangle.C.Y - triangle.B.Y) / (triangle.C.X - triangle.B.X);
|
||||
|
||||
Vector2D center;
|
||||
if (MathF.Abs(slopeAB - slopeBC) > float.Epsilon)
|
||||
{
|
||||
float x = (slopeAB * slopeBC * (triangle.A.Y - triangle.C.Y) + slopeBC * (triangle.A.X + triangle.B.X) - slopeAB * (triangle.B.X + triangle.C.X)) / (2f * (slopeBC - slopeAB));
|
||||
float y = -(x - (triangle.A.X + triangle.B.X) / 2f) / slopeAB + (triangle.A.Y + triangle.B.Y) / 2f;
|
||||
center = new Vector2D(x, y);
|
||||
}
|
||||
else
|
||||
center = (midAB + midBC) * .5f;
|
||||
|
||||
return new(center, Vector2D.Distance(center, triangle.A));
|
||||
}
|
||||
|
||||
public static bool ApproximatelyEquals(Triangle left, Triangle right)
|
||||
=> left.A.ApproximatelyEquals(right.A) && left.B.ApproximatelyEquals(right.B) && left.C.ApproximatelyEquals(right.C);
|
||||
}
|
||||
|
||||
public static class TriangleExtensions
|
||||
{
|
||||
public static bool ApproximatelyEquals(this Triangle left, Triangle right) => Triangle.ApproximatelyEquals(left, right);
|
||||
}
|
Reference in New Issue
Block a user