diff --git a/emulator/.gitignore b/emulator/.gitignore index 4dcd35b..e1eddc1 100644 --- a/emulator/.gitignore +++ b/emulator/.gitignore @@ -6,6 +6,9 @@ image/ !divrgb-decoded.png !divrgb-frame0.png !divrgb-live-gl.png +!game-cockpit-decoded.png +!game-mech-decoded.png +!game-live-gl.png dbx_out.txt vpx*.txt sweep_*.txt diff --git a/emulator/PHASE3-PROGRESS.md b/emulator/PHASE3-PROGRESS.md index ff9eb11..8cb40bd 100644 --- a/emulator/PHASE3-PROGRESS.md +++ b/emulator/PHASE3-PROGRESS.md @@ -112,6 +112,54 @@ before its first receive, which the POLL_THRESHOLD gating stalls. This is not a 3b issue (the `flyk` clean-launch path renders fine); it is the same production-sync item still open from Phase 2. +## 3d. The game's own world decodes and renders — LIVE + +![live GL window: BattleTech cockpit view](game-live-gl.png) + +The live backend now draws the game's actual out-the-window view in real +time: sky, the arena floor receding to the horizon, and the player's own gun +barrels at frame bottom (static scene — without a RIO the sim doesn't +advance). Same frame decoded offline below. + +![decoded BattleTech mech from the game's wire stream](game-mech-decoded.png) + +That is an enemy mech (object 1048, 488 verts) standing in the mission arena — +reconstructed **entirely from the game's captured FIFO stream** by +`render_game.py` (real hull/armor/cockpit-glass materials from the wire; the +half-buried look is the offline painter's-algorithm artifact, which the live +GL depth buffer doesn't have). `game-cockpit-decoded.png` is the actual +cockpit camera: the arena floor to the horizon with the player's own gun +barrels rising at frame bottom. + +What the game adds over flyk's flat scene (all now handled, offline + +live backend): + +- **Stride-aware vertices.** `set_geom_verts` header word 3 = floats per + vertex: 3 (xyz), 4, 5 (xyz+uv), 8 (xyz+normal+uv), 9. Mech meshes carry + normals and texture coordinates — lighting/texturing data is on the wire. +- **Full DPL hierarchy.** Instances are `list_add` children of DCS nodes + (dcs→instance); instance flush field 4 references the object (type 7); + object→lod→geogroup→geometry via list_add. `dcs_link` (action 7) builds the + articulation tree (mech torso/arms/legs), each DCS a 4×4 at payload floats + 4–19 (row-major, row 3 = translation). +- **The world is y-down** (the DCS matrices carry a y reflection; game coords + vs Division's). The renderer flips both x (Division mirror) and y. +- **Mission scene scale**: 10 km arena of 1000-unit ground tiles, 246 placed + instances, 330 geometries, 280 materials; the player's Thor (644 verts, + articulated sub-parts as sibling instances) sits exactly at the camera; + six enemy mechs stand ~1.5 km north. +- Camera: action 31 (rotation + eye at the cockpit position); with the sim + stalled (no RIO) it arrives once and the scene is static. + +Offline tool: `render_game.py ` (near-plane clipping, hierarchy +traversal). Fixture: a 954-frame capture. The live backend (`vpxlog.cpp`) +gained the same traversal: stride-aware geometry, dcs/instance/object link +decode, cached world transforms per DCS, y-down projection. + +Still to come: texturing (stride-8 UVs + action-26 texel maps), lighting from +the wire normals, per-frame articulation once the RIO drives the sim, LOD +selection by distance. + ## 3c. The full game runs through the live renderer (sync abort fixed) **The production `vr_sync` abort is fixed, and BattleTech v4.10 now runs diff --git a/emulator/game-cockpit-decoded.png b/emulator/game-cockpit-decoded.png new file mode 100644 index 0000000..70a8fb3 Binary files /dev/null and b/emulator/game-cockpit-decoded.png differ diff --git a/emulator/game-live-gl.png b/emulator/game-live-gl.png new file mode 100644 index 0000000..f49cc7a Binary files /dev/null and b/emulator/game-live-gl.png differ diff --git a/emulator/game-mech-decoded.png b/emulator/game-mech-decoded.png new file mode 100644 index 0000000..753c36d Binary files /dev/null and b/emulator/game-mech-decoded.png differ diff --git a/emulator/render_game.py b/emulator/render_game.py new file mode 100644 index 0000000..9c1b08e --- /dev/null +++ b/emulator/render_game.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python3 +"""Phase 3d: render a captured *game* VPX FIFO stream (full DPL hierarchy). + +Unlike flyk's flat DIVRGB scene (render_capture.py), the game builds the real +DPL graph: instances reference objects and DCS transform nodes; objects hold +LODs holding geogroups holding geometries; dcs_link builds an articulation +tree of 4x4 matrices (mech torso/legs/arms). This reconstructs that graph +from a VPX_FIFODUMP capture and renders the scene each draw_scene commits. + +Node types (empirical, Rel4.10 wire): 2=? 3=view 4=instance 5=dcs 6=material +(old) 7=object 8=lod 9=geogroup 10=geometry 11=material 12=texmap 13=? 14=? + +Usage: render_game.py [-o out.png] [--frame N] [--eye x,y,z] +""" +import struct +import sys + +from PIL import Image, ImageDraw + + +def read_messages(path): + msgs = [] + with open(path, "rb") as f: + while True: + hdr = f.read(8) + if len(hdr) < 8: + break + if hdr[:4] != b"VPXM": + raise SystemExit("bad magic") + d = f.read(struct.unpack("= 4: + msgs.append((struct.unpack(" [children] + self.inst_object = {} # instance -> object + self.inst_dcs = {} # instance -> dcs + self.dcs_mat = {} # dcs -> 4x4 local matrix + self.dcs_parent = {} # dcs child -> parent (dcs_link) + self.view = None + self.background = (0, 0, 0) + self.frames = [] + + +def parse_dcs_matrix(f): + """The 132-byte dcs flush payload: [name][type] then fields; the 4x4 is + the 16 floats starting at float index 4 (rows [x,y,z,0], row 3 = T).""" + m = [f[4 + r * 4: 8 + r * 4] for r in range(4)] + # sanity: last column should be ~(0,0,0,1) + if abs(m[3][3] - 1.0) > 0.5 and abs(m[0][3]) < 0.01: + # some builds put w=1 elsewhere; force affine + m[3][3] = 1.0 + for r in range(3): + m[r][3] = 0.0 + m[3][3] = 1.0 + return m + + +def reconstruct(msgs): + sc = Scene() + camera = None + geom_pend = None + conn_pend = None + for action, d in msgs: + if action == 1: + w = U(d) + if len(w) >= 2: + sc.types[w[1]] = w[0] + elif action == 3: + w = U(d) + if len(w) < 2: + continue + name, t = w[0], w[1] + if t == 11 and len(d) >= 92: + f = F(d) + sc.material[name] = tuple(f[12:15]) + elif t == 9 and len(d) >= 80: + sc.gg_material[name] = w[16] + elif t == 3 and len(d) >= 104: + f = F(d) + sc.view = (f[6], f[7], f[8], f[9], f[10], + f[11], f[12], f[13], f[14]) + sc.background = tuple(f[15:18]) + elif t == 5 and len(d) >= 132: + sc.dcs_mat[name] = parse_dcs_matrix(F(d)) + elif t == 4: + # instance: find object/dcs refs by node-type lookup + for val in w[2:]: + if val and val != 0xFFFFFFFF: + vt = sc.types.get(val) + if vt == 7: + sc.inst_object[name] = val + elif vt == 5: + sc.inst_dcs[name] = val + elif action == 7 and len(d) >= 8: # dcs_link parent -> child + w = U(d) + sc.dcs_parent[w[1]] = w[0] + elif action == 11 and len(d) >= 8: + w = U(d) + sc.children.setdefault(w[0], []).append(w[1]) + elif action == 23: + if geom_pend is None: + w = U(d) + if len(d) >= 36: + # header: [name][0][n_verts][stride_floats][n_msgs]... + geom_pend = [w[0], w[2], w[3], []] + sc.verts[w[0]] = [] + else: + name, n, stride, acc = geom_pend + acc.extend(F(d)) + if len(acc) >= n * stride: + sc.verts[name] = [ + (acc[i], acc[i + 1], acc[i + 2]) + for i in range(0, n * stride, stride)] + geom_pend = None + elif action == 25: + if conn_pend is None: + w = U(d) + if len(d) >= 16: + conn_pend = (w[0], w[1], w[2]) + sc.polys[w[0]] = [] + else: + name, n_polys, loop = conn_pend + idx = U(d) + pl = sc.polys[name] + if loop >= 2: + for i in range(0, len(idx), loop): + pl.append(idx[i:i + loop - 1]) + if len(pl) >= n_polys: + conn_pend = None + elif action == 31: + f = F(d) + camera = ([f[2:5], f[5:8], f[8:11]], f[11:14]) + elif action == 9: + sc.frames.append(camera) + return sc + + +def dcs_world(sc, dcs, cache, depth=0): + if dcs in cache: + return cache[dcs] + m = sc.dcs_mat.get(dcs, mat_id()) + p = sc.dcs_parent.get(dcs) + if p is not None and p != dcs and depth < 64: + m = mat_mul(m, dcs_world(sc, p, cache, depth + 1)) + cache[dcs] = m + return m + + +def gather_polys(sc): + """instance -> object -> lod -> geogroup -> geometry, transformed. + + Placement: instances are list_add children of DCS nodes (dcs -> instance); + dcs_link builds the dcs->dcs articulation tree above them.""" + inst_parent = {} + for parent, kids in sc.children.items(): + if sc.types.get(parent) == 5: + for k in kids: + if sc.types.get(k) == 4: + inst_parent[k] = parent + out = [] + cache = {} + for inst, obj in sc.inst_object.items(): + world = mat_id() + d = inst_parent.get(inst, sc.inst_dcs.get(inst)) + if d is not None: + world = dcs_world(sc, d, cache) + for lod in sc.children.get(obj, []): + # use only the first (highest-detail) LOD child set + ggs = sc.children.get(lod, []) + if not ggs: + continue + for gg in ggs: + rgb = sc.material.get(sc.gg_material.get(gg, -1), + (0.8, 0.2, 0.8)) + col = tuple(max(0, min(255, int(c * 255 + .5))) for c in rgb) + for geo in sc.children.get(gg, []): + vl = sc.verts.get(geo) + if not vl: + continue + wv = [xform(v, world) for v in vl] + for poly in sc.polys.get(geo, []): + pts = [wv[i] for i in poly if i < len(wv)] + if len(pts) >= 3: + out.append((pts, col)) + break # first LOD only + return out + + +def render(sc, frame, out, ss=2): + if sc.view: + wl, wb, wr, wt, wd, vw, vh, near, far = sc.view + vw, vh = int(vw), int(vh) + else: + wl, wb, wr, wt, wd = -1, -0.6154, 1, 0.6154, 1.732 + vw, vh, near, far = 832, 512, 0.25, 1150 + cam = sc.frames[frame] + if cam is None: + raise SystemExit("no camera at that frame") + rot, eye = cam + W, H = vw * ss, vh * ss + bg = tuple(max(0, min(255, int(c * 255 + .5))) for c in sc.background) + img = Image.new("RGB", (W, H), bg) + draw = ImageDraw.Draw(img) + + def project(p): + x, y, z = (p[0] - eye[0], p[1] - eye[1], p[2] - eye[2]) + ex = rot[0][0] * x + rot[0][1] * y + rot[0][2] * z + ey = rot[1][0] * x + rot[1][1] * y + rot[1][2] * z + ez = rot[2][0] * x + rot[2][1] * y + rot[2][2] * z + return ex, ey, ez + + def clip_near(evs, zn): + """Sutherland-Hodgman clip against eye-space plane z = -zn.""" + out = [] + n = len(evs) + for i in range(n): + a, b = evs[i], evs[(i + 1) % n] + ain, bin_ = a[2] <= -zn, b[2] <= -zn + if ain: + out.append(a) + if ain != bin_: + t = (-zn - a[2]) / (b[2] - a[2]) + out.append((a[0] + t * (b[0] - a[0]), + a[1] + t * (b[1] - a[1]), -zn)) + return out + + polys = gather_polys(sc) + print(f"gathered {len(polys)} world polys") + items = [] + for pts, col in polys: + evs = clip_near([project(p) for p in pts], near) + if len(evs) < 3: + continue + depth = sum(p[2] for p in evs) / len(evs) + scr = [] + for ex, ey, ez in evs: + # game world is y-down (DCS matrices carry a y reflection), so + # screen-up = -eye_y; screen-x mirrored as with flyk. + ndc_x = (-ex * wd / -ez - wl) / (wr - wl) + ndc_y = (-ey * wd / -ez - wb) / (wt - wb) + scr.append((ndc_x * W, (1 - ndc_y) * H)) + items.append((depth, scr, col)) + items.sort(key=lambda it: it[0]) + for _, scr, col in items: + draw.polygon(scr, fill=col) + if ss > 1: + img = img.resize((vw, vh), Image.LANCZOS) + img.save(out) + print(f"rendered frame {frame}: {len(items)} polys -> {out} ({vw}x{vh})") + + +def main(): + path = sys.argv[1] + out = "game_frame.png" + frame = -1 + if "-o" in sys.argv: + out = sys.argv[sys.argv.index("-o") + 1] + if "--frame" in sys.argv: + frame = int(sys.argv[sys.argv.index("--frame") + 1]) + msgs = read_messages(path) + sc = reconstruct(msgs) + print(f"{len(msgs)} msgs: {len(sc.verts)} geoms, {len(sc.material)} mats, " + f"{len(sc.inst_object)} placed instances, {len(sc.dcs_mat)} dcs, " + f"{len(sc.frames)} frames, view={sc.view}") + if "--eye" in sys.argv: + eye = [float(v) for v in sys.argv[sys.argv.index("--eye") + 1].split(",")] + sc.frames = [([[1, 0, 0], [0, 1, 0], [0, 0, 1]], eye)] + frame = 0 + render(sc, frame, out) + + +if __name__ == "__main__": + main() diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index 4cec028..ec98559 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -443,16 +443,19 @@ struct VFrame { float nearp, farp; int vw, vh; bool has_cam; + bool ydown; /* game world is y-down (DCS reflections) */ float rot[9], eye[3]; /* row-major rotation; eye = R*(p - e) */ std::vector polys; VFrame() : valid(false), nearp(2), farp(12000), vw(832), vh(512), - has_cam(false) { + has_cam(false), ydown(false) { bg[0] = bg[1] = bg[2] = 0; win[0] = -1; win[1] = -0.6153846f; win[2] = 1; win[3] = 0.6153846f; win[4] = 1.3f; } }; +struct M16 { float m[16]; }; /* row-major 4x4, row 3 = translation */ + static struct VScene { std::map type; /* name -> node type */ std::map > verts; /* geometry -> xyz */ @@ -460,12 +463,53 @@ static struct VScene { std::map mat; /* material -> RGB */ std::map ggmat; /* geogroup -> material */ std::map > children; + /* game (full DPL) hierarchy: instance placement + articulation */ + std::map inst_object; /* instance -> object */ + std::map dcs_mat; /* dcs -> local matrix */ + std::map dcs_parent; /* dcs_link child->parent */ VFrame view; /* view/bg/camera state */ - /* multi-burst assembly */ - unsigned geom_node; size_t geom_need; bool geom_active; + /* multi-burst assembly (stride-aware: game verts are 3..9 floats each) */ + unsigned geom_node; size_t geom_need, geom_stride; bool geom_active; + std::vector geom_acc; unsigned conn_node, conn_npolys, conn_loop; bool conn_active; } S; +static void m16_id(M16 &o) { + for (int i = 0; i < 16; i++) o.m[i] = (i % 5 == 0) ? 1.0f : 0.0f; +} +static void m16_mul(const M16 &a, const M16 &b, M16 &o) { /* o = a * b */ + for (int r = 0; r < 4; r++) + for (int c = 0; c < 4; c++) { + float s = 0; + for (int k = 0; k < 4; k++) s += a.m[r * 4 + k] * b.m[k * 4 + c]; + o.m[r * 4 + c] = s; + } +} +static void m16_xform(const M16 &w, const float *v, float *o) { + for (int c = 0; c < 3; c++) + o[c] = v[0] * w.m[c] + v[1] * w.m[4 + c] + v[2] * w.m[8 + c] + w.m[12 + c]; +} +/* world transform of a dcs: local * parent_world (row-vector convention) */ +static void dcs_world(unsigned dcs, std::map &cache, M16 &out, + int depth = 0) { + std::map::iterator ci = cache.find(dcs); + if (ci != cache.end()) { out = ci->second; return; } + M16 local; + std::map::iterator mi = S.dcs_mat.find(dcs); + if (mi != S.dcs_mat.end()) local = mi->second; else m16_id(local); + std::map::iterator pi = S.dcs_parent.find(dcs); + if (pi != S.dcs_parent.end() && pi->second != dcs && depth < 64) { + M16 pw; + dcs_world(pi->second, cache, pw, depth + 1); + M16 w; + m16_mul(local, pw, w); + out = w; + } else { + out = local; + } + cache[dcs] = out; +} + /* ---- render thread ------------------------------------------------------ */ static HANDLE rt_thread = NULL, rt_event = NULL; static CRITICAL_SECTION rt_lock; @@ -484,8 +528,9 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); /* Division screen x runs opposite to GL eye x (SMPTE pattern comes - * out mirrored otherwise) -- flip x after projection. */ - glScalef(-1.0f, 1.0f, 1.0f); + * out mirrored otherwise) -- flip x after projection. The game world + * is additionally y-down (its DCS matrices carry a reflection). */ + glScalef(-1.0f, f.ydown ? -1.0f : 1.0f, 1.0f); glFrustum(f.win[0] * n / wd, f.win[2] * n / wd, f.win[1] * n / wd, f.win[3] * n / wd, n, fa); glMatrixMode(GL_MODELVIEW); @@ -565,40 +610,79 @@ static DWORD WINAPI rt_main(LPVOID) { } /* ---- scene-graph message decode ----------------------------------------- */ +static void emit_geogroup(VFrame &f, unsigned gg, const M16 *world) { + VCol col = { { 1.0f, 0.0f, 1.0f } }; /* missing = magenta */ + std::map::const_iterator gmi = S.ggmat.find(gg); + if (gmi != S.ggmat.end()) { + std::map::const_iterator mi = S.mat.find(gmi->second); + if (mi != S.mat.end()) col = mi->second; + } + std::map >::const_iterator ci = + S.children.find(gg); + if (ci == S.children.end()) return; + for (size_t k = 0; k < ci->second.size(); k++) { + unsigned geo = ci->second[k]; + std::map >::const_iterator vi = + S.verts.find(geo); + std::map > >::const_iterator + pi = S.polys.find(geo); + if (vi == S.verts.end() || pi == S.polys.end()) continue; + const std::vector &vv = vi->second; + for (size_t q = 0; q < pi->second.size(); q++) { + const std::vector &idx = pi->second[q]; + VPoly poly; + memcpy(poly.rgb, col.c, sizeof poly.rgb); + for (size_t j = 0; j < idx.size(); j++) { + size_t o = (size_t)idx[j] * 3; + if (o + 2 >= vv.size()) continue; + float out[3]; + if (world) m16_xform(*world, &vv[o], out); + else { out[0] = vv[o]; out[1] = vv[o + 1]; out[2] = vv[o + 2]; } + poly.xyz.push_back(out[0]); + poly.xyz.push_back(out[1]); + poly.xyz.push_back(out[2]); + } + if (poly.xyz.size() >= 9) f.polys.push_back(poly); + } + } +} + static void scene_publish_frame(void) { VFrame f = S.view; f.valid = true; - for (std::map::const_iterator gi = S.ggmat.begin(); - gi != S.ggmat.end(); ++gi) { - VCol col = { { 1.0f, 0.0f, 1.0f } }; /* missing = magenta */ - std::map::const_iterator mi = S.mat.find(gi->second); - if (mi != S.mat.end()) col = mi->second; - std::map >::const_iterator ci = - S.children.find(gi->first); - if (ci == S.children.end()) continue; - for (size_t k = 0; k < ci->second.size(); k++) { - unsigned geo = ci->second[k]; - std::map >::const_iterator vi = - S.verts.find(geo); - std::map > >::const_iterator - pi = S.polys.find(geo); - if (vi == S.verts.end() || pi == S.polys.end()) continue; - const std::vector &vv = vi->second; - for (size_t q = 0; q < pi->second.size(); q++) { - const std::vector &idx = pi->second[q]; - VPoly poly; - memcpy(poly.rgb, col.c, sizeof poly.rgb); - for (size_t j = 0; j < idx.size(); j++) { - size_t o = (size_t)idx[j] * 3; - if (o + 2 >= vv.size()) continue; - poly.xyz.push_back(vv[o]); - poly.xyz.push_back(vv[o + 1]); - poly.xyz.push_back(vv[o + 2]); - } - if (poly.xyz.size() >= 9) f.polys.push_back(poly); + f.ydown = !S.dcs_mat.empty(); /* game path: world is y-down */ + /* Game (full DPL) path: instances are list_add children of DCS nodes; + * instance -> object -> lod -> geogroup -> geometry, transformed by the + * dcs_link articulation tree. */ + std::map cache; + std::map gg_done; + for (std::map >::const_iterator di = + S.children.begin(); di != S.children.end(); ++di) { + if (S.type.count(di->first) == 0 || S.type[di->first] != 5) continue; + M16 world; + dcs_world(di->first, cache, world); + for (size_t i = 0; i < di->second.size(); i++) { + unsigned inst = di->second[i]; + std::map::const_iterator oi = + S.inst_object.find(inst); + if (oi == S.inst_object.end()) continue; + std::map >::const_iterator li = + S.children.find(oi->second); + if (li == S.children.end() || li->second.empty()) continue; + unsigned lod = li->second[0]; /* highest LOD */ + std::map >::const_iterator ggi = + S.children.find(lod); + if (ggi == S.children.end()) continue; + for (size_t g = 0; g < ggi->second.size(); g++) { + emit_geogroup(f, ggi->second[g], &world); + gg_done[ggi->second[g]] = true; } } } + /* flyk (flat) path: geogroups with geometry directly, no instance */ + for (std::map::const_iterator gi = S.ggmat.begin(); + gi != S.ggmat.end(); ++gi) + if (!gg_done.count(gi->first)) emit_geogroup(f, gi->first, NULL); EnterCriticalSection(&rt_lock); rt_pending = f; rt_new = true; @@ -614,13 +698,20 @@ static void scene_burst(const unsigned char *p, size_t n) { /* multi-burst payload continuations take priority over new headers */ if (S.geom_active && action == 23) { - std::vector &vl = S.verts[S.geom_node]; - for (size_t o = 0; o + 11 < nb; o += 12) { - vl.push_back(rd_f32(d + o)); - vl.push_back(rd_f32(d + o + 4)); - vl.push_back(rd_f32(d + o + 8)); + for (size_t o = 0; o + 3 < nb; o += 4) + S.geom_acc.push_back(rd_f32(d + o)); + if (S.geom_acc.size() >= S.geom_need * S.geom_stride) { + std::vector &vl = S.verts[S.geom_node]; + vl.clear(); + for (size_t i = 0; i + 2 < S.geom_need * S.geom_stride; + i += S.geom_stride) { + vl.push_back(S.geom_acc[i]); + vl.push_back(S.geom_acc[i + 1]); + vl.push_back(S.geom_acc[i + 2]); + } + S.geom_acc.clear(); + S.geom_active = false; } - if (vl.size() / 3 >= S.geom_need) S.geom_active = false; return; } if (S.conn_active && action == 25) { @@ -656,17 +747,38 @@ static void scene_burst(const unsigned char *p, size_t n) { S.view.nearp = rd_f32(d + 52); S.view.farp = rd_f32(d + 56); S.view.bg[0] = rd_f32(d + 60); S.view.bg[1] = rd_f32(d + 64); S.view.bg[2] = rd_f32(d + 68); + } else if (t == 5 && nb >= 132) { /* dcs: 4x4 at f[4..19] */ + M16 &mm = S.dcs_mat[name]; + for (int i = 0; i < 16; i++) mm.m[i] = rd_f32(d + 16 + i * 4); + for (int r = 0; r < 3; r++) mm.m[r * 4 + 3] = 0.0f; + mm.m[15] = 1.0f; + } else if (t == 4) { /* instance: object ref */ + for (size_t o = 8; o + 3 < nb; o += 4) { + unsigned val = rd_u32(d + o); + if (val && val != 0xFFFFFFFFu) { + std::map::const_iterator ti = + S.type.find(val); + if (ti != S.type.end() && ti->second == 7) + S.inst_object[name] = val; + } + } } break; } + case 7: /* dcs_link [parent][child]: articulation tree */ + if (nb >= 8) S.dcs_parent[rd_u32(d + 4)] = rd_u32(d); + break; case 11: /* list_add [parent][child] */ if (nb >= 8) S.children[rd_u32(d)].push_back(rd_u32(d + 4)); break; - case 23: /* set_geom_verts header */ + case 23: /* set_geom_verts header: [name][0][n_verts][stride_floats].. */ if (nb >= 36) { S.geom_node = rd_u32(d); S.geom_need = rd_u32(d + 8); + S.geom_stride = rd_u32(d + 12); + if (S.geom_stride < 3 || S.geom_stride > 16) S.geom_stride = 3; S.geom_active = S.geom_need > 0; + S.geom_acc.clear(); S.verts[S.geom_node].clear(); } break; @@ -700,8 +812,10 @@ static void scene_burst(const unsigned char *p, size_t n) { static void scene_reset(void) { S.type.clear(); S.verts.clear(); S.polys.clear(); S.mat.clear(); S.ggmat.clear(); S.children.clear(); + S.inst_object.clear(); S.dcs_mat.clear(); S.dcs_parent.clear(); S.view = VFrame(); S.geom_active = false; S.conn_active = false; + S.geom_acc.clear(); } static void vpx_render_start(void) {