Add PolygonShape.split_to_convex (polygon to convex tiles)

Ports geometry/planar/Polygon.java (corner de-duplication and collinear
removal, winding number) and PolygonShape.java, including the recursive
split_to_convex that decomposes a simple polygon into convex Simplex
tiles by dividing at concave corners along minimal axis-parallel lines.

Orientation and convexity tests are exact; the division-point search is
approximate (float line evaluation, split point rounded to an integer
corner) as upstream. The concave-corner search starts deterministically
at corner 0 rather than a seeded PRNG; this only affects which valid
decomposition is produced.

Supporting additions: Simplex.from_corners (convex polygon to simplex)
and Line.function_value_approx / function_in_y_value_approx.

Invariant tests over L, plus, staircase and square polygons: tile areas
sum to the polygon area (no gaps, no overlap), a point is in the polygon
iff in some tile, and no point is strictly inside more than one tile
(interiors disjoint). Also covers Polygon normalization and orientation.
This commit is contained in:
2026-07-11 18:41:07 -06:00
parent 1599d181b5
commit 68ab16a7cf
6 changed files with 525 additions and 0 deletions
+22
View File
@@ -198,6 +198,28 @@ class Line:
result = self.side_of(line_1.intersection(line_2))
return result
def function_value_approx(self, x: float) -> float:
"""Approximate ``y`` on this (non-vertical) line at abscissa ``x``."""
p1 = self.a.to_float()
p2 = self.b.to_float()
dx = p2.x - p1.x
if dx == 0:
return 0.0
dy = p2.y - p1.y
det = p1.x * p2.y - p2.x * p1.y
return (dy * x - det) / dx
def function_in_y_value_approx(self, y: float) -> float:
"""Approximate ``x`` on this (non-horizontal) line at ordinate ``y``."""
p1 = self.a.to_float()
p2 = self.b.to_float()
dy = p2.y - p1.y
if dy == 0:
return 0.0
dx = p2.x - p1.x
det = p1.x * p2.y - p2.x * p1.y
return (dx * y + det) / dy
# --- intersection (exact) ----------------------------------------------
def intersection(self, other: Line) -> Point: