Phase 3d: game world decodes and renders -- offline and LIVE

The full DPL hierarchy the game uses (vs flyk's flat scene) is now decoded
and rendered:
- stride-aware set_geom_verts (header word 3 = floats/vertex: 3/4/5/8/9;
  mech meshes carry normals + UVs)
- instances are list_add children of DCS nodes; instance flush field 4 ->
  object; object->lod->geogroup->geometry; dcs_link builds the articulation
  tree of 4x4s (payload floats 4..19, row-major, row 3 = translation)
- game world is y-down (DCS matrices carry a reflection); projection flips
  x (Division mirror) and y

render_game.py reconstructs a captured game stream offline: the mission
arena (10km, 246 instances, 330 geometries), the player's Thor at the
camera, six enemy mechs 1.5km north -- game-mech-decoded.png shows one with
real hull/armor/glass materials; game-cockpit-decoded.png the cockpit view.

The live backend (vpxlog.cpp) gained the same traversal and now draws the
game's out-the-window view in real time (game-live-gl.png): sky, arena
floor to the horizon, own gun barrels at frame bottom.

Next: texturing (action-26 texel maps + UVs), lighting from wire normals,
per-frame articulation once the RIO drives the sim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-03 16:22:35 -05:00
co-authored by Claude Opus 4.8
parent 47fe174c6e
commit 99652d894b
7 changed files with 512 additions and 40 deletions
+3
View File
@@ -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
+48
View File
@@ -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
419 (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 <fifodump>` (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
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+307
View File
@@ -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 <dump> [-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("<I", hdr[4:])[0])
if len(d) >= 4:
msgs.append((struct.unpack("<I", d[:4])[0], d[4:]))
return msgs
def U(b):
return list(struct.unpack(f"<{len(b) // 4}I", b[: len(b) // 4 * 4]))
def F(b):
return list(struct.unpack(f"<{len(b) // 4}f", b[: len(b) // 4 * 4]))
def mat_mul(a, b):
"""4x4 (row-major, row-vector convention: v' = v @ M)"""
return [[sum(a[i][k] * b[k][j] for k in range(4)) for j in range(4)]
for i in range(4)]
def mat_id():
return [[1.0 if i == j else 0.0 for j in range(4)] for i in range(4)]
def xform(v, m):
x, y, z = v
return (x * m[0][0] + y * m[1][0] + z * m[2][0] + m[3][0],
x * m[0][1] + y * m[1][1] + z * m[2][1] + m[3][1],
x * m[0][2] + y * m[1][2] + z * m[2][2] + m[3][2])
class Scene:
def __init__(self):
self.types = {}
self.verts = {}
self.polys = {}
self.material = {}
self.gg_material = {}
self.children = {} # list_add: parent -> [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()
+154 -40
View File
@@ -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<VPoly> 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<unsigned, unsigned> type; /* name -> node type */
std::map<unsigned, std::vector<float> > verts; /* geometry -> xyz */
@@ -460,12 +463,53 @@ static struct VScene {
std::map<unsigned, VCol> mat; /* material -> RGB */
std::map<unsigned, unsigned> ggmat; /* geogroup -> material */
std::map<unsigned, std::vector<unsigned> > children;
/* game (full DPL) hierarchy: instance placement + articulation */
std::map<unsigned, unsigned> inst_object; /* instance -> object */
std::map<unsigned, M16> dcs_mat; /* dcs -> local matrix */
std::map<unsigned, unsigned> 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<float> 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<unsigned, M16> &cache, M16 &out,
int depth = 0) {
std::map<unsigned, M16>::iterator ci = cache.find(dcs);
if (ci != cache.end()) { out = ci->second; return; }
M16 local;
std::map<unsigned, M16>::iterator mi = S.dcs_mat.find(dcs);
if (mi != S.dcs_mat.end()) local = mi->second; else m16_id(local);
std::map<unsigned, unsigned>::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<unsigned, unsigned>::const_iterator gmi = S.ggmat.find(gg);
if (gmi != S.ggmat.end()) {
std::map<unsigned, VCol>::const_iterator mi = S.mat.find(gmi->second);
if (mi != S.mat.end()) col = mi->second;
}
std::map<unsigned, std::vector<unsigned> >::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<unsigned, std::vector<float> >::const_iterator vi =
S.verts.find(geo);
std::map<unsigned, std::vector<std::vector<int> > >::const_iterator
pi = S.polys.find(geo);
if (vi == S.verts.end() || pi == S.polys.end()) continue;
const std::vector<float> &vv = vi->second;
for (size_t q = 0; q < pi->second.size(); q++) {
const std::vector<int> &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<unsigned, unsigned>::const_iterator gi = S.ggmat.begin();
gi != S.ggmat.end(); ++gi) {
VCol col = { { 1.0f, 0.0f, 1.0f } }; /* missing = magenta */
std::map<unsigned, VCol>::const_iterator mi = S.mat.find(gi->second);
if (mi != S.mat.end()) col = mi->second;
std::map<unsigned, std::vector<unsigned> >::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<unsigned, std::vector<float> >::const_iterator vi =
S.verts.find(geo);
std::map<unsigned, std::vector<std::vector<int> > >::const_iterator
pi = S.polys.find(geo);
if (vi == S.verts.end() || pi == S.polys.end()) continue;
const std::vector<float> &vv = vi->second;
for (size_t q = 0; q < pi->second.size(); q++) {
const std::vector<int> &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<unsigned, M16> cache;
std::map<unsigned, bool> gg_done;
for (std::map<unsigned, std::vector<unsigned> >::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<unsigned, unsigned>::const_iterator oi =
S.inst_object.find(inst);
if (oi == S.inst_object.end()) continue;
std::map<unsigned, std::vector<unsigned> >::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<unsigned, std::vector<unsigned> >::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<unsigned, unsigned>::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<float> &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<float> &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<unsigned, unsigned>::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) {