Beams at the models' NATURAL width, per-weapon tube models (de-cartooned)

The "cartoonish" beams: the port drew the ermlaser tube INFLATED -- a 3.0x
glow + 0.9x core two-layer hack, 13x the authored radius.  Parsed the beam
models: ERMLASER/MLASER 0.22u radius, SLASER 0.11, LLASER 0.32, PPC 0.62 --
thin pencil beams with an authored per-class size progression; the PPC bolt
is genuinely ~3x fatter with its own 62-vert shape.

Now: ONE draw per beam at natural model scale (width multiplier 1.0), using
the weapon's OWN tube model (ermlaser for lasers, ppc.bgf for PPCs; the
s/m/llaser tubes load for other mechs' loadouts).  BTPushBeamKind carries the
model selector; BTPushBeam keeps its signature (kind 0).  Tint = 40+215x the
authored PipColor; the thin natural tubes stay under additive saturation so
the scrolling grit reads without a hand-dimmed core layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-08 18:33:20 -05:00
co-authored by Claude Fable 5
parent 2036e7da51
commit 31626a3b97
2 changed files with 68 additions and 39 deletions
+53 -27
View File
@@ -56,7 +56,8 @@ struct BTBeamFx
D3DXVECTOR3 from, to;
DWORD color; // 0x00RRGGBB glow colour (additive; alpha ignored)
float ttl, maxTtl; // seconds
float width; // world units
float width; // natural-model-scale multiplier (1.0 = authored radius)
int kind; // tube model: 0=ermlaser 1=ppc 2=slaser 3=mlaser 4=llaser
};
static std::vector<BTBeamFx> gBTBeams;
@@ -85,8 +86,8 @@ void BTSetLodEye(float x, float y, float z)
}
}
void BTPushBeam(float fx, float fy, float fz, float tx, float ty, float tz,
unsigned color, float ttl, float width)
void BTPushBeamKind(float fx, float fy, float fz, float tx, float ty, float tz,
unsigned color, float ttl, float width, int kind)
{
if (gBTBeams.size() > 256) return; // runaway guard
BTBeamFx b;
@@ -95,9 +96,16 @@ void BTPushBeam(float fx, float fy, float fz, float tx, float ty, float tz,
b.color = color;
b.ttl = ttl; b.maxTtl = (ttl > 1e-4f) ? ttl : 1e-4f;
b.width = width;
b.kind = (kind >= 0 && kind < 5) ? kind : 0;
gBTBeams.push_back(b);
}
void BTPushBeam(float fx, float fy, float fz, float tx, float ty, float tz,
unsigned color, float ttl, float width)
{
BTPushBeamKind(fx, fy, fz, tx, ty, tz, color, ttl, width, 0);
}
void BTDrawBeams(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
{
if (gBTBeams.empty()) return;
@@ -190,37 +198,51 @@ void BTDrawBeams(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
}
}
}
// AUTHENTIC GEOMETRY: render the real ermlaser.bgf TUBE (a thin 2000-long tube
// with UVs tiled ~8x down its length), transformed per beam (scale the length to
// the shot distance, orient local -Z -> muzzle->target), instead of a camera-
// facing billboard quad. BT_BEAM_TUBE=0 falls back to the billboard.
// AUTHENTIC GEOMETRY: render each weapon's REAL beam model (thin 2000-long
// tubes, -Z aligned, UVs tiled down the length), transformed per beam (scale
// the length to the shot distance, orient local -Z -> muzzle->target). The
// authored radii ARE the beam widths: ERMLASER 0.22, PPC 0.62 (a genuinely
// fatter bolt), SLASER 0.11 / MLASER 0.22 / LLASER 0.32 -- draw at natural
// scale (width multiplier 1.0), NOT inflated. BT_BEAM_TUBE=0 -> billboard.
static int s_tubeTried = 0;
static std::vector<float> s_tubeVB; // x,y,z,u,v per vertex
static std::vector<uint32_t> s_tubeIB;
static int s_tubeVerts = 0, s_tubeTris = 0;
struct BTTubeModel
{
std::vector<float> vb; // x,y,z,u,v per vertex
std::vector<uint32_t> ib;
int verts, tris;
};
static BTTubeModel s_tubes[5];
static const char *const kTubeNames[5] =
{ "ermlaser.bgf", "ppc.bgf", "slaser.bgf", "mlaser.bgf", "llaser.bgf" };
if (!s_tubeTried)
{
s_tubeTried = 1;
const char *tvv = getenv("BT_BEAM_TUBE");
if (tvv == 0 || tvv[0] != '0')
{
BgfData bd;
if (LoadBgfFile("ermlaser.bgf", bd) && bd.ok && !bd.indices.empty())
for (int t = 0; t < 5; ++t)
{
s_tubeVerts = (int)bd.verts.size();
s_tubeVB.reserve(bd.verts.size() * 5);
for (size_t k = 0; k < bd.verts.size(); ++k)
BgfData bd;
s_tubes[t].verts = s_tubes[t].tris = 0;
if (LoadBgfFile(kTubeNames[t], bd) && bd.ok && !bd.indices.empty())
{
s_tubeVB.push_back(bd.verts[k].x); s_tubeVB.push_back(bd.verts[k].y);
s_tubeVB.push_back(bd.verts[k].z);
s_tubeVB.push_back(bd.verts[k].u); s_tubeVB.push_back(bd.verts[k].v);
s_tubes[t].verts = (int)bd.verts.size();
s_tubes[t].vb.reserve(bd.verts.size() * 5);
for (size_t k = 0; k < bd.verts.size(); ++k)
{
s_tubes[t].vb.push_back(bd.verts[k].x);
s_tubes[t].vb.push_back(bd.verts[k].y);
s_tubes[t].vb.push_back(bd.verts[k].z);
s_tubes[t].vb.push_back(bd.verts[k].u);
s_tubes[t].vb.push_back(bd.verts[k].v);
}
s_tubes[t].ib = bd.indices;
s_tubes[t].tris = (int)(bd.indices.size() / 3);
}
s_tubeIB = bd.indices;
s_tubeTris = (int)(bd.indices.size() / 3);
}
}
}
const bool useTube = (s_beamTex != 0 && s_tubeTris > 0);
const bool useTube = (s_beamTex != 0 && s_tubes[0].tris > 0);
static float s_tubeWidth = -1.0f;
if (s_tubeWidth < 0.0f)
{ const char *wv = getenv("BT_BEAM_WIDTH"); s_tubeWidth = wv ? (float)atof(wv) : 1.0f; } // global width multiplier
@@ -299,9 +321,9 @@ void BTDrawBeams(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
D3DXVECTOR3 xc; D3DXVec3Cross(&xc, &up, &zc); D3DXVec3Normalize(&xc, &xc);
D3DXVECTOR3 yc; D3DXVec3Cross(&yc, &zc, &xc);
D3DXMATRIX S, R, T, W;
// PER-BEAM width (b.width) x global BT_BEAM_WIDTH multiplier: a wide dim glow
// and a THIN bright core render at different radii (real ermlaser = thin core
// in a wider glow) instead of stacking to a solid white cylinder.
// NATURAL width: the model's authored radius IS the beam width
// (ERMLASER 0.22u, PPC 0.62u...). b.width is a multiplier on that
// (1.0 for weapon beams); BT_BEAM_WIDTH is the global dev override.
const float ws = b.width * s_tubeWidth;
D3DXMatrixScaling(&S, ws, ws, beamLen / 2000.0f);
D3DXMatrixIdentity(&R);
@@ -313,16 +335,20 @@ void BTDrawBeams(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
D3DXMatrixMultiply(&W, &W, &T);
dev->SetTransform(D3DTS_WORLD, &W);
dev->SetRenderState(D3DRS_TEXTUREFACTOR, col);
// the beam's own weapon-model tube (ermlaser/ppc/slaser/...); fall
// back to the ermlaser tube when that model didn't load
const BTTubeModel &tube =
(s_tubes[b.kind].tris > 0) ? s_tubes[b.kind] : s_tubes[0];
// per-beam UVs: world-fixed grit density + scroll (see banner above)
const float uScale = beamLen / 2000.0f;
s_tubeScrolled = s_tubeVB;
s_tubeScrolled = tube.vb;
for (size_t k = 0; k + 4 < s_tubeScrolled.size(); k += 5)
{
s_tubeScrolled[k + 3] = s_tubeScrolled[k + 3] * uScale + uScroll;
s_tubeScrolled[k + 4] += vScroll;
}
dev->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, s_tubeVerts, s_tubeTris,
&s_tubeIB[0], D3DFMT_INDEX32, &s_tubeScrolled[0], 5 * sizeof(float));
dev->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, tube.verts, tube.tris,
&tube.ib[0], D3DFMT_INDEX32, &s_tubeScrolled[0], 5 * sizeof(float));
}
else
{