diff --git a/Scripts/ShapeEditor/Decomposition/Bayazit/BayazitDecomposer.cs b/Scripts/ShapeEditor/Decomposition/Bayazit/BayazitDecomposer.cs index c038e9e..3529b31 100644 --- a/Scripts/ShapeEditor/Decomposition/Bayazit/BayazitDecomposer.cs +++ b/Scripts/ShapeEditor/Decomposition/Bayazit/BayazitDecomposer.cs @@ -22,6 +22,8 @@ namespace AeternumGames.ShapeEditor public static class BayazitDecomposer { private static int MaxPolygonVertices = 1024; // henry: used to be 8, but we want less CSG brushes / meshes. + private const float SingularityEpsilon = 1e-4f; + private const float SingularityEpsilonSqr = SingularityEpsilon * SingularityEpsilon; /// /// [2D] Decompose the polygon into several smaller non-concave polygon. If the polygon is @@ -32,6 +34,14 @@ public static List ConvexPartition(Polygon vertices) Debug.Assert(vertices.Count >= 3); Debug.Assert(vertices.IsCounterClockWise2D()); + if (TrySplitAtSingularity(vertices, out var splitPolygons)) + { + var splitResult = new List(); + for (int i = 0; i < splitPolygons.Count; i++) + splitResult.AddRange(ConvexPartition(splitPolygons[i])); + return splitResult; + } + var depth = 800; var result = TriangulatePolygon(vertices, ref depth); @@ -41,6 +51,164 @@ public static List ConvexPartition(Polygon vertices) return result; } + private static bool TrySplitAtSingularity(Polygon vertices, out List polygons) + { + polygons = null; + + if (vertices.Count < 4) + return false; + + if (TrySplitAtCoincidentVertex(vertices, out polygons)) + return true; + + return TrySplitAtVertexOnEdge(vertices, out polygons); + } + + private static bool TrySplitAtCoincidentVertex(Polygon vertices, out List polygons) + { + polygons = null; + var count = vertices.Count; + + for (int i = 0; i < count; i++) + { + for (int j = i + 1; j < count; j++) + { + if (AreAdjacent(i, j, count)) + continue; + + if (!SamePosition((Vector2)vertices[i].position, (Vector2)vertices[j].position)) + continue; + + var first = CopyRange(i, j, vertices); + var second = CopyRange(j, i, vertices); + if (TryCreateSplitResult(vertices, first, second, out polygons)) + return true; + } + } + + return false; + } + + private static bool TrySplitAtVertexOnEdge(Polygon vertices, out List polygons) + { + polygons = null; + var count = vertices.Count; + + for (int vertexIndex = 0; vertexIndex < count; vertexIndex++) + { + var point = (Vector2)vertices[vertexIndex].position; + + for (int edgeStartIndex = 0; edgeStartIndex < count; edgeStartIndex++) + { + var edgeEndIndex = (edgeStartIndex + 1) % count; + if (vertexIndex == edgeStartIndex || vertexIndex == edgeEndIndex) + continue; + + var edgeStart = (Vector2)vertices[edgeStartIndex].position; + var edgeEnd = (Vector2)vertices[edgeEndIndex].position; + if (!PointIsInsideEdge(point, edgeStart, edgeEnd)) + continue; + + var first = CopyRange(vertexIndex, edgeStartIndex, vertices); + first.Add(CreateSplitVertex(vertices[vertexIndex], point)); + + var second = new Polygon(); + second.Add(CreateSplitVertex(vertices[vertexIndex], point)); + second.AddRange(CopyRange(edgeEndIndex, vertexIndex, vertices)); + + if (TryCreateSplitResult(vertices, first, second, out polygons)) + return true; + } + } + + return false; + } + + private static bool TryCreateSplitResult(Polygon source, Polygon first, Polygon second, out List polygons) + { + polygons = null; + + if (!PrepareSplitPolygon(source, first) || !PrepareSplitPolygon(source, second)) + return false; + + polygons = new List { first, second }; + return true; + } + + private static bool PrepareSplitPolygon(Polygon source, Polygon polygon) + { + RemoveDuplicateVertices(polygon); + polygon.CollinearSimplify(SingularityEpsilon); + + if (polygon.Count < 3 || Mathf.Abs(polygon.GetSignedArea2D()) <= SingularityEpsilon) + return false; + + polygon.ForceCounterClockWise2D(); + polygon.booleanOperator = source.booleanOperator; + return true; + } + + private static Polygon CopyRange(int start, int end, Polygon vertices) + { + var count = vertices.Count; + while (end < start) + end += count; + + var result = new Polygon(end - start + 1); + for (int i = start; i <= end; i++) + { + var vertex = vertices[i % count]; + result.Add(new Vertex(vertex.position, vertex.uv0, vertex.hidden, vertex.material)); + } + return result; + } + + private static void RemoveDuplicateVertices(Polygon polygon) + { + for (int i = polygon.Count - 1; i > 0; i--) + { + if (SamePosition((Vector2)polygon[i].position, (Vector2)polygon[i - 1].position)) + polygon.RemoveAt(i); + } + + if (polygon.Count > 1 && SamePosition((Vector2)polygon[0].position, (Vector2)polygon[polygon.Count - 1].position)) + polygon.RemoveAt(polygon.Count - 1); + } + + private static Vertex CreateSplitVertex(Vertex template, Vector2 position) + { + return new Vertex(new Vector3(position.x, position.y, template.position.z), template.uv0, template.hidden, template.material); + } + + private static bool AreAdjacent(int first, int second, int count) + { + return Mathf.Abs(first - second) == 1 || Mathf.Abs(first - second) == count - 1; + } + + private static bool SamePosition(Vector2 first, Vector2 second) + { + return SquareDist(first, second) <= SingularityEpsilonSqr; + } + + private static bool PointIsInsideEdge(Vector2 point, Vector2 edgeStart, Vector2 edgeEnd) + { + if (SquareDist(point, edgeStart) <= SingularityEpsilonSqr || SquareDist(point, edgeEnd) <= SingularityEpsilonSqr) + return false; + + var edge = edgeEnd - edgeStart; + var pointOffset = point - edgeStart; + var edgeLengthSqr = edge.sqrMagnitude; + if (edgeLengthSqr <= SingularityEpsilonSqr) + return false; + + var t = Vector2.Dot(pointOffset, edge) / edgeLengthSqr; + if (t <= 0f || t >= 1f) + return false; + + var closestPoint = edgeStart + edge * t; + return SquareDist(point, closestPoint) <= SingularityEpsilonSqr; + } + private static List TriangulatePolygon(Polygon vertices, ref int depth) { if (depth-- <= 0) return new List { vertices }; @@ -365,4 +533,4 @@ private static bool FloatEquals(float value1, float value2) } } -#endif \ No newline at end of file +#endif