291 lines
12 KiB
C#
291 lines
12 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
using Syntriax.Engine.Core.Abstract;
|
|
|
|
namespace Syntriax.Engine.Core;
|
|
|
|
/// <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 a <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 Square = 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 <see cref="Shape2D"/>.
|
|
/// </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 <see cref="Shape2D"/>.
|
|
/// </summary>
|
|
/// <param name="shape">The <see cref="Shape2D"/> to copy.</param>
|
|
/// <returns>A copy of the input <see cref="Shape2D"/>.</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 <see cref="Shape2D"/>.
|
|
/// </summary>
|
|
/// <param name="shape">The <see cref="Shape2D"/> to enclose.</param>
|
|
/// <returns>The super triangle that encloses the given <see cref="Shape2D"/>.</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>
|
|
/// Triangulates the given convex <see cref="Shape2D"/>.
|
|
/// </summary>
|
|
/// <param name="shape">The <see cref="Shape2D"/> to triangulate.</param>
|
|
/// <param name="triangles">The list to populate with triangles.</param>
|
|
public static void TriangulateConvex(Shape2D shape, IList<Triangle> triangles)
|
|
{
|
|
triangles.Clear();
|
|
|
|
for (int i = 2; i < shape.Vertices.Count; i++)
|
|
triangles.Add(new Triangle(shape[0], shape[i - 1], shape[i]));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triangulates the given convex <see cref="Shape2D"/>.
|
|
/// </summary>
|
|
/// <param name="shape">The <see cref="Shape2D"/> to triangulate.</param>
|
|
/// <returns>A list of <see cref="Triangle"/>s that makes up the given convex <see cref="Shape2D"/>.</returns>
|
|
public static List<Triangle> TriangulateConvex(Shape2D shape)
|
|
{
|
|
List<Triangle> triangles = new(shape.Vertices.Count - 2);
|
|
TriangulateConvex(shape, triangles);
|
|
return triangles;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="Line2D"/>s that form the edges of the <see cref="Shape2D"/>.
|
|
/// </summary>
|
|
/// <param name="shape">The <see cref="Shape2D"/> to get <see cref="Line2D"/>s from.</param>
|
|
/// <param name="lines">The list to populate with <see cref="Line2D"/>.</sparam>
|
|
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 <see cref="Line2D"/>s that form the edges of the <see cref="Shape2D"/>.
|
|
/// </summary>
|
|
/// <param name="shape">The shape to get <see cref="Line2D"/>s from.</param>
|
|
/// <returns>A list of <see cref="Line2D"/>s that form the edges of the <see cref="Shape2D"/>.</returns>
|
|
public static List<Line2D> GetLines(Shape2D shape)
|
|
{
|
|
List<Line2D> lines = new(shape.Vertices.Count - 1);
|
|
GetLines(shape, lines);
|
|
return lines;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Projects the <see cref="Shape2D"/> onto a 1D plane.
|
|
/// </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 <see cref="Shape2D"/> onto a vector.
|
|
/// </summary>
|
|
/// <param name="shape">The <see cref="Shape2D"/> to project.</param>
|
|
/// <param name="projectionVector">The vector to project onto.</param>
|
|
/// <returns>The projection of the <see cref="Shape2D"/> 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 <see cref="Shape2D"/> using the specified <see cref="ITransform2D"/>.
|
|
/// </summary>
|
|
/// <param name="shape">The <see cref="Shape2D"/> to transform.</param>
|
|
/// <param name="transform">The <see cref="ITransform2D"/> to apply.</param>
|
|
/// <returns>The transformed <see cref="Shape2D"/>.</returns>
|
|
public static Shape2D Transform(Shape2D shape, ITransform2D transform)
|
|
{
|
|
List<Vector2D> vertices = new(shape.Vertices.Count);
|
|
|
|
int count = shape.Vertices.Count;
|
|
for (int i = 0; i < count; i++)
|
|
vertices.Add(transform.Transform(shape[i]));
|
|
|
|
return new Shape2D(vertices);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transforms the <see cref="Shape2D"/> using the specified <see cref="ITransform2D"/>.
|
|
/// </summary>
|
|
/// <param name="from">The <see cref="Shape2D"/> to transform.</param>
|
|
/// <param name="transform">The <see cref="ITransform2D"/> to apply.</param>
|
|
/// <param name="to">The transformed <see cref="Shape2D"/>.</param>
|
|
public static void Transform(Shape2D from, ITransform2D transform, ref Shape2D to)
|
|
{
|
|
to._verticesList.Clear();
|
|
|
|
int count = from._verticesList.Count;
|
|
for (int i = 0; i < count; i++)
|
|
to._verticesList.Add(transform.Transform(from[i]));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether two <see cref="Shape2D"/>s are approximately equal.
|
|
/// </summary>
|
|
/// <param name="left">The first <see cref="Shape2D"/> to compare.</param>
|
|
/// <param name="right">The second <see cref="Shape2D"/> to compare.</param>
|
|
/// <param name="epsilon">The epsilon range.</param>
|
|
/// <returns><c>true</c> if the <see cref="Shape2D"/>s are approximately equal; otherwise, <c>false</c>.</returns>
|
|
public static bool ApproximatelyEquals(Shape2D left, Shape2D right, float epsilon = float.Epsilon)
|
|
{
|
|
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], epsilon))
|
|
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
|
|
{
|
|
/// <inheritdoc cref="Shape2D.CreateCopy(Shape2D)" />
|
|
public static Shape2D CreateCopy(this Shape2D shape) => Shape2D.CreateCopy(shape);
|
|
|
|
/// <inheritdoc cref="Shape2D.GetSuperTriangle(Shape2D)" />
|
|
public static Triangle ToSuperTriangle(this Shape2D shape) => Shape2D.GetSuperTriangle(shape);
|
|
|
|
/// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D, IList{Triangle})" />
|
|
public static void ToTrianglesConvex(this Shape2D shape, IList<Triangle> lines) => Shape2D.TriangulateConvex(shape, lines);
|
|
|
|
/// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D)" />
|
|
public static List<Triangle> ToTrianglesConvex(this Shape2D shape) => Shape2D.TriangulateConvex(shape);
|
|
|
|
/// <inheritdoc cref="Shape2D.GetLines(Shape2D, IList{Line2D})" />
|
|
public static void ToLines(this Shape2D shape, IList<Line2D> lines) => Shape2D.GetLines(shape, lines);
|
|
|
|
/// <inheritdoc cref="Shape2D.GetLines(Shape2D)" />
|
|
public static List<Line2D> ToLines(this Shape2D shape) => Shape2D.GetLines(shape);
|
|
|
|
/// <inheritdoc cref="Shape2D.Project(Shape2D, Vector2D, IList{float})" />
|
|
public static void ToProjection(this Shape2D shape, Vector2D projectionVector, IList<float> list) => Shape2D.Project(shape, projectionVector, list);
|
|
|
|
/// <inheritdoc cref="Shape2D.Project(Shape2D, Vector2D)" />
|
|
public static Projection1D ToProjection(this Shape2D shape, Vector2D projectionVector) => Shape2D.Project(shape, projectionVector);
|
|
|
|
/// <inheritdoc cref="Shape2D.Transform(Shape2D, ITransform2D)" />
|
|
public static Shape2D Transform(this ITransform2D transform, Shape2D shape) => Shape2D.Transform(shape, transform);
|
|
|
|
/// <inheritdoc cref="Shape2D.Transform(Shape2D, ITransform2D, Shape2D)" />
|
|
public static void Transform(this ITransform2D transform, Shape2D from, ref Shape2D to) => Shape2D.Transform(from, transform, ref to);
|
|
|
|
/// <inheritdoc cref="Shape2D.Transform(Shape2D, ITransform2D)" />
|
|
public static Shape2D Transform(this Shape2D shape, ITransform2D transform) => Shape2D.Transform(shape, transform);
|
|
|
|
/// <inheritdoc cref="Shape2D.Transform(Shape2D, ITransform2D, ref Shape2D)" />
|
|
public static void Transform(this Shape2D from, ITransform2D transform, ref Shape2D to) => Shape2D.Transform(from, transform, ref to);
|
|
|
|
/// <inheritdoc cref="Shape2D.ApproximatelyEquals(Shape2D, Shape2D, float)" />
|
|
public static bool ApproximatelyEquals(this Shape2D left, Shape2D right, float epsilon = float.Epsilon) => Shape2D.ApproximatelyEquals(left, right, epsilon);
|
|
}
|