// 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 , // so it builds under the engine's C++14 target). #pragma once #include #include #include // 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 verts; std::vector indices; // triangle list (already double-sided) std::vector 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& dirs);