333 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System.Collections;
 | 
						|
using System.Collections.Generic;
 | 
						|
 | 
						|
namespace 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 class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
 | 
						|
{
 | 
						|
    public static Shape2D Triangle => CreateNgon(3, Vector2D.Up);
 | 
						|
    public static Shape2D Square => CreateNgon(4, Vector2D.One);
 | 
						|
    public static Shape2D Pentagon => CreateNgon(5, Vector2D.Up);
 | 
						|
    public static Shape2D Hexagon => CreateNgon(6, Vector2D.Right);
 | 
						|
 | 
						|
    public Event<Shape2D> OnShapeUpdated { get; } = new();
 | 
						|
 | 
						|
    private List<Vector2D> _vertices = vertices;
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Gets the vertices of the <see cref="Shape2D"/>.
 | 
						|
    /// </summary>
 | 
						|
    public IReadOnlyList<Vector2D> Vertices
 | 
						|
    {
 | 
						|
        get => _vertices;
 | 
						|
        set
 | 
						|
        {
 | 
						|
            _vertices.Clear();
 | 
						|
 | 
						|
            foreach (Vector2D vertex in value)
 | 
						|
                _vertices.Add(vertex);
 | 
						|
 | 
						|
            OnShapeUpdated?.Invoke(this);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// <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], shape[i - 1]));
 | 
						|
    }
 | 
						|
 | 
						|
    /// <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, Shape2D to)
 | 
						|
    {
 | 
						|
        to._vertices.Clear();
 | 
						|
 | 
						|
        int count = from._vertices.Count;
 | 
						|
        for (int i = 0; i < count; i++)
 | 
						|
            to._vertices.Add(transform.Transform(from[i]));
 | 
						|
 | 
						|
        to.OnShapeUpdated?.Invoke(to);
 | 
						|
    }
 | 
						|
 | 
						|
    /// <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;
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Determines whether the specified object is equal to the current <see cref="Shape2D"/>.
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="obj">The object to compare with the current <see cref="Shape2D"/>.</param>
 | 
						|
    /// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Shape2D"/>; otherwise, <see cref="false"/>.</returns>
 | 
						|
    public override bool Equals(object? obj) => obj is Shape2D shape2D && _vertices.Equals(shape2D._vertices);
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Generates a hash code for the <see cref="Shape2D"/>.
 | 
						|
    /// </summary>
 | 
						|
    /// <returns>A hash code for the <see cref="Shape2D"/>.</returns>
 | 
						|
    public override int GetHashCode() => System.HashCode.Combine(Vertices);
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Converts the <see cref="Shape2D"/> to its string representation.
 | 
						|
    /// </summary>
 | 
						|
    /// <returns>A string representation of the <see cref="Shape2D"/>.</returns>
 | 
						|
    public override string ToString()
 | 
						|
    {
 | 
						|
        System.Text.StringBuilder stringBuilder = new(Vertices[0].ToString());
 | 
						|
        for (int i = 1; i < Vertices.Count; i++)
 | 
						|
        {
 | 
						|
            stringBuilder.Append(", ");
 | 
						|
            stringBuilder.Append(Vertices[i].ToString());
 | 
						|
        }
 | 
						|
        return $"{nameof(Shape2D)}({stringBuilder})";
 | 
						|
    }
 | 
						|
 | 
						|
    /// <inheritdoc/>
 | 
						|
    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> triangles) => Shape2D.TriangulateConvex(shape, triangles);
 | 
						|
 | 
						|
    /// <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, Shape2D to) => Shape2D.Transform(from, transform, 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,Shape2D)" />
 | 
						|
    public static void Transform(this Shape2D from, ITransform2D transform, Shape2D to) => Shape2D.Transform(from, transform, 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);
 | 
						|
}
 |