Add scrub edge bushes
This commit is contained in:
@@ -103,12 +103,105 @@ def sample_tree_row(points, spacing, height):
|
||||
|
||||
def polygon_area(ring):
|
||||
"""Unsigned shoelace area; 0.0 for degenerate rings."""
|
||||
return abs(signed_polygon_area(ring))
|
||||
|
||||
|
||||
def signed_polygon_area(ring):
|
||||
"""Signed shoelace area; positive for counter-clockwise rings."""
|
||||
if len(ring) < 3:
|
||||
return 0.0
|
||||
area = 0.0
|
||||
for (x1, y1), (x2, y2) in zip(ring, ring[1:] + ring[:1]):
|
||||
area += x1 * y2 - x2 * y1
|
||||
return abs(area) * 0.5
|
||||
return area * 0.5
|
||||
|
||||
|
||||
def sample_ring_boundary(ring, spacing, inset=0.0, max_samples=None):
|
||||
"""Evenly sample a closed ring's boundary.
|
||||
|
||||
Returns (x, y, angle, index) samples. ``angle`` follows the local edge
|
||||
direction, and ``inset`` moves the sample toward the polygon interior.
|
||||
"""
|
||||
if len(ring) > 1 and ring[0] == ring[-1]:
|
||||
ring = ring[:-1]
|
||||
if len(ring) < 3 or spacing <= 0.0:
|
||||
return []
|
||||
|
||||
edges = []
|
||||
perimeter = 0.0
|
||||
winding = signed_polygon_area(ring)
|
||||
for index, (start, end) in enumerate(zip(ring, ring[1:] + ring[:1])):
|
||||
dx = end[0] - start[0]
|
||||
dy = end[1] - start[1]
|
||||
length = math.hypot(dx, dy)
|
||||
if length <= 1e-9:
|
||||
continue
|
||||
ux = dx / length
|
||||
uy = dy / length
|
||||
# Counter-clockwise rings have their interior on the left side of each
|
||||
# edge; clockwise rings have it on the right.
|
||||
inward = (-uy, ux) if winding >= 0.0 else (uy, -ux)
|
||||
edges.append((perimeter, start, ux, uy, length, inward, index))
|
||||
perimeter += length
|
||||
if not edges:
|
||||
return []
|
||||
|
||||
count = max(1, int(perimeter / spacing))
|
||||
if max_samples:
|
||||
count = min(count, max_samples)
|
||||
step = perimeter / count
|
||||
samples = []
|
||||
edge_cursor = 0
|
||||
for sample_index in range(count):
|
||||
target = (sample_index + 0.5) * step
|
||||
while edge_cursor + 1 < len(edges) and (
|
||||
edges[edge_cursor][0] + edges[edge_cursor][4] < target
|
||||
):
|
||||
edge_cursor += 1
|
||||
edge_start, start, ux, uy, length, inward, _ = edges[edge_cursor]
|
||||
along = max(0.0, min(length, target - edge_start))
|
||||
x = start[0] + ux * along
|
||||
y = start[1] + uy * along
|
||||
sx = x + inward[0] * inset
|
||||
sy = y + inward[1] * inset
|
||||
if inset > 0.0 and not point_in_polygon((sx, sy), ring):
|
||||
sx, sy = x, y
|
||||
samples.append((sx, sy, math.atan2(uy, ux), sample_index))
|
||||
return samples
|
||||
|
||||
|
||||
def sample_polygon_interior(ring, spacing, edge_clearance=0.0, max_samples=None,
|
||||
seed=0):
|
||||
"""Jittered interior samples for sparse planting inside a polygon."""
|
||||
if len(ring) > 1 and ring[0] == ring[-1]:
|
||||
ring = ring[:-1]
|
||||
if len(ring) < 3 or spacing <= 0.0 or polygon_area(ring) <= 1e-9:
|
||||
return []
|
||||
|
||||
xmin = min(x for x, _ in ring)
|
||||
xmax = max(x for x, _ in ring)
|
||||
ymin = min(y for _, y in ring)
|
||||
ymax = max(y for _, y in ring)
|
||||
cols = max(1, int(math.ceil((xmax - xmin) / spacing)))
|
||||
rows = max(1, int(math.ceil((ymax - ymin) / spacing)))
|
||||
samples = []
|
||||
for col in range(cols):
|
||||
for row in range(rows):
|
||||
sample_seed = ((col + 1) * 73856093) ^ ((row + 1) * 19349663) ^ seed
|
||||
jx = ((sample_seed * 0.61803398875) % 1.0 - 0.5) * spacing * 0.7
|
||||
jy = ((sample_seed * 0.41421356237) % 1.0 - 0.5) * spacing * 0.7
|
||||
x = xmin + (col + 0.5) * spacing + jx
|
||||
y = ymin + (row + 0.5) * spacing + jy
|
||||
if not point_in_polygon((x, y), ring):
|
||||
continue
|
||||
if edge_clearance > 0.0 and distance_to_ring((x, y), ring) < edge_clearance:
|
||||
continue
|
||||
samples.append((x, y, sample_seed))
|
||||
|
||||
if max_samples and len(samples) > max_samples:
|
||||
samples.sort(key=lambda item: (item[2] * 0.754877666) % 1.0)
|
||||
samples = samples[:max_samples]
|
||||
return samples
|
||||
|
||||
|
||||
def point_in_polygon(point, ring):
|
||||
|
||||
Reference in New Issue
Block a user