The "fireballs like the demo vids" arc, decomp/content-grounded end to end,
plus the live-play UX batch verified over the same sessions:
FIRESMOKE SHEET (the PFX fireball fix): every firesmokeN_scr_tex in BTFX.VMF
maps the SAME 64x64 tileable noise image bintA (variants differ only in SCROLL
rate) and firesmoke1_mtl colours it through the "fiery" ramp (0.3,0.1,0.1)->
(0.9,0.7,0.3). The particle layer now bakes ramp(lum(bintA)) as its sprite
colour (noise detail in alpha) and SCROLLS it at firesmoke1's authored rate
via a texture-transform; the port's radial soft-edge mask moved to a second
CLAMPed stage so the WRAPPED scroll rolls flame through the sprite without
scrolling the edge away. Old grit x radial bake kept as the no-BINTA fallback.
Impact hits, damage-band smoke and death booms all ride this layer.
AUTHORED TEXTURE SCROLL in the model path: the BMF TEXTURE records carry
SPECIAL " SCROLL u0 v0 du dv" (tag 0x2037); the draw path always supported
per-op scrolling (SetTextureScrolling) but the BGF loader never parsed it, so
every scrolling material rendered frozen. Wired TexRef -> MatInfo -> batch ->
L4TEXOP.doScroll: the flame cards (flamebig/fire5) now roll fire noise.
VERTEX-ALPHA EFFECT CARDS (the "twisted drill bit of fire" fix): FLAMEBIG's
verts carry authored float RGBA -- white-hot base (1.0,0.99,0.97) -> dark-red
tip fading to alpha -0.2 (the DPL clamp convention). The loader kept a flat
batch colour and drew it OPAQUE = a solid orange spike. Corpus sweep: exactly
14 shipped BGFs carry vertex alpha, ALL effect cards (flames, MUZFLASH,
EXDISK_A/B/C, TMST_A/B/C, beam models, DECLOUDS). Such batches now keep the
authored per-vertex gradient and route to the alpha-blend pass, unlit,
colour = texture x gradient, alpha = the vertex fade; sky objects excluded
(drawAsSky + alphaTest passes NEITHER pass filter -- DECLOUDS stays in the
sky pass). MUZFLASH/EXDISK render correctly for free when the muzzle-model
work lands.
WRECK DRESSING (the 1996 ExplosionScripts case-4 transcription): pieces spawn
HIDDEN and reveal 0.25s after the boom (the InstanceSwitch delay, behind the
dnboom flash); flamebig hangs over the pile, Y-BILLBOARDED at the camera
(SetOffsetYaw + a camera-pos getter -- the dpl_SetDCSReorientAxes analog);
the MakeDCSFall settle arms at the reveal with the two authored rates (hulk/
debris -0.025 t^2, fires -0.01 t^2 -- the flames ride above the sinking pile
and die with it at burial). EMPTY-PLACEHOLDER hulk guard: THRDBR.BGF is a
153-byte zero-geometry stub that "loads fine" -- vertex-count check now routes
it to the gendbr fallback (a Thor wreck was invisible). Hulk content census
recorded: AVADBR==MADDBR==VULDBR geometry (palette-only prefix diffs),
RAPDBR==SNDDBR==STIDBR byte-identical -- wreck variety is materials + the
dressing, not unique piles.
LIVE-PLAY BATCH: muzzle resolve uses the named segmentIndex (raw +0xdc read
was layout garbage); forward launch frame (authored MuzzleVelocity +Z vs the
mech's -Z facing); dock-bottom single window (gauge strip appended below the
world viewport, 1100x600 default, BT_DEV_GAUGES_WINDOW=1 restores the separate
window); portrait sec surface unrotated CW; ammo counters live via typed
bridges (BTAmmoBinCountPtr/BTAmmoBinFeeding/BTWeaponAmmoBin -- raw bin+0x180
and a hand-rolled link walk were garbage); fourth fire key ('4' = Pinky);
panel/arc probes de-aliased (%61 prime).
KB: rendering.md (vertex-alpha card family + scroll), combat-damage.md (hulk
census + THRDBR stub), gauges-hud.md (ammo bridges).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
6.6 KiB
C++
121 lines
6.6 KiB
C++
// bgfload.h - C++14 loader for VWE .BGF (DIV-BIZ2) geometry, adapted from the
|
|
// proven standalone port loader (port/src/bgf.cpp). Resolves the model under the
|
|
// pod content tree (VIDEO\ + VIDEO\GEO\...) and its .BMF material libraries +
|
|
// texture image basenames. Produces an L4VERTEX-compatible vertex/index/batch set
|
|
// ready to be turned into an ID3DXMesh by d3d_OBJECT.
|
|
//
|
|
// Self-contained: uses only the Win32 API for directory scanning (no <filesystem>,
|
|
// so it builds under the engine's C++14 target).
|
|
#pragma once
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
// Layout MUST match L4VERTEX (L4D3D.h): XYZ | NORMAL | DIFFUSE | TEX1.
|
|
struct BgfVtx {
|
|
float x, y, z;
|
|
float nx, ny, nz;
|
|
uint32_t color; // ARGB 0xAARRGGBB (unused once material diffuse drives lighting)
|
|
float u, v;
|
|
};
|
|
|
|
// One draw range sharing a material.
|
|
struct BgfDrawBatch {
|
|
uint32_t indexStart = 0;
|
|
uint32_t indexCount = 0;
|
|
uint32_t color = 0xFFB0B0B8u; // ARGB fallback / resolved diffuse
|
|
std::string texPath; // resolved texture image file (.vtx/.bsl/.tga), or empty
|
|
// BSL BIT-SLICE (BMF TEXTURE tag 0x18, absent = 0): which 4-bit slice of the
|
|
// .bsl container this texture samples (0..5 mono; 7 = RGB444, 8 = RGBA4444).
|
|
// BSLs pack up to SIX grayscale sub-images per file (mech skins: BLKHWK.BSL
|
|
// holds blkhwk1..4); decoding the packed word as one RGBA image overlaid
|
|
// 2-3 different sheets as color channels = the rainbow "graffiti" mechs.
|
|
int texChannel = 0;
|
|
// TEXTURE SCROLL (the BMF TEXTURE record's SPECIAL " SCROLL u0 v0 du dv",
|
|
// tag 0x2037): authored UV animation in cycles/second -- the firesmoke
|
|
// family (flamebig/fire5 flame cards, the burning-wreck dressing) rolls
|
|
// its fire noise this way. Propagated to L4TEXOP::doScroll, which the
|
|
// draw path (d3d_OBJECT::SetTextureScrolling) already honours.
|
|
bool texScroll = false;
|
|
float texScrollU = 0.0f;
|
|
float texScrollV = 0.0f;
|
|
// VERTEX ALPHA (tags 0x0082/0x008A with any authored alpha < 1): the batch
|
|
// is a BLENDED effect card (FLAMEBIG's flame fan fades its tip to negative
|
|
// alpha). The verts keep their authored RGBA gradient and the draw routes
|
|
// to the alpha-blend pass, unlit, colour = texture x vertex gradient.
|
|
bool vertexAlpha = false;
|
|
// RAMP (task #20): colorize the grayscale texture by luminance across
|
|
// rampLo->rampHi (the authentic IG-board terrain colouring). When set, the
|
|
// renderer bakes lerp(rampLo, rampHi, texLum) into the texture and draws with
|
|
// a WHITE material so the ramp colour shows directly.
|
|
bool hasRamp = false;
|
|
float rampLo[3] = {0,0,0};
|
|
float rampHi[3] = {1,1,1};
|
|
// TRANSLOCATION WARP band count: the IG board realised a ramp as a QUANTISED
|
|
// shade colour-map (~16 discrete levels; the material 'dither' field exists to
|
|
// soften the band seams), which turns a smooth intensity into clean concentric
|
|
// CONTOUR BANDS. Our generic ramp bake is a continuous lerp (right for terrain/
|
|
// sky). For tsphere_mtl only, snap the ramp index to this many levels so the
|
|
// warp reads as bands, not a blotchy cloud. 0 = continuous (all other ramps).
|
|
int warpBands = 0;
|
|
// EMISSIVE (tag 0x26): self-lit material colour. Pure-emissive materials
|
|
// (diffuse black + emissive set, e.g. btpolar:pintBIceEmit_mtl -- the polar
|
|
// ice mounds) render as an UNLIT glow: tex x emissive, no sun/ambient.
|
|
bool hasEmissive = false;
|
|
float emissive[3] = {0,0,0};
|
|
// LOD band (the LOD chunk's 0x2046 header = [near..far) viewing range). The
|
|
// authentic renderer selects the LOD whose band contains the camera distance;
|
|
// "take the first LOD" broke composite structures (arena buildings/ad signs)
|
|
// whose NEAR band is a detail subset and whose massing lives in the FAR bands.
|
|
float lodNear = 0.0f;
|
|
float lodFar = 1.0e9f; // default: always drawn (single-LOD models)
|
|
// PATCH-level SV_SPECIAL "PUNCH" (the 1995 L4VIDEO TestSpecialCallBack ->
|
|
// dpl_Punchize): per-texel punch-through -- black texels are HOLES (cutout
|
|
// lattice: arena scaffold layers AR01-04, gratings). Drawn SOLID they
|
|
// shingle near-coplanar over the massing = depth-shimmer "noisy surfaces".
|
|
bool punch = false;
|
|
// COCKPIT PUNCH STENCIL-CUT ROLES (task #55, decoded from the i860 VREND.MNG
|
|
// firmware 'damageize' handler): a PUNCH patch's three pmeshes are, in file
|
|
// order, {aperture MASK, visible HULL, hull twin (damage-reset record)}.
|
|
// 0 = normal batch; 1 = aperture mask (stencil-only, no colour/z);
|
|
// 2 = hull (drawn stencil-rejected under the mask = the window cutouts).
|
|
int copRole = 0;
|
|
// LAYER DEPTH BIAS (additive objects): the layers contain EXACTLY-COPLANAR
|
|
// duplicated surfaces (flush wall patches between detail + massing). The
|
|
// IG board resolved those deterministically (exact per-polygon plane
|
|
// equations -> identical depth -> submission order wins); D3D9 interpolates
|
|
// per-vertex, so two tessellations of one plane differ by rounding per
|
|
// pixel = venetian-blind z-fight stripes, at ANY distance. Each finer
|
|
// layer gets a slightly more negative normalized-depth bias so coplanar
|
|
// overlaps always resolve to the detail layer. 0 = no bias (massing/
|
|
// non-additive).
|
|
float lodBias = 0.0f;
|
|
// SHADOW MATERIAL (e.g. basev:shadow_mtl): baked ground-shadow quads
|
|
// (MECHMOVS.BGF = two coplanar quads at y=0.1 under the wreck props).
|
|
// Drawn opaque they z-fight each other/the floor; an all-shadow object is
|
|
// routed through the mech-shadow pipeline (translucent dark, depth-biased,
|
|
// no z-write) instead.
|
|
bool shadowMat = false;
|
|
};
|
|
|
|
struct BgfData {
|
|
std::vector<BgfVtx> verts;
|
|
std::vector<uint32_t> indices; // triangle list (already double-sided)
|
|
std::vector<BgfDrawBatch> batches;
|
|
bool ok = false;
|
|
std::string error;
|
|
int tris = 0; // unique (single-sided) triangle count
|
|
int matTotal = 0, matResolved = 0;
|
|
};
|
|
|
|
// Resolve `name` (e.g. "blx_cop.bgf", basename only) under the pod content tree and
|
|
// load it. Returns false (with .error set) if not found / no geometry.
|
|
bool LoadBgfFile(const std::string& name, BgfData& out);
|
|
|
|
// DAY/NIGHT path priority (task #20): the renderer (DPLReadEnvironment) passes the
|
|
// INI's objectpath/materialpath/texmappath search dirs, MOST-SPECIFIC-FIRST, so the
|
|
// loader picks the correct time-of-day variant of a colliding stem instead of
|
|
// first-match-wins. cacheKey is "bgf" (objects), "bmf" (material libs), or "img"
|
|
// (textures). Invalidates that index so it rebuilds with the new priority.
|
|
void SetVideoPathPriority(const std::string& cacheKey, const std::vector<std::string>& dirs);
|