Sight groups, DM reveal areas (cones), client-side image shrink for map upload
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
+21
-7
@@ -11,8 +11,9 @@ from PIL import Image, ImageDraw
|
||||
EPS = 1e-5
|
||||
|
||||
|
||||
def polygon(origin, segs, w, h, radius=0.0):
|
||||
"""Points of the area visible from origin. segs: [(x1, y1, x2, y2)] that block sight; radius 0 means unlimited."""
|
||||
def polygon(origin, segs, w, h, radius=0.0, sector=None):
|
||||
"""Points of the area visible from origin. segs: [(x1, y1, x2, y2)] that block sight; radius 0 means unlimited.
|
||||
sector = (direction, half angle) in radians limits the view to a cone."""
|
||||
ox, oy = origin
|
||||
border = [(0, 0, w, 0), (w, 0, w, h), (w, h, 0, h), (0, h, 0, 0)]
|
||||
if radius:
|
||||
@@ -34,7 +35,13 @@ def polygon(origin, segs, w, h, radius=0.0):
|
||||
angles += (a - EPS, a, a + EPS)
|
||||
if radius:
|
||||
angles += [i * (2 * math.pi / 120) for i in range(120)]
|
||||
angles.sort()
|
||||
if sector:
|
||||
d, half = sector
|
||||
rel = [(a - d + math.pi) % (2 * math.pi) - math.pi for a in angles]
|
||||
rel = [a for a in rel if abs(a) <= half] + [-half, half] + [-half + i * (2 * half / 48) for i in range(49)]
|
||||
angles = [d + a for a in sorted(rel)]
|
||||
else:
|
||||
angles.sort()
|
||||
pts = []
|
||||
for a in angles:
|
||||
dx, dy = math.cos(a), math.sin(a)
|
||||
@@ -54,7 +61,7 @@ def polygon(origin, segs, w, h, radius=0.0):
|
||||
if best == float("inf"):
|
||||
continue
|
||||
pts.append((ox + dx * best, oy + dy * best))
|
||||
return pts
|
||||
return [(ox, oy)] + pts if sector else pts
|
||||
|
||||
|
||||
def inside(pt, poly) -> bool:
|
||||
@@ -112,19 +119,26 @@ def source(path):
|
||||
return im
|
||||
|
||||
|
||||
def cut(path, poly, key):
|
||||
def inside_any(pt, polys) -> bool:
|
||||
return any(inside(pt, p) for p in polys)
|
||||
|
||||
|
||||
def cut(path, polys, key):
|
||||
"""The part of the image inside the polygon (everything else black), cropped to its bounding box.
|
||||
Returns (webp bytes, (x, y, w, h))."""
|
||||
hit = _cuts.fetch(key)
|
||||
if hit:
|
||||
return hit
|
||||
im = source(path)
|
||||
xs, ys = [p[0] for p in poly], [p[1] for p in poly]
|
||||
xs, ys = [p[0] for poly in polys for p in poly], [p[1] for poly in polys for p in poly]
|
||||
x0, y0 = max(0, int(math.floor(min(xs)))), max(0, int(math.floor(min(ys))))
|
||||
x1, y1 = min(im.width, int(math.ceil(max(xs)))), min(im.height, int(math.ceil(max(ys))))
|
||||
w, h = max(1, x1 - x0), max(1, y1 - y0)
|
||||
mask = Image.new("L", (w, h), 0)
|
||||
ImageDraw.Draw(mask).polygon([(x - x0, y - y0) for x, y in poly], fill=255)
|
||||
draw = ImageDraw.Draw(mask)
|
||||
for poly in polys:
|
||||
if len(poly) > 2:
|
||||
draw.polygon([(x - x0, y - y0) for x, y in poly], fill=255)
|
||||
piece = Image.composite(im.crop((x0, y0, x0 + w, y0 + h)), Image.new("RGB", (w, h), (0, 0, 0)), mask)
|
||||
out = io.BytesIO()
|
||||
piece.save(out, "WEBP", quality=82, method=1)
|
||||
|
||||
Reference in New Issue
Block a user