"""Convex camera-cell stroke shapes and perspective-preserving clipping.""" import math import random from patch_geometry import clip_halfspace, interpolate_uv def capsule_in_cell(i, j, nx, ny, seed, width_ratio=0.42, length_ratio=0.84): """Return a round-ended stroke wholly inside one normalized camera cell.""" if not 0 < width_ratio <= length_ratio < 1: raise ValueError("require 0 < width_ratio <= length_ratio < 1") rng = random.Random((seed * 1000003 + i * 9176 + j * 6113) & 0xFFFFFFFF) angle = rng.uniform(0, math.pi) radius = width_ratio / 2 half_body = (length_ratio - width_ratio) / 2 outline = [] for centre, start in ((half_body, -math.pi / 2), (-half_body, math.pi / 2)): for step in range(9): theta = start + math.pi * step / 8 x = centre + radius * math.cos(theta) y = radius * math.sin(theta) xr = x * math.cos(angle) - y * math.sin(angle) yr = x * math.sin(angle) + y * math.cos(angle) outline.append(((i + 0.5 + xr) / nx, (j + 0.5 + yr) / ny)) return outline def clip_to_stroke(fragment, outline): """Clip a projected triangle while retaining its perspective attributes.""" points = fragment for a, b in zip(outline, outline[1:] + outline[:1]): dx, dy = b[0] - a[0], b[1] - a[1] points = clip_halfspace( points, lambda p, a=a, dx=dx, dy=dy: dx * (p[1] - a[1]) - dy * (p[0] - a[0]), ) if len(points) < 3: return [] return points def projected_stroke_uv(fragment, loop_uvs, outline): """Resolve a clipped camera stroke through the same UV interpolation as patches.""" return [interpolate_uv(point, loop_uvs) for point in clip_to_stroke(fragment, outline)]