···63636464 return buffer.Slice(0, vertices.Length);
6565 }
6666+6767+ public static int GetClipBufferSize<TPolygon1, TPolygon2>(this TPolygon1 clipPolygon, TPolygon2 subjectPolygon)
6868+ where TPolygon1 : IPolygon
6969+ where TPolygon2 : IPolygon
7070+ {
7171+ if (clipPolygon is IConvexPolygon && subjectPolygon is IConvexPolygon)
7272+ {
7373+ // If both polygons are convex, there can only be at most 2 intersections for each edge of the subject
7474+ return subjectPolygon.GetVertices().Length * 2;
7575+ }
7676+7777+ // If both polygons are non-convex, each edge of one may intersect with each edge of the other, leading to at most n^2 vertices
7878+ // For simplicity, the case where only one of the two is non-convex is also covered under this case
7979+ return clipPolygon.GetVertices().Length * subjectPolygon.GetVertices().Length;
8080+ }
8181+8282+ public static Span<Vector2> Clip<TPolygon1, TPolygon2>(this TPolygon1 clipPolygon, TPolygon2 subjectPolygon)
8383+ where TPolygon1 : IPolygon
8484+ where TPolygon2 : IPolygon
8585+ => clipPolygon.Clip(subjectPolygon, new Vector2[clipPolygon.GetClipBufferSize(subjectPolygon)]);
8686+8787+ public static Span<Vector2> Clip<TPolygon1, TPolygon2>(this TPolygon1 clipPolygon, TPolygon2 subjectPolygon, Span<Vector2> buffer)
8888+ where TPolygon1 : IPolygon
8989+ where TPolygon2 : IPolygon
9090+ {
9191+ if (buffer.Length < GetClipBufferSize(clipPolygon, subjectPolygon))
9292+ {
9393+ throw new ArgumentException($"Clip buffer must have a length of {GetClipBufferSize(clipPolygon, subjectPolygon)}, but was {buffer.Length}."
9494+ + $"Use {nameof(GetClipBufferSize)} to calculate the size of the buffer.", nameof(buffer));
9595+ }
9696+9797+ ReadOnlySpan<Vector2> subjectVertices = subjectPolygon.GetVertices();
9898+ ReadOnlySpan<Vector2> clipVertices = clipPolygon.GetVertices();
9999+100100+ // Buffer is initially filled with the all of the subject's vertices
101101+ subjectVertices.CopyTo(buffer);
102102+103103+ // Make sure that the subject vertices are clockwise-sorted
104104+ ClockwiseSort(buffer.Slice(0, subjectVertices.Length));
105105+106106+ // The edges of clip that the subject will be clipped against
107107+ Span<Line> clipEdges = stackalloc Line[clipVertices.Length];
108108+109109+ // Joins consecutive vertices to form the clip edges
110110+ // This is done via GetRotation() to avoid a secondary temporary storage
111111+ if (GetRotation(clipVertices) < 0)
112112+ {
113113+ for (int i = clipVertices.Length - 1, c = 0; i > 0; i--, c++)
114114+ clipEdges[c] = new Line(clipVertices[i], clipVertices[i - 1]);
115115+ clipEdges[clipEdges.Length - 1] = new Line(clipVertices[0], clipVertices[clipVertices.Length - 1]);
116116+ }
117117+ else
118118+ {
119119+ for (int i = 0; i < clipVertices.Length - 1; i++)
120120+ clipEdges[i] = new Line(clipVertices[i], clipVertices[i + 1]);
121121+ clipEdges[clipEdges.Length - 1] = new Line(clipVertices[clipVertices.Length - 1], clipVertices[0]);
122122+ }
123123+124124+ // Number of vertices in the buffer that need to be tested against
125125+ // This becomes the number of vertices in the resulting polygon after each clipping iteration
126126+ int inputCount = subjectVertices.Length;
127127+128128+ // Temporary storage for the vertices from the buffer as the buffer gets altered
129129+ Span<Vector2> inputVertices = stackalloc Vector2[buffer.Length];
130130+131131+ foreach (var ce in clipEdges)
132132+ {
133133+ if (inputCount == 0)
134134+ break;
135135+136136+ // Store the original vertices (buffer will get altered)
137137+ buffer.CopyTo(inputVertices);
138138+139139+ int outputCount = 0;
140140+ var startPoint = inputVertices[inputCount - 1];
141141+142142+ for (int i = 0; i < inputCount; i++)
143143+ {
144144+ var endPoint = inputVertices[i];
145145+146146+ if (isInsideRightHalfPlane(ce, endPoint))
147147+ {
148148+ if (!isInsideRightHalfPlane(ce, startPoint))
149149+ buffer[outputCount++] = ce.At(ce.IntersectWith(new Line(startPoint, endPoint)).distance);
150150+151151+ buffer[outputCount++] = endPoint;
152152+ }
153153+ else if (isInsideRightHalfPlane(ce, startPoint))
154154+ buffer[outputCount++] = ce.At(ce.IntersectWith(new Line(startPoint, endPoint)).distance);
155155+156156+ startPoint = endPoint;
157157+ }
158158+159159+ inputCount = outputCount;
160160+ }
161161+162162+ return buffer.Slice(0, inputCount);
163163+ }
164164+165165+ /// <summary>
166166+ /// Determines whether a point is within the right half-plane of a line.
167167+ /// </summary>
168168+ /// <param name="line">The line.</param>
169169+ /// <param name="point">The point.</param>
170170+ /// <returns>Whether <paramref name="point"/> is in the right half-plane of <paramref name="line"/>.</returns>
171171+ private static bool isInsideRightHalfPlane(Line line, Vector2 point)
172172+ {
173173+ var diff1 = line.Direction;
174174+ var diff2 = point - line.StartPoint;
175175+176176+ return diff1.X * diff2.Y - diff1.Y * diff2.X <= 0;
177177+ }
178178+179179+ /// <summary>
180180+ /// Retrieves the rotation of a set of vertices.
181181+ /// </summary>
182182+ /// <param name="vertices">The vertices.</param>
183183+ /// <returns>Twice the area enclosed by the vertices. The vertices are in clockwise order if the value is positive.</returns>
184184+ public static float GetRotation(ReadOnlySpan<Vector2> vertices)
185185+ {
186186+ float rotation = 0;
187187+ for (int i = 0; i < vertices.Length - 1; ++i)
188188+ {
189189+ var vi = vertices[i];
190190+ var vj = vertices[i + 1];
191191+192192+ rotation += (vj.X - vi.X) * (vj.Y + vi.Y);
193193+ }
194194+195195+ rotation += (vertices[0].X - vertices[vertices.Length - 1].X) * (vertices[0].Y + vertices[vertices.Length - 1].Y);
196196+197197+ return rotation;
198198+ }
199199+200200+ /// <summary>
201201+ /// Sorts a set of vertices in clockwise order.
202202+ /// </summary>
203203+ /// <param name="vertices">The vertices to sort.</param>
204204+ public static void ClockwiseSort(Span<Vector2> vertices)
205205+ {
206206+ if (GetRotation(vertices) < 0)
207207+ vertices.Reverse();
208208+ }
66209 }
67210}
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22+// See the LICENCE file in the repository root for full licence text.
33+44+using System;
55+using osuTK;
66+77+namespace osu.Framework.Graphics.Primitives
88+{
99+ public class SimpleConvexPolygon : IConvexPolygon
1010+ {
1111+ private readonly Vector2[] vertices;
1212+1313+ public SimpleConvexPolygon(Vector2[] vertices)
1414+ {
1515+ this.vertices = vertices;
1616+ }
1717+1818+ public ReadOnlySpan<Vector2> GetAxisVertices() => vertices;
1919+2020+ public ReadOnlySpan<Vector2> GetVertices() => vertices;
2121+2222+ public int MaxClipVertices => vertices.Length * 2;
2323+ }
2424+}