120 lines
6.3 KiB
Python
120 lines
6.3 KiB
Python
# Usage: python3 tools/floorre/explain_t1l4m002.py tools/floorre OUT.png (WAD via env WAD, default t1l4m002 path in floor.py; needs Pillow, numpy)
|
||
# Explainer: Kuzco floating next to the crate in t1l4m002 (builds on floorre/plot.py)
|
||
import sys, math
|
||
sys.path.insert(0, sys.argv[1]) # floorre dir (floor.py, corners.py)
|
||
OUT = sys.argv[2]
|
||
sys.argv = sys.argv[:1]
|
||
from floor import * # mp, trak, floor_test
|
||
from corners import corners
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
SS = 2 # supersampling
|
||
F = lambda n, b=False: ImageFont.truetype('DejaVuSans-Bold.ttf' if b else 'DejaVuSans.ttf', n * SS)
|
||
K = (57.204, 2.474, 43.733)
|
||
T = 443
|
||
TX, TY, TZ, ROT, MI, _ = mp['A'][T]
|
||
recs = [r for g in trak[MI].coll for r in g]
|
||
|
||
def xf(B):
|
||
rot = B[1]; tx, ty, tz = B[3] / 4096, B[4] / 4096, B[5] / 4096
|
||
a = -rot / 4096 * 2 * math.pi; s, c = math.sin(a), math.cos(a)
|
||
return lambda p: (tx + c * p[0] - s * p[2], ty + p[1], tz + s * p[0] + c * p[2])
|
||
|
||
RED, BLUE, MAG, PINK = (215, 25, 25), (20, 90, 230), (210, 0, 210), (255, 70, 150)
|
||
|
||
def panel(X0, Z0, span, size, detail):
|
||
S = size / span
|
||
img = Image.new('RGB', (size * SS, size * SS), 'white'); d = ImageDraw.Draw(img, 'RGBA')
|
||
P = lambda x, z: ((x - X0) * S * SS, (size - (z - Z0) * S) * SS)
|
||
# rendered terrain coloured by height (like the agent's plot)
|
||
for t in range(mp['N']):
|
||
B = mp['B'][t]
|
||
if not (X0 - 2 <= B[3] / 4096 <= X0 + span + 2 and Z0 - 2 <= B[5] / 4096 <= Z0 + span + 2): continue
|
||
m = trak[mp['midx'][t]]; f = xf(B)
|
||
for fc in sorted(m.faces, key=lambda fc: sum(m.verts[v][1] for v in fc['v'])):
|
||
Q = [f((m.verts[v][0], m.verts[v][1], -m.verts[v][2])) for v in fc['v']]
|
||
h = sum(p[1] for p in Q) / len(Q); g = int(max(0, min(255, (h - 1.5) / 2 * 255)))
|
||
d.polygon([P(p[0], p[2]) for p in Q], fill=(g, 200 - g // 2, 255 - g, 255),
|
||
outline=(130, 130, 130) if not detail else None)
|
||
# cell grid
|
||
for k in range(int(X0) - 1, int(X0 + span) + 2):
|
||
d.line([P(k, Z0 - 1), P(k, Z0 + span + 1)], fill=(0, 140, 0), width=SS * (2 if detail else 1))
|
||
for k in range(int(Z0) - 1, int(Z0 + span) + 2):
|
||
d.line([P(X0 - 1, k), P(X0 + span + 1, k)], fill=(0, 140, 0), width=SS * (2 if detail else 1))
|
||
return img, d, P, S
|
||
|
||
def collision(d, P, labels, sliver=None, fsz=12):
|
||
f = xf(mp['B'][T])
|
||
if sliver:
|
||
for (x, z, w, i) in sliver:
|
||
a, b = P(x, z), P(x + w, z - w)
|
||
d.rectangle([a[0], a[1], b[0], b[1]], fill=PINK + (170,))
|
||
for i, r in enumerate(recs):
|
||
C = [f(p) for p in corners(r)]
|
||
col = RED if i in (11, 15) else BLUE if i == 2 else (20, 20, 20)
|
||
wid = 3 if i in (2, 11, 15) else 1
|
||
pts = [P(p[0], p[2]) for p in C]
|
||
if i in (11, 15): d.polygon(pts, fill=RED + (70,))
|
||
d.line(pts + pts[:1], fill=col, width=wid * SS)
|
||
if labels:
|
||
cx = sum(p[0] for p in pts) / len(pts); cy = sum(p[1] for p in pts) / len(pts)
|
||
lim = d.im.size
|
||
if not (10 * SS < cx < lim[0] - 10 * SS and 10 * SS < cy < lim[1] - (80 if labels == 'zoom' else 10) * SS):
|
||
continue
|
||
txt = f'poly {i} · mat {r[1]}' if labels == 'zoom' and i in (2, 11, 15) else str(i)
|
||
d.text((cx, cy), txt, fill=col, font=F(fsz, i in (2, 11, 15)), anchor='mm',
|
||
stroke_width=SS * 2, stroke_fill='white')
|
||
|
||
def sliver_cells(x0, z0, x1, z1, step):
|
||
out = []; tx, ty, tz = int(round(TX * 4096)), int(round(TY * 4096)), int(round(-TZ * 4096))
|
||
z = z0
|
||
while z < z1:
|
||
x = x0
|
||
while x < x1:
|
||
if int(x) == 57 and int(z) == 43:
|
||
_, res = floor_test(int(round(x * 4096)), int(round(2.27 * 4096)), int(round(z * 4096)), tx, ty, tz, ROT, recs)
|
||
best = None
|
||
for i, i7, ok, vals, tol, Y in res:
|
||
if ok and i7 >= -0x80 and (best is None or i7 < best[0]): best = (i7, i)
|
||
if best and best[1] in (11, 15): out.append((x, z, step, best[1]))
|
||
x += step
|
||
z += step
|
||
return out
|
||
|
||
W = 1500
|
||
# --- left: overview (3 x 3 cells) ---
|
||
ov, d, P, S = panel(56.0, 42.0, 3.0, 640, False)
|
||
collision(d, P, 'all', fsz=11)
|
||
for t in range(mp['N']):
|
||
B = mp['B'][t]; x, z = B[3] / 4096, B[5] / 4096
|
||
if 56 <= x <= 59 and 42 <= z <= 45:
|
||
d.text(P(x - 0.45, z - 0.38), f'tile {t}', fill=(110, 0, 140), font=F(12, True), stroke_width=SS * 2, stroke_fill='white')
|
||
k = P(K[0], K[2]); d.ellipse([k[0] - 7 * SS, k[1] - 7 * SS, k[0] + 7 * SS, k[1] + 7 * SS], fill=MAG, outline='black', width=SS)
|
||
ZX0, ZZ0, ZSP = 57.06, 43.16, 0.76
|
||
a, b = P(ZX0, ZZ0 + ZSP), P(ZX0 + ZSP, ZZ0)
|
||
d.rectangle([a, b], outline=(255, 140, 0), width=3 * SS)
|
||
ov = ov.resize((640, 640), Image.LANCZOS)
|
||
|
||
# --- right: zoom on the crate's side with both needles ---
|
||
zm, d, P, S = panel(ZX0, ZZ0, ZSP, 820, True)
|
||
collision(d, P, 'zoom', sliver=sliver_cells(ZX0, ZZ0, ZX0 + ZSP, ZZ0 + ZSP, 0.0012), fsz=15)
|
||
k = P(K[0], K[2]); r = 9 * SS
|
||
d.ellipse([k[0] - r, k[1] - r, k[0] + r, k[1] + r], fill=MAG, outline='black', width=2 * SS)
|
||
d.text((k[0], k[1] + 30 * SS), 'Kuzco\n57.204 / 43.733\n(measured live)', fill=MAG, font=F(15, True), anchor='ma',
|
||
align='center', stroke_width=SS * 2, stroke_fill='white')
|
||
tip = P(57.315, 43.729)
|
||
y = tip[1] - 34 * SS
|
||
d.line([(k[0], y), (tip[0], y)], fill='black', width=2 * SS)
|
||
for xq in (k[0], tip[0]): d.line([(xq, y - 8 * SS), (xq, y + 8 * SS)], fill='black', width=2 * SS)
|
||
d.text(((k[0] + tip[0]) / 2, y - 6 * SS), '0.11 outside poly 15', fill='black', font=F(14, True), anchor='md',
|
||
stroke_width=SS * 2, stroke_fill='white')
|
||
d.text((P(57.38, 43.80)), 'needle tips 13.5°', fill=RED, font=F(14, True), stroke_width=SS * 2, stroke_fill='white')
|
||
d.text((P(ZX0 + 0.01, ZZ0 + 0.035)), 'pink = allowance: where the engine still takes the needle as floor\n(edge tolerance ≈ 0.02 → ≈ 0.19 long, 0.01 wide at a 13.5° tip)',
|
||
fill=(190, 20, 110), font=F(14, True), stroke_width=SS * 2, stroke_fill='white')
|
||
zm = zm.resize((820, 820), Image.LANCZOS)
|
||
|
||
out = Image.new('RGB', (20 + 640 + 20 + 820 + 20, 820 + 40), 'white')
|
||
d = ImageDraw.Draw(out)
|
||
out.paste(ov, (20, 20 + 90)); out.paste(zm, (680, 20))
|
||
d.text((20, 20 + 60), 'Overview (3 × 3 cells), orange box = zoom →', fill='black', font=ImageFont.truetype('DejaVuSans-Bold.ttf', 17))
|
||
out.save(OUT); print('saved', OUT, out.size)
|