···6364 return buffer.Slice(0, vertices.Length);
65 }
66+67+ public static int GetClipBufferSize<TPolygon1, TPolygon2>(this TPolygon1 clipPolygon, TPolygon2 subjectPolygon)
68+ where TPolygon1 : IPolygon
69+ where TPolygon2 : IPolygon
70+ {
71+ if (clipPolygon is IConvexPolygon && subjectPolygon is IConvexPolygon)
72+ {
73+ // If both polygons are convex, there can only be at most 2 intersections for each edge of the subject
74+ return subjectPolygon.GetVertices().Length * 2;
75+ }
76+77+ // 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
78+ // For simplicity, the case where only one of the two is non-convex is also covered under this case
79+ return clipPolygon.GetVertices().Length * subjectPolygon.GetVertices().Length;
80+ }
81+82+ public static Span<Vector2> Clip<TPolygon1, TPolygon2>(this TPolygon1 clipPolygon, TPolygon2 subjectPolygon)
83+ where TPolygon1 : IPolygon
84+ where TPolygon2 : IPolygon
85+ => clipPolygon.Clip(subjectPolygon, new Vector2[clipPolygon.GetClipBufferSize(subjectPolygon)]);
86+87+ public static Span<Vector2> Clip<TPolygon1, TPolygon2>(this TPolygon1 clipPolygon, TPolygon2 subjectPolygon, Span<Vector2> buffer)
88+ where TPolygon1 : IPolygon
89+ where TPolygon2 : IPolygon
90+ {
91+ if (buffer.Length < GetClipBufferSize(clipPolygon, subjectPolygon))
92+ {
93+ throw new ArgumentException($"Clip buffer must have a length of {GetClipBufferSize(clipPolygon, subjectPolygon)}, but was {buffer.Length}."
94+ + $"Use {nameof(GetClipBufferSize)} to calculate the size of the buffer.", nameof(buffer));
95+ }
96+97+ ReadOnlySpan<Vector2> subjectVertices = subjectPolygon.GetVertices();
98+ ReadOnlySpan<Vector2> clipVertices = clipPolygon.GetVertices();
99+100+ // Buffer is initially filled with the all of the subject's vertices
101+ subjectVertices.CopyTo(buffer);
102+103+ // Make sure that the subject vertices are clockwise-sorted
104+ ClockwiseSort(buffer.Slice(0, subjectVertices.Length));
105+106+ // The edges of clip that the subject will be clipped against
107+ Span<Line> clipEdges = stackalloc Line[clipVertices.Length];
108+109+ // Joins consecutive vertices to form the clip edges
110+ // This is done via GetRotation() to avoid a secondary temporary storage
111+ if (GetRotation(clipVertices) < 0)
112+ {
113+ for (int i = clipVertices.Length - 1, c = 0; i > 0; i--, c++)
114+ clipEdges[c] = new Line(clipVertices[i], clipVertices[i - 1]);
115+ clipEdges[clipEdges.Length - 1] = new Line(clipVertices[0], clipVertices[clipVertices.Length - 1]);
116+ }
117+ else
118+ {
119+ for (int i = 0; i < clipVertices.Length - 1; i++)
120+ clipEdges[i] = new Line(clipVertices[i], clipVertices[i + 1]);
121+ clipEdges[clipEdges.Length - 1] = new Line(clipVertices[clipVertices.Length - 1], clipVertices[0]);
122+ }
123+124+ // Number of vertices in the buffer that need to be tested against
125+ // This becomes the number of vertices in the resulting polygon after each clipping iteration
126+ int inputCount = subjectVertices.Length;
127+128+ // Temporary storage for the vertices from the buffer as the buffer gets altered
129+ Span<Vector2> inputVertices = stackalloc Vector2[buffer.Length];
130+131+ foreach (var ce in clipEdges)
132+ {
133+ if (inputCount == 0)
134+ break;
135+136+ // Store the original vertices (buffer will get altered)
137+ buffer.CopyTo(inputVertices);
138+139+ int outputCount = 0;
140+ var startPoint = inputVertices[inputCount - 1];
141+142+ for (int i = 0; i < inputCount; i++)
143+ {
144+ var endPoint = inputVertices[i];
145+146+ if (isInsideRightHalfPlane(ce, endPoint))
147+ {
148+ if (!isInsideRightHalfPlane(ce, startPoint))
149+ buffer[outputCount++] = ce.At(ce.IntersectWith(new Line(startPoint, endPoint)).distance);
150+151+ buffer[outputCount++] = endPoint;
152+ }
153+ else if (isInsideRightHalfPlane(ce, startPoint))
154+ buffer[outputCount++] = ce.At(ce.IntersectWith(new Line(startPoint, endPoint)).distance);
155+156+ startPoint = endPoint;
157+ }
158+159+ inputCount = outputCount;
160+ }
161+162+ return buffer.Slice(0, inputCount);
163+ }
164+165+ /// <summary>
166+ /// Determines whether a point is within the right half-plane of a line.
167+ /// </summary>
168+ /// <param name="line">The line.</param>
169+ /// <param name="point">The point.</param>
170+ /// <returns>Whether <paramref name="point"/> is in the right half-plane of <paramref name="line"/>.</returns>
171+ private static bool isInsideRightHalfPlane(Line line, Vector2 point)
172+ {
173+ var diff1 = line.Direction;
174+ var diff2 = point - line.StartPoint;
175+176+ return diff1.X * diff2.Y - diff1.Y * diff2.X <= 0;
177+ }
178+179+ /// <summary>
180+ /// Retrieves the rotation of a set of vertices.
181+ /// </summary>
182+ /// <param name="vertices">The vertices.</param>
183+ /// <returns>Twice the area enclosed by the vertices. The vertices are in clockwise order if the value is positive.</returns>
184+ public static float GetRotation(ReadOnlySpan<Vector2> vertices)
185+ {
186+ float rotation = 0;
187+ for (int i = 0; i < vertices.Length - 1; ++i)
188+ {
189+ var vi = vertices[i];
190+ var vj = vertices[i + 1];
191+192+ rotation += (vj.X - vi.X) * (vj.Y + vi.Y);
193+ }
194+195+ rotation += (vertices[0].X - vertices[vertices.Length - 1].X) * (vertices[0].Y + vertices[vertices.Length - 1].Y);
196+197+ return rotation;
198+ }
199+200+ /// <summary>
201+ /// Sorts a set of vertices in clockwise order.
202+ /// </summary>
203+ /// <param name="vertices">The vertices to sort.</param>
204+ public static void ClockwiseSort(Span<Vector2> vertices)
205+ {
206+ if (GetRotation(vertices) < 0)
207+ vertices.Reverse();
208+ }
209 }
210}
···1+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2+// See the LICENCE file in the repository root for full licence text.
3+4+using System;
5+using osuTK;
6+7+namespace osu.Framework.Graphics.Primitives
8+{
9+ public class SimpleConvexPolygon : IConvexPolygon
10+ {
11+ private readonly Vector2[] vertices;
12+13+ public SimpleConvexPolygon(Vector2[] vertices)
14+ {
15+ this.vertices = vertices;
16+ }
17+18+ public ReadOnlySpan<Vector2> GetAxisVertices() => vertices;
19+20+ public ReadOnlySpan<Vector2> GetVertices() => vertices;
21+22+ public int MaxClipVertices => vertices.Length * 2;
23+ }
24+}