Files
BT411/engine/MUNGA_L4/bgfload.h
T
arcattackandClaude Fable 5 96b8892e7c Cockpit: the authentic punch STENCIL-CUT -- all 8 cockpits real (task #55)
The user caught the inversion (blocked viewports, see-through frames); the
answer came from the rasterizer board itself: an i860 disassembly of the
VREND.MNG firmware decoded the damageize handler (@0xf040f6f8) -- cmd 0x20
(vr_damage_action) writes the dpl_Punchize token triple onto the punch
geogroup's first three geometry chunks IN FILE ORDER:

  chunk1 = the detailed window-aperture shapes -> an invisible per-pixel MASK
  chunk2 = the coarse hull -> the ONLY colour-drawn geometry (where not masked)
  chunk3 = byte-identical hull twin -> the damage-reset record, never visible

Visible canopy = hull-minus-apertures.  Both earlier readings were wrong ways
around the same three chunks: dropping all punch left floating fragments (no
hull); hiding the duplicate pair hid the HULL and drew the MASK -- the exact
inversion observed.

Port: bgfload tags per punch patch {non-paired pmesh = mask (role 1),
first twin = hull (role 2), second twin = skip}; L4D3D DrawMesh runs
mask->stencil-1 (no colour/z-write, z-tested), hull->stencil-NOTEQUAL,
mask->stencil-0; device D24X8 -> D24S8 + stencil clear.  The cut never
touches z, so later-drawn entities show through the apertures.
BT_COP_PLATES=1 disables (diag).

In-game, all 8 mechs on pure defaults: connected dark frames with clear
viewports -- thor (the footage mech) shows the central viewport + surrounding
frame exactly as filmed.  Combat regression clean (kill + no NaN).

Arena textured punch uses the same firmware kit; the black-texel alpha path
there is a working approximation, flagged in the KB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 08:53:22 -05:00

108 lines
5.9 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;
// 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);