pose_chase.png: the MadCat assembled from its 19 parts, standing in the arena at its live articulated position. Feet, legs, torso, weapon pods, canopy plate. Not a collapsed stack at the origin, which is what every prior frame actually showed. The convention is no longer inferred -- it is read from the engine. RootRenderable's ctor writes an entity pose into a DCS with *(Matrix4x4*)dpl_GetDCSMatrix(myDCS) = localToWorld, so Matrix4x4::operator=(const AffineMatrix&) IS the wire layout: row-major, rotation in rows 0-2, zeros in column 3, translation in ROW 3 (entries 12/13/14). The old 3/7/11 choice rested on two errors, both corrected in the code comments: AffineMatrix does keep translation at 3/7/11, but because it is COLUMN-major -- citing it as precedent for a row-major layout read the storage and ignored the indexer (AFFNMTRX.HPP:99). And 'the last row made the maths blow up' came from the contaminated bisect; those crashes were the RIO fault. Rotation now composes through the engine's own Matrix4x4::operator=(const EulerAngles&) from the page's pitch/yaw/roll, and the node write uses the RootRenderable idiom (dpl_GetDCSMatrix + assign). Also banked: the chase-frame fifobridge variant -- camera framed on the articulated root instead of the arena bounds -- as the offline proof harness for any future pose question. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
596 lines
19 KiB
C++
596 lines
19 KiB
C++
//===========================================================================//
|
|
// File: btl4vid.cpp //
|
|
// Project: BattleTech //
|
|
// Contents: Implementation details for the BT video renderer //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1995, Virtual World Entertainment, Inc. //
|
|
// All Rights reserved worldwide //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#include <btl4.hpp>
|
|
#pragma hdrstop
|
|
|
|
#if !defined(BTL4VID_HPP)
|
|
# include <btl4vid.hpp>
|
|
#endif
|
|
//
|
|
// Matrix4x4 -- the DCS wire layout (see RecurseSKLFile). l4video.hpp pulls
|
|
// rotation.hpp but not matrix.hpp; L4VIDRND.CPP includes it the same way.
|
|
//
|
|
#if !defined(MATRIX_HPP)
|
|
# include <matrix.hpp>
|
|
#endif
|
|
|
|
//
|
|
// The engine's damage-zone tagging callback reads this while geometry is
|
|
// loading (L4VIDEO.CPP:527). It is a bare global there -- defined at
|
|
// L4VIDEO.CPP:351 with no header declaration -- so declare it here to set
|
|
// it around our own loads, exactly as DPLRenderer does around its.
|
|
//
|
|
extern Entity
|
|
*Entity_Being_Created;
|
|
|
|
BTL4VideoRenderer::BTL4VideoRenderer(
|
|
RendererRate calibration_rate,
|
|
RendererComplexity calibration_complexity,
|
|
RendererPriority calibration_priority,
|
|
InterestType interest_type,
|
|
InterestDepth depth_calibration
|
|
):
|
|
DPLRenderer(
|
|
calibration_rate,
|
|
calibration_complexity,
|
|
calibration_priority,
|
|
interest_type,
|
|
depth_calibration
|
|
)
|
|
{
|
|
}
|
|
|
|
BTL4VideoRenderer::~BTL4VideoRenderer()
|
|
{
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// LoadMissionImplementation -- called by Renderer::LoadMission (the authentic
|
|
// engine, CODE/RP/MUNGA/RENDERER.CPP:263) after it has set
|
|
// LoadingRendererStatus, stamped nextRenderTime and started the renderer with
|
|
// the RendererManager. This is where a GAME renderer builds the mission's
|
|
// scene content on the Division board.
|
|
//
|
|
// BRING-UP NO-OP (phase 1 of the btl4vid ladder). A no-op is a LEGAL body
|
|
// here, not a cheat: the engine's own VideoRenderer::LoadMissionImplementation
|
|
// (CODE/RP/MUNGA/VIDREND.CPP:259) is a bare Tell, and our GaugeRenderer
|
|
// (GAUGREND.CPP:3275) ships the same. The renderer therefore comes up and
|
|
// runs its frame loop with an EMPTY scene, which is exactly what we want to
|
|
// measure before writing any content.
|
|
//
|
|
// THE AUTHENTIC SHAPE, pinned by the surviving sibling header for Red
|
|
// Planet's renderer (CODE/RP/RP_L4/RPL4VID.HPP -- same engine, same board,
|
|
// same year; only the .CPP is missing there too):
|
|
//
|
|
// LoadMissionImplementation walks the mission's entities and calls
|
|
// MakeEntityRenderables(entity, model_resource, view_type) for each,
|
|
// which builds a dpl_DCS hierarchy through ReadSKLFile /
|
|
// RecurseSKLFile (the skeleton notation pages), with a material
|
|
// substitution list set up around it.
|
|
//
|
|
// The BT-specific renderables (BTReticleRenderable, BTTranslocationRenderable,
|
|
// the pending-wrecks map) come from the BT411 donor game/reconstructed/
|
|
// btl4vid.cpp -- 3188 lines -- but NOTE that port restructured this hook and
|
|
// has no method under this name, so the CONTRACT above comes from the 1995
|
|
// engine and only the CONTENT comes from the donor.
|
|
//#############################################################################
|
|
//
|
|
//
|
|
//#############################################################################
|
|
// ReadSKLFile -- build one entity's render skeleton from its .SKL file.
|
|
//
|
|
// The .SKL is a plain NotationFile. Each page is one node of the skeleton:
|
|
//
|
|
// [jointhip]
|
|
// parent=jointlocal
|
|
// Type=hingex <- joint kind (animation; unused at build)
|
|
// Object=mad_hip.bgf <- optional geometry for this node
|
|
// dzone=dz_hip <- damage-zone tags (zero or more)
|
|
// tranx=.. trany=.. tranz=..
|
|
// pitch=.. yaw=.. roll=..
|
|
// joint=jointtorso <- child pages (zero or more)
|
|
//
|
|
// so the whole model is a recursive walk from [ROOT].
|
|
//
|
|
// Files live under video\ -- the engine uses the same bare "video\\" prefix
|
|
// for its .pfx loads (L4VIDEO.CPP:1513).
|
|
//
|
|
// STAGE 1 (this version): the tree, the geometry and the parenting are real;
|
|
// every node is given an IDENTITY matrix. The model therefore collapses onto
|
|
// the entity origin and looks wrong, which is deliberate -- the STRUCTURE is
|
|
// what is being proved here, and it is provable without looking at a pixel:
|
|
// MAD.SKL declares JointCount=25 (+1 root) and DZoneCount=22, and the
|
|
// dpl3-revive capture of a REAL pod decodes as 26 DCS bodies and 22 instance
|
|
// bodies. The counts this walk reports must match.
|
|
//
|
|
// STAGE 2 is the transforms. The dpl_MATRIX convention is NOT yet proven
|
|
// (see RENDER-ROADMAP.NOTES.md) -- a DCS flush body carries 16 float32, and a
|
|
// decoded capture suggests row-major with the translation in the last row,
|
|
// but the decoder's offset is suspect by one word. Rather than guess it and
|
|
// ship a mech that renders confidently in the wrong orientation, this stage
|
|
// leaves identity in place.
|
|
//#############################################################################
|
|
//
|
|
dpl_DCS *
|
|
BTL4VideoRenderer::ReadSKLFile(
|
|
Entity *entity,
|
|
dpl_DCS *parent_dcs,
|
|
const char *skeleton_filename,
|
|
ViewFrom view_type)
|
|
{
|
|
Check(this);
|
|
Check_Pointer(skeleton_filename);
|
|
|
|
char
|
|
path[256];
|
|
strcpy(path, "video\\");
|
|
strcat(path, skeleton_filename);
|
|
|
|
NotationFile
|
|
*skeleton = new NotationFile(path);
|
|
Register_Object(skeleton);
|
|
|
|
if (skeleton->PageCount() == 0)
|
|
{
|
|
DEBUG_STREAM << "[skl] could not read " << path << "\n" << flush;
|
|
Unregister_Object(skeleton);
|
|
delete skeleton;
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Every node of this model shares one zone, switched on and flushed by
|
|
// the engine helper (L4VIDEO.CPP:1338).
|
|
//
|
|
dpl_ZONE
|
|
*zone = MakeNewZone();
|
|
|
|
int
|
|
node_count = 0,
|
|
object_count = 0;
|
|
|
|
//
|
|
// The ROOT page's DCS attaches under the caller's parent (the mech's
|
|
// RootRenderable DCS, so the model rides the entity's localToWorld) or,
|
|
// with no parent, straight to the scene -- the old bring-up behaviour,
|
|
// which parks the model at the world origin.
|
|
//
|
|
dpl_DCS
|
|
*root = RecurseSKLFile(
|
|
entity, parent_dcs, skeleton, "ROOT", 0, view_type,
|
|
zone, &node_count, &object_count);
|
|
|
|
DEBUG_STREAM << "[skl] " << path
|
|
<< " -> " << node_count << " nodes, "
|
|
<< object_count << " objects\n" << flush;
|
|
|
|
//
|
|
// The engine guards this the same way (L4VIDEO.CPP, DPLReadEnvironment):
|
|
// ~NotationFile REWRITES the file when dirtyFlag is set, and these are
|
|
// the game's shipped .SKL data files.
|
|
//
|
|
Verify(!skeleton->IsDirty());
|
|
Unregister_Object(skeleton);
|
|
delete skeleton;
|
|
return root;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// RecurseSKLFile -- one page of the skeleton, then its children.
|
|
//#############################################################################
|
|
//
|
|
dpl_DCS *
|
|
BTL4VideoRenderer::RecurseSKLFile(
|
|
Entity *entity,
|
|
dpl_DCS *parent_dcs,
|
|
NotationFile *skeleton,
|
|
const char *page_name,
|
|
int recursion_depth,
|
|
ViewFrom view_type,
|
|
dpl_ZONE *zone,
|
|
int *node_count,
|
|
int *object_count)
|
|
{
|
|
Check(this);
|
|
Check(skeleton);
|
|
Check_Pointer(page_name);
|
|
|
|
if (!skeleton->PageExists(page_name))
|
|
{
|
|
DEBUG_STREAM << "[skl] missing page '" << page_name << "'\n" << flush;
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// A guard, not a limit: the file is authored data and a bad parent= chain
|
|
// could otherwise recurse forever. The deepest real chain in MAD.SKL is
|
|
// nowhere near this.
|
|
//
|
|
if (recursion_depth > 32)
|
|
{
|
|
DEBUG_STREAM << "[skl] recursion too deep at '" << page_name
|
|
<< "'\n" << flush;
|
|
return NULL;
|
|
}
|
|
|
|
dpl_DCS
|
|
*dcs = dpl_NewDCS();
|
|
Check_Pointer(dcs);
|
|
dpl_SetDCSZone(dcs, zone);
|
|
|
|
//
|
|
// The node's LOCAL TRANSFORM, in the engine's own terms -- the wire
|
|
// convention is no longer inferred from captures, it is READ from the
|
|
// engine: RootRenderable's ctor (L4VIDRND.CPP:853) writes an entity pose
|
|
// into a DCS with
|
|
//
|
|
// *(Matrix4x4*)dpl_GetDCSMatrix(myDCS) = myEntity->localToWorld;
|
|
//
|
|
// so whatever Matrix4x4::operator=(const AffineMatrix&) produces IS the
|
|
// DCS matrix layout. Reading it (MATRIX.CPP:130): Matrix4x4 is ROW-major
|
|
// (MATRIX.HPP:113, entries[(Row<<2)+Column]) with rotation in rows 0-2,
|
|
// ZEROS in column 3, and the TRANSLATION IN ROW 3 -- entries 12/13/14.
|
|
//
|
|
// (The previous revision put translation at entries 3/7/11, quoting
|
|
// AffineMatrix as precedent. AffineMatrix does keep translation at
|
|
// 3/7/11 -- but because it is COLUMN-major, entries[(column<<2)+row]
|
|
// (AFFNMTRX.HPP:99); its (3,c) translation row lands at 3/7/11 by
|
|
// storage, not by convention. Matrix4x4 transposes that on copy. The
|
|
// 'last row made the maths blow up' claim attached to the old choice
|
|
// came from the contaminated bisect -- the crashes were the RIO fault.)
|
|
//
|
|
// Rotation comes from the page's pitch/yaw/roll through the engine's own
|
|
// Matrix4x4::operator=(const EulerAngles&), so the rotation ORDER is the
|
|
// engine's by construction, not a guess. MAD.SKL base-pose angles are
|
|
// all 0 or ~1e-3, so this is nearly identity today -- but it is the
|
|
// correct compose for any skeleton authored with real angles.
|
|
//
|
|
Scalar
|
|
tran_x = 0.0f,
|
|
tran_y = 0.0f,
|
|
tran_z = 0.0f,
|
|
rot_pitch = 0.0f,
|
|
rot_yaw = 0.0f,
|
|
rot_roll = 0.0f;
|
|
skeleton->GetEntry(page_name, "tranx", &tran_x);
|
|
skeleton->GetEntry(page_name, "trany", &tran_y);
|
|
skeleton->GetEntry(page_name, "tranz", &tran_z);
|
|
skeleton->GetEntry(page_name, "pitch", &rot_pitch);
|
|
skeleton->GetEntry(page_name, "yaw", &rot_yaw);
|
|
skeleton->GetEntry(page_name, "roll", &rot_roll);
|
|
|
|
Matrix4x4
|
|
node_matrix;
|
|
node_matrix = EulerAngles(rot_pitch, rot_yaw, rot_roll);
|
|
node_matrix(3,0) = tran_x;
|
|
node_matrix(3,1) = tran_y;
|
|
node_matrix(3,2) = tran_z;
|
|
|
|
//
|
|
// Write in place and flush -- the same idiom as RootRenderable's ctor
|
|
// (dpl_GetDCSMatrix + assign), not dpl_SetDCSMatrix.
|
|
//
|
|
float32
|
|
*dcs_matrix = dpl_GetDCSMatrix(dcs);
|
|
Check_Pointer(dcs_matrix);
|
|
*(Matrix4x4 *)dcs_matrix = node_matrix;
|
|
|
|
if (parent_dcs != NULL)
|
|
{
|
|
dpl_AddDCSToDCS(parent_dcs, dcs);
|
|
}
|
|
else
|
|
{
|
|
dpl_AddDCSToScene(dcs);
|
|
}
|
|
++(*node_count);
|
|
|
|
//
|
|
// This node's geometry, if it has any. Entity_Being_Created is already
|
|
// set by our caller so the library's C callback can tag the geometry with
|
|
// damage zones (L4VIDEO.CPP:4176).
|
|
//
|
|
const char
|
|
*object_name;
|
|
if (skeleton->GetEntry(page_name, "Object", &object_name) && object_name)
|
|
{
|
|
dpl_OBJECT
|
|
*object = dpl_LoadObject((char *)object_name, dpl_load_normal);
|
|
if (object != NULL)
|
|
{
|
|
dpl_INSTANCE
|
|
*instance = dpl_NewInstance();
|
|
Check_Pointer(instance);
|
|
dpl_SetInstanceObject(instance, object);
|
|
dpl_AddInstanceToDCS(dcs, instance);
|
|
dpl_FlushInstance(instance);
|
|
++(*object_count);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_STREAM << "[skl] couldn't load object " << object_name
|
|
<< " for '" << page_name << "'\n" << flush;
|
|
}
|
|
}
|
|
|
|
dpl_FlushDCS(dcs);
|
|
|
|
//
|
|
// Children. Repeated "joint=" entries: the entry NAME is "joint" and the
|
|
// VALUE (dataReference) is the child page -- the same shape as the
|
|
// engine's objectpath= walk at L4VIDEO.CPP:1858.
|
|
//
|
|
NameList
|
|
*children = skeleton->MakeEntryList(page_name, "joint");
|
|
if (children != NULL)
|
|
{
|
|
Register_Object(children);
|
|
|
|
NameList::Entry
|
|
*entry;
|
|
for (entry = children->GetFirstEntry();
|
|
entry != NULL;
|
|
entry = entry->GetNextEntry())
|
|
{
|
|
const char
|
|
*child_page = (const char *)entry->dataReference;
|
|
if (child_page != NULL && *child_page != '\0')
|
|
{
|
|
RecurseSKLFile(
|
|
entity, dcs, skeleton, child_page, recursion_depth + 1,
|
|
view_type, zone, node_count, object_count);
|
|
}
|
|
}
|
|
|
|
Unregister_Object(children);
|
|
delete children;
|
|
}
|
|
|
|
return dcs;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// MakeEntityRenderables -- the game level of the renderable factory.
|
|
//
|
|
// The engine's DPLRenderer::MakeEntityRenderables (L4VIDEO.CPP:4151) knows
|
|
// the ENGINE entity classes and calls DOWN to
|
|
// VideoRenderer::MakeEntityRenderables for anything else, which only prints
|
|
// Entity <id> class<n> couldn't figure out how to MakeEntityRenderables
|
|
// So every BT class has to be answered here.
|
|
//
|
|
// FIRST ANSWER: BTPlayer (class 3035) carries no graphics. The engine
|
|
// already does exactly this for its own PlayerClassID -- an empty case --
|
|
// and BT's player is simply a different id it cannot know about. This is
|
|
// the class the live pod run complained about.
|
|
//
|
|
// Everything else still chains to the engine, so this override can only
|
|
// ADD answers, never remove the ones DPLRenderer already gives.
|
|
//#############################################################################
|
|
//
|
|
void
|
|
BTL4VideoRenderer::MakeEntityRenderables(
|
|
Entity *entity,
|
|
ResourceDescription *model_resource,
|
|
ViewFrom view_type)
|
|
{
|
|
Check(this);
|
|
Check(entity);
|
|
|
|
//
|
|
// RENDERABLE-BUILD TRACE (env BT_MER_LOG).
|
|
//
|
|
// The mission never launches because 530 renderer events queue at
|
|
// priority 0 during load and the background pump drains only one per
|
|
// seven frames, and the page fault lands on one of the LAST ~35 of them.
|
|
// So the fault belongs to a specific entity, not to elapsed time. The
|
|
// oldest log named it -- class 42, UnscalableTerrain -- but that was a
|
|
// different build, so this prints the class of every entity as its
|
|
// renderables are built and the last line before the fault names the
|
|
// culprit outright. One line per entity, not per frame: ~530 lines for a
|
|
// whole run, which the COM3 log carries without the 3x cost that per-frame
|
|
// logging brought.
|
|
//
|
|
if (getenv("BT_MER_LOG"))
|
|
{
|
|
static int
|
|
mer_count = 0;
|
|
|
|
mer_count++;
|
|
DEBUG_STREAM << "[mer] " << mer_count
|
|
<< " class=" << (int)entity->GetClassID()
|
|
<< " view=" << (int)view_type
|
|
<< " res=" << (model_resource ? 1 : 0)
|
|
<< endl << flush;
|
|
}
|
|
|
|
switch (entity->GetClassID())
|
|
{
|
|
case RegisteredClass::MechClassID:
|
|
//
|
|
// The mech's video resource is a SKELETON. Walk the chain the
|
|
// same way the engine does (L4VIDEO.CPP:4250) and hand every
|
|
// Skeleton entry to ReadSKLFile; anything else falls through to
|
|
// the engine, which knows what to do with plain objects.
|
|
//
|
|
{
|
|
if (model_resource == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ChainOf<L4VideoObjectWrapper*>
|
|
video_chain(NULL);
|
|
L4VideoObjectWrapper::BuildVideoObjectChainFromResource(
|
|
&video_chain, model_resource);
|
|
|
|
ChainIteratorOf<L4VideoObjectWrapper*>
|
|
video_iterator(video_chain);
|
|
L4VideoObjectWrapper
|
|
*video_wrapper;
|
|
Logical
|
|
handled = False;
|
|
|
|
//
|
|
// BT_NO_SKL skips the skeleton build so the SAME binary can be
|
|
// run with and without it -- the only way to attribute the
|
|
// intermittent pod crash without a rebuild between samples.
|
|
//
|
|
if (getenv("BT_NO_SKL") != NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Entity_Being_Created = entity;
|
|
video_iterator.First();
|
|
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
|
|
{
|
|
const L4VideoObject
|
|
*video_object = video_wrapper->GetVideoObject();
|
|
|
|
if (getenv("BT_MER_LOG"))
|
|
{
|
|
DEBUG_STREAM << " [vid] type="
|
|
<< (int)video_object->GetResourceType()
|
|
<< " file='" << video_object->GetObjectFilename()
|
|
<< "'" << endl << flush;
|
|
}
|
|
|
|
if (video_object->GetResourceType()
|
|
== L4VideoObject::Skeleton)
|
|
{
|
|
//
|
|
// THE ENGINE COMPOSITION, mirrored from the Mover branch
|
|
// of DPLRenderer::MakeEntityRenderables
|
|
// (L4VIDEO.CPP:4795-4860), which the base cannot apply
|
|
// here because it only accepts Object/Rubble resources --
|
|
// chaining it with a Skeleton just prints "wrong video
|
|
// resource type" and builds NOTHING (measured: zero
|
|
// vr_flush_dcs_artic records on the wire, camera frozen
|
|
// at the world origin, and the mech statue parked there
|
|
// with the eye inside it).
|
|
//
|
|
// 1. A DYNAMIC RootRenderable. Its ctor adds its DCS to
|
|
// the scene and seeds it from entity->localToWorld;
|
|
// its Execute re-flushes whenever the entity moves.
|
|
// That per-frame flush is the ONLY source of wire
|
|
// articulation (0x1f) for the vehicle -- without it
|
|
// nothing on the board ever moves. NULL graphical
|
|
// object exactly like the CameraShip inside-view case
|
|
// (L4VIDEO.CPP:4548): the skeleton supplies the
|
|
// geometry.
|
|
//
|
|
RootRenderable
|
|
*this_root = new RootRenderable(
|
|
entity,
|
|
RootRenderable::Dynamic,
|
|
NULL,
|
|
dplMainZone,
|
|
dpl_isect_mode_obj,
|
|
NULL);
|
|
Register_Object(this_root);
|
|
|
|
dpl_DCS
|
|
*root_DCS = this_root->GetDCS();
|
|
|
|
//
|
|
// 2. The skeleton hangs UNDER the root DCS -- so the whole
|
|
// model rides the entity's localToWorld instead of
|
|
// being parked at the world origin.
|
|
//
|
|
ReadSKLFile(entity, root_DCS,
|
|
video_object->GetObjectFilename(), view_type);
|
|
|
|
//
|
|
// 3. Inside view: the eyepoint, driven off the same root
|
|
// DCS plus the mech's published EyepointRotation --
|
|
// this is what the camera follows.
|
|
//
|
|
if (view_type == insideEntity)
|
|
{
|
|
EulerAngles
|
|
*eyepoint_rotation =
|
|
(EulerAngles *)entity->GetAttributePointer(
|
|
"EyepointRotation");
|
|
DPLEyeRenderable
|
|
*this_eye = new DPLEyeRenderable(
|
|
entity,
|
|
dplMainZone,
|
|
LinearMatrix::Identity,
|
|
root_DCS,
|
|
dplMainView,
|
|
eyepoint_rotation);
|
|
Register_Object(this_eye);
|
|
}
|
|
handled = True;
|
|
}
|
|
}
|
|
Entity_Being_Created = NULL;
|
|
|
|
//
|
|
// Only chain the base when NO skeleton was found -- for a
|
|
// skeleton it contributes nothing but the complaint, and the
|
|
// root/eye it would otherwise build were built above.
|
|
//
|
|
if (!handled)
|
|
{
|
|
if (getenv("BT_MER_LOG"))
|
|
{
|
|
DEBUG_STREAM << " [chain] no skeleton -> DPLRenderer"
|
|
<< endl << flush;
|
|
}
|
|
DPLRenderer::MakeEntityRenderables(
|
|
entity, model_resource, view_type);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RegisteredClass::BTPlayerClassID:
|
|
//
|
|
// No graphics -- the player is a control/scoring entity.
|
|
//
|
|
break;
|
|
|
|
default:
|
|
DPLRenderer::MakeEntityRenderables(entity, model_resource, view_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
BTL4VideoRenderer::LoadMissionImplementation(Mission *mission)
|
|
{
|
|
Check(this);
|
|
//
|
|
// CHAIN THE BASE. DPLRenderer::LoadMissionImplementation
|
|
// (L4VIDEO.CPP:6007) is NOT empty -- it reads the renderer environment
|
|
// and loads the name bitmaps. DPLReadEnvironment opens the
|
|
// notation file named by L4DPLCFG (SETENV.BAT defaults it to
|
|
// btdpl.ini) and hands its "main" page to DPLReadINIPage, which walks
|
|
// the compare/branch pages for this location/time and calls
|
|
// dpl_SetObjectFilePath / material / texmap from the objectpath=
|
|
// entries (L4VIDEO.CPP:1852). It is PRIVATE to DPLRenderer, so the
|
|
// game renderer reaches it only by chaining -- which is the whole
|
|
// point: an override here REPLACES the base, it does not extend it.
|
|
//
|
|
// WITHOUT IT every dpl_LoadObject returns NULL. The live pod run
|
|
// failed all 40 arena objects (sky / aw01..aw04 / afloor / bcor1 /
|
|
// bdet1 / bdet2 / bpip1) and ended in "NULL instance", while the
|
|
// SHIPPED binary on the SAME rig loaded every one of them. The
|
|
// models were never missing -- the loader simply had no paths, because
|
|
// the bring-up no-op that used to live here SUPPRESSED the base.
|
|
//
|
|
DPLRenderer::LoadMissionImplementation(mission);
|
|
}
|
|
|