"""Generate per-tile ASCII walkmeshes (`twu01_.wok`) from tiles.py. python make_wok.py [outdir] # default: ./wok Pure Python, no Blender. Emits the Aurora ASCII walkmesh node (verts + faces with per-face surface material + an AABB bounding-volume tree). This is the usable intermediate the toolset/cleanmodels/nwnmdlcomp compile to a binary `.wok` (AGENTS.md §6); NeverBlender would otherwise produce this. Geometry comes from tiles.wok_quads so it matches the Blender WOK_/BLOCK_ proxies exactly. Surface material ids (surfacemat.2da, AGENTS.md §3.3): walkable faces use WALK, non-walkable proxy faces use BLOCK. Verify these two ids against your surfacemat.2da before shipping. """ import os import sys import tiles WALK = 1 # a walkable stone-ish row in surfacemat.2da (verify) BLOCK = 7 # a non-walkable row (verify) def geometry(spec): """Return (verts, faces): dedup verts, faces = (a, b, c, surfacemat).""" verts, vmap, faces = [], {}, [] def vid(v): key = tuple(round(c, 4) for c in v) if key not in vmap: vmap[key] = len(verts) verts.append(key) return vmap[key] for quad, walkable in tiles.wok_quads(spec): s = WALK if walkable else BLOCK a, b, c, d = (vid(p) for p in quad) faces.append((a, b, c, s)) # triangulate quad -> 2 tris faces.append((a, c, d, s)) return verts, faces def _box(verts, face): pts = [verts[face[0]], verts[face[1]], verts[face[2]]] return ([min(p[i] for p in pts) for i in range(3)], [max(p[i] for p in pts) for i in range(3)]) def aabb_tree(verts, faces): """Binary BVH, pre-order (node, left subtree, right subtree). Leaf carries its face index; internal carries -1. Reader reconstructs by the same recursion.""" nodes = [] def rec(items): # items = [(face_index, (min, max)), ...] mn = [min(b[0][i] for _, b in items) for i in range(3)] mx = [max(b[1][i] for _, b in items) for i in range(3)] if len(items) == 1: nodes.append((mn, mx, items[0][0])) return ax = max(range(3), key=lambda i: mx[i] - mn[i]) # split longest axis items.sort(key=lambda ib: ib[1][0][ax] + ib[1][1][ax]) # by box centre mid = len(items) // 2 nodes.append((mn, mx, -1)) # internal placeholder rec(items[:mid]) rec(items[mid:]) rec([(i, _box(verts, f)) for i, f in enumerate(faces)]) assert sum(1 for _, _, fi in nodes if fi >= 0) == len(faces) # every face is a leaf assert len(nodes) == 2 * len(faces) - 1 # full binary tree return nodes def emit(spec): verts, faces = geometry(spec) nodes = aabb_tree(verts, faces) name = tiles.resref(spec) L = [f"# {name} walkmesh (ASCII, first-pass) - compile via nwnmdlcomp/cleanmodels", f"node aabb {name}_wok", f" parent {name}", " position 0.0 0.0 0.0", " orientation 0.0 0.0 0.0 0.0", f" verts {len(verts)}"] L += [f" {v[0]:.5f} {v[1]:.5f} {v[2]:.5f}" for v in verts] L.append(f" faces {len(faces)}") # face line: v1 v2 v3 smoothgroup tv1 tv2 tv3 surfacemat L += [f" {a} {b} {c} 1 0 0 0 {s}" for a, b, c, s in faces] L.append(" aabb") L += [f" {mn[0]:.5f} {mn[1]:.5f} {mn[2]:.5f} {mx[0]:.5f} {mx[1]:.5f} {mx[2]:.5f} {fi}" for mn, mx, fi in nodes] L.append("endnode") return name, "\n".join(L) + "\n" def main(): outdir = sys.argv[1] if len(sys.argv) > 1 else "wok" os.makedirs(outdir, exist_ok=True) for spec in tiles.TILES: name, text = emit(spec) with open(os.path.join(outdir, name + ".wok"), "w") as fh: fh.write(text) print(f"wrote {len(tiles.TILES)} .wok files to {outdir}/") if __name__ == "__main__": main()