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:
+354
-17
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user