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 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, 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 { /// <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); public static bool ApproximatelyEquals(this Shape2D left, Shape2D right, float epsilon = float.Epsilon) => Shape2D.ApproximatelyEquals(left, right, epsilon); }