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:
+154
-40
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user