Weapon visuals + aim: beams render from the guns; pick self-target fixes
Renderer (dpl3-revive/patha): - Instance visibility honored at DRAW time (stored word 4 = live dpl_SetInstanceVisibility field): laser beams (4-frame pulses) and missile models now render; cache tags w3 0/1 instances 'gated' - PSFX world bursts (dcs=0 muzzle/impact/explosion) rendered; psfx storage list-based so one-shot bursts stop collapsing onto one key - fix_degenerate applied to INSTANCE chains (was camera-only): the beam chain rides four rank-2 rig DCSs; composed rank-2 drew beams away from the guns. Offline f1635 now shows both arm beams converging on the aim point - First-person canopy cull: the always-armed cockpit model at the head drew as a vertical line down screen center (user-confirmed fixed live) Device (vpx-device/vpxlog.cpp): - CAM backchannel on the fifosock: bridge streams its validated camera (+ vehicle root DCS); raycast_pick aims along the player's real look instead of the static view-node pose, which had locked every missile/beam onto one fixed wrong world point - raycast_pick skips hidden-until-armed instances and the player's own articulation subtree: picking own beams created a target feedback loop, picking own missiles retargeted salvos mid-flight, and a picked missile's deleted DCS froze the game (arena2 crash) Sound (vpx-device/vweawe.cpp): stereo-linked peak limiter replaces the hard output clamp (the "generator out" crackle); VWE_AWE_LOG reports pre-limit peaks + duck counts, enabled in launch_pod.ps1. Missile-fire crackle persists: per-voice EMU8000 clamp suspected. Eggs/confs: TESTAR2.EGG + gauge_arena2_sound.conf (map=arena2 city variant); cavern.egg (console-era reference); plasma display on COM2 in the gauge confs; gauge_arena_net_sound.conf (slirp net stack). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -265,13 +265,83 @@ static void fifo_sock_write(const unsigned char *p, size_t n) {
|
||||
} else { fifo_sock_drop(); return; }
|
||||
}
|
||||
}
|
||||
|
||||
/* bridge -> device backchannel on the same socket. The bridge streams its
|
||||
* camera pose (the user-validated cockpit view: cam chain + torso twist +
|
||||
* hat glance) as text lines: CAM ex ey ez dx dy dz\n
|
||||
* raycast_pick prefers this over the device's own S.view decode -- S.view
|
||||
* tracks the static view-node pose (~arena origin, not the player's aim),
|
||||
* which pointed every reticle pick, and therefore every missile volley and
|
||||
* laser beam, at one fixed wrong world point. */
|
||||
static char brcam_line[192];
|
||||
static size_t brcam_len = 0;
|
||||
static float brcam[6];
|
||||
static bool brcam_valid = false;
|
||||
static DWORD brcam_tick = 0;
|
||||
static unsigned brcam_root = 0; /* player vehicle root DCS (7th CAM field) */
|
||||
|
||||
static void fifo_sock_poll(void) {
|
||||
if (fifo_csock == INVALID_SOCKET) return;
|
||||
char buf[512];
|
||||
for (;;) {
|
||||
int r = recv(fifo_csock, buf, (int)sizeof buf, 0);
|
||||
if (r == 0) { fifo_sock_drop(); return; } /* client closed */
|
||||
if (r < 0) {
|
||||
if (WSAGetLastError() != WSAEWOULDBLOCK) fifo_sock_drop();
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < r; i++) {
|
||||
char ch = buf[i];
|
||||
if (ch == '\n') {
|
||||
brcam_line[brcam_len] = 0;
|
||||
float v[6]; unsigned rt = 0;
|
||||
int nf = sscanf(brcam_line, "CAM %f %f %f %f %f %f %x",
|
||||
&v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &rt);
|
||||
if (nf >= 6) {
|
||||
memcpy(brcam, v, sizeof brcam);
|
||||
brcam_valid = true;
|
||||
brcam_tick = GetTickCount();
|
||||
brcam_root = (nf >= 7) ? rt : 0;
|
||||
}
|
||||
brcam_len = 0;
|
||||
} else if (brcam_len + 1 < sizeof brcam_line) {
|
||||
brcam_line[brcam_len++] = ch;
|
||||
} else {
|
||||
brcam_len = 0; /* oversize: resync */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#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) {}
|
||||
static void fifo_sock_poll(void) {}
|
||||
#endif
|
||||
|
||||
/* Latest bridge camera, if fresh: the pick must not outlive a dead bridge
|
||||
* by more than a couple of seconds (stale aim = the fixed-point bug again,
|
||||
* just slower), so fall back to the device view after 2s of silence. */
|
||||
static bool bridge_cam_get(float O[3], float D[3]) {
|
||||
#ifdef _WIN32
|
||||
if (brcam_valid && (GetTickCount() - brcam_tick) < 2000) {
|
||||
O[0] = brcam[0]; O[1] = brcam[1]; O[2] = brcam[2];
|
||||
D[0] = brcam[3]; D[1] = brcam[4]; D[2] = brcam[5];
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
static unsigned bridge_cam_root(void) {
|
||||
#ifdef _WIN32
|
||||
return brcam_root;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void fifo_buf_push(unsigned char v) {
|
||||
if (fifo_dump_fp == NULL && !vpx_render && !fifo_sock_active()) return;
|
||||
if (fifo_buf_len >= (1u << 20)) return; /* runaway guard */
|
||||
@@ -295,6 +365,7 @@ static void fifo_flush_record(void) {
|
||||
}
|
||||
if (fifo_sock_active()) {
|
||||
fifo_sock_accept();
|
||||
fifo_sock_poll(); /* drain bridge CAM backchannel */
|
||||
fifo_sock_write(hdr, sizeof hdr);
|
||||
fifo_sock_write(fifo_buf, fifo_buf_len);
|
||||
}
|
||||
@@ -2132,9 +2203,18 @@ static float ray_tri(const float O[3], const float D[3],
|
||||
* 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 */
|
||||
/* Ray = the player's actual look: prefer the bridge's camera (cam chain
|
||||
* + torso twist + hat glance, streamed back over the fifosock). The
|
||||
* device's own S.view decode tracks the static view-node pose, so with
|
||||
* it every pick -- and every missile/beam -- aimed at one fixed wrong
|
||||
* world point. Fallback keeps weapons firing bridge-less. */
|
||||
float O[3], D[3];
|
||||
const bool from_bridge = bridge_cam_get(O, D);
|
||||
if (!from_bridge) {
|
||||
if (!S.view.has_cam) return false;
|
||||
O[0] = S.view.eye[0]; O[1] = S.view.eye[1]; O[2] = S.view.eye[2];
|
||||
D[0] = -S.view.rot[6]; D[1] = -S.view.rot[7]; D[2] = -S.view.rot[8];
|
||||
}
|
||||
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;
|
||||
@@ -2153,6 +2233,36 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
|
||||
M16 world; dcs_world(di->first, cache, world);
|
||||
for (size_t ii = 0; ii < di->second.size(); ii++) {
|
||||
unsigned inst = di->second[ii];
|
||||
/* Skip hidden-until-armed instances (w3 0/1: laser beams,
|
||||
* missile models, effects). Picking them is poison: the beam
|
||||
* stretches to the pick point, the next pick hits the BEAM at
|
||||
* that point and locks the target there (feedback loop); a
|
||||
* volley's own missiles get picked and homed on; and a picked
|
||||
* missile whose DCS deletes on impact hands the game a stale
|
||||
* handle (the arena2 freeze). The reticle resolves the WORLD:
|
||||
* terrain, buildings, vehicles. */
|
||||
std::map<unsigned, unsigned>::const_iterator wi =
|
||||
S.inst_w3.find(inst);
|
||||
if (wi != S.inst_w3.end() && wi->second != 2 && wi->second != 3)
|
||||
continue;
|
||||
/* Never pick the player's OWN articulation subtree (arms, torso,
|
||||
* muzzle flash, searchlight): the ray grazes own geometry from
|
||||
* the over-the-shoulder eye and a mid-volley pick of the muzzle
|
||||
* flash retargets the salvo ("missiles hare off sometimes").
|
||||
* Root DCS comes from the bridge's CAM line, 7th field. */
|
||||
unsigned own_root = bridge_cam_root();
|
||||
if (own_root) {
|
||||
unsigned a = di->first;
|
||||
bool own = false;
|
||||
for (int hop = 0; hop < 64; hop++) {
|
||||
if (a == own_root) { own = true; break; }
|
||||
std::map<unsigned, unsigned>::const_iterator ppi =
|
||||
S.dcs_parent.find(a);
|
||||
if (ppi == S.dcs_parent.end() || ppi->second == a) break;
|
||||
a = ppi->second;
|
||||
}
|
||||
if (own) continue;
|
||||
}
|
||||
std::map<unsigned, unsigned>::const_iterator oi =
|
||||
S.inst_object.find(inst);
|
||||
if (oi == S.inst_object.end()) continue;
|
||||
@@ -2210,8 +2320,10 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
|
||||
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]);
|
||||
"pos(%.1f,%.1f,%.1f) cam=%s eye(%.1f,%.1f,%.1f)\n",
|
||||
best_inst, best_dcs, best_gg, best_t,
|
||||
xyz_out[0], xyz_out[1], xyz_out[2],
|
||||
from_bridge ? "bridge" : "view", O[0], O[1], O[2]);
|
||||
fflush(vpx_fp);
|
||||
last_i = best_inst; last_d = best_dcs;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user