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>
222 lines
8.0 KiB
C++
222 lines
8.0 KiB
C++
#pragma once
|
|
|
|
#include <D3DX9.h>
|
|
#include <hash_map>
|
|
#include <string>
|
|
|
|
#define L4VERTEX_FVF (D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE | D3DFVF_NORMAL)
|
|
#define L4POINTVERTEX_FVF (D3DFVF_XYZ)
|
|
#define L4VERTEX_2D_FVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
|
|
#define L4VERTEX_2D_TEX_FVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1)
|
|
|
|
#define PASS_OPAQUE 0
|
|
#define PASS_DECAL 1
|
|
#define PASS_ALPHABLEND 2
|
|
#define PASS_SPHERE 3
|
|
#define PASS_SKY 4
|
|
// GROUND-SHADOW list (task #49b): tshd proxies draw between the STATIC opaque
|
|
// geometry (terrain) and the DYNAMIC opaque list (mech bodies) -- the classic
|
|
// decal order. The feet then z-pass over the already-drawn shadow, so the
|
|
// shadow's big terrain depth-bias can never paint OVER the mech. (The list is
|
|
// drawn with pass id PASS_ALPHABLEND so d3d_OBJECT::DrawMesh's mIsShadow
|
|
// state-block branch runs unchanged.)
|
|
#define PASS_SHADOW 5
|
|
#define PASS_TOTAL_COUNT 6
|
|
|
|
struct L4VERTEX
|
|
{
|
|
float x, y, z;
|
|
float nx, ny, nz;
|
|
DWORD color;
|
|
float u, v;
|
|
};
|
|
|
|
struct L4POINTVERTEX
|
|
{
|
|
float x, y, z;
|
|
};
|
|
|
|
struct L4VERTEX_2D
|
|
{
|
|
float x, y, z, rhw;
|
|
DWORD color;
|
|
};
|
|
|
|
struct L4VERTEX_2D_TEX
|
|
{
|
|
float x, y, z, rhw;
|
|
DWORD color;
|
|
float u, v;
|
|
};
|
|
|
|
struct L4TEXOP
|
|
{
|
|
enum WrapType
|
|
{
|
|
REPEAT,
|
|
CLAMP,
|
|
SELECT
|
|
} wrap_u, wrap_v;
|
|
|
|
LPDIRECT3DTEXTURE9 texture;
|
|
|
|
bool doScroll;
|
|
float scrollUDelta;
|
|
float scrollVDelta;
|
|
};
|
|
|
|
struct L4RAMP
|
|
{
|
|
float r0, g0, b0;
|
|
float r1, g1, b1;
|
|
};
|
|
|
|
struct L4DRAWOP
|
|
{
|
|
D3DMATERIAL9 material;
|
|
L4TEXOP texture;
|
|
bool alphaTest;
|
|
bool drawAsDecal;
|
|
bool drawAsSky;
|
|
// PORT (draw-cost fix): explicit index range for BGF-built meshes. D3DX
|
|
// DrawSubset needs an attribute TABLE; our double-sided BGF indices make
|
|
// GenerateAdjacency/OptimizeInplace fail silently, so DrawSubset fell back to
|
|
// scanning the whole attribute buffer PER CALL (~44us x ~1400 calls = ~60ms/
|
|
// frame). The loader knows each batch's exact range -- draw it directly.
|
|
int bgfStartIndex; // first index of the batch in the mesh IB
|
|
int bgfPrimCount; // triangle count (0 = not a BGF batch -> DrawSubset)
|
|
// PORT (authentic LODs): this batch's LOD viewing band [lodNear..lodFar) from
|
|
// the BGF's 0x2046 headers. DrawMesh draws only the ops whose band contains
|
|
// the camera->object distance (the IG board's distance LOD selection). A
|
|
// zero-init op (lodFar==0) is treated as always-drawn (non-BGF fallback).
|
|
float lodNear;
|
|
float lodFar;
|
|
// DIAG (BT_LOD_LOG): last band-gate visibility (0=unknown 1=visible 2=hidden)
|
|
// so DrawMesh can log per-op FLIP events during movement (which piece blinks).
|
|
int lastBandVisible;
|
|
// PUNCH (dpl_Punchize, PATCH SV_SPECIAL): cutout punch-through -- the texture's
|
|
// black texels were loaded with alpha 0; draw with alpha TEST in the opaque pass
|
|
// (z-write preserved), rejecting hole texels.
|
|
bool punchThrough;
|
|
// VERTEX-ALPHA BLEND (BGF 0x0082/0x008A verts with authored alpha < 1): an
|
|
// effect card (the FLAMEBIG flame fan). Drawn in the alpha-blend pass,
|
|
// UNLIT, colour = texture x the authored per-vertex gradient, alpha = the
|
|
// per-vertex fade (SRCALPHA/INVSRCALPHA).
|
|
bool vertexAlphaBlend;
|
|
// COCKPIT PUNCH STENCIL-CUT (task #55, i860-firmware-decoded): 0=normal,
|
|
// 1=aperture MASK (drawn stencil-only, paired with the following hull op),
|
|
// 2=HULL (drawn stencil-rejected under the mask = window cutouts).
|
|
int copRole;
|
|
// LAYER DEPTH BIAS (additive-LOD coplanar resolution; see bgfload.h): finer
|
|
// layers get a slightly more negative normalized-depth bias so EXACTLY-
|
|
// coplanar duplicated surfaces resolve to the detail layer instead of
|
|
// venetian-blind z-fighting (the board's submission-order rule, in D3D terms).
|
|
float lodDepthBias;
|
|
};
|
|
|
|
class d3d_OBJECT
|
|
{
|
|
public:
|
|
d3d_OBJECT(LPDIRECT3DDEVICE9 device, int vertCount);
|
|
d3d_OBJECT(LPDIRECT3DDEVICE9 device, LPD3DXMESH mesh, DWORD *adjacencyBuffer, int drawOpCount);
|
|
~d3d_OBJECT();
|
|
|
|
inline LPDIRECT3DDEVICE9 GetDevice() { return mDevice; }
|
|
inline int GetVertCount() { return mVertCount; }
|
|
inline int GetDrawOpCount() { return mDrawOpCount; }
|
|
inline L4DRAWOP* GetDrawOp(int index) { return &(mDrawOps[index]); }
|
|
|
|
void Draw(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame);
|
|
|
|
inline void SetRadius(float radius) { mRadius = radius; }
|
|
inline void SetOrigin(L4POINTVERTEX origin) { mOrigin = origin; }
|
|
inline void SetImmune(bool immune) { mImmune = immune; }
|
|
// BT (task #20): mark the mech's *_tshd SHADOW-PROXY geometry -- the binary's
|
|
// shadow is this flat silhouette posed by jointshadow/jointtshadow; it must
|
|
// draw TRANSLUCENT dark in the blend pass (the port drew it opaque black).
|
|
inline void SetIsShadow(int v) { mIsShadow = v; }
|
|
inline int GetIsShadow() { return mIsShadow; }
|
|
int mIsShadow;
|
|
// DIAG (BT_LOD_LOG): model basename for flip telemetry (set by LoadObjectBGF;
|
|
// MUST be initialized in BOTH ctors -- debug heap 0xCD fill reads as garbage).
|
|
char mDbgName[24];
|
|
inline void SetLocalToWorld(D3DXMATRIX localToWorld) { mLocalToWorld = localToWorld; }
|
|
|
|
inline float GetRadius() { return mRadius; }
|
|
inline L4POINTVERTEX GetOrigin() { return mOrigin; }
|
|
inline bool GetImmune() { return mImmune; }
|
|
inline D3DXMATRIX GetLocalToWorld() { return mLocalToWorld; }
|
|
inline LPD3DXMESH GetMesh() { return mMesh; }
|
|
|
|
inline d3d_OBJECT* GetNext(int pass) { return mNext[pass + 1]; }
|
|
inline d3d_OBJECT* GetPrevious( int pass) { return mPrev[pass + 1]; }
|
|
inline void SetNext(d3d_OBJECT *next, int pass) { mNext[pass + 1] = next; }
|
|
inline void SetPrevious(d3d_OBJECT *previous, int pass) { mPrev[pass + 1] = previous; }
|
|
|
|
static void ResetState(LPDIRECT3DDEVICE9 device);
|
|
static d3d_OBJECT* LoadObject(LPDIRECT3DDEVICE9 device, char *fileName);
|
|
static L4TEXOP LoadTexture(LPDIRECT3DDEVICE9 device, const char *fileName);
|
|
|
|
// PORT (turn-hitch fix): bounding-sphere frustum culling. The 1995 renderer
|
|
// drew EVERYTHING every frame (the IG board clipped in hardware); on D3D9 that
|
|
// is ~2700 draw calls/frame = ~107ms CPU. The renderer sets the frame's view
|
|
// frustum once (SetCullFrustum); Draw() then skips objects fully outside it.
|
|
static void SetCullFrustum(const D3DXMATRIX *viewProj); // NULL disables for the frame
|
|
static void SetCameraPosition(float x, float y, float z); // for LOD distance selection
|
|
static void GetCameraPosition(float *x, float *y, float *z); // wreck-flame billboard yaw
|
|
D3DXVECTOR3 mCullCenter; // model-space bounding sphere (computed at load)
|
|
float mCullRadius; // <= 0 -> unknown, never culled
|
|
|
|
// PORT (draw-cost fix): direct-draw path for BGF meshes (see L4DRAWOP). The
|
|
// mesh's own VB/IB, cached AddRef'd at load; drawn with DrawIndexedPrimitive
|
|
// per batch range instead of D3DX DrawSubset. NULL -> DrawSubset fallback.
|
|
LPDIRECT3DVERTEXBUFFER9 mBgfVB;
|
|
LPDIRECT3DINDEXBUFFER9 mBgfIB;
|
|
UINT mBgfStride;
|
|
UINT mBgfNumVerts;
|
|
|
|
private:
|
|
static d3d_OBJECT* LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName);
|
|
// Builds a mesh-backed d3d_OBJECT directly from a VWE .BGF (pod ships .bgf, not .x).
|
|
static d3d_OBJECT* LoadObjectBGF(LPDIRECT3DDEVICE9 device, char *fileName);
|
|
|
|
LPDIRECT3DDEVICE9 mDevice;
|
|
inline void SetVB(LPDIRECT3DVERTEXBUFFER9 buffer) { mVB = buffer; }
|
|
void DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame);
|
|
void DrawSpheres(int pass, const D3DXMATRIX *viewTransform);
|
|
|
|
long mID;
|
|
|
|
// vertex based
|
|
int mVertCount;
|
|
LPDIRECT3DVERTEXBUFFER9 mVB;
|
|
|
|
// mesh based
|
|
LPD3DXMESH mMesh;
|
|
|
|
int mDrawOpCount;
|
|
L4DRAWOP* mDrawOps;
|
|
|
|
bool mImmune;
|
|
D3DXMATRIX mLocalToWorld;
|
|
L4POINTVERTEX mOrigin;
|
|
float mRadius;
|
|
|
|
d3d_OBJECT *mNext[PASS_TOTAL_COUNT + 1];
|
|
d3d_OBJECT *mPrev[PASS_TOTAL_COUNT + 1];
|
|
|
|
void SetTextureScrolling(const L4TEXOP *texture, Time targetRenderFrame);
|
|
void SetTextureAddressing(L4TEXOP::WrapType wrap_u, L4TEXOP::WrapType wrap_v);
|
|
void SetTexture(LPDIRECT3DTEXTURE9 texture);
|
|
// state information for optimizing renderstate changes
|
|
static bool mLastTextureScrollState;
|
|
static L4TEXOP::WrapType mLastWrapU;
|
|
static L4TEXOP::WrapType mLastWrapV;
|
|
static bool mLastTexturingState;
|
|
|
|
static long mNextID;
|
|
static stdext::hash_map< std::string , L4TEXOP > mTextureCache;
|
|
};
|
|
|
|
extern int gNumBatches;
|