Files
BT412/game/reconstructed/btl4vid.hpp
T
arcattackandClaude Fable 5 5236d4263a Wreck sink (quadratic burial) + ldbr debris field -- the 1996 script completed
Answers "does the wreck fade away?": YES, by sinking.  FUN_00456410 (the 1996
sink renderable) computes offsetY = rate * t^2; the hulk's authored rate is
-0.025 -> the ~7-unit hulk is fully underground ~17s after the kill.  The
script also pairs the standing hulk with the LDBR strewn-debris field (12x13u
flat scatter), parented together and sinking together.

Also verified from the mesh data: BLHDBR (1537 verts -- more than the intact
torso) IS the authored Blackhawk wreck: the classic standing-leg-in-rubble
sculpt.  The "just a leg standing there" report is the authentic art.
(THRDBR -- the mesh the 1996 script hardcoded -- parses to ZERO vertices;
more evidence the hardcode was an unfinished dev shortcut.)

Implementation: SwapToWreck adds the ldbr piece; TickWreck applies the
quadratic sink per frame (driven from the dead mech's UpdateDeathState),
hides both pieces at burial and reports it so the wreck-smoke re-arm stops
with the wreck.  DPLStaticChildRenderable::SetOffsetTranslation added
(Execute re-reads OrientationMatrix per frame -- same in-place idiom as
SetDrawObj).

Lifecycle verified live: kill -> 'blhdbr.bgf' + ldbr debris -> smoke re-arm
@10s -> wreck buried @~17s -> smoke stops.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:55:01 -05:00

689 lines
21 KiB
C++

//===========================================================================//
// File: btl4vid.hpp //
// Project: BattleTech Brick: Video Renderer Manager //
// Contents: BTL4VideoRenderer -- the BattleTech L4 out-the-window 3D WORLD //
// renderer manager. Builds the per-entity renderable tree for the //
// main view each time an interesting entity becomes visible: //
// the player mech + its weapons/effects/reticle, terrain, and //
// other movers, walking the jointed-mover segment table and the //
// subsystem roster and submitting renderables to the scene. //
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 02/13/95 CPB Initial coding. //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1996, Virtual World Entertainment, Inc. //
// PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
//
// RECONSTRUCTED from the shipped binary (BTL4OPT.EXE). NO header survived.
// Class/member/method names are taken from the embedded assert path
// "d:\tesla\bt\bt_l4\BTL4VID.CPP", the embedded class name string
// "BTL4VideoRenderer::Material name ..." (@0051d6f8), and the direct
// Red Planet analogue RP_L4/RPL4VID.cpp (RPL4VideoRenderer) plus the surviving
// MUNGA headers MUNGA_L4/L4VIDEO.HPP and MUNGA_L4/L4VIDRND.HPP. Each method
// cites its originating @ADDR.
//
// PORTING NOTES (reconstruction -> WinTesla engine)
// * BTL4VideoRenderer is the BattleTech analogue of RPL4VideoRenderer; both
// subclass the concrete L4 manager DPLRenderer (L4VIDEO.HPP). Deriving
// from DPLRenderer supplies SetFogStyle/AddDynamicRenderable, the fog
// colour/plane fields, dplMainView/dplMainZone/dplDeathZone and the
// vehicleReticle slot the recovered code touches.
// * The 1996 BattleTech build drove a pre-DPL renderable hierarchy whose
// joint renderables parent on a dpl_DCS* (rather than the WinTesla
// HierarchicalDrawComponent* parent). That hierarchy did NOT survive in
// source, so the BT-specific renderables it constructs are declared here
// (BTRootRenderable / BTHingeRenderable / ... ) with the signatures the
// recovered code actually calls. They are distinct from the modern
// L4VIDRND.HPP renderables of the same role (which is why the targeting
// reticle is BTReticleRenderable, NOT the engine's ReticleRenderable --
// the BT object is 0x358 bytes and carries up to 10 AddWeapon pip markers).
//
#if !defined(BTL4VID_HPP)
# define BTL4VID_HPP
//
// Forward declaration so MUNGA_L4/l4vidrnd.h (which references DPLRenderer in
// HierarchicalDrawComponent before l4video.h defines it) parses cleanly
// regardless of include order.
//
class DPLRenderer;
#if !defined(L4VIDEO_HPP)
# include <l4video.hpp> // DPLRenderer / VideoRenderer / INTERSECT_ALL
#endif
#if !defined(L4VIDRND_HPP)
# include <l4vidrnd.hpp> // VideoComponent / the *Renderable hierarchy
#endif
#if !defined(RETICLE_HPP)
# include <reticle.hpp>
#endif
#if !defined(STATE_HPP)
# include <state.hpp> // StateIndicator
#endif
#if !defined(JMOVER_HPP)
# include <jmover.hpp> // JointedMover
#endif
#if !defined(JOINT_HPP)
# include <joint.hpp> // Joint / JointSubsystem / Hinge
#endif
#if !defined(SEGMENT_HPP)
# include <segment.hpp> // EntitySegment
#endif
#if !defined(SUBSYSTM_HPP)
# include <subsystm.hpp> // Subsystem
#endif
#if !defined(NAMELIST_HPP)
# include <namelist.hpp> // NameList / NameList::Entry
#endif
#include <map> // per-mech render-tree bookkeeping (RemakeEntity)
class Entity;
class Mission;
class ResourceDescription;
class NotationFile;
//
// BT "scene" handle. In the 1996 build the world-view renderables were
// built against an opaque scene root (the analogue of RP's dpl_ZONE pair);
// it is used pointer-only in this module, so a forward declaration suffices.
//
class Scene;
//
//===========================================================================//
// Legacy 2D / material-callback DPL entry points
//===========================================================================//
//
// The dpl2d_ display-list layer and the material-name-substitution callback
// hook were part of libDPL (the IG-board driver) and were NOT carried into the
// WinTesla engine. The recovered reticle builder and material setup call them
// directly; declare them here so the translation unit compiles (resolved at
// link time against the libDPL shim).
//
dpl2d_DISPLAY* dpl2d_NewDisplayList();
void dpl2d_Begin(dpl2d_DISPLAY *list, int mode);
void dpl2d_SetColor(dpl2d_DISPLAY *list, Scalar red, Scalar green, Scalar blue);
void dpl2d_Circle(dpl2d_DISPLAY *list, Scalar x, Scalar y, Scalar radius, int fill);
void dpl2d_PushMatrix(dpl2d_DISPLAY *list);
void dpl2d_PopMatrix(dpl2d_DISPLAY *list);
void dpl2d_MoveTo(dpl2d_DISPLAY *list, Scalar x, Scalar y);
void dpl2d_End(dpl2d_DISPLAY *list);
void dpl2d_Compile(dpl2d_DISPLAY *list);
void dpl_SetMaterialNameCallback(char *(*callback)(char *source));
//
//===========================================================================//
// BattleTech renderable hierarchy (pre-DPL)
//===========================================================================//
//
// Common base: every BT world renderable carries the dpl_DCS / dpl_INSTANCE it
// creates and offers the graphical (Add) / control (Connect) hook-up the world
// container uses. Modelled on VideoComponent so a BTRootRenderable can be
// returned as the VideoComponent* mech root.
//
class BTRenderableBase:
public VideoComponent
{
public:
BTRenderableBase(
Entity *entity,
VideoExecutionType execution_type = DynamicVideoExecutionType)
: VideoComponent(entity, execution_type), myDCS(0), myInstance(0) {}
dpl_DCS* GetDCS() { return myDCS; }
dpl_INSTANCE* GetInstance() { return myInstance; }
//
// Graphical / control hook-up. These hide VideoComponent::Add/Connect
// (which take VideoComponent*) so the world container can accept any
// renderable in the BT hierarchy by its common HierarchicalDrawComponent
// base.
//
void Add(HierarchicalDrawComponent *child) { if (child) addChild(child); }
void Connect(HierarchicalDrawComponent *child) { if (child) addChild(child); }
protected:
dpl_DCS *myDCS;
dpl_INSTANCE *myInstance;
};
//
// World container renderable (FUN_00455de4, alloc 0x60). Holds the death /
// world DCS roots for one entity's tree.
//
class BTWorldContainerRenderable:
public BTRenderableBase
{
public:
BTWorldContainerRenderable(
Entity *entity,
int execution_type,
int in_death_zone,
Scene *scene);
};
//
// Root DCS object renderable (FUN_00453578, alloc 0x64).
//
class BTRootRenderable:
public BTRenderableBase
{
public:
BTRootRenderable(
Entity *entity,
int execution_type,
d3d_OBJECT *graphical_object,
Scene *scene,
int intersect_mode,
uint32 intersect_mask);
};
//
// Static / jointed child DCS renderables.
//
class BTDCSObjectRenderable:
public BTRenderableBase
{
public:
BTDCSObjectRenderable( // FUN_0045848c, alloc 0x50
Entity *entity,
Scene *scene,
d3d_OBJECT *graphical_object,
int execution_type,
uint32 intersect_mask,
LinearMatrix *offset_matrix,
dpl_DCS *parent_DCS);
};
class BTHingeRenderable:
public BTRenderableBase
{
public:
BTHingeRenderable( // FUN_004537e8, alloc 0x78
Entity *entity,
int execution_type,
d3d_OBJECT *graphical_object,
Scene *scene,
int exec2,
uint32 intersect_mask,
dpl_DCS *parent_DCS,
LinearMatrix *offset_matrix,
const Hinge *my_hinge);
};
class BTBallJointRenderable:
public BTRenderableBase
{
public:
BTBallJointRenderable( // FUN_004539b4, alloc 0x7c
Entity *entity,
int execution_type,
d3d_OBJECT *graphical_object,
Scene *scene,
int exec2,
uint32 intersect_mask,
dpl_DCS *parent_DCS,
LinearMatrix *offset_matrix,
const EulerAngles *my_euler);
};
class BTBallTranslateJointRenderable:
public BTRenderableBase
{
public:
BTBallTranslateJointRenderable( // FUN_00453ac4, alloc 0x8c
Entity *entity,
int execution_type,
d3d_OBJECT *graphical_object,
Scene *scene,
int exec2,
uint32 intersect_mask,
dpl_DCS *parent_DCS,
LinearMatrix *offset_matrix,
const EulerAngles *my_euler,
const Point3D *my_translation);
};
//
// Inside-view eyepoint (POV/eye) renderable (FUN_004579a8, alloc 0x5c).
//
class BTEyeRenderable:
public BTRenderableBase
{
public:
BTEyeRenderable(
Entity *entity,
Scene *scene,
LinearMatrix *offset_matrix,
dpl_DCS *parent_DCS,
dpl_VIEW *this_view,
EulerAngles *eyepoint_rotation);
};
//
// Death / explosion effect renderable (FUN_00453f18, alloc 0x48).
//
class BTDeathEffectRenderable:
public BTRenderableBase
{
public:
BTDeathEffectRenderable(
Entity *entity,
int execution_type,
dpl_VIEW *this_view,
Scene *scene,
dpl_DCS *parent_DCS,
StateIndicator *death_state,
int death_control_state);
};
//
// Marker (timestamp/beacon) watcher renderable (FUN_00458c58, alloc 0x120).
//
class BTMarkerWatcherRenderable:
public BTRenderableBase
{
public:
BTMarkerWatcherRenderable(
Entity *entity,
int execution_type,
dpl_VIEW *this_view,
dpl_DCS *parent_DCS);
};
//
// Drop-zone translocation effect renderable (FUN_00458d2c, alloc 0x40).
//
class BTTranslocationRenderable:
public BTRenderableBase
{
public:
BTTranslocationRenderable(
Entity *entity,
int execution_type,
dpl_VIEW *this_view,
StateIndicator *effect_trigger,
Point3D *drop_zone,
int effect_control_state);
};
//
// Player POV mission start/end fade renderable (FUN_00454394, alloc 0x50).
// (BT-local twin of the engine's POVStartEndRenderable; the BT ctor takes the
// main-view handle plus the world/death zones explicitly.)
//
class BTPOVStartEndRenderable:
public BTRenderableBase
{
public:
BTPOVStartEndRenderable(
Entity *entity,
int execution_type,
dpl_VIEW *this_view,
dpl_ZONE *main_zone,
dpl_ZONE *death_zone,
StateIndicator *effect_trigger,
float red_fog,
float green_fog,
float blue_fog,
float near_fog,
float far_fog,
int start_mission_state,
int end_mission_state);
};
//
// Coolant / reservoir / tracer reservoir effect renderable (FUN_00456a68,
// alloc 0x138).
//
class BTTracerEffectRenderable:
public BTRenderableBase
{
public:
BTTracerEffectRenderable(
Entity *entity,
int execution_type,
void *state_attr,
int mode,
dpl_ZONE *zone,
dpl_DCS *parent_DCS,
LinearMatrix *offset_matrix);
};
//
// Emitter beam renderable (FUN_004593c0, alloc 0x80).
//
class BTEmitterBeamRenderable:
public BTRenderableBase
{
public:
BTEmitterBeamRenderable(
Entity *entity,
int execution_type,
d3d_OBJECT *graphical_object,
Scene *scene,
int exec2,
uint32 intersect_mask,
dpl_DCS *parent_DCS,
LinearMatrix *offset_matrix,
void *sim_state,
int flag,
void *beam_scale,
void *beam_orient);
};
//
// PPC beam renderable (FUN_004590d8, alloc 0x88).
//
class BTPPCBeamRenderable:
public BTRenderableBase
{
public:
BTPPCBeamRenderable(
Entity *entity,
int execution_type,
d3d_OBJECT *graphical_object,
Scene *scene,
int exec2,
uint32 intersect_mask,
dpl_DCS *parent_DCS,
LinearMatrix *offset_matrix,
void *sim_state,
int flag,
void *beam_scale,
void *beam_orient,
float beam_width);
};
//
// Searchlight LightOn -> spotlight connector (FUN_0045612c, alloc 0x2c).
//
class BTLightConnection:
public BTRenderableBase
{
public:
BTLightConnection(
Entity *entity,
int execution_type,
dpl_INSTANCE *spot_instance,
int flag,
int *light_on);
};
//
//#############################################################################
// BTReticleRenderable (BattleTech targeting reticle)
//#############################################################################
//
// File-private to btl4vid.cpp. Parallels the engine ReticleRenderable
// (L4VIDRND.HPP) but the BT object is 0x358 bytes and carries up to 10 weapon
// pip / range markers built by AddWeapon. Constructed @004cc40c for the inside
// view; weapon markers are appended by MakeMechRenderables via AddWeapon.
//
// header layout actually touched (this+...):
// +0x38 weaponCount (cap 10 -- "Tried to display too many weapons")
// ... parallel per-weapon arrays (see members below)
// reticle geometry: minRange@0x230, maxRange@0x22c, rangeScale@0x234,
// originX@0x1fc, originY@0x200, scaleY@0x204, biasX@0x208
//
class BTReticleRenderable:
public VideoRenderable
{
public:
BTReticleRenderable( // @004cc40c
Entity *entity,
int execution_type,
Reticle **my_reticle,
dpl_VIEW *this_view,
void *pip_position_template,
void *pip_color_template,
void *range_template,
void *range_lo_template,
void *range_hi_template,
Scalar max_range,
int flag,
void *sim_template_a,
void *sim_template_b,
void *sim_template_c);
~BTReticleRenderable();
//
// Append a weapon range/pip marker to the reticle. @004cdac0
//
void
AddWeapon(
Scalar weapon_range,
int pip_position,
StateIndicator *within_range_attr,
int extended_range,
Scalar pip_red,
Scalar pip_green,
Scalar pip_blue,
StateIndicator *sim_state,
int arg9,
int arg10,
StateIndicator *sim_state2,
int arg12,
int weapon_mode);
void
Execute();
protected:
//
// Per-weapon parallel arrays (cap 10).
//
int weaponCount; // +0x38
int weaponMode[10]; // +0x3c
Scalar pipColor[10]; // +0x64
StateIndicator *withinRangeAttr[10]; // +0x8c
StateIndicator *simState[10]; // +0xb4
int simState2Value[10]; // +0xdc
int arg12Value[10]; // +0x104
StateIndicator *simState2Ptr[10]; // +0x130
int arg12Ptr[10]; // +0x158
Scalar *rangeAttr[10]; // +0x18c
Scalar rangeCache[10]; // +0x1b4
dpl2d_DISPLAY *pipDisplayListB[10]; // +0x288
dpl2d_DISPLAY *pipDisplayListA[10]; // +0x2b0
//
// Calibrated reticle geometry.
//
Scalar originX; // +0x1fc
Scalar originY; // +0x200
Scalar scaleY; // +0x204
Scalar biasX; // +0x208
Scalar maxRange; // +0x22c
Scalar minRange; // +0x230
Scalar rangeScale; // +0x234
};
//
//#############################################################################
// BTL4VideoRenderer
//#############################################################################
//
// @ vtable: BattleTech L4 video renderer manager subclass. Base is the MUNGA
// L4 renderer manager (DPLRenderer, l4video.hpp). Parallels RPL4VideoRenderer.
//
class BTL4VideoRenderer:
public DPLRenderer
{
public:
BTL4VideoRenderer(
RendererRate calibration_rate,
RendererComplexity calibration_complexity,
RendererPriority calibration_priority,
InterestType interest_type,
InterestDepth depth_calibration
);
~BTL4VideoRenderer();
Logical
TestInstance() const;
//
// Material substitution (mirrors RPL4VideoRenderer).
//
void
SetupMaterialSubstitutionList(Entity *entity); // @004d0cc0
void
TearDownMaterialSubstitutionList(); // @004d11e8
//
// RemakeEntity (damage-model swap). The 1996 binary drives a per-entity
// render state machine (Make / RemakeEntity / DestroyEntity -- the state
// name strings survive at .rdata 0x4e3f20). Only "Make" was ported (the
// tree is built ONCE at entity creation, with fixed d3d_OBJECTs).
// RemakeEntity is the model REFRESH: when a damage zone's graphic state
// changes (a segment becomes Destroyed/Gone), re-pick each segment's video-
// object variant by its zone graphic state (EntitySegment::GetVideoObjectName
// is keyed by {skeleton, graphic_state}) and swap it onto the already-built
// joint renderable IN PLACE (HierarchicalDrawComponent::Execute re-reads
// graphicalObject each frame). No teardown: the component dtor does not
// cascade to children (L4VIDRND.cpp:104) so a rebuild would leak -- the
// authentic behaviour is an in-place mesh swap.
//
void
RemakeEntityRenderables(Entity *entity);
protected:
//
// Per-mech render-tree bookkeeping so RemakeEntityRenderables can find each
// segment's joint renderable (the dcs_array in MakeMechRenderables is local
// and freed). Keyed by Entity*; one entry per built mech. segRenderable
// maps a segment SLOT (EntitySegment::GetIndex) to its draw component;
// segGState is the graphic state last applied to that slot so a swap only
// reloads geometry on an actual state change.
//
struct MechRenderTree
{
int skeletonType; // EntitySegment::SkeletonType used at build
HierarchicalDrawComponent *rootRenderable;// the tree root (wreck hulk parent)
int wrecked; // 1 = swapped to the <mech>dbr hulk
DPLStaticChildRenderable *wreckHulk; // the <mech>dbr piece (sinks)
DPLStaticChildRenderable *wreckDebris; // the ldbr scatter (sinks)
float wreckAge; // seconds since the swap
std::map<int, HierarchicalDrawComponent*> segRenderable; // slot -> joint renderable
std::map<int, int> segGState; // slot -> last applied graphic state
};
std::map<Entity*, MechRenderTree> mMechRenderTrees;
public:
//
// The wreck's quadratic SINK (the 1996 script's burial: FUN_00456410
// computes offsetY = rate * t^2; the hulk's authored rate is -0.025 ->
// the ~7-unit hulk is fully buried ~17s after the kill). Ticked per
// frame from the dead mech's UpdateDeathState. Returns 0 once the
// wreck is fully buried (the caller stops the wreck-smoke re-arm),
// 1 while anything is still visible.
//
int
TickWreck(Entity *victim, float dt);
public:
//
// The death-wreck swap (ExplosionScripts effect 104, reconstructed): the
// victim's whole visual becomes its burning hulk -- hide every segment
// mesh and hang "<prefix>dbr.bgf" (gendbr.bgf fallback) on the tree root.
// The 1996 script hardcoded thrdbr.bgf (a dev shortcut; every mech ships
// its own *DBR hulk). If the victim's tree isn't built yet, the swap is
// remembered and applied when MakeMechRenderables builds it.
//
void
SwapToWreck(Entity *victim);
protected:
//
// Renderer-manager overrides
//
void
LoadMissionImplementation(Mission *mission);
void
MakeEntityRenderables( // @004d0774
Entity *this_entity,
ResourceDescription *model_resource,
ViewFrom type);
HierarchicalDrawComponent*
MakeMechRenderables( // @004cef28
Entity *entity,
ResourceDescription *model_resource,
ViewFrom type);
//
//--------------------------------------------------------------------
// BT renderer helpers (recovered).
//--------------------------------------------------------------------
//
Scene*
GetScene(); // FUN_0045a724
dpl_VIEW*
GetMainView() { return dplMainView; } // this[0x124]
dpl_ZONE*
GetMainZone() { return dplMainZone; } // this[300]
d3d_OBJECT*
LoadObject(const char *object_name); // FUN_00498448
void
AttachToEyeDCS(dpl_DCS *root_dcs, LinearMatrix &local_to_world); // FUN_00489cec
void
AddDynamicRenderable(VideoComponent *renderable, Entity *entity); // FUN_0045a994
protected:
//
// vtvCount / vtvsExpected analogues (RPL4VideoRenderer).
//
int
mechsExpected,
mechCount;
//
// True once the linked (player) entity exists, so its root DCS is
// hooked onto the eye. ([0xb0] in the recovered layout.)
//
Logical
linkedEntityPresent;
//
// Effect zones touched by the recovered code.
//
dpl_ZONE
*scene_zone, // [0x318] coolant / reservoir effects
*tracer_zone; // [0x2e4] projectile tracers
};
//
// Sim-side bridge to RemakeEntityRenderables. MechDeathHandler (sim TU) calls
// this when a mech's damage graphic state changes; defined in btl4vid.cpp, it
// reaches the live renderer via l4_application->GetVideoRenderer(). A free
// function so the sim TU needs no renderer header (just this extern).
//
extern void BTRemakeMechModel(Entity *entity);
//
// Render bridge for the death-wreck swap (effect 104): called by the engine's
// ExplosionClassID dispatch with the explosion's entityHit (the dead mech).
//
extern void BTSwapMechToWreck(Entity *victim);
//
// Per-frame wreck sink tick (sim -> render bridge; called by the dead mech's
// UpdateDeathState with the frame dt). Returns 0 once the wreck is fully
// buried -- the caller stops re-arming the wreck smoke.
//
extern int BTWreckSinkTick(Entity *victim, float dt);
#endif // BTL4VID_HPP
//===========================================================================//