Weapons fire; hat-glance + MFD fixes; windowless bridge

vpx-device/vpxlog.cpp -- WEAPONS FIRE. The game's fire gate is
targetReticle.targetEntity (mech+0x388), filled every frame by a Division-board
reticle pick (dpl_RapidSectPixel) our HLE never answered, so every shot
misfired. The device now casts the camera centre ray against the live scene
(Moller-Trumbore) and returns the full hit -- instance + DCS + geogroup +
geometry -- piggybacked on the draw_scene reply; terrain is a valid target so
you can fire and miss. Sending geogroup=0 was hanging the game (GetAppSpecific
on a null); returning the real geogroup fixed it. Pick is default-on with a
single VPX_NO_PICK escape hatch. Also swaps the upper-left/right MFD explode
windows (screen location only -- decode untouched, real cause TBD on a pod).

dpl3-revive/patha/vrview.py -- HAT-GLANCE fix at the source. One cockpit DCS
(0xa2c) flushes a rank-2 rotation (shifted body -> wrong read window) that
collapsed the camera chain to rank 2 and smeared the head glance onto the wrong
axis (left/right hat read as pitch). chain_matrix(fix_degenerate) treats a
degenerate chain DCS as identity: preserves the look exactly and keeps stick-Y
torso pitch working (an earlier bridge-level yaw/pitch swap broke stick-Y and
was reverted). User-verified live: hat all 4 dirs + stick-Y both correct.

render-bridge/launch_pod.ps1 -- launch the bridge with pyw (windowless) so no
console window parks over the cockpit displays (Start-Process ignores
-WindowStyle once stdout/stderr are redirected).

render-bridge/live_bridge.py -- surface bridge render errors, flush the status
line; reverted glance-swap note.

Also vendored HUD (dpl2d) renderer work in vrboard/vrview_gl and RENDERER-COLLAB
/ RIO notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-07 09:53:14 -05:00
co-authored by Claude Opus 4.8
parent c18c253658
commit 2bb2ff7302
8 changed files with 909 additions and 51 deletions
+354 -17
View File
@@ -30,6 +30,16 @@
* pure host->board writes; the device just absorbs them.
*/
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h> /* VPX_FIFOSOCK live tee; must precede windows.h */
#endif
#include "dosbox.h"
#include "inout.h"
#include "logging.h"
@@ -51,6 +61,21 @@ static int vpx_max_handshakes = 3;
static unsigned char in_fifo[64];
static int in_len = 0, in_pos = 0;
/* ---- reticle pick (weapons-fire target gate) ---------------------------- *
* The game runs a continuous reticle intersection test: it arms it once with
* set_sect_pixel (action 38) at screen (0.5,0.5), then every frame reads the
* result -- hit instance + DCS + 3D intersection point -- which the board is
* expected to return piggybacked on the draw_scene (action 9) reply. The host
* (dpl_RapidSectPixel @0x4903c8) copies that into globals; targetReticle.
* targetEntity = the hit DCS's app-specific pointer. That entity IS the fire
* gate the weapon state machine checks (mech+0x388): no hit => targetEntity
* NULL => every weapon misfires ("boo-beep"). A real i860 answered this each
* frame; our HLE never did. We now carry a real scene hit in the frame ack. */
static bool sect_armed = false;
/* Real reticle raycast (Moller-Trumbore against the live scene, center ray). */
static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out,
unsigned *geom_out, float xyz_out[3]);
/* ---- boot state machine ------------------------------------------------- */
enum Phase { P_INIT, P_HANDSHAKE, P_POSTBOOT };
static Phase phase = P_INIT;
@@ -148,8 +173,107 @@ static bool vpx_render = false; /* Phase 3b live GL backend */
static void scene_burst(const unsigned char *p, size_t n); /* fwd (3b) */
static void scene_reset(void); /* fwd (3b) */
/* ---- live socket tee (VPX_FIFOSOCK=<port>) ------------------------------ *
* Streams the same VPXM records to one localhost TCP client (the dpl3-revive
* bridge) with TCP_NODELAY -- removes the fifodump file-poll quantum from the
* wire-to-photon path. All calls are non-blocking on the emulation thread: a
* stalled client gets a bounded pending buffer, then is dropped (the bridge
* reconnects; vehicle poses are absolute so a brief gap self-heals). */
#ifdef _WIN32
static SOCKET fifo_lsock = INVALID_SOCKET; /* listener */
static SOCKET fifo_csock = INVALID_SOCKET; /* single client */
static unsigned char *fifo_pend = NULL; /* client-stalled unsent tail */
static size_t fifo_pend_len = 0, fifo_pend_cap = 0;
static bool fifo_sock_active(void) { return fifo_lsock != INVALID_SOCKET; }
static void fifo_sock_init(int port) {
WSADATA wd;
if (WSAStartup(MAKEWORD(2, 2), &wd) != 0) return;
fifo_lsock = socket(AF_INET, SOCK_STREAM, 0);
if (fifo_lsock == INVALID_SOCKET) return;
sockaddr_in a; memset(&a, 0, sizeof a);
a.sin_family = AF_INET;
a.sin_port = htons((u_short)port);
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
u_long nb = 1;
if (bind(fifo_lsock, (sockaddr *)&a, sizeof a) != 0 ||
listen(fifo_lsock, 1) != 0 ||
ioctlsocket(fifo_lsock, FIONBIO, &nb) != 0) {
closesocket(fifo_lsock); fifo_lsock = INVALID_SOCKET;
LOG_MSG("VPXLOG: fifosock: cannot listen on 127.0.0.1:%d", port);
return;
}
LOG_MSG("VPXLOG: fifosock listening on 127.0.0.1:%d", port);
}
static void fifo_sock_drop(void) {
if (fifo_csock != INVALID_SOCKET) closesocket(fifo_csock);
fifo_csock = INVALID_SOCKET;
fifo_pend_len = 0;
}
static void fifo_sock_accept(void) {
if (fifo_lsock == INVALID_SOCKET) return;
SOCKET c = accept(fifo_lsock, NULL, NULL);
if (c == INVALID_SOCKET) return; /* WOULDBLOCK: nobody waiting */
fifo_sock_drop();
u_long nb = 1; ioctlsocket(c, FIONBIO, &nb);
int v = 1;
setsockopt(c, IPPROTO_TCP, TCP_NODELAY, (const char *)&v, sizeof v);
v = 1 << 20;
setsockopt(c, SOL_SOCKET, SO_SNDBUF, (const char *)&v, sizeof v);
fifo_csock = c;
LOG_MSG("VPXLOG: fifosock client connected");
}
static void fifo_pend_stash(const unsigned char *p, size_t n) {
if (fifo_pend_len + n > (8u << 20)) { fifo_sock_drop(); return; }
if (fifo_pend_len + n > fifo_pend_cap) {
size_t ncap = fifo_pend_cap ? fifo_pend_cap : 65536;
while (ncap < fifo_pend_len + n) ncap *= 2;
unsigned char *np = (unsigned char *)realloc(fifo_pend, ncap);
if (np == NULL) { fifo_sock_drop(); return; }
fifo_pend = np; fifo_pend_cap = ncap;
}
memcpy(fifo_pend + fifo_pend_len, p, n);
fifo_pend_len += n;
}
static void fifo_sock_write(const unsigned char *p, size_t n) {
if (fifo_csock == INVALID_SOCKET) return;
/* drain any stalled tail first (framing must stay contiguous) */
while (fifo_pend_len) {
int r = send(fifo_csock, (const char *)fifo_pend,
(int)(fifo_pend_len > 65536 ? 65536 : fifo_pend_len), 0);
if (r > 0) {
memmove(fifo_pend, fifo_pend + r, fifo_pend_len - (size_t)r);
fifo_pend_len -= (size_t)r;
} else if (r == SOCKET_ERROR &&
WSAGetLastError() == WSAEWOULDBLOCK) {
fifo_pend_stash(p, n); return;
} else { fifo_sock_drop(); return; }
}
size_t off = 0;
while (off < n) {
int r = send(fifo_csock, (const char *)(p + off),
(int)((n - off) > 65536 ? 65536 : (n - off)), 0);
if (r > 0) off += (size_t)r;
else if (r == SOCKET_ERROR &&
WSAGetLastError() == WSAEWOULDBLOCK) {
fifo_pend_stash(p + off, n - off); return;
} else { fifo_sock_drop(); return; }
}
}
#else
static bool fifo_sock_active(void) { return false; }
static void fifo_sock_init(int) {}
static void fifo_sock_accept(void) {}
static void fifo_sock_write(const unsigned char *, size_t) {}
#endif
static void fifo_buf_push(unsigned char v) {
if (fifo_dump_fp == NULL && !vpx_render) return;
if (fifo_dump_fp == NULL && !vpx_render && !fifo_sock_active()) return;
if (fifo_buf_len >= (1u << 20)) return; /* runaway guard */
if (fifo_buf_len == fifo_buf_cap) {
size_t ncap = fifo_buf_cap ? fifo_buf_cap * 2 : 4096;
@@ -161,14 +285,19 @@ static void fifo_buf_push(unsigned char v) {
}
static void fifo_flush_record(void) {
if (fifo_buf_len == 0) return;
unsigned char hdr[8] = { 'V','P','X','M',
(unsigned char)(fifo_buf_len), (unsigned char)(fifo_buf_len >> 8),
(unsigned char)(fifo_buf_len >> 16), (unsigned char)(fifo_buf_len >> 24) };
if (fifo_dump_fp) {
unsigned char hdr[8] = { 'V','P','X','M',
(unsigned char)(fifo_buf_len), (unsigned char)(fifo_buf_len >> 8),
(unsigned char)(fifo_buf_len >> 16), (unsigned char)(fifo_buf_len >> 24) };
fwrite(hdr, 1, sizeof hdr, fifo_dump_fp);
fwrite(fifo_buf, 1, fifo_buf_len, fifo_dump_fp);
fflush(fifo_dump_fp);
}
if (fifo_sock_active()) {
fifo_sock_accept();
fifo_sock_write(hdr, sizeof hdr);
fifo_sock_write(fifo_buf, fifo_buf_len);
}
if (vpx_render) scene_burst(fifo_buf, fifo_buf_len);
fifo_buf_len = 0;
}
@@ -238,6 +367,42 @@ static void queue_render_ack_node(unsigned char action, unsigned node) {
}
static void queue_render_ack(unsigned char action) { queue_render_ack_node(action, 0); }
static void wr_u32(unsigned char *p, unsigned v) {
p[0] = (unsigned char)v; p[1] = (unsigned char)(v >> 8);
p[2] = (unsigned char)(v >> 16); p[3] = (unsigned char)(v >> 24);
}
/* A draw_scene (action 9) reply that also carries the reticle sect result, so
* velocirender_receive stores a live hit (see 0x491d08 / 0x4922e0 in BTL4OPT).
* The receive loop copies payload+4 into a host buffer; the store handler then
* reads instance @buf+0xc, xi/yi/zi @buf+0x10, DCS @buf+0x1c, gg @buf+0x20,
* geom @buf+0x24. In payload terms (buf == payload+4): action@0x00,
* instance@0x10, xyz@0x14, DCS@0x20, gg@0x24, geom@0x28. length_word = the
* payload byte count (matches queue_render_ack_node's nb=8 convention). */
static void queue_sect_frame_reply(unsigned inst, unsigned dcs,
unsigned gg, unsigned geom,
const float xyz[3]) {
fifo_flush_record(); /* a receive means the outstanding burst is done */
unsigned char *m = in_fifo;
memset(m, 0, sizeof in_fifo);
const unsigned nb = 0x2c; /* payload = action(4) + 40 = 44 bytes */
wr_u32(m, nb); /* length_word (non-iserver: nb) */
unsigned char *pl = m + 4;
wr_u32(pl + 0x00, 9); /* action = vr_draw_scene_action */
/* pl+0x04/0x08/0x0c: debug echo words (left zero) */
wr_u32(pl + 0x10, inst); /* hit instance handle (type 4) */
unsigned u;
memcpy(&u, &xyz[0], 4); wr_u32(pl + 0x14, u); /* xi */
memcpy(&u, &xyz[1], 4); wr_u32(pl + 0x18, u); /* yi */
memcpy(&u, &xyz[2], 4); wr_u32(pl + 0x1c, u); /* zi */
wr_u32(pl + 0x20, dcs); /* hit DCS handle (type 5) */
wr_u32(pl + 0x24, gg); /* hit geogroup handle (type 9) --
* the game does GetAppSpecific(gg) on
* a hit; sending 0 handed it a null. */
wr_u32(pl + 0x28, geom); /* hit geometry handle (type 0xa) */
in_len = (int)(4 + nb); /* 48 bytes */
in_pos = 0;
}
/* ---- logging (run-length coalesced) ------------------------------------- */
static unsigned long vpx_seq = 0;
static io_port_t last_port = 0xFFFF; static unsigned last_val = ~0u;
@@ -332,13 +497,32 @@ static Bitu vpx_read(Bitu port, Bitu /*iolen*/) {
fprintf(vpx_fp, "# post-boot: sync reply token=0x%X\n",
sync_token); fflush(vpx_fp); }
} else if (frame_outstanding) {
/* velocirender_frameack expects a message with
* action == vr_draw_scene_action (9). */
/* velocirender_frameack expects a draw_scene (9)
* reply; we carry the reticle sect result on it
* so weapons acquire a target (else every shot
* misfires). The pick runs whenever the game has
* armed the reticle (sect_armed); VPX_NO_PICK=1
* is the only escape hatch (falls back to the
* plain ack -- the pre-pick baseline). */
frame_outstanding = false;
queue_render_ack(9);
if (vpx_fp) { flush_run();
fprintf(vpx_fp, "# post-boot: frame ack (action 9)\n");
fflush(vpx_fp); }
static int no_pick = -1;
if (no_pick < 0)
no_pick = getenv("VPX_NO_PICK") ? 1 : 0;
unsigned pinst = 0, pdcs = 0, pgg = 0, pgeom = 0;
float pxyz[3] = { 0, 0, 0 };
if (!no_pick && sect_armed &&
raycast_pick(&pinst, &pdcs, &pgg, &pgeom, pxyz)) {
queue_sect_frame_reply(pinst, pdcs, pgg, pgeom, pxyz);
if (vpx_fp) { flush_run();
fprintf(vpx_fp, "# post-boot: frame ack "
"(9) + sect inst=%08X dcs=%08X\n",
pinst, pdcs); fflush(vpx_fp); }
} else {
queue_render_ack(9);
if (vpx_fp) { flush_run();
fprintf(vpx_fp, "# post-boot: frame ack (action 9)\n");
fflush(vpx_fp); }
}
} else {
/* Reply action is handler-specific (board side,
* VR_REMOT.C): most echo data[0] (the sent
@@ -784,8 +968,19 @@ static void rsh_env(void) {
}
static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
/* VPX_CLEAR="r,g,b" (0-1 floats) overrides the wire back_color clear.
* Black makes decode gaps honest -- the game's sky-blue back_color reads
* as terrain-to-the-horizon when instances are missing. */
static int has_clear = -1;
static float cc[3];
if (has_clear < 0) {
const char *cv = getenv("VPX_CLEAR");
has_clear = (cv && sscanf(cv, "%f,%f,%f",
&cc[0], &cc[1], &cc[2]) == 3) ? 1 : 0;
}
glViewport(0, 0, cw, ch);
glClearColor(f.bg[0], f.bg[1], f.bg[2], 1.0f);
if (has_clear) glClearColor(cc[0], cc[1], cc[2], 1.0f);
else glClearColor(f.bg[0], f.bg[1], f.bg[2], 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (f.has_cam && !f.polys.empty()) {
double n = f.nearp > 0 ? f.nearp : 2.0;
@@ -1187,13 +1382,20 @@ static DWORD WINAPI rt_main(LPVOID) {
bool cockpit = (ck && ck[0] && ck[0] != '0');
const char *ex = getenv("VPX_EXPLODE");
bool explode = !cockpit && ex && ex[0] && ex[0] != '0';
/* VPX_NOMAIN=1: no native Division window (the dpl3-revive bridge is the
* out-the-window view; ours stays available as a wire-decode diagnostic).
* Radar/MFD windows are unaffected; the main slot geometry still anchors
* the DOSBox parking below. */
const char *nm = getenv("VPX_NOMAIN");
bool nomain = nm && nm[0] && nm[0] != '0';
HWND wnd; HDC dc; HGLRC gl;
HWND wnd = NULL; HDC dc = NULL; HGLRC gl = NULL;
int mx = 40, my = 40, mw = 832, mh = 512;
if (cockpit) { mx = 0; my = 0; mw = 800; mh = 600; }
else if (explode) { mx = 2020; my = 20; mw = 800; mh = 600; }
env_rect("VPX_MAIN", &mx, &my, &mw, &mh);
if (!make_gl_window("VPX VelociRender (emulated)", mw, mh, mx, my,
if (!nomain &&
!make_gl_window("VPX VelociRender (emulated)", mw, mh, mx, my,
cockpit, &wnd, &dc, &gl)) return 1;
/* Display windows: win0 = bits 0-7 via pal0 (color radar); win3/win4 =
@@ -1218,8 +1420,14 @@ static DWORD WINAPI rt_main(LPVOID) {
* lower MFDs in the outer columns (LR aligned under UR) with the radar
* centered between them; main in the right-hand column (mx/my above). */
pal_radar_cw = explode;
static const int ex_x[10] = { 760, 0,0,0,0, 20, 1340, 20, 680, 1340 };
static const int ex_y[10] = { 560, 0,0,0,0, 560, 560, 20, 20, 20 };
/* win7 (UL-decoded) and win9 (UR-decoded) trade screen positions: the two
* upper-outer MFDs read backward on the desktop (user 2026-07-07). This is
* a LOCATION swap only -- the decode (channel/palette) is untouched; whether
* the underlying cause is a color-translation or pentapus-cable order is
* still TBD and must be checked on a real pod. So win7 sits on the right
* (x=1340), win9 on the left (x=20). */
static const int ex_x[10] = { 760, 0,0,0,0, 20, 1340, 1340, 680, 20 };
static const int ex_y[10] = { 560, 0,0,0,0, 560, 560, 20, 20, 20 };
HWND pwnd[10]; HDC pdc[10]; HGLRC pgl[10];
bool phave[10];
int slot = 0; /* debug-grid position counter */
@@ -1260,7 +1468,7 @@ static DWORD WINAPI rt_main(LPVOID) {
EnterCriticalSection(&rt_lock);
if (rt_new) { cur = rt_pending; rt_new = false; redraw = true; }
LeaveCriticalSection(&rt_lock);
if (redraw && cur.valid) {
if (redraw && cur.valid && wnd) {
wglMakeCurrent(dc, gl);
RECT cr; GetClientRect(wnd, &cr);
rt_draw(dc, cur, cr.right, cr.bottom);
@@ -1288,7 +1496,9 @@ static DWORD WINAPI rt_main(LPVOID) {
EnumWindows(find_dosbox_wnd, (LPARAM)&dos);
if (dos) {
RECT dv, db;
GetWindowRect(wnd, &dv);
if (wnd) GetWindowRect(wnd, &dv);
else { dv.left = mx; dv.top = my; /* VPX_NOMAIN: the */
dv.right = mx + mw; dv.bottom = my + mh; } /* main slot */
GetWindowRect(dos, &db);
int x = (dv.left + dv.right) / 2 - (int)(db.right - db.left) / 2;
int y = dv.bottom + 10;
@@ -1880,11 +2090,134 @@ static void scene_burst(const unsigned char *p, size_t n) {
case 9: /* draw_scene: commit */
scene_publish_frame();
break;
case 38: /* set_sect_pixel: game armed the continuous reticle pick.
* From here the board is expected to return the hit in each
* frame reply -- that's what unblocks weapons fire. */
sect_armed = true;
break;
default:
break;
}
}
/* Moller-Trumbore ray/triangle: returns hit distance t>0, or -1 (no hit). */
static float ray_tri(const float O[3], const float D[3],
const float a[3], const float b[3], const float c[3]) {
float e1[3], e2[3], p[3], q[3], s[3];
for (int i = 0; i < 3; i++) { e1[i] = b[i] - a[i]; e2[i] = c[i] - a[i]; }
p[0] = D[1]*e2[2] - D[2]*e2[1];
p[1] = D[2]*e2[0] - D[0]*e2[2];
p[2] = D[0]*e2[1] - D[1]*e2[0];
float det = e1[0]*p[0] + e1[1]*p[1] + e1[2]*p[2];
if (det > -1e-6f && det < 1e-6f) return -1.0f; /* ray parallel */
float inv = 1.0f / det;
for (int i = 0; i < 3; i++) s[i] = O[i] - a[i];
float u = (s[0]*p[0] + s[1]*p[1] + s[2]*p[2]) * inv;
if (u < 0.0f || u > 1.0f) return -1.0f;
q[0] = s[1]*e1[2] - s[2]*e1[1];
q[1] = s[2]*e1[0] - s[0]*e1[2];
q[2] = s[0]*e1[1] - s[1]*e1[0];
float v = (D[0]*q[0] + D[1]*q[1] + D[2]*q[2]) * inv;
if (v < 0.0f || u + v > 1.0f) return -1.0f;
float t = (e2[0]*q[0] + e2[1]*q[1] + e2[2]*q[2]) * inv;
return t > 1e-3f ? t : -1.0f; /* in front only */
}
/* Real reticle pick: cast the camera centre ray (the screen-0.5,0.5 reticle)
* against the live scene and return the nearest triangle's owning instance, the
* DCS it hangs under (whose app-specific the game reads as the target Entity),
* and the world hit point. Same picking Dave's renderer does, self-contained.
* Traversal mirrors scene_publish_frame: dcs -> instance -> object -> lod[0] ->
* geogroup -> geometry -> polys, transformed by the DCS world matrix. */
static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out,
unsigned *geom_out, float xyz_out[3]) {
if (!S.view.has_cam) return false;
const float O[3] = { S.view.eye[0], S.view.eye[1], S.view.eye[2] };
float D[3] = { -S.view.rot[6], -S.view.rot[7], -S.view.rot[8] }; /* cam fwd */
float dl = sqrtf(D[0]*D[0] + D[1]*D[1] + D[2]*D[2]);
if (dl < 1e-6f) return false;
D[0] /= dl; D[1] /= dl; D[2] /= dl;
/* Report the TRUE nearest hit -- terrain IS a valid target (you must be
* able to fire into the ground and miss). Return ALL of the hit handles:
* instance, DCS, geogroup, geometry -- the game does GetAppSpecific on the
* DCS (=> targetEntity) AND the geogroup (=> damage zone), so a real board
* always has a geogroup on a hit; sending 0 there handed the game a null. */
float best_t = 1e30f;
unsigned best_inst = 0, best_dcs = 0, best_gg = 0, best_geo = 0;
std::map<unsigned, M16> cache;
for (std::map<unsigned, std::vector<unsigned> >::const_iterator di =
S.children.begin(); di != S.children.end(); ++di) {
std::map<unsigned, unsigned>::const_iterator ti = S.type.find(di->first);
if (ti == S.type.end() || ti->second != 5) continue; /* DCS parent */
M16 world; dcs_world(di->first, cache, world);
for (size_t ii = 0; ii < di->second.size(); ii++) {
unsigned inst = di->second[ii];
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;
std::map<unsigned, std::vector<unsigned> >::const_iterator ggi =
S.children.find(li->second[0]); /* lod[0] */
if (ggi == S.children.end()) continue;
for (size_t g = 0; g < ggi->second.size(); g++) {
unsigned gg = ggi->second[g]; /* geogroup handle */
std::map<unsigned, std::vector<unsigned> >::const_iterator gci =
S.children.find(gg); /* geogroup->geoms */
if (gci == S.children.end()) continue;
for (size_t k = 0; k < gci->second.size(); k++) {
unsigned geo = gci->second[k]; /* geometry handle */
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 r = 0; r < pi->second.size(); r++) {
const std::vector<int> &idx = pi->second[r];
if (idx.size() < 3) continue;
size_t o0 = (size_t)idx[0] * 3;
if (o0 + 2 >= vv.size()) continue;
float w0[3]; m16_xform(world, &vv[o0], w0);
for (size_t j = 1; j + 1 < idx.size(); j++) {
size_t o1 = (size_t)idx[j] * 3;
size_t o2 = (size_t)idx[j + 1] * 3;
if (o1 + 2 >= vv.size() || o2 + 2 >= vv.size()) continue;
float w1[3], w2[3];
m16_xform(world, &vv[o1], w1);
m16_xform(world, &vv[o2], w2);
float t = ray_tri(O, D, w0, w1, w2);
if (t > 0.0f && t < best_t) {
best_t = t; best_inst = inst;
best_dcs = di->first; best_gg = gg; best_geo = geo;
}
}
}
}
}
}
}
if (!best_inst) return false;
xyz_out[0] = O[0] + D[0] * best_t;
xyz_out[1] = O[1] + D[1] * best_t;
xyz_out[2] = O[2] + D[2] * best_t;
*inst_out = best_inst; *dcs_out = best_dcs;
*gg_out = best_gg; *geom_out = best_geo;
static unsigned last_i = 0, last_d = 0;
if ((best_inst != last_i || best_dcs != last_d) && vpx_fp) {
flush_run();
fprintf(vpx_fp, "# raycast pick: inst=%08X dcs=%08X gg=%08X t=%.1f "
"pos(%.1f,%.1f,%.1f)\n", best_inst, best_dcs, best_gg, best_t,
xyz_out[0], xyz_out[1], xyz_out[2]);
fflush(vpx_fp);
last_i = best_inst; last_d = best_dcs;
}
return true;
}
static void scene_reset(void) {
S.type.clear(); S.verts.clear(); S.polys.clear(); S.mat.clear();
S.ggmat.clear(); S.children.clear();
@@ -1903,6 +2236,7 @@ static void vpx_render_start(void) {
}
#else /* !VPX_RENDER_SUPPORTED */
static void scene_burst(const unsigned char *, size_t) {}
static bool raycast_pick(unsigned *, unsigned *, unsigned *, unsigned *, float *) { return false; }
static void scene_reset(void) {}
static void vpx_render_start(void) {}
#endif
@@ -2077,6 +2411,9 @@ void VPXLOG_Init(void) {
if (fifo_dump_fp == NULL) LOG_MSG("VPXLOG: cannot open fifodump '%s'", fd);
}
const char *fsk = getenv("VPX_FIFOSOCK");
if (fsk && atoi(fsk) > 0) fifo_sock_init(atoi(fsk));
const char *dd = getenv("VPX_DUMPDIR");
if (dd && dd[0]) {
strncpy(pal_dump_dir, dd, sizeof pal_dump_dir - 1);