#!/usr/bin/env python3 """Generates an example OBJ with completely new geometry for geom_import.py. geom_demo.py base.wad demo.obj [--plaza] Builds from tile quads (1x1 each, texture = whole texture tile, the way the engine wants it): - (--plaza) a floor plate at y=0 over all grid cells occupied in the base level (so strats/start point find floor somewhere) - a few blocks and a ramp in the middle of the occupied area Textures: most frequent texture on upward-facing faces (floor) and on side faces (wall). """ import argparse import collections import math import os import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import geomlib as G # noqa: E402 class ObjW: def __init__(self, texs): self.v, self.vt, self.f, self.texs = [], [], [], texs def quad(self, p, tex, mat=0): """p: 4 points, ordered so that (p1-p0)x(p2-p0) points outwards.""" U0, U1, V0, V1 = G.tex_uv(self.texs[tex]) uv = [(U1, V1), (U0, V1), (U0, V0), (U1, V0)] base = len(self.v) self.v += p self.vt += uv self.f.append((tex, mat, [base + k for k in range(4)])) def box(self, x0, z0, x1, z1, y0, y1, top, side): # export space: x0z1 (negative z = further into the grid); save() writes game space self.quad([(x0, y1, z0), (x1, y1, z0), (x1, y1, z1), (x0, y1, z1)], top) for (ax, az), (bx, bz) in [((x0, z0), (x0, z1)), ((x0, z1), (x1, z1)), ((x1, z1), (x1, z0)), ((x1, z0), (x0, z0))]: # wall from a to b, outside = clockwise n = math.ceil(y1 - y0) for k in range(n): ya, yb = y0 + k * (y1 - y0) / n, y0 + (k + 1) * (y1 - y0) / n self.quad([(ax, ya, az), (bx, ya, bz), (bx, yb, bz), (ax, yb, az)][::-1], side) def save(self, path): with open(path, 'w') as fo: for p in self.v: fo.write('v %.4f %.4f %.4f 1 1 1\n' % G.to_file(p)) for u, v in self.vt: fo.write('vt %.6f %.6f\n' % (u, 1 - v)) for tex, mat, ids in self.f: fo.write(f'g demo_mat{mat:02d}\nusemtl tex_{tex}\n') fo.write('f ' + ' '.join(f'{i + 1}/{i + 1}' for i in G.file_order(ids)) + '\n') def main(): ap = argparse.ArgumentParser() ap.add_argument('base'); ap.add_argument('out'); ap.add_argument('--plaza', action='store_true') a = ap.parse_args() ch, wf, trak, mp = G.load_level(a.base) pages, texs = G.parse_text(G.wad_get(ch, 'TEXT')) up, side = collections.Counter(), collections.Counter() for m in trak: for f in m.faces: t = texs[f['tex']] if t['flags'] & 1 or t['page'] < 0: continue (up if f['plane'][1] > 0.9 else side if abs(f['plane'][1]) < 0.2 else collections.Counter())[f['tex']] += 1 grass, wall = up.most_common(1)[0][0], side.most_common(1)[0][0] o = ObjW(texs) cells = sorted(set((math.floor(t[0]), math.floor(-t[2])) for t in mp['A'])) if a.plaza: for cx, cz in cells: o.quad([(cx, 0, -cz), (cx + 1, 0, -cz), (cx + 1, 0, -cz - 1), (cx, 0, -cz - 1)], grass) mx = sorted(c[0] for c in cells)[len(cells) // 2]; mz = sorted(c[1] for c in cells)[len(cells) // 2] if not a.plaza: # find a spot where the 9x4 window is as flat as possible and fully occupied top = collections.defaultdict(lambda: -99.0) for t in mp['A']: if trak[t[4]].faces: f, _ = G.tile_xform(*t[:4]) k = (math.floor(t[0]), math.floor(-t[2])) top[k] = max(top[k], max(f(*v[:3])[1] for v in trak[t[4]].verts)) best = None for (cx, cz) in list(top): win = [top.get((x, z)) for x in range(cx - 4, cx + 5) for z in range(cz - 1, cz + 3)] if None in win: continue spread = max(win) - min(win) if best is None or spread < best[0]: best = (spread, cx, cz) if best: mx, mz = best[1], best[2] # blocks + ramp around the centre; without --plaza placed on top of the existing geometry b = 0.0 if not a.plaza: ys = [] for t in mp['A']: if mx - 4 <= t[0] <= mx + 5 and mz - 1 <= -t[2] <= mz + 3 and trak[t[4]].faces: f, _ = G.tile_xform(*t[:4]) ys += [f(*v[:3])[1] for v in trak[t[4]].verts] b = float(round(sorted(ys)[len(ys) * 3 // 4] * 4) / 4) if ys else 0.0 o.box(mx, -mz, mx + 2, -mz - 2, b, b + 1, grass, wall) o.box(mx + 3, -mz, mx + 4, -mz - 1, b, b + 3, grass, wall) for k in range(4): # ramp in x direction, 4 cells long, rises to 1 y0, y1 = b + k / 4, b + (k + 1) / 4 x = mx - 4 + k o.quad([(x, y0, -mz), (x + 1, y1, -mz), (x + 1, y1, -mz - 2), (x, y0, -mz - 2)], grass) o.save(a.out) print(f'{a.out}: {len(o.f)} quads, floor texture {grass}, wall texture {wall}, centre cell ({mx},{mz})') if __name__ == '__main__': main()