Searchlight beam: the btfx brighten material class + BT_SPOT_SELF rig

SPOT.BGF decoded: a 7-vert cone from the mount, ~50u forward and ~35deg DOWN
(a ground-pool lamp, not an air beam), verts tinted cyan-white, material
class 'brighten' smuggling its additive factor in DIFFUSE.r (0.25) with a
warm emissive on the night page.  The loader had never met the class -- it
drew as an opaque dark-red blob.  Now: brightenFactor parsed (name-gated to
brighten*), batch -> L4DRAWOP.brightenAlpha, drawn in the blend pass as an
additive veil (dest += vertexRGB x factor), unlit.  [T3] tint compose
(vertex cyan vs night emissive warm) noted in the draw branch -- field
eyeball accepted the current look.

BT_SPOT_SELF=1 (bench-only): builds the cone on the own-cockpit tree so the
BT_CAM=face view can inspect it solo.  KB: view toggle is BACKTICK ('V' is
the rear-view hold since #68 -- the toggle skips bound keys); stale V-toggle
claims swept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-05 22:21:32 -05:00
co-authored by Claude Fable 5
parent 7d8f109241
commit b75bb4a04c
7 changed files with 69 additions and 7 deletions
+18 -1
View File
@@ -350,6 +350,12 @@ struct MatInfo {
// EMISSIVE (tag 0x26): pure-emissive materials (diffuse black) render as an
// unlit glow -- tex x emissive (btpolar:pintBIceEmit_mtl, the polar ice).
bool hasEmissive = false;
// BTFX "brighten" class (SPOT.BGF searchlight beam cone): the material
// smuggles its ADDITIVE factor in DIFFUSE.r (brighten.25 authors diffuse
// {0.25,0,0}) and its glow colour in EMISSIVE ({0.9,0.4,0.2} on the night
// page). 0 = not a brighten material; >0 = draw as an additive veil,
// dest += emissive x factor (the 1995 DIV brighten semantics).
float brightenFactor = 0.0f;
float emissive[3] = {0,0,0};
};
@@ -495,12 +501,14 @@ struct MaterialResolver {
std::string name, texName, rampName;
MatInfo info;
bool haveColor = false;
float rawDiffuseR = -1.0f; // brighten-class factor carrier
for (const Chunk& ch : c.children) {
if (ch.id == TAG_NAME) name = chunkStr(ch);
else if (ch.id == TAG_MATERIAL_TEXTURE && ch.len > 1) texName = chunkStr(ch, 1);
else if (ch.id == TAG_RAMP_REF) rampName = chunkStr(ch);
else if (ch.id == TAG_DIFFUSE && ch.len >= 12) {
info.color = packColor(rdF32(ch.data), rdF32(ch.data + 4), rdF32(ch.data + 8));
rawDiffuseR = rdF32(ch.data);
haveColor = true;
info.hasDiffuse = true;
} else if (ch.id == TAG_AMBIENT && ch.len >= 12 && !haveColor
@@ -520,6 +528,12 @@ struct MaterialResolver {
info.hasEmissive = (info.emissive[0] + info.emissive[1] + info.emissive[2]) > 0.001f;
}
}
// BTFX brighten class: name-gated so ordinary materials keep
// their diffuse semantics untouched (the factor rides only in
// "brighten*" records -- brighten.25/.5/.75 in btfx.bmf).
if (name.compare(0, 8, "brighten") == 0 && rawDiffuseR > 0.0f)
info.brightenFactor = rawDiffuseR;
// Resolve the ramp reference to its low/high colours: this
// file's own definitions first, then the cross-library
// registry (mech skins reference 'softer' defined elsewhere).
@@ -645,6 +659,7 @@ struct Builder {
int currentCopRole = 0; // role of the pmesh being built (consumed by buildPmesh)
bool currentHasEmissive = false;
float currentEmissive[3] = {0,0,0};
float currentBrighten = 0.0f; // btfx brighten-class additive factor
// per-vertex scratch (normals accumulated from forward triangles only)
std::vector<float> px, py, pz, nx, ny, nz, uu, vv;
@@ -926,6 +941,7 @@ struct Builder {
batch.shadowMat = currentShadowMat;
batch.hasEmissive = currentHasEmissive;
for (int i = 0; i < 3; ++i) batch.emissive[i] = currentEmissive[i];
batch.brightenFactor = currentBrighten; // btfx additive veil (spot cone)
// SUBMISSION-ORDER DEPTH BIAS (additive objects): the content layers
// EXACTLY-COPLANAR shells (AR02 LOD2: solid concrete + a coplanar
// PUNCH-cutout overlay + an inner shell, plane separations
@@ -1251,8 +1267,9 @@ struct Builder {
for (int i = 0; i < 3; ++i) { currentRampLo[i] = info.rampLo[i]; currentRampHi[i] = info.rampHi[i]; }
currentHasEmissive = info.hasEmissive;
for (int i = 0; i < 3; ++i) currentEmissive[i] = info.emissive[i];
currentBrighten = info.brightenFactor;
}
else { currentColor = colorForMaterial(full); currentTex.clear(); currentHasDiffuse = false; currentTexChannel = 0; currentTexScroll = false; currentTexScrollU = currentTexScrollV = 0.0f; }
else { currentColor = colorForMaterial(full); currentTex.clear(); currentHasDiffuse = false; currentTexChannel = 0; currentTexScroll = false; currentTexScrollU = currentTexScrollV = 0.0f; currentBrighten = 0.0f; }
// TRANSLOCATION WARP: override the material's coarse "sky" ramp with the
// NARROW LAVENDER ramp that matched the original (capture.png). The swirl
// is the bintA cloud coloured lo->hi by luminance; a wide sky ramp