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:
@@ -6,6 +6,9 @@ image/
|
|||||||
!divrgb-decoded.png
|
!divrgb-decoded.png
|
||||||
!divrgb-frame0.png
|
!divrgb-frame0.png
|
||||||
!divrgb-live-gl.png
|
!divrgb-live-gl.png
|
||||||
|
!game-cockpit-decoded.png
|
||||||
|
!game-mech-decoded.png
|
||||||
|
!game-live-gl.png
|
||||||
dbx_out.txt
|
dbx_out.txt
|
||||||
vpx*.txt
|
vpx*.txt
|
||||||
sweep_*.txt
|
sweep_*.txt
|
||||||
|
|||||||
@@ -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
|
a 3b issue (the `flyk` clean-launch path renders fine); it is the same
|
||||||
production-sync item still open from Phase 2.
|
production-sync item still open from Phase 2.
|
||||||
|
|
||||||
|
## 3d. The game's own world decodes and renders — LIVE
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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 <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)
|
## 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
|
**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 |
@@ -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
@@ -443,16 +443,19 @@ struct VFrame {
|
|||||||
float nearp, farp;
|
float nearp, farp;
|
||||||
int vw, vh;
|
int vw, vh;
|
||||||
bool has_cam;
|
bool has_cam;
|
||||||
|
bool ydown; /* game world is y-down (DCS reflections) */
|
||||||
float rot[9], eye[3]; /* row-major rotation; eye = R*(p - e) */
|
float rot[9], eye[3]; /* row-major rotation; eye = R*(p - e) */
|
||||||
std::vector<VPoly> polys;
|
std::vector<VPoly> polys;
|
||||||
VFrame() : valid(false), nearp(2), farp(12000), vw(832), vh(512),
|
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;
|
bg[0] = bg[1] = bg[2] = 0;
|
||||||
win[0] = -1; win[1] = -0.6153846f; win[2] = 1; win[3] = 0.6153846f;
|
win[0] = -1; win[1] = -0.6153846f; win[2] = 1; win[3] = 0.6153846f;
|
||||||
win[4] = 1.3f;
|
win[4] = 1.3f;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct M16 { float m[16]; }; /* row-major 4x4, row 3 = translation */
|
||||||
|
|
||||||
static struct VScene {
|
static struct VScene {
|
||||||
std::map<unsigned, unsigned> type; /* name -> node type */
|
std::map<unsigned, unsigned> type; /* name -> node type */
|
||||||
std::map<unsigned, std::vector<float> > verts; /* geometry -> xyz */
|
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, VCol> mat; /* material -> RGB */
|
||||||
std::map<unsigned, unsigned> ggmat; /* geogroup -> material */
|
std::map<unsigned, unsigned> ggmat; /* geogroup -> material */
|
||||||
std::map<unsigned, std::vector<unsigned> > children;
|
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 */
|
VFrame view; /* view/bg/camera state */
|
||||||
/* multi-burst assembly */
|
/* multi-burst assembly (stride-aware: game verts are 3..9 floats each) */
|
||||||
unsigned geom_node; size_t geom_need; bool geom_active;
|
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;
|
unsigned conn_node, conn_npolys, conn_loop; bool conn_active;
|
||||||
} S;
|
} 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 ------------------------------------------------------ */
|
/* ---- render thread ------------------------------------------------------ */
|
||||||
static HANDLE rt_thread = NULL, rt_event = NULL;
|
static HANDLE rt_thread = NULL, rt_event = NULL;
|
||||||
static CRITICAL_SECTION rt_lock;
|
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);
|
glMatrixMode(GL_PROJECTION);
|
||||||
glLoadIdentity();
|
glLoadIdentity();
|
||||||
/* Division screen x runs opposite to GL eye x (SMPTE pattern comes
|
/* Division screen x runs opposite to GL eye x (SMPTE pattern comes
|
||||||
* out mirrored otherwise) -- flip x after projection. */
|
* out mirrored otherwise) -- flip x after projection. The game world
|
||||||
glScalef(-1.0f, 1.0f, 1.0f);
|
* 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,
|
glFrustum(f.win[0] * n / wd, f.win[2] * n / wd,
|
||||||
f.win[1] * n / wd, f.win[3] * n / wd, n, fa);
|
f.win[1] * n / wd, f.win[3] * n / wd, n, fa);
|
||||||
glMatrixMode(GL_MODELVIEW);
|
glMatrixMode(GL_MODELVIEW);
|
||||||
@@ -565,40 +610,79 @@ static DWORD WINAPI rt_main(LPVOID) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---- scene-graph message decode ----------------------------------------- */
|
/* ---- 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) {
|
static void scene_publish_frame(void) {
|
||||||
VFrame f = S.view;
|
VFrame f = S.view;
|
||||||
f.valid = true;
|
f.valid = true;
|
||||||
for (std::map<unsigned, unsigned>::const_iterator gi = S.ggmat.begin();
|
f.ydown = !S.dcs_mat.empty(); /* game path: world is y-down */
|
||||||
gi != S.ggmat.end(); ++gi) {
|
/* Game (full DPL) path: instances are list_add children of DCS nodes;
|
||||||
VCol col = { { 1.0f, 0.0f, 1.0f } }; /* missing = magenta */
|
* instance -> object -> lod -> geogroup -> geometry, transformed by the
|
||||||
std::map<unsigned, VCol>::const_iterator mi = S.mat.find(gi->second);
|
* dcs_link articulation tree. */
|
||||||
if (mi != S.mat.end()) col = mi->second;
|
std::map<unsigned, M16> cache;
|
||||||
std::map<unsigned, std::vector<unsigned> >::const_iterator ci =
|
std::map<unsigned, bool> gg_done;
|
||||||
S.children.find(gi->first);
|
for (std::map<unsigned, std::vector<unsigned> >::const_iterator di =
|
||||||
if (ci == S.children.end()) continue;
|
S.children.begin(); di != S.children.end(); ++di) {
|
||||||
for (size_t k = 0; k < ci->second.size(); k++) {
|
if (S.type.count(di->first) == 0 || S.type[di->first] != 5) continue;
|
||||||
unsigned geo = ci->second[k];
|
M16 world;
|
||||||
std::map<unsigned, std::vector<float> >::const_iterator vi =
|
dcs_world(di->first, cache, world);
|
||||||
S.verts.find(geo);
|
for (size_t i = 0; i < di->second.size(); i++) {
|
||||||
std::map<unsigned, std::vector<std::vector<int> > >::const_iterator
|
unsigned inst = di->second[i];
|
||||||
pi = S.polys.find(geo);
|
std::map<unsigned, unsigned>::const_iterator oi =
|
||||||
if (vi == S.verts.end() || pi == S.polys.end()) continue;
|
S.inst_object.find(inst);
|
||||||
const std::vector<float> &vv = vi->second;
|
if (oi == S.inst_object.end()) continue;
|
||||||
for (size_t q = 0; q < pi->second.size(); q++) {
|
std::map<unsigned, std::vector<unsigned> >::const_iterator li =
|
||||||
const std::vector<int> &idx = pi->second[q];
|
S.children.find(oi->second);
|
||||||
VPoly poly;
|
if (li == S.children.end() || li->second.empty()) continue;
|
||||||
memcpy(poly.rgb, col.c, sizeof poly.rgb);
|
unsigned lod = li->second[0]; /* highest LOD */
|
||||||
for (size_t j = 0; j < idx.size(); j++) {
|
std::map<unsigned, std::vector<unsigned> >::const_iterator ggi =
|
||||||
size_t o = (size_t)idx[j] * 3;
|
S.children.find(lod);
|
||||||
if (o + 2 >= vv.size()) continue;
|
if (ggi == S.children.end()) continue;
|
||||||
poly.xyz.push_back(vv[o]);
|
for (size_t g = 0; g < ggi->second.size(); g++) {
|
||||||
poly.xyz.push_back(vv[o + 1]);
|
emit_geogroup(f, ggi->second[g], &world);
|
||||||
poly.xyz.push_back(vv[o + 2]);
|
gg_done[ggi->second[g]] = true;
|
||||||
}
|
|
||||||
if (poly.xyz.size() >= 9) f.polys.push_back(poly);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* 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);
|
EnterCriticalSection(&rt_lock);
|
||||||
rt_pending = f;
|
rt_pending = f;
|
||||||
rt_new = true;
|
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 */
|
/* multi-burst payload continuations take priority over new headers */
|
||||||
if (S.geom_active && action == 23) {
|
if (S.geom_active && action == 23) {
|
||||||
std::vector<float> &vl = S.verts[S.geom_node];
|
for (size_t o = 0; o + 3 < nb; o += 4)
|
||||||
for (size_t o = 0; o + 11 < nb; o += 12) {
|
S.geom_acc.push_back(rd_f32(d + o));
|
||||||
vl.push_back(rd_f32(d + o));
|
if (S.geom_acc.size() >= S.geom_need * S.geom_stride) {
|
||||||
vl.push_back(rd_f32(d + o + 4));
|
std::vector<float> &vl = S.verts[S.geom_node];
|
||||||
vl.push_back(rd_f32(d + o + 8));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (S.conn_active && action == 25) {
|
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.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[0] = rd_f32(d + 60); S.view.bg[1] = rd_f32(d + 64);
|
||||||
S.view.bg[2] = rd_f32(d + 68);
|
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;
|
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] */
|
case 11: /* list_add [parent][child] */
|
||||||
if (nb >= 8) S.children[rd_u32(d)].push_back(rd_u32(d + 4));
|
if (nb >= 8) S.children[rd_u32(d)].push_back(rd_u32(d + 4));
|
||||||
break;
|
break;
|
||||||
case 23: /* set_geom_verts header */
|
case 23: /* set_geom_verts header: [name][0][n_verts][stride_floats].. */
|
||||||
if (nb >= 36) {
|
if (nb >= 36) {
|
||||||
S.geom_node = rd_u32(d);
|
S.geom_node = rd_u32(d);
|
||||||
S.geom_need = rd_u32(d + 8);
|
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_active = S.geom_need > 0;
|
||||||
|
S.geom_acc.clear();
|
||||||
S.verts[S.geom_node].clear();
|
S.verts[S.geom_node].clear();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -700,8 +812,10 @@ static void scene_burst(const unsigned char *p, size_t n) {
|
|||||||
static void scene_reset(void) {
|
static void scene_reset(void) {
|
||||||
S.type.clear(); S.verts.clear(); S.polys.clear(); S.mat.clear();
|
S.type.clear(); S.verts.clear(); S.polys.clear(); S.mat.clear();
|
||||||
S.ggmat.clear(); S.children.clear();
|
S.ggmat.clear(); S.children.clear();
|
||||||
|
S.inst_object.clear(); S.dcs_mat.clear(); S.dcs_parent.clear();
|
||||||
S.view = VFrame();
|
S.view = VFrame();
|
||||||
S.geom_active = false; S.conn_active = false;
|
S.geom_active = false; S.conn_active = false;
|
||||||
|
S.geom_acc.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void vpx_render_start(void) {
|
static void vpx_render_start(void) {
|
||||||
|
|||||||
Reference in New Issue
Block a user