Warp: faithful POVTranslocateRenderable -- eye-centered, ramp color, no spin

Deep-dived the authentic engine effect (L4VIDRND.cpp POVTranslocateRenderable, T0)
and reworked our one-shot to match it, fixing the user's "blob from my own POV /
not aligned to my orientation" + "gray, not blue" reports:

1. PLACEMENT was the misalignment. The engine parents the sphere to the VTV/eye
   ("rotated and scaled around the VTV", L4VIDRND.cpp:1812) -- it is centered on and
   oriented to the viewpoint. We world-fixed it at the mech's feet, so from your own
   camera it sat off-axis and, once expanded, engulfed the eye as a shapeless blob.
   Now: the own-respawn warp (BTStartWarpEffectPOV, from btplayer) uses
   world = Scale(s) * inverse(view) -> centered on + carried by your eye (the tunnel
   wraps you). Observing a PEER (BTStartWarpEffect from mechdmg) stays world-anchored
   at the peer's point.

2. GEOMETRY SPIN was inauthentic. myRotateY/myRotateYSpeed are set in the engine
   ctor but NEVER applied -- the swirl's rotation is entirely the material's texture
   SCROLL (bintA, SCROLL 0.0 0.0 0.1 0.5). Dropped our world-Y geometry spin.

3. COLOR (gray) fixed at the source. The blue/white is the "sky" RAMP remapping the
   grayscale bintA cloud per-texel (argb=lerp(0,0,0.6 -> .99,.99,.99, luminance),
   L4D3D.cpp:480). The port bakes exactly this but GATED it off for normal-bearing
   meshes; tsphere has normals so it arrived raw gray, and a MODULATE tint can't add
   the blue floor (it only multiplies) -> the wash-out. Un-gate the ramp for
   tsphere_mtl (bgfload.cpp) so the bound texture is already the blue-white swirl;
   draw it MODULATE by white (no tint), alpha from TFACTOR.

4. SCALE to authentic 100 (collapse) / 150 (expand end) -- correct now that it's
   eye-centered (you're meant to be inside it as it collapses through you and blasts
   back open, the "blast off into the distance").

Still deferred (higher risk / SimulationState-coupling trap): the WaitForReincarnate
Lissajous wobble + the SetIsDead world-mask (black-out during the dead hold). Noted
for follow-up; the placement/color/motion are the parts the user flagged.

Smoke-verified 2-node: own respawn logs POV(eye), observer logs world-anchored, no
crash. BT_WARP_ADDITIVE / BT_WARP_COLOR / BT_WARP_SCALE remain as live tuning knobs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-10 08:42:02 -05:00
co-authored by Claude Opus 4.8
parent 160b78e38d
commit a9d1534292
3 changed files with 110 additions and 54 deletions
+10 -1
View File
@@ -500,6 +500,7 @@ struct Builder {
float currentLodBias = 0.0f; // (retained; per-batch bias now set in buildPmesh)
bool currentPunch = false; // inside a PUNCH patch (SV_SPECIAL 0x2037, geogroup level)
bool currentShadowMat = false; // material name contains "shadow" (baked ground-shadow quads)
bool currentTSphere = false; // material is tsphere_mtl (translocation warp): ramp it despite normals
bool currentHasEmissive = false;
float currentEmissive[3] = {0,0,0};
@@ -561,7 +562,7 @@ struct Builder {
// TRUECOLOR BSL slices (channel >= 6: RGB444/RGBA4444, e.g. damcolor's bdam8
// damage sheet) are real colour images -- luminance-ramping them would
// destroy their colour, so they take the plain textured path.
const bool useRamp = currentHasRamp && !hasNormals(vtag) && currentTexChannel < 6;
const bool useRamp = currentHasRamp && (!hasNormals(vtag) || currentTSphere) && currentTexChannel < 6;
const uint32_t base = (uint32_t)px.size();
const uint32_t localCount = (uint32_t)lx.size();
@@ -865,6 +866,7 @@ struct Builder {
bool savedHasRamp = currentHasRamp;
bool savedPunch = currentPunch;
bool savedShadowMat = currentShadowMat;
bool savedTSphere = currentTSphere;
bool savedHasEmissive = currentHasEmissive;
float savedEmissive[3] = { currentEmissive[0], currentEmissive[1], currentEmissive[2] };
float savedRampLo[3] = { currentRampLo[0], currentRampLo[1], currentRampLo[2] };
@@ -889,6 +891,12 @@ struct Builder {
currentShadowMat = false;
for (size_t si = 0; si + 6 <= full.size(); ++si)
if (_strnicmp(full.c_str() + si, "shadow", 6) == 0) { currentShadowMat = true; break; }
// The translocation warp sphere (tsphere_mtl): its swirl COLOUR is
// the "sky" ramp remapping the grayscale bintA cloud, but tsphere.bgf
// carries vertex normals so the normals-gate below would skip the ramp
// and leave it raw gray. Flag it so the ramp bake runs anyway (the
// authentic look; matches L4VIDRND POVTranslocateRenderable).
currentTSphere = (full.find("tsphere_mtl") != std::string::npos);
currentHasRamp = false;
if (res) {
MatInfo info = res->resolve(full);
@@ -911,6 +919,7 @@ struct Builder {
currentHasRamp = savedHasRamp;
currentPunch = savedPunch;
currentShadowMat = savedShadowMat;
currentTSphere = savedTSphere;
currentHasEmissive = savedHasEmissive;
for (int i = 0; i < 3; ++i) currentEmissive[i] = savedEmissive[i];
for (int i = 0; i < 3; ++i) { currentRampLo[i] = savedRampLo[i]; currentRampHi[i] = savedRampHi[i]; }