130 lines
6.1 KiB
Python
130 lines
6.1 KiB
Python
# Draws notes/img/floor_explained_t1l4m002.png: floorviz.py <t1l4m002.wad> <out.png> (needs Pillow)
|
|
import sys
|
|
sys.path.insert(0, '/home/elias/projects/llamamod/tools')
|
|
import geomlib as G, floortest as F
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
WAD = sys.argv[1]; OUT = sys.argv[2]
|
|
ch, wf, trak, mp = G.load_level(WAD)
|
|
T = 443
|
|
x, y, z, rot, mi, _ = mp['A'][T]
|
|
f, _ = G.tile_xform(x, y, z, rot)
|
|
b = mp['B'][T]
|
|
recs = [r for g in trak[mp['midx'][T]].coll for r in g]
|
|
|
|
# world polygons (x, height, z) of up-facing floors of tile 443
|
|
polys = []
|
|
k = 0
|
|
for g, grp in enumerate(trak[mi].coll):
|
|
for r in grp:
|
|
c = G.coll_decode(r); pts = G.coll_polygon(c)
|
|
if len(pts) >= 3 and c['plane'][1] > 0.2:
|
|
W = [f(*p) for p in pts]; W = [(p[0], p[1], -p[2]) for p in W]
|
|
polys.append((k, c['mat'], c['plane'][1], W))
|
|
k += 1
|
|
|
|
KX, KY, KZ = 57.204, 2.474, 43.733
|
|
X0, X1, Z0, Z1 = 56.98, 57.86, 43.56, 43.86
|
|
S = 1700 # px per unit
|
|
PADL, PADT = 70, 120
|
|
w = int((X1 - X0) * S) + PADL + 40; h = int((Z1 - Z0) * S) + PADT + 60
|
|
def P(px, pz): return (PADL + (px - X0) * S, PADT + (Z1 - pz) * S)
|
|
|
|
img = Image.new('RGB', (w, h), 'white')
|
|
d = ImageDraw.Draw(img, 'RGBA')
|
|
try:
|
|
font = ImageFont.truetype('DejaVuSans.ttf', 18); fb = ImageFont.truetype('DejaVuSans-Bold.ttf', 20); fs = ImageFont.truetype('DejaVuSans.ttf', 15); ft = ImageFont.truetype('DejaVuSans-Bold.ttf', 24)
|
|
except Exception:
|
|
font = fb = fs = ft = ImageFont.load_default()
|
|
|
|
# accepted region of poly 15 at path height (exact engine test), sampled
|
|
ly = int(round((2.27 + 0) * 4096)) - b[4] + F.PROBE
|
|
step = 0.0015
|
|
zz = Z0
|
|
while zz < Z1:
|
|
xx = X0
|
|
while xx < X1:
|
|
px, pz = int(round(xx * 4096)), int(round(zz * 4096))
|
|
if (px >> 12, pz >> 12) == (b[3] >> 12, b[5] >> 12):
|
|
lx, lz = F.to_local(px, pz, b)
|
|
best = None
|
|
for i, r in enumerate(recs):
|
|
if r[1] == 0x11: continue
|
|
res = F.test_poly(r, lx, ly, lz)
|
|
if res is None or res[0] < -0x80: continue
|
|
if res[2] and (best is None or res[0] < best[0]): best = (res[0], i)
|
|
if best and best[1] == 15:
|
|
a = P(xx, zz); d.rectangle([a[0], a[1] - step * S, a[0] + step * S, a[1]], fill=(255, 120, 160, 150))
|
|
xx += step
|
|
zz += step
|
|
|
|
# polygons
|
|
for k, mat, ny, W in polys:
|
|
pts = [P(p[0], p[2]) for p in W]
|
|
if k == 15: col, wid, fill = (210, 0, 0), 4, (255, 0, 0, 40)
|
|
elif k == 2: col, wid, fill = (0, 90, 220), 3, (0, 90, 220, 25)
|
|
else: col, wid, fill = (150, 150, 150), 2, None
|
|
if fill: d.polygon(pts, fill=fill)
|
|
d.line(pts + [pts[0]], fill=col, width=wid)
|
|
cx = sum(p[0] for p in pts) / len(pts); cy = sum(p[1] for p in pts) / len(pts)
|
|
if X0 < sum(p[0] for p in W) / len(W) < X1 and Z0 < sum(p[2] for p in W) / len(W) < Z1:
|
|
lab = f'poly {k} · mat {mat}'
|
|
if k in (2, 15) or True:
|
|
d.text((cx - 50, cy - 10), lab, fill=col if k in (2, 15) else (110, 110, 110), font=fb if k in (2, 15) else fs)
|
|
|
|
# needle tip and Kuzco
|
|
TIPX, TIPZ = 57.315, 43.729
|
|
t = P(TIPX, TIPZ); kp = P(KX, KZ)
|
|
d.ellipse([kp[0] - 9, kp[1] - 9, kp[0] + 9, kp[1] + 9], fill=(200, 0, 200), outline='black')
|
|
d.text((kp[0] - 70, kp[1] + 14), 'Kuzco\nx 57.204 z 43.733', fill=(150, 0, 150), font=fb)
|
|
d.ellipse([t[0] - 5, t[1] - 5, t[0] + 5, t[1] + 5], fill=(210, 0, 0))
|
|
d.text((t[0] + 8, t[1] - 52), 'needle tip (13.5°)', fill=(210, 0, 0), font=font)
|
|
# distance Kuzco -> tip
|
|
d.line([kp[0], kp[1] - 40, t[0], t[1] - 40], fill='black', width=2)
|
|
for xq in (kp[0], t[0]): d.line([xq, kp[1] - 48, xq, kp[1] - 32], fill='black', width=2)
|
|
d.text(((kp[0] + t[0]) / 2 - 60, kp[1] - 70), '0.11 outside poly 15', fill='black', font=font)
|
|
# sliver length
|
|
s0 = P(57.125, 43.70)
|
|
d.line([s0[0], s0[1] + 55, t[0], s0[1] + 55], fill=(200, 60, 110), width=2)
|
|
for xq in (s0[0], t[0]): d.line([xq, s0[1] + 47, xq, s0[1] + 63], fill=(200, 60, 110), width=2)
|
|
d.text((s0[0] + 10, s0[1] + 62), 'invisible sliver: ~0.19 long, ~0.01 wide', fill=(200, 60, 110), font=font)
|
|
|
|
# title / legend
|
|
d.text((PADL, 18), 'Jungle Day 2 (t1l4m002) · tile 443 · top view of the floor collision next to the crate', fill='black', font=ft)
|
|
d.text((PADL, 56), 'red = poly 15 (steep needle triangle, mat 0) blue = poly 2 (path, mat 0) grey = other floor polygons '
|
|
'pink = where the engine still accepts poly 15 (edge tolerance)', fill=(60, 60, 60), font=fs)
|
|
d.text((PADL, 80), 'x →, z ↑, 1 unit = 1 grid cell (Kuzco is ~0.6 tall)', fill=(60, 60, 60), font=fs)
|
|
|
|
# side view
|
|
top = img
|
|
img = Image.new('RGB', (w, 520), 'white')
|
|
d = ImageDraw.Draw(img, 'RGBA')
|
|
oy = 30
|
|
d.text((PADL, oy - 10), 'Side view at Kuzco\'s position: which height the game picks', fill='black', font=ft)
|
|
base = oy + 440; hs = 520
|
|
def H(v): return base - (v - 2.15) * hs
|
|
xl, xr = PADL + 60, PADL + 900
|
|
d.line([xl, H(2.270), xr, H(2.270)], fill=(0, 90, 220), width=5)
|
|
d.text((xr + 10, H(2.270) - 12), 'poly 2 (path) under Kuzco: height 2.270', fill=(0, 90, 220), font=font)
|
|
d.line([xl, H(2.270) + 150 * 0, xl, H(2.270)], fill='white')
|
|
# extended plane of poly 15 (slope ny 0.51 -> steep), drawn as dashed line
|
|
for i in range(0, 840, 24):
|
|
d.line([xl + i, H(2.474), xl + i + 12, H(2.474)], fill=(210, 0, 0), width=4)
|
|
d.text((xr + 10, H(2.474) - 12), 'poly 15 plane extended to Kuzco: 2.474 ← chosen (highest)', fill=(210, 0, 0), font=font)
|
|
d.line([xl + 450, H(2.474), xl + 450, H(2.270)], fill='black', width=2)
|
|
d.text((xl + 460, (H(2.474) + H(2.270)) / 2 - 10), '0.20 in the air', fill='black', font=fb)
|
|
pr = 2.270 + 0.25
|
|
for i in range(0, 840, 10):
|
|
d.point((xl + i, H(2.474 + 0.25)), fill=(120, 120, 120))
|
|
d.text((xr + 10, H(2.474 + 0.25) - 10), 'probe = feet + 0.25 (floors up to here count)', fill=(110, 110, 110), font=fs)
|
|
# kuzco figure
|
|
kx = xl + 300
|
|
d.rectangle([kx - 18, H(2.474 + 0.6), kx + 18, H(2.474)], outline=(200, 0, 200), width=3)
|
|
d.text((kx - 30, H(2.474 + 0.6) - 26), 'Kuzco (~0.6)', fill=(150, 0, 150), font=font)
|
|
side = img
|
|
out = Image.new('RGB', (w, top.height + side.height + 10), (200, 200, 200))
|
|
out.paste(top, (0, 0)); out.paste(side, (0, top.height + 10))
|
|
out.save(OUT)
|
|
img = out
|
|
print('saved', OUT, img.size)
|