Files
BT411/engine/MUNGA_L4/L4VIDEO.cpp
T
CydandClaude Opus 5 61563c9efe Glass cockpit refit: one mode resolver, one button geometry, and a cockpit that scales
Replicates the RP412 cockpit line into BT411's own architecture -- porting the
geometry and keeping our renderers, so BT_SHOT single-frame verification stays
intact.

MODE RESOLVER.  Where the secondary displays go was decided in TWO places with
duplicated precedence, and the boot banner read NEITHER -- it announced
"per-display cockpit windows" for every glass boot, surround included.  The
split had also broken BT_COCKPIT=0: documented as the dock-bottom opt-out, the
profile block converted it to BT_GLASS_PANELS=1, so the docked strip was
UNREACHABLE under the glass profile.  One resolver now, consumed by the banner,
the pad-panel decision and the window sizing; BT_GLASS_PANELS is explicit-only;
dock/window modes auto-raise BT_PAD_PANEL so the button field always has a home.

L4RIOBANK -- one button geometry.  Both renderers carried their own copy and had
drifted: an MFD button was 156x138 reaching under the glass in the exploded
window and a 76x24 sliver entirely OUTSIDE the glass in the surround.  One
module owns it now, both are consumers, placement stays per-renderer.  The pod's
under-glass rule (RP412 L4MFDVIEW): reach half the glass in behind the display,
leave a lamp strip clearing the edge, paint buttons first and imagery over --
so the lamp reads as a bar and practically the whole display is the press
target.  The strip scales off the display's SHORT axis (the map is portrait)
with a readable floor.  Retired L4GLASSWIN's three local placers and seven
layout constants.

LAMP FLASH DECODE was wrong [T1].  BTLampBrightnessOf returned max(state1,
state2) and blanked on the alternate phase; RIO::LampState (L4RIO.h [T0]) says
solid shows state 1 and flashing ALTERNATES the two.  Agrees only when one state
is Off -- true for the Panic lamp, which is why it survived -- but L4LAMP.cpp:252
commands flashFast + state1Dim + state2Bright, a dim->bright pulse that rendered
as a hard bright->off blink.  Three copies existed (l4vb16.h, L4GLASSWIN,
L4PADPANEL), all three wrong; the two locals now forward to the one fixed inline.

THE COCKPIT SCALES.  The fixed canvas was stretched into whatever the client
area was, so a window dragged to a different shape squashed the instruments (the
projection was aspect-corrected in task #20; the panels never were).  Now one
uniform scale, centred, leftover black.  D3D9 applies it as a Present
destination rect, which DISCARD forbids -- so the WINDOWED swap effect becomes
COPY when the surround is up and multisampling is off.  The click mapping had to
follow (mapping against the full client drifts the hit test off every button by
the bar width), as did the world aspect (under a uniform scale it is the view
rect's own).  -fit / -windowed-fullscreen: borderless over the monitor.

 - ordering trap: the first WM_SIZE beats the device, so a -fit boot logged
   aspect=3.14 and applied it on frame 1.  The letterbox INTENT is decided in
   btl4main; L4VIDEO only confirms or withdraws it.

PLAYER-TUNABLE DISPLAYS.  BT_MFD_SCALE (+ _UL/_UC/_UR/_LL/_LR), BT_RADAR_SCALE,
BT_RADAR_POS (CENTER/LEFT/RIGHT/MIDLEFT/MIDRIGHT).  The surround BANDS derive
from the resolved sizes -- that is why the sizes could not stay constants: the
band a display hangs in has to grow with it or the canvas clips it.  100%
reproduces the historical L276 R276 T223 B336 exactly.  A corner map goes flush
to the CANVAS edge and the lower MFD slides beside it (measuring off the view
edge overlapped them by 232px).

MAP LEGEND GRID -- measured, not inherited.  scratchpad/measurelegend.py over a
native capture: top 3, cell 102, pitch 107 of 640.  RP412's map is 13 + 6x102 @
105 -- same cell height, different top and pitch, so its numbers do NOT
transfer.  Our old even division had the pitch right by luck and sat 3px high of
the labels.

environ.ini.  It was read ~300 lines into WinMain, AFTER the platform-profile
block had run its getenv()s -- so every setting the profile reads was silently
ignored FROM THE FILE and only worked as a real env var.  It also putenv()'d
comments verbatim.  Now loaded immediately after the first-breath line, comments
skipped, the real environment WINS over the file, and a fully documented default
is written on first run (the bindings.txt convention: untracked, so
extract-over-top never clobbers a player's settings).

VERIFICATION HARNESS (new, reusable): BT_RIOBANK_LOG=1 dumps every bank;
checkbank.py proves no address is SHADOWED (an address whose rect is covered by
earlier buttons is dead however big it looks -- the overlapping under-glass banks
make that a live hazard); clickbank.py posts a real click at every button centre.

Verified: 72/72 placed, 0 shadowed, 72/72 dispatched in BOTH modes, after a
resize, at 150%/135%, and at 75%+BOTTOMRIGHT; wide/tall drags and -fit
undistorted on a 3440x1440; exploded/dock/pod/dev un-regressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 02:23:40 -05:00

10618 lines
380 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "..\munga\door.h"
#include "..\munga\doorfram.h"
#include "..\munga\eyecandy.h"
#include "l4vidrnd.h"
#include "l4video.h"
#include "l4vb16.h" // BTCockpitLayout / BTCockpitComputeLayout (cockpit aspect)
#include "..\munga\matrix.h"
#include "..\munga\mover.h"
#include "..\munga\jmover.h"
#include "..\munga\player.h"
#include "..\munga\camship.h"
#include "..\munga\director.h"
#include "..\munga\mission.h"
#include "..\munga\cultural.h"
#include "..\munga\nttmgr.h"
#include "..\munga\explode.h" // Explosion::GetEntityHit (the effect-104 wreck swap)
#include "..\munga\app.h"
#include "l4particles.h"
#include "DXUtils.h"
#include "bgfload.h" // BT: SetVideoPathPriority (day/night path priority)
#include "image.h" // BT: decodeImage (the authored firesmoke sheet bintA)
using namespace std;
#include <conio.h>
#include <vector>
#include <hash_map>
#include <map>
#include <algorithm> // std::sort (the .PFX particle depth sort)
LPDIRECT3D9 gD3D = NULL;
//STUBBED: DPL RB 1/14/07
// when this is resolved it can be removed
#include "..\DPLSTUB.h"
#include <d3dx9.h>
#define char8 unsigned char
#define uint32 unsigned __int32
// basic macro to help with releasing things
#define SAFE_RELEASE(handle) if (handle) { handle->Release(); handle = NULL; }
//===========================================================================//
// BT WEAPON BEAMS (port addition) -- the visible muzzle->hit beam the game's
// fire path pushes each shot. The 1995 IG board drew weapon beams through the
// dpl_* display-list layer that was never ported (so a firing Emitter drew
// NOTHING). This is a self-contained additive-quad beam renderer: BTPushBeam
// (called from the reconstructed fire path, mech4.cpp) queues a world-space
// segment; BTDrawBeams (called in the alpha pass below, world proj + view set,
// Z-test on so a beam into a hill is occluded) billboards each as a camera-
// facing additive quad and fades it over its short life. No content needed.
//===========================================================================//
struct BTBeamFx
{
D3DXVECTOR3 from, to;
DWORD color; // 0x00RRGGBB glow colour (additive; alpha ignored)
float ttl, maxTtl; // seconds
float width; // natural-model-scale multiplier (1.0 = authored radius)
int kind; // tube model: 0=ermlaser 1=ppc 2=slaser 3=mlaser 4=llaser
};
static std::vector<BTBeamFx> gBTBeams;
// the decoded beam grit sheet (built lazily in BTDrawBeams; shared with the
// .PFX particle layer below)
LPDIRECT3DTEXTURE9 gBTBeamGritTex = 0;
LPDIRECT3DTEXTURE9 BTGetBeamGritTexture() { return gBTBeamGritTex; }
// Called from the game (mech4.cpp) -- external linkage, matched by an extern decl there.
// LOD eyepoint feed (game -> renderer): the viewpoint mech's position, the
// authentic LOD reference (see the ExecuteImplementation banner). mech4 calls
// this every player frame; once fed, the view-matrix extraction fallback stops.
int gBTLodEyeValid = 0;
void BTSetLodEye(float x, float y, float z)
{
d3d_OBJECT::SetCameraPosition(x, y, z);
gBTLodEyeValid = 1;
// DIAG (BT_LOD_LOG): prove the feed runs + what it feeds (1-in-300 calls)
static int s_eyeLog = -1;
if (s_eyeLog < 0) { const char *lv = getenv("BT_LOD_LOG"); s_eyeLog = (lv != 0 && lv[0] == '1') ? 1 : 0; }
if (s_eyeLog)
{
static int s_n = 0;
if ((++s_n % 300) == 1)
DEBUG_STREAM << "[lodeye] fed (" << x << ", " << y << ", " << z << ") n=" << s_n << "\n" << std::flush;
}
}
// ---------------------------------------------------------------------------
// AIM CAMERA FEED (task #36 authentic targeting): the ACTIVE eye publishes its
// world pose here (DPLEyeRenderable, the mCamera==this write site) and the
// render loop publishes the live projection's FOV term; the game side then
// derives the reticle PICK RAY (crosshair -> world, the engine Reticle's
// "pick point intersection" input) and the inverse projection (world ->
// reticle coords, the target-designator box position). Reticle coordinate
// convention = the dpl2d frame: centered origin, +y down, unit = half the
// viewport height (see dpl2d.cpp MapX/MapY).
// ---------------------------------------------------------------------------
// COORDINATE MODEL: everything talks in the DPL2D/RETICLE frame -- centered
// origin, +y down, unit = half the D3D VIEWPORT height (dpl2d MapX/MapY).
// The windowed present STRETCHES the fixed backbuffer into the client area,
// and the projection's aspect follows the CLIENT (the resize rebuild) -- so
// the viewport frame, the client frame and the NDC frame all differ unless
// the window happens to be exactly backbuffer-shaped. Every conversion below
// goes through the true NDC (per-axis P11/P22), NOT a square-frame shortcut:
// a shortcut is exact at screen centre and drifts outward with a side-flipped
// sign (the "locks left of the mech when I aim right of it" report).
static float gBTAimCamPos[3] = {0, 0, 0};
static float gBTAimCamX[3] = {1, 0, 0}; // camera right (LookAtRH xaxis)
static float gBTAimCamY[3] = {0, 1, 0}; // camera up (LookAtRH yaxis)
static float gBTAimCamZ[3] = {0, 0, 1}; // camera BACK (LookAtRH zaxis; view dir = -Z)
static float gBTAimP11 = 0.0f; // proj._11 (x scale; carries the aspect)
static float gBTAimP22 = 0.0f; // proj._22 (y scale = 1/tan(fovY/2))
// AUTHENTIC VIEW FIELD (task #55 FOV fix): the INI 'viewangle' (BTDPL.INI
// [dpl_defaults] viewangle=60.0) is the HORIZONTAL field of view -- the
// original Tesla call (kept in the comment at DPLRenderer::SetView below) is
// dpl_SetViewProjection(view, -1, -aspect, +1, +aspect, 1/tan(viewangle/2))
// i.e. horizontal half-extent 1.0 at plane distance 1/tan(viewangle/2), with
// the VERTICAL extent scaled by aspect. Our port fed the 60 to D3DX as the
// VERTICAL fov -> ~75x60 deg instead of the pod's 60x46.8 (4:3): the world
// read ~25%% farther away ("camera too far back") and the cockpit canopy was
// massively over-revealed. Derive fovY from the horizontal angle at the live
// aspect so the horizontal field stays the authentic 60 at any window shape.
static float BTFovYFromHorizontal(float viewAngleDeg, float aspect)
{
if (aspect <= 0.0f)
aspect = 4.0f / 3.0f;
return 2.0f * atanf(tanf(viewAngleDeg * 0.5f * (PI / 180.0f)) / aspect);
}
static float gBTAimVpW = 0.0f; // D3D viewport (backbuffer) size --
static float gBTAimVpH = 0.0f; // the dpl2d frame's pixel space
static int gBTAimCamValid = 0;
// issue #16 (boresight parallax) diagnostics: the RENDER camera pose -- the
// frame the crosshair is actually drawn in (screen centre of the ACTIVE view).
// Published unconditionally by the active eye each frame; BT_PARALLAX_LOG
// projects the PICK point back into this view to measure how far the shot
// lands from the crosshair. A healthy boresight keeps the pick point pinned
// at reticle (0,0) in cockpit view from max range all the way in.
static float gBTRenderCamPos[3] = {0, 0, 0};
static float gBTRenderCamX[3] = {1, 0, 0}; // camera right
static float gBTRenderCamY[3] = {0, 1, 0}; // camera up
static float gBTRenderCamZ[3] = {0, 0, 1}; // camera BACK (view dir = -Z)
static int gBTRenderCamValid = 0;
void BTSetRenderCamera(const float pos[3], const float xax[3],
const float yax[3], const float zax[3])
{
for (int i = 0; i < 3; ++i)
{
gBTRenderCamPos[i] = pos[i];
gBTRenderCamX[i] = xax[i];
gBTRenderCamY[i] = yax[i];
gBTRenderCamZ[i] = zax[i];
}
gBTRenderCamValid = 1;
}
void BTSetAimCamera(const float pos[3], const float xax[3],
const float yax[3], const float zax[3])
{
for (int i = 0; i < 3; ++i)
{
gBTAimCamPos[i] = pos[i];
gBTAimCamX[i] = xax[i];
gBTAimCamY[i] = yax[i];
gBTAimCamZ[i] = zax[i];
}
gBTAimCamValid = 1;
}
void BTSetAimProjection(float p11, float p22, float vpW, float vpH)
{
if (p11 > 1e-6f && p22 > 1e-6f && vpW > 0.0f && vpH > 0.0f)
{
gBTAimP11 = p11;
gBTAimP22 = p22;
gBTAimVpW = vpW;
gBTAimVpH = vpH;
}
}
float BTGetPresentAspect(); // defined below with gWindowAspect (task #44)
//
// Client-area mouse position -> reticle coords. The reticle frame is SQUARE
// on the PRESENTED image (task #44): unit = half the CLIENT height on both
// axes -- no backbuffer round-trip. Clamped to the visible frame.
//
void BTClientToReticle(float mx, float my, float cw, float ch,
float *rx, float *ry)
{
if (cw <= 0.0f || ch <= 0.0f)
{
*rx = 0.0f; *ry = 0.0f;
return;
}
float x = (mx - cw * 0.5f) / (ch * 0.5f);
float y = (my - ch * 0.5f) / (ch * 0.5f);
const float xmax = cw / ch;
if (x < -xmax) x = -xmax;
if (x > xmax) x = xmax;
if (y < -1.0f) y = -1.0f;
if (y > 1.0f) y = 1.0f;
*rx = x;
*ry = y;
}
//
// Torso-twist angle (radians) -> reticle X (task #39): the crosshair marks
// where the torso guns point relative to the body-mounted view. A boresight
// ray at horizontal angle `twist` from forward hits camera-space
// xc = d*tan(twist) at depth d, so ndc_x = tan(twist)*P11 and
// rx = ndc_x * (vw/vh). Zero for a fixed torso (dead-centre boresight).
//
float BTTwistToReticleX(float twist_rad)
{
if (!gBTAimCamValid || gBTAimP11 <= 0.0f || gBTAimVpH <= 0.0f)
return 0.0f;
return (float)tan((double)twist_rad) * gBTAimP11 * BTGetPresentAspect();
}
//
// Crosshair (reticle coords) -> world pick ray. reticle -> NDC:
// ndc_x = rx * vh/vw (the reticle x unit is half the viewport HEIGHT),
// ndc_y = -ry (reticle +y is down). NDC -> camera: divide by the true
// per-axis projection scales; RH camera looks down -Z.
//
int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3])
{
if (!gBTAimCamValid || gBTAimP11 <= 0.0f || gBTAimP22 <= 0.0f
|| gBTAimVpW <= 0.0f)
return 0;
const float ndcX = rx / BTGetPresentAspect(); // square reticle frame (task #44)
const float ndcY = -ry;
const float cx = ndcX / gBTAimP11;
const float cy = ndcY / gBTAimP22;
float d[3];
float len = 0.0f;
for (int i = 0; i < 3; ++i)
{
d[i] = cx * gBTAimCamX[i] + cy * gBTAimCamY[i] - gBTAimCamZ[i];
len += d[i] * d[i];
}
len = sqrtf(len);
if (len < 1e-6f) return 0;
for (int i = 0; i < 3; ++i)
{
outStart[i] = gBTAimCamPos[i];
outDir[i] = d[i] / len;
}
return 1;
}
//
// World point -> reticle coords (the inverse; the designator box position).
// Returns 1 when the point is IN FRONT of the camera (rx/ry valid for
// on-screen placement); 0 when behind (rx still carries the correct SIDE
// sign so the caller can pick the left/right off-screen arrow).
//
int BTProjectToReticle(const float world[3], float *rx, float *ry)
{
if (!gBTAimCamValid || gBTAimP11 <= 0.0f || gBTAimP22 <= 0.0f
|| gBTAimVpH <= 0.0f)
{
*rx = 0.0f; *ry = 0.0f;
return 0;
}
float rel[3];
for (int i = 0; i < 3; ++i)
rel[i] = world[i] - gBTAimCamPos[i];
const float xc = rel[0]*gBTAimCamX[0] + rel[1]*gBTAimCamX[1] + rel[2]*gBTAimCamX[2];
const float yc = rel[0]*gBTAimCamY[0] + rel[1]*gBTAimCamY[1] + rel[2]*gBTAimCamY[2];
const float zc = rel[0]*gBTAimCamZ[0] + rel[1]*gBTAimCamZ[1] + rel[2]*gBTAimCamZ[2];
const float depth = -zc; // camera looks down -Z
if (depth < 0.1f)
{
*rx = (xc >= 0.0f) ? 2.0f : -2.0f; // side sign only
*ry = 0.0f;
return 0;
}
const float ndcX = (xc * gBTAimP11) / depth;
const float ndcY = (yc * gBTAimP22) / depth;
*rx = ndcX * BTGetPresentAspect(); // square reticle frame (task #44)
*ry = -ndcY;
return 1;
}
//
// issue #16 (boresight parallax) diag: world point -> reticle coords through
// the RENDER camera (the view the crosshair is drawn in), NOT the aim camera
// (projecting the pick point through the aim camera is trivially (0,0) --
// the pick point lies ON the aim ray). eyeOut receives the render eye
// position so the caller can log the true sight-line range. Same reticle
// frame as BTProjectToReticle (+y down; unit = half the viewport height).
//
int BTProjectToRenderView(const float world[3], float *rx, float *ry,
float eyeOut[3])
{
if (!gBTRenderCamValid || gBTAimP11 <= 0.0f || gBTAimP22 <= 0.0f
|| gBTAimVpH <= 0.0f)
{
*rx = 0.0f; *ry = 0.0f;
return 0;
}
if (eyeOut != 0)
for (int i = 0; i < 3; ++i)
eyeOut[i] = gBTRenderCamPos[i];
float rel[3];
for (int i = 0; i < 3; ++i)
rel[i] = world[i] - gBTRenderCamPos[i];
const float xc = rel[0]*gBTRenderCamX[0] + rel[1]*gBTRenderCamX[1] + rel[2]*gBTRenderCamX[2];
const float yc = rel[0]*gBTRenderCamY[0] + rel[1]*gBTRenderCamY[1] + rel[2]*gBTRenderCamY[2];
const float zc = rel[0]*gBTRenderCamZ[0] + rel[1]*gBTRenderCamZ[1] + rel[2]*gBTRenderCamZ[2];
const float depth = -zc; // camera looks down -Z
if (depth < 0.1f)
{
*rx = (xc >= 0.0f) ? 2.0f : -2.0f;
*ry = 0.0f;
return 0;
}
const float ndcX = (xc * gBTAimP11) / depth;
const float ndcY = (yc * gBTAimP22) / depth;
*rx = ndcX * BTGetPresentAspect();
*ry = -ndcY;
return 1;
}
//
// The target HOTBOX projection (the recovered reticle Execute @4cdff7-4ce0f9
// [T1]): project the box hugging the target's extents -- x +-4 around the
// hotbox point (the target's top-centre), +1 above / -11.5 below it (the
// authored pod mech envelope; constants @4cee7c/4ceea0/4ceeac). Returns 1
// with the reticle-frame edges when the box is displayable; 0 when the target
// is BEHIND or both x-edges pass +-1.6 (constants @4cee88/90) -- then *side
// carries the arrow side (-1 left / +1 right).
//
int BTProjectHotBox(const float top[3], float *xl, float *xr,
float *yt, float *yb, int *side)
{
*side = 0;
if (!gBTAimCamValid || gBTAimP11 <= 0.0f || gBTAimP22 <= 0.0f
|| gBTAimVpH <= 0.0f)
return 0;
float rel[3];
for (int i = 0; i < 3; ++i)
rel[i] = top[i] - gBTAimCamPos[i];
const float xc = rel[0]*gBTAimCamX[0] + rel[1]*gBTAimCamX[1] + rel[2]*gBTAimCamX[2];
const float yc = rel[0]*gBTAimCamY[0] + rel[1]*gBTAimCamY[1] + rel[2]*gBTAimCamY[2];
const float zc = rel[0]*gBTAimCamZ[0] + rel[1]*gBTAimCamZ[1] + rel[2]*gBTAimCamZ[2];
const float depth = -zc; // camera looks down -Z
if (depth < 0.1f)
{
*side = (xc >= 0.0f) ? 1 : -1;
return 0;
}
const float xs = (gBTAimP11 / depth) * BTGetPresentAspect(); // (task #44)
const float ys = gBTAimP22 / depth;
*xl = (xc - 4.0f) * xs;
*xr = (xc + 4.0f) * xs;
*yt = -(yc + 1.0f) * ys; // 1 above the hotbox point
*yb = -(yc - 11.5f) * ys; // 11.5 below it
if (*xl > 1.6f && *xr > 1.6f) { *side = 1; return 0; }
if (*xl < -1.6f && *xr < -1.6f) { *side = -1; return 0; }
return 1;
}
void BTPushBeamKind(float fx, float fy, float fz, float tx, float ty, float tz,
unsigned color, float ttl, float width, int kind)
{
if (gBTBeams.size() > 256) return; // runaway guard
BTBeamFx b;
b.from = D3DXVECTOR3(fx, fy, fz);
b.to = D3DXVECTOR3(tx, ty, tz);
b.color = color;
b.ttl = ttl; b.maxTtl = (ttl > 1e-4f) ? ttl : 1e-4f;
b.width = width;
b.kind = (kind >= 0 && kind < 5) ? kind : 0;
gBTBeams.push_back(b);
}
void BTPushBeam(float fx, float fy, float fz, float tx, float ty, float tz,
unsigned color, float ttl, float width)
{
BTPushBeamKind(fx, fy, fz, tx, ty, tz, color, ttl, width, 0);
}
void BTDrawBeams(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
{
if (gBTBeams.empty()) return;
D3DXMATRIX iv; D3DXMatrixInverse(&iv, NULL, view);
const D3DXVECTOR3 cam(iv._41, iv._42, iv._43); // camera world position
DWORD sLight, sFog, sZW, sBlend, sSrc, sDst, sCull, sCop, sCa1, sCa2, sAop, sAa1, sAddrU, sAddrV;
dev->GetRenderState(D3DRS_LIGHTING, &sLight);
dev->GetRenderState(D3DRS_FOGENABLE, &sFog);
dev->GetRenderState(D3DRS_ZWRITEENABLE, &sZW);
dev->GetRenderState(D3DRS_ALPHABLENDENABLE, &sBlend);
dev->GetRenderState(D3DRS_SRCBLEND, &sSrc);
dev->GetRenderState(D3DRS_DESTBLEND, &sDst);
dev->GetRenderState(D3DRS_CULLMODE, &sCull);
dev->GetTextureStageState(0, D3DTSS_COLOROP, &sCop);
dev->GetTextureStageState(0, D3DTSS_COLORARG1, &sCa1);
dev->GetTextureStageState(0, D3DTSS_COLORARG2, &sCa2);
dev->GetTextureStageState(0, D3DTSS_ALPHAOP, &sAop);
dev->GetTextureStageState(0, D3DTSS_ALPHAARG1, &sAa1);
dev->GetSamplerState(0, D3DSAMP_ADDRESSU, &sAddrU);
dev->GetSamplerState(0, D3DSAMP_ADDRESSV, &sAddrV);
// AUTHENTIC BEAM TEXTURE (decoded): the original laser beam (ermlaser.bgf) is a
// tube textured with `beamwhite_scr_tex` -> the `bexp` image (VIDEO/TEX/BEXP.BSL,
// a chaotic red/yellow/green noise) SCROLLED fast (BTFX.VMF: SCROLL u=0.10 v=9.5)
// and ramp-colourised by `softer` (0.25->0.99) for the white core. Load BEXP.BSL
// once, ramp its luminance to a grayscale grit texture, and MODULATE the beam
// colour by it with a scrolling UV -> the streaming "gritty" look, not a clean
// gradient. BT_BEAM_TEX=0 falls back to the plain additive beam.
// (file-scope so the .PFX particle layer below can share the decoded sheet)
static int s_beamTexTried = 0;
extern LPDIRECT3DTEXTURE9 gBTBeamGritTex;
LPDIRECT3DTEXTURE9 &s_beamTex = gBTBeamGritTex;
if (!s_beamTexTried)
{
s_beamTexTried = 1;
const char *bv = getenv("BT_BEAM_TEX");
if (bv == 0 || bv[0] != '0')
{
FILE *fp = fopen("VIDEO\\TEX\\BEXP.BSL", "rb");
if (fp)
{
fseek(fp, 0, SEEK_END); long sz = ftell(fp); fseek(fp, 0, SEEK_SET);
std::vector<unsigned char> buf(sz > 0 ? sz : 1);
size_t got = fread(&buf[0], 1, sz, fp); fclose(fp);
if (got >= 16 && memcmp(&buf[0], "DIV-BSL2", 8) == 0)
{
int tw = *(int *)&buf[8], th = *(int *)&buf[12]; // 128 x 64
long need = (long)tw * th * 4;
if (tw > 0 && th > 0 && (long)got >= need &&
SUCCEEDED(dev->CreateTexture(tw, th, 1, 0, D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED, &s_beamTex, NULL)))
{
const unsigned char *bimg = &buf[got - need]; // trailing base image
D3DLOCKED_RECT lr;
if (SUCCEEDED(s_beamTex->LockRect(0, &lr, NULL, 0)))
{
// GRIT CONTRAST (BT_BEAM_GRIT, default 2.0): the authentic
// 'softer' ramp (0.25->0.99) only spans ~4:1, and additive
// blending over a bright scene flattens it to near-invisible.
// Expand the ramped luminance about its midpoint before baking
// so the interference pattern reads like the pod footage.
float grit = 2.0f;
{ const char *gv = getenv("BT_BEAM_GRIT"); if (gv) grit = (float)atof(gv); }
for (int y = 0; y < th; ++y)
{
DWORD *dst = (DWORD *)((char *)lr.pBits + y * lr.Pitch);
for (int x = 0; x < tw; ++x)
{
// BSL BIT-SLICE decode: beamwhite_scr_tex maps to 'bexp'
// with NO BITSLICE tag = slice 0 (bexp1, the grit sheet)
// = bits 12-15 (byte1 high nibble) of the texel word.
// (The old byte-luminance read mixed bexp1/bexp2 and the
// bexp99 RGBA-sprite nibbles.)
const unsigned char *p = &bimg[(y * tw + x) * 4];
float lum = (float)(p[1] & 0xF0) / 240.0f;
float v = 0.25f + 0.74f*lum; // 'softer' ramp
v = 0.62f + (v - 0.62f) * grit; // expand about ramp midpoint
if (v < 0.0f) v = 0.0f;
int gg = (int)(v * 255.0f);
if (gg > 255) gg = 255;
dst[x] = 0xFF000000u | (gg << 16) | (gg << 8) | gg;
}
}
s_beamTex->UnlockRect(0);
}
}
}
}
}
}
// AUTHENTIC GEOMETRY: render each weapon's REAL beam model (thin 2000-long
// tubes, -Z aligned, UVs tiled down the length), transformed per beam (scale
// the length to the shot distance, orient local -Z -> muzzle->target). The
// authored radii ARE the beam widths: ERMLASER 0.22, PPC 0.62 (a genuinely
// fatter bolt), SLASER 0.11 / MLASER 0.22 / LLASER 0.32 -- draw at natural
// scale (width multiplier 1.0), NOT inflated. BT_BEAM_TUBE=0 -> billboard.
static int s_tubeTried = 0;
struct BTTubeModel
{
std::vector<float> vb; // x,y,z,u,v per vertex
std::vector<uint32_t> ib;
int verts, tris;
};
static BTTubeModel s_tubes[5];
static const char *const kTubeNames[5] =
{ "ermlaser.bgf", "ppc.bgf", "slaser.bgf", "mlaser.bgf", "llaser.bgf" };
if (!s_tubeTried)
{
s_tubeTried = 1;
const char *tvv = getenv("BT_BEAM_TUBE");
if (tvv == 0 || tvv[0] != '0')
{
for (int t = 0; t < 5; ++t)
{
BgfData bd;
s_tubes[t].verts = s_tubes[t].tris = 0;
if (LoadBgfFile(kTubeNames[t], bd) && bd.ok && !bd.indices.empty())
{
s_tubes[t].verts = (int)bd.verts.size();
s_tubes[t].vb.reserve(bd.verts.size() * 5);
for (size_t k = 0; k < bd.verts.size(); ++k)
{
s_tubes[t].vb.push_back(bd.verts[k].x);
s_tubes[t].vb.push_back(bd.verts[k].y);
s_tubes[t].vb.push_back(bd.verts[k].z);
s_tubes[t].vb.push_back(bd.verts[k].u);
s_tubes[t].vb.push_back(bd.verts[k].v);
}
s_tubes[t].ib = bd.indices;
s_tubes[t].tris = (int)(bd.indices.size() / 3);
}
}
}
}
const bool useTube = (s_beamTex != 0 && s_tubes[0].tris > 0);
static float s_tubeWidth = -1.0f;
if (s_tubeWidth < 0.0f)
{ const char *wv = getenv("BT_BEAM_WIDTH"); s_tubeWidth = wv ? (float)atof(wv) : 1.0f; } // global width multiplier
static float s_beamTime = 0.0f; s_beamTime += dt;
const float uScroll = s_beamTime * 0.10f; // VMF SCROLL u-speed
const float vScroll = s_beamTime * 9.5f; // VMF SCROLL v-speed (fast)
DWORD sTFactor, sTexXf;
dev->GetRenderState(D3DRS_TEXTUREFACTOR, &sTFactor);
dev->GetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, &sTexXf);
D3DXMATRIX ident; D3DXMatrixIdentity(&ident);
dev->SetTransform(D3DTS_WORLD, &ident);
dev->SetFVF(useTube ? (D3DFVF_XYZ | D3DFVF_TEX1)
: (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1));
dev->SetRenderState(D3DRS_LIGHTING, FALSE);
dev->SetRenderState(D3DRS_FOGENABLE, FALSE);
dev->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
dev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
dev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); // additive glow
dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
// beam colour: from the TFACTOR (tube -- verts carry no colour) or vertex DIFFUSE (billboard).
const DWORD kColArg = useTube ? D3DTA_TFACTOR : D3DTA_DIFFUSE;
if (s_beamTex)
{
dev->SetTexture(0, s_beamTex);
dev->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
dev->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); // grit x beam colour
dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
dev->SetTextureStageState(0, D3DTSS_COLORARG2, kColArg);
}
else
{
dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
dev->SetTextureStageState(0, D3DTSS_COLORARG1, kColArg);
}
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, kColArg);
// SCROLL + GRIT DENSITY: bake into the tube's UVs on the CPU, PER BEAM (89
// verts, trivial). Two lessons:
// (1) the previous D3DTS_TEXTURE0 texture-coordinate transform SILENTLY
// STOPPED APPLYING when the device moved to HARDWARE vertex processing
// (the perf fix) -- vertex UVs are pipeline-independent;
// (2) the tube's baked U (0..7.7 tiles over its NATIVE 2000u length) stays
// put while the GEOMETRY compresses to the shot distance, so at typical
// ranges all ~8 tiles squeeze into a short beam -> the grit minifies to
// ~3 texels/pixel and bilinear-averages into a SMOOTH GRADIENT (the
// "no interference pattern" report). Scale U by the compression ratio
// (beamLen/2000) so the grit density is WORLD-FIXED (~1 tile / 260u --
// the billboard's proven mapping).
static std::vector<float> s_tubeScrolled;
struct BV { float x, y, z; DWORD c; float u, v; };
for (size_t i = 0; i < gBTBeams.size(); ++i)
{
BTBeamFx &b = gBTBeams[i];
float f = b.ttl / b.maxTtl; if (f < 0) f = 0; if (f > 1) f = 1;
const int r = (int)(((b.color >> 16) & 0xFF) * f);
const int g = (int)(((b.color >> 8) & 0xFF) * f);
const int bl = (int)(( b.color & 0xFF) * f);
const DWORD col = 0xFF000000u | (r << 16) | (g << 8) | bl;
D3DXVECTOR3 d3 = b.to - b.from;
const float beamLen = D3DXVec3Length(&d3);
D3DXVECTOR3 dir = d3; D3DXVec3Normalize(&dir, &dir);
if (useTube)
{
// world = Scale(width,width,len/2000) * Rotate(local -Z -> dir) * Translate(muzzle).
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
if (fabsf(dir.y) > 0.99f) up = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
D3DXVECTOR3 zc = -dir; // local +Z image
D3DXVECTOR3 xc; D3DXVec3Cross(&xc, &up, &zc); D3DXVec3Normalize(&xc, &xc);
D3DXVECTOR3 yc; D3DXVec3Cross(&yc, &zc, &xc);
D3DXMATRIX S, R, T, W;
// NATURAL width: the model's authored radius IS the beam width
// (ERMLASER 0.22u, PPC 0.62u...). b.width is a multiplier on that
// (1.0 for weapon beams); BT_BEAM_WIDTH is the global dev override.
const float ws = b.width * s_tubeWidth;
D3DXMatrixScaling(&S, ws, ws, beamLen / 2000.0f);
D3DXMatrixIdentity(&R);
R._11 = xc.x; R._12 = xc.y; R._13 = xc.z;
R._21 = yc.x; R._22 = yc.y; R._23 = yc.z;
R._31 = zc.x; R._32 = zc.y; R._33 = zc.z;
D3DXMatrixTranslation(&T, b.from.x, b.from.y, b.from.z);
D3DXMatrixMultiply(&W, &S, &R);
D3DXMatrixMultiply(&W, &W, &T);
dev->SetTransform(D3DTS_WORLD, &W);
dev->SetRenderState(D3DRS_TEXTUREFACTOR, col);
// the beam's own weapon-model tube (ermlaser/ppc/slaser/...); fall
// back to the ermlaser tube when that model didn't load
const BTTubeModel &tube =
(s_tubes[b.kind].tris > 0) ? s_tubes[b.kind] : s_tubes[0];
// per-beam UVs: world-fixed grit density + scroll (see banner above)
const float uScale = beamLen / 2000.0f;
s_tubeScrolled = tube.vb;
for (size_t k = 0; k + 4 < s_tubeScrolled.size(); k += 5)
{
s_tubeScrolled[k + 3] = s_tubeScrolled[k + 3] * uScale + uScroll;
s_tubeScrolled[k + 4] += vScroll;
}
dev->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, tube.verts, tube.tris,
&tube.ib[0], D3DFMT_INDEX32, &s_tubeScrolled[0], 5 * sizeof(float));
}
else
{
D3DXVECTOR3 mid = (b.from + b.to) * 0.5f;
D3DXVECTOR3 toCam = cam - mid; D3DXVec3Normalize(&toCam, &toCam);
D3DXVECTOR3 side; D3DXVec3Cross(&side, &dir, &toCam); D3DXVec3Normalize(&side, &side);
side *= b.width * 0.5f;
const float uLen = uScroll + beamLen / 260.0f;
const float v0 = vScroll, v1 = vScroll + 1.6f;
BV quad[4] =
{
{ b.from.x + side.x, b.from.y + side.y, b.from.z + side.z, col, uScroll, v0 },
{ b.from.x - side.x, b.from.y - side.y, b.from.z - side.z, col, uScroll, v1 },
{ b.to.x + side.x, b.to.y + side.y, b.to.z + side.z, col, uLen, v0 },
{ b.to.x - side.x, b.to.y - side.y, b.to.z - side.z, col, uLen, v1 },
};
dev->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, quad, sizeof(BV));
}
b.ttl -= dt;
}
dev->SetTexture(0, NULL);
dev->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, sTexXf);
dev->SetRenderState(D3DRS_TEXTUREFACTOR, sTFactor);
// drop expired beams
size_t w = 0;
for (size_t i = 0; i < gBTBeams.size(); ++i)
if (gBTBeams[i].ttl > 0.0f) gBTBeams[w++] = gBTBeams[i];
gBTBeams.resize(w);
dev->SetTransform(D3DTS_WORLD, &ident);
dev->SetRenderState(D3DRS_LIGHTING, sLight);
dev->SetRenderState(D3DRS_FOGENABLE, sFog);
dev->SetRenderState(D3DRS_ZWRITEENABLE, sZW);
dev->SetRenderState(D3DRS_ALPHABLENDENABLE, sBlend);
dev->SetRenderState(D3DRS_SRCBLEND, sSrc);
dev->SetRenderState(D3DRS_DESTBLEND, sDst);
dev->SetRenderState(D3DRS_CULLMODE, sCull);
dev->SetTextureStageState(0, D3DTSS_COLOROP, sCop);
dev->SetTextureStageState(0, D3DTSS_COLORARG1, sCa1);
dev->SetTextureStageState(0, D3DTSS_COLORARG2, sCa2);
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, sAop);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, sAa1);
dev->SetSamplerState(0, D3DSAMP_ADDRESSU, sAddrU);
dev->SetSamplerState(0, D3DSAMP_ADDRESSV, sAddrV);
}
//===========================================================================//
// BT PARTICLE EFFECTS (.PFX) -- the 1995 explosion/damage effect layer.
//
// The pod's damage/explosion visuals are DATA: VIDEO/*.PFX text files (format
// documented inside each file), mapped to dpl effect NUMBERS by BTDPL.INI's
// [pfx_day]/[pfx_night] pages ("psfxN=file.pfx" -- N is the number embedded in
// resources: 2..5 = the mech damage bands, 6 = projectile-gun hit, 7 = the
// mech-death explosion, 8 = zone destroyed...). The 1995 board consumed these
// through dpl particle calls that were never ported: DPLIndependantEffect's
// (<100) arm and ReadPSFX are both stubbed, so every weapon-hit / damage-band /
// death explosion rendered NOTHING. Like the weapon-beam layer above, this is
// the self-contained D3D9 port of that layer:
// BTLoadPfxFile -- parses a .PFX into BTPfxDef (the documented format)
// BTStartPfx -- starts an emitter instance at a world position
// (called by DPLIndependantEffect for effect_number < 100)
// BTDrawPfx -- per-frame sim + camera-facing additive-quad billboards
// (called beside BTDrawBeams in the render loop)
// Variance convention (decoded from DNBOOM.PFX: vel 150 + var -300 = a
// symmetric +-150 burst): sampled = value + variance * rand01().
// NOT yet honoured (noted, low-visibility): atten/attenv (distance
// attenuation), colorWarp/alphaWarp exponents are applied as t^(1/warp),
// the per-file texture name (all BT effects use the firesmoke sheet; we use
// the same decoded grit texture as the beams).
//===========================================================================//
struct BTPfxDef
{
int valid;
unsigned identifier;
int maxIssue; // total particles an instance may issue
float releasePeriod; // batch interval (s)
float rate; // particles per second while releasing
float px, py, pz, pv; // spawn offset + positional variance
float velx, vely, velz, velxv, velyv, velzv;
float rad, radv, exp, expv, dexp, dexpv; // radius, expansion rate, expansion decay
float accelx, accely, accelz, accelxv, accelyv, accelzv;
float atten, attenv; // (not yet honoured)
float sI[4], sIv[4]; // start colour inner RGBA + variance
float sO[4], sOv[4]; // start colour outer RGBA + variance
float eI[4], eIv[4]; // end colour inner RGBA + variance
float eO[4], eOv[4]; // end colour outer RGBA + variance
float colorWarp, alphaWarp;
float duration, durationv; // particle lifetime
};
#define BT_PFX_SLOTS 32
static BTPfxDef gBTPfxDefs[BT_PFX_SLOTS];
struct BTPfxEmitter
{
const BTPfxDef *def;
D3DXVECTOR3 pos;
// The effect's LOCAL FRAME (.PFX offsets/velocities are authored in the
// victim's mech-local space -- e.g. DAFC.PFX sprays -Z, out through the
// front armor toward the shooter). Identity when no frame is known.
D3DXVECTOR3 ax, ay, az;
float emitAccum; // fractional particles owed (rate * dt integration)
int issued;
int active;
// ATTACHED effects (the @004d097c per-zone dispatcher): the emitter rides
// the owner entity's SEGMENT -- zone smoke follows the walking mech, and
// StopAllEntityEffects (@004d0c14: the respawn cleanup) kills every
// emitter tagged to the entity. ownerTag==0 = a free world effect.
void *ownerTag;
int followSeg;
};
struct BTPfxParticle
{
D3DXVECTOR3 pos, vel, accel;
float age, life;
float rad, exp, dexp;
float colorWarp, alphaWarp; // def-level warps, carried per particle
float sI[4], sO[4], eI[4], eO[4];
// OLD-STYLE SPECIALFX mode (the VREND.MNG heat model, firmware-decoded
// 2026-07-12): when sfxMode != 0 the colour comes from the board's heat
// equation instead of the .PFX colour quads:
// h *= coolMul per board frame (30 Hz);
// channel = h_old*(h_new*cook_ch - 0.25) + 0.25 (0.25 = the ember floor,
// firmware constant @VA 0xF080); kill at h <= 1e-4 (@0xF0A0), at
// fade <= 0 (fade -= coolSub per board frame), or below the kill plane
// (the descriptor's y_off -- Chunks die 1m under the burst: the floor).
int sfxMode;
float heat, coolMul, coolSub, fade;
float cookR, cookG, cookB;
float killY;
int sfxType; // descriptor 'type' (draw weight)
int uvVariant; // 0..7 mirror/swap of the noise UVs
// (de-correlates the stamps like the
// board's random burst spin; mirror/
// swap keeps UVs in 0..1 so the
// CLAMPed radial mask stays valid)
};
static std::vector<BTPfxEmitter> gBTPfxEmitters;
static std::vector<BTPfxParticle> gBTPfxParticles;
static float BTPfxRand01() // cheap deterministic LCG
{
static unsigned s = 0x2545F491u;
s = s * 1664525u + 1013904223u;
return (float)((s >> 8) & 0xFFFFFF) / 16777215.0f;
}
// Parse one .PFX text file (VIDEO\<name>) into a def slot. Format per the
// spec block carried inside every .PFX (and ReadPSFX's comment).
static int BTLoadPfxFile(const char *file_name, BTPfxDef &d)
{
char path[256];
strcpy(path, "VIDEO\\");
strcat(path, file_name);
FILE *fp = fopen(path, "rt");
if (!fp)
{
DEBUG_STREAM << "[pfx] could not open " << path << " -- its effects will not draw\n" << std::flush;
return 0;
}
memset(&d, 0, sizeof(d));
char line[256];
int ok = 1;
// line 1: texture name (recorded in the banner note; the shared sheet is used)
if (!fgets(line, sizeof(line), fp)) ok = 0;
if (ok && fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%x %d %f %f", &d.identifier, &d.maxIssue, &d.releasePeriod, &d.rate) == 4);
else ok = 0;
if (ok && fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%f %f %f %f", &d.px, &d.py, &d.pz, &d.pv) == 4);
else ok = 0;
if (ok && fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%f %f %f %f %f %f", &d.velx, &d.vely, &d.velz, &d.velxv, &d.velyv, &d.velzv) == 6);
else ok = 0;
if (ok && fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%f %f %f %f %f %f", &d.rad, &d.radv, &d.exp, &d.expv, &d.dexp, &d.dexpv) == 6);
else ok = 0;
if (ok && fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%f %f %f %f %f %f", &d.accelx, &d.accely, &d.accelz, &d.accelxv, &d.accelyv, &d.accelzv) == 6);
else ok = 0;
if (ok && fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%f %f", &d.atten, &d.attenv) == 2);
else ok = 0;
float *quads[8] = { d.sI, d.sIv, d.sO, d.sOv, d.eI, d.eIv, d.eO, d.eOv };
for (int q = 0; ok && q < 4; ++q) // 4 lines: sI+var, sO+var, eI+var, eO+var
{
if (fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%f %f %f %f %f %f %f %f",
&quads[q*2][0], &quads[q*2][1], &quads[q*2][2], &quads[q*2][3],
&quads[q*2+1][0], &quads[q*2+1][1], &quads[q*2+1][2], &quads[q*2+1][3]) == 8);
else ok = 0;
}
if (ok && fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%f %f", &d.colorWarp, &d.alphaWarp) == 2);
else ok = 0;
if (ok && fgets(line, sizeof(line), fp))
ok = (sscanf(line, "%f %f", &d.duration, &d.durationv) == 2);
else ok = 0;
fclose(fp);
if (!ok)
{
DEBUG_STREAM << "[pfx] " << path << " did not parse -- effect disabled\n" << std::flush;
return 0;
}
d.valid = 1;
return 1;
}
// Slot-checked load entry used by the psfx page loader (LoadMission walk).
int BTLoadPfxFile_slot(const char *file_name, int slot)
{
if (slot < 0 || slot >= BT_PFX_SLOTS)
return 0;
return BTLoadPfxFile(file_name, gBTPfxDefs[slot]);
}
// Start one effect instance at a world position (the DPLIndependantEffect
// contract: renderer-owned, runs to termination on its own). The optional
// frame rows orient the .PFX's mech-local offsets/velocities in the world
// (pass the victim entity's localToWorld X/Y/Z rows); identity when absent.
void BTStartPfxFrame(int effect_number, float x, float y, float z,
const float *xrow, const float *yrow, const float *zrow)
{
if (effect_number < 0 || effect_number >= BT_PFX_SLOTS)
return;
const BTPfxDef &d = gBTPfxDefs[effect_number];
if (!d.valid)
return;
if (gBTPfxEmitters.size() > 256) // runaway guard
return;
BTPfxEmitter e;
e.def = &d;
e.pos = D3DXVECTOR3(x, y, z);
e.ax = xrow ? D3DXVECTOR3(xrow[0], xrow[1], xrow[2]) : D3DXVECTOR3(1, 0, 0);
e.ay = yrow ? D3DXVECTOR3(yrow[0], yrow[1], yrow[2]) : D3DXVECTOR3(0, 1, 0);
e.az = zrow ? D3DXVECTOR3(zrow[0], zrow[1], zrow[2]) : D3DXVECTOR3(0, 0, 1);
e.emitAccum = 1.0f; // first particle immediately
e.issued = 0;
e.active = 1;
e.ownerTag = 0;
e.followSeg = -1;
gBTPfxEmitters.push_back(e);
}
void BTStartPfx(int effect_number, float x, float y, float z)
{
BTStartPfxFrame(effect_number, x, y, z, 0, 0, 0);
}
// ATTACHED effect start (the @004d097c per-zone dispatcher): like
// BTStartPfxFrame, but the emitter rides the owner entity's segment -- the
// per-frame sim re-resolves the segment's world transform through the
// game-side bridge below, so a damage-band smoke plume TRAILS the walking
// mech instead of hanging at the hit point. rows9 = the initial 3x3 basis
// (rows = local axes in world), pos = the segment's current world position.
extern int BTResolveSegmentWorld(void *entity, int seg_index,
float *pos3, float *rows9); // mech4.cpp (game side)
void BTStartPfxAttached(int effect_number, void *owner, int seg_index,
float x, float y, float z, const float *rows9)
{
if (effect_number < 0 || effect_number >= BT_PFX_SLOTS)
return;
const BTPfxDef &d = gBTPfxDefs[effect_number];
if (!d.valid)
return;
if (gBTPfxEmitters.size() > 256)
return;
BTPfxEmitter e;
e.def = &d;
e.pos = D3DXVECTOR3(x, y, z);
e.ax = rows9 ? D3DXVECTOR3(rows9[0], rows9[1], rows9[2]) : D3DXVECTOR3(1, 0, 0);
e.ay = rows9 ? D3DXVECTOR3(rows9[3], rows9[4], rows9[5]) : D3DXVECTOR3(0, 1, 0);
e.az = rows9 ? D3DXVECTOR3(rows9[6], rows9[7], rows9[8]) : D3DXVECTOR3(0, 0, 1);
e.emitAccum = 1.0f;
e.issued = 0;
e.active = 1;
e.ownerTag = owner;
e.followSeg = seg_index;
gBTPfxEmitters.push_back(e);
}
// StopAllEntityEffects (@004d0c14 analog): the firmware killed every effect
// instance tagged (playerIdx<<16 .. |0xffff); the port kills every emitter
// tagged to the entity (the respawn cleanup -- a respawned mech must not
// trail its corpse's zone smoke). Emitted particles fade out naturally.
void BTStopEntityPfx(void *owner)
{
if (owner == 0)
return;
for (size_t i = 0; i < gBTPfxEmitters.size(); ++i)
if (gBTPfxEmitters[i].ownerTag == owner)
gBTPfxEmitters[i].active = 0;
}
// Spawn a few particles of a slot's effect DIRECTLY at a moving point (no
// emitter instance) -- the per-frame projectile SMOKE TRAIL (psfx 0 = dsrm,
// "the lrm smoke trail": its velocities stream +Z = BEHIND the round, so the
// frame is built with local +Z = the flight's backward direction).
static void BTPfxSpawn(const BTPfxEmitter &e); // defined below
void BTPfxTrailPuff(int effect_number, float x, float y, float z,
float backx, float backy, float backz, int count)
{
if (effect_number < 0 || effect_number >= BT_PFX_SLOTS)
return;
const BTPfxDef &d = gBTPfxDefs[effect_number];
if (!d.valid)
return;
BTPfxEmitter e; // transient -- used only as the spawn context
e.def = &d;
e.pos = D3DXVECTOR3(x, y, z);
D3DXVECTOR3 az(backx, backy, backz);
if (D3DXVec3LengthSq(&az) < 1e-6f)
az = D3DXVECTOR3(0, 0, 1);
D3DXVec3Normalize(&az, &az);
D3DXVECTOR3 up(0, 1, 0);
if (fabsf(az.y) > 0.99f) up = D3DXVECTOR3(1, 0, 0);
D3DXVECTOR3 ax; D3DXVec3Cross(&ax, &up, &az); D3DXVec3Normalize(&ax, &ax);
D3DXVECTOR3 ay; D3DXVec3Cross(&ay, &az, &ax);
e.ax = ax; e.ay = ay; e.az = az;
e.emitAccum = 0.0f; e.issued = 0; e.active = 0;
e.ownerTag = 0; e.followSeg = -1;
for (int i = 0; i < count; ++i)
BTPfxSpawn(e);
}
static void BTPfxSpawn(const BTPfxEmitter &e)
{
const BTPfxDef &d = *e.def;
if (gBTPfxParticles.size() > 8192) // global particle cap (2048 starved
// laser bursts during missile volleys)
return;
BTPfxParticle p;
// Sample in the effect's LOCAL frame, then orient into the world through
// the emitter's basis (the victim's localToWorld rows). The position
// jitter pv is an isotropic scatter about the base offset -> symmetric
// (rand +-pv); the paired variances stay value + var*rand01 (DNBOOM's
// vel 150 / var -300 decodes to the symmetric +-150 burst).
D3DXVECTOR3 lp(
d.px + d.pv * (BTPfxRand01() * 2.0f - 1.0f),
d.py + d.pv * (BTPfxRand01() * 2.0f - 1.0f),
d.pz + d.pv * (BTPfxRand01() * 2.0f - 1.0f));
D3DXVECTOR3 lv(
d.velx + d.velxv * BTPfxRand01(),
d.vely + d.velyv * BTPfxRand01(),
d.velz + d.velzv * BTPfxRand01());
D3DXVECTOR3 la(
d.accelx + d.accelxv * BTPfxRand01(),
d.accely + d.accelyv * BTPfxRand01(),
d.accelz + d.accelzv * BTPfxRand01());
p.pos = e.pos + e.ax * lp.x + e.ay * lp.y + e.az * lp.z;
p.vel = e.ax * lv.x + e.ay * lv.y + e.az * lv.z;
p.accel = e.ax * la.x + e.ay * la.y + e.az * la.z;
p.age = 0.0f;
p.life = d.duration + d.durationv * BTPfxRand01();
if (p.life < 0.05f) p.life = 0.05f;
p.rad = d.rad + d.radv * BTPfxRand01();
p.exp = d.exp + d.expv * BTPfxRand01();
p.dexp = d.dexp + d.dexpv * BTPfxRand01();
p.colorWarp = (d.colorWarp > 1e-3f) ? d.colorWarp : 1.0f;
p.alphaWarp = (d.alphaWarp > 1e-3f) ? d.alphaWarp : 1.0f;
for (int i = 0; i < 4; ++i)
{
p.sI[i] = d.sI[i] + d.sIv[i] * BTPfxRand01();
p.sO[i] = d.sO[i] + d.sOv[i] * BTPfxRand01();
p.eI[i] = d.eI[i] + d.eIv[i] * BTPfxRand01();
p.eO[i] = d.eO[i] + d.eOv[i] * BTPfxRand01();
}
p.sfxMode = 0; // .PFX path: authored colour quads
p.uvVariant = (int)(BTPfxRand01() * 7.99f);
gBTPfxParticles.push_back(p);
}
//===========================================================================//
// OLD-STYLE SPECIALFX (the "more fire and 'splosions" layer) -- the 1995
// per-weapon impact effects: BTDPL.INI [effects_to_load] "specialfxN=Name" +
// [Name] descriptor sections (Chunks1/Sparks/PPCHit/LaserHit/MissileHit/
// Fireball/...). These are the dpl EFFECT-TABLE entries that effect numbers
// < 100 index (the 1996 ExplosionScripts' own comments match: 3=Chunks,
// 12=GroundHit, 15=Fireball) -- NOT psfx slots.
//
// SEMANTICS [T1, decoded from the i860 firmware VREND.MNG 2026-07-12]:
// - host installer FUN_00490210 ships the descriptor VERBATIM (cmd 0x1C,
// 0x44 bytes; trigger = cmd 0x1B {id, dcs, 4x4 matrix});
// - per board frame (30 Hz): h *= cool_a; RGB_ch = h_old*(h_new*cook_ch
// - 0.25) + 0.25 (superbright start -> 0.25 ember floor; constants pool
// @VA 0xF070: K=0.25 @0xF080, kill floor 1e-4 @0xF0A0);
// - alpha fades linearly (the step: out[0x40] -= dt * rate);
// - pos += vel; the burst frame is randomly SPUN per trigger (quadratic
// small-angle rotation, theta from the host-fed random table);
// - "variance" is DEAD DATA in the shipping pod: the binary looks up
// "variance" (@004f2604) but every INI section spells "varience" -- the
// lookup missed and the field stayed 0. Honoured (not parsed).
// - y_off reads as the particle KILL PLANE relative to the burst origin
// (Chunks -1 = die at the ground; hits -3) [T3 -- strongly data-indicated].
//===========================================================================//
struct BTSfxDef
{
int valid;
int type; // 0 = chunks (solid debris), 1/2 = fiery ball
int noTex; // texture=NOTEX(T): untextured sparks
float size, velocity, vBias, yOff;
float cook[3]; // RGB heat gains
float gravity;
float coolMul, coolSub;
int count, repeats;
};
#define BT_SFX_SLOTS 32
static BTSfxDef gBTSfxDefs[BT_SFX_SLOTS];
int BTInstallSfx(int slot, int type, int no_tex, float size, float velocity,
float v_bias, float y_off, const float cook[3], float gravity,
float cool_mul, float cool_sub, int count, int repeats)
{
if (slot < 0 || slot >= BT_SFX_SLOTS)
return 0;
BTSfxDef &d = gBTSfxDefs[slot];
d.type = type; d.noTex = no_tex;
d.size = size; d.velocity = velocity; d.vBias = v_bias; d.yOff = y_off;
d.cook[0] = cook[0]; d.cook[1] = cook[1]; d.cook[2] = cook[2];
d.gravity = gravity; d.coolMul = cool_mul; d.coolSub = cool_sub;
d.count = count > 0 ? count : 1;
d.repeats = repeats > 0 ? repeats : 1;
d.valid = 1;
return 1;
}
void BTStartSfx(int effect_number, float x, float y, float z)
{
if (effect_number < 0 || effect_number >= BT_SFX_SLOTS)
return;
const BTSfxDef &d = gBTSfxDefs[effect_number];
if (!d.valid)
return;
for (int i = 0; i < d.count; ++i)
{
if (gBTPfxParticles.size() > 8192)
return;
BTPfxParticle p;
memset(&p, 0, sizeof(p));
// Random burst direction; v_bias biases it upward (Sparks 1.4 =
// fountain, GroundHit 0.01 = flat ring, Fireball 0.99 = rising
// column -- the authored values read cleanly under this model).
// The firmware spins the whole burst frame by a random angle;
// per-particle isotropic randoms give the same coverage.
float ax = BTPfxRand01() * 2.0f - 1.0f;
float ay = BTPfxRand01() * 2.0f - 1.0f;
float az = BTPfxRand01() * 2.0f - 1.0f;
float dl = sqrtf(ax * ax + ay * ay + az * az);
if (dl < 1e-4f) { ax = 0; ay = 1; az = 0; dl = 1; }
ax /= dl; ay /= dl; az /= dl;
ay = fabsf(ay) * 0.3f + d.vBias; // upward bias
dl = sqrtf(ax * ax + ay * ay + az * az);
ax /= dl; ay /= dl; az /= dl;
float spd = d.velocity * (0.6f + 0.4f * BTPfxRand01());
// burst scatter: break the perfect overlap of count co-spawned
// sprites (the flash read as a single smooth ball) [T3]
float sc = d.size * 0.2f;
p.pos = D3DXVECTOR3(
x + sc * (BTPfxRand01() * 2.0f - 1.0f),
y + sc * (BTPfxRand01() * 2.0f - 1.0f),
z + sc * (BTPfxRand01() * 2.0f - 1.0f));
p.vel = D3DXVECTOR3(ax * spd, ay * spd, az * spd);
p.accel = D3DXVECTOR3(0.0f, -d.gravity, 0.0f);
p.age = 0.0f;
p.life = 10.0f; // real kills: heat/fade/plane
// size scale [T3]: x0.5 (size ~ diameter) with a 1.25m visibility
// floor for textured bursts -- combat ranges run 100m+; LaserHit
// (authored size 1.9) at <1m rendered ~8px = "practically nothing"
// (user-rejected). MissileHit 12 -> 6m radius core. Sparks/NOTEX
// keep their authored tininess (they read by count, not size).
p.rad = (d.size > 0.01f) ? d.size * 0.5f : 0.15f;
if (!d.noTex && d.size > 0.01f && p.rad < 1.25f) p.rad = 1.25f;
p.exp = 0.0f; p.dexp = 0.0f;
p.colorWarp = 1.0f; p.alphaWarp = 1.0f;
p.sfxMode = 1;
p.heat = 1.0f;
p.coolMul = (d.coolMul > 0.0f && d.coolMul < 1.0f) ? d.coolMul : 0.9f;
p.coolSub = d.coolSub > 0.0f ? d.coolSub : 0.02f;
p.fade = 1.0f;
p.cookR = d.cook[0]; p.cookG = d.cook[1]; p.cookB = d.cook[2];
p.killY = y + d.yOff;
p.sfxType = d.type;
p.uvVariant = (int)(BTPfxRand01() * 7.99f);
gBTPfxParticles.push_back(p);
}
}
// The beams' decoded grit texture (built in BTDrawBeams) -- shared with the
// particles so the fire quads carry the authentic firesmoke-family noise.
extern LPDIRECT3DTEXTURE9 BTGetBeamGritTexture();
void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt)
{
// ---- sim ----
if (dt > 0.1f) dt = 0.1f; // stall guard
// DEV HARNESS: BT_FX_TEST="10" (or "10,1003,15") fires the listed effect
// numbers every 3s, 30m in front of the camera -- iterate the effect look
// via screenshots without a live fire loop. <100 = specialfx descriptor,
// >=1000 = psfx slot n-1000 (the real dispatch encodings).
{
static int s_fxTestN = -1;
static int s_fxTest[8];
static float s_fxTestTimer = 0.0f;
if (s_fxTestN < 0)
{
s_fxTestN = 0;
const char *tv = getenv("BT_FX_TEST");
if (tv != 0)
{
const char *p = tv;
while (*p && s_fxTestN < 8)
{
s_fxTest[s_fxTestN++] = atoi(p);
while (*p && *p != ',') ++p;
if (*p == ',') ++p;
}
}
}
if (s_fxTestN > 0)
{
s_fxTestTimer -= dt;
if (s_fxTestTimer <= 0.0f)
{
s_fxTestTimer = 3.0f;
float cx, cy, cz;
d3d_OBJECT::GetCameraPosition(&cx, &cy, &cz);
// fire on BOTH sides of the camera (view-matrix handedness
// varies; one of the two is dead-centre in frame)
for (int side = -1; side <= 1; side += 2)
{
float fx = cx + view->_13 * 30.0f * side;
float fy = cy + view->_23 * 30.0f * side + 4.0f;
float fz = cz + view->_33 * 30.0f * side;
for (int i = 0; i < s_fxTestN; ++i)
{
extern void BTStartSfx(int, float, float, float);
if (s_fxTest[i] >= 1000)
BTStartPfx(s_fxTest[i] - 1000, fx, fy, fz);
else
BTStartSfx(s_fxTest[i], fx, fy, fz);
}
}
DEBUG_STREAM << "[fxtest] fired +-30m of cam (" << cx << "," << cy << "," << cz
<< ") particles=" << (int)gBTPfxParticles.size()
<< " emitters=" << (int)gBTPfxEmitters.size() << "\n" << std::flush;
}
}
}
for (size_t ei = 0; ei < gBTPfxEmitters.size(); ++ei)
{
BTPfxEmitter &e = gBTPfxEmitters[ei];
if (!e.active) continue;
// attached emitters ride their owner's segment (@004d097c dispatcher)
if (e.ownerTag != 0)
{
float p3[3], r9[9];
if (BTResolveSegmentWorld(e.ownerTag, e.followSeg, p3, r9))
{
e.pos = D3DXVECTOR3(p3[0], p3[1], p3[2]);
e.ax = D3DXVECTOR3(r9[0], r9[1], r9[2]);
e.ay = D3DXVECTOR3(r9[3], r9[4], r9[5]);
e.az = D3DXVECTOR3(r9[6], r9[7], r9[8]);
}
}
const BTPfxDef &d = *e.def;
// CONTINUOUS emission at `rate` particles/second until maximum_issue is
// exhausted. Data-verified semantics: in EVERY shipped .PFX,
// maxIssue/rate == release_period (DAFC 25/100~=0.2, DNBOOM 35/150~=0.2,
// DDAM2 35/16~=2, DDTHSMK 30/3=10) -- release_period IS the emission
// WINDOW, not a batch interval. (The old one-burst batching dumped a
// 10-second smoke plume in a single frame's puff.)
// EMISSION-WINDOW RULE (hardened 2026-07-12): release_period IS the
// emission window -- derive the rate from maxIssue/releasePeriod
// instead of trusting the authored 'rate' field. SHKWAVE.PFX (the
// missile-hit shockwave, mslhit's 1023) authors maxIssue=5,
// releasePeriod=0.2, rate=1 -- the one shipped file where they
// disagree; trusting 'rate' stretched the single 0.2s layered
// 5-shell blast into 5 metronomic 1Hz pulses ("silver mist exactly
// 5 times at perfectly timed intervals" -- user-reported).
float erate = (d.releasePeriod > 1e-3f)
? (float)d.maxIssue / d.releasePeriod : d.rate;
e.emitAccum += erate * dt;
int n = (int)e.emitAccum;
if (n > 0)
{
e.emitAccum -= (float)n;
if (e.issued + n > d.maxIssue) n = d.maxIssue - e.issued;
for (int b = 0; b < n; ++b)
BTPfxSpawn(e);
e.issued += n;
if (e.issued >= d.maxIssue)
e.active = 0; // done issuing -> instance ends
}
}
{ // compact finished emitters
size_t w = 0;
for (size_t i = 0; i < gBTPfxEmitters.size(); ++i)
if (gBTPfxEmitters[i].active) gBTPfxEmitters[w++] = gBTPfxEmitters[i];
gBTPfxEmitters.resize(w);
}
{ // advance + expire particles
const float boardSteps = dt * 30.0f; // the pod stepped the sfx sim per 30Hz frame
size_t w = 0;
for (size_t i = 0; i < gBTPfxParticles.size(); ++i)
{
BTPfxParticle &p = gBTPfxParticles[i];
p.age += dt;
if (p.age >= p.life) continue;
if (p.sfxMode)
{
// the firmware kills (heat floor 1e-4 @0xF0A0; the y_off kill
// plane). TIMEBASE [T1, re-read from the step disasm]: heat
// decays per BOARD FRAME (f18 = f17*f16, no dt -- hence the
// 30Hz boardSteps exponent) but the alpha fade is multiplied
// by RAW dt (@f0413754-64: out[0x40] -= dt * inst[0x1a4]) --
// cool_b is PER-SECOND. The earlier x30 scaling killed every
// burst in ~0.1s: fireballs read as a subliminal pop and
// laser hits as "nothing happens" (user-reported).
p.heat *= powf(p.coolMul, boardSteps);
p.fade -= p.coolSub * dt;
if (p.heat <= 1e-4f || p.fade <= 0.0f) continue;
if (p.pos.y < p.killY) continue;
}
p.vel += p.accel * dt;
p.pos += p.vel * dt;
p.rad += p.exp * dt;
p.exp += p.dexp * dt;
if (p.rad < 0.05f) p.rad = 0.05f;
gBTPfxParticles[w++] = p;
}
gBTPfxParticles.resize(w);
}
if (gBTPfxParticles.empty())
return;
// ---- draw: camera-facing additive quads (inner core + outer glow) ----
const D3DXVECTOR3 right(view->_11, view->_21, view->_31);
const D3DXVECTOR3 up (view->_12, view->_22, view->_32);
DWORD sLight, sFog, sZW, sBlend, sSrc, sDst, sCull, sCop, sCa1, sCa2, sAop, sAa1, sAa2;
dev->GetRenderState(D3DRS_LIGHTING, &sLight);
dev->GetRenderState(D3DRS_FOGENABLE, &sFog);
dev->GetRenderState(D3DRS_ZWRITEENABLE, &sZW);
dev->GetRenderState(D3DRS_ALPHABLENDENABLE, &sBlend);
dev->GetRenderState(D3DRS_SRCBLEND, &sSrc);
dev->GetRenderState(D3DRS_DESTBLEND, &sDst);
dev->GetRenderState(D3DRS_CULLMODE, &sCull);
dev->GetTextureStageState(0, D3DTSS_COLOROP, &sCop);
dev->GetTextureStageState(0, D3DTSS_COLORARG1, &sCa1);
dev->GetTextureStageState(0, D3DTSS_COLORARG2, &sCa2);
dev->GetTextureStageState(0, D3DTSS_ALPHAOP, &sAop);
dev->GetTextureStageState(0, D3DTSS_ALPHAARG1, &sAa1);
dev->GetTextureStageState(0, D3DTSS_ALPHAARG2, &sAa2);
DWORD sAdrU, sAdrV; // the scroll WRAPs sampler 0
dev->GetSamplerState(0, D3DSAMP_ADDRESSU, &sAdrU);
dev->GetSamplerState(0, D3DSAMP_ADDRESSV, &sAdrV);
// PREMULTIPLIED blending (ONE, INVSRCALPHA): the one model that renders BOTH
// authored particle families correctly. FIRE (bright colour, the additive
// glow) and SMOKE (dark/negative-ramp colour with high alpha -> OCCLUDES the
// scene behind it -- DDAM2 is 30% grey, DDTHSMK ramps to negative: both are
// invisible under pure additive, which is why damaged mechs never smoked).
// Vertex colour carries colour*alpha (premultiplied), vertex alpha carries
// the occlusion; the texture's falloff mask modulates both.
// BT_PFX_ADD=1 flips back to pure additive for A/B comparison.
static int s_pfxAdditive = -1;
if (s_pfxAdditive < 0)
{
const char *av = getenv("BT_PFX_ADD");
s_pfxAdditive = (av != 0 && av[0] == '1') ? 1 : 0;
}
dev->SetRenderState(D3DRS_LIGHTING, FALSE);
dev->SetRenderState(D3DRS_FOGENABLE, FALSE);
dev->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); // test Z, don't write it
dev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
dev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
dev->SetRenderState(D3DRS_DESTBLEND, s_pfxAdditive ? D3DBLEND_ONE : D3DBLEND_INVSRCALPHA);
dev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
// Particle sprite texture -- the AUTHORED firesmoke sheet. Every
// firesmokeN_scr_tex in BTFX.VMF maps the SAME 64x64 tileable noise image
// "bintA" (the variants differ only in SCROLL rate: firesmoke1 = 0.05,-0.331
// uv/s, firesmoke2 = 0,0.131, firesmoke4 = 0.05,-0.131), and firesmoke1_mtl
// colours it through the "fiery" ramp (0.3,0.1,0.1)->(0.9,0.7,0.3) -- the
// rolling orange fireball of the original. Bake ramp(lum) as the sprite
// colour with the noise detail in alpha; the RADIAL falloff lives in a
// SECOND (CLAMPed) texture stage so the WRAPPED scroll can roll flame
// through the sprite without scrolling the soft edge away. (The mask is
// port-shaped: we draw billboards where the original drew flame geometry,
// and a square scrolled sheet on a quad reads as a hard box.)
// Fallback when BINTA.VTX is absent: the old grit x radial single-tex bake.
static LPDIRECT3DTEXTURE9 s_pfxTex = 0; // fallback single-texture
static LPDIRECT3DTEXTURE9 s_pfxNoise = 0; // bintA through the "fiery" ramp
static LPDIRECT3DTEXTURE9 s_pfxNoiseGray = 0; // RAW bintA (grayscale) -- the
// old-style specialfx sample the
// TEXTURE, not the ramped material:
// the heat model supplies ALL colour
static LPDIRECT3DTEXTURE9 s_pfxMask = 0; // radial falloff (stage 1, CLAMP)
static int s_pfxTexTried = 0;
if (!s_pfxTexTried)
{
s_pfxTexTried = 1;
const int TW = 64, TH = 64;
Image fireImg = decodeImage("VIDEO\\TEX\\BINTA.VTX");
if (fireImg.ok && fireImg.w > 0 && fireImg.h > 0 &&
SUCCEEDED(dev->CreateTexture(fireImg.w, fireImg.h, 1, 0, D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED, &s_pfxNoise, NULL)))
{
D3DLOCKED_RECT lr;
if (SUCCEEDED(s_pfxNoise->LockRect(0, &lr, NULL, 0)))
{
const float lo[3] = { 0.3f, 0.1f, 0.1f }; // RAMP "fiery" (BTFX.VMF)
const float hi[3] = { 0.9f, 0.7f, 0.3f };
for (int y = 0; y < fireImg.h; ++y)
{
DWORD *dst = (DWORD *)((char *)lr.pBits + y * lr.Pitch);
for (int x = 0; x < fireImg.w; ++x)
{
uint32_t s = fireImg.argb[(size_t)y * fireImg.w + x];
float lum = (((s >> 16) & 0xFF) + ((s >> 8) & 0xFF) + (s & 0xFF)) / (3.0f * 255.0f);
int r = (int)((lo[0] + (hi[0] - lo[0]) * lum) * 255.0f);
int g = (int)((lo[1] + (hi[1] - lo[1]) * lum) * 255.0f);
int b = (int)((lo[2] + (hi[2] - lo[2]) * lum) * 255.0f);
// alpha carries the noise detail (same 0.35 floor the grit
// bake used) -> the occlusion edge breaks up like flame
int a = (int)((0.35f + 0.65f * lum) * 255.0f);
dst[x] = ((DWORD)a << 24) | (r << 16) | (g << 8) | b;
}
}
s_pfxNoise->UnlockRect(0);
}
else { s_pfxNoise->Release(); s_pfxNoise = 0; }
}
if (s_pfxNoise && fireImg.ok &&
SUCCEEDED(dev->CreateTexture(fireImg.w, fireImg.h, 1, 0, D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED, &s_pfxNoiseGray, NULL)))
{
D3DLOCKED_RECT lr;
if (SUCCEEDED(s_pfxNoiseGray->LockRect(0, &lr, NULL, 0)))
{
for (int y = 0; y < fireImg.h; ++y)
{
DWORD *dst = (DWORD *)((char *)lr.pBits + y * lr.Pitch);
for (int x = 0; x < fireImg.w; ++x)
{
uint32_t s = fireImg.argb[(size_t)y * fireImg.w + x];
int l = (((s >> 16) & 0xFF) + ((s >> 8) & 0xFF) + (s & 0xFF)) / 3;
int a = (int)((0.35f + 0.65f * (l / 255.0f)) * 255.0f);
dst[x] = ((DWORD)a << 24) | (l << 16) | (l << 8) | l;
}
}
s_pfxNoiseGray->UnlockRect(0);
}
else { s_pfxNoiseGray->Release(); s_pfxNoiseGray = 0; }
}
if (s_pfxNoise &&
SUCCEEDED(dev->CreateTexture(TW, TH, 1, 0, D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED, &s_pfxMask, NULL)))
{
D3DLOCKED_RECT lr;
if (SUCCEEDED(s_pfxMask->LockRect(0, &lr, NULL, 0)))
{
for (int y = 0; y < TH; ++y)
{
DWORD *dst = (DWORD *)((char *)lr.pBits + y * lr.Pitch);
for (int x = 0; x < TW; ++x)
{
float dx = (x + 0.5f) / TW * 2.0f - 1.0f;
float dy = (y + 0.5f) / TH * 2.0f - 1.0f;
float r = sqrtf(dx * dx + dy * dy);
float f = 1.0f - r; // radial falloff
if (f < 0.0f) f = 0.0f;
f = f * f * (3.0f - 2.0f * f); // smoothstep edge
int v = (int)(f * 255.0f);
dst[x] = ((DWORD)v << 24) | (v << 16) | (v << 8) | v;
}
}
s_pfxMask->UnlockRect(0);
}
else { s_pfxMask->Release(); s_pfxMask = 0; }
}
if (s_pfxNoise && !s_pfxMask) { s_pfxNoise->Release(); s_pfxNoise = 0; }
if (!s_pfxNoise)
{
LPDIRECT3DTEXTURE9 grit = BTGetBeamGritTexture();
unsigned char gritLum[128 * 64]; // grit is 128x64 when present
int gw = 0, gh = 0;
if (grit)
{
D3DSURFACE_DESC gd;
if (SUCCEEDED(grit->GetLevelDesc(0, &gd)) && gd.Width <= 128 && gd.Height <= 64)
{
D3DLOCKED_RECT glr;
if (SUCCEEDED(grit->LockRect(0, &glr, NULL, D3DLOCK_READONLY)))
{
gw = gd.Width; gh = gd.Height;
for (int y = 0; y < gh; ++y)
{
const DWORD *src = (const DWORD *)((const char *)glr.pBits + y * glr.Pitch);
for (int x = 0; x < gw; ++x)
gritLum[y * gw + x] = (unsigned char)(src[x] & 0xFF);
}
grit->UnlockRect(0);
}
}
}
if (SUCCEEDED(dev->CreateTexture(TW, TH, 1, 0, D3DFMT_A8R8G8B8,
D3DPOOL_MANAGED, &s_pfxTex, NULL)))
{
D3DLOCKED_RECT lr;
if (SUCCEEDED(s_pfxTex->LockRect(0, &lr, NULL, 0)))
{
for (int y = 0; y < TH; ++y)
{
DWORD *dst = (DWORD *)((char *)lr.pBits + y * lr.Pitch);
for (int x = 0; x < TW; ++x)
{
float dx = (x + 0.5f) / TW * 2.0f - 1.0f;
float dy = (y + 0.5f) / TH * 2.0f - 1.0f;
float r = sqrtf(dx * dx + dy * dy);
float f = 1.0f - r; // radial falloff
if (f < 0.0f) f = 0.0f;
f = f * f * (3.0f - 2.0f * f); // smoothstep edge
float n = 1.0f;
if (gw > 0) // authentic grit detail
n = 0.35f + 0.65f * (gritLum[(y % gh) * gw + (x % gw)] / 255.0f);
int v = (int)(f * n * 255.0f);
if (v > 255) v = 255;
// the falloff mask lives in ALPHA too: the premultiplied
// draw (ONE, INVSRCALPHA) needs soft-edged OCCLUSION for
// smoke, not just soft-edged colour for fire
dst[x] = (v << 24) | (v << 16) | (v << 8) | v;
}
}
s_pfxTex->UnlockRect(0);
}
else { s_pfxTex->Release(); s_pfxTex = 0; }
}
}
}
if (s_pfxNoise)
{
// stage 0: diffuse x the scrolled noise (WRAPped). GRAYSCALE: every
// .PFX and specialfx descriptor references the TEXTURE
// btfx:firesmokeN_scr_tex (the raw bintA noise), NOT the fiery-ramped
// material -- the AUTHORED per-particle colours are the sole colour
// source (DNBOOM's 0.8,0.3,0 = the orange fire; DDAM's 0.5-gray = the
// smoke). The earlier ramp-baked sheet gilded the gray smoke bands
// into "gold blasts of light" (user-rejected). The fiery ramp
// belongs to the MATERIAL path only (flamebig etc. via bgfload).
dev->SetTexture(0, s_pfxNoiseGray ? s_pfxNoiseGray : s_pfxNoise);
dev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
dev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
// NO SCROLL on particle stamps: the authored SCROLL belongs to the
// TEXTURE record for the MATERIAL path (flame meshes); sliding the
// noise through short-lived particle sprites reads as a shimmering
// crawl ("not sure what I'm looking at" -- user-rejected). Each
// particle instead gets a FIXED random window into the tileable
// sheet (u0/v0, set at spawn; the WRAP addressing makes any window
// valid) -- the board's per-burst random spin de-correlation.
dev->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
dev->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
dev->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
// stage 1: x the CLAMPed radial mask (same UV set, UNscrolled) -- both
// colour and alpha, so the premultiplied occlusion stays soft-edged
dev->SetTexture(1, s_pfxMask);
dev->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE);
dev->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_CURRENT);
dev->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TEXTURE);
dev->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
dev->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_CURRENT);
dev->SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
dev->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 0);
dev->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
dev->SetSamplerState(1, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
dev->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
dev->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
}
else
{
dev->SetTexture(0, s_pfxTex);
dev->SetTextureStageState(0, D3DTSS_COLOROP, s_pfxTex ? D3DTOP_MODULATE : D3DTOP_SELECTARG1);
dev->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
dev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);
// occlusion = vertex alpha x the texture's falloff mask (soft-edged smoke)
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, s_pfxTex ? D3DTOP_MODULATE : D3DTOP_SELECTARG1);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
}
D3DXMATRIX ident; D3DXMatrixIdentity(&ident);
dev->SetTransform(D3DTS_WORLD, &ident);
dev->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1);
struct V { float x, y, z; DWORD c; float u, v; };
static std::vector<V> verts;
verts.clear();
// SPECIALFX batch: drawn with the FIERY-RAMPED sheet. On the IG board the
// heat channels are BRIGHTNESS multipliers over an inherently fire-
// coloured texture (firesmoke through its material ramp): white-hot flash
// -> ember-orange dissipating cloud (the demo fireballs). The .PFX batch
// keeps the GRAYSCALE sheet (its authored colours decide: DNBOOM orange
// fire, DDAM gray smoke). Drawing sfx ember clouds grayscale made them
// "explosive silver mist" (user-rejected).
static std::vector<V> sfxVerts;
sfxVerts.clear();
// BACK-TO-FRONT order: premultiplied occlusion (smoke) composites in depth
// order; unsorted draws pop when a near puff renders before a far one.
static std::vector<size_t> order;
static std::vector<float> depth;
order.resize(gBTPfxParticles.size());
depth.resize(gBTPfxParticles.size());
for (size_t i = 0; i < gBTPfxParticles.size(); ++i)
{
const D3DXVECTOR3 &pp = gBTPfxParticles[i].pos;
order[i] = i;
depth[i] = pp.x * view->_13 + pp.y * view->_23 + pp.z * view->_33; // view-space z
}
std::sort(order.begin(), order.end(),
[](size_t a, size_t b) { return depth[a] > depth[b]; });
for (size_t oi = 0; oi < order.size(); ++oi)
{
const BTPfxParticle &p = gBTPfxParticles[order[oi]];
float t = p.age / p.life;
if (t < 0.0f) t = 0.0f; if (t > 1.0f) t = 1.0f;
// colour/alpha interpolation start->end, warped (t^(1/warp): a large
// warp shifts to the end colour early -- the fast orange->smoke shift)
float tc = powf(t, 1.0f / p.colorWarp);
float ta = powf(t, 1.0f / p.alphaWarp);
// The .PFX sI/sO are the INTERIOR/EXTERIOR colours of ONE sprite (the
// dpl_PARTICLE model) -- approximated as an exterior quad at the
// AUTHORED radius + an interior core at 0.45x, with the exterior's
// occlusion halved (it is the sprite's soft skirt, not a second
// particle). The old 2.2x "outer glow" inflated every particle and
// doubled its occlusion -- 20 overlapping damage-smoke particles
// merged into one giant black blob (seen in the BT_FX_TEST captures).
// Old-style specialfx draw ONE sprite per particle (the firmware
// draw emits a single projected sprite).
for (int layer = (p.sfxMode ? 1 : 0); layer < 2; ++layer)
{
const float *cs = layer ? p.sI : p.sO;
const float *ce = layer ? p.eI : p.eO;
float scale = layer ? 0.45f : 1.0f;
if (p.sfxMode) scale = 1.0f;
float r, g, b, a;
if (p.sfxMode)
{
// THE FIRMWARE HEAT EQUATION [T1]: channel = h_old*(h_new*
// cook_ch - 0.25) + 0.25. h was already stepped in the sim
// above; approximate h_old over this frame's step from the
// per-frame factor (h_old = h / coolMul^steps was h before
// the step -- one step back is close enough at 30Hz).
float hNew = p.heat;
float hOld = p.heat / p.coolMul; // one board step back
if (hOld > 1.0f) hOld = 1.0f;
r = hOld * (hNew * p.cookR - 0.25f) + 0.25f;
g = hOld * (hNew * p.cookG - 0.25f) + 0.25f;
b = hOld * (hNew * p.cookB - 0.25f) + 0.25f;
if (r < 0) r = 0; if (r > 1) r = 1;
if (g < 0) g = 0; if (g > 1) g = 1;
if (b < 0) b = 0; if (b > 1) b = 1;
// occlusion weight: solid debris chunks occlude; fiery balls
// mostly-occlude while HOT (bright fire body), thinning as
// the heat dies (0.5 halved the flash into a faint wisp at
// combat range -- "practically nothing", user-rejected)
float occl = (p.sfxType == 0) ? 0.85f
: (0.45f + 0.45f * (p.heat > 1.0f ? 1.0f : p.heat));
a = p.fade * occl;
}
else
{
r = cs[0] + (ce[0] - cs[0]) * tc;
g = cs[1] + (ce[1] - cs[1]) * tc;
b = cs[2] + (ce[2] - cs[2]) * tc;
a = cs[3] + (ce[3] - cs[3]) * ta;
if (layer == 0) a *= 0.5f; // exterior skirt: half occlusion
}
if (a <= 0.0f) continue;
if (a > 1.0f) a = 1.0f;
// PREMULTIPLIED: vertex rgb = colour x alpha (the framebuffer
// contribution), vertex a = alpha (the occlusion). A colour ramped
// NEGATIVE (DDTHSMK's smoke tail) clamps to 0 -> pure occluding
// black smoke; a bright fire colour with fading alpha dims out.
int ir = (int)(r * a * 255.0f); if (ir > 255) ir = 255; if (ir < 0) ir = 0;
int ig = (int)(g * a * 255.0f); if (ig > 255) ig = 255; if (ig < 0) ig = 0;
int ib = (int)(b * a * 255.0f); if (ib > 255) ib = 255; if (ib < 0) ib = 0;
int ia = (int)(a * 255.0f); if (ia > 255) ia = 255; if (ia < 0) ia = 0;
if (ia == 0 && ir == 0 && ig == 0 && ib == 0) continue;
DWORD c = ((DWORD)ia << 24) | (ir << 16) | (ig << 8) | ib;
float rad = p.rad * scale;
D3DXVECTOR3 rv = right * rad, uv = up * rad;
// noise-stamp orientation: mirror/swap the 0..1 UVs by the
// particle's variant (stays in 0..1 -> the CLAMPed mask holds)
float cu[4] = { 0, 0, 1, 1 }; // corners: BL, TL, TR, BR
float cv[4] = { 1, 0, 0, 1 };
for (int k = 0; k < 4; ++k)
{
float uu2 = cu[k], vv2 = cv[k];
if (p.uvVariant & 1) uu2 = 1.0f - uu2;
if (p.uvVariant & 2) vv2 = 1.0f - vv2;
if (p.uvVariant & 4) { float t = uu2; uu2 = vv2; vv2 = t; }
cu[k] = uu2; cv[k] = vv2;
}
V q[6];
q[0].x = p.pos.x - rv.x - uv.x; q[0].y = p.pos.y - rv.y - uv.y; q[0].z = p.pos.z - rv.z - uv.z; q[0].u = cu[0]; q[0].v = cv[0];
q[1].x = p.pos.x - rv.x + uv.x; q[1].y = p.pos.y - rv.y + uv.y; q[1].z = p.pos.z - rv.z + uv.z; q[1].u = cu[1]; q[1].v = cv[1];
q[2].x = p.pos.x + rv.x + uv.x; q[2].y = p.pos.y + rv.y + uv.y; q[2].z = p.pos.z + rv.z + uv.z; q[2].u = cu[2]; q[2].v = cv[2];
q[3] = q[0];
q[4] = q[2];
q[5].x = p.pos.x + rv.x - uv.x; q[5].y = p.pos.y + rv.y - uv.y; q[5].z = p.pos.z + rv.z - uv.z; q[5].u = cu[3]; q[5].v = cv[3];
std::vector<V> &sink = p.sfxMode ? sfxVerts : verts;
for (int k = 0; k < 6; ++k) { q[k].c = c; sink.push_back(q[k]); }
}
}
// DIAG (BT_FX_TEST): prove the draw reaches pixels -- particle count, the
// vertex count, the first particle's colour/alpha/pos, texture presence.
if (getenv("BT_FX_TEST") != 0 && !gBTPfxParticles.empty())
{
static float s_drawLog = 0.0f;
s_drawLog += dt;
if (s_drawLog >= 2.0f)
{
s_drawLog = 0.0f;
const BTPfxParticle &p0 = gBTPfxParticles[0];
DEBUG_STREAM << "[pfx-draw] n=" << (int)gBTPfxParticles.size()
<< " verts=" << (int)verts.size()
<< " tex=" << (s_pfxNoiseGray ? "gray" : (s_pfxNoise ? "fiery" : (s_pfxTex ? "grit" : "NONE")))
<< " p0(sfx=" << p0.sfxMode << " pos=" << p0.pos.x << "," << p0.pos.y << "," << p0.pos.z
<< " rad=" << p0.rad << " heat=" << p0.heat << " fade=" << p0.fade
<< ")\n" << std::flush;
if (!verts.empty())
DEBUG_STREAM << "[pfx-draw] v0=(" << verts[0].x << "," << verts[0].y << "," << verts[0].z
<< ") c=" << std::hex << (unsigned)verts[0].c << std::dec << "\n" << std::flush;
}
}
if (!verts.empty())
dev->DrawPrimitiveUP(D3DPT_TRIANGLELIST, (UINT)verts.size() / 3,
&verts[0], sizeof(V));
if (!sfxVerts.empty())
{
// the fiery-ramped sheet for the heat-model bursts (see above)
if (s_pfxNoise)
dev->SetTexture(0, s_pfxNoise);
dev->DrawPrimitiveUP(D3DPT_TRIANGLELIST, (UINT)sfxVerts.size() / 3,
&sfxVerts[0], sizeof(V));
}
dev->SetTexture(0, NULL);
if (s_pfxNoise)
{
// tear down the two-stage fire pipe: stage 1 back to the D3D default
// (DISABLE), the scroll transform off, sampler 0 addressing restored
dev->SetTexture(1, NULL);
dev->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
dev->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
dev->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
}
dev->SetSamplerState(0, D3DSAMP_ADDRESSU, sAdrU);
dev->SetSamplerState(0, D3DSAMP_ADDRESSV, sAdrV);
dev->SetRenderState(D3DRS_LIGHTING, sLight);
dev->SetRenderState(D3DRS_FOGENABLE, sFog);
dev->SetRenderState(D3DRS_ZWRITEENABLE, sZW);
dev->SetRenderState(D3DRS_ALPHABLENDENABLE, sBlend);
dev->SetRenderState(D3DRS_SRCBLEND, sSrc);
dev->SetRenderState(D3DRS_DESTBLEND, sDst);
dev->SetRenderState(D3DRS_CULLMODE, sCull);
dev->SetTextureStageState(0, D3DTSS_COLOROP, sCop);
dev->SetTextureStageState(0, D3DTSS_COLORARG1, sCa1);
dev->SetTextureStageState(0, D3DTSS_COLORARG2, sCa2);
dev->SetTextureStageState(0, D3DTSS_ALPHAOP, sAop);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG1, sAa1);
dev->SetTextureStageState(0, D3DTSS_ALPHAARG2, sAa2);
}
// BT (task #20): the live window client aspect (0 = never resized -> the
// projection builders fall back to the configured x_size/y_size). Set by
// L4NotifyWindowResized (WM_SIZE) so a user-resized window doesn't stretch
// the scene fat/skinny.
float gWindowAspect = 0.0f;
// The PRESENTED aspect (task #45): the fixed 800x600 backbuffer stretches into
// the client area, so anything drawn in backbuffer pixels must pre-compensate
// by the CLIENT aspect to appear square on screen. The HUD + the aim-ray
// reticle<->NDC conversions use this.
float BTGetPresentAspect()
{
extern float gWindowAspect;
return (gWindowAspect > 0.0f) ? gWindowAspect : (800.0f / 600.0f);
}
#define PILL_COUNT 20
#define PILL_SIZE (y_size*0.03125) // 32 @ 1280x1024
#define PILL_SPACING (PILL_SIZE*0.625) // 20 @ 1280x1024
#define LOAD_COLOR 0xFF6565FF
#define FADE_IN 0.05f
#define PILL_ON 0.05f
#define FADE_OUT 0.5f
#define LOAD_TEXT_HEIGHT PILL_SIZE // 32 @ 1280x1024
#define LOAD_TEXT_WIDTH (LOAD_TEXT_HEIGHT*12.125) // 388 @ 1280x1024
#define PS_OFF 0
#define PS_FADINGIN 1
#define PS_ON 2
#define PS_FADINGOUT 3
#define LOAD_TEXT_VERT (PILL_SIZE/2.0)
#ifdef LOGFRAMERATE
FILE *FRAMERATE_LOG;
#endif
//
// Includes for the DPL libraries
//
// RB 1/15/07
//#include <dpl\dpl.h>
//#include <dpl\dpl_2d.h>
//#include <dpl\dpl_vpx.h>
//#include <dpl\dplutils.h>
//#include <dpl\matrix.h>
//
// You can enable or disable these traces here for quicker rebuilding during
// profile testing, but for production they should be all turned off!
//
//#define TRACE_VIDEO_CULL_SETUP
//#define TRACE_VIDEO_VEHICLE_RENDERABLES
// #define TRACE_VIDEO_ALL_RENDERABLES
//
// These are other traces you can use to extract data
//
#if defined(TRACE_VIDEO_LOAD_OBJECT)
static BitTrace Video_Load_Object("Video Load Object");
#define SET_VIDEO_LOAD_OBJECT() Video_Load_Object.Set()
#define CLEAR_VIDEO_LOAD_OBJECT() Video_Load_Object.Clear()
#else
#define SET_VIDEO_LOAD_OBJECT()
#define CLEAR_VIDEO_LOAD_OBJECT()
#endif
#if defined(TRACE_VIDEO_CONSTRUCT_ROOT)
static BitTrace Video_Construct_Root("Video Construct Root");
#define SET_VIDEO_CONSTRUCT_ROOT() Video_Construct_Root.Set()
#define CLEAR_VIDEO_CONSTRUCT_ROOT() Video_Construct_Root.Clear()
#else
#define SET_VIDEO_CONSTRUCT_ROOT()
#define CLEAR_VIDEO_CONSTRUCT_ROOT()
#endif
//
// This is the time spent fetching and processing the pickpoint information
// from the DPL renderer.
//
#if defined(TRACE_VIDEO_PICKPOINT)
static BitTrace Video_Pickpoint("Video Pickpoint");
#define SET_VIDEO_PICKPOINT() Video_Pickpoint.Set()
#define CLEAR_VIDEO_PICKPOINT() Video_Pickpoint.Clear()
#else
#define SET_VIDEO_PICKPOINT()
#define CLEAR_VIDEO_PICKPOINT()
#endif
//
// Traces the time spent setting up the video culling parameters
//
#if defined(TRACE_VIDEO_CULL_SETUP)
static BitTrace Video_Cull_Setup("Video Cull Setup");
# define SET_VIDEO_CULL_SETUP() Video_Cull_Setup.Set()
# define CLEAR_VIDEO_CULL_SETUP() Video_Cull_Setup.Clear()
#else
# define SET_VIDEO_CULL_SETUP()
# define CLEAR_VIDEO_CULL_SETUP()
#endif
//
// This trace covers the entire time spent executing renderables as one
// transition. It goes up when the execute implimentation starts running
// over the all iterator and goes down when it's done.
//
#if defined(TRACE_VIDEO_RENDERABLES)
static BitTrace Video_Renderables("Video Renderables");
#define SET_VIDEO_RENDERABLES() Video_Renderables.Set()
#define CLEAR_VIDEO_RENDERABLES() Video_Renderables.Clear()
#else
#define SET_VIDEO_RENDERABLES()
#define CLEAR_VIDEO_RENDERABLES()
#endif
//
// Traces time spent executing renderables that belong to a vehicle entity
// (currently Mech, VTV or BTPlayer)
//
#if defined(TRACE_VIDEO_VEHICLE_RENDERABLES)
static BitTrace Video_Vehicle_Renderables("Video Vehicle Renderables");
# define SET_VIDEO_VEHICLE_RENDERABLES() Video_Vehicle_Renderables.Set()
# define CLEAR_VIDEO_VEHICLE_RENDERABLES() Video_Vehicle_Renderables.Clear()
#else
# define SET_VIDEO_VEHICLE_RENDERABLES()
# define CLEAR_VIDEO_VEHICLE_RENDERABLES()
#endif
//
// This trace will transition as each individual renderable is executed.
// It will buzz and cause serious impact to the accuracy of the trace but is
// good for identifying time-eating individual renderables
//
#if defined(TRACE_VIDEO_ALL_RENDERABLES)
static BitTrace Video_All_Renderables("Video All Renderables");
# define SET_VIDEO_ALL_RENDERABLES() Video_All_Renderables.Set()
# define CLEAR_VIDEO_ALL_RENDERABLES() Video_All_Renderables.Clear()
#else
# define SET_VIDEO_ALL_RENDERABLES()
# define CLEAR_VIDEO_ALL_RENDERABLES()
#endif
//
// This is the routine that does a DPL_FlushArticulations on the batch of
// DCS's that we've been holding during rendering, std::flushing them all to
// the card in a big batch
//
#if defined(TRACE_VIDEO_BATCH_FLUSH)
static BitTrace Video_Batch_Flush("Video Batch Flush");
# define SET_VIDEO_BATCH_FLUSH() Video_Batch_Flush.Set()
# define CLEAR_VIDEO_BATCH_FLUSH() Video_Batch_Flush.Clear()
#else
# define SET_VIDEO_BATCH_FLUSH()
# define CLEAR_VIDEO_BATCH_FLUSH()
#endif
//
// This trace shows the time spent doing the "frame done" processing where the
// renderer waits for the DPL card to become ready and then issues a command
// to start the scene drawing. An additional batch std::flush can happen here.
// If USE_ONE_VIDEO_TRACE is turned on, data from this trace will show up as
// part of the Video_Renderer trace.
//
#if defined(TRACE_VIDEO_RENDERER_FRAME_DONE)
# if defined(USE_ONE_VIDEO_TRACE)
# define SET_VIDEO_RENDERER_FRAME_DONE() Video_Renderer.Set()
# define CLEAR_VIDEO_RENDERER_FRAME_DONE() Video_Renderer.Clear()
# else
static BitTrace Video_Renderer_Frame_Done("Video Renderer Frame Done");
# define SET_VIDEO_RENDERER_FRAME_DONE() Video_Renderer_Frame_Done.Set()
# define CLEAR_VIDEO_RENDERER_FRAME_DONE() Video_Renderer_Frame_Done.Clear()
# endif
#else
# define SET_VIDEO_RENDERER_FRAME_DONE()
# define CLEAR_VIDEO_RENDERER_FRAME_DONE()
#endif
//
// The following traces are specifically designed for profileing the low level
// DPL routines in Division's libraries. You need to link to a special "profile"
// version of the division libraries for these to work. If you enable them
// with a production libdpl you will get NO traces. If you disable them and
// try to link to a profile version of libdpl you will get link errors. Sorry
// but this is the most efficient way of connecting the c++ trace routines to
// the C code of division's libraries.
//
// There are switches in the DPL libraries that handle turning these functions
// on and off in the profile version.
//
//#define TRACE_DPL_LOAD_OBJECT
//#define TRACE_DPL_TRANSPUTER_LINK // will buzz
//#define TRACE_DPL_DO_OUTSW // will buzz
//#define TRACE_DPL_OUTINT32 // will buzz
#if defined(TRACE_DPL_LOAD_OBJECT)
static BitTrace DPL_Load_Object("DPL Load Object");
extern "C"
{
void Set_DPL_Load_Object();
void Clear_DPL_Load_Object();
}
void
Set_DPL_Load_Object()
{
DPL_Load_Object.Set();
}
void
Clear_DPL_Load_Object()
{
DPL_Load_Object.Clear();
}
#endif
#if defined(TRACE_DPL_TRANSPUTER_LINK)
static BitTrace DPL_Transputer_Link("DPL Transputer Link");
extern "C"
{
void Set_DPL_Transputer_Link();
void Clear_DPL_Transputer_Link();
}
void
Set_DPL_Transputer_Link()
{
DPL_Transputer_Link.Set();
}
void
Clear_DPL_Transputer_Link()
{
DPL_Transputer_Link.Clear();
}
#endif
#if defined(TRACE_DPL_DO_OUTSW)
static BitTrace DPL_do_outsw("DPL do_outsw");
extern "C"
{
void Set_DPL_do_outsw();
void Clear_DPL_do_outsw();
}
void
Set_DPL_do_outsw()
{
DPL_do_outsw.Set();
}
void
Clear_DPL_do_outsw()
{
DPL_do_outsw.Clear();
}
#endif
#if defined(TRACE_DPL_OUTINT32)
static BitTrace DPL_outint32("DPL outint32");
extern "C"
{
void Set_DPL_outint32();
void Clear_DPL_outint32();
}
void
Set_DPL_outint32()
{
DPL_outint32.Set();
}
void
Clear_DPL_outint32()
{
DPL_outint32.Clear();
}
#endif
//
// The following trace will probably be obsolete soon
//
#if 0
#if defined(TRACE_VIDEO_FIRST_FRAME_DONE)
static BitTrace Video_First_Frame_Done("Video First Frame Done");
# define SET_VIDEO_FIRST_FRAME_DONE() Video_First_Frame_Done.Set()
# define CLEAR_VIDEO_FIRST_FRAME_DONE() Video_First_Frame_Done.Clear()
#else
# define SET_VIDEO_FIRST_FRAME_DONE()
# define CLEAR_VIDEO_FIRST_FRAME_DONE()
#endif
#endif
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Definitions of various debugging switches
//
#define DEBUG_SPECIAL_CALLBACK 0 // 0 = none, 1 = print special, 2 = real noisy
#define DEBUG_CREATE_CALLBACK 0 // 0 = none,
#define PRINT_THE_PFX False // Set to true and the pfx definition will print when it is read in.
#define RAPID_SECT_PIXEL True // True if you want to use the rapidsectpixel call for intersections
#define PARTICLE_TEST False // True to issue a stream of particles at the detected intersect point
#define PRINT_PICKPOINT_TEST False // True to print data whenever the intersect changes
#define NOISY_RENDERER False // True enables a lot of printouts as the renderer makes/kills object
#define USE_TRACKER_STRUCTURE False // True to enable the full tracker structure that holds damage zone names & such
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Definitions of system constants used only in this file
//
#define MAX_SPECIAL_ARGUMENTS 25 // Maximum number of arguments a "special" callback can handle
#define MAX_SPECIAL_SIZE 512 // Maximum size (characters) of a "special"
#define dpl_arg_sep '~' // seperator used when parsing dpl argument string
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity
*Entity_Being_Created = 0; // !!! temp, till callback handlers become a class
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// These extern "c" routines and constants are from startdpl or from dpl
// itself and need to be here.
//
extern "C" {
//STUBBED: DPL RB 1/15/07
//extern const int
// x_size, // x size of the screen (from startdpl)
// y_size; // y size of the screen (startdpl)
void
dpl_TexmapTexels2D ( // A routine from DPL we normally couldn't access
dpl_TEXMAP *tm,
uint32 *texels,
int32 u_size,
int32 v_size,
int32 bytes_per_texel);
int
screen_resolution( // Sets up the screen resolution (startdpl)
char *dpl_argv ),
explode_args( // Parses DPLARG system environmental (startdpl)
char **argv,
char *dpl_args,
char sep );
char
*dpl_TypeToString( // Converts a DPL type code into a string (libdpl)
dpl_TYPE t);
int32
dpl_DrawSceneComplete(void); // missing definition (libdpl)
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// see DPLReportPerfStats()
//STUBBED: VIDEO RB 1/15/07
//extern "C" const int32 __sect_time;
//extern "C" const int32 __last_cull_time;
//extern "C" const int32 __last_draw_time;
//extern "C" const int32 __last_frame_time;
//extern "C" const int32 __last_pxpl_time;
//extern "C" const int32 __last_frame_prims;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// dump_frame_buffer() std::declaration
void dump_frame_buffer(
dpl_VIEW *eye,
int32 x_size,
int32 y_size,
Logical antialias);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The following are definitions of DPL callback functions. These are called
// by the dpl file loader as it is loading graphics files so we can do material
// substitution and set special attributes on geometry & such.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The "special" callback is called whenever a "special" field is processed in
// incomming geometry.
// dPL special callback function type.
// typedef void (dpl_SPECIAL_CALLBACK)(dpl_TYPE type, void *handle,
// char8 *special, uint32 special_len);
static void
TestSpecialCallBack(
dpl_TYPE type,
void *handle,
char8 *special,
uint32 special_len)
{
//STUBBED: DPL RB 1/14/07
// char
// *args[MAX_SPECIAL_ARGUMENTS],
// **argptr,
// *argstring;
// int
// argcount;
// float
// u0,
// v0,
// du,
// dv;
// int32
// immunity;
// char
//// NameBuff[80],
// TempBuff[MAX_SPECIAL_SIZE + 1];
// #if USE_TRACKER_STRUCTURE
// dpl_tracker
// *this_tracker;
// #endif
//
// if(special_len > MAX_SPECIAL_SIZE)
// {
// DEBUG_STREAM<<"SPECIAL was bigger than "<<MAX_SPECIAL_SIZE<<"\n" << std::flush;
// Verify(special_len > MAX_SPECIAL_SIZE);
// }
// strncpy(TempBuff, (const char*)special, special_len);
// TempBuff[special_len] = 0;
// if(TempBuff[special_len-1] != 0)
// {
// DEBUG_STREAM<<"Caution, the following SPECIAL was not null terminated\n" << std::flush;
// printf("SPECIAL->%08x: %s, %s\n", handle, dpl_TypeToString(type), TempBuff);
// }
// #if DEBUG_SPECIAL_CALLBACK >= 1
// printf("SPECIAL->%08x: %s, %s\n", handle, dpl_TypeToString(type), TempBuff);
// #endif
////
//// Scan all the argument start points into an array for easier processing.
//// also avoids non-reentrand problem with strtok
////
// argstring = TempBuff;
// for(argcount = 0; argcount<MAX_SPECIAL_ARGUMENTS; argcount++)
// {
// args[argcount] = strtok(argstring," ");
// if(!args[argcount])
// break;
// argstring = NULL;
// }
// args[MAX_SPECIAL_ARGUMENTS-1] = 0;
////
//// Decode the dpl_type the special was found in, all I know of are listed here
//// though some we may never see.
////
// switch(type)
// {
// case dpl_type_error:
// break;
// case dpl_type_scene:
// break;
// case dpl_type_zones:
// break;
// case dpl_type_view:
// break;
// case dpl_type_instance:
// break;
// case dpl_type_dcs:
// break;
// case dpl_type_light:
// break;
// case dpl_type_object:
// for(argptr = args;
// *argptr;
// argptr++)
// {
// if (strcmp(*argptr, "ADDITIVE_LODS") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Calling SetObjectAdditiveLODs on an object\n");
// #endif
// // REMOVE dpl_SetObjectAdditiveLODs to run with older renderers
// dpl_SetObjectAdditiveLODs ( (dpl_OBJECT *)handle);
// }
// }
// break;
// case dpl_type_lod:
// break;
// case dpl_type_geogroup:
// #if USE_TRACKER_STRUCTURE
// this_tracker = (dpl_tracker *)dpl_GetAppSpecific(handle);
// #endif
// for(argptr = args;
// *argptr;
// argptr++)
// {
// if (strncmp(*argptr, "dz_", 3) == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SPECIAL GEOGROUP Damage zone ");
// #endif
//
// #if USE_TRACKER_STRUCTURE
// if (this_tracker)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("marked\n");
// #endif
// strncpy(this_tracker->dz_name, *argptr, MAX_DZ_NAME_LENGTH);
// this_tracker->dz_name[MAX_DZ_NAME_LENGTH - 1] = 0;
// #endif
// //---------------------------------------------
// // lookup damage zone index in global namelist
// //---------------------------------------------
// if (Entity_Being_Created->damageZones)
// {
// Check_Pointer(Entity_Being_Created->damageZones);
//
// int
// damage_zone_index;
//
// damage_zone_index =
// Entity_Being_Created->GetDamageZoneIndex(*argptr);
//
// if (damage_zone_index != -1)
// {
// #if USE_TRACKER_STRUCTURE
// this_tracker->Damage_Zone_Number = damage_zone_index;
// #else
// if(dpl_GetAppSpecific(handle))
// Fail("AppSpecific already set\n");
// dpl_PutAppSpecific(handle, (void *)(damage_zone_index+1));
// #endif
// }
// else
// {
// DEBUG_STREAM << std::endl << "Damage zone '" <<
// *argptr << "' not in table." << std::endl;
// }
// }
// #if USE_TRACKER_STRUCTURE
// }
// else
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("NOT marked\n");
// #endif
// }
// #endif
// }
// else if(strcmp(*argptr,"PUNCH") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SPECIAL GEOGROUP Punchize\n");
// #endif
// dpl_Punchize((dpl_GEOGROUP *)handle);
// }
// else if(strcmp(*argptr,"DAMAGE") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SPECIAL GEOGROUP damagize\n");
// #endif
// if(*(++argptr))
// {
// int
// status;
// dpl_MATERIAL *damagize_material = dpl_LookupMaterial ( *argptr, dpl_lookup_normal, &status );
// if(damagize_material)
// {
// dpl_Damagize((dpl_GEOGROUP *)handle, damagize_material);
// }
// else
// {
// DEBUG_STREAM<<"SPECIAL GEOGROUP DAMAGE couldn't find material "<<*argptr<<"\n" << std::flush;
// Verify(damagize_material);
// }
// }
// else
// {
// DEBUG_STREAM<<"SPECIAL GEOGROUP DAMAGE no material name \n" << std::flush;
// Verify(*argptr);
// }
// }
// else if(strcmp(*argptr,"WIREFRAME") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SPECIAL GEOGROUP WIREFRAME\n");
// #endif
// dpl_SetGeogroupWireframe((dpl_GEOGROUP *)handle, True);
// }
// else if(strcmp(*argptr,"GEOMETRIZE") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SPECIAL GEOGROUP geometrize\n");
// #endif
// if(*(++argptr))
// {
// dpl_GEOMETRY
// *geometry_ptr;
// long
// geometrize_code;
// int
// geometry_id = 0;
// sscanf(
// *argptr,
// "%lx",
// &geometrize_code);
// do
// {
// geometry_ptr = dpl_GetGeogroupGeometry((dpl_GEOGROUP *)handle,geometry_id);
// if(geometry_ptr)
// {
// dpl_Geometrize(geometry_ptr,geometrize_code);
// }
// geometry_id++;
// } while(geometry_ptr);
// }
// else
// {
// DEBUG_STREAM<<"SPECIAL GEOGROUP GEOMETRIZE has no geometrize code\n" << std::flush;
// Verify(*argptr);
// }
// }
// else if(strcmp(*argptr,"DRAWLAST") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SPECIAL GEOGROUP DRAWLAST\n");
// #endif
// dpl_SetGeogroupDrawLast((dpl_GEOGROUP *)handle, True);
// dpl_FlushGeogroup((dpl_GEOGROUP *)handle);
// }
// else if(strcmp(*argptr,"BLINK") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("SPECIAL GEOGROUP BLINK is not supported\n");
// #endif
// }
// else
// {
// DEBUG_STREAM<<"SPECIAL GEOGROUP "<<*argptr<<" is not a geogroup modifier\n" << std::flush;
// }
// }
// break;
// case dpl_type_geometry:
// break;
// case dpl_type_material:
// for(argptr = args;
// *argptr;
// argptr++)
// {
// if(strcmp(*argptr,"IMMUNE") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SPECIAL MATERIAL IMMUNE\n");
// #endif
// if(*(++argptr))
// {
// immunity = atol(*argptr);
// dpl_SetMaterialFogImmunity ( (dpl_MATERIAL *)handle, immunity );
// dpl_FlushMaterial((dpl_MATERIAL *)handle);
// }
// else
// {
// DEBUG_STREAM<<"SPECIAL MATERIAL IMMUNE has no immune code\n" << std::flush;
// Verify(*argptr);
// }
// }
// else
// {
// DEBUG_STREAM<<"SPECIAL MATERIAL "<<*argptr<<" is not a material modifier\n" << std::flush;
// }
// }
// break;
// case dpl_type_texture:
// for(argptr = args;
// *argptr;
// argptr++)
// {
// if(strcmp(*argptr,"SCROLL") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SCROLL\n");
// #endif
// u0 = v0 = du = dv = 0.0;
// if(*(++argptr))
// u0 = atof(*argptr);
// else
// {
// DEBUG_STREAM<<"SPECIAL TECTURE SCROLL missing u0\n" << std::flush;
// Verify(*argptr);
// }
// if(*(++argptr))
// v0 = atof(*argptr);
// else
// {
// DEBUG_STREAM<<"SPECIAL TECTURE SCROLL missing v0\n" << std::flush;
// Verify(*argptr);
// }
// if(*(++argptr))
// du = atof(*argptr);
// else
// {
// DEBUG_STREAM<<"SPECIAL TECTURE SCROLL missing du\n" << std::flush;
// Verify(*argptr);
// }
// if(*(++argptr))
// dv = atof(*argptr);
// else
// {
// DEBUG_STREAM<<"SPECIAL TECTURE SCROLL missing dv\n" << std::flush;
// Verify(*argptr);
// }
// dpl_SetTextureScroll (
// (dpl_TEXTURE *)handle,
// u0,
// v0,
// du,
// dv);
// dpl_FlushTexture((dpl_TEXTURE *)handle);
// }
// else if(strcmp(*argptr,"FAKESIZE") == 0)
// {
// #if DEBUG_SPECIAL_CALLBACK >= 2
// Tell("Doing SPECIAL TEXURE FAKESIZE\n");
// #endif
// int
// size, offset;
// if(*(++argptr))
// size = atoi(*argptr);
// else
// {
// DEBUG_STREAM<<"SPECIAL TEXTURE FAKESIZE missing size\n" << std::flush;
// Verify(*argptr);
// }
// if(*(++argptr))
// offset = atoi(*argptr);
// else
// {
// DEBUG_STREAM<<"SPECIAL TEXTURE FAKESIZE missing offset\n" << std::flush;
// Verify(*argptr);
// }
// dpl_FakeTextureSize((dpl_TEXTURE *)handle,size,offset);
// dpl_FlushTexture((dpl_TEXTURE *)handle);
// }
// else
// {
// DEBUG_STREAM<<"SPECIAL TEXTURE "<<*argptr<<" is not a texture modifier\n" << std::flush;
// }
// }
// break;
// case dpl_type_texmap:
// break;
// case dpl_type_ramp:
// break;
// }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The TestCreateCallBack callback is called whenever a dpl node of the indicated
// type is created while loading a DPL graphics file.
/* dPL create/delete callback function type. */
//typedef void (dpl_CREATE_DELETE_CALLBACK)(dpl_TYPE type, void *handle);
static void
TestCreateCallBack(
dpl_TYPE type,
#if DEBUG_CREATE_CALLBACK>=1
void *handle
#else
void *
#endif
)
{
//
// get a pointer to the node structure (first part of most dpl types)
//
#if DEBUG_CREATE_CALLBACK >=1
printf("CREATE->%08x: %s\n", handle, dpl_TypeToString(type));
#endif
switch(type)
{
case dpl_type_error:
break;
case dpl_type_scene:
break;
case dpl_type_zones:
break;
case dpl_type_view:
break;
case dpl_type_instance:
break;
case dpl_type_dcs:
break;
case dpl_type_light:
break;
case dpl_type_object:
break;
case dpl_type_lod:
break;
case dpl_type_geogroup:
#if USE_TRACKER_STRUCTURE
{
dpl_tracker *this_tracker = new dpl_tracker;
this_tracker->This_Entity = Entity_Being_Created;
this_tracker->dz_name[0] = 0;
this_tracker->Damage_Zone_Number = -1; // no damage zone (default)
if(dpl_GetAppSpecific(handle))
DEBUG_STREAM<<"app_specific hook already set!\n" << std::flush;
dpl_PutAppSpecific(handle, this_tracker);
break;
}
#endif
case dpl_type_geometry:
break;
case dpl_type_material:
break;
case dpl_type_texture:
break;
case dpl_type_texmap:
break;
case dpl_type_ramp:
break;
}
}
//#############################################################################
// Code to setup and handle material substitutions.
//#############################################################################
//
NameList
*materialSubstitutionList = NULL;
const char *opMaterialName(const char *fileName, int opId)
{
hash_map<string, hash_map<int, string>>::const_iterator fileIter = gOpNames->find(string(fileName));
if (fileIter != gOpNames->end())
{
hash_map<int, string>::const_iterator matIter = (*fileIter).second.find(opId);
if (matIter != (*fileIter).second.end())
{
return (*matIter).second.c_str();
}
}
return NULL;
}
void loadTables()
{
gOpNames = new hash_map<string, hash_map<int, string>>();
gReplacementData = new hash_map<string, ReplacementMaterialData>();
FILE * opNames = fopen("VIDEO\\REPLACEMATS.tbl", "rb");
size_t numMats;
fread(&numMats, sizeof(size_t), 1, opNames);
for (int i = 0; i < (int)numMats; i++)
{
int opNum, fileNameLen, matNameLen;
fread(&opNum, sizeof(int), 1, opNames);
fread(&fileNameLen, sizeof(int), 1, opNames);
char *fileName = new char[fileNameLen];
fread(fileName, sizeof(char), fileNameLen, opNames);
fread(&matNameLen, sizeof(int), 1, opNames);
char *matName = new char[matNameLen];
fread(matName, sizeof(char), matNameLen, opNames);
hash_map<string, hash_map<int, string>>::const_iterator fileIter = gOpNames->find(string(fileName));
if (fileIter != gOpNames->end())
{
hash_map<int, string>::const_iterator matIter = (*fileIter).second.find(opNum);
if (matIter != (*fileIter).second.end())
{
(*gOpNames)[string(fileName)][opNum] = string(matName);
} else
{
(*gOpNames)[string(fileName)].insert(pair<int, string>(opNum, string(matName)));
}
} else
{
hash_map<int, string> fileMap;
fileMap.insert(pair<int, string>(opNum, string(matName)));
gOpNames->insert(pair<string, hash_map<int, string>>(string(fileName), fileMap));
}
delete [] fileName;
delete [] matName;
}
fclose(opNames);
FILE *replacementData = fopen("VIDEO\\MATREPLACETABLE.tbl", "rb");
fread(&numMats, sizeof(size_t), 1, replacementData);
for (int i = 0; i < (int)numMats; i++)
{
int matNameLen, texNameLen;
fread(&matNameLen, sizeof(int), 1, replacementData);
char *matName = new char[matNameLen];
fread(matName, sizeof(char), matNameLen, replacementData);
fread(&texNameLen, sizeof(int), 1, replacementData);
char *texName = new char[texNameLen];
fread(texName, sizeof(char), texNameLen, replacementData);
ReplacementMaterialData data;
data.texName = string(texName);
fread(&data, sizeof(float), 3, replacementData);
gReplacementData->insert(pair<string, ReplacementMaterialData>(string(matName), data));
delete [] matName;
delete [] texName;
}
fclose(replacementData);
}
/* dPL material name callback function type. */
//typedef char8 *(dpl_MATERIAL_NAME_CALLBACK)(char8 *mat_name);
char*
substituteMaterial(
char *source
)
{
static char
buffer[MATERIAL_NAME_BUFFER_LENGTH];
NameList::Entry
*entry;
const char
*search,
*replace,
*pc;
int
len;
//----------------------------------------------
// perform text substitution
// first match in the sub list gets substituted
// materialSubstitutionList is a pre-prepared global namelist.
//----------------------------------------------
if (materialSubstitutionList == NULL)
{
return source;
}
entry = materialSubstitutionList->GetFirstEntry();
while (entry)
{
search = entry->GetName();
if (search && *search && (pc = strstr((char*)source, search)) != NULL)
{
replace = entry->GetChar();
*buffer = '\0';
len = (char*)pc - source;
while (*replace == '<')
{
++replace;
--len;
}
if (len > 0)
{
strncat(buffer, (const char*)source, len);
}
pc += strlen(search);
len = strlen(replace);
while (len && *(replace+len-1) == '>')
{
--len;
if (*pc) { ++pc; }
}
if (len > 0)
{
strncat(buffer, replace, len);
}
Str_Cat(buffer, pc, MATERIAL_NAME_BUFFER_LENGTH);
delete [] source;
source = new char[strlen(buffer) + 1];
Str_Copy((char*)source, buffer, MATERIAL_NAME_BUFFER_LENGTH);
break;
}
entry = entry->GetNextEntry();
}
return source;
}
//#############################################################################
// dpl material-name callback registry (FUN_0049664c / DAT_00527f24).
//-----------------------------------------------------------------------------
// The 1995 dpl board applied the installed callback to every material name as
// a model loaded (dpl_LoadObject). The port analogue: the BGF loader
// (bgfload.cpp MaterialResolver::resolve) runs every "lib:material" name
// through dpl_ApplyMaterialNameCallback before resolving it, so the per-pilot
// paint substitutions (skin: -> _<color><serno>:, skin:lgo -> emblem<serno>:)
// installed by SetupMaterialSubstitutionList take effect on the mech model
// loads that happen between the Setup/TearDown bracket.
//#############################################################################
static char *(*gMaterialNameCallback)(char *source) = NULL;
void dpl_SetMaterialNameCallback(char *(*callback)(char *source))
{
gMaterialNameCallback = callback;
}
// C++-friendly wrapper honouring the callback's legacy protocol (the callback
// may delete[] the input and return a new[] buffer). Returns the (possibly
// substituted) name.
std::string dpl_ApplyMaterialNameCallback(const std::string &name)
{
if (gMaterialNameCallback == NULL)
return name;
char *buf = new char[name.size() + 1];
strcpy(buf, name.c_str());
buf = gMaterialNameCallback(buf);
std::string result(buf);
delete [] buf;
return result;
}
vector<MONITORINFO> DPLRenderer::MonitorsCreateAll(int &monitorCount)
{
monitorCount = gD3D->GetAdapterCount();
vector<MONITORINFO> allMonitors(monitorCount);
for(UINT i = 0; i < monitorCount; i++)
{
HMONITOR monitorHandle = gD3D->GetAdapterMonitor(i);
MONITORINFO info;
info.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(monitorHandle, &info);
allMonitors[i] = info;
}
return allMonitors;
}
void DPLRenderer::SetCoreRenderStates()
{
mDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_USEW);
float size = 1.0f;
mDevice->SetRenderState(D3DRS_POINTSIZE, *(DWORD*)(&size));
mDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
D3DXMATRIX view_matrix, proj_matrix;
D3DXMatrixIdentity(&view_matrix);
D3DXMatrixOrthoLH(&proj_matrix, x_size, y_size, 1.0f, 1000.0f);
mDevice->SetTransform(D3DTS_VIEW, &view_matrix);
mDevice->SetTransform(D3DTS_PROJECTION, &proj_matrix);
mDevice->SetRenderState(D3DRS_LIGHTING, false);
mDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TFACTOR);
mDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
mDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
mDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
mDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
mDevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
}
//-----------------------------------------------------------------------------
//--------------------------DPL video resource object--------------------------
//-----------------------------------------------------------------------------
//#############################################################################
//############################ L4VideoObject ############################
//#############################################################################
L4VideoObject::L4VideoObject(
const char *filename,
ResourceType resource_type,
Enumeration renderer_modes, // RendererModes
float blink_period,
float percent_time_on
)
{
Check_Pointer(filename);
// BT audit (task #20): count every loaded video object (map pieces + props +
// mech parts) so the runtime total can be compared against the map source's
// instance list. 1-per-10 logging keeps the volume sane.
{
static int s_vidObjCount = 0;
++s_vidObjCount;
if ((s_vidObjCount % 10) == 0 || s_vidObjCount < 5)
DEBUG_STREAM << "[vidobj] " << s_vidObjCount << " loaded (latest: "
<< filename << ")" << "\n" << std::flush;
}
Str_Copy(objectFilename, filename, sizeof(objectFilename));
//---------------------------------------------------------------
// pad objectFilename with nulls so all .res files are identical
//---------------------------------------------------------------
char
*p = objectFilename + strlen(filename) + 1,
*c = objectFilename + sizeof(objectFilename);
for (; p < c; ++p)
{ *p = '\0'; }
resourceType = resource_type;
rendererModes = renderer_modes;
blinkPeriod = blink_period;
percentTimeOn = percent_time_on;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4VideoObject::~L4VideoObject()
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
L4VideoObject::TestInstance() const
{
Verify( strlen(objectFilename) < sizeof(objectFilename) );
return True;
}
//#############################################################################
//######################## L4VideoObjectWrapper #########################
//#############################################################################
L4VideoObjectWrapper::L4VideoObjectWrapper(
L4VideoObject *video_object,
Logical delete_object
)
{
Check_Pointer(video_object); // do not use Check()
videoObject = video_object;
deleteObject = delete_object;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
L4VideoObjectWrapper::~L4VideoObjectWrapper()
{
if (deleteObject)
{
Unregister_Pointer(videoObject);
delete videoObject;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
L4VideoObjectWrapper::TestInstance() const
{
if (videoObject)
{
Check_Pointer(videoObject); // do not use Check()
}
return True;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// static
int L4VideoObjectWrapper::BuildVideoObjectChainFromResource(ChainOf<L4VideoObjectWrapper*> *video_chain, ResourceDescription *video_resource)
{
//do not Check(this) - static
Check(video_chain);
Check(video_resource);
const char *video_pointer;
int object_count, index;
long object_size;
L4VideoObject *video_object;
L4VideoObjectWrapper *video_wrapper;
//----------------------------------------------------
// convert video resource into chain of video objects
//----------------------------------------------------
video_pointer = (char *)video_resource->resourceAddress;
Check_Pointer(video_pointer);
object_count = *((int *)video_pointer);
video_pointer += sizeof(int);
object_size = sizeof(L4VideoObject);
Verify(video_resource->resourceSize == sizeof(int) + object_count * object_size);
//Tell("video chain: ("<<object_count<<")");
for (index = 0; index < object_count; ++index)
{
video_object = (L4VideoObject *)video_pointer;
//do not register (done through resource)
video_pointer += object_size;
//Tell(" '"<<video_object->GetObjectFilename()<<"' 0x"<<std::hex<<video_object->GetRendererModes());
video_wrapper = new L4VideoObjectWrapper(video_object, False);
Register_Object(video_wrapper);
video_chain->Add(video_wrapper);
}
//Tell(std::dec<<"\n");
return object_count;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// static
void
L4VideoObjectWrapper::DeleteVideoObjectChain(
ChainOf<L4VideoObjectWrapper*> *video_chain
)
{
//do not Check(this) - static
Check(video_chain);
ChainIteratorOf<L4VideoObjectWrapper*>
video_iterator(video_chain);
L4VideoObjectWrapper
*video_wrapper;
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
{
Check(video_wrapper);
Unregister_Object(video_wrapper);
delete video_wrapper;
}
return;
}
void DPLRenderer::FindBestAdapterIndices(bool isWindowed)
{
bool spanDisable = false;
bool *spanDisablePtr = NULL;
//Get all available monitor indices & stats
int monitorCount = 0;
std::vector<MONITORINFO> monitors = this->MonitorsCreateAll(monitorCount);
int monitorReserved = 0x10000000;
//Test for primary gauge override
char* gaugeAdapterString = getenv("PRIMGAUGE");
if (gaugeAdapterString != NULL)
{
mPrimaryIndex = new int;
*mPrimaryIndex = atoi(gaugeAdapterString);
DEBUG_STREAM << "Primary gauge override- adapter " << *mPrimaryIndex << std::endl;
}
//Test for secondary gauge override
gaugeAdapterString = getenv("SECGAUGE");
if (gaugeAdapterString != NULL)
{
mSecondaryIndex = new int;
*mSecondaryIndex = atoi(gaugeAdapterString);
DEBUG_STREAM << "Secondary gauge override- adapter " << *mSecondaryIndex << std::endl;
}
//Only do gauge overrides if we're using gauges during this execution
if (!Application::DoSuppressGauges())
{
//Test for gauge1 override
gaugeAdapterString = getenv("MFDGAUGE");
if (gaugeAdapterString != NULL)
{
mAux1Index = new int;
*mAux1Index = atoi(gaugeAdapterString);
DEBUG_STREAM << "MFD Gauge Override- adapter " << *mAux1Index << std::endl;
}
//Test for span disable override
spanDisablePtr = NULL;
gaugeAdapterString = getenv("SPANDISABLE");
if (gaugeAdapterString != NULL)
{
spanDisable = atoi(gaugeAdapterString);
spanDisablePtr = &spanDisable;
DEBUG_STREAM << "Spanning Override: Span ";
if (*spanDisablePtr)
{
DEBUG_STREAM << "Disabled";
} else
{
DEBUG_STREAM << "Enabled";
}
DEBUG_STREAM << std::endl;
}
if (spanDisablePtr == NULL || *spanDisablePtr)
{
//Test for gauge2 override
gaugeAdapterString = getenv("MFDGAUGE2");
if (gaugeAdapterString != NULL)
{
mAux2Index = new int;
*mAux2Index = atoi(gaugeAdapterString);
DEBUG_STREAM << "MFD Gauge #2 Override- adapter " << *mAux2Index << std::endl;
}
}
//Disable spanning if they overrode the 2nd gauge
if (spanDisablePtr == NULL && mAux2Index != NULL)
{
DEBUG_STREAM << "MFD Gauge #2 was overridden... forcing spanning disabled." << std::endl;
spanDisable = true;
spanDisablePtr = &spanDisable;
}
}
//Remove all monitors explicitly assigned somewhere from the list of available monitors
if (mPrimaryIndex != NULL && (*mPrimaryIndex) >= 0 && (*mPrimaryIndex) < monitorCount)
{
monitors[*mPrimaryIndex].dwFlags |= monitorReserved;
}
if (mSecondaryIndex != NULL && (*mSecondaryIndex) >= 0 && (*mSecondaryIndex) < monitorCount)
{
monitors[*mSecondaryIndex].dwFlags |= monitorReserved;
}
if (mAux1Index != NULL && (*mAux1Index) >= 0 && (*mAux1Index) < monitorCount)
{
monitors[*mAux1Index].dwFlags |= monitorReserved;
}
if (mAux2Index != NULL && (*mAux2Index) >= 0 && (*mAux2Index) < monitorCount)
{
monitors[*mAux2Index].dwFlags |= monitorReserved;
}
if (mPrimaryIndex == NULL)
{
DEBUG_STREAM << "Trying to find the monitor marked as active in Windows..." << std::endl;
//Set up the monitor so marked as the primary (unless none are marked)
for (int i = 0; i < monitorCount; i++)
{
int flags = monitors[i].dwFlags;
if ((flags & (MONITORINFOF_PRIMARY | monitorReserved)) == MONITORINFOF_PRIMARY)
{
DEBUG_STREAM << "Monitor " << i << " was set as active in Windows... setting it as the primary monitor." << endl;
mPrimaryIndex = new int;
*mPrimaryIndex = i;
monitors[i].dwFlags |= monitorReserved;
break;
}
}
}
//Do gauges next, since they're the easiest to pick out
if (!Application::DoSuppressGauges() && !isWindowed && monitorCount > 2)
{
DEBUG_STREAM << "Gauges are on, we are not in windowed mode, and we have " << monitorCount << " available monitors, so will try to find MFDs..." << std::endl;
if (mAux1Index == NULL && (spanDisablePtr == NULL || !(*spanDisablePtr)))
{
DEBUG_STREAM << "Don't have a MFD #1 monitor yet, and spanning is not explicitly disabled, so will try to find a really wide monitor for both MFDs..." << std::endl;
//try to find a really wide monitor to use as gauge1
for (int i = 0; i < monitorCount; i++)
{
if ((monitors[i].dwFlags & monitorReserved) == 0)
{
RECT size = monitors[i].rcMonitor;
float aspect = ( (double)(size.right - size.left) / (double)(size.bottom - size.top));
if (aspect > 2.5f)
{
//Really wide! Probably a spanning monitor
DEBUG_STREAM << "Monitor " << i << " is really wide, so will try to use it as the MFDs." << std::endl;
mAux1Index = new int;
*mAux1Index = i;
monitors[i].dwFlags |= monitorReserved;
if (spanDisablePtr == NULL)
{
DEBUG_STREAM << "Explicitly setting spanning to enabled..." << std::endl;
spanDisable = FALSE;
spanDisablePtr = &(spanDisable);
}
break;
}
}
}
}
int possibleAux2Index = -1;
if (mAux1Index == NULL && (spanDisablePtr == NULL || *spanDisablePtr))
{
DEBUG_STREAM << "MFDs not found yet, and spanning isn't explicitly enabled. Will try to find two same-size monitors to use as MFDs..." << std::endl;
map<pair<int,int>,vector<int>> sameSizeMonitors;
//Group the unclaimed monitors by resolution
for (int i = 0; i < monitorCount; i++)
{
if ((monitors[i].dwFlags & monitorReserved) == 0)
{
int width = monitors[i].rcMonitor.right - monitors[i].rcMonitor.left;
int height = monitors[i].rcMonitor.bottom - monitors[i].rcMonitor.top;
pair<int, int> resolution(width, height);
if (sameSizeMonitors.find(resolution) == sameSizeMonitors.end())
{
sameSizeMonitors[resolution] = vector<int>();
}
sameSizeMonitors[resolution].insert(sameSizeMonitors[resolution].begin(), i);
}
}
//Retrieve the bucket of monitors with at least two monitors and the smallest resolution (y res counts more than x res)
vector<int> bestMonitors;
int smallestYRes = -1;
int bestXRes = -1;
for (map<pair<int,int>,vector<int>>::iterator it = sameSizeMonitors.begin(); it != sameSizeMonitors.end(); it++)
{
if ((*it).second.size() >= 2)
{
int yRes = (*it).first.second;
int xRes = (*it).first.first;
if (smallestYRes >= 0)
{
DEBUG_STREAM << "Multiple sets of identically-sized monitors are available. Will use smallest set..." << std::endl;
}
if (smallestYRes < 0 || yRes < smallestYRes || (yRes == smallestYRes && xRes < bestXRes))
{
smallestYRes = yRes;
bestXRes = xRes;
bestMonitors = (*it).second;
}
}
}
int aux1Candidate = -1;
int aux2Candidate = -1;
int aux1Right = 0;
int aux2Right = 0;
if (bestMonitors.size() > 2)
{
DEBUG_STREAM << "More than 2 monitors in the set of smallest, identically-sized monitors... Will use the furthest right as right MFD, one just to the left of that as left MFD..." << std::endl;
}
//The second aux screen is the one furthest to the right
//the first aux screen is the one just to the left of that one
for (vector<int>::iterator it = bestMonitors.begin(); it != bestMonitors.end(); it++)
{
int i = (*it);
if ((monitors[i].dwFlags & monitorReserved) == 0)
{
int right = monitors[i].rcMonitor.right;
if (aux2Candidate < 0 || aux2Right < right)
{
aux1Candidate = aux2Candidate;
aux1Right = aux2Right;
aux2Candidate = i;
aux2Right = right;
} else if (aux1Candidate < 0 || aux1Right < right)
{
aux1Candidate = i;
aux1Right = right;
}
}
}
if (aux1Candidate >= 0 && aux2Candidate >= 0)
{
DEBUG_STREAM << "Got two decent same-size monitors, will use as MFDs... Left is " << aux1Candidate << " and right is " << aux2Candidate << "." << std::endl;
mAux1Index = new int;
*mAux1Index = aux1Candidate;
monitors[aux1Candidate].dwFlags |= monitorReserved;
mAux2Index = new int;
*mAux2Index = aux2Candidate;
monitors[aux2Candidate].dwFlags |= monitorReserved;
spanDisable = true;
spanDisablePtr = &spanDisable;
} else
{
DEBUG_STREAM << "Could not find two identical monitors to use for MFDs." << std::endl;
}
}
if (mAux2Index == NULL && mAux1Index != NULL && (spanDisablePtr == NULL || *spanDisablePtr))
{
DEBUG_STREAM << "We have a left MFD and no right MFD, and spanning is not explicitly enabled. Will try to find identically-sized right MFD." << std::endl;
//find a monitor that is identical to the first gauge monitor
//if multiple, use the furthest to the right
int bestMonitorIndex = -1;
int bestMonitorRight = 0;
int aux1Width = (monitors[*mAux1Index].rcMonitor.right - monitors[*mAux1Index].rcMonitor.left);
int aux1Height = (monitors[*mAux1Index].rcMonitor.bottom - monitors[*mAux1Index].rcMonitor.top);
for (int i = 0; i < monitorCount; i++)
{
if ((monitors[i].dwFlags & monitorReserved) == 0)
{
int monitorWidth = (monitors[i].rcMonitor.right - monitors[i].rcMonitor.left);
int monitorHeight = (monitors[i].rcMonitor.bottom - monitors[i].rcMonitor.top);
if (monitorWidth == aux1Width && monitorHeight == aux1Height)
{
if (bestMonitorIndex >= 0)
{
DEBUG_STREAM << "Found more than one monitor identically sized to the left MFD. Will use furthest-right monitor." << std::endl;
}
if (bestMonitorIndex < 0 || bestMonitorRight < monitors[i].rcMonitor.right)
{
bestMonitorIndex = i;
bestMonitorRight = monitors[i].rcMonitor.right;
}
}
}
}
if (bestMonitorIndex >= 0)
{
DEBUG_STREAM << "Using monitor " << bestMonitorIndex << "as right MFD monitor." << std::endl;
mAux2Index = new int;
*mAux2Index = bestMonitorIndex;
monitors[bestMonitorIndex].dwFlags |= monitorReserved;
} else
{
DEBUG_STREAM << "Could not find decent monitor for right MFD." << std::endl;
}
}
} else
{
DEBUG_STREAM << "Either MFDs are explicitly disabled, we're running in windowed mode, or we don't have enough monitors attached to this machine. MFD monitors will not be detected." << std::endl;
}
if (mPrimaryIndex == NULL)
{
DEBUG_STREAM << "Still no appropriate primary monitor- will find tallest monitor to use." << std::endl;
//Pick the monitor with the highest y resolution (leftmost if there's a tie)
int bestMonitorIndex = -1;
int bestMonitorHeight = 0;
int bestMonitorX = 0;
for (int i = 0; i < monitorCount; i++)
{
if ((monitors[i].dwFlags & monitorReserved) == 0)
{
int height = (monitors[i].rcMonitor.bottom - monitors[i].rcMonitor.top);
if (bestMonitorIndex < 0 || height > bestMonitorHeight || (height == bestMonitorHeight && monitors[i].rcMonitor.left < bestMonitorX))
{
bestMonitorIndex = i;
bestMonitorHeight = height;
bestMonitorX = monitors[i].rcMonitor.left;
}
}
}
if (bestMonitorIndex >= 0)
{
DEBUG_STREAM << "Using monitor " << bestMonitorIndex << " as primary monitor." << std::endl;
mPrimaryIndex = new int;
*mPrimaryIndex = bestMonitorIndex;
monitors[bestMonitorIndex].dwFlags |= monitorReserved;
} else
{
DEBUG_STREAM << "Could not find decent primary monitor. Likely no unclaimed monitors." << std::endl;
}
}
if (mPrimaryIndex != NULL && isWindowed)
{
DEBUG_STREAM << "Game is currently running windowed mode- will set all remaining undefined monitors to be the same as the primary monitor." << std::endl;
if (mSecondaryIndex == NULL)
{
mSecondaryIndex = new int;
*mSecondaryIndex = *mPrimaryIndex;
}
if (mAux1Index == NULL && mAux2Index == NULL)
{
mAux1Index = new int;
*mAux1Index = *mPrimaryIndex;
mAux2Index = new int;
*mAux2Index = *mPrimaryIndex;
}
} else if (!isWindowed && mSecondaryIndex == NULL)
{
DEBUG_STREAM << "Detecting secondary monitor- will attempt to use furthest-left remaining monitor." << std::endl;
//Pick the leftmost remaining monitor
int leftmostIndex = -1;
int leftmostX = 0;
for (int i = 0; i < monitorCount; i++)
{
if ((monitors[i].dwFlags & monitorReserved) == 0)
{
if (leftmostIndex < 0 || monitors[i].rcMonitor.left < leftmostX)
{
leftmostIndex = i;
leftmostX = monitors[i].rcMonitor.left;
}
}
}
if (leftmostIndex >= 0)
{
DEBUG_STREAM << "Using monitor " << leftmostIndex << " as secondary monitor." << std::endl;
mSecondaryIndex = new int;
*mSecondaryIndex = leftmostIndex;
} else
{
DEBUG_STREAM << "Could not find appropriate secondary monitor. Likely no unclaimed monitors remain." << std::endl;
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constructor for DPLRenderer
//
DPLRenderer::DPLRenderer(
HWND hWnd,
unsigned int screenWidth,
unsigned int screenHeight,
bool fullscreen,
InterestType interest_type,
InterestDepth depth_calibration
):
VideoRenderer(
1.0f,
1.0f,
RendererPriority::DefaultRendererPriority,
interest_type,
depth_calibration
),
projectile_list(NULL),
dplObjectCacheSocket(NULL, False),
dplJointToDCSTranslatorSocket(NULL,False),
dplRenderableSocket(NULL),
mRenderables(NULL),
x_size(screenWidth),
y_size(screenHeight),
mReticle(NULL),
mCamShipHUD(NULL),
mStaticObjectsHead(NULL),
mStaticObjectsCount(0),
mPrimaryIndex(NULL),
mSecondaryIndex(NULL),
mAux1Index(NULL),
mAux2Index(NULL)
{
__int64 frequency = HiResCounterFreq();
#ifdef LOGFRAMERATE
FRAMERATE_LOG = fopen("framerate.log", "wb");
fwrite(&frequency, sizeof(__int64), 1, FRAMERATE_LOG);
#endif
mCamera = NULL;
loadTables();
// clear out our render lists
memset(mRenderLists, 0, sizeof(mRenderLists));
memset(mNameTextures, 0, sizeof(mNameTextures));
memset(mOrdinalTextures, 0, sizeof(mOrdinalTextures));
D3DXCreateMatrixStack(0, &m_MatrixStack);
m_MatrixStack->LoadIdentity();
gD3D = Direct3DCreate9(D3D_SDK_VERSION);
if (!gD3D)
{
DEBUG_STREAM<<"Couldn't create Direct3D interface!"<<std::endl<<std::flush << std::flush;
PostQuitMessage(1);
}
this->FindBestAdapterIndices(!fullscreen);
memset(&mPresentParams, 0, sizeof(D3DPRESENT_PARAMETERS));
mPresentParams.BackBufferCount = 1;
mPresentParams.MultiSampleType = (D3DMULTISAMPLE_TYPE)atol(getenv("MULTISAMPLE"));
if (mPresentParams.MultiSampleType > 0)
{
gD3D->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, D3DFMT_X8R8G8B8, !fullscreen, mPresentParams.MultiSampleType, &mPresentParams.MultiSampleQuality);
mPresentParams.MultiSampleQuality--;
}
mPresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
//
// COCKPIT LETTERBOX (2026-07-26): the fixed canvas is fitted into the client
// at Present time via a destination rect, which DISCARD forbids -- COPY is
// the swap effect that takes one. Windowed + surround only, and only with
// multisampling off (COPY cannot multisample). Full contract: l4vb16.h.
//
// btl4main sets the INTENT (it has to: the first WM_SIZE beats the device
// here); this is where it becomes real, or gets withdrawn.
{
extern int gBTGaugeCockpit;
extern int gBTCockpitLetterbox;
if (!fullscreen && gBTGaugeCockpit && gBTCockpitLetterbox &&
mPresentParams.MultiSampleType == D3DMULTISAMPLE_NONE)
mPresentParams.SwapEffect = D3DSWAPEFFECT_COPY;
else
gBTCockpitLetterbox = 0; // withdrawn -- the canvas stretches
// only meaningful when there IS a cockpit canvas to fit
if (gBTGaugeCockpit)
DEBUG_STREAM << "[cockpit] letterbox " << (gBTCockpitLetterbox ? "ON" : "off")
<< " (swap effect "
<< (mPresentParams.SwapEffect == D3DSWAPEFFECT_COPY ? "COPY" : "DISCARD")
<< (mPresentParams.MultiSampleType != D3DMULTISAMPLE_NONE
? "; multisampling on -- COPY unavailable, canvas stretches to the client"
: "")
<< ")\n" << std::flush;
}
mPresentParams.hDeviceWindow = hWnd;
mPresentParams.Flags = 0;
mPresentParams.FullScreen_RefreshRateInHz = (fullscreen)?60:D3DPRESENT_RATE_DEFAULT;
mPresentParams.PresentationInterval = D3DPRESENT_RATE_DEFAULT;
mPresentParams.BackBufferFormat = D3DFMT_X8R8G8B8;
mPresentParams.EnableAutoDepthStencil = TRUE;
mPresentParams.AutoDepthStencilFormat = D3DFMT_D24S8; // S8: the cockpit punch stencil-cut (task #55)
mPresentParams.Windowed = !fullscreen;
if (fullscreen)
{
mPresentParams.BackBufferWidth = screenWidth;
mPresentParams.BackBufferHeight = screenHeight;
}
else
{
//
// PIN THE WINDOWED BACKBUFFER (Gitea #56, and #50 -- the same bug).
//
// These were left at 0 for the windowed case, which tells D3D9 to derive the
// backbuffer from the device window's client area at CreateDevice time. Fine
// at startup -- but the device-lost recovery path saves and restores
// mPresentParams around Reset(), so it faithfully preserves the ZEROS and
// Reset re-derives the backbuffer from whatever the client area is at THAT
// moment.
//
// Field consequence: a player resizes or maximises the window (harmless by
// itself -- the cockpit layout lives in backbuffer space and the hit-test maps
// client -> canvas), then the device is lost (alt-tab, Steam overlay, monitor
// sleep, UAC prompt, RDP, driver hiccup). On recovery the backbuffer silently
// becomes the NEW client size while gBTCockpitCanvasW/H still describe the
// startup canvas -- so panels are drawn for one geometry and clicks are mapped
// in another. Buttons drift off their artwork, and at a big enough mismatch
// nothing is clickable at all (#50, "none of my MFDs clickable" while other
// players' worked). It needs BOTH steps in that order, which is why the
// operator -- who never resizes the cockpit window -- has never seen it.
//
// Recording the client rect here yields exactly what D3D would have derived,
// so startup behaviour is unchanged, but the save/restore now preserves a REAL
// size and Reset can never silently pick a different one. (Do NOT use
// screenWidth/screenHeight: those are the app's -res values, default 800x600,
// while the cockpit-surround window is sized independently -- pinning to them
// would shrink the backbuffer and break the composite.)
//
RECT rc;
if (hWnd != NULL && GetClientRect(hWnd, &rc)
&& rc.right > rc.left && rc.bottom > rc.top)
{
mPresentParams.BackBufferWidth = (UINT)(rc.right - rc.left);
mPresentParams.BackBufferHeight = (UINT)(rc.bottom - rc.top);
}
}
HRESULT hr;
//DEBUG_STREAM<<"**************************"<<std::endl<<"**************************"<<std::endl;
//for (UINT adapter = 0; adapter<=3; adapter++)
//{
// DEBUG_STREAM<<"\tAdapter "<<adapter<<": ";
// D3DFORMAT format = (adapter == 0 ? D3DFMT_X8R8G8B8 : D3DFMT_R5G6B5);
// UINT modes = gD3D->GetAdapterModeCount(adapter, format);
// DEBUG_STREAM<<modes<<" display modes"<<std::endl;
// for (UINT mode=0; mode<modes; mode++)
// {
// D3DDISPLAYMODE displayMode;
// hr = gD3D->EnumAdapterModes(adapter, format, mode, &displayMode);
// if (FAILED(hr))
// {
// DEBUG_STREAM<<"\t\tFailed to retrieve display mode "<<mode<<"."<<std::endl;
// continue;
// }
// DEBUG_STREAM<<"\t\t"<<displayMode.Format<<": "<<displayMode.Width<<" x "<<displayMode.Height<<" @ "<<displayMode.RefreshRate<<" Hz"<<std::endl;
// }
// DEBUG_STREAM<<std::endl;
//}
//DEBUG_STREAM<<"**************************"<<std::endl<<"**************************"<<std::endl<<std::flush;
if (mPrimaryIndex == NULL)
{
DEBUG_STREAM<<"Unable to locate a suitable primary device index."<<std::endl<<std::flush;
PostQuitMessage(1);
return;
}
// PORT (perf root cause): the device was created SOFTWARE_VERTEXPROCESSING --
// every vertex of every draw transformed + LIT on the CPU (the ~10fps baseline
// and the multi-hundred-ms "hitch" frames whenever the dense map side entered
// the view; the GPU sat idle, Present <1ms). Try HARDWARE first (any modern
// or pod GPU has fixed-function T&L); fall back to software if it fails, as
// the original error message always intended.
// DEVICE-CREATION RETRY (2026-07-22): a transient failure is REAL -- a
// just-exited instance can still hold the adapter for a moment (caught
// live: a round-2 pod died here while round-1 tore down). The old code
// had TWO bugs: no retry, and on double failure PostQuitMessage(1) did
// NOT stop execution -- the NULL mDevice deref right below was the
// actual crash (the intermittent "exit 139" / silent join death).
// Retry HW->SW with a short backoff; on true exhaustion exit CLEANLY
// with a visible message.
{
unsigned long device_deadline = GetTickCount() + 20000UL;
for (;;)
{
hr = gD3D->CreateDevice(*mPrimaryIndex, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING, &mPresentParams, &mDevice);
if (SUCCEEDED(hr) && mDevice != NULL)
break;
DEBUG_STREAM << "Couldn't create HARDWARE_VERTEXPROCESSING device (hr=0x"
<< std::hex << (unsigned)hr << std::dec << ")." << std::endl << std::flush;
hr = gD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &mPresentParams, &mDevice);
if (SUCCEEDED(hr) && mDevice != NULL)
break;
DEBUG_STREAM << "SOFTWARE_VERTEXPROCESSING failed too (hr=0x"
<< std::hex << (unsigned)hr << std::dec
<< ") -- retrying in 500ms" << std::endl << std::flush;
if (GetTickCount() >= device_deadline)
{
MessageBoxA(NULL,
"Could not create a Direct3D device after 20 seconds.\n\n"
"Another program may be holding the graphics adapter.\n"
"Close other 3D applications and try again.",
"BattleTech -- graphics init failed", MB_OK | MB_ICONERROR);
ExitProcess(1); // CLEAN exit, not the NULL-deref crash
}
Sleep(500);
}
}
mDevice->Clear(0, NULL, D3DCLEAR_TARGET, 0xFF000000, 0.0f, 0);
mDevice->Present(NULL, NULL, NULL, NULL);
ParticleEngine::Initialize(mDevice);
SetCoreRenderStates();
//STUBBED: DPL RB 1/14/07
char
Eye_Type[50],
*Eye_Arg, // Controls generation of the eye point
*DPL_Arg; // Points to DPLARG environment pointer once we get it
int
psfx_number;
// int
// i; // Temporary loop counter
float
eye_x,
eye_y,
eye_z,
eye_x_rot,
eye_y_rot,
eye_z_rot;
//
// Initialize a bunch of the variables to their starting values
//
eyeRelative = False;
completeCycleNeeded = False;
lastAppState = 0;
fogUpdating = True;
fogRed = 0.0f;
fogBlue = 0.0f;
fogGreen = 0.0f;
fogNear = 0.0f;
fogFar = 0.0f;
currentFogFar = 0.0f;
currentFogNear = 0.0f;
searchLightFogRed = 0.0f;
searchLightFogGreen = 0.0f;
searchLightFogBlue = 0.0f;
searchLightFogNear = 0.0f;
searchLightFogFar = 0.0f;
noSearchLightFogRed = 0.0f;
noSearchLightFogGreen = 0.0f;
noSearchLightFogBlue = 0.0f;
noSearchLightFogNear = 0.0f;
noSearchLightFogFar = 0.0f;
clipNear = 0.0f;
clipFar = 0.0f;
mEnvAmbient = 0x00404040; // bring-up floor until the env ambient loads
mCloudRed = mCloudGreen = mCloudBlue = 1.0f; // no cloud tint until env sets it
mCloudEmitRed = mCloudEmitGreen = mCloudEmitBlue = 0.0f;
backgroundRed = 0.0f;
backgroundGreen = 0.0f;
backgroundBlue = 0.0f;
viewAngle = 30.0f;
dplMainView = NULL;
dplDeathZone = NULL;
dplMainZone = NULL;
vehicleReticle = NULL;
dplHitInstance = NULL;
dplHitDCS = NULL;
dplHitGeoGroup = NULL;
dplHitGeometry = NULL;
delayedDCSCount = 0;
myUniqueID = 0;
sceneLightDCS = NULL;
sceneLight = NULL;
sceneLightCount = 0;
worldToEyeMatrix = LinearMatrix::Identity; // the current world to eye transform for our linked entity
currentFrameTime = Now(); // the time at the start of renderable execution
Verify(!DPLHeap);
// DPLHeap = new UserHeap("DPL Heap", 2000000);
Register_Object(DPLHeap);
//
// Clear the myPSFXDescriptons array to all zeros so we can detect attempts
// to use uninitialized items.
//
memset(myPSFXDescriptons, 0, sizeof(myPSFXDescriptons));
//
// These guys will come out of a world environment variable eventually
//
eye_x = 0.0f;
eye_y = 10.0f;
eye_z = 0.0f;
eye_x_rot = 0.0f;
eye_y_rot = 0.0f;
eye_z_rot = 0.0f;
//
// Get pointers to environmentals containing DPL startup arguments and
// eye positioning information. If L4EYES is present set the renderer
// variable that controls the hooking up of the eye position.
//
Eye_Type[0] = 0;
DPL_Arg = getenv("DPLARG");
Eye_Arg = getenv("L4EYES");
if(Eye_Arg)
{
sscanf(
Eye_Arg,
"%f %f %f %f %f %f %s",
&eye_x,
&eye_y,
&eye_z,
&eye_x_rot,
&eye_y_rot,
&eye_z_rot,
Eye_Type);
printf("%f, %f, %f %f, %f, %f\n",
eye_x,
eye_y,
eye_z,
eye_x_rot,
eye_y_rot,
eye_z_rot);
Disconnected_Eye = True; // tells vidrend:: to request outside view of linked entity
if(*Eye_Type == 'r')
{
DEBUG_STREAM<<"DPLRenderer::DPLRenderer Eye will be offset relative to vehicle\n" << std::flush;
eyeRelative = True;
}
}
//
// If the argument was empty, we can't render !!! should exit sensiblly
//
if(!DPL_Arg)
{
DEBUG_STREAM << "DPLARG must be set for the Division card to come up\n" << std::flush;
Verify(DPL_Arg);
}
//
// If we're still here, try to interpret and setup the screen resolution
// requested by DPLARG. The screen_resolution function is Phil's routine
// in startdpl.
//
// if (screen_resolution ( DPL_Arg ) == 0)
// {
// DEBUG_STREAM << "DPLARG is bad, I couldn't understand video format\n" << std::flush;
// Verify(DPL_Arg);
// }
//
// Breakdown the DPLARGS string using expload_args from startdpl, then feed
// the results to the dpl_Init routine to get DPL up and running.
//
char *argv[32];
// int argc = explode_args ( argv, DPL_Arg, dpl_arg_sep );
// dpl_Init ( argc, argv );
//
// Setup the file paths for geometry and texture loading
//
// dpl_SetObjectFilePath ( ".\\video", "\\video", "..\\video" );
// dpl_SetTexmapFilePath ( ".\\video", "\\video", "..\\video" );
// dpl_SetMaterialFilePath ( ".\\video", "\\video", "..\\video" );
// dpl_SetObjectFilePath ( ".\\video", "", "" );
// std::cout<<dpl_GetObjectFilePath()<<"\n";
// dpl_SetTexmapFilePath ( ".\\video", "", "" );
// std::cout<<dpl_GetTexmapFilePath()<<"\n";
// dpl_SetMaterialFilePath ( ".\\video", "", "" );
// std::cout<<dpl_GetMaterialFilePath()<<"\n";
//
// Set the extensions and the DPL routines that will load them
//
// dpl_SetExtnObjectLoadFunc (".bgf", dpl_bgfRead);
// dpl_SetExtnTexmapLoadFunc (".vtx", dpl_vtxRead);
// dpl_SetExtnTexmapLoadFunc (".tga", dpl_tgaRead);
// dpl_SetExtnTexmapLoadFunc (".int", dpl_sgiRead);
// dpl_SetExtnTexmapLoadFunc (".inta", dpl_sgiRead);
// dpl_SetExtnTexmapLoadFunc (".rgb", dpl_sgiRead);
// dpl_SetExtnTexmapLoadFunc (".rgba", dpl_sgiRead);
//
// Setup the dpl callback routines for handling specials, object creation and
// material name munging.
//
// dpl_SetSpecialCallback(TestSpecialCallBack);
// dpl_SetCreateNodeCallback(TestCreateCallBack);
// dpl_SetMaterialNameCallback(TestMaterialCallBack);
//
// Create the one zone everything will live in !!!! more zones later for smart culling
//
// dplMainZone = dpl_NewZone();
Check_Pointer(dplMainZone);
// dplDeathZone = dpl_NewZone();
Check_Pointer(dplDeathZone);
//
// Create the view we will use for our eyepoint
//
// dplMainView = dpl_NewView();
Check_Pointer(dplMainView);
//
// This creates a DCS and links it to the view, I probably should just let the
// eye renderable do this but it's convenient since it can be used as the
// eye point for testing.
//
// dplTestEyeDCS = dpl_NewDCS();
Check_Pointer ( dplTestEyeDCS);
// dpl_RotateDCS ( dplTestEyeDCS, eye_z_rot, dpl_Z );
// dpl_RotateDCS ( dplTestEyeDCS, eye_x_rot, dpl_X );
// dpl_RotateDCS ( dplTestEyeDCS, eye_y_rot, dpl_Y );
// dpl_TranslateDCS ( dplTestEyeDCS, eye_x, eye_y, eye_z);
// dpl_SetDCSZone ( dplTestEyeDCS, dplMainZone );
// dpl_SetViewDCS ( dplMainView, dplTestEyeDCS);
//
// Do some setup work on the zone
//
// dpl_SetZoneAllViewsOn ( dplMainZone );
// dpl_SetZoneAllViewsOn ( dplDeathZone );
//
// Setup some defaults so we can safely render a blank screen now
//
//
// dpl_SetViewClipPlanes ( dplMainView, 1.0f, 2.0f );
// dpl_SetViewBackGround ( dplMainView, 0.0f, 0.0f, 0.0f);
// dpl_SetPipeWindow (0, 0, 0, x_size, y_size);
// dpl_SetViewProjection ( dplMainView, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f/(tan( 0.5235987666667f)));
// dpl_SetViewPipe ( dplMainView, 0);
// dpl_AddViewToScene ( dplMainView );
// dpl_SetViewFog (dplMainView, dpl_fog_type_pixel_lin, 0.0, 0.0, 0.0, 0.01, 0.05 );
//
// DPLReadEnvironment();
//
// dpl_FlushDCS ( dplTestEyeDCS);
// dpl_FlushView ( dplMainView);
// dpl_FlushZone ( dplMainZone);
// dpl_FlushZone ( dplDeathZone);
//
// Call DrawScene to force the system do draw a blank screen
//
// dpl_DrawScene();
//
// Start a clock used to keep track of the frame rate
//
StartSample = Now();
FrameCount = 0;
ResetStatistics();
statistics_started = False;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dpl_ZONE*
DPLRenderer::MakeNewZone()
{
//STUBBED: DPL RB 1/14/07
dpl_ZONE *temp_zone;
////
//// Make a new zone for use by someone constructing an object, switch it on
//// and std::flush it out to the card.
////
//temp_zone = dpl_NewZone();
//Check_Pointer(temp_zone);
//dpl_SetZoneAllViewsOn (temp_zone);
//dpl_FlushZone (temp_zone);
////
//// HACK !!! at this point we should put the zone into a chain so we can
//// find it to switch it on and off later.
////
return(temp_zone);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// AddDynamicRenderable puts a renderable into this renderer's dynamic execution
// socket so it will be run every frame.
//
void DPLRenderer::AddDynamicRenderable(Component *my_renderable)
{
Check(my_renderable);
Check(&dplRenderableSocket);
dplRenderableSocket.Add(my_renderable);
}
void DPLRenderer::AddToPassList(d3d_OBJECT *object, int pass)
{
if (pass >= PASS_TOTAL_COUNT)
return;
for (d3d_OBJECT *iter = mRenderLists[pass]; iter; iter = iter->GetNext(pass))
if ((void*)iter == (void*)object)
return;
object->SetPrevious(NULL, pass);
object->SetNext(mRenderLists[pass], pass);
if (mRenderLists[pass] != NULL)
mRenderLists[pass]->SetPrevious(object, pass);
mRenderLists[pass] = object;
}
hyper DPLRenderer::HashAdd(hyper input, char data)
{
const hyper multiplier = 37;
return (input * multiplier) + data;
}
hyper DPLRenderer::HashDrawOp(L4DRAWOP *op)
{
char diffR = (char)(op->material.Diffuse.r * 255);
char diffG = (char)(op->material.Diffuse.g * 255);
char diffB = (char)(op->material.Diffuse.b * 255);
char diffA = (char)(op->material.Diffuse.a * 255);
char ambA = (char)(op->material.Ambient.a * 255);
char ambR = (char)(op->material.Ambient.r * 255);
char ambG = (char)(op->material.Ambient.g * 255);
char ambB = (char)(op->material.Ambient.b * 255);
char spcA = (char)(op->material.Specular.a * 255);
char spcR = (char)(op->material.Specular.r * 255);
char spcG = (char)(op->material.Specular.g * 255);
char spcB = (char)(op->material.Specular.b * 255);
char spcP = (char)(op->material.Power * 255);
char emiA = (char)(op->material.Emissive.a * 255);
char emiR = (char)(op->material.Emissive.r * 255);
char emiG = (char)(op->material.Emissive.g * 255);
char emiB = (char)(op->material.Emissive.b * 255);
const hyper startValue = 37;
hyper hash = startValue;
hash = HashAdd(hash, diffA);
hash = HashAdd(hash, diffR);
hash = HashAdd(hash, diffG);
hash = HashAdd(hash, diffB);
hash = HashAdd(hash, ambA);
hash = HashAdd(hash, ambR);
hash = HashAdd(hash, ambG);
hash = HashAdd(hash, ambB);
hash = HashAdd(hash, spcA);
hash = HashAdd(hash, spcR);
hash = HashAdd(hash, spcG);
hash = HashAdd(hash, spcB);
hash = HashAdd(hash, spcP);
hash = HashAdd(hash, emiA);
hash = HashAdd(hash, emiR);
hash = HashAdd(hash, emiG);
hash = HashAdd(hash, emiB);
hash = HashAdd(hash, (char)op->drawAsDecal);
hash = HashAdd(hash, (char)op->alphaTest);
hash = HashAdd(hash, (char)op->drawAsSky);
char *pointerPointer = (char*)(&(op->texture.texture));
for (int i = 0; i < 4; i++)
{
hash = HashAdd(hash, pointerPointer[i]);
}
return hash;
}
void DPLRenderer::AddStaticObject(d3d_OBJECT *object)
{
object->SetPrevious(NULL, -1);
object->SetNext(mStaticObjectsHead, -1);
if (mStaticObjectsHead != NULL)
mStaticObjectsHead->SetPrevious(object, -1);
mStaticObjectsHead = object;
mStaticObjectsCount++;
}
void DPLRenderer::RecurseStaticObject(HierarchicalDrawComponent *obj)
{
if (obj->IsStatic())
{
if (obj->GetDrawObj() != NULL && obj->GetDrawObj()->GetMesh() != NULL)
{
// ADDITIVE_LODS objects (decoded IG-board semantics, see bgfload.cpp
// TAG_OBJECT) carry per-op distance bands [0..OutDist) that DrawMesh
// gates each frame -- merging them into a consolidated static would
// draw every LOD of the composite simultaneously forever. Leave any
// object with a restricted band OUT of consolidation; its component
// keeps drawing it individually with the per-op band test.
bool banded = false;
d3d_OBJECT *dobj = obj->GetDrawObj();
for (int opn = 0; opn < dobj->GetDrawOpCount() && !banded; opn++)
{
const L4DRAWOP *op = dobj->GetDrawOp(opn);
if (op->lodFar > 0.0f && op->lodFar < 1.0e8f)
banded = true;
}
// baked ground-shadow models draw via the mIsShadow blend path
// (translucent + depth bias); merging them would draw them opaque.
if (dobj->GetIsShadow())
banded = true;
// Issue #3 (cultural destruction): CulturalIcon/Landmark objects are
// DESTRUCTIBLE -- their intact model must vanish (and rubble appear) at
// BurningState via the revived StateInstanceSwitchRenderable's
// SetDrawObj swap. A model merged into the consolidated static mesh
// draws forever regardless of DrawObj, so destructibles stay OUT of
// consolidation and keep drawing individually (the same mechanism as
// the banded-LOD exclusion above).
{
Entity *owning = obj->GetOwningEntity();
if (owning != NULL
&& (owning->GetClassID() == CulturalIconClassID
|| owning->GetClassID() == LandmarkClassID))
banded = true;
}
if (!banded)
{
//Only load it for a static object if it has a valid mesh-
//we'll handle valid sphere lists later
this->AddStaticObject(obj->GetDrawObj());
obj->ResetDrawObj();
}
}
std::vector<HierarchicalDrawComponent *>::const_iterator child_it = obj->Enumerate();
while (child_it != obj->End())
{
RecurseStaticObject(*child_it);
++child_it;
}
}
}
d3d_OBJECT * DPLRenderer::ConsolidateSingleObject(LPD3DXMESH *meshes, D3DXMATRIX *transforms, UINT startingMesh, UINT meshCount, hash_map<DWORD, hyper> subsetHash, hash_map<hyper, DWORD> hashToOp, vector<L4DRAWOP*> finalOps)
{
HRESULT hr;
LPD3DXMESH outMesh = NULL;
// BT bring-up: the pod content ships .bgf, loaded by d3d_OBJECT::LoadObjectBGF
// with D3DXMESH_32BIT (32-bit index buffer). D3DXConcatenateMeshes was called
// with only D3DXMESH_MANAGED (16-bit output) -> concatenating 32-bit-index
// source meshes into a 16-bit output fails, leaving outMesh NULL -> the
// GetNumFaces() below dereferenced NULL and crashed (RP never hit this: its
// world meshes load from .x as 16-bit). Match the source meshes' index width.
V( D3DXConcatenateMeshes(meshes + startingMesh, meshCount, D3DXMESH_MANAGED | D3DXMESH_32BIT, transforms + startingMesh, NULL, NULL, mDevice, &outMesh) );
if (outMesh == NULL)
{
DEBUG_STREAM << "ConsolidateSingleObject: D3DXConcatenateMeshes failed (hr=0x"
<< std::hex << (unsigned)hr << std::dec << "), skipping " << meshCount
<< " static meshes\n" << std::flush;
return NULL;
}
DWORD *attributes;
int numFaces = outMesh->GetNumFaces();
V( outMesh->LockAttributeBuffer(0, &attributes) );
for (int i = 0; i < numFaces; i++)
{
stdext::hash_map<DWORD, hyper>::const_iterator face_it = subsetHash.find(attributes[i]);
if (face_it == subsetHash.end())
{
//Freak out
}
stdext::hash_map<hyper, DWORD>::const_iterator subset_it = hashToOp.find((*face_it).second);
if (subset_it == hashToOp.end())
{
//Freak out
}
attributes[i] = (*subset_it).second;
}
//
// PORT (draw-cost fix): sort the faces by draw-op OURSELVES and record each
// op's contiguous index range for the direct-draw path. The old path relied
// on GenerateAdjacency + OptimizeInplace(ATTRSORT) to build D3DX's attribute
// table -- but the concatenated source meshes are double-sided BGF geometry,
// whose degenerate adjacency makes the optimize FAIL silently. DrawSubset then
// SCANS the whole attribute buffer of this huge merged mesh per call (~350us
// x ~1300 ops when a merged map chunk is in view = the ~500ms hitch frames).
//
const int numOps = (int)finalOps.size();
std::vector<int> opStart(numOps, 0), opCount(numOps, 0);
{
DWORD *ib = NULL;
V( outMesh->LockIndexBuffer(0, (void **)&ib) ); // 32-bit (created 32BIT above)
if (ib != NULL)
{
int badAttr = 0; // remap failures (see audit below)
for (int i = 0; i < numFaces; i++)
{
DWORD op = attributes[i];
if ((int)op >= numOps) { op = 0; ++badAttr; } // SAME clamp as the write pass
++opCount[op];
}
for (int o = 1; o < numOps; ++o)
opStart[o] = opStart[o - 1] + opCount[o - 1];
std::vector<DWORD> sortedIB((size_t)numFaces * 3);
std::vector<DWORD> sortedAttr((size_t)numFaces);
std::vector<int> cursor(opStart.begin(), opStart.end());
for (int i = 0; i < numFaces; i++)
{
DWORD op = attributes[i];
if ((int)op >= numOps) op = 0;
const int dst = cursor[op]++;
sortedIB[(size_t)dst * 3 + 0] = ib[(size_t)i * 3 + 0];
sortedIB[(size_t)dst * 3 + 1] = ib[(size_t)i * 3 + 1];
sortedIB[(size_t)dst * 3 + 2] = ib[(size_t)i * 3 + 2];
sortedAttr[dst] = op;
}
memcpy(ib, sortedIB.data(), sortedIB.size() * sizeof(DWORD));
memcpy(attributes, sortedAttr.data(), sortedAttr.size() * sizeof(DWORD));
V( outMesh->UnlockIndexBuffer() );
// AUDIT (turret-panels hunt): faces whose remapped attribute is out of
// range mean the subsetHash/hashToOp lookup FAILED for their source op
// ("Freak out" above is a no-op) -- those faces draw with op 0's material
// or, before this counting fix, corrupted neighbouring ranges.
DEBUG_STREAM << "[consol] group: srcMeshes=" << meshCount
<< " faces=" << numFaces << " ops=" << numOps
<< " badAttr=" << badAttr << "\n" << std::flush;
}
}
V( outMesh->UnlockAttributeBuffer() );
// explicit attribute table over the now-sorted faces (keeps DrawSubset and any
// D3DX consumer valid; the direct-draw ranges below are the primary path)
{
std::vector<D3DXATTRIBUTERANGE> atable((size_t)numOps);
for (int o = 0; o < numOps; ++o)
{
atable[o].AttribId = (DWORD)o;
atable[o].FaceStart = (DWORD)opStart[o];
atable[o].FaceCount = (DWORD)opCount[o];
atable[o].VertexStart = 0;
atable[o].VertexCount = outMesh->GetNumVertices();
}
V( outMesh->SetAttributeTable(atable.data(), (DWORD)numOps) );
}
d3d_OBJECT *consolObj = new d3d_OBJECT(mDevice, outMesh, NULL, finalOps.size());
// direct-draw ranges + cached buffers (same fast path as LoadObjectBGF objects)
outMesh->GetVertexBuffer(&consolObj->mBgfVB);
outMesh->GetIndexBuffer(&consolObj->mBgfIB);
consolObj->mBgfStride = outMesh->GetNumBytesPerVertex();
consolObj->mBgfNumVerts = outMesh->GetNumVertices();
for (int o = 0; o < numOps; ++o)
{
consolObj->GetDrawOp(o)->bgfStartIndex = opStart[o] * 3;
consolObj->GetDrawOp(o)->bgfPrimCount = opCount[o];
}
for (int drawOpInd = 0; drawOpInd < consolObj->GetDrawOpCount(); drawOpInd++)
{
L4DRAWOP *op = consolObj->GetDrawOp(drawOpInd);
L4DRAWOP *source = finalOps[drawOpInd];
op->material = source->material;
op->texture = source->texture;
op->drawAsDecal = source->drawAsDecal;
op->alphaTest = source->alphaTest;
op->drawAsSky = source->drawAsSky;
// CONSOLIDATED-WORLD DEPTH RECESSION (coplanar cross-object resolution):
// entity props lay flat polys EXACTLY in the floor plane (MECHMOVR's
// dead-mech ground plates: 71 verts at y=0.000 vs afloor's y=0 top --
// the "flickering floor tile under the wreckage"). The board's global
// submission-order rule drew scene traversal LAST-over-FIRST on exact
// plane ties; in D3D terms, recess the merged static world by 3 depth-
// buffer steps so anything drawn individually (entity props, banded
// structures, the mech) deterministically wins floor-plane ties.
// 7.5e-7 NDC ~ 3 LSB of D24: invisible as parallax, decisive vs
// interpolation rounding.
op->lodDepthBias = 7.5e-7f;
if (op->texture.texture != NULL)
{
op->texture.texture->AddRef();
}
}
return consolObj;
}
void DPLRenderer::ConsolidateStaticObjects()
{
HRESULT hr;
// Consolidation runs by DEFAULT (the shipping-engine configuration). It is
// skipped when (a) BT_CONSOL=0 (diagnostic kill-switch), or (b) the
// EXPERIMENTAL runtime-LOD selection is on (BT_LODSEL=1): merged ops combine
// many instances + LOD bands of one material, so per-op band selection cannot
// survive the merge (a consolidated run draws every LOD simultaneously).
{
const char *cv = getenv("BT_CONSOL");
const char *lv = getenv("BT_LODSEL");
const bool lodSel = (lv != 0 && *lv == '1');
if ((cv != 0 && *cv == '0') || lodSel)
{
DEBUG_STREAM << "[consol] OFF (" << (lodSel ? "BT_LODSEL experimental" : "BT_CONSOL=0")
<< ") -- statics render individually\n" << std::flush;
return;
}
}
HierarchicalDrawComponent *drawComp;
SChainIteratorOf<HierarchicalDrawComponent*> iterator(&mRenderables);
while ((drawComp = iterator.ReadAndNext()) != NULL)
{
if (drawComp->IsStatic())
{
drawComp->Execute();
this->RecurseStaticObject(drawComp);
}
}
LPD3DXMESH *meshes = new LPD3DXMESH[mStaticObjectsCount];
D3DXMATRIX *transforms = new D3DXMATRIX[mStaticObjectsCount];
int i = 0;
int opCount = 0;
std::vector<L4DRAWOP*> finalOps;
stdext::hash_map<hyper, DWORD> hashToOp;
stdext::hash_map<DWORD, hyper> subsetHash;
int vertCount = 0;
int startMesh = 0;
int meshCount = 0;
for (d3d_OBJECT *obj = mStaticObjectsHead; obj; obj = obj->GetNext(-1), i++)
{
if (vertCount + obj->GetMesh()->GetNumVertices() > 65535)
{
d3d_OBJECT *consol = this->ConsolidateSingleObject(meshes, transforms, startMesh, meshCount, subsetHash, hashToOp, finalOps);
if (consol != NULL)
this->mConsolidatedStaticObjects.push_back(consol);
vertCount = 0;
startMesh = meshCount;
meshCount = 0;
opCount = 0;
//Clear the draw ops lists
finalOps.clear();
hashToOp.clear();
subsetHash.clear();
}
meshes[i] = obj->GetMesh();
transforms[i] = obj->GetLocalToWorld();
vertCount += meshes[i]->GetNumVertices();
meshCount++;
for (int opNum = 0; opNum < obj->GetDrawOpCount(); opNum++)
{
hyper hash = this->HashDrawOp(obj->GetDrawOp(opNum));
stdext::hash_map<hyper, DWORD>::const_iterator hash_it = hashToOp.find(hash);
if (hash_it == hashToOp.end())
{
finalOps.insert(finalOps.end(), obj->GetDrawOp(opNum));
hashToOp.insert(std::pair<hyper, DWORD>(hash, (DWORD)(finalOps.size() - 1)));
hash_it = hashToOp.find(hash);
}
subsetHash.insert(std::pair<DWORD, hyper>((DWORD)opCount, hash));
opCount++;
}
}
if (meshCount > 0)
{
d3d_OBJECT *consol = this->ConsolidateSingleObject(meshes, transforms, startMesh, meshCount, subsetHash, hashToOp, finalOps);
if (consol != NULL)
this->mConsolidatedStaticObjects.push_back(consol);
}
d3d_OBJECT *obj = mStaticObjectsHead;
while (obj != NULL)
{
d3d_OBJECT *next = obj->GetNext(-1);
delete obj;
obj = next;
}
mStaticObjectsHead = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// RemoveDynamicRenderable removes a renderable from the dynamic execution
// socket.
//
void
DPLRenderer::RemoveDynamicRenderable(Component *my_renderable)
{
Check(my_renderable);
Check(&dplRenderableSocket);
PlugIterator remover(my_renderable);
remover.RemoveSocket(&dplRenderableSocket);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
DPLRenderer::SetFogStyle(FogStyle my_fog)
{
if (getenv("BT_FOG_LOG"))
DEBUG_STREAM << "[fogstyle] SetFogStyle(" << (int)my_fog
<< ") 0=update 1=noUpdate 2=searchLightOn 3=searchLightOff 4=winnersCircle\n"
<< std::flush;
//STUBBED: DPL RB 1/14/07
switch(my_fog)
{
case updateFogSetting:
fogUpdating = True;
break;
case noUpdateFogSetting:
fogUpdating = False;
break;
case searchLightOnFogStyle:
fogRed = searchLightFogRed;
fogGreen = searchLightFogGreen;
fogBlue = searchLightFogBlue;
fogNear = searchLightFogNear;
fogFar = searchLightFogFar;
if(fogUpdating)
{
mDevice->SetRenderState(D3DRS_FOGCOLOR, D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen), (int)(255 * fogBlue)));
// dpl_SetViewFog(
// dplMainView,
// dpl_fog_type_pixel_lin,
// fogRed,
// fogGreen,
// fogBlue,
// fogNear,
// fogFar );
// dpl_FlushView(dplMainView);
}
break;
case searchLightOffFogStyle:
fogRed = noSearchLightFogRed;
fogGreen = noSearchLightFogGreen;
fogBlue = noSearchLightFogBlue;
fogNear = noSearchLightFogNear;
fogFar = noSearchLightFogFar;
if(fogUpdating)
{
mDevice->SetRenderState(D3DRS_FOGCOLOR, D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen), (int)(255 * fogBlue)));
// dpl_SetViewFog(
// dplMainView,
// dpl_fog_type_pixel_lin,
// fogRed,
// fogGreen,
// fogBlue,
// fogNear,
// fogFar );
// dpl_FlushView(dplMainView);
}
break;
case winnersCircleFogStyle:
//
// HACK!! This really shouldn't reset the clip planes, but since
// it only happens at the end of the review, it should be safe for now.
//
// dpl_SetViewClipPlanes ( dplMainView, 0.25f, 1100.0f );
// dpl_SetViewFog(
// dplMainView,
// dpl_fog_type_pixel_lin,
// 0.32,
// 0.3,
// 0.65,
// 100.0f,
// 1050.0f );
// dpl_FlushView(dplMainView);
break;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
DPLRenderer::GetCurrentFogSettings(
float *fog_Red,
float *fog_Green,
float *fog_Blue,
float *fog_Near,
float *fog_Far)
{
*fog_Red = fogRed;
*fog_Green = fogGreen;
*fog_Blue = fogBlue;
*fog_Near = fogNear;
*fog_Far = fogFar;
}
void DPLRenderer::SetCurrentFogLimits(
float fog_Near,
float fog_Far)
{
currentFogNear = fog_Near;
currentFogFar = fog_Far;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// MarkDCSHiearchy This will std::decend a DCS tree and set the appSpecific hook
// in every DCS equal to the entity pointer "entity", it calls itself recursively
// to do this.
//
void
DPLRenderer::MarkDCSHiearchy(
dpl_DCS *root_DCS, // Root of the hiearchy to mark
Entity *entity) // Entity pointer to mark it with
{
//STUBBED: DPL RB 1/14/07
//int
// dcs_counter;
//dpl_DCS
// *child_DCS;
////
//// First, mark this DCS
////
//if(dpl_GetAppSpecific(root_DCS))
// Fail("DPLRenderer::MarkDCSHiearchy tried to mark a DCS that was already marked!\n");
//dpl_PutAppSpecific(root_DCS,entity);
////
//// Now call this routine on all this dcs's children
////
//dcs_counter = 0;
//while((child_DCS=dpl_GetDCSChildDCS(root_DCS,dcs_counter)) != NULL)
//{
// MarkDCSHiearchy(child_DCS, entity);
// dcs_counter++;
//}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ReadPSFX
// reads a pfx file into a supplied array
// Most of this routine was a direct copy of phil's code and as such is a bit
// on the messy side.
//
dpl_PARTICLESTART_EFFECT_INFO*
DPLRenderer::ReadPSFX(
const char *file_name) // Name of the file containing the PSFX description
{
//STUBBED: DPL RB 1/14/07
// FILE
// *fp;
// int
// i,
// status;
// char
// line[256],
// *cp;
// //
// // Open the file containing the psfx, fail if it doesn't exist
// //
// strcpy(line,"video\\");
// strcat(line,file_name);
// fp = fopen ( line, "rt" );
// if(!fp)
// {
// DEBUG_STREAM<<"Could not open pfx file "<<line<<" effects using it will fail\n" << std::flush;
// return(NULL);
//// Fail("Required PFX file not found\n");
// }
// //
// // Allocate the memory for the pfx to be stored in.
// //
dpl_PARTICLESTART_EFFECT_INFO *psfx_info = new dpl_PARTICLESTART_EFFECT_INFO;
// if(!psfx_info)
// {
// Fail("ran out of RAM trying to read a pfx file\n");
// }
// //
// // This is the format of the psfx file we are going to read
// //
// #if 0
// format of psfx file is (NO BLANK LINES UNTIL END!)
// %s texture
// %d identifier %d maximum_issue %f release_period %f rate
// %f px %f py %f pz %f pv
// %f velx %f vely %f velz %f velxv %f velyv %f velzv
// %f rad %f radv %f exp %f expv %f dexp %f dexpv
// %f accelx %f accely %f accelz %f accelxv %f accelyv %f accelzv
// %f atten %f attenv
// %f sRi %f sGi %f sBi %f sAi %f sRiv %f sGiv %f sBiv %f sAiv
// %f sRo %f sGo %f sBo %f sAo %f sRov %f sGov %f sBov %f sAov
// %f eRi %f eGi %f eBi %f eAi %f eRiv %f eGiv %f eBiv %f eAiv
// %f eRo %f eGo %f eBo %f eAo %f eRov %f eGov %f eBov %f eAov
// %f color_warp %f alpha_warp
// %f duration %f durationv
// #endif
// //
// // Read in the texture name, null terminate it and look it up,
// // we don't complain if it fails since it is possible to do one of these
// // effects with no texture on it.
// //
// cp = fgets ( line, 255, fp );
// for (i=0; i<255; i++ )
// {
// if (cp[i] == '\n')
// cp[i]=0x0;
// }
// psfx_info->tex = dpl_LookupTexture ( cp, dpl_lookup_normal, &status );
// #if PRINT_THE_PFX
// printf ( "texture => %s\n", cp );
// #endif
// //
// // The remander of these statements get the rest of the PFX data
// //
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp,
// "%x %d %f %f\n",
// &psfx_info->identifier, &psfx_info->maximum_issue, &psfx_info->release_period, &psfx_info->rate );
// if(status < 4)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f %f %f\n", &psfx_info->px, &psfx_info->py, &psfx_info->pz, &psfx_info->pv );
// if(status < 4)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f %f %f %f %f\n",
// &psfx_info->velx, &psfx_info->vely, &psfx_info->velz,
// &psfx_info->velxv, &psfx_info->velyv, &psfx_info->velzv );
// if(status < 6)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f %f %f %f %f\n",
// &psfx_info->rad, &psfx_info->radv, &psfx_info->exp, &psfx_info->expv, &psfx_info->dexp, &psfx_info->dexpv );
// if(status < 6)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f %f %f %f %f\n",
// &psfx_info->accelx, &psfx_info->accely, &psfx_info->accelz,
// &psfx_info->accelxv, &psfx_info->accelyv, &psfx_info->accelzv );
// if(status < 6)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f\n", &psfx_info->atten, &psfx_info->attenv );
// if(status < 2)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f %f %f %f %f %f %f\n",
// &psfx_info->sRi,&psfx_info->sGi,&psfx_info->sBi,&psfx_info->sAi,&psfx_info->sRiv,&psfx_info->sGiv,&psfx_info->sBiv,&psfx_info->sAiv );
// if(status < 8)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f %f %f %f %f %f %f\n",
// &psfx_info->sRo,&psfx_info->sGo,&psfx_info->sBo,&psfx_info->sAo,&psfx_info->sRov,&psfx_info->sGov,&psfx_info->sBov,&psfx_info->sAov );
// if(status < 8)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f %f %f %f %f %f %f\n",
// &psfx_info->eRi,&psfx_info->eGi,&psfx_info->eBi,&psfx_info->eAi,&psfx_info->eRiv,&psfx_info->eGiv,&psfx_info->eBiv,&psfx_info->eAiv );
// if(status < 8)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f %f %f %f %f %f %f\n",
// &psfx_info->eRo,&psfx_info->eGo,&psfx_info->eBo,&psfx_info->eAo,&psfx_info->eRov,&psfx_info->eGov,&psfx_info->eBov,&psfx_info->eAov );
// if(status < 8)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f\n", &psfx_info->colour_warp, &psfx_info->alpha_warp );
// if(status < 2)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// cp = fgets ( line, 255, fp );
// status = sscanf ( cp, "%f %f\n", &psfx_info->dur, &psfx_info->durv );
// if(status < 2)
// {
// std::cout<<"pfx file "<<file_name<<" did not read correctly\n";
// return(NULL);
// }
//
// fclose (fp);
// #if PRINT_THE_PFX
// printf ( "Read psfx ->\n" );
// /*{{{ trace the psfx*/
// printf ( "psfx_info->tex = 0x%x\n",
// psfx_info->tex );
// printf ( "identifier = 0x%x ",
// psfx_info->identifier );
// printf ( "maximum_issue = %d ",
// psfx_info->maximum_issue );
// printf ( "release_period = %f\n",
// psfx_info->release_period );
// printf ( "rate = %f ",
// psfx_info->rate );
// printf ( "px = %f ",
// psfx_info->px );
// printf ( "py = %f ",
// psfx_info->py );
// printf ( "pz = %f ",
// psfx_info->pz );
// printf ( "pv = %f\n",
// psfx_info->pv );
// printf ( "velx = %f ",
// psfx_info->velx );
// printf ( "vely = %f ",
// psfx_info->vely );
// printf ( "velz = %f\n",
// psfx_info->velz );
// printf ( "velxv = %f ",
// psfx_info->velxv );
// printf ( "velyv = %f ",
// psfx_info->velyv );
// printf ( "velzv = %f\n",
// psfx_info->velzv );
// printf ( "rad = %f ",
// psfx_info->rad );
// printf ( "radv = %f\n",
// psfx_info->radv );
// printf ( "exp = %f ",
// psfx_info->exp );
// printf ( "expv = %f ",
// psfx_info->expv );
// printf ( "dexp = %f ",
// psfx_info->dexp );
// printf ( "dexpv = %f\n",
// psfx_info->dexpv );
// printf ( "accelx = %f ",
// psfx_info->accelx );
// printf ( "accely = %f ",
// psfx_info->accely );
// printf ( "accelz = %f ",
// psfx_info->accelz );
// printf ( "accelxv = %f ",
// psfx_info->accelxv );
// printf ( "accelyv = %f ",
// psfx_info->accelyv );
// printf ( "accelzv = %f\n",
// psfx_info->accelzv );
// printf ( "atten = %f ",
// psfx_info->atten );
// printf ( "attenv = %f\n",
// psfx_info->attenv );
// printf ( "sRi = %f ",
// psfx_info->sRi );
// printf ( "sGi = %f ",
// psfx_info->sGi );
// printf ( "sBi = %f ",
// psfx_info->sBi );
// printf ( "sAi = %f\n",
// psfx_info->sAi );
// printf ( "sRiv = %f ",
// psfx_info->sRiv );
// printf ( "sGiv = %f ",
// psfx_info->sGiv );
// printf ( "sBiv = %f ",
// psfx_info->sBiv );
// printf ( "sAiv = %f\n",
// psfx_info->sAiv );
// printf ( "sRo = %f ",
// psfx_info->sRo );
// printf ( "sGo = %f ",
// psfx_info->sGo );
// printf ( "sBo = %f ",
// psfx_info->sBo );
// printf ( "sAo = %f\n",
// psfx_info->sAo );
// printf ( "sRov = %f ",
// psfx_info->sRov );
// printf ( "sGov = %f ",
// psfx_info->sGov );
// printf ( "sBov = %f ",
// psfx_info->sBov );
// printf ( "sAov = %f\n",
// psfx_info->sAov );
// printf ( "eRi = %f ",
// psfx_info->eRi );
// printf ( "eGi = %f ",
// psfx_info->eGi );
// printf ( "eBi = %f ",
// psfx_info->eBi );
// printf ( "eAi = %f\n",
// psfx_info->eAi );
// printf ( "eRiv = %f ",
// psfx_info->eRiv );
// printf ( "eGiv = %f ",
// psfx_info->eGiv );
// printf ( "eBiv = %f ",
// psfx_info->eBiv );
// printf ( "eAiv = %f\n",
// psfx_info->eAiv );
// printf ( "eRo = %f ",
// psfx_info->eRo );
// printf ( "eGo = %f ",
// psfx_info->eGo );
// printf ( "eBo = %f ",
// psfx_info->eBo );
// printf ( "eAo = %f\n",
// psfx_info->eAo );
// printf ( "eRov = %f ",
// psfx_info->eRov );
// printf ( "eGov = %f ",
// psfx_info->eGov );
// printf ( "eBov = %f ",
// psfx_info->eBov );
// printf ( "eAov = %f\n",
// psfx_info->eAov );
// printf ( "colour_warp = %f ",
// psfx_info->colour_warp );
// printf ( "alpha_warp = %f ",
// psfx_info->alpha_warp );
// printf ( "dur = %f ",
// psfx_info->dur );
// printf ( "durv = %f\n",
// psfx_info->durv );
// #endif
return(psfx_info);
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// New INI file handler
// routine to read in an environment from a notation file and setup lights,
// spfx and such...
//
void
DPLRenderer::DPLReadINIPage(
NotationFile *master_notation_file,
const char *starting_page_name,
Mission *mission,
Logical debug_printing)
{
//STUBBED: DPL RB 1/14/07
float
red,
green,
blue,
x_rotate,
y_rotate,
z_rotate;
NameList
*cache_namelist,
*light_namelist,
*psfx_namelist,
*specialfx_namelist,
*include_pages,
*path_pages;
NameList::Entry
*entry;
const char
*next_include_page_name,
*TempStringPtr,
*compare_source;
if (debug_printing)
std::cout<<"DPLReadINIPage processing "<<starting_page_name<<"\n";
//
//---------------------------------------------------
// Process all the DPL gunk found on this page first
//---------------------------------------------------
//
// BT (task #20): accumulate the day/night search paths (was DPL-stubbed).
// Each entry is PREPENDED so the last-encountered (more-specific: the
// des_day/arena branch pages are visited after dpl_defaults) ends up FIRST
// = highest priority. Consumed by SetVideoPathPriority in DPLReadEnvironment.
if ((path_pages = master_notation_file->MakeEntryList(starting_page_name, "objectpath")) != NULL)
{
Register_Object(path_pages);
for(entry = path_pages->GetFirstEntry(); entry; entry = entry->GetNextEntry())
{
mObjectPaths.insert(mObjectPaths.begin(), std::string((char *)entry->dataReference));
if (debug_printing)
std::cout << "objectpath '" << (char *)entry->dataReference << "'\n";
}
Unregister_Object(path_pages);
delete path_pages;
}
if ((path_pages = master_notation_file->MakeEntryList(starting_page_name, "texmappath")) != NULL)
{
Register_Object(path_pages);
for(entry = path_pages->GetFirstEntry(); entry; entry = entry->GetNextEntry())
{
mTexmapPaths.insert(mTexmapPaths.begin(), std::string((char *)entry->dataReference));
if (debug_printing)
std::cout << "texmappath '" << (char *)entry->dataReference << "'\n";
}
Unregister_Object(path_pages);
delete path_pages;
}
if ((path_pages = master_notation_file->MakeEntryList(starting_page_name, "materialpath")) != NULL)
{
Register_Object(path_pages);
for(entry = path_pages->GetFirstEntry(); entry; entry = entry->GetNextEntry())
{
mMaterialPaths.insert(mMaterialPaths.begin(), std::string((char *)entry->dataReference));
if (debug_printing)
std::cout << "materialpath '" << (char *)entry->dataReference << "'\n";
}
Unregister_Object(path_pages);
delete path_pages;
}
if (master_notation_file->GetEntry(starting_page_name, "priorityobjectpath", &TempStringPtr))
{
// dpl_AddToObjectFilePath((char *)TempStringPtr, dpl_path_system);
if (debug_printing)
{ DEBUG_STREAM << "priorityobjectpath '" << TempStringPtr << "'" << std::endl << std::flush; }
}
if (master_notation_file->GetEntry(starting_page_name, "prioritytexmappath", &TempStringPtr))
{
// dpl_AddToTexmapFilePath((char *)TempStringPtr, dpl_path_system);
if (debug_printing)
{ DEBUG_STREAM << "prioritytexmappath '" << TempStringPtr << "'" << std::endl << std::flush; }
}
if (master_notation_file->GetEntry(starting_page_name, "prioritymaterialpath", &TempStringPtr))
{
// dpl_AddToMaterialFilePath((char *)TempStringPtr, dpl_path_system);
if (debug_printing)
{ DEBUG_STREAM << "prioritymaterialpath '" << TempStringPtr << "'" << std::endl << std::flush; }
}
//
// Get a the list of dpl objects that should be loaded into cache and load them
//
if ((cache_namelist = master_notation_file->MakeEntryList(starting_page_name, "cache")) != NULL)
{
d3d_OBJECT *d3d_a_object;
Register_Object(cache_namelist);
for(entry = cache_namelist->GetFirstEntry(); entry; entry = entry->GetNextEntry())
{
d3d_a_object = d3d_OBJECT::LoadObject(mDevice, (char *)entry->dataReference);
if(!d3d_a_object)
DEBUG_STREAM<<"Unable to cache "<<(char *)entry->dataReference<<"\n" << std::flush;
else
{
if(debug_printing)
std::cout<<"Caching "<<(char *)entry->dataReference<<"\n";
}
}
Unregister_Object(cache_namelist);
delete cache_namelist;
}
//
// Get the clip range
//
if(master_notation_file->GetEntry(starting_page_name, "clip" ,&TempStringPtr))
{
sscanf(TempStringPtr, "%f %f", &clipNear, &clipFar);
if(debug_printing)
std::cout<<"Clip Range "<<TempStringPtr<<"\n";
}
//
// Background color
//
if(master_notation_file->GetEntry(starting_page_name, "backgnd" ,&TempStringPtr))
{
sscanf(TempStringPtr, "%f %f %f", &backgroundRed, &backgroundGreen, &backgroundBlue);
if(debug_printing)
std::cout<<"Background Color "<<TempStringPtr<<"\n";
}
//
// Get the projection, pipe and other view related junk
//
if(master_notation_file->GetEntry(starting_page_name, "viewangle" ,&TempStringPtr))
{
sscanf(TempStringPtr, "%f", &viewAngle);
if(debug_printing)
std::cout<<"View Angle "<<TempStringPtr<<"\n";
}
//
// now that we have the viewing angle, screen size, and clip ranges, setup the projection matrix
//
D3DXMatrixIdentity(&mProjectionMatrix);
// DEBUG(bring-up): the camera view matrix is built with D3DXMatrixLookAtRH
// (DPLEyeRenderable, L4VIDRND.cpp) -> visible geometry has NEGATIVE view-space
// Z. A LH projection expects +Z and clips everything (w<0) -> black frame.
// Use the matching RH projection so the mech becomes visible.
// BT (task #20): honor the live window aspect if the window has been resized
// (gWindowAspect, top of file; set by L4NotifyWindowResized on WM_SIZE).
{
const float projAspect = gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size;
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix,
BTFovYFromHorizontal(viewAngle, projAspect), projAspect, clipNear, clipFar);
}
//mProjectionMatrix(0, 0) *= -1;
mDecalEpsilon = 0.0000005f;
mDecalProjectionMatrix = mProjectionMatrix;
mDecalProjectionMatrix._33 -= mDecalEpsilon;
//
// setup the fog
//
if(master_notation_file->GetEntry(starting_page_name, "fog" ,&TempStringPtr))
{
sscanf(TempStringPtr, "%f %f %f %f %f", &fogNear, &fogFar, &fogRed, &fogGreen, &fogBlue);
// BT (task #63): BT_FOG="near far r g b" overrides the AUTHORED fog for
// tuning/demo (works on the main branch now, not just the no-fog fallback).
if (const char *bf = getenv("BT_FOG"))
if (!(bf[0]=='0' && bf[1]==0))
sscanf(bf, "%f %f %f %f %f", &fogNear, &fogFar, &fogRed, &fogGreen, &fogBlue);
searchLightFogRed = fogRed;
searchLightFogGreen = fogGreen;
searchLightFogBlue = fogBlue;
searchLightFogNear = fogNear;
searchLightFogFar = fogFar;
noSearchLightFogRed = fogRed;
noSearchLightFogGreen = fogGreen;
noSearchLightFogBlue = fogBlue;
noSearchLightFogNear = fogNear;
noSearchLightFogFar = fogFar;
currentFogNear = fogNear;
currentFogFar = fogFar;
DEBUG_STREAM << "[fog] resolved page='" << starting_page_name << "' near=" << fogNear
<< " far=" << fogFar << " rgb=(" << fogRed << "," << fogGreen << ","
<< fogBlue << ")\n" << std::flush;
// Force a 0-0 black fog on startup
// dpl_SetViewFog(dplMainView, dpl_fog_type_pixel_lin, 0.0, 0.0, 0.0, 0.01, 0.05);
//TODO: for fog testing, just set the values here
mDevice->SetRenderState(D3DRS_FOGENABLE, TRUE);
mDevice->SetRenderState(D3DRS_FOGCOLOR, D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen), (int)(255 * fogBlue)));
mDevice->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_LINEAR);
if(debug_printing)
std::cout<<"Fog "<<TempStringPtr<<"\n";
}
else
{
//
// CORRECTION (task #63): task #20's claim "the SHIPPED maps define no fog"
// was WRONG -- it looked in .MAP/.RES, but the arcade sourced fog from the
// DPL env INI (L4DPLCFG = content/BTDPL.INI), which authors a fog= (usually
// nosearchlightfog= too) key on EVERY map/time/weather leaf page. The main
// (if) branch above handles the real authored fog; this else branch is only
// reached by a page that genuinely lacks a fog= key (e.g. a *_default page
// whose weather compare is commented out AND has no own fog). Keep a period-
// plausible fallback curtain there so such a page still fades to the horizon;
// BT_FOG="near far r g b" overrides, BT_FOG=0 disables.
//
float fN = 150.0f, fF = 520.0f, fR = 0.44f, fG = 0.44f, fB = 0.62f;
int fogOn = 1;
if (const char *bf = getenv("BT_FOG"))
{
if (bf[0] == '0' && bf[1] == 0)
fogOn = 0;
else
sscanf(bf, "%f %f %f %f %f", &fN, &fF, &fR, &fG, &fB);
}
if (fogOn)
{
fogNear = fN; fogFar = fF; fogRed = fR; fogGreen = fG; fogBlue = fB;
searchLightFogRed = noSearchLightFogRed = fogRed;
searchLightFogGreen = noSearchLightFogGreen = fogGreen;
searchLightFogBlue = noSearchLightFogBlue = fogBlue;
searchLightFogNear = noSearchLightFogNear = fogNear;
searchLightFogFar = noSearchLightFogFar = fogFar;
currentFogNear = fogNear;
currentFogFar = fogFar;
mDevice->SetRenderState(D3DRS_FOGENABLE, TRUE);
mDevice->SetRenderState(D3DRS_FOGCOLOR, D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen), (int)(255 * fogBlue)));
mDevice->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_LINEAR);
}
}
//
// setup the no searchlight fog if any
//
if(master_notation_file->GetEntry(starting_page_name, "nosearchlightfog" ,&TempStringPtr))
{
sscanf(TempStringPtr, "%f %f %f %f %f", &noSearchLightFogNear, &noSearchLightFogFar, &noSearchLightFogRed, &noSearchLightFogGreen, &noSearchLightFogBlue);
if(debug_printing)
std::cout<<"nosearchlightfog "<<TempStringPtr<<"\n";
}
//
// setup the ambient light
//
if(master_notation_file->GetEntry(starting_page_name, "ambient" ,&TempStringPtr))
{
sscanf(TempStringPtr, "%f %f %f ", &red, &green, &blue);
mEnvAmbient = D3DCOLOR_XRGB((int)(255 * red), (int)(255 * green), (int)(255 * blue));
mDevice->SetRenderState(D3DRS_AMBIENT, mEnvAmbient); // BT: capture for the per-frame re-assert
if(debug_printing)
std::cout<<"Ambient Light "<<TempStringPtr<<"\n";
}
//
// Set up the day/night cloud color
//
if (master_notation_file->GetEntry(starting_page_name, "clouds", &TempStringPtr))
{
sscanf(TempStringPtr, "%f %f %f ", &this->mCloudRed, &this->mCloudGreen, &this->mCloudBlue);
}
if (master_notation_file->GetEntry(starting_page_name, "cloudemit", &TempStringPtr))
{
sscanf(TempStringPtr, "%f %f %f ", &this->mCloudEmitRed, &this->mCloudEmitGreen, &this->mCloudEmitBlue);
}
//
// Get a the list of lights from this page
//
if ((light_namelist = master_notation_file->MakeEntryList(starting_page_name, "light")) != NULL)
{
Register_Object(light_namelist);
//
// HACK !!! All the lights must (temporarily) be defined on a single page.
// The lights and their DCS's should really be stored in a light object on
// a chain, or as an actual entity in the simulation. This line will throw
// an exception if this rule is violated
//
if(light_namelist->EntryCount() != 0)
{
if(sceneLightCount != 0)
{
Fail("All lights must be defined on a single INI file page!\n");
}
//
// Create storage space for the dpl_LIGHT and dpl_DCS structures
//
sceneLightCount = light_namelist->EntryCount();
if (sceneLightCount)
{
sceneLight = new D3DLIGHT9[sceneLightCount];
memset(sceneLight, 0, sizeof(D3DLIGHT9) * sceneLightCount);
}
int current_entry = 0;
for(entry = light_namelist->GetFirstEntry(); entry; entry = entry->GetNextEntry(), ++current_entry)
{
//
// Read the parameters for the light
//
sscanf(
(char *)entry->dataReference,
"%f %f %f %f %f %f",
&red,
&green,
&blue,
&x_rotate,
&y_rotate,
&z_rotate);
if(debug_printing)
std::cout<<"Light "<<(char *)entry->dataReference<<"\n";
//
// Create and rotate a dcs to hold the light, and the light itself
//
sceneLight[current_entry].Type = D3DLIGHT_DIRECTIONAL;
sceneLight[current_entry].Diffuse.r = red;
sceneLight[current_entry].Diffuse.g = green;
sceneLight[current_entry].Diffuse.b = blue;
sceneLight[current_entry].Diffuse.a = 1.0f;
D3DXMATRIX rot, rotX, rotY, rotZ;
D3DXMatrixRotationX(&rotX, x_rotate * (PI/180.0f));
D3DXMatrixRotationY(&rotY, y_rotate * (PI/180.0f));
D3DXMatrixRotationZ(&rotZ, z_rotate * (PI/180.0f));
rot = rotZ * rotX * rotY;
D3DXVECTOR3 dir(0, 0, -1);
D3DXVECTOR4 vec;
D3DXVec3Transform(&vec, &dir, &rot);
dir = D3DXVECTOR3(vec.x, vec.y, vec.z);
D3DXVec3Normalize(&dir, &dir);
sceneLight[current_entry].Direction.x = dir.x;
sceneLight[current_entry].Direction.y = dir.y;
sceneLight[current_entry].Direction.z = dir.z;
mDevice->SetLight(current_entry, &sceneLight[current_entry]);
mDevice->LightEnable(current_entry, TRUE);
// sceneLight[current_entry] = dpl_NewLight();
// sceneLightDCS[current_entry] = dpl_NewDCS();
// dpl_AddDCSToScene ( sceneLightDCS[current_entry] );
// dpl_SetLightType ( sceneLight[current_entry], dpl_light_type_directional );
// dpl_SetLightColor ( sceneLight[current_entry], red, green, blue);
// dpl_SetLightDCS ( sceneLight[current_entry], sceneLightDCS[current_entry] );
// dpl_RotateDCS ( sceneLightDCS[current_entry], z_rotate, dpl_Z );
// dpl_RotateDCS ( sceneLightDCS[current_entry], x_rotate, dpl_X );
// dpl_RotateDCS ( sceneLightDCS[current_entry], y_rotate, dpl_Y );
// dpl_FlushLight ( sceneLight[current_entry] );
// dpl_FlushDCS ( sceneLightDCS[current_entry] );
}
}
//
// Get rid of the light entry list
//
Unregister_Object(light_namelist);
delete light_namelist;
}
//
// Get the list of PSFX effects we should load into RAM. RECONSTRUCTED
// (was stubbed with ReadPSFX): the "psfxN=file.pfx" entries on the visited
// pages ([pfx_day]/[pfx_night], include-reached from the mission's arena
// page) bind each dpl effect NUMBER to its authentic .PFX definition,
// loaded into the BT particle layer (BTLoadPfxFile / BTStartPfx / BTDrawPfx
// at the top of this file) that DPLIndependantEffect's <100 arm consumes.
//
if ((psfx_namelist = master_notation_file->MakeEntryList(starting_page_name, "psfx")) != NULL)
{
extern int BTLoadPfxFile_slot(const char *file_name, int slot);
int pfx_loaded = 0;
for (entry = psfx_namelist->GetFirstEntry();
entry;
entry = entry->GetNextEntry())
{
int psfx_number = atoi(entry->GetName() + 4); // "psfxN"
const char *psfx_file_name = entry->GetChar();
pfx_loaded += BTLoadPfxFile_slot(psfx_file_name, psfx_number);
}
DEBUG_STREAM << "[pfx] page '" << starting_page_name << "': "
<< pfx_loaded << " effect definitions loaded\n" << std::flush;
delete psfx_namelist;
}
//
// OLD-STYLE SPECIALFX ("specialfxN=Name" + [Name] descriptor pages) -- the
// per-weapon impact effects (PPCHit/LaserHit/MissileHit/Chunks/Sparks/
// Fireball...). Field names per the 1995 binary's parser (part_008.c:1546:
// type/size/velocity/v_bias/y_off/variance/gravity/count/repeats +
// "cook" "%f %f %f" + "cool" "%f %f" + texture). NOTE the binary reads
// "variance" but the INI authors "varience" -- the lookup MISSED in the
// shipping pod, so variance is authentically dead (not parsed here).
// The 2007 loader's version-2-only walk below skips all of these
// (version < 2 -> continue), which is why the layer never rendered.
//
{
NameList *sfx_namelist =
master_notation_file->MakeEntryList(starting_page_name, "specialfx");
if (sfx_namelist != NULL)
{
extern int BTInstallSfx(int slot, int type, int no_tex, float size,
float velocity, float v_bias, float y_off, const float cook[3],
float gravity, float cool_mul, float cool_sub, int count, int repeats);
int sfx_loaded = 0;
for (entry = sfx_namelist->GetFirstEntry();
entry;
entry = entry->GetNextEntry())
{
int sfx_number = atoi(entry->GetName() + 9); // "specialfxN"
const char *page = entry->GetChar();
if (page == NULL || page[0] == '\0')
continue;
int type = 0, count = 1, repeats = 1;
float size = 0, velocity = 0, v_bias = 0, y_off = 0, gravity = 0;
float cook[3] = { 1, 1, 1 };
float cool_mul = 0.9f, cool_sub = 0.02f;
const char *sp = NULL;
master_notation_file->GetEntry(page, "type", &type);
master_notation_file->GetEntry(page, "size", &size);
master_notation_file->GetEntry(page, "velocity", &velocity);
master_notation_file->GetEntry(page, "v_bias", &v_bias);
master_notation_file->GetEntry(page, "y_off", &y_off);
master_notation_file->GetEntry(page, "gravity", &gravity);
master_notation_file->GetEntry(page, "count", &count);
master_notation_file->GetEntry(page, "repeats", &repeats);
if (master_notation_file->GetEntry(page, "cook", &sp) && sp != NULL)
sscanf(sp, "%f %f %f", &cook[0], &cook[1], &cook[2]);
if (master_notation_file->GetEntry(page, "cool", &sp) && sp != NULL)
sscanf(sp, "%f %f", &cool_mul, &cool_sub);
int no_tex = 0;
if (master_notation_file->GetEntry(page, "texture", &sp) && sp != NULL)
no_tex = (strncmp(sp, "NOTEX", 5) == 0); // NOTEX / NOTEXT
sfx_loaded += BTInstallSfx(sfx_number, type, no_tex, size, velocity,
v_bias, y_off, cook, gravity, cool_mul, cool_sub, count, repeats);
}
DEBUG_STREAM << "[sfx] page '" << starting_page_name << "': "
<< sfx_loaded << " old-style specialfx installed\n" << std::flush;
delete sfx_namelist;
}
}
////
//// (original stubbed loader retained for provenance)
////
//if ((psfx_namelist = master_notation_file->MakeEntryList(starting_page_name, "psfx")) != NULL)
//{
// const char *psfx_file_name;
// int psfx_number;
// //
// // create all the PSFX on the current page
// //
// for( entry = psfx_namelist->GetFirstEntry();
// entry;
// entry = entry->GetNextEntry())
// {
// psfx_number = atoi(entry->GetName()+4);
// psfx_file_name = entry->GetChar();
// if(debug_printing)
// std::cout<<"psfx"<<psfx_number<<" "<<psfx_file_name<<"\n";
// if(psfx_number < 0 || psfx_number > MAX_PSFX_COUNT-1)
// {
// Fail("PSFX id number was not in the allowed range");
// }
// //
// // See if we are overwriting an existing psfx
// //
// if(myPSFXDescriptons[psfx_number])
// {
// std::cout<<"psfx#"<<psfx_number<<" already existed from an earlier page, redefining it\n";
// delete myPSFXDescriptons[psfx_number];
// Unregister_Pointer(myPSFXDescriptons[psfx_number]);
// }
//// DEBUG_STREAM<<"Loading PSFX# "<<psfx_number<<" from file "<<psfx_file_name<<"\n" << std::flush;
// myPSFXDescriptons[psfx_number] = ReadPSFX(psfx_file_name);
// Register_Pointer(myPSFXDescriptons[psfx_number]);
// }
// //
// // Get rid of the PSFX entry list
// //
// delete psfx_namelist;
//}
//
// Get a the list of special effects
//
if ((specialfx_namelist = master_notation_file->MakeEntryList(starting_page_name, "effect")) != NULL)
{
const char *effect_page_name;
int status, spfx_number, version;
INDIE_EFFECT spfx;
memset(&spfx, 0, sizeof(PARTICLE_EFFECT));
Register_Object(specialfx_namelist);
//
// create all the effects on the current page
//
for( entry = specialfx_namelist->GetFirstEntry();
entry;
entry = entry->GetNextEntry())
{
//
// Get the page with this effect on it
//
effect_page_name = entry->GetChar();
if(debug_printing)
std::cout<<"specialfx"<<spfx_number<<" "<<effect_page_name<<"\n";
master_notation_file->GetEntry(effect_page_name, "id", &spfx_number);
version = 1;
master_notation_file->GetEntry(effect_page_name, "version", &version);
if (version < 2)
continue;
master_notation_file->GetEntry(effect_page_name, "texbounds", &TempStringPtr);
sscanf(TempStringPtr, "%f %f %f %f", &spfx.textureBounds.left, &spfx.textureBounds.top, &spfx.textureBounds.right, &spfx.textureBounds.bottom);
master_notation_file->GetEntry(effect_page_name, "rotate", (int*)&spfx.rotate);
master_notation_file->GetEntry(effect_page_name, "size", &spfx.fragSize);
master_notation_file->GetEntry(effect_page_name, "velocity", &spfx.velocity);
master_notation_file->GetEntry(effect_page_name, "varianceX", &spfx.varianceX);
master_notation_file->GetEntry(effect_page_name, "varianceY", &spfx.varianceY);
master_notation_file->GetEntry(effect_page_name, "varianceZ", &spfx.varianceZ);
master_notation_file->GetEntry(effect_page_name, "gravity", &spfx.gravity);
master_notation_file->GetEntry(effect_page_name, "count", &spfx.fragCount);
master_notation_file->GetEntry(effect_page_name, "life", &spfx.fragLifetime);
if (!master_notation_file->GetEntry(effect_page_name, "max_repeat", &spfx.maxRepeat))
spfx.maxRepeat = 30.0;
NameList *color_list = master_notation_file->MakeEntryList(effect_page_name, "color");
int i = 0;
for (NameList::Entry *color_entry = color_list->GetFirstEntry(); color_entry; color_entry = color_entry->GetNextEntry())
{
memset(&spfx.colors[i], 0, sizeof(COLOR_POINT));
spfx.colors[i].active = true;
int a, r, g, b;
sscanf(color_entry->GetChar(), "%f %d %d %d %d", &spfx.colors[i].time, &a, &r, &g, &b);
spfx.colors[i].color.argb = D3DCOLOR_ARGB(a, r, g, b);
i++;
}
for (;i < COLOR_POINT_COUNT; i++)
{
memset(&spfx.colors[i], 0, sizeof(COLOR_POINT));
}
//
// install the effect
//
int isIndie = 0;
master_notation_file->GetEntry(effect_page_name, "independent", &isIndie);
if (isIndie)
{
master_notation_file->GetEntry(effect_page_name, "maxIssue", &spfx.maxIssue);
master_notation_file->GetEntry(effect_page_name, "releasePeriod", &spfx.releasePeriod);
master_notation_file->GetEntry(effect_page_name, "duration", &spfx.duration);
spfx.id = 1000 + spfx_number;
myPSFXDescriptons[spfx_number] = spfx;
}
else
ParticleEngine::InstallEffect(spfx_number, *((PARTICLE_EFFECT*)&spfx));
}
//
// Get rid of the effect entry list
//
Unregister_Object(specialfx_namelist);
delete specialfx_namelist;
}
//--------------------------------------------------
// Make a list of all the include pages on this page
//--------------------------------------------------
if ((include_pages = master_notation_file->MakeEntryList(starting_page_name, "include")) != NULL)
{
Register_Object(include_pages);
//
// Recursively process all the include pages
//
for( entry = include_pages->GetFirstEntry();
entry;
entry = entry->GetNextEntry())
{
// Get the name of the next include page
next_include_page_name = (char *)entry->dataReference;
// Process the next page recursively through this routine
DPLReadINIPage(
master_notation_file,
next_include_page_name,
mission,
debug_printing);
if(debug_printing)
std::cout<<"DPLReadINIPage returned to "<<starting_page_name<<"\n";
}
Unregister_Object(include_pages);
delete include_pages;
}
//
// Process a compare and branch if there is one on this page
//
if(master_notation_file->GetEntry(starting_page_name, "compare" ,&compare_source))
{
Logical
match;
NameList
*branch_pages;
CString
compare_strings[10],
wild_card("*"),
token_string,
target_string,
master_compare_string(compare_source);
int
token_count;
ResourceFile
*this_resource_file;
//
// Get a pointer to our resource file so we can find the name of the map
// resource later in this process.
//
Check(application);
this_resource_file = application->GetResourceFile();
Check(this_resource_file);
//
// Print the compare string
//
if(debug_printing)
std::cout<<"compare = "<<master_compare_string<<"\n";
//
// Break the compare string apart, fetch the mission variables for each
// item in it and store them in the compare_strings array for later.
//
token_count = 0;
while(target_string = master_compare_string.GetNthToken(token_count,","))
{
// std::cout<<"Mission variable "<<token_count<<": ";
if(strcmp(target_string,"location") == 0)
{
compare_strings[token_count] = (this_resource_file->FindResourceDescription(mission->GetMapID()))->resourceName;
// std::cout<<target_string<<" = "<<compare_strings[token_count]<<"\n";
}
else if(strcmp(target_string,"time") == 0)
{
compare_strings[token_count] = mission->GetMissionTime();
// std::cout<<target_string<<" = "<<compare_strings[token_count]<<"\n";
}
else if(strcmp(target_string,"weather") == 0)
{
// BT (task #63): BT_WEATHER overrides the egg's authored weather so
// clear/fog/soup fog curtains can be A/B'd without re-authoring eggs
// (the shipped eggs pin weather=clear). Authentic default = the egg.
const char *wov = getenv("BT_WEATHER");
compare_strings[token_count] = (wov && *wov) ? wov : mission->GetMissionWeather();
// std::cout<<target_string<<" = "<<compare_strings[token_count]<<"\n";
}
else if(strcmp(target_string,"scenario") == 0)
{
compare_strings[token_count] = mission->GetScenarioName();
// std::cout<<target_string<<" = "<<compare_strings[token_count]<<"\n";
}
else
{
std::cout<<"Mission variable "<<token_count<<": "<<target_string<<" IS AN UNKNOWN MISSION VARIABLE\n";
}
token_count++;
}
//
// Print the version of the compare string with the actual mission variables
// in it
//
int i;
if(debug_printing)
{
std::cout<<"comparing to: ";
for(i = 0; i<token_count; i++)
{
std::cout<<compare_strings[i]<<", ";
}
std::cout<<"\n";
}
//
// Now compare all the branch pages till we have done them all or found a match
//
branch_pages = master_notation_file->MakeEntryList(starting_page_name,"branch");
Register_Object(branch_pages);
for( entry = branch_pages->GetFirstEntry();
entry;
entry = entry->GetNextEntry())
{
CString
branch_command,
match_string;
int
compare_token;
//
// Get the text of the branch command and print it
//
branch_command = (char *)entry->dataReference;
if(debug_printing)
std::cout<<"branch = "<<branch_command<<"\n";
//
// Grab each token out of the branch string and compare it to the
// corrisponding item in compare_strings. This also checks for the
// wildcard cararcter. Any failure to match skips this branch string.
//
match = True;
for(compare_token = 0; compare_token<token_count; compare_token++)
{
match_string = branch_command.GetNthToken(compare_token,",");
if(match_string != compare_strings[compare_token] && match_string != wild_card)
{
match = False;
// std::cout<<match_string<<"!="<<compare_strings[compare_token]<<"\n";
break;
}
else
{
// std::cout<<match_string<<"=="<<compare_strings[compare_token]<<"\n";
}
}
//
// See if we had a successful match
//
if(match)
{
// Match! Get the name of the page to branch to and do it.
match_string = branch_command.GetNthToken(compare_token,",");
DPLReadINIPage(
master_notation_file,
match_string,
mission,
debug_printing);
if(debug_printing)
std::cout<<"DPLReadINIPage returned to "<<starting_page_name<<"\n";
// now break out of the loop because we only allow one page to match
break;
}
}
Unregister_Object(branch_pages);
delete branch_pages;
//
// If we found no match, yell about it
//
if(!match)
{
DEBUG_STREAM << "BRANCH ERROR IN '" <<
master_notation_file->GetFileName() <<
"' NO MATCH FOUND!!" << std::endl;
}
}
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Read environment
// routine to read in an environment from a notation file and setup lights,
// spfx and such...
//
void
DPLRenderer::DPLReadEnvironment(Mission *mission)
{
//STUBBED: DPL RB 1/14/07
char *L4DPLcfg;
// const char
// *cc;
NotationFile *renderer_environment;
Logical debug_printing;
//
// Get the name of the notation file from an environmental
//
L4DPLcfg = getenv("L4DPLCFG");
if (!L4DPLcfg)
{
DEBUG_STREAM<<"L4DPLCFG not defined, using dpldflt.ini!\n" << std::flush;
L4DPLcfg = "dpldflt.ini";
}
//
// Read the environment out of a notation file
//
renderer_environment = new NotationFile(L4DPLcfg);
Register_Object(renderer_environment);
if (renderer_environment->PageCount() == 0)
{
DEBUG_STREAM<<"Renderer config notation file "<<L4DPLcfg<<"was empty, can't continue\n" << std::flush;
Verify(renderer_environment->PageCount() == 0);
}
//
// Register the notation file and check it
//
Check(renderer_environment);
// BT (task #20): reset the day/night search-path accumulators before reading
// this mission's INI; DPLReadINIPage prepends each objectpath/materialpath/
// texmappath entry as it visits pages (most-specific ends up first).
mObjectPaths.clear();
mMaterialPaths.clear();
mTexmapPaths.clear();
// BT (task #52): LoadMission runs ONCE PER VIEWPOINT-MAKE, not once per
// mission -- MakeAndLinkViewpointEntity (APP.cpp:1265) re-links the renderer
// and re-reads the environment every time a new viewpoint mech is built,
// which the RESPAWN path does. The light block in DPLReadINIPage Fails ("All
// lights must be defined on a single INI file page") when sceneLightCount is
// already non-zero -- true on the second read, since neither the ctor-only
// init nor DPLReadINIPage ever clears it. That invariant is meant to catch
// lights SPLIT ACROSS pages WITHIN one read; across separate reads the state
// must reset. Tear the prior mission's lights down so the re-read is clean.
if (sceneLight != NULL)
{
if (mDevice != NULL)
for (int li = 0; li < sceneLightCount; ++li)
mDevice->LightEnable(li, FALSE);
delete [] sceneLight;
sceneLight = NULL;
}
sceneLightCount = 0;
if (renderer_environment->PageExists("main"))
{
if (!(renderer_environment->GetLogicalEntry("main", "debug", &debug_printing)))
{
debug_printing=False;
}
DPLReadINIPage(renderer_environment, "main", mission, debug_printing);
#if 0
if ((cc = dpl_GetObjectFilePath()) == NULL || *cc == NULL)
{
dpl_SetObjectFilePath ( ".\\video", NULL, NULL );
}
if ((cc = dpl_GetTexmapFilePath()) == NULL || *cc == NULL)
{
dpl_SetTexmapFilePath ( ".\\video", NULL, NULL );
}
if ((cc = dpl_GetMaterialFilePath()) == NULL || *cc == NULL)
{
dpl_SetMaterialFilePath ( ".\\video", NULL, NULL );
}
#endif
}
else
{
if (!(renderer_environment->GetLogicalEntry("dpl_config", "debug", &debug_printing)))
{
debug_printing=False;
}
std::cout<<"READING OLD FORMAT INI FILE '"<<renderer_environment->GetFileName()<<"'---hope this works\n";
// dpl_SetObjectFilePath ( ".\\video", NULL, NULL );
// dpl_SetTexmapFilePath ( ".\\video", NULL, NULL );
// dpl_SetMaterialFilePath ( ".\\video", NULL, NULL );
DPLReadINIPage(renderer_environment, "dpl_config", mission, debug_printing);
}
if (debug_printing)
{
// DEBUG_STREAM << "ObjectFilePath '" << dpl_GetObjectFilePath() << "'" << std::endl << std::flush;
// DEBUG_STREAM << "TexmapFilePath '" << dpl_GetTexmapFilePath() << "'" << std::endl << std::flush;
// DEBUG_STREAM << "MaterialFilePath '" << dpl_GetMaterialFilePath() << "'" << std::endl << std::flush;
}
//
// BT (task #20): push the accumulated day/night search-path priority to the
// BGF/BMF/texture loader. objectpath->.bgf, materialpath->.bmf, texmappath->
// textures. This invalidates the loader's cached indices so the next object
// load (the mission entities, incl. the sky dome) picks the correct
// time-of-day variant of every colliding stem.
//
// Apply the day/night SEARCH-PATH priority for ALL indices (default ON now).
// The mat\day material libs carry the AUTHENTIC per-time-of-day colour RAMPs
// (rock 0.25,0.21,0.16->0.8,0.5,0.4 warm tan) which the loader now bakes into
// the terrain textures -- so preferring mat\day gives the correct warm desert.
// (Earlier BT_MATPRI grayed the terrain ONLY because the ramp wasn't applied:
// mat\day materials have no diffuse and fell back to gray. With the ramp bake
// live they are correct.) BT_MATPRI=0 falls back to first-match (generic
// GEO/*.BMF gray ramps + the vertex tint) for A/B.
SetVideoPathPriority("bgf", mObjectPaths);
{
const char *mp = getenv("BT_MATPRI");
if (mp == 0 || mp[0] != '0')
{
SetVideoPathPriority("bmf", mMaterialPaths);
SetVideoPathPriority("img", mTexmapPaths);
}
}
if (debug_printing)
{
DEBUG_STREAM << "[env] object paths (" << mObjectPaths.size() << "), material paths ("
<< mMaterialPaths.size() << "), texmap paths (" << mTexmapPaths.size()
<< ") -- most-specific first\n" << std::flush;
}
//
// Close the notation file
//
Verify( !renderer_environment->IsDirty() );
Unregister_Object(renderer_environment);
delete renderer_environment;
//
// Flush all the things we've set/changed here
//
// dpl_FlushView(dplMainView);
return;
}
//
//#############################################################################
// Destructor for the video renderer
//#############################################################################
//
DPLRenderer::~DPLRenderer()
{
delete gOpNames;
delete gReplacementData;
if (mPrimaryIndex != NULL)
{
delete mPrimaryIndex;
}
if (mSecondaryIndex != NULL)
{
delete mSecondaryIndex;
}
if (mAux1Index != NULL)
{
delete mAux1Index;
}
if (mAux2Index != NULL)
{
delete mAux2Index;
}
SAFE_RELEASE(mDevice);
SAFE_RELEASE(gD3D);
//STUBBED: DPL RB 1/14/07
//int
// psfx_number;
////------------------------------------------------------
//// release allocated memory used by dump_frame_buffer()
////------------------------------------------------------
//dump_frame_buffer(NULL, NULL, NULL, NULL);
////
//// Delete any memory allocated to hold psfx effect descriptions
////
//for( psfx_number = 0; psfx_number < MAX_PSFX_COUNT; psfx_number++)
//{
// if(myPSFXDescriptons[psfx_number])
// {
// delete myPSFXDescriptons[psfx_number];
// Unregister_Pointer(myPSFXDescriptons[psfx_number]);
// myPSFXDescriptons[psfx_number] = NULL;
// }
//}
////
////~~~~~~~~~~~~~~~~~~
//// Delete all Lights
////~~~~~~~~~~~~~~~~~~
////
//if (ambientLight)
//{
// Check_Pointer(ambientLight);
// Unregister_Pointer(ambientLight);
// dpl_DeleteLight(ambientLight);
//}
//if (sceneLightDCS && sceneLight)
//{
// Check_Pointer(sceneLightDCS);
// for(int ii=0;ii<sceneLightCount;++ii)
// {
// Check_Pointer(sceneLight[ii]);
// Check_Pointer(sceneLightDCS[ii]);
// dpl_RemoveDCSFromScene(sceneLightDCS[ii]);
// dpl_DeleteDCS(sceneLightDCS[ii]);
// Unregister_Pointer(sceneLight[ii]);
// Unregister_Pointer(sceneLightDCS[ii]);
// }
// Unregister_Pointer(sceneLight);
// delete[] sceneLight;
// Unregister_Pointer(sceneLightDCS);
// delete[] sceneLightDCS;
//}
//Unregister_Object(DPLHeap);
//delete DPLHeap;
// DPLHeap = NULL;
}
//
//#############################################################################
// TestInstance
//#############################################################################
//
Logical
DPLRenderer::TestInstance() const
{
VideoRenderer::TestInstance();
Verify(dplMainView != NULL);
Verify(dplMainZone != NULL);
return True;
}
//
//#############################################################################
// FlushBitSliceTexture
// Sends the bitslice texture to the division card.
// LoadBitSliceTexture
// Handles building of a bitmap into a texture map for the Division card, this
// specifically deals with merging a 1 bit bitmap into a 4 bit bitslice texmap.
//#############################################################################
//
void
DPLRenderer::FlushBitSliceTexture(
unsigned int *local_storage)
{
//STUBBED: DPL RB 1/14/07
//int
// status;
//dpl_TEXTURE *temp_texture =
// dpl_LookupTexture("bmap:bmap1_tex", dpl_lookup_normal, &status);
//if(temp_texture == NULL)
//{
// DEBUG_STREAM<<"WARNING, textures for player names not defined\n" << std::flush;
//}
//else
//{
// dpl_TEXMAP *temp_texmap =
// dpl_GetTextureTexmap(temp_texture);
// if(temp_texmap != NULL)
// {
// dpl_TexmapTexels2D ( temp_texmap, local_storage,
// 128,
// 64,
// 4 );
// dpl_FlushTexmap(temp_texmap);
// }
// else
// {
// DEBUG_STREAM<<"WARNING, texmap for player names not defined\n" << std::flush;
// }
//}
}
void DPLRenderer::LoadBitSliceTexture(BitMap *bitmap_to_load, LPDIRECT3DTEXTURE9 local_storage)
{
// lock the texture and grab a pointer to the data
D3DLOCKED_RECT lockedRect;
local_storage->LockRect(0, &lockedRect, NULL, D3DLOCK_DISCARD);
//
// Figure out the position to start copying the data at
//
Word *word_pointer = bitmap_to_load->Data.MapPointer;
__int16 *pixel_pointer = (__int16*)lockedRect.pBits;
//
// Actually process the texels
//
int bit_mask = 0x8000;
for(int y = 0; y < 32; y++)
{
for(int x = 0; x < 128; x++)
{
if (*word_pointer & bit_mask)
*(pixel_pointer++) = 0xFFFF;
else
*(pixel_pointer++) = 0x0000;
bit_mask >>= 1;
if (bit_mask == 0)
{
bit_mask = 0x8000;
word_pointer++;
}
}
}
// unlock the texture now that we're done updating it
local_storage->UnlockRect(0);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// CacheExplosionScripts
// Handles code to preload the morph and other caches with things needed by
// explosion scripts. Should be called at startup, once for each.
//
void
DPLRenderer::CacheExplosionScripts(
int script_select) // The script to be cached
{
//STUBBED: DPL RB 1/14/07
// dpl_OBJECT
// *object;
// //
// // Load the morph targets into the MUNGA object cache
// //
// switch(script_select)
// {
// case 104: // Thor death explosion
// case 106: // Thor death explosion
// {
// dpl_LoadObject("flamebig.bgf", dpl_load_normal);
// dpl_LoadObject("thrdbr.bgf" , dpl_load_normal);
// dpl_LoadObject("ldbr.bgf" , dpl_load_normal);
// dpl_LoadObject("flamesml.bgf", dpl_load_normal);
//// DEBUG_STREAM << "explosion "<< script_select<<" cached\n" << std::flush;
// break;
// }
// }
}
//
//#############################################################################
// ExplosionScripts
// Contains several scripts for running various types of explosion effects.
//#############################################################################
//
void
DPLRenderer::ExplosionScripts(
Entity *entity, // The entity we are dealing with
ResourceDescription* ,//model_resource, // Pointer to the video resource
ViewFrom ,//view_type, // Type of reference (inside/outside...etc.)
int script_select)
{
//STUBBED: DPL RB 1/14/07
// dpl_OBJECT
// *object_1;
// //
// // Construct a root renderable to hang the explosion effects on
// //
// RootRenderable *this_root =
// new RootRenderable(
// entity, // Entity to attach the renderable to
// RootRenderable::Dynamic, // How/when to execute the renderable
// NULL, // object to hang on the DCS, may be a list later <NULL>
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL); // intersection mask for the object
// Register_Object(this_root);
// //
// // Select which script to construct
// //
// switch(script_select)
// {
// case 0: // Shock wave
// {
// //
// // Load object(s) we will be using for the explosions
// //
// object_1 = dpl_LoadObject("shock.bgf", dpl_load_normal);
// //
// // Setup control variables and transforms we need
// //
// Vector3D scaling_velocity_1(3.5, 3.5, 3.5);
// Vector3D velocity_accel_1(0.0, 0.0, 0.0);
// LinearMatrix explosion_1_offset(True);
// //
// // Create the scaling explosion renderables
// //
// ScalingExplosionRenderable *explosion_1 =
// new ScalingExplosionRenderable(
// entity, // Entity to attach the renderable to
// ScalingExplosionRenderable::Dynamic, // How/when to execute the renderable
// object_1, // This will be the scaling explosion object
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS(), // the parent DCS we will be offsetting from
// &explosion_1_offset, // offset matrix to be applied prior to joint DCS
// &scaling_velocity_1, // Effect control vector, Y is acceleration, X, Z are velocity
// &velocity_accel_1,
// 0.0f,
// NULL); // -.25 gravity is normal
// Register_Object(explosion_1);
// //
// // Create a sweep renderable to drive the material morph
// //
// SweepRenderable *sweep_1 =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 0.5f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// NULL);
// Register_Object(sweep_1);
// //
// // Lookup a texture that we can use for the morphing materials
// //
// int
// status;
// dpl_TEXTURE *effect_texture =
// dpl_LookupTexture ( "btfx:firesmoke1_scr_tex", dpl_lookup_normal, &status );
// if (effect_texture == NULL)
// DEBUG_STREAM<<"couldn't find texture btfx:firesmoke1_scr_tex for an effect\n" << std::flush;
// //
// // Setup a morph material renderable for each explosion shape
// //
// MorphMaterialRenderable *morph_material_1 =
// new MorphMaterialRenderable(
// entity, // Entity to attach the renderable to
// MorphMaterialRenderable::Dynamic, // How/when to execute the renderable
// 1.0f,1.0f,1.0f, // Material's ambient component
// 1.0f,1.0f,1.0f, // Material's emissive component
// 1.0f,1.0f,1.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 4.0f,0.2f,-0.05f, // Material's opacity
// effect_texture, // Material's texture pointer
// 0.0f, // Material's Z dither value
// 0, // Material's Fog Imunity value
// 0.5f,0.5f,0.5f, // Material's ambient component
// 0.5f,0.5f,0.5f, // Material's emissive component
// 0.5f,0.5f,0.5f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
//// 0.25f,0.25f,-0.05f, // Material's opacity
// 0.1f,0.1f,-0.05f, // Material's opacity
// 0.0f, // Material's Z dither value
// sweep_1->GetSweepAttribute());
// Register_Object(morph_material_1);
// dpl_INSTANCE *explosion_1_instance = dpl_GetDCSInstance(explosion_1->GetDCS(), 1);
// if(!explosion_1_instance)
// Fail("explosion_1_instance came back null\n");
// dpl_SetInstanceFrontMaterial(explosion_1_instance, morph_material_1->GetMaterial());
// dpl_FlushInstance(explosion_1_instance);
// break;
// }
// case 1: // Big explosion
// {
// //
// // Load object(s) we will be using for the explosions
// //
// object_1 = dpl_LoadObject("exp.bgf", dpl_load_normal);
// //
// // Setup control variables and transforms we need
// //
// Vector3D scaling_velocity_1(0.15, 0.15, 0.15);
// Vector3D scaling_velocity_2(0.18, 0.22, 0.18);
// Vector3D velocity_accel_1(0.0, 0.0, 0.0);
// Vector3D velocity_accel_2(0.0, 0.0, 0.0);
// LinearMatrix explosion_1_offset(True);
// LinearMatrix explosion_2_offset(True);
// Point3D explosion_2_translate(0.0f, 0.0f, 0.0f);
// EulerAngles explosion_2_rotate(0.0f, 1.0f, 0.0f);
// explosion_2_offset = explosion_2_rotate;
// explosion_2_offset = explosion_2_translate;
// //
// // Create the scaling explosion renderables
// //
// ScalingExplosionRenderable *explosion_1 =
// new ScalingExplosionRenderable(
// entity, // Entity to attach the renderable to
// ScalingExplosionRenderable::Dynamic, // How/when to execute the renderable
// object_1, // This will be the scaling explosion object
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS(), // the parent DCS we will be offsetting from
// &explosion_1_offset, // offset matrix to be applied prior to joint DCS
// &scaling_velocity_1, // Effect control vector, Y is acceleration, X, Z are velocity
// &velocity_accel_1,
// 0.0f,
// NULL); // -.25 gravity is normal
// Register_Object(explosion_1);
// ScalingExplosionRenderable *explosion_2 =
// new ScalingExplosionRenderable(
// entity, // Entity to attach the renderable to
// ScalingExplosionRenderable::Dynamic, // How/when to execute the renderable
// object_1, // This will be the scaling explosion object
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS(), // the parent DCS we will be offsetting from
// &explosion_2_offset, // offset matrix to be applied prior to joint DCS
// &scaling_velocity_2, // Effect control vector, Y is acceleration, X, Z are velocity
// &velocity_accel_2,
// 0.0f,
// NULL);
// Register_Object(explosion_2);
// //
// // Create a sweep renderable to drive the material morph
// //
// SweepRenderable *sweep_1 =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 3.0f,
// 1, // number of times to cycle before stopping
// NULL); // How long to take to sweep from 0 to 1
// Register_Object(sweep_1);
// SweepRenderable *sweep_2 =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 2.0f,
// 1, // number of times to cycle before stopping
// NULL); // How long to take to sweep from 0 to 1
// Register_Object(sweep_2);
// //
// // Lookup a texture that we can use for the morphing materials
// //
// int
// status;
// dpl_TEXTURE *effect_texture =
// dpl_LookupTexture ( "btfx:firesmoke1_scr_tex", dpl_lookup_normal, &status );
// if (effect_texture == NULL)
// DEBUG_STREAM<<"couldn't find texture btfx:firesmoke1_scr_tex for an effect\n" << std::flush;
// //
// // Setup a morph material renderable for each explosion shape
// //
// #if 0
// 0.9f,0.0f,0.0f, // Material's ambient component
// 1.0f,1.0f,0.0f, // Material's emissive component
// 1.0f,0.7f,0.2f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 8.0f,0.2f,-0.05f, // Material's opacity
// #endif
// MorphMaterialRenderable *morph_material_1 =
// new MorphMaterialRenderable(
// entity, // Entity to attach the renderable to
// MorphMaterialRenderable::Dynamic, // How/when to execute the renderable
// 0.9f,0.0f,0.0f, // Material's ambient component
// 1.0f,0.2f,0.0f, // Material's emissive component
// 1.0f,0.2f,0.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 10.0f,0.6f,-0.05f, // Material's opacity
// effect_texture, // Material's texture pointer
// 0.0f, // Material's Z dither value
// 0, // Material's Fog Imunity value
// 0.3f,0.0f,0.0f, // Material's ambient component
// 0.5f,0.0f,0.0f, // Material's emissive component
// 0.2f,0.0f,0.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 0.25f,0.25f,-0.05f, // Material's opacity
// 15.0f, // Material's Z dither value
// sweep_1->GetSweepAttribute());
// Register_Object(morph_material_1);
// MorphMaterialRenderable *morph_material_2 =
// new MorphMaterialRenderable(
// entity, // Entity to attach the renderable to
// MorphMaterialRenderable::Dynamic, // How/when to execute the renderable
// 0.9f,0.0f,0.0f, // Material's ambient component
// 1.0f,0.6f,0.3f, // Material's emissive component
// 1.0f,0.4f,0.2f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 8.0f,0.2f,-0.05f, // Material's opacity
// effect_texture, // Material's texture pointer
// 0.0f, // Material's Z dither value
// 0, // Material's Fog Imunity value
// 0.1f,0.0f,0.0f, // Material's ambient component
// 0.0f,0.0f,0.0f, // Material's emissive component
// 0.1f,0.0f,0.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 0.25f,0.25f,-0.05f, // Material's opacity
// 15.0f, // Material's Z dither value
// sweep_2->GetSweepAttribute());
// Register_Object(morph_material_2);
// //
// // Override the materials in the two explosion shapes using these morph materials
// //
// dpl_INSTANCE *explosion_1_instance = dpl_GetDCSInstance(explosion_1->GetDCS(), 1);
// dpl_INSTANCE *explosion_2_instance = dpl_GetDCSInstance(explosion_2->GetDCS(), 1);
//
// if(!explosion_1_instance)
// Fail("explosion_1_instance came back null\n");
// if(!explosion_2_instance)
// Fail("explosion_2_instance came back null\n");
//
// dpl_SetInstanceFrontMaterial(explosion_1_instance, morph_material_1->GetMaterial());
// dpl_SetInstanceFrontMaterial(explosion_2_instance, morph_material_2->GetMaterial());
//
// dpl_FlushInstance(explosion_1_instance);
// dpl_FlushInstance(explosion_2_instance);
// break;
// }
// case 2: // Big explosion
// {
// //
// // Load object(s) we will be using for the explosions
// //
// object_1 = dpl_LoadObject("exp.bgf", dpl_load_normal);
// //
// // Setup control variables and transforms we need
// //
// Vector3D scaling_velocity_1(1.0, 0.2, 1.0);
// Vector3D scaling_velocity_2(0.75, 0.75, 0.75);
// Vector3D velocity_accel_1(-0.00725, -0.004, -0.00725);
//// Vector3D velocity_accel_1(-0.00925, -0.00925, -0.00925);
// Vector3D velocity_accel_2(-0.01388, -0.01388, -0.01388);
// LinearMatrix explosion_1_offset(True);
// LinearMatrix explosion_2_offset(True);
// Point3D explosion_2_translate(0.0f, 0.0f, 0.0f);
// EulerAngles explosion_2_rotate(0.0f, 1.0f, 0.0f);
// explosion_2_offset = explosion_2_rotate;
// explosion_2_offset = explosion_2_translate;
// //
// // Create the scaling explosion renderables
// //
// ScalingExplosionRenderable *explosion_1 =
// new ScalingExplosionRenderable(
// entity, // Entity to attach the renderable to
// ScalingExplosionRenderable::Dynamic, // How/when to execute the renderable
// object_1, // This will be the scaling explosion object
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS(), // the parent DCS we will be offsetting from
// &explosion_1_offset, // offset matrix to be applied prior to joint DCS
// &scaling_velocity_1, // Effect control vector, Y is acceleration, X, Z are velocity
// &velocity_accel_1,
// 0.0f,
// NULL); // -.25 gravity is normal
// Register_Object(explosion_1);
// ScalingExplosionRenderable *explosion_2 =
// new ScalingExplosionRenderable(
// entity, // Entity to attach the renderable to
// ScalingExplosionRenderable::Dynamic, // How/when to execute the renderable
// object_1, // This will be the scaling explosion object
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS(), // the parent DCS we will be offsetting from
// &explosion_2_offset, // offset matrix to be applied prior to joint DCS
// &scaling_velocity_2, // Effect control vector, Y is acceleration, X, Z are velocity
// &velocity_accel_2,
// 0.0f,
// NULL);
// Register_Object(explosion_2);
// //
// // Create a sweep renderable to drive the material morph
// //
// SweepRenderable *sweep_1 =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 3.0f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// NULL);
// Register_Object(sweep_1);
// SweepRenderable *sweep_2 =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 2.0f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// NULL);
// Register_Object(sweep_2);
// //
// // Lookup a texture that we can use for the morphing materials
// //
// int
// status;
// dpl_TEXTURE *effect_texture =
// dpl_LookupTexture ( "btfx:firesmoke1_scr_tex", dpl_lookup_normal, &status );
// if (effect_texture == NULL)
// DEBUG_STREAM<<"couldn't find texture btfx:firesmoke1_scr_tex for an effect\n" << std::flush;
// //
// // Setup a morph material renderable for each explosion shape
// //
// #if 0
// 0.9f,0.0f,0.0f, // Material's ambient component
// 1.0f,1.0f,0.0f, // Material's emissive component
// 1.0f,0.7f,0.2f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 8.0f,0.2f,-0.05f, // Material's opacity
// #endif
// MorphMaterialRenderable *morph_material_1 =
// new MorphMaterialRenderable(
// entity, // Entity to attach the renderable to
// MorphMaterialRenderable::Dynamic, // How/when to execute the renderable
// 0.9f,0.0f,0.0f, // Material's ambient component
// 1.0f,0.2f,0.0f, // Material's emissive component
// 1.0f,0.2f,0.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 8.0f,0.2f,-0.05f, // Material's opacity
// effect_texture, // Material's texture pointer
// 0.0f, // Material's Z dither value
// 0, // Material's Fog Imunity value
// 0.3f,0.0f,0.0f, // Material's ambient component
// 0.5f,0.0f,0.0f, // Material's emissive component
// 0.2f,0.0f,0.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 0.25f,0.25f,-0.05f, // Material's opacity
// 15.0f, // Material's Z dither value
// sweep_1->GetSweepAttribute());
// Register_Object(morph_material_1);
// MorphMaterialRenderable *morph_material_2 =
// new MorphMaterialRenderable(
// entity, // Entity to attach the renderable to
// MorphMaterialRenderable::Dynamic, // How/when to execute the renderable
// 0.9f,0.0f,0.0f, // Material's ambient component
// 1.0f,0.6f,0.4f, // Material's emissive component
// 1.0f,0.3f,0.2f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 8.0f,0.2f,-0.05f, // Material's opacity
// effect_texture, // Material's texture pointer
// 0.0f, // Material's Z dither value
// 0, // Material's Fog Imunity value
// 0.1f,0.0f,0.0f, // Material's ambient component
// 0.0f,0.0f,0.0f, // Material's emissive component
// 0.0f,0.0f,0.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 0.25f,0.25f,-0.05f, // Material's opacity
// 15.0f, // Material's Z dither value
// sweep_2->GetSweepAttribute());
// Register_Object(morph_material_2);
// //
// // Override the materials in the two explosion shapes using these morph materials
// //
// dpl_INSTANCE *explosion_1_instance = dpl_GetDCSInstance(explosion_1->GetDCS(), 1);
// dpl_INSTANCE *explosion_2_instance = dpl_GetDCSInstance(explosion_2->GetDCS(), 1);
//
// if(!explosion_1_instance)
// Fail("explosion_1_instance came back null\n");
// if(!explosion_2_instance)
// Fail("explosion_2_instance came back null\n");
//
// dpl_SetInstanceFrontMaterial(explosion_1_instance, morph_material_1->GetMaterial());
// dpl_SetInstanceFrontMaterial(explosion_2_instance, morph_material_2->GetMaterial());
//
// dpl_FlushInstance(explosion_1_instance);
// dpl_FlushInstance(explosion_2_instance);
// break;
// }
// case 3: // Sparks
// {
// //
// // Load object(s) we will be using for the explosions
// //
// object_1 = dpl_LoadObject("spk1.bgf", dpl_load_normal);
// //
// // Setup control variables and transforms we need
// //
// Vector3D scaling_velocity_1(0.1, 0.1, 0.1);
// Vector3D velocity_accel_1(0.0, 0.0, 0.0);
// LinearMatrix explosion_1_offset(True);
// //
// // Create the scaling explosion renderables
// //
// ScalingExplosionRenderable *explosion_1 =
// new ScalingExplosionRenderable(
// entity, // Entity to attach the renderable to
// ScalingExplosionRenderable::Dynamic, // How/when to execute the renderable
// object_1, // This will be the scaling explosion object
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS(), // the parent DCS we will be offsetting from
// &explosion_1_offset, // offset matrix to be applied prior to joint DCS
// &scaling_velocity_1, // Effect control vector, Y is acceleration, X, Z are velocity
// &velocity_accel_1,
// -0.15f,
// NULL); // -.25 gravity is normal
// Register_Object(explosion_1);
// //
// // Create a sweep renderable to drive the material morph
// //
// SweepRenderable *sweep_1 =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 0.5f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// NULL);
// Register_Object(sweep_1);
// //
// // Lookup a texture that we can use for the morphing materials
// //
// int
// status;
// dpl_TEXTURE *effect_texture =
// dpl_LookupTexture ( "btfx:firesmoke1_scr_tex", dpl_lookup_normal, &status );
// if (effect_texture == NULL)
// DEBUG_STREAM<<"couldn't find texture btfx:firesmoke1_scr_tex for an effect\n" << std::flush;
// //
// // Setup a morph material renderable for each explosion shape
// //
// MorphMaterialRenderable *morph_material_1 =
// new MorphMaterialRenderable(
// entity, // Entity to attach the renderable to
// MorphMaterialRenderable::Dynamic, // How/when to execute the renderable
// 1.0f,1.0f,0.0f, // Material's ambient component
// 1.0f,1.0f,0.0f, // Material's emissive component
// 1.0f,1.0f,0.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 1.0f,1.0f,1.0f, // Material's opacity
// effect_texture, // Material's texture pointer
// 0.0f, // Material's Z dither value
// 2, // Material's Fog Imunity value
// 0.9f,0.0f,0.0f, // Material's ambient component
// 0.9f,0.0f,0.0f, // Material's emissive component
// 0.9f,0.0f,0.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0, // Material's specular component
// 1.0f,1.0f,1.0f, // Material's opacity
// 0.0f, // Material's Z dither value
// sweep_1->GetSweepAttribute());
// Register_Object(morph_material_1);
// dpl_INSTANCE *explosion_1_instance = dpl_GetDCSInstance(explosion_1->GetDCS(), 1);
// if(!explosion_1_instance)
// Fail("explosion_1_instance came back null\n");
// dpl_SetInstanceFrontMaterial(explosion_1_instance, morph_material_1->GetMaterial());
// dpl_FlushInstance(explosion_1_instance);
// break;
// }
// case 4: // A huge mech explodes (yawn)
// {
// Point3D null_offset(0.0f, 0.0f, 0.0f);
// LinearMatrix null_offset_matrix(True);
// Point3D local_height(0.0f, 6.2f, 0.0f); // height of Thor
// LinearMatrix local_offset(True);
// local_offset = local_height;
// DPLStaticChildRenderable *local_root =
// new DPLStaticChildRenderable(
// entity,
// false,
// NULL,
// dpl_isect_mode_obj,
// NULL,
// local_offset,
// this_root->GetDCS());
// Register_Object(local_root);
// //
// // Load morph sources we need for the effect
// //
// dpl_OBJECT *thor_debris_object = dpl_LoadObject("thrdbr.bgf", dpl_load_normal);
// dpl_OBJECT *large_debris_object = dpl_LoadObject("ldbr.bgf", dpl_load_normal);
// dpl_OBJECT *small_flames_object = dpl_LoadObject("flamesml.bgf", dpl_load_normal);
// //
// // Load morph targets we need for the effect
// //
// //------------------------------------------------
// // Renderables to handle the chunks and fireballs
// //------------------------------------------------
//#if 0
// OneShotDelayRenderable *fireball_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.25f); // How long to wait before raising the trigger
// Register_Object(fireball_delay);
// Point3D my_offset(0.0, 4.0, 0.0);
// #if DEBUG_LEVEL > 0
// DPLPSFXRenderable* initial_boom =
// #endif
// new DPLPSFXRenderable(
// entity, // Entity to attach the renderable to
// DPLPSFXRenderable::Dynamic, // How/when to execute the renderable
// fireball_delay->GetTriggerAttribute(), // address containing the trigger
// myPSFXDescriptons[7], // pointer to the PFX description
// this_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &my_offset); // Offset (or world coordinants if DCS is NULL)
// Register_Object(initial_boom);
// #if DEBUG_LEVEL > 0
// DPLEffectRenderable *chunks_effect =
// #endif
// new DPLEffectRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// fireball_delay->GetTriggerAttribute(), // address containing the trigger
// 3, // Chunks // DPL effect number to trigger
// local_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &null_offset); // Offset (or world coordinants if DCS is NULL)
// Register_Object(chunks_effect);
// Point3D fireball_offset(1.0f, 5.0f, 1.0f);
// #if DEBUG_LEVEL > 0
// DPLEffectRenderable *fireball_effect =
// #endif
// new DPLEffectRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// fireball_delay->GetTriggerAttribute(), // address containing the trigger
// 15, // Fireball // DPL effect number to trigger
// local_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &fireball_offset); // Offset (or world coordinants if DCS is NULL)
// Register_Object(fireball_effect);
//#endif
// OneShotDelayRenderable *fireball_delay_2 =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.55f); // How long to wait before raising the trigger
// Register_Object(fireball_delay_2);
// Point3D fireball_offset_2(-1.0f, 5.0f, -1.0f);
// #if DEBUG_LEVEL > 0
// DPLEffectRenderable *fireball_effect_2 =
// #endif
// new DPLEffectRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// fireball_delay_2->GetTriggerAttribute(), // address containing the trigger
// 15, // Fireball // DPL effect number to trigger
// local_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &fireball_offset_2); // Offset (or world coordinants if DCS is NULL)
// Register_Object(fireball_effect_2);
// //-----------------------------------------------
// // Renderables to handle static debris
// //-----------------------------------------------
// OneShotDelayRenderable *static_debris_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.25f); // How long to wait before raising the trigger
// Register_Object(static_debris_delay);
// DPLStaticChildRenderable *mech_debris =
// new DPLStaticChildRenderable(
// entity,
// false,
// thor_debris_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// this_root->GetDCS());
// Register_Object(mech_debris);
// #if DEBUG_LEVEL > 0
// MakeDCSFall *mech_debris_fall =
// #endif
// new MakeDCSFall(
// entity, // Entity to attach the renderable to
// MakeDCSFall::Dynamic, // How/when to execute the renderable
// mech_debris->GetDCS(), // the DCS to control
// -0.025f, // Gravity in meters/sec squared
// static_debris_delay->GetTriggerAttribute()); // true if the instance is on, false if off
// Register_Object(mech_debris_fall);
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *mech_debris_instance = dpl_GetDCSInstance(mech_debris->GetDCS(), 1);
// #if DEBUG_LEVEL > 0
// InstanceSwitchRenderable *mech_debris_instance_switch =
// #endif
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// mech_debris_instance, // the instance to control
// True, // Instance is on when trigger is...
// static_debris_delay->GetTriggerAttribute());
// Register_Object(mech_debris_instance_switch);
//
// DPLStaticChildRenderable *large_debris =
// new DPLStaticChildRenderable(
// entity,
// false,
// large_debris_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// mech_debris->GetDCS());
// Register_Object(large_debris);
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *large_debris_instance = dpl_GetDCSInstance(large_debris->GetDCS(), 1);
// #if DEBUG_LEVEL > 0
// InstanceSwitchRenderable *large_debris_instance_switch =
// #endif
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// large_debris_instance, // the instance to control
// True, // Instance is on when trigger is...
// static_debris_delay->GetTriggerAttribute());
// Register_Object(large_debris_instance_switch);
// //-----------------------------------------------
// // Renderables to handle fires
// //-----------------------------------------------
// OneShotDelayRenderable *fires_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.25f); // How long to wait before raising the trigger
// Register_Object(fires_delay);
// DPLStaticChildRenderable *fires =
// new DPLStaticChildRenderable(
// entity,
// false,
// NULL,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// this_root->GetDCS());
// Register_Object(fires);
// #if DEBUG_LEVEL > 0
// MakeDCSFall *fires_fall =
// #endif
// new MakeDCSFall(
// entity, // Entity to attach the renderable to
// MakeDCSFall::Dynamic, // How/when to execute the renderable
// fires->GetDCS(), // the DCS to control
// -0.01f, // Gravity in meters/sec squared
// fires_delay->GetTriggerAttribute()); // true if the instance is on, false if off
// Register_Object(fires_fall);
// DPLStaticChildRenderable *small_flames =
// new DPLStaticChildRenderable(
// entity,
// false,
// small_flames_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// fires->GetDCS());
// Register_Object(small_flames);
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *small_flames_instance = dpl_GetDCSInstance(small_flames->GetDCS(), 1);
// #if DEBUG_LEVEL > 0
// InstanceSwitchRenderable *small_flames_instance_switch =
// #endif
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// small_flames_instance, // the instance to control
// True, // Instance is on when trigger is...
// fires_delay->GetTriggerAttribute());
// Register_Object(small_flames_instance_switch);
//
// dpl_OBJECT *big_flames_object = dpl_LoadObject("flamebig.bgf", dpl_load_normal);
// DPLStaticChildRenderable *big_flames =
// new DPLStaticChildRenderable(
// entity,
// false,
// big_flames_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// fires->GetDCS());
// Register_Object(big_flames);
// // Make big flames object billboard along y-axis
// dpl_SetDCSReorientAxes(big_flames->GetDCS(), dpl_reorient_axes_y);
// dpl_FlushDCS(big_flames->GetDCS());
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *big_flames_instance = dpl_GetDCSInstance(big_flames->GetDCS(), 1);
// #if DEBUG_LEVEL > 0
// InstanceSwitchRenderable *big_flames_instance_switch =
// #endif
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// big_flames_instance, // the instance to control
// True, // Instance is on when trigger is...
// fires_delay->GetTriggerAttribute());
// Register_Object(big_flames_instance_switch);
// //
// // These renderables create the rising smoke column using a pfx effect.
// //
//#if 0
// Point3D my_offset2(0.0, 5.0, 0.0);
// #if DEBUG_LEVEL > 0
// OnePSFXRenderable *this_effect=
// #endif
// new OnePSFXRenderable(
// entity, // Entity to attach the renderable to
// OnePSFXRenderable::Static, // How/when to execute the renderable
// myPSFXDescriptons[1], // name of file with the PFX description in it
// this_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &my_offset2); // Offset (or world coordinants if DCS is NULL)
// Register_Object(this_effect);
//#endif
// break;
// }
// //----------------------------------------
// case 5: // Sfx #105 - Delayed ground hit
// //----------------------------------------
// {
// //
// // Create the delayed ground hit renderables
// //
// Point3D null_offset(0.0f, 0.0f, 0.0f);
// OneShotDelayRenderable *ground_hit_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.15f); // How long to wait before raising the trigger
// Register_Object(ground_hit_delay);
// #if DEBUG_LEVEL > 0
// DPLEffectRenderable *ground_hit_effect =
// #endif
// new DPLEffectRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// ground_hit_delay->GetTriggerAttribute(), // address containing the trigger
// 12, // GroundHit // DPL effect number to trigger
// this_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &null_offset); // Offset (or world coordinants if DCS is NULL)
// Register_Object(ground_hit_effect);
// break;
// }
// //------------------------------------------
// case 6: // Sfx #106 - Thor death explosion
// //------------------------------------------
// {
// #if 0
// int status; // used with dpl calls
// Point3D null_offset(0.0f, 0.0f, 0.0f);
// LinearMatrix null_offset_matrix(True);
// Point3D local_height(0.0f, 6.2f, 0.0f); // height of Thor
// LinearMatrix local_offset(True);
// local_offset = local_height;
// DPLStaticChildRenderable *local_root =
// new DPLStaticChildRenderable(
// entity,
// false,
// NULL,
// dpl_isect_mode_obj,
// NULL,
// local_offset,
// this_root->GetDCS());
// Register_Object(local_root);
//#if 0
// //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// //
// // Show static thor (TEMPORARY for testing)
// //
// dpl_OBJECT *static_thr_object = dpl_LoadObject("thr.bgf", dpl_load_normal);
// DPLStaticChildRenderable *static_thr =
// new DPLStaticChildRenderable(
// entity,
// false,
// static_thr_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// local_root->GetDCS());
// Register_Object(static_thr);
// // This defines how long the static_thr stays visible
// OneShotDelayRenderable *static_thr_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.5f); // How long to wait before raising the trigger
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *static_thr_instance = dpl_GetDCSInstance(static_thr->GetDCS(), 1);
// InstanceSwitchRenderable *static_thr_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// static_thr_instance, // the instance to control
// False, // Instance is on when trigger is...
// static_thr_delay->GetTriggerAttribute());
// //
// //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//#endif
// //
// // Load objects we will be using for the explosion effect
// //
// DPLObjectWrapper *flash_object_wrapper =
// new DPLObjectWrapper(
// entity, // Entity to attach the renderable to
// "exdisk_A.bgf", // Name of the DPL object to load into the wrapper
// dpl_load_nocache); // Type of loading to perform on this object
// Register_Object(flash_object_wrapper);
// dpl_OBJECT *flash_object_a = flash_object_wrapper->GetDPLObject();
// dpl_OBJECT *flash_object_b = dpl_LoadObject("exdisk_B.bgf", dpl_load_normal);
// dpl_OBJECT *flash_object_c = dpl_LoadObject("exdisk_C.bgf", dpl_load_normal);
// DPLObjectWrapper *debris_object_wrapper =
// new DPLObjectWrapper(
// entity, // Entity to attach the renderable to
// "thrtor_A.bgf", // Name of the DPL object to load into the wrapper
// dpl_load_nocache); // Type of loading to perform on this object
// Register_Object(debris_object_wrapper);
// dpl_OBJECT *morph_debris_a = debris_object_wrapper->GetDPLObject();
// dpl_OBJECT *morph_debris_b = dpl_LoadObject("thrtor_B.bgf", dpl_load_normal);
// dpl_OBJECT *morph_debris_c = dpl_LoadObject("thrtor_C.bgf", dpl_load_normal);
// DPLObjectWrapper *hips_object_wrapper =
// new DPLObjectWrapper(
// entity, // Entity to attach the renderable to
// "thrhip_A.bgf", // Name of the DPL object to load into the wrapper
// dpl_load_nocache); // Type of loading to perform on this object
// Register_Object(hips_object_wrapper);
// dpl_OBJECT *morph_hips_a = hips_object_wrapper->GetDPLObject();
// dpl_OBJECT *morph_hips_b = dpl_LoadObject("thrhip_B.bgf", dpl_load_normal);
// dpl_OBJECT *morph_hips_c = dpl_LoadObject("thrhip_C.bgf", dpl_load_normal);
// dpl_OBJECT *thor_debris_object = dpl_LoadObject("thrdbr.bgf", dpl_load_normal);
// dpl_OBJECT *large_debris_object = dpl_LoadObject("ldbr.bgf", dpl_load_normal);
// dpl_OBJECT *small_flames_object = dpl_LoadObject("flamesml.bgf", dpl_load_normal);
// dpl_OBJECT *big_flames_object = dpl_LoadObject("flamebig.bgf", dpl_load_normal);
// //----------------------------------------------
// // Renderables to perform morphing flash object
// //----------------------------------------------
// SweepRenderable *flash_morph_sweep =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 1.0f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// NULL, // trigger
// -0.5f, // Initial value
// 2.0f); // Final value
// ChildMorphRenderable *flash_morph =
// new ChildMorphRenderable(
// entity, // Entity to attach the renderable to
// ChildMorphRenderable::Dynamic, // How/when to execute the renderable
// flash_object_a, // destination
// flash_object_b, // start object
// flash_object_c, // end object
// flash_morph_sweep->GetSweepAttribute(), // pointer to control variable
// dpl_morph_vertices | dpl_morph_colors, // Defines type of morph to do
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS()); // the parent DCS we will be offsetting from
// // Make flash object billboard along y-axis
// dpl_SetDCSReorientAxes(flash_morph->GetDCS(), dpl_reorient_axes_y);
// dpl_FlushDCS(flash_morph->GetDCS());
// // This defines how long the flash stays up
// OneShotDelayRenderable *flash_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 1.0f); // How long to wait before raising the trigger
// Register_Object(flash_delay);
// // Find the instance with the flash in it and hook up the instance switch
// dpl_INSTANCE *flash_morph_instance = dpl_GetDCSInstance(flash_morph->GetDCS(), 1);
// InstanceSwitchRenderable *flash_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// flash_morph_instance, // the instance to control
// False, // Instance is on when trigger is...
// flash_delay->GetTriggerAttribute());
// //---------------------------------------------------
// // Create a material morph to fade flash object away
// //---------------------------------------------------
// OneShotDelayRenderable *flash_fade_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.5f); // How long to wait before raising the trigger
// SweepRenderable *flash_fade_sweep =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 0.4f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// flash_fade_delay->GetTriggerAttribute()); // trigger variable
// Register_Object(flash_fade_sweep);
// // Lookup texture for smoke column
// dpl_TEXTURE *flash_texture =
// dpl_LookupTexture("btfx:bexp9_tex", dpl_lookup_normal, &status);
// if (flash_texture == NULL)
// DEBUG_STREAM<<"couldn't find texture btfx:bexp9_tex for an effect\n" << std::flush;
// //
// MorphMaterialRenderable *flash_fade_material =
// new MorphMaterialRenderable(
// entity, // Entity to attach the renderable to
// MorphMaterialRenderable::Dynamic, // How/when to execute the renderable
// 1.0f,1.0f,1.0f, // Material's ambient component
// 1.0f,1.0f,1.0f, // Material's emissive component
// 1.0f,1.0f,1.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0f, // Material's specular component
// 1.0f,1.0f,1.0f, // Material's opacity
// flash_texture, // Material's texture pointer
// 0.0f, // Material's Z dither value
// 1, // Material's Fog Imunity value
// 1.0f,1.0f,1.0f, // Material's ambient component
// 1.0f,1.0f,1.0f, // Material's emissive component
// 1.0f,1.0f,1.0f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0f, // Material's specular component
// 0.0f,0.0f,0.0f, // Material's opacity
// 0.0f, // Material's Z dither value
// flash_fade_sweep->GetSweepAttribute());
// Register_Object(flash_fade_material);
//// dpl_INSTANCE *flash_morph_instance = dpl_GetDCSInstance(flash_morph->GetDCS(), 1);
// if (!flash_morph_instance)
// Fail("flash_morph_instance came back null\n");
// dpl_SetInstanceFrontMaterial(flash_morph_instance, flash_fade_material->GetMaterial());
// dpl_FlushInstance(flash_morph_instance);
// //------------------------------------------------
// // Renderables to handle the chunks and fireballs
// //------------------------------------------------
// OneShotDelayRenderable *fireball_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.25f); // How long to wait before raising the trigger
// Register_Object(fireball_delay);
// DPLEffectRenderable *chunks_effect =
// new DPLEffectRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// fireball_delay->GetTriggerAttribute(), // address containing the trigger
// 3, // Chunks // DPL effect number to trigger
// local_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &null_offset); // Offset (or world coordinants if DCS is NULL)
// Register_Object(chunks_effect);
// Point3D fireball_offset(1.0f, 5.0f, 1.0f);
// DPLEffectRenderable *fireball_effect =
// new DPLEffectRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// fireball_delay->GetTriggerAttribute(), // address containing the trigger
// 15, // Fireball // DPL effect number to trigger
// local_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &fireball_offset); // Offset (or world coordinants if DCS is NULL)
// Register_Object(fireball_effect);
// OneShotDelayRenderable *fireball_delay_2 =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.55f); // How long to wait before raising the trigger
// Register_Object(fireball_delay_2);
// Point3D fireball_offset_2(-1.0f, 5.0f, -1.0f);
// DPLEffectRenderable *fireball_effect_2 =
// new DPLEffectRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// fireball_delay_2->GetTriggerAttribute(), // address containing the trigger
// 15, // Fireball // DPL effect number to trigger
// local_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &fireball_offset_2); // Offset (or world coordinants if DCS is NULL)
// Register_Object(fireball_effect_2);
// //------------------------------------------------------
// // Renderables to handle the torso debris explode morph
// //------------------------------------------------------
// OneShotDelayRenderable *morph_debris_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.25f, // How long to wait before raising the trigger
// 4.25f); // Duration of trigger
// SweepRenderable *debris_morph_sweep =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 4.0f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// morph_debris_delay->GetTriggerAttribute(), // trigger variable
// 0.0f, // initial value of sweep
// 9.0f, // final value of sweep
// SweepRenderable::Y_SQR_X); // function to apply
// ChildMorphRenderable *debris_morph =
// new ChildMorphRenderable(
// entity, // Entity to attach the renderable to
// ChildMorphRenderable::Dynamic, // How/when to execute the renderable
// morph_debris_a, // destination
// morph_debris_b, // start object
// morph_debris_c, // end object
// debris_morph_sweep->GetSweepAttribute(), // pointer to control variable
// dpl_morph_vertices, // Defines type of morph to do
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// local_root->GetDCS()); // the parent DCS we will be offsetting from
// MakeDCSFall *debris_make_fall =
// new MakeDCSFall(
// entity, // Entity to attach the renderable to
// MakeDCSFall::Dynamic, // How/when to execute the renderable
// debris_morph->GetDCS(), // the DCS to control
// -9.81f, // Gravity in meters/sec squared
// morph_debris_delay->GetTriggerAttribute()); // true if the instance is on, false if off
// // Find the instance with the debris in it and hook up the instance switch
// dpl_INSTANCE *debris_morph_instance = dpl_GetDCSInstance(debris_morph->GetDCS(), 1);
// InstanceSwitchRenderable *debris_morph_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// debris_morph_instance, // the instance to control
// True, // Instance is on when trigger is...
// morph_debris_delay->GetTriggerAttribute());
// //-----------------------------------------------------
// // Renderables to handle the hips debris explode morph
// //-----------------------------------------------------
// ChildMorphRenderable *hips_morph =
// new ChildMorphRenderable(
// entity, // Entity to attach the renderable to
// ChildMorphRenderable::Dynamic, // How/when to execute the renderable
// morph_hips_a, // destination
// morph_hips_b, // start object
// morph_hips_c, // end object
// debris_morph_sweep->GetSweepAttribute(), // pointer to control variable
// dpl_morph_vertices, // Defines type of morph to do
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// local_root->GetDCS()); // the parent DCS we will be offsetting from
// MakeDCSFall *hips_make_fall =
// new MakeDCSFall(
// entity, // Entity to attach the renderable to
// MakeDCSFall::Dynamic, // How/when to execute the renderable
// hips_morph->GetDCS(), // the DCS to control
// -9.81f, // Gravity in meters/sec squared
// morph_debris_delay->GetTriggerAttribute()); // true if the instance is on, false if off
// // Find the instance with the debris in it and hook up the instance switch
// dpl_INSTANCE *hips_morph_instance = dpl_GetDCSInstance(hips_morph->GetDCS(), 1);
// InstanceSwitchRenderable *hips_morph_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// hips_morph_instance, // the instance to control
// True, // Instance is on when trigger is...
// morph_debris_delay->GetTriggerAttribute());
// //-----------------------------------------------
// // Renderables to handle static debris and fires
// //-----------------------------------------------
// OneShotDelayRenderable *static_debris_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.25f); // How long to wait before raising the trigger
// DPLStaticChildRenderable *mech_debris =
// new DPLStaticChildRenderable(
// entity,
// false,
// thor_debris_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// this_root->GetDCS());
// Register_Object(mech_debris);
// MakeDCSFall *mech_debris_fall =
// new MakeDCSFall(
// entity, // Entity to attach the renderable to
// MakeDCSFall::Dynamic, // How/when to execute the renderable
// mech_debris->GetDCS(), // the DCS to control
// -0.01f, // Gravity in meters/sec squared
// static_debris_delay->GetTriggerAttribute()); // true if the instance is on, false if off
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *mech_debris_instance = dpl_GetDCSInstance(mech_debris->GetDCS(), 1);
// InstanceSwitchRenderable *mech_debris_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// mech_debris_instance, // the instance to control
// True, // Instance is on when trigger is...
// static_debris_delay->GetTriggerAttribute());
//
// DPLStaticChildRenderable *large_debris =
// new DPLStaticChildRenderable(
// entity,
// false,
// large_debris_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// mech_debris->GetDCS());
// Register_Object(large_debris);
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *large_debris_instance = dpl_GetDCSInstance(large_debris->GetDCS(), 1);
// InstanceSwitchRenderable *large_debris_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// large_debris_instance, // the instance to control
// True, // Instance is on when trigger is...
// static_debris_delay->GetTriggerAttribute());
//
// DPLStaticChildRenderable *small_flames =
// new DPLStaticChildRenderable(
// entity,
// false,
// small_flames_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// mech_debris->GetDCS());
// Register_Object(small_flames);
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *small_flames_instance = dpl_GetDCSInstance(small_flames->GetDCS(), 1);
// InstanceSwitchRenderable *small_flames_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// small_flames_instance, // the instance to control
// True, // Instance is on when trigger is...
// static_debris_delay->GetTriggerAttribute());
//
// DPLStaticChildRenderable *big_flames =
// new DPLStaticChildRenderable(
// entity,
// false,
// big_flames_object,
// dpl_isect_mode_obj,
// NULL,
// null_offset_matrix,
// mech_debris->GetDCS());
// Register_Object(big_flames);
// // Make big flames object billboard along y-axis
// dpl_SetDCSReorientAxes(big_flames->GetDCS(), dpl_reorient_axes_y);
// dpl_FlushDCS(big_flames->GetDCS());
// // Find the instance with the static_thr in it and hook up the instance switch
// dpl_INSTANCE *big_flames_instance = dpl_GetDCSInstance(big_flames->GetDCS(), 1);
// InstanceSwitchRenderable *big_flames_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// big_flames_instance, // the instance to control
// True, // Instance is on when trigger is...
// static_debris_delay->GetTriggerAttribute());
// //-------------------------------------------------------------
// // Renderables to handle the rising smoke column (first shape)
// //-------------------------------------------------------------
//#if 1
// OneShotDelayRenderable *first_one_shot =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 0.0f); // How long to wait before raising the trigger
// Register_Object(first_one_shot);
// Point3D my_offset(0.0, 6.0, 0.0);
// #if DEBUG_LEVEL > 0
// DPLPSFXRenderable* this_effect =
// #endif
// new DPLPSFXRenderable(
// entity, // Entity to attach the renderable to
// DPLPSFXRenderable::Dynamic, // How/when to execute the renderable
// first_one_shot->GetTriggerAttribute(), // address containing the trigger
// myPSFXDescriptons[1], // pointer to the PFX description
// this_root->GetDCS(), // DCS the effect is relative to (may be NULL)
// &my_offset); // Offset (or world coordinants if DCS is NULL)
// Register_Object(this_effect);
//
//// the below code will be removed in favor of PFX effects once they are tested
//#else
//// OneShotDelayRenderable *smoke_1_morph_delay =
//// new OneShotDelayRenderable(
//// entity, // Entity to attach the renderable to
//// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
//// 0.25f); // How long to wait before raising the trigger
// SweepRenderable *smoke_1_sweep =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 40.0f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// static_debris_delay->GetTriggerAttribute(),
// 0.2f,
// 2.5f);
// ChildMorphRenderable *smoke_1_morph =
// new ChildMorphRenderable(
// entity, // Entity to attach the renderable to
// ChildMorphRenderable::Dynamic, // How/when to execute the renderable
// smoke_1_a, // destination
// smoke_b, // start object
// smoke_c, // end object
// smoke_1_sweep->GetSweepAttribute(), // pointer to control variable
// dpl_morph_vertices, // Defines type of morph to do
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS()); // the parent DCS we will be offsetting from
// // Find the instance with the flash in it and hook up the instance switch
// dpl_INSTANCE *smoke_1_instance = dpl_GetDCSInstance(smoke_1_morph->GetDCS(), 1);
// InstanceSwitchRenderable *smoke_1_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// smoke_1_instance, // the instance to control
// True, // Instance is on when trigger is...
// static_debris_delay->GetTriggerAttribute());
// //--------------------------------------------------------------
// // Renderables to handle the rising smoke column (second shape)
// //--------------------------------------------------------------
//#if 0
// OneShotDelayRenderable *smoke_2_morph_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 6.0f); // How long to wait before raising the trigger
// SweepRenderable *smoke_2_sweep =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 30.0f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// smoke_2_morph_delay->GetTriggerAttribute(),
// 0.1f,
// 1.0f);
// ChildMorphRenderable *smoke_2_morph =
// new ChildMorphRenderable(
// entity, // Entity to attach the renderable to
// ChildMorphRenderable::Dynamic, // How/when to execute the renderable
// smoke_2_a, // destination
// smoke_b, // start object
// smoke_c, // end object
// smoke_2_sweep->GetSweepAttribute(), // pointer to control variable
// dpl_morph_vertices, // Defines type of morph to do
// false, // DPL Zone this stuff will live in (for culling)
// dpl_isect_mode_obj, // type of intersections to do on this object
// NULL, // intersection mask for the object
// this_root->GetDCS()); // the parent DCS we will be offsetting from
// // Find the instance with the flash in it and hook up the instance switch
// dpl_INSTANCE *smoke_2_instance = dpl_GetDCSInstance(smoke_2_morph->GetDCS(), 1);
// InstanceSwitchRenderable *smoke_2_morph_instance_switch =
// new InstanceSwitchRenderable(
// entity, // Entity to attach the renderable to
// InstanceSwitchRenderable::Dynamic, // How/when to execute the renderable
// smoke_2_instance, // the instance to control
// True, // Instance is on when trigger is...
// smoke_2_morph_delay->GetTriggerAttribute());
//#endif
// //-------------------------------------------
// // Set dither Z on material for smoke column
// //-------------------------------------------
// dpl_MATERIAL *damage_material =
// dpl_LookupMaterial ("btfx:smoke1_mtl",
// dpl_lookup_normal,
// &status);
// if (damage_material == 0)
// {
// std::cout << "couldn't find material\n";
// }
// else
// {
// dpl_SetMaterialDitherZ(damage_material, 10.0f);
// dpl_FlushMaterial(damage_material);
// }
// //---------------------------------------------------
// // Create a material morph to fade smoke column away
// //---------------------------------------------------
// OneShotDelayRenderable *smoke_fade_delay =
// new OneShotDelayRenderable(
// entity, // Entity to attach the renderable to
// OneShotDelayRenderable::Dynamic, // How/when to execute the renderable
// 25.0f); // How long to wait before raising the trigger
// SweepRenderable *smoke_fade_sweep =
// new SweepRenderable(
// entity, // Entity to attach the renderable to
// SweepRenderable::Dynamic, // How/when to execute the renderable
// 18.5f, // How long to take to sweep from 0 to 1
// 1, // number of times to cycle before stopping
// smoke_fade_delay->GetTriggerAttribute()); // trigger variable
// Register_Object(smoke_fade_sweep);
// // Lookup texture for smoke column
// dpl_TEXTURE *smoke_texture =
// dpl_LookupTexture("btfx:smoke1_scr_tex", dpl_lookup_normal, &status);
// if (smoke_texture == NULL)
// DEBUG_STREAM<<"couldn't find texture btfx:smoke1_scr_tex for an effect\n" << std::flush;
// //
// MorphMaterialRenderable *smoke_fade_material =
// new MorphMaterialRenderable(
// entity, // Entity to attach the renderable to
// MorphMaterialRenderable::Dynamic, // How/when to execute the renderable
// 1.0f,1.0f,1.0f, // Material's ambient component
// 0.7f,0.4f,0.4f, // Material's emissive component
// 0.3714f,0.2899f,0.3714f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0f, // Material's specular component
// 0.7f,0.99f,0.0f, // Material's opacity
// smoke_texture, // Material's texture pointer
// 10.0f, // Material's Z dither value
// 3, // Material's Fog Imunity value
// 1.0f,1.0f,1.0f, // Material's ambient component
// 0.7f,0.4f,0.4f, // Material's emissive component
// 0.3714f,0.2899f,0.3714f, // Material's diffuse component
// 0.0f,0.0f,0.0f,0.0f, // Material's specular component
// 0.0f,0.99f,0.0f, // Material's opacity
// 10.0f, // Material's Z dither value
// smoke_fade_sweep->GetSweepAttribute());
// Register_Object(smoke_fade_material);
//// dpl_INSTANCE *smoke_1_instance = dpl_GetDCSInstance(smoke_1_morph->GetDCS(), 1);
// if (!smoke_1_instance)
// Fail("smoke_1_instance came back null\n");
// dpl_SetInstanceFrontMaterial(smoke_1_instance, smoke_fade_material->GetMaterial());
// dpl_FlushInstance(smoke_1_instance);
//#endif
// #else
// DEBUG_STREAM <<"Explosion effect 104 called, this is disabled and shouldn't be used!\n" << std::flush;
// #endif
// break;
// }
// default:
// {
// break;
// }
// }
}
//
//#############################################################################
// MakeEntityRenderables handles creating all the renderables necessary to
// display an object. This routine contains default behaviors for creating
// some types of simple objects. The idea is that if a higher level routine
// can't figure out how to make renderables for something this routine will be
// called and the default behavior will be used.
//#############################################################################
//
void
DPLRenderer::MakeEntityRenderables(
Entity *entity, // The entity we are dealing with
ResourceDescription *model_resource, // Pointer to the video resource
ViewFrom view_type) // Type of reference (inside/outside...etc.)
{
//STUBBED: DPL RB 1/14/07
char *object_filename;
#if DEBUG_LEVEL > 0
int object_count;
#endif
L4VideoObject *video_object;
L4VideoObjectWrapper *video_wrapper;
ChainOf<L4VideoObjectWrapper*> video_chain(NULL);
L4VideoObject::ResourceType resource_type;
// dpl_DCS *my_root_dcs;
Enumeration renderer_modes;
//
// Set the Entity_Being_Created global so the C language callback will
// be able to mark the geometry with damage zone values if necessary.
//
Entity_Being_Created = entity;
// my_root_dcs = NULL;
//
// convert video resource into chain of video objects and make an iterator
// for that chain
//
if (model_resource)
{
#if DEBUG_LEVEL > 0
object_count =
#endif
L4VideoObjectWrapper::BuildVideoObjectChainFromResource(&video_chain, model_resource);
}
ChainIteratorOf<L4VideoObjectWrapper*> video_iterator(video_chain);
//
// Switch to allow us to have scripts at this level for constructing certain
// types of video objects (whether or not they have a video resource)
//
switch (entity->GetClassID())
{
//
// Dropzones have no graphical appearance so we do nothing
//
case DropZoneClassID:
break;
//
// Player objects have no graphical appearance unless one is created
// at a higher (game specific) level, so we do nothing here.
//
case PlayerClassID:
break;
// this entity has no visual appearance
case AudioEntityClassID:
break;
//
// Cultural and landmark are the only ones with "destroyed" processing
// This is similar to the default case but does not allow us to be
// inside the entity, does not allow us to be a mover,
//
case CulturalIconClassID:
case LandmarkClassID:
{
d3d_OBJECT *this_object;
dpl_ISECT_MODE intersect_mode;
HierarchicalDrawComponent *component = NULL;
uint32 intersect_mask;
LinearMatrix offset_matrix = LinearMatrix::Identity;
//dpl_DCS *root_DCS, *this_DCS;
//
// Make sure the object has a video resource
//
if (!model_resource)
{
VideoRenderer::MakeEntityRenderables(entity, model_resource, view_type);
Entity_Being_Created = NULL;
return;
}
// intersect_mode = dpl_isect_mode_geometry;
intersect_mask = INTERSECT_ALL;
Logical first_object = True;
video_iterator.First();
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
{
video_object = video_wrapper->GetVideoObject();
object_filename = video_object->GetObjectFilename();
resource_type = video_object->GetResourceType();
renderer_modes = video_object->GetRendererModes();
#if NOISY_RENDERER
Tell("L4VIDEO.cpp loading object " << object_filename);
Tell(" type " << resource_type);
Tell(" mode 0x" <<std::hex<<renderer_modes<<std::dec<< "." << std::endl);
#endif
if ((resource_type != L4VideoObject::Object) &&
(resource_type != L4VideoObject::Rubble))
{
DEBUG_STREAM << "L4VIDEO.cpp wrong video resource type for object "<<object_filename<<"\n" << std::flush;
continue; // next object
}
// BT_CULT_LOG (issue #3): per-object census -- name/type + draw-op
// flags (hunting the opaque-black scorch quad on rubble models).
if (getenv("BT_CULT_LOG"))
{
DEBUG_STREAM << "[cultobj] entity=" << (long)entity->GetEntityID()
<< " file=" << object_filename
<< " type=" << (int)resource_type << std::flush;
}
SET_VIDEO_LOAD_OBJECT();
// HACK !!! load the object as uncached so entity identification will work right
// this_object = dpl_LoadObject(object_filename, dpl_load_normal);
this_object = d3d_OBJECT::LoadObject(mDevice, object_filename);
CLEAR_VIDEO_LOAD_OBJECT();
if (this_object == NULL)
{
DEBUG_STREAM << "L4VIDEO.cpp couldn't load object "<<object_filename<<"\n" << std::flush;
if (!first_object)
{
continue; // next object
}
}
else if (getenv("BT_CULT_LOG"))
{
// [cultobj] tail: the loaded object's per-op flags
DEBUG_STREAM << " ops=" << this_object->GetDrawOpCount();
for (int opn = 0; opn < this_object->GetDrawOpCount(); opn++)
{
const L4DRAWOP *op = this_object->GetDrawOp(opn);
DEBUG_STREAM << " [" << opn
<< " aT=" << (int)op->alphaTest
<< " dcl=" << (int)op->drawAsDecal
<< " sky=" << (int)op->drawAsSky << "]";
}
DEBUG_STREAM << " shadow=" << (int)this_object->GetIsShadow()
<< "\n" << std::flush;
}
if (first_object)
{
first_object = False;
component = new RootRenderable(
entity, // Entity to attach the renderable to
RootRenderable::Static, // How/when to execute the renderable
this_object, // object to hang on the DCS, may be a list later <NULL>
false, // DPL Zone this stuff will live in (for culling)
intersect_mode, // type of intersections to do on this object
intersect_mask); // intersection mask for the object
//root_DCS = this_static_root->GetDCS();
//------------------------------------------------------
// if root object billboards (all others billboard too)
//------------------------------------------------------
//this_DCS = root_DCS;
// this_instance = this_static_root->GetInstance();
//
// Set my_root_dcs because we want this guy's hiearchy to be marked
// with his entity pointer
//
// my_root_dcs = root_DCS;
}
else
{
//------------------------------------------------------------
// Issue #3 (cultural destruction): additional cultural objects
// (the RUBBLE model above all) used to go to
// DPLStaticChildRenderable / DCSInstanceRenderable -- but as
// TOP-LEVEL mRenderables entries those classes read the bare
// matrix stack (identity), so the rubble drew at the WORLD
// ORIGIN, not at the icon (and pre-revival it consolidated
// there permanently). RootRenderable places from
// myEntity->localToWorld -- the same path the intact (first)
// object draws through -- so every cultural object is one.
//------------------------------------------------------------
component = new RootRenderable(
entity, // Entity to attach the renderable to
RootRenderable::Static, // How/when to execute the renderable
this_object, // the additional object (rubble etc.)
false, // DPL Zone (for culling)
intersect_mode, // type of intersections to do on this object
intersect_mask); // intersection mask for the object
}
// add the new object to our renderables list. Keep the draw
// component in hand -- the revived state switch (issue #3)
// controls IT (SetDrawObj show/hide), not a dead DPL instance.
HierarchicalDrawComponent *draw_component = component;
if (component)
mRenderables.Add(component);
component = NULL;
//
// Hook up the object to an instance switch renderable responsive to state
//
StateIndicator* simulation_state = (StateIndicator *)entity->GetAttributePointer("SimulationState");
if (simulation_state != NULL && draw_component != NULL)
switch(resource_type)
{
case L4VideoObject::Object:
{
component = new StateInstanceSwitchRenderable(
entity, // Entity to attach the renderable to
StateInstanceSwitchRenderable::Watcher, // How/when to execute the renderable
draw_component, // the draw component to control
this_object, // its drawable (restored on show)
False, // true to turn on in this state, false for off
simulation_state, // State dial we use to control the on/off
CulturalIcon::BurningState); // State that we look for
break;
}
case L4VideoObject::Rubble:
{
component = new StateInstanceSwitchRenderable(
entity, // Entity to attach the renderable to
StateInstanceSwitchRenderable::Watcher, // How/when to execute the renderable
draw_component, // the draw component to control
this_object, // its drawable (restored on show)
True, // true to turn on in this state, false for off
simulation_state, // State dial we use to control the on/off
CulturalIcon::BurningState); // State that we look for
break;
}
}
if (component)
mRenderables.Add(component);
//----------------------------------
// billboard object if so indicated
//----------------------------------
// if (this_DCS && (renderer_modes & L4VideoObject::BillboardObject))
// {
// int axes = dpl_reorient_axes_none;
// if (renderer_modes & L4VideoObject::BillboardXAxis)
// {
// axes |= dpl_reorient_axes_x;
// }
// if (renderer_modes & L4VideoObject::BillboardYAxis)
// {
// axes |= dpl_reorient_axes_y;
// }
// if (renderer_modes & L4VideoObject::BillboardZAxis)
// {
// axes |= dpl_reorient_axes_z;
// }
// dpl_SetDCSReorientAxes(this_DCS, (dpl_REORIENT_AXES)axes);
// dpl_FlushDCS(this_DCS);
// }
}
break;
}
//
// Case to handle rivets using the fast projectile code
//
case RivetClassID:
{
d3d_OBJECT
*this_object;
video_iterator.First();
video_wrapper = video_iterator.ReadAndNext();
Check(video_wrapper);
video_object = video_wrapper->GetVideoObject();
Check_Pointer(video_object);
object_filename = video_object->GetObjectFilename();
this_object = d3d_OBJECT::LoadObject(GetDevice(), object_filename);
#if DEBUG_LEVEL > 0
ProjectileRootRenderable *projectile =
#endif
new ProjectileRootRenderable(
entity, // Entity to attach the renderable to
ProjectileRootRenderable::Dynamic, // How/when to execute the renderable
this_object, // object to hang on the DCS, may be a list later <NULL>
false); // DPL Zone this stuff will live in (for culling)
Register_Object(projectile);
break;
}
//
// Script for generating renderables for eyecandy
//
case EyeCandyClassID:
{
int effect_number;
dpl_ISECT_MODE dpl_isect_mode_obj;
RootRenderable *this_root =
new RootRenderable(
entity, // Entity to attach the renderable to
RootRenderable::Dynamic, // How/when to execute the renderable
NULL, // object to hang on the DCS, may be a list later <NULL>
false, // DPL Zone this stuff will live in (for culling)
dpl_isect_mode_obj, // type of intersections to do on this object
NULL); // intersection mask for the object
Register_Object(this_root);
StateIndicator* simulation_state = (StateIndicator *)entity->GetAttributePointer("SimulationState");
video_iterator.First();
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
{
video_object = video_wrapper->GetVideoObject();
effect_number = atoi(video_object->GetObjectFilename());
#if DEBUG_LEVEL > 0
DPLSFXRenderable *this_effect =
#endif
new DPLSFXRenderable(
entity, // Entity to attach the effect to
false, // DPL zone everything will be in
Point3D::Identity, // Point offset from the parent DCS
this_root, // Parent DCS (can be NULL for world)
simulation_state, // Trigger effect when this state changes
EyeCandy::effectOn, // Trigger effect when in this state
effect_number, // Type of effect to trigger
.01); // Effect repeat speed.
Register_Object(this_effect);
}
break;
}
//
// Script for generating an explosion of type specified in the resource file
//
case ExplosionClassID:
{
DEBUG_STREAM << "Explosion Created in MakeEntityRenderables:" << std::endl << std::flush;
int effect_number;
video_iterator.First();
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
{
video_object = video_wrapper->GetVideoObject();
effect_number = atoi(video_object->GetObjectFilename());
DEBUG_STREAM << " ** effect_number = " << effect_number << std::endl << std::flush;
if(effect_number < 100 || effect_number >= 1000)
{
DEBUG_STREAM << " ** DPLIndependantEffect(["
<< entity->localOrigin.linearPosition.x
<< ", " << entity->localOrigin.linearPosition.y
<< ", " << entity->localOrigin.linearPosition.z << "], " << effect_number << ");" << std::endl << std::flush;
// TWO DISTINCT ENCODINGS (routing corrected 2026-07-12):
// < 100 = the dpl EFFECT-TABLE number = an OLD-STYLE
// specialfx descriptor (BTDPL.INI [effects_to_load]
// "specialfxN": 8=PPCHit, 9=LaserHit, 10=MissileHit,
// 3=Chunks1, 5=Sparks, 15=Fireball -- the 1996
// script's own comments match) -> the firmware-
// decoded heat-model burst (BTStartSfx);
// >= 1000 = the "1000+slot" psfx id (1002-1005 = ddam1-4,
// 1008 = ddam5, 1009-1013 = idam1-5) -> the .PFX
// layer, oriented in the victim's frame.
// (The old "<100 -> psfx slot" mapping rendered PPCHit as
// ddam5 SMOKE and Chunks as damage smoke -- the "hits are
// just a little smoke" report.)
if (effect_number >= 1000)
{
extern void BTStartPfxFrame(int, float, float, float,
const float *, const float *, const float *);
float xr[3] = { (float)entity->localToWorld(0,0), (float)entity->localToWorld(0,1), (float)entity->localToWorld(0,2) };
float yr[3] = { (float)entity->localToWorld(1,0), (float)entity->localToWorld(1,1), (float)entity->localToWorld(1,2) };
float zr[3] = { (float)entity->localToWorld(2,0), (float)entity->localToWorld(2,1), (float)entity->localToWorld(2,2) };
BTStartPfxFrame(effect_number - 1000,
(float)entity->localOrigin.linearPosition.x,
(float)entity->localOrigin.linearPosition.y,
(float)entity->localOrigin.linearPosition.z,
xr, yr, zr);
}
else
{
extern void BTStartSfx(int, float, float, float);
BTStartSfx(effect_number,
(float)entity->localOrigin.linearPosition.x,
(float)entity->localOrigin.linearPosition.y,
(float)entity->localOrigin.linearPosition.z);
}
}
else
{
DEBUG_STREAM << " ** ExplosionScripts(*entity*, RES["
<< model_resource->resourceID << " - " << model_resource->resourceName << "], "
<< "ViewFrom::" << (view_type == ViewFrom::insideEntity ? "insideEntity" : (view_type == ViewFrom::outsideEntity ? "outsideEntity" : "collisionEntity"))
<< ", " << (effect_number - 100) << ");" << std::endl << std::flush;
//
// Effect 104 = the death-WRECK script (1996 ExplosionScripts
// case 4: swap the victim to its <mech>dbr burning hulk),
// dispatched by the per-mech death ModelLists ('blhdead'...).
// RECONSTRUCTED in the BT renderer (SwapToWreck); the other
// script numbers remain stubbed with the rest of the layer.
//
if (effect_number == 104)
{
extern void BTSwapMechToWreck(Entity *victim);
BTSwapMechToWreck(((Explosion *)entity)->GetEntityHit());
}
else
ExplosionScripts(
entity, // The entity we are dealing with
model_resource, // Pointer to the video resource
view_type, // Type of reference (inside/outside...etc.)
effect_number - 100);
}
}
break;
}
case CameraDirectorClassID:
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Create the CameraDirector HUD Renderable if not a Replicant
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
if (entity->GetInstance() == CameraDirector::MasterInstance)
{
CameraDirector *camera_director = (CameraDirector*) entity;
Check_Pointer(camera_director);
int *player_index = &camera_director->goalPlayerIndex;
Check_Pointer(player_index);
Logical *display_rank_window = &camera_director->displayRankingWindow;
Check_Pointer(display_rank_window);
mCamShipHUD = new CameraShipHUDRenderable(entity, CameraShipHUDRenderable::Dynamic, player_index, display_rank_window);
if (getenv("BT_CAM_LOG"))
DEBUG_STREAM << "[cam] CameraShipHUDRenderable created\n"
<< std::flush;
}
break;
}
//
// Script for a drivable camera, the camera is invisible to other players
//
case CameraShipClassID:
{
if(view_type == insideEntity)
{
//
// Build an empty root renderable and an eye renderable
//
dpl_ISECT_MODE dpl_isect_mode_obj;
RootRenderable *this_root =
new RootRenderable(
entity, // Entity to attach the renderable to
RootRenderable::Dynamic, // How/when to execute the renderable
NULL, // object to hang on the DCS, may be a list later <NULL>
false, // DPL Zone this stuff will live in (for culling)
dpl_isect_mode_obj, // type of intersections to do on this object
NULL); // intersection mask for the object
Register_Object(this_root);
#if 0 //DEBUG_LEVEL > 0
DPLEyeRenderable* this_eye =
#endif
mCamera =
new DPLEyeRenderable(
entity,
LinearMatrix::Identity,
this_root,
NULL
);
Register_Object(this_eye);
}
break;
}
//
// Script for doorframe, so it can be used in any game
//
case DoorFrameClassID:
{
Verify( object_count == 6 );
//------------------------------------------------
// First video object is root shape for the door
// followed by left door shape, right door shape,
// door lights, left door lights, and right door
// lights.
//------------------------------------------------
d3d_OBJECT
*object,
*left,
*right,
*object_lights,
*left_lights,
*right_lights;
int object_number = 0;
video_iterator.First();
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
{
video_object = video_wrapper->GetVideoObject();
object_filename = video_object->GetObjectFilename();
#if NOISY_RENDERER
Tell("L4VIDEO.cpp loading door object "<<object_filename<<"\n");
#endif
switch (object_number)
{
case 0:
// object = dpl_LoadObject(object_filename, dpl_load_normal);
object = d3d_OBJECT::LoadObject(mDevice, object_filename);
break;
case 1:
// left = dpl_LoadObject(object_filename, dpl_load_normal);
left = d3d_OBJECT::LoadObject(mDevice, object_filename);
break;
case 2:
// right = dpl_LoadObject(object_filename, dpl_load_normal);
right = d3d_OBJECT::LoadObject(mDevice, object_filename);
break;
case 3:
// object_lights = dpl_LoadObject(object_filename, dpl_load_normal);
object_lights = d3d_OBJECT::LoadObject(mDevice, object_filename);
break;
case 4:
// left_lights = dpl_LoadObject(object_filename, dpl_load_normal);
left_lights = d3d_OBJECT::LoadObject(mDevice, object_filename);
break;
case 5:
// right_lights = dpl_LoadObject(object_filename, dpl_load_normal);
right_lights = d3d_OBJECT::LoadObject(mDevice, object_filename);
break;
default:
// ignore any additional objects
break;
}
++object_number;
}
//
// HACK Hard Coded Doorframes SubsystemArray access
//
Door *left_door_sub = (Door*) entity->GetSubsystem(0);
Point3D* left_door_position = &left_door_sub->currentPosition;
Door *right_door_sub = (Door*) entity->GetSubsystem(1);
Point3D* right_door_position = &right_door_sub->currentPosition;
dpl_ISECT_MODE dpl_isect_mode_obj;
RootRenderable *door_sill =
new RootRenderable(
entity, // Entity to attach the renderable to
RootRenderable::Static, // How/when to execute the renderable
object, // object to hang on the DCS, may be a list later <NULL>
false, // DPL Zone this stuff will live in (for culling)
dpl_isect_mode_obj, // type of intersections to do on this object
NULL); // intersection mask for the object
Register_Object(door_sill);
//
// Set my_root_dcs because we want this guy's hiearchy to be marked
// with his entity pointer
//
// my_root_dcs = door_sill->GetDCS();
LinearMatrix my_ident(True);
DPLChildPointRenderable* left_door =
new DPLChildPointRenderable(
entity,
false,
left,
dpl_isect_mode_obj,
NULL,
my_ident,
door_sill,
left_door_position);
Register_Object(left_door);
DPLChildPointRenderable* right_door =
new DPLChildPointRenderable(
entity,
false,
right,
dpl_isect_mode_obj,
NULL,
my_ident,
door_sill,
right_door_position);
Register_Object(right_door);
#if DEBUG_LEVEL > 0
DCSInstanceRenderable *door_sill_lights_instance =
#endif
new DCSInstanceRenderable(
entity, // Entity to attach the renderable to
DCSInstanceRenderable::Static, // How/when to execute the renderable
object_lights, // object to connect to the instance
door_sill, // the DCS to add the instance to
dpl_isect_mode_obj, // type of intersections to do on this object
NULL, // intersection mask for the object
True); // initial visibility setting
Register_Object(door_sill_lights_instance);
#if DEBUG_LEVEL > 0
DCSInstanceRenderable *left_door_lights_instance =
#endif
new DCSInstanceRenderable(
entity, // Entity to attach the renderable to
DCSInstanceRenderable::Static, // How/when to execute the renderable
left_lights, // object to connect to the instance
left_door, // the DCS to add the instance to
dpl_isect_mode_obj, // type of intersections to do on this object
NULL, // intersection mask for the object
True); // initial visibility setting
Register_Object(left_door_lights_instance);
#if DEBUG_LEVEL > 0
DCSInstanceRenderable *right_door_lights_instance =
#endif
new DCSInstanceRenderable(
entity, // Entity to attach the renderable to
DCSInstanceRenderable::Static, // How/when to execute the renderable
right_lights, // object to connect to the instance
right_door, // the DCS to add the instance to
dpl_isect_mode_obj, // type of intersections to do on this object
NULL, // intersection mask for the object
True); // initial visibility setting
Register_Object(right_door_lights_instance);
break;
}
//
// This is the script run on anything that isn't already listed above
//
default:
{
d3d_OBJECT *this_object;
dpl_ISECT_MODE intersect_mode;
uint32 intersect_mask;
LinearMatrix offset_matrix = LinearMatrix::Identity;
Component *component = NULL;
HierarchicalDrawComponent *rootCom = NULL;
HierarchicalDrawComponent *thisCom = NULL;
//
// First, establish that this level doesn't know what to do
// if the object has no video resource.
//
if (!model_resource)
{
VideoRenderer::MakeEntityRenderables(entity, model_resource, view_type);
Entity_Being_Created = NULL;
return;
}
//
// If we're inside the entity, set up the intersect mode and mask so we
// won't intersect ourselves with the pickpoint. Other items get full
// geometry intersection.
//
if (view_type == insideEntity)
{
// intersect_mode = dpl_isect_mode_obj;
intersect_mask = NULL;
}
else
{
// intersect_mode = dpl_isect_mode_geometry;
intersect_mask = INTERSECT_ALL;
}
Logical first_object = True;
video_iterator.First();
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
{
video_object = video_wrapper->GetVideoObject();
object_filename = video_object->GetObjectFilename();
resource_type = video_object->GetResourceType();
renderer_modes = video_object->GetRendererModes();
#if NOISY_RENDERER
Tell("L4VIDEO.cpp loading object " << object_filename);
Tell(" type " << resource_type);
Tell(" mode 0x" <<std::hex<<renderer_modes<<std::dec<< "." << std::endl);
#endif
if ((resource_type != L4VideoObject::Object) && (resource_type != L4VideoObject::Rubble))
{
DEBUG_STREAM << "L4VIDEO.cpp wrong video resource type for object "<<object_filename<<"\n" << std::flush;
continue; // next object
}
SET_VIDEO_LOAD_OBJECT();
this_object = d3d_OBJECT::LoadObject(mDevice, object_filename);
CLEAR_VIDEO_LOAD_OBJECT();
if (this_object == NULL)
{
DEBUG_STREAM << "L4VIDEO.cpp couldn't load object "<<object_filename<<"\n" << std::flush;
if (!first_object)
{
continue; // next object
}
}
if (first_object)
{
first_object = False;
//
// Determine if the entity is a mover or not, which tells us what type of
// renderable to use to construct it.
//
if (entity->IsDerivedFrom(*Mover::GetClassDerivations()))
{
//
// It's a mover, construct it with a dynamic root renderable so it can move
// also remember it's DCS so we can hook other shapes to it later.
//
SET_VIDEO_CONSTRUCT_ROOT();
rootCom = new RootRenderable(
entity, // Entity to attach the renderable to
RootRenderable::Dynamic, // How/when to execute the renderable
this_object, // object to hang on the DCS, may be a list later <NULL>
false, // DPL Zone this stuff will live in (for culling)
intersect_mode, // type of intersections to do on this object
intersect_mask); // intersection mask for the object
Register_Object(this_root);
CLEAR_VIDEO_CONSTRUCT_ROOT();
}
else
{
//
// It's a static, construct it with a static RootRenderable and
// remember it's DCS so we can hook other shapes to it later.
//
rootCom = new RootRenderable(
entity, // Entity to attach the renderable to
RootRenderable::Static, // How/when to execute the renderable
this_object, // object to hang on the DCS, may be a list later <NULL>
false, // DPL Zone this stuff will live in (for culling)
intersect_mode, // type of intersections to do on this object
intersect_mask); // intersection mask for the object
}
//
// Set my_root_dcs because we want this guy's hiearchy to be marked
// with his entity pointer
//
// my_root_dcs = root_DCS;
//
// If we are inside the entity, then we must build an eyepoint for it too.
// since there is no special construction, we build the eyepoint with a
// zero offset.
//
if (view_type == insideEntity)
{
EulerAngles *eyepoint_rotation = (EulerAngles *)entity->GetAttributePointer("EyepointRotation");
#if 0 //DEBUG_LEVEL > 0
DPLEyeRenderable *this_eye =
#endif
mCamera =
new DPLEyeRenderable(
entity,
LinearMatrix::Identity,
rootCom,
eyepoint_rotation);
Register_Object(this_eye);
}
//------------------------------------------------------
// if root object billboards (all others billboard too)
//------------------------------------------------------
// this_DCS = root_DCS;
}
else
{
if (renderer_modes & L4VideoObject::BillboardObject)
{
//------------------------------------------
// attach additional object to separate DCS
// because it is billboarded
//------------------------------------------
thisCom = new DPLStaticChildRenderable(
entity,
false,
this_object,
intersect_mode,
intersect_mask,
offset_matrix,
rootCom);
Register_Object(this_child);
// this_DCS = this_child->GetDCS();
}
else
{
//----------------------------------------
// attach additional objects to root_DCS
// (HACK) temporary implementation (HACK)
//----------------------------------------
thisCom = new DCSInstanceRenderable(
entity, // Entity to attach the renderable to
DCSInstanceRenderable::Static, // How/when to execute the renderable
this_object, // object to connect to the instance
rootCom, // the DCS to add the instance to
intersect_mode, // type of intersections to do on this object
intersect_mask, // intersection mask for the object
True); // initial visibility setting
Register_Object(another_instance);
// this_DCS = NULL;
}
}
//----------------------------------
// billboard object if so indicated
//----------------------------------
if (thisCom && (renderer_modes & L4VideoObject::BillboardObject))
{
// int axes = dpl_reorient_axes_none;
if (renderer_modes & L4VideoObject::BillboardXAxis)
{
// axes |= dpl_reorient_axes_x;
}
if (renderer_modes & L4VideoObject::BillboardYAxis)
{
// axes |= dpl_reorient_axes_y;
}
if (renderer_modes & L4VideoObject::BillboardZAxis)
{
// axes |= dpl_reorient_axes_z;
}
// dpl_SetDCSReorientAxes(this_DCS, (dpl_REORIENT_AXES)axes);
// dpl_FlushDCS(this_DCS);
}
//MOVED THIS TO ROOTRENDERABLE - Was this a hack?
//mRenderables.Add(component);
}
break;
}
}
//
// release the video chain memory
//
L4VideoObjectWrapper::DeleteVideoObjectChain(&video_chain);
//
// Clear the entity pointer passed to the callback system
//
Entity_Being_Created = NULL;
//
// If my_root_dcs is non-null, mark the entity's DCS hiearchy with it's
// entity pointer.
//
// if(my_root_dcs)
// {
// MarkDCSHiearchy(my_root_dcs,entity);
//#if 0
// if(entity->GetClassID() == DemolitionPackClassID)
// {
// Point3D temp_point(0.0,0.0,0.0);
// #if DEBUG_LEVEL > 0
// OnePSFXRenderable *this_effect=
// #endif
// new OnePSFXRenderable(
// entity, // Entity to attach the renderable to
// OnePSFXRenderable::Static, // How/when to execute the renderable
// myPSFXDescriptons[3], // name of file with the PFX description in it
// my_root_dcs, // DCS the effect is relative to (may be NULL)
// &temp_point); // Offset (or world coordinants if DCS is NULL)
// Register_Object(this_effect);
// }
//#endif
// }
Check_Fpu();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This should be called prior to the rendering of a frame to setup the culling
// data the renderer keeps (mostly the world to eye transform)
//
void
DPLRenderer::SetupCull()
{
Entity
*linked_entity;
LinearMatrix
site_to_world,
world_to_site,
eye_rotation,
eye_inverted;
EntitySegment
*eyepoint_segment;
EulerAngles
*eyepoint_rotation;
//
// To test the functionality, this is not as efficient as it could be.
// when it gets fully patched into the renderer it will be improved.
//
// Figure out what type of entity this is.
//
linked_entity = GetLinkedEntity();
eyepoint_rotation = (EulerAngles*)linked_entity->GetAttributePointer("EyepointRotation");
if(linked_entity->IsDerivedFrom(*JointedMover::GetClassDerivations()))
{
JointedMover
*linked_jointed_mover;
linked_jointed_mover = Cast_Object(JointedMover*, linked_entity);
eyepoint_segment = linked_jointed_mover->GetSegment("siteeyepoint");
if(!eyepoint_segment)
{
Fail("DPLRenderer::SetupCull jointed mover had no siteeyepoint\n");
}
if(!eyepoint_rotation)
{
Fail("DPLRenderer::SetupCull jointed mover had no EyepointRotation attribute\n");
}
linked_jointed_mover->GetSegmentToWorld(*eyepoint_segment, &site_to_world);
if(eyepoint_rotation)
{
world_to_site.Invert(site_to_world);
eye_rotation = *eyepoint_rotation;
eye_inverted.Invert(eye_rotation);
worldToEyeMatrix.Multiply(world_to_site, eye_inverted);
}
else
{
worldToEyeMatrix.Invert(site_to_world);
}
}
else
{
site_to_world = linked_entity->localOrigin;
if(eyepoint_rotation)
{
world_to_site.Invert(site_to_world);
eye_rotation = *eyepoint_rotation;
eye_inverted.Invert(eye_rotation);
worldToEyeMatrix.Multiply(world_to_site, eye_inverted);
}
else
{
worldToEyeMatrix.Invert(site_to_world);
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
DPLRenderer::ResetStatistics()
{
total_cull = 0;
total_draw = 0;
total_pixelplanes = 0;
total_frame_time = 0;
frame_count = 0;
target_frame_time = 56000;
target_frame_count= 0;
report_time = currentFrameTime + 60.0f;
}
void
DPLRenderer::ReportStatistics()
{
if(frame_count != 0)
{
std::cout<<"Frames "<<frame_count<<"\n";
std::cout<<((float)target_frame_count/(float)frame_count)*100.0f<<"% of the frames were under the target frame time\n";
std::cout<<"Average Cull "<<(unsigned long)((float)total_cull/(float)frame_count)<<"\n";
std::cout<<"Average Draw "<<(unsigned long)((float)total_draw/(float)frame_count)<<"\n";
std::cout<<"Average Pixel Planes "<<(unsigned long)((float)total_pixelplanes/(float)frame_count)<<"\n";
std::cout<<"Average Frame Time "<<(unsigned long)((float)total_frame_time/(float)frame_count)<<"\n";
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// BTVerifyCockpitCanvasAfterReset (Gitea #56)
//
// The cockpit-surround layout is computed in BACKBUFFER space and clicks are
// mapped client -> backbuffer through gBTCockpitCanvasW/H (L4VB16.cpp
// BTCockpitMouseDown). With the windowed backbuffer now pinned at CreateDevice
// this should always agree -- so a mismatch here means the surface was re-created
// at a different size behind our back, which is exactly the state where panels
// draw in one geometry and clicks land in another. Say so, and re-sync, rather
// than drift silently for the rest of the session.
//
static void BTVerifyCockpitCanvasAfterReset(LPDIRECT3DDEVICE9 device)
{
extern int gBTGaugeCockpit, gBTCockpitCanvasW, gBTCockpitCanvasH;
if (device == NULL || !gBTGaugeCockpit || gBTCockpitCanvasW <= 0)
{
return;
}
IDirect3DSurface9 *back = NULL;
if (FAILED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back))
|| back == NULL)
{
return;
}
D3DSURFACE_DESC desc;
if (SUCCEEDED(back->GetDesc(&desc))
&& ((int)desc.Width != gBTCockpitCanvasW
|| (int)desc.Height != gBTCockpitCanvasH))
{
DEBUG_STREAM << "[resize] Gitea #56: backbuffer is " << desc.Width << "x"
<< desc.Height << " after Reset but the cockpit canvas is "
<< gBTCockpitCanvasW << "x" << gBTCockpitCanvasH
<< " -- clicks would be mis-mapped; re-syncing" << std::endl;
gBTCockpitCanvasW = (int)desc.Width;
gBTCockpitCanvasH = (int)desc.Height;
}
back->Release();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Execute Method, performs the rendering of one frame
//
void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::InterestingEntityIterator* all_iterator)
{
Component *component;
HRESULT hr;
// timing variables
__int64 ticks = HiResNowTicks();
#ifdef LOGFRAMERATE
fputc(0, FRAMERATE_LOG);
fwrite(&ticks, sizeof(__int64), 1, FRAMERATE_LOG);
#endif
int currentAppState = application->GetApplicationState();
switch (currentAppState)
{
case Application::CreatingMission:
case Application::LoadingMission:
case Application::WaitingForLaunch:
//if (mLoadingScreenThread == NULL)
//{
// mLoadingScreenRunning = true;
// mLoadingScreenThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ExecuteLoadScreenThread, this, 0, NULL);
//}
//hr = mDevice->TestCooperativeLevel();
//if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET)
//{
// mPrimaryDeviceReseting = true;
// DEBUG_STREAM << this << " RESETTING DEVICE - WAITING FOR FRAME TO FINISH" << std::endl << std::flush;
// while (mRenderingLoadingFrame)
// {
// Sleep(100);
// }
// DEBUG_STREAM << this << " RESETTING DEVICE - DONE WAITING FOR FRAME TO FINISH" << std::endl << std::flush;
// int bbCount = mPresentParams.BackBufferCount;
// int bbWidth = mPresentParams.BackBufferWidth;
// int bbHeight = mPresentParams.BackBufferHeight;
// ParticleEngine::Destroy();
// V(mDevice->Reset(&mPresentParams));
// ParticleEngine::Initialize(mDevice);
// this->SetCoreRenderStates();
// mPresentParams.BackBufferCount = bbCount;
// mPresentParams.BackBufferWidth = bbWidth;
// mPresentParams.BackBufferHeight = bbHeight;
// mPrimaryDeviceReseting = false;
//}
ticks = HiResNowTicks();
#ifdef LOGFRAMERATE
fputc(1, FRAMERATE_LOG);
fwrite(&ticks, sizeof(__int64), 1, FRAMERATE_LOG);
#endif
return;
case Application::LaunchingMission:
case Application::RunningMission:
break;
}
if (lastAppState != currentAppState && currentAppState == Application::LaunchingMission)
{
this->mDevice->SetRenderState(D3DRS_LIGHTING, true);
this->mDevice->SetTransform(D3DTS_PROJECTION, &this->mProjectionMatrix);
this->mDevice->SetRenderState(D3DRS_TEXTUREFACTOR, D3DCOLOR_ARGB(128, 255, 255, 255));
this->mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
}
lastAppState = currentAppState;
// BT (task #20): window-resize aspect fix, applied PER-FRAME on the render
// thread. (The WM_SIZE-time path can't reach the renderer: APPMGR.cpp:73
// reassigns the global `application` while iterating and leaves it NULL
// between frames, so l4_application is NULL at message-pump time.) The
// WndProc just records gWindowAspect; here the projection is rebuilt when
// it changes so the fixed-size backbuffer's stretch into the resized client
// area is cancelled.
{
static float appliedAspect = 0.0f;
if (gWindowAspect > 0.0f && gWindowAspect != appliedAspect
&& viewAngle > 0.0f && clipFar > clipNear)
{
appliedAspect = gWindowAspect;
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix,
BTFovYFromHorizontal(viewAngle, appliedAspect), appliedAspect, clipNear, clipFar);
mDecalProjectionMatrix = mProjectionMatrix;
mDecalProjectionMatrix._33 -= mDecalEpsilon;
mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix);
DEBUG_STREAM << "[resize] projection rebuilt (frame): aspect="
<< appliedAspect << "\n" << std::flush;
}
}
// before we execute everything we need to clear
// the render lists
memset(mRenderLists, 0, sizeof(mRenderLists));
// aim-ray feed (task #36): publish the live projection's per-axis scales +
// the BACKBUFFER size (= the dpl2d reticle frame -- the full-screen
// viewport the 2D layer draws in) so the game side can build the reticle
// pick ray / designator projection through the TRUE NDC. NOTE: do NOT
// poll GetViewport here -- at this point it still holds whatever viewport
// the PREVIOUS frame's last pass set (gauge/MFD passes shrink it), which
// made the pick ray flicker frame to frame.
{
extern void BTSetAimProjection(float p11, float p22, float vpW, float vpH);
BTSetAimProjection(mProjectionMatrix._11, mProjectionMatrix._22,
(float)GetWidth(), (float)GetHeight());
}
HierarchicalDrawComponent *drawComp;
SChainIteratorOf<HierarchicalDrawComponent*> iterator(&mRenderables);
while ((drawComp = iterator.ReadAndNext()) != NULL)
drawComp->Execute();
if (mReticle)
mReticle->Execute();
if (mCamShipHUD)
mCamShipHUD->Execute();
gNumBatches = 0;
{ extern int gBTNumCulled; gBTNumCulled = 0; }
static Time lastFrameTime = mTargetRenderTime;
Scalar dT = mTargetRenderTime - lastFrameTime;
lastFrameTime = mTargetRenderTime;
currentFrameTime = Now();
// DIAG (turn-hitch hunt): time the render phases -- draw CPU vs Present
// (GPU-queue block). Logged on slow frames + 1 Hz stats.
LARGE_INTEGER _rt0; QueryPerformanceCounter(&_rt0);
DWORD currentFog;
mDevice->GetRenderState(D3DRS_FOGCOLOR, &currentFog);
hr = mDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, currentFog, 1.0f, 0);
hr = mDevice->BeginScene();
// DOCK-BOTTOM (single-window gauges): keep the world in the top region;
// the gauge strip owns the bottom band (drawn by BTDrawGaugeInset with a
// full-target viewport before EndScene). No-op unless the mode is on.
{
extern void BTApplyWorldViewport(LPDIRECT3DDEVICE9 device);
BTApplyWorldViewport(mDevice);
}
mDevice->SetFVF(L4VERTEX_FVF);
D3DXMATRIX viewTransform;
mDevice->GetTransform(D3DTS_VIEW, &viewTransform);
{
static int dbgVP = 0;
if (dbgVP < 3)
{
D3DXMATRIX projNow;
mDevice->GetTransform(D3DTS_PROJECTION, &projNow);
DEBUG_STREAM << "[VP] VIEW row3=(" << viewTransform._41 << "," << viewTransform._42 << "," << viewTransform._43
<< ") VIEW._33=" << viewTransform._33
<< " | PROJ diag=(" << projNow._11 << "," << projNow._22 << "," << projNow._33 << "," << projNow._34
<< ") PROJ._43=" << projNow._43 << "\n" << std::flush;
++dbgVP;
}
}
// PORT (turn-hitch fix): publish this frame's view frustum so d3d_OBJECT::Draw
// can skip objects fully outside it. The 1995 code drew everything (the IG
// board clipped in hardware) -- ~2700 draw calls/frame = ~107ms CPU on D3D9;
// culling the off-screen set is the port's equivalent of that hardware stage.
// Gate: BT_CULL_FRUSTUM (default ON; =0 restores draw-everything).
{
static int s_fcull = -1;
if (s_fcull < 0)
{
const char *cv = getenv("BT_CULL_FRUSTUM");
s_fcull = (cv == 0 || *cv != '0') ? 1 : 0;
}
// GPU WARM-UP: managed-pool buffers/textures upload to the GPU on FIRST
// USE, so the first frame that reveals a previously-unculled object pays
// its upload right then (one big hitch on the first look around). Draw
// EVERYTHING for the first few frames so all uploads land during mission
// start, then cull normally.
static int s_warmup = 3;
if (s_warmup > 0)
{
--s_warmup;
d3d_OBJECT::SetCullFrustum(NULL);
}
else if (s_fcull)
{
// use the renderer's OWN main projection (mProjectionMatrix), not a
// GetTransform read-back -- the device state between frames can hold a
// pass-specific matrix (sky/decal/HUD), which made the frustum silently
// degenerate for whole stretches (nothing culled).
D3DXMATRIX viewProj;
D3DXMatrixMultiply(&viewProj, &viewTransform, &mProjectionMatrix);
d3d_OBJECT::SetCullFrustum(&viewProj);
}
else
d3d_OBJECT::SetCullFrustum(NULL);
// camera world position for LOD distance selection. Row-vector D3D view
// matrix (LookAt layout): the camera BASIS VECTORS ARE THE COLUMNS
// (xaxis = (_11,_21,_31) etc.) and row 4 = (-eye.x_axis, -eye.y_axis,
// -eye.z_axis), so eye = -(t.x*col1 + t.y*col2 + t.z*col3). (The first
// version used the ROWS as the basis -- a transposed rotation whose
// "position" varied with the camera ANGLE, making LOD bands pop in/out
// as the view rotated: structures AND the mech blinked with turns.)
// LOD EYEPOINT: prefer the GAME-FED viewpoint-entity position (BTSetLodEye
// from mech4 -- the authentic reference: the pod's eyepoint sat ON the
// mech, so turning never changed any LOD distance). Our chase camera
// ORBITS the mech +-40u as it turns, which swept objects near their band
// edges in and out ("scenery blinks with viewing angle", floor flicker at
// the arena fringe). Fall back to the camera extracted from the view
// matrix (eye = -t*colBasis) when the game never fed a position.
extern int gBTLodEyeValid;
if (!gBTLodEyeValid)
{
const float ex = -(viewTransform._41 * viewTransform._11 + viewTransform._42 * viewTransform._12 + viewTransform._43 * viewTransform._13);
const float ey = -(viewTransform._41 * viewTransform._21 + viewTransform._42 * viewTransform._22 + viewTransform._43 * viewTransform._23);
const float ez = -(viewTransform._41 * viewTransform._31 + viewTransform._42 * viewTransform._32 + viewTransform._43 * viewTransform._33);
d3d_OBJECT::SetCameraPosition(ex, ey, ez);
}
}
//
// Start with the opaque pass
//
//
// BACKFACE CULLING (task #20): the bring-up D3DCULL_NONE drew interior/back
// faces the original never rendered -- distant "dark wedge" shapes were the
// INSIDES of dune/butte meshes, and standing inside a crescent-ridge mound
// looked like being sealed in rock (the authentic renderer culls those, so
// you can see out). BT_CULL selects the mode: cw (default) / ccw / off.
//
{
static DWORD s_cull = 0;
if (s_cull == 0)
{
const char *cv = getenv("BT_CULL");
s_cull = (cv == NULL) ? D3DCULL_CW
: (cv[0]=='0' || cv[0]=='n' || cv[0]=='N') ? D3DCULL_NONE
: (cv[0]=='c' && cv[1]=='c') ? D3DCULL_CCW : D3DCULL_CW;
}
mDevice->SetRenderState(D3DRS_CULLMODE, s_cull);
}
//
// LIGHTING (task #20): the maps define their own directional lights (an INI
// light page parsed at map load into sceneLight[] -- SetLight/LightEnable at
// :3065) and the BGF loader computes smooth per-vertex normals (bgfload.cpp:
// 319-456), so the whole authentic pipeline already exists; the old bring-up
// line here force-disabled it. Lit whenever the map shipped lights (env
// BT_LIGHTING=0 falls back to flat); ambient floor keeps the shadow sides
// readable (BT_AMBIENT=<hex> to tune, default 0x404040).
//
{
static int s_lit = -1;
static int s_ambientOverride = 0;
static DWORD s_ambient = 0x00404040;
if (s_lit < 0)
{
const char *lv = getenv("BT_LIGHTING");
s_lit = (lv == 0 || *lv != '0') ? 1 : 0;
const char *av = getenv("BT_AMBIENT");
if (av != 0)
{
s_ambient = (DWORD)strtoul(av, NULL, 16);
s_ambientOverride = 1;
}
}
if (s_lit && sceneLightCount > 0)
{
mDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
// BT (task #20): re-assert the AUTHENTIC env ambient (mEnvAmbient, set
// by the map's INI 'ambient=' page -- e.g. des_day 0.45) each frame,
// NOT the bring-up gray floor. BT_AMBIENT=<hex> still overrides for tuning.
mDevice->SetRenderState(D3DRS_AMBIENT, s_ambientOverride ? s_ambient : mEnvAmbient);
mDevice->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE); // animated joints scale
}
else
{
mDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
}
}
mDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*)(&currentFogNear)));
mDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*)(&currentFogFar)));
// BT (task #63): the arcade fog was EYE-SPACE linear (dpl_fog_type_pixel_lin).
// The port left D3DRS_FOGTABLEMODE = D3DFOG_LINEAR (table/PIXEL fog), whose fog
// factor comes from the perspective-NONLINEAR z-buffer -- and without a WFOG cap
// FOGSTART/END are z-buffer [0,1] units, so the authored 90..1100 range gave a
// fog factor of ~1 (NO fog) across the entire scene: fog was wired but invisible.
// Use VERTEX fog: it is computed per-vertex in EYE SPACE, so FOGSTART/END are the
// authored world near/far and the haze curtain renders across the real range.
// This is the world-geometry pass (SHOULD be fogged; sky/cockpit passes manage
// their own). BT_FOGMODE=table = the old z-based mode (A/B); BT_FOGMODE=off.
{
static const char *s_fm = getenv("BT_FOGMODE");
static const int s_fOff = (s_fm && strcmp(s_fm, "off") == 0) ? 1 : 0;
// Fog MODEL. The arcade used per-PIXEL linear fog (dpl_fog_type_pixel_lin) --
// smooth. D3D fixed-function per-pixel (TABLE) fog is only eye-space (correct
// over the authored near/far) when the GPU exposes D3DPRASTERCAPS_WFOG; without
// it, table fog reads the NONLINEAR z-buffer and the 90..1100 range collapses
// to no fog. Auto-detect: WFOG -> table (smooth, faithful); else VERTEX fog
// (eye-space but per-vertex -> splotchy on coarse terrain tris). s_mode:
// 1=table, 0=vertex. BT_FOGMODE=table|vertex|off forces.
static int s_mode = -1;
if (s_mode < 0)
{
if (s_fm && strcmp(s_fm, "table") == 0) s_mode = 1;
else if (s_fm && strcmp(s_fm, "vertex") == 0) s_mode = 0;
else
{
D3DCAPS9 caps; memset(&caps, 0, sizeof(caps));
s_mode = (SUCCEEDED(mDevice->GetDeviceCaps(&caps))
&& (caps.RasterCaps & D3DPRASTERCAPS_WFOG)) ? 1 : 0;
}
DEBUG_STREAM << "[fog] model=" << (s_mode ? "table/WFOG (per-pixel, smooth)"
: "vertex (per-vertex; splotchy on coarse tris)")
<< " (BT_FOGMODE=table|vertex|off overrides)\n" << std::flush;
}
mDevice->SetRenderState(D3DRS_FOGENABLE, s_fOff ? FALSE : TRUE);
if (!s_fOff)
{
mDevice->SetRenderState(D3DRS_FOGVERTEXMODE, s_mode ? D3DFOG_NONE : D3DFOG_LINEAR);
mDevice->SetRenderState(D3DRS_FOGTABLEMODE, s_mode ? D3DFOG_LINEAR : D3DFOG_NONE);
}
}
mDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
mDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
//
// (task #20 lighting) With lighting ON, take the lit material colors from the
// per-vertex diffuse (the BGF loader bakes the BMF material tint there) --
// no SetMaterial exists in this path and D3D's default material has a BLACK
// ambient (shadow sides would render pitch black).
//
{
const char *lv2 = getenv("BT_LIGHTING");
if ((lv2 == 0 || *lv2 != '0') && sceneLightCount > 0)
{
mDevice->SetRenderState(D3DRS_COLORVERTEX, TRUE);
mDevice->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
mDevice->SetRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_COLOR1);
}
else
{
mDevice->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
}
}
mDevice->SetTextureStageState(2, D3DTSS_COLOROP, D3DTOP_DISABLE);
d3d_OBJECT::ResetState(mDevice);
if (!l4_application->IsDead())
{
std::list<d3d_OBJECT*>::const_iterator iter;
for (iter = this->mConsolidatedStaticObjects.begin(); iter != this->mConsolidatedStaticObjects.end(); ++iter)
{
(*iter)->Draw(PASS_OPAQUE, &viewTransform, mTargetRenderTime);
}
}
// GROUND SHADOWS (task #49b): the classic decal order -- terrain (statics,
// above) -> shadow decals (here) -> dynamic opaque bodies (below). The
// shadow's depth-bias only has to beat the TERRAIN already in the z-buffer;
// the mech drawn AFTER simply z-passes over the shadow, so the bias can
// never paint the shadow over the feet. Drawn with pass id PASS_ALPHABLEND
// so DrawMesh's mIsShadow state-block branch (blend + bias + restore) runs
// unchanged.
for (d3d_OBJECT *obj = mRenderLists[PASS_SHADOW]; obj != NULL; obj = obj->GetNext(PASS_SHADOW))
obj->Draw(PASS_ALPHABLEND, &viewTransform, mTargetRenderTime);
for (d3d_OBJECT *obj = mRenderLists[PASS_OPAQUE]; obj != NULL; obj = obj->GetNext(PASS_OPAQUE))
obj->Draw(PASS_OPAQUE, &viewTransform, mTargetRenderTime);
//
// Next up is the decal pass
//
mDevice->SetTransform(D3DTS_PROJECTION, &mDecalProjectionMatrix);
if (!l4_application->IsDead())
{
std::list<d3d_OBJECT*>::const_iterator iter;
for (iter = this->mConsolidatedStaticObjects.begin(); iter != this->mConsolidatedStaticObjects.end(); ++iter)
{
(*iter)->Draw(PASS_DECAL, &viewTransform, mTargetRenderTime);
}
}
for (d3d_OBJECT *obj = mRenderLists[PASS_DECAL]; obj != NULL; obj = obj->GetNext(PASS_DECAL))
obj->Draw(PASS_DECAL, &viewTransform, mTargetRenderTime);
//
// The sphere pass
//
//Set it up so the fog won't affect spheres' colors so much- extend the near fog plane out
float fogModNear = currentFogFar;
float fogModFar = fogModNear + (currentFogFar - currentFogNear);
mDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*)(&fogModNear)));
mDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*)(&fogModFar)));
for (d3d_OBJECT *obj = mRenderLists[PASS_SPHERE]; obj != NULL; obj = obj->GetNext(PASS_SPHERE))
obj->Draw(PASS_SPHERE, &viewTransform, mTargetRenderTime);
//
// The sky pass
//
// BT (task #20): HORIZON SEAM fix. The sky is a FLAT plane at Y~110 spanning
// +/-6000 (not a dome). Reusing the world far=2100 projection TRUNCATES that
// plane a few degrees above the true horizon, and the gap below its rim shows
// the frame Clear = fog colour -> a hard lavender band. Also the old code
// extended sky fog to near*3/far*6, leaving the plane's rim near-unfogged
// (saturated blue edge above the band). FIX: (a) a sky-only projection with
// a far plane large enough for the plane to reach the horizon; (b) the SAME
// fog as the world so the descended rim hazes to the fog colour continuously.
// Both restored after the sky loop (the alpha-blend/particle passes MUST run
// under the world projection + world fog). BT_SKY_FAR=0 restores the old
// truncated behaviour for A/B.
static const int s_skyFar =
(getenv("BT_SKY_FAR") == 0 || getenv("BT_SKY_FAR")[0] != '0');
if (s_skyFar)
{
// world fog (no *3/*6): the sky plane's far edge fogs to the horizon colour.
mDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*)(&currentFogNear)));
mDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*)(&currentFogFar)));
D3DXMATRIX skyProj;
{
const float skyAspect = gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size;
D3DXMatrixPerspectiveFovRH(&skyProj,
BTFovYFromHorizontal(viewAngle, skyAspect), skyAspect, clipNear, 9000.0f);
}
mDevice->SetTransform(D3DTS_PROJECTION, &skyProj);
}
else
{
//Further extend the fog distance
fogModNear = currentFogNear * 3;
fogModFar = currentFogFar * 6;
mDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*)(&fogModNear)));
mDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*)(&fogModFar)));
mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix);
}
// BT fix (task #20): the SKY is pre-shaded art and must draw FULLBRIGHT --
// with scene lighting enabled (the task-#20 lighting revival) the dome was
// being lit like world geometry: daylight on the sun side, black on the far
// side ("the sky is half day and half black"). Exempt the sky pass.
DWORD skySavedLighting = FALSE;
mDevice->GetRenderState(D3DRS_LIGHTING, &skySavedLighting);
mDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
// BT (task #20): SCREEN-toward-white combine for the sky (was MODULATE).
// The cloud art (bintA) is a GRAYSCALE intensity map; the sky's colour is the
// material tint (0.3,0.5,1.0) baked into the vertex diffuse. MODULATE (texel
// x tint) gave saturated NAVY with dark banding ("two-tone, nothing like
// clouds"). The original combined them as a SCREEN / lerp-to-white:
// screen(D,T) = T + D*(1-T) = lerp(D, white, T) -- bright cloud texels -> near
// white, dark -> the pale blue tint. One texture stage does it exactly:
// D3DTOP_LERP(Arg0=TEXTURE, Arg1=TFACTOR=white, Arg2=DIFFUSE) = T*1 + D*(1-T).
// Measured screen(0.3,0.5,1.0, 0.85)=(228,236,255) ~ original top (229,230,255).
// NOW DEFAULT OFF: the sky material (dsky_mtl) has its own 'sky' RAMP
// (0,0,0.6 -> 0.99,0.99,0.99) which the loader bakes into the sky texture and
// draws with a white vertex/material -- the authentic path with proper WHITES.
// The screen combine was the pre-ramp heuristic; BT_SKY_SCREEN=1 re-enables it.
static const int s_skyScreen =
(getenv("BT_SKY_SCREEN") != 0 && getenv("BT_SKY_SCREEN")[0] != '0');
DWORD skyOp = 0, skyA0 = 0, skyA1 = 0, skyA2 = 0, skyTF = 0;
if (s_skyScreen)
{
mDevice->GetTextureStageState(0, D3DTSS_COLOROP, &skyOp);
mDevice->GetTextureStageState(0, D3DTSS_COLORARG0, &skyA0);
mDevice->GetTextureStageState(0, D3DTSS_COLORARG1, &skyA1);
mDevice->GetTextureStageState(0, D3DTSS_COLORARG2, &skyA2);
mDevice->GetRenderState(D3DRS_TEXTUREFACTOR, &skyTF);
mDevice->SetRenderState(D3DRS_TEXTUREFACTOR, 0xFFFFFFFF);
mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_LERP);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG0, D3DTA_TEXTURE);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TFACTOR);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
}
if (!l4_application->IsDead())
{
std::list<d3d_OBJECT*>::const_iterator iter;
for (iter = this->mConsolidatedStaticObjects.begin(); iter != this->mConsolidatedStaticObjects.end(); ++iter)
{
(*iter)->Draw(PASS_SKY, &viewTransform, mTargetRenderTime);
}
}
for (d3d_OBJECT *obj = mRenderLists[PASS_SKY]; obj != NULL; obj = obj->GetNext(PASS_SKY))
obj->Draw(PASS_SKY, &viewTransform, mTargetRenderTime);
if (s_skyScreen)
{
mDevice->SetTextureStageState(0, D3DTSS_COLOROP, skyOp);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG0, skyA0);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, skyA1);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, skyA2);
mDevice->SetRenderState(D3DRS_TEXTUREFACTOR, skyTF);
}
mDevice->SetRenderState(D3DRS_LIGHTING, skySavedLighting); // restore for the world
// BT (task #20): RESTORE the world projection after the sky pass -- the
// alpha-blend (explosions/effects), particle, and reticle/HUD passes below
// MUST render under the world far=2100 projection, not the sky's far=9000.
if (s_skyFar)
mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix);
//Reactivate fog
mDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*)(&currentFogNear)));
mDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*)(&currentFogFar)));
//
// Finally we do the alpha blend pass
//
mDevice->SetRenderState(D3DRS_ZWRITEENABLE,false);
mDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,true);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2);
if (!l4_application->IsDead())
{
std::list<d3d_OBJECT*>::const_iterator iter;
for (iter = this->mConsolidatedStaticObjects.begin(); iter != this->mConsolidatedStaticObjects.end(); ++iter)
{
(*iter)->Draw(PASS_ALPHABLEND, &viewTransform, mTargetRenderTime);
}
}
for (d3d_OBJECT *obj = mRenderLists[PASS_ALPHABLEND]; obj != NULL; obj = obj->GetNext(PASS_ALPHABLEND))
obj->Draw(PASS_ALPHABLEND, &viewTransform, mTargetRenderTime);
// BT weapon beams (port addition): draw + age the queued muzzle->hit beams
// here in the alpha pass (world projection + view already set; Z-test on so a
// beam is occluded where it enters terrain). See BTDrawBeams above.
BTDrawBeams(mDevice, &viewTransform, (float)dT);
// BT .PFX particle effects (explosions / damage bands / mech death) -- the
// same pass as the beams: world proj+view set, Z-test on, additive quads.
{
extern void BTDrawPfx(LPDIRECT3DDEVICE9 dev, const D3DXMATRIX *view, float dt);
BTDrawPfx(mDevice, &viewTransform, (float)dT);
}
// BT translocation spheres (task #52): the authentic POVTranslocateRenderable
// warp -- collapse onto the dying eye, throb black, then expand-reveal the reborn
// world. Drawn out-of-band here (as SKY, opaque) even while IsDead so the sphere
// survives the world mask it itself raises via BTSetWorldDead (below).
{
extern void BTDrawTranslocationSpheres(LPDIRECT3DDEVICE9 dev,
const D3DXMATRIX *view, float dt, Time frame_time);
BTDrawTranslocationSpheres(mDevice, &viewTransform, (float)dT, mTargetRenderTime);
}
// The BT targeting reticle / weapon pips (2D screen space, cockpit view
// only -- the dpl2d layer; see game/reconstructed/dpl2d.cpp).
{
extern void BTDrawReticle(struct IDirect3DDevice9 *device);
BTDrawReticle(mDevice);
}
//
// And don't forget particles too
//
D3DXMATRIX ident;
mDevice->SetTransform(D3DTS_WORLD, D3DXMatrixIdentity(&ident));
if (!l4_application->IsDead())
{
ParticleEngine::RenderParticles(&viewTransform, dT);
for (int i=0; i<MAX_INDIE_EMITTERS; ++i)
myPSFXEmitters[i].Execute();
}
//
// Wrap it up by doing the 2D pass
//
mDevice->SetFVF(L4VERTEX_2D_FVF);
mDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
mDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
mDevice->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1);
mDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
if (mReticle && !l4_application->IsDead())
mReticle->Render(0, &viewTransform);
if (mCamShipHUD)
mCamShipHUD->Render(0, &viewTransform);
// DEV-COMPOSITE: in DOCKED mode (BT_DEV_GAUGES_DOCK) blit the 6-surface gauge panel
// into this window as the LAST draw before EndScene (no-op otherwise / off pod).
extern void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device);
BTDrawGaugeInset(mDevice);
// DIAG (off by default): BT_SHOT=<path> dumps this instance's backbuffer to a
// PNG every 90 frames -- non-disruptive per-instance frame capture without
// foregrounding the window. Built for the -net render glitch (task #53);
// moved AFTER the 2D pass (2026-07-18, camera-seat bring-up) so captures
// include the HUD overlays (reticle, camera-ship ranking window), and AFTER
// the docked gauge inset (2026-07-19, Gitea #10 audit -- the 07-18 reorder
// had left the capture BEFORE BTDrawGaugeInset, so BT_DEV_GAUGES_DOCK shots
// were missing the gauge panel the screen actually shows).
{
const char *shotPath = getenv("BT_SHOT");
if (shotPath)
{
static int s_btShotN = 0;
++s_btShotN;
if (s_btShotN >= 90 && (s_btShotN % 90) == 0)
{
IDirect3DSurface9 *bb = 0;
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &bb)) && bb)
{
HRESULT hr = D3DXSaveSurfaceToFileA(shotPath, D3DXIFF_PNG, bb, 0, 0);
bb->Release();
DEBUG_STREAM << "[btshot] frame " << s_btShotN << " -> " << shotPath
<< " hr=0x" << std::hex << (unsigned)hr << std::dec << "\n" << std::flush;
}
else
DEBUG_STREAM << "[btshot] GetBackBuffer FAILED frame " << s_btShotN << "\n" << std::flush;
}
}
}
hr = mDevice->EndScene();
// DEV-COMPOSITE: default mode -- render the 6 cockpit surfaces into a SEPARATE window
// (its own additional swap chain on this device) + present it, between the main
// EndScene and the main Present. No-op unless BT_DEV_GAUGES (and not docked mode).
extern void BTGaugeWindowRenderAndPresent(LPDIRECT3DDEVICE9 device);
BTGaugeWindowRenderAndPresent(mDevice);
// DIAG (turn-hitch hunt): draw CPU is _rt0..here; Present blocks on the GPU.
LARGE_INTEGER _rt1; QueryPerformanceCounter(&_rt1);
// COCKPIT LETTERBOX: uniform-scale the canvas into the client (NULL = the
// old full-client stretch, which is what every non-cockpit run gets).
RECT _ckDestStorage;
const RECT *_ckDest = BTCockpitPresentDest(mPresentParams.hDeviceWindow, &_ckDestStorage);
hr = mDevice->Present(NULL, _ckDest, NULL, NULL);
// The wait-screen overlay stands down PERMANENTLY the moment the first
// REAL scene frame presents (user report 2026-07-22: the launch handoff
// briefly interleaved idle-overlay presents with game frames = flicker).
extern Logical gBTSceneHasPresented;
if (!gBTSceneHasPresented)
DEBUG_STREAM << "[waitscreen] scene LIVE (first real present) t="
<< GetTickCount() << std::endl << std::flush;
gBTSceneHasPresented = True;
{
LARGE_INTEGER _rt2, _rf; QueryPerformanceCounter(&_rt2); QueryPerformanceFrequency(&_rf);
const double drawMs = (double)(_rt1.QuadPart - _rt0.QuadPart) * 1000.0 / (double)_rf.QuadPart;
const double presentMs = (double)(_rt2.QuadPart - _rt1.QuadPart) * 1000.0 / (double)_rf.QuadPart;
static double sAcc = 0.0, sMaxD = 0.0, sMaxP = 0.0; static int sFrames = 0;
sAcc += drawMs + presentMs; ++sFrames;
// RENDER-frame heading probe (BT_RENDHDG): count render frames that redraw
// the SAME peer heading (stale between sim updates) vs frames where it moved.
// sameHeading >> moved => the peer sim updates slower than the render draws,
// so the rotation stutters regardless of how smooth each sim step is.
if (getenv("BT_RENDHDG"))
{
extern volatile float gBTReplRenderYaw;
static float sPrevY = -999.0f, sMaxStep = 0.0f, sSumStep = 0.0f;
static int sN = 0, sBack = 0; static double sHAcc = 0.0;
const float y = gBTReplRenderYaw;
if (sPrevY > -900.0f)
{
float d = y - sPrevY; // per-RENDER-frame heading change
if (d > 3.14159f) d -= 6.28319f; // unwrap
if (d < -3.14159f) d += 6.28319f;
const float ad = (d < 0.0f) ? -d : d;
sSumStep += ad; if (ad > sMaxStep) sMaxStep = ad;
if (d > 0.0f) ++sBack; // spin is -yaw here; +step == backward
++sN;
}
sPrevY = y; sHAcc += drawMs + presentMs;
if (sHAcc >= 1000.0 && sN > 0)
{
const float avg = sSumStep / sN;
DEBUG_STREAM << "[rendhdg] avgStep=" << avg << " maxStep=" << sMaxStep
<< " max/avg=" << (avg > 1e-6f ? sMaxStep / avg : 0.0f)
<< " backwardFrames=" << sBack << "/" << sN
<< " (smooth: max/avg~1, backward~0)\n" << std::flush;
sMaxStep = 0.0f; sSumStep = 0.0f; sN = 0; sBack = 0; sHAcc = 0.0;
}
}
if (drawMs > sMaxD) sMaxD = drawMs;
if (presentMs > sMaxP) sMaxP = presentMs;
if (drawMs + presentMs > 150.0)
DEBUG_STREAM << "[rslow] draw=" << drawMs << "ms present=" << presentMs
<< "ms batches=" << gNumBatches << "\n" << std::flush;
if (sAcc >= 1000.0)
{
extern int gBTNumCulled;
DEBUG_STREAM << "[rstat] frames=" << sFrames << " avg=" << (sAcc / sFrames)
<< "ms maxDraw=" << sMaxD << " maxPresent=" << sMaxP
<< " batches=" << gNumBatches << " culled=" << gBTNumCulled << "\n" << std::flush;
sAcc = 0.0; sFrames = 0; sMaxD = 0.0; sMaxP = 0.0;
}
}
if (hr == D3DERR_DEVICELOST)
{
if (mWaitOverlaySurface != NULL)
{
mWaitOverlaySurface->Release(); // D3DPOOL_DEFAULT: pre-Reset
mWaitOverlaySurface = NULL;
}
int bbCount = mPresentParams.BackBufferCount;
int bbWidth = mPresentParams.BackBufferWidth;
int bbHeight = mPresentParams.BackBufferHeight;
ParticleEngine::Destroy();
V(mDevice->Reset(&mPresentParams));
ParticleEngine::Initialize(mDevice);
this->SetCoreRenderStates();
mPresentParams.BackBufferCount = bbCount;
mPresentParams.BackBufferWidth = bbWidth;
mPresentParams.BackBufferHeight = bbHeight;
BTVerifyCockpitCanvasAfterReset(mDevice); // Gitea #56 guard
}
ticks = HiResNowTicks();
#ifdef LOGFRAMERATE
fputc(1, FRAMERATE_LOG);
fwrite(&ticks, sizeof(__int64), 1, FRAMERATE_LOG);
#endif
}
//
// BTWaitScreenPaint -- the animated "waiting" screen (join/join_lan UX fix,
// 2026-07-22). Pure GDI over the main window: used while the relay seat-wait
// loop blocks startup (no D3D presenting yet -> flicker-free) and re-painted
// after each ExecuteIdle Present during WaitingForEgg (the black idle frame).
// A 12-segment spinner phases on the tick clock; text centered above it.
//
static void BTWaitScreenPaintDC(HDC dc, int w, int h,
const char *line1, const char *line2)
{
RECT rc = { 0, 0, w, h };
FillRect(dc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
static HFONT s_font = NULL, s_fontDim = NULL;
if (s_font == NULL)
{
s_font = CreateFontA(30, 0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
FIXED_PITCH | FF_MODERN, "Consolas");
s_fontDim = CreateFontA(20, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
FIXED_PITCH | FF_MODERN, "Consolas");
}
SetBkMode(dc, TRANSPARENT);
HGDIOBJ old_font = SelectObject(dc, s_font);
SetTextColor(dc, RGB(64, 255, 64));
RECT r1 = { 0, h / 2 - 70, w, h / 2 - 30 };
DrawTextA(dc, line1, -1, &r1, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
SelectObject(dc, s_fontDim);
SetTextColor(dc, RGB(24, 140, 24));
RECT r2 = { 0, h / 2 - 24, w, h / 2 + 8 };
DrawTextA(dc, line2, -1, &r2, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
SelectObject(dc, old_font);
// the spinner: 12 segments, three brightness tiers marching clockwise
int cx = w / 2, cy = h / 2 + 80, r_in = 18, r_out = 34;
int phase = (int)((GetTickCount() / 80UL) % 12UL);
for (int i = 0; i < 12; ++i)
{
int age = (i - phase + 12) % 12;
int lum = (age == 0) ? 255 : (age < 3) ? 150 : 60;
HPEN pen = CreatePen(PS_SOLID, 4, RGB(lum / 4, lum, lum / 4));
HGDIOBJ old_pen = SelectObject(dc, pen);
double a = i * 3.14159265 / 6.0;
MoveToEx(dc, cx + (int)(r_in * cos(a)), cy + (int)(r_in * sin(a)), NULL);
LineTo(dc, cx + (int)(r_out * cos(a)), cy + (int)(r_out * sin(a)));
SelectObject(dc, old_pen);
DeleteObject(pen);
}
}
void BTWaitScreenPaint(const char *line1, const char *line2)
{
extern void *BTResolveMainWindow();
HWND wnd = (HWND)BTResolveMainWindow();
if (wnd == NULL)
return;
HDC dc = GetDC(wnd);
if (dc == NULL)
return;
RECT rc;
GetClientRect(wnd, &rc);
BTWaitScreenPaintDC(dc, rc.right - rc.left, rc.bottom - rc.top,
line1, line2);
ReleaseDC(wnd, dc);
}
// One-way per-process latch: the real render path has presented a frame.
// ExecuteIdle (the wait-screen overlay) stands down for good once set -- the
// launch handoff must never interleave an idle black-clear/overlay present
// with live game frames.
Logical gBTSceneHasPresented = False;
//
// BTLoadPump -- throttled message pump + wait-screen repaint for the LONG
// SYNCHRONOUS stretches of mission load (thousands of small model/texture
// loads back-to-back starve the message pump; the window ghosts "Not
// Responding" and the animation freezes -- user report 2026-07-22). Called
// from the model-load hot path (L4D3D LoadObject); at most one pump per
// 150ms so the load itself is unaffected. Pre-run states only: once the
// scene is live the render loop owns pumping and this is a no-op.
//
void BTLoadPump(void)
{
if (gBTSceneHasPresented)
return;
static unsigned long s_lastPump = 0;
unsigned long now = GetTickCount();
if (now - s_lastPump < 150UL)
return;
s_lastPump = now;
MSG msg;
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
BTWaitScreenPaint("LOADING MISSION", "stand by");
}
void DPLRenderer::ExecuteIdle()
{
HRESULT hr;
if (gBTSceneHasPresented)
return; // game graphics own the screen now
// Issue #40 (wait screen burned 45-50% GPU, field 2026-07-23): the idle
// loop Cleared+Presented at an UNCAPPED rate for the whole join wait.
// Cap it at ~40 Hz -- the overlay's own change gate runs at 12.5 Hz, so
// nothing visible changes faster than that anyway. The 5 ms sleep also
// keeps the caller's spin off a CPU core.
{
static unsigned long s_lastIdlePresent = 0;
unsigned long now = GetTickCount();
if (now - s_lastIdlePresent < 25UL)
{
Sleep(5);
return;
}
s_lastIdlePresent = now;
}
{
static unsigned long s_lastIdleLog = 0;
unsigned long now = GetTickCount();
if (now - s_lastIdleLog > 1000UL)
{
s_lastIdleLog = now;
DEBUG_STREAM << "[waitscreen] idle paint t=" << now << " state="
<< (application ? (int)application->GetApplicationState() : -1)
<< std::endl << std::flush;
}
}
hr = mDevice->Clear(0, NULL, D3DCLEAR_TARGET, 0xFF000000, 1.0f, 0);
hr = mDevice->BeginScene();
hr = mDevice->EndScene();
// The waiting screen (join/join_lan UX fix 2026-07-22; FLICKER fix
// 2026-07-22; LOAD-SPEED fix 2026-07-23): paint the text + spinner INTO
// the presented frame -- but NEVER via GDI on a DEFAULT-pool surface
// per frame: GetDC/FillRect on a GPU surface forces a full pipeline
// sync (~140ms/frame on the operator GPU at glass size), which throttled
// the pre-run loop to ~7 iterations/s and multiplied every mission load
// (stack-sampled: 4/6 samples inside the overlay FillRect/GetDC; drain
// census: same 560 events at 190/s dev vs 7/s glass+gauges). Now: the
// GDI paint goes to a SYSTEMMEM staging surface (GetDC there is pure
// CPU), uploaded with UpdateSurface, and the whole frame is SKIPPED
// unless the spinner phase or text changed (12.5 Hz) -- between paints
// the loop runs free and the event queue drains at full speed.
{
const char *wait_line1_probe = ""; // (filled below; declared for the gate)
(void)wait_line1_probe;
const char *wait_line1 = "WAITING FOR MISSION ASSIGNMENT";
const char *wait_line2 = "the operator starts the session from the console";
switch (application ? application->GetApplicationState() : -1)
{
case Application::LoadingMission:
case Application::LaunchingMission:
wait_line1 = "LOADING MISSION";
wait_line2 = "stand by";
break;
case Application::WaitingForLaunch:
wait_line1 = "MISSION READY";
wait_line2 = "waiting for the operator to launch";
break;
}
// The backbuffer refuses GetDC on this driver (D3DERR_INVALIDCALL
// without D3DPRESENTFLAG_LOCKABLE_BACKBUFFER, which we won't force on
// the whole game). Standard workaround: GDI-paint into a cached
// OFFSCREEN PLAIN surface (GetDC always works there) and StretchRect
// it onto the backbuffer pre-Present. The cache is D3DPOOL_DEFAULT,
// released on device loss below.
// CHANGE GATE: repaint/present only when the spinner phase or the
// text changes -- otherwise skip the whole idle frame (the last
// presented image stays on screen; nothing else draws pre-run).
{
static unsigned long s_lastPhase = 0xFFFFFFFF;
static const char *s_lastLine1 = NULL;
unsigned long phase = (GetTickCount() / 80UL) % 12UL;
if (phase == s_lastPhase && wait_line1 == s_lastLine1)
{
return; // nothing changed: free the loop
}
s_lastPhase = phase;
s_lastLine1 = wait_line1;
}
Logical painted_in_backbuffer = False;
IDirect3DSurface9 *backbuffer = NULL;
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO,
&backbuffer)) && backbuffer != NULL)
{
D3DSURFACE_DESC desc;
if (SUCCEEDED(backbuffer->GetDesc(&desc)))
{
if (mWaitOverlaySurface != NULL)
{
D3DSURFACE_DESC have;
if (FAILED(mWaitOverlaySurface->GetDesc(&have))
|| have.Width != desc.Width
|| have.Height != desc.Height)
{
mWaitOverlaySurface->Release();
mWaitOverlaySurface = NULL;
}
}
if (mWaitOverlaySurface == NULL)
{
// SYSTEMMEM: GetDC is pure CPU (no GPU sync) and the
// surface survives device Reset for free.
mDevice->CreateOffscreenPlainSurface(desc.Width,
desc.Height, D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM,
&mWaitOverlaySurface, NULL);
}
HDC surface_dc = NULL;
if (mWaitOverlaySurface != NULL
&& SUCCEEDED(mWaitOverlaySurface->GetDC(&surface_dc))
&& surface_dc != NULL)
{
BTWaitScreenPaintDC(surface_dc, (int)desc.Width,
(int)desc.Height, wait_line1, wait_line2);
mWaitOverlaySurface->ReleaseDC(surface_dc);
// SYSTEMMEM -> DEFAULT is UpdateSurface's job
// (StretchRect cannot take a sysmem source).
if (SUCCEEDED(mDevice->UpdateSurface(mWaitOverlaySurface,
NULL, backbuffer, NULL)))
{
painted_in_backbuffer = True;
}
}
}
backbuffer->Release();
}
// same letterbox as the scene present -- the wait overlay paints into
// this backbuffer, so it has to land in the same rect
RECT wait_dest_storage;
const RECT *wait_dest =
BTCockpitPresentDest(mPresentParams.hDeviceWindow, &wait_dest_storage);
HRESULT present_hr = mDevice->Present(NULL, wait_dest, NULL, NULL);
static int s_wait_path_logged = 0;
if (!s_wait_path_logged)
{
s_wait_path_logged = 1;
DEBUG_STREAM << "[waitscreen] paint path: "
<< (painted_in_backbuffer ? "BACKBUFFER (flicker-free)"
: "window-DC FALLBACK (GetDC failed)")
<< std::endl << std::flush;
}
if (!painted_in_backbuffer)
{
BTWaitScreenPaint(wait_line1, wait_line2);
}
if (present_hr == D3DERR_DEVICELOST)
{
if (mWaitOverlaySurface != NULL)
{
mWaitOverlaySurface->Release(); // D3DPOOL_DEFAULT: must go pre-Reset
mWaitOverlaySurface = NULL;
}
int bbCount = mPresentParams.BackBufferCount;
int bbWidth = mPresentParams.BackBufferWidth;
int bbHeight = mPresentParams.BackBufferHeight;
ParticleEngine::Destroy();
V(mDevice->Reset(&mPresentParams));
ParticleEngine::Initialize(mDevice);
this->SetCoreRenderStates();
mPresentParams.BackBufferCount = bbCount;
mPresentParams.BackBufferWidth = bbWidth;
mPresentParams.BackBufferHeight = bbHeight;
BTVerifyCockpitCanvasAfterReset(mDevice); // Gitea #56 guard
}
}
}
//
//#############################################################################
// DPLDelayDCSFlush and DPLDoDCSBatchFlush queue up a list of DCS pointers
// for later std::flushing in one big batch.
//#############################################################################
//
void
DPLRenderer::DPLDelayDCSFlush(
dpl_DCS *my_dcs) // The DCS we want to remember for later
{
//
// Make sure the array hasn't become overfilled somehow and make
// sure the DCS is valid.
//
Verify(delayedDCSCount <= DELAY_DCS_FLUSH_ARRAY_SIZE);
Check_Pointer(my_dcs);
//
// If the array is full, std::flush it out to make space for this DCS
//
if(delayedDCSCount == DELAY_DCS_FLUSH_ARRAY_SIZE)
{
DPLDoDCSBatchFlush();
}
//
// Add the New DCS to the list
//
delayDCSFlushArray[delayedDCSCount++] = my_dcs;
}
void
DPLRenderer::DPLDoDCSBatchFlush() // Flush the dcs's remembered by DPLDelayDCSFlush
{
//STUBBED: DPL RB 1/14/07
//SET_VIDEO_BATCH_FLUSH();
////
//// Make sure the array hasn't become overfilled somehow
////
//Verify(delayedDCSCount <= DELAY_DCS_FLUSH_ARRAY_SIZE);
////
//// Flush the array and reset the counters that go with it
////
//if(delayedDCSCount != 0)
//{
// delayDCSFlushArray[delayedDCSCount] = NULL;
// dpl_FlushDCSArticulations(delayDCSFlushArray);
// delayedDCSCount = 0;
//}
//CLEAR_VIDEO_BATCH_FLUSH();
}
//
//#############################################################################
// DPLReportFreeMemory writes current free memory in graphics card to any
// output stream.
//#############################################################################
//
void
DPLRenderer::DPLReportFreeMemory(std::ostream &output)
{
//STUBBED: DPL RB 1/14/07
// output << "Free memory in card: " << dpl_FreeMemory() << " bytes." << std::endl;
return;
}
//
//#############################################################################
// DPLReportPerfStats writes performance statistics to std::cout (stdout).
//#############################################################################
//
void
DPLRenderer::DPLReportPerfStats(std::ostream &output)
{
//STUBBED: DPL RB 1/15/07
////-----------------------------------------------------
//// HACK - copied from camera.c must re-copy if changed
////
//// case '?':
//// printf ("sect time %d dcs 0x%x inst 0x%x\n",
//// __sect_time, sect_dcs, sect_inst );
////-----------------------------------------------------
//output << "sect time " << __sect_time <<
// " dcs and inst not available" << std::endl;
////------------------------------------------------------------
//// HACK - copied from dpl_vpx.c must re-copy if changed
////
//// void dpl_PerfStats(void)
//// printf ( "cull %d draw %d frame %d pxpl %d prims %d\n",
//// __last_cull_time,
//// __last_draw_time,
//// __last_frame_time,
//// __last_pxpl_time,
//// __last_frame_prims );
////------------------------------------------------------------
//output <<
// "cull " << __last_cull_time <<
// " draw " << __last_draw_time <<
// " frame " << __last_frame_time <<
// " pxpl " << __last_pxpl_time <<
// " prims " << __last_frame_prims <<
// std::endl;
//if(statistics_started)
//{
// ReportStatistics();
//}
//else
//{
// ResetStatistics();
// statistics_started = True;
//}
//return;
}
//
//#############################################################################
// DPLToggleWireframe toggles the state of dpl global wireframe.
//#############################################################################
//
void
DPLRenderer::DPLToggleWireframe()
{
//STUBBBED: DPL RB 1/14/07
//static Logical wireframe_on = 0;
//if ((wireframe_on ^= 1) != 0)
//{
// DEBUG_STREAM << "wireframe ON" << std::endl << std::flush;
// dpl_SetRenderProperty(dpl_render_prop_wireframe, dpl_render_value_on, NULL );
//}
//else
//{
// DEBUG_STREAM << "wireframe OFF" << std::endl << std::flush;
// dpl_SetRenderProperty(dpl_render_prop_wireframe, dpl_render_value_off, NULL );
//}
}
//
//#############################################################################
// DPLTogglePVision toggles the state of dpl "predator" vision.
//#############################################################################
//
void
DPLRenderer::DPLTogglePVision()
{
//STUBBED: DPL RB 1/14/07
//static Logical pvision_on = 0;
//dpl_EXPLOSION_EFFECT_INFO sfx_info;
//sfx_info.x = sfx_info.y = sfx_info.z = 0;
//if ((pvision_on ^= 1) != 0)
//{
// DEBUG_STREAM << "pvision ON" << std::endl << std::flush;
// sfx_info.type = -1;
//}
//else
//{
// DEBUG_STREAM << "pvision OFF" << std::endl << std::flush;
// sfx_info.type = -2;
//}
//dpl_Effect(dpl_effect_type_explosion, NULL, &sfx_info);
//return;
}
//
//#############################################################################
// DPLFrameDump writes screen image to targa file.
//#############################################################################
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// oneD_filter() antialiased framegrab support
void
oneD_filter(
uint32 *linebuffer,
unsigned short *rframe,
unsigned short *gframe,
unsigned short *bframe,
int32 y,
int32 x_size
)
{
int32
pos,
i,
r, g, b;
unsigned char
*clinebuf = (unsigned char *)linebuffer;
pos = y * x_size;
for (i=0; i<x_size; ++i)
{
r = rframe[pos];
g = gframe[pos];
b = bframe[pos];
++pos;
*clinebuf++ = (unsigned char)((r >> 8) & 0xff);
*clinebuf++ = (unsigned char)((g >> 8) & 0xff);
*clinebuf++ = (unsigned char)((b >> 8) & 0xff);
}
return;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// dump_frame_buffer()
void
dump_frame_buffer(
dpl_VIEW *eye,
int32 x_size,
int32 y_size,
Logical antialias
)
{
//STUBBED: DPL RB 1/14/07
////do not check eye here
////----------------------------------------
//// control variable and operating buffers
////----------------------------------------
//static uint32
// dumped_frames = 0,
// *line_buffer = NULL;
//static unsigned short
// *rframebuffer = NULL,
// *gframebuffer = NULL,
// *bframebuffer = NULL;
//if (eye == NULL)
//{
// //--------------------------
// // release allocated memory
// //--------------------------
// if (line_buffer)
// {
// Unregister_Pointer(line_buffer);
// delete line_buffer;
// }
// if (rframebuffer)
// {
// Unregister_Pointer(rframebuffer);
// delete rframebuffer;
// }
// if (gframebuffer)
// {
// Unregister_Pointer(gframebuffer);
// delete gframebuffer;
// }
// if (bframebuffer)
// {
// Unregister_Pointer(bframebuffer);
// delete bframebuffer;
// }
// return;
//}
//Check_Pointer(eye);
////-----------------------------------------------------------------
//// AA kernel definition:
////
//// normalized screen coordinates are -1.0 .. +1.0
//// these map (in NTSC) to 705 and 512.
//// So a pixel in x is
//// 2.0 / 704 = 2.841e-3
//// in y = 3.906e-3
//// for the re-use 5-sample kernel we displace by 0.5 pixels ...
////-----------------------------------------------------------------
//static const int32 kernel_size = 4;
//static const float xk = 2.0f / x_size;
//static const float yk = 2.0f / y_size;
//static const float ox = xk / 4.0f;
//static const float oy = yk / 4.0f;
//static const float jx = 0.0f; // ox / 4.0f
//static const float jy = 0.0f; // oy / 4.0f
//static const float x_kernel[4] = { -(ox+jx), ox-jx, jx-ox, jx+ox };
//static const float y_kernel[4] = { oy-jy, oy+jy, -(jy+oy), jy-oy };
//static const int kernel_weights[4] = { 64, 64, 64, 64 };
////---------------------
//// operating variables
////---------------------
//int32 passes = (antialias)?kernel_size:1;
//int32 size = x_size * y_size;
//int32 frameptr;
//int32 i, x, y;
//DEBUG_STREAM << "Dump frame buffer (antialias=" <<
// ((antialias)?"True":"False") << ") - press Esc to cancel." << std::endl;
////----------------------------
//// allocate operating buffers
////----------------------------
//if (line_buffer == NULL)
//{
// //-----------------------------------------------------------------------
// // NOTE - must call dump_frame_buffer(NULL, NULL, NULL, NULL) to release
// //-----------------------------------------------------------------------
// line_buffer = new uint32[1024];
// Register_Pointer(line_buffer);
// rframebuffer = new unsigned short[size];
// Register_Pointer(rframebuffer);
// gframebuffer = new unsigned short[size];
// Register_Pointer(gframebuffer);
// bframebuffer = new unsigned short[size];
// Register_Pointer(bframebuffer);
//}
////------------------------------
//// clear out r g b framebuffers
////------------------------------
//frameptr = 0;
//for (i=0; i<size; ++i)
//{
// rframebuffer[frameptr] = 0x0;
// gframebuffer[frameptr] = 0x0;
// bframebuffer[frameptr] = 0x0;
// ++frameptr;
//}
//dpl_RedrawScene();
//dpl_RedrawScene();
////--------------------------------
//// perform AA framecapture passes
////--------------------------------
//int this_weight;
//float32 x0, y0, x1, y1, zeye;
//for (i=0; i<passes; ++i)
//{
// DEBUG_STREAM << " pass " << (i+1) << " of " << passes << " " << std::flush;
// //------------
// // jitter eye
// //------------
// dpl_GetViewProjection(eye, &x0, &y0, &x1, &y1, &zeye);
// dpl_SetViewProjection(eye,
// x0 + x_kernel[i],
// y0 + y_kernel[i],
// x1 + x_kernel[i],
// y1 + y_kernel[i], zeye);
// dpl_FlushView(eye);
// //--------------
// // redraw frame
// //--------------
// dpl_RedrawScene();
// dpl_RedrawScene();
// //--------------------
// // capture accumulate
// //--------------------
// this_weight = kernel_weights[i];
// frameptr = 0;
// for (y=0; y<y_size; ++y )
// {
// unsigned char *linep = (unsigned char *)&line_buffer[0];
// //------------------------------
// // Esc key cancelles frame dump
// //------------------------------
// if (kbhit())
// {
// if (getch() == 27)
// {
// DEBUG_STREAM << std::endl << "cancelled." << std::endl << std::flush;
// return;
// }
// }
// if ((y & 15) == 0)
// { DEBUG_STREAM << "." << std::flush; }
// dpl_ReadFrameStore(eye, (uint32 *)linep, 0, y, x_size, 1);
// for (x=0; x<x_size; ++x)
// {
// rframebuffer[frameptr] += (unsigned short)(linep[2] * this_weight);
// gframebuffer[frameptr] += (unsigned short)(linep[1] * this_weight);
// bframebuffer[frameptr] += (unsigned short)(linep[0] * this_weight);
// ++frameptr;
// linep += 4;
// }
// }
// DEBUG_STREAM << std::endl << std::flush;
//}
////----------------------------------
//// write frame buffer to targa file
////----------------------------------
//FILE *fp;
//char fname[64];
//unsigned char tga_hdr[18];
//sprintf(fname, "dump%d.tga", dumped_frames);
//tga_hdr[0] = 0x00;
//tga_hdr[1] = 0x00;
//tga_hdr[2] = 0x02;
//tga_hdr[3] = 0x00;
//tga_hdr[4] = 0x00;
//tga_hdr[5] = 0x00;
//tga_hdr[6] = 0x00;
//tga_hdr[7] = 0x00;
//tga_hdr[8] = 0x00;
//tga_hdr[9] = 0x00;
//tga_hdr[10] = 0x00;
//tga_hdr[11] = 0x00;
//tga_hdr[12] = (unsigned char)(x_size & 0xff);
//tga_hdr[13] = (unsigned char)((x_size >> 8) & 0xff);
//tga_hdr[14] = (unsigned char)(y_size & 0xff);
//tga_hdr[15] = (unsigned char)((y_size >> 8) & 0xff);
//tga_hdr[16] = 0x18;
//tga_hdr[17] = 0x00;
//DEBUG_STREAM << "Writing image to file '" << fname << "' . . . " << std::flush;
//fp = fopen(fname, "wb");
//fwrite(tga_hdr, 18, 1, fp);
//for (y=0; y<y_size; ++y )
//{
// oneD_filter(line_buffer,
// rframebuffer,
// gframebuffer,
// bframebuffer,
// (y_size-1) - y,
// x_size);
// fwrite(line_buffer, x_size*3, 1, fp);
//}
//fclose(fp);
//DEBUG_STREAM << "done." << std::endl << std::flush;
//++dumped_frames; // increment for next frame dump
//return;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DPLRenderer::DPLFrameDump()
void
DPLRenderer::DPLFrameDump(Logical antialias)
{
//STUBBED: DPL RB 1/15/07
//dump_frame_buffer(dplMainView, x_size, y_size, antialias);
//return;
}
//
//#############################################################################
// DPLIndependantPFX lets you start up an effect in absolute space that is
// run to termination automatically by the renderer.
//#############################################################################
//
void
DPLRenderer::DPLIndependantPFX(
Point3D location, // Location in space to trigger the effect
dpl_PARTICLESTART_EFFECT_INFO *psfx_definition, // Description of the pfx
dpl_DCS *my_DCS, // Optional DCS to link to
int subid) // inserted into third byte of effect id.
{
//STUBBED: DPL RB 1/14/07
// we put our id into the lower 16 bits because (at least) the upper 8 bits are flags
// HACK...for testing pfx bug
//dpl_PARTICLESTART_EFFECT_INFO tempParticle;
//tempParticle = *psfx_definition;
//tempParticle.identifier = (psfx_definition->identifier & 0xffff0000) | GetUniqueID() | ((subid << 16) & 0x00ff0000);
//tempParticle.px = location.x;
//tempParticle.py = location.y;
//tempParticle.pz = location.z;
//dpl_Effect(dpl_effect_type_particlestart, my_DCS, &tempParticle);
// std::cout<<"psfx identifier used was "<<tempParticle.identifier<<"\n";
}
//
//#############################################################################
// DPLIndependantEffect lets you start up an effect in absolute space that is
// run to termination automatically by the renderer.
//#############################################################################
//
void
DPLRenderer::DPLIndependantEffect(
Point3D location,
int effect_number,
dpl_DCS *my_DCS, // Optional DCS to link to
int subid)
{
//STUBBED: DPL RB 1/14/07
if(effect_number >= 1000)
{
effect_number -= 1000;
if(effect_number < 0 || effect_number > MAX_PSFX_COUNT-1)
{
Fail("PSFX id number was not in the allowed range");
}
// DPLIndependantPFX(location,myPSFXDescriptons[effect_number],my_DCS,subid);
// find a free emitter
ParticleEmitter *emitter = NULL;
for (int i=0; i<MAX_INDIE_EMITTERS; ++i)
{
if (!myPSFXEmitters[i].IsActive())
{
emitter = &myPSFXEmitters[i];
break;
}
}
if (emitter != NULL)
{
emitter->SetEffect(&myPSFXDescriptons[effect_number]);
emitter->SetPosition(location.x, location.y, location.z);
emitter->Start();
}
return;
}
//
// Board effect numbers (<100) -- the 1995 dpl EFFECT TABLE = the old-style
// specialfx descriptors (BTDPL.INI [effects_to_load]), NOT psfx slots
// (routing corrected 2026-07-12; the psfx encoding is "1000+slot"). The
// firmware-decoded heat-model burst renders them (BTStartSfx).
//
{
extern void BTStartSfx(int effect_number, float x, float y, float z);
BTStartSfx(effect_number, location.x, location.y, location.z);
}
//dpl_EXPLOSION_EFFECT_INFO my_explosion;
//my_explosion.type = effect_number;
//my_explosion.x = location.x;
//my_explosion.y = location.y;
//my_explosion.z = location.z;
//dpl_Effect ( dpl_effect_type_explosion, my_DCS, &my_explosion );
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DPLRenderer::SetViewAngle(Degree new_angle)
{
//STUBBED: DPL RB 1/14/07
//Check(this);
////
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//// Convert From Degree To Radian
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
////
//Radian view_angle;
//view_angle = new_angle;
//viewAngle = view_angle;
//viewRatio = tan(viewAngle/2.0f);
////
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//// Calc Aspect Ratio and Set View Projection
////~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
////
//aspectRatio = (float) y_size / (float) x_size;
//dpl_SetViewProjection ( dplMainView, -1.0f, -aspectRatio, 1.0f, aspectRatio, 1.0f/viewRatio);
//dpl_FlushView(dplMainView);
}
//
//#############################################################################
// Startup the implementation of the Division video renderer
//#############################################################################
//
void
DPLRenderer::LoadMissionImplementation(Mission *mission)
{
// LOAD-PHASE MARKER (2026-07-22): the teal->green minute is almost all
// unattributed (models are ~0.2s of ~58s measured) -- stamp the
// renderer-side load so the next run names its share.
struct BTPhaseTimer
{
unsigned long t0;
BTPhaseTimer() : t0(GetTickCount())
{
DEBUG_STREAM << "[loadphase] renderer LoadMission BEGIN t=" << t0
<< std::endl << std::flush;
}
~BTPhaseTimer()
{
DEBUG_STREAM << "[loadphase] renderer LoadMission END +"
<< (GetTickCount() - t0) << "ms" << std::endl << std::flush;
}
} _phase;
Check(this);
Tell("DPLVideoRenderer::StartImplementation has been called\n");
DPLReadEnvironment(mission);
LoadNameBitmaps();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// BT (task #20): window-resize aspect fix. gWindowAspect (defined at the top
// of this file) holds the live client aspect; D3D9 stretches the fixed-size
// backbuffer into the client area, so rendering with the CLIENT aspect cancels
// the stretch.
//
// DOCK-BOTTOM (single-window gauges): the world occupies the client MINUS the
// gauge strip (strip height tracks width at the panel's 480/1320 design ratio),
// so the projection aspect must come from the WORLD region or the scene
// stretches vertically by the strip's share.
static float BTWorldAspectOf(int client_w, int client_h)
{
extern int gBTGaugeDockBottom;
extern int BTGaugeStripHeightFor(int width);
// COCKPIT: the world occupies the CENTERED view sub-rect of the fixed-size
// backbuffer (canvas), which D3D stretches into the client area. The view's
// ON-SCREEN aspect = (viewW/canvasW * client_w) / (viewH/canvasH * client_h).
extern int gBTGaugeCockpit;
extern int gBTCockpitCanvasW, gBTCockpitCanvasH;
extern int gBTCockpitLetterbox;
if (gBTGaugeCockpit && gBTCockpitCanvasW > 0 && gBTCockpitCanvasH > 0)
{
BTCockpitLayout L;
BTCockpitComputeLayout(gBTCockpitCanvasW, gBTCockpitCanvasH, &L);
if (L.viewW > 0 && L.viewH > 0)
{
// LETTERBOXED: the canvas is scaled UNIFORMLY, so the view's
// on-screen aspect is just its own -- the client no longer enters
// into it (2026-07-26).
if (gBTCockpitLetterbox)
return (float)L.viewW / (float)L.viewH;
float w = (float)L.viewW * (float)client_w / (float)gBTCockpitCanvasW;
float h = (float)L.viewH * (float)client_h / (float)gBTCockpitCanvasH;
if (h > 0.0f) return w / h;
}
}
float world_h = (float)client_h;
if (gBTGaugeDockBottom)
{
float strip = (float)BTGaugeStripHeightFor(client_w);
if (strip > 0.0f && strip < world_h - 8.0f)
world_h -= strip;
}
return (float)client_w / world_h;
}
void L4NotifyWindowResized(int client_w, int client_h)
{
if (client_w <= 0 || client_h <= 0)
return;
gWindowAspect = BTWorldAspectOf(client_w, client_h);
DEBUG_STREAM << "[resize] client " << client_w << "x" << client_h
<< " aspect=" << gWindowAspect
<< " app=" << (void *)l4_application << "\n" << std::flush;
if (l4_application != NULL)
{
DPLRenderer *renderer = l4_application->GetVideoRenderer();
if (renderer != NULL)
renderer->UpdateWindowAspect(client_w, client_h);
}
}
void
DPLRenderer::UpdateWindowAspect(int client_w, int client_h)
{
if (client_w <= 0 || client_h <= 0)
return;
gWindowAspect = BTWorldAspectOf(client_w, client_h);
if (viewAngle <= 0.0f || clipFar <= clipNear)
{
DEBUG_STREAM << "[resize] projection not built yet (viewAngle="
<< viewAngle << ")\n" << std::flush;
return; // projection not built yet; the builder below picks it up
}
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix,
BTFovYFromHorizontal(viewAngle, gWindowAspect), gWindowAspect, clipNear, clipFar);
mDecalProjectionMatrix = mProjectionMatrix;
mDecalProjectionMatrix._33 -= mDecalEpsilon;
if (mDevice != NULL)
mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix);
DEBUG_STREAM << "[resize] projection rebuilt: fov=" << viewAngle
<< " aspect=" << gWindowAspect << " _11=" << mProjectionMatrix._11
<< " _22=" << mProjectionMatrix._22 << "\n" << std::flush;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DEBUG(bring-up): install a valid projection + lighting so loaded geometry is
// visible even when the full DPLReadEnvironment path has not been wired (BT).
//
void
DPLRenderer::EnsureValidProjection()
{
//
// BT (task #20): this was a bring-up SAFETY NET for when the real DPL
// environment (BTDPL.INI via DPLReadEnvironment) had not yet been wired --
// it forced near/far=0.25/1300, KILLED fog, and set WHITE ambient, which
// clobbered the authentic clip range (2100), the haze fog (600/2050), and
// the map ambient (0.45). DPLReadEnvironment now runs and sets all of those
// from the map's INI page, so honour them: only fall back to fixed values
// when the env genuinely failed to produce a valid projection.
//
if (viewAngle > 0.0f && clipFar > clipNear)
{
// Env is valid -- just (re)assert the env-built projection on the device.
// Do NOT touch fog or ambient (the env set the authentic values).
if (mDevice != NULL)
mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix);
DEBUG_STREAM << "[VP] env projection honoured: fov=" << viewAngle
<< " near=" << clipNear << " far=" << clipFar
<< " fog=" << currentFogNear << ".." << currentFogFar << "\n" << std::flush;
return;
}
// ---- FALLBACK: env did not set a projection (should not happen with the
// catch-all branch in BTDPL.INI, but keeps the mech visible if it does).
viewAngle = 60.0f;
clipNear = 0.25f;
clipFar = 1300.0f;
viewRatio = (float)tan(viewAngle * 0.5f * (PI / 180.0f));
D3DXMatrixIdentity(&mProjectionMatrix);
{
const float fbAspect = gWindowAspect > 0.0f ? gWindowAspect : (float)x_size / (float)y_size;
D3DXMatrixPerspectiveFovRH(&mProjectionMatrix,
BTFovYFromHorizontal(viewAngle, fbAspect), fbAspect, clipNear, clipFar);
}
mDecalEpsilon = 0.0000005f;
mDecalProjectionMatrix = mProjectionMatrix;
mDecalProjectionMatrix._33 -= mDecalEpsilon;
// push fog far away so the mech is not fogged to the background colour
fogNear = currentFogNear = 1.0e9f;
fogFar = currentFogFar = 1.0e9f;
if (mDevice != NULL)
{
mDevice->SetTransform(D3DTS_PROJECTION, &mProjectionMatrix);
mDevice->SetRenderState(D3DRS_FOGENABLE, FALSE);
mDevice->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(255, 255, 255));
}
DEBUG_STREAM << "[VP] EnsureValidProjection FALLBACK (env failed): RH fov="
<< viewAngle << " near=" << clipNear << " far=" << clipFar << "\n" << std::flush;
}
//##############################################################################
// Name Bitmap Support
//
void DPLRenderer::SortAndReloadNameBitmaps()
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Get the Entity Group of Players
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
EntityGroup *player_group = application->GetEntityManager()->FindGroup("Players");
ChainIteratorOf<Node*> player_iterator(player_group->groupMembers);
Player *current_player;
while ((current_player = (Player*)player_iterator.ReadAndNext()) != NULL)
{
BitMap *name_bitmap = application->GetCurrentMission()->GetLargeNameBitmap(current_player->playerBitmapIndex);
if (name_bitmap && current_player->IsScoringPlayer())
{
int index = current_player->playerRanking + 1;
if (mNameTextures[index])
mNameTextures[index]->Release();
mDevice->CreateTexture(128, 32, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &mNameTextures[index], NULL);
LoadBitSliceTexture(name_bitmap, mNameTextures[index]);
}
}
LoadOrdinalBitmaps();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DPLRenderer::LoadOrdinalBitmaps()
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Check if we have a Director on this machine,
// if so, create load the ordinal Bitmaps only
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//
if (application->GetMissionPlayer()->GetInstance() == CameraDirector::MasterInstance &&
application->GetMissionPlayer()->IsDerivedFrom(*CameraDirector::GetClassDerivations()))
{
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Make Sure index is in the right place
// in case < 8 players!!!!
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int index = 0;
for(int ii=1; ii<5; ++ii)
{
//
//~~~~~~~~~~~~~~~~~~
// Index Starts at 1
//~~~~~~~~~~~~~~~~~~
//
BitMap *ordinal_bitmap = application->GetCurrentMission()->GetOrdinalBitmap(ii);
if (mOrdinalTextures[index])
mOrdinalTextures[index]->Release();
mDevice->CreateTexture(128, 32, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &mOrdinalTextures[index], NULL);
LoadBitSliceTexture(ordinal_bitmap, mOrdinalTextures[index]);
++index;
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
unsigned int*
DPLRenderer::MakeBitSliceStorage()
{
Check(this);
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Allocate some temporary memory
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
uint32 *worst_case_texels = new uint32[128*64];
if(!worst_case_texels)
{
Fail("Could not allocate RAM for worst case texels\n");
}
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Zero Out Memory so Empty Texture space is Black
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
for(int ii=0;ii<8192;++ii)
{
worst_case_texels[ii] = 0;
}
return worst_case_texels;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void DPLRenderer::LoadNameBitmaps()
{
int player_count = application->GetCurrentMission()->GetPlayerCount();
for (int ii=0; ii<player_count; ++ii)
{
BitMap *name_bitmap = application->GetCurrentMission()->GetLargeNameBitmap(ii + 1);
if (name_bitmap)
{
if (mNameTextures[ii])
mNameTextures[ii]->Release();
HRESULT hr;
V(mDevice->CreateTexture(128, 32, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &mNameTextures[ii], NULL));
LoadBitSliceTexture(name_bitmap, mNameTextures[ii]);
}
}
LoadOrdinalBitmaps();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// BT: the RETICLE TARGET NAME PLATE's texture (the 1995 PNAMEx.bgf plate art).
// The plate meshes are quads UV-mapped onto a shared BITMAP material -- e.g.
// PNAME1.BGF and PNAME2.BGF both reference `bmap:name12_mtl` and differ only in
// their V-half (verified: the two files differ in 4 UV bytes -- the V floats;
// U runs 0..1 in both). Our per-player texture is 128x32 = ONE cell, so the
// port's quad samples v 0..1 -- porting the literal PNAMEn.BGF UVs onto it
// would render half a callsign. I.e. the plate
// shows the player's CALLSIGN raster, which on our side is exactly this
// per-player 128x32 name texture (4:1, the plate quad's 1.0 x 0.25 aspect).
// These load for EVERY mission from StartImplementation, so a cockpit session
// has them, not just a camera seat. The reticle TU (btl4vid.cpp) carries no
// d3d9 types, so hand the texture back opaquely. index0 is 0-BASED
// (playerBitmapIndex - 1).
//
void *BTGetPlayerNameTexture(int index0)
{
DPLRenderer *renderer =
(l4_application != 0) ? l4_application->GetVideoRenderer() : 0;
if (renderer == 0 || index0 < 0 || index0 >= MAX_PLAYER_NAMES)
{
return 0;
}
return (void *)renderer->GetNameTexture(index0);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
DPLRenderer::ShutdownImplementation()
{
//STUBBED: DPL RB 1/14/07
// Tell("DPLVideoRenderer::StopImplementation has been called\n");
// dpl_EnableSyncOnCreate();
// SChainIteratorOf<InnerProjectileRenderable*> projectile_iterator(&projectile_list);
// TreeIteratorOf<DPLObjectCacheLine*, CString> cache_iterator(&dplObjectCacheSocket);
//#if defined(LAB_ONLY)
// std::cout<<"max projectiles "<<projectile_iterator.GetSize()<<"\n";
// std::cout<<"max cached objects "<<cache_iterator.GetSize()<<"\n";
//#endif
//// std::cout<<"DPL renderer has "<<dpl_FreeMemory()<<" free\n";
}
void
DPLRenderer::SuspendImplementation()
{
Tell("DPLVideoRenderer::SuspendImplementation has been called\n");
}
void
DPLRenderer::ResumeImplementation()
{
Tell("DPLVideoRenderer::ResumeImplementation has been called\n");
}
//-----------------------------------------------------------------------------
//--------------------------Id's for psfx effects---------------------------------
//-----------------------------------------------------------------------------
int
DPLRenderer::GetUniqueID()
{
myUniqueID = (myUniqueID + 1) & 0x0000ffff;
return(myUniqueID);
}
//-----------------------------------------------------------------------------
//--------------------------MUNGA caching of objects---------------------------
//-----------------------------------------------------------------------------
dpl_OBJECT*
DPLRenderer::GetCachedObject(
const CString &object_name) // Name of the object we want to get
{
dpl_OBJECT
*object_pointer;
//
// Get this object from the cache by name
//
DPLObjectCacheLine *my_cache_line;
if ((my_cache_line = dplObjectCacheSocket.Find(object_name)) == NULL)
return NULL;
//
// Remove the cache line from the cache
//
object_pointer = my_cache_line->objectPointer;
delete my_cache_line;
//
// Return the object pointer
//
return(object_pointer);
}
void
DPLRenderer::PutCachedObject(
const CString &object_name, // Name of the object we will cache
dpl_OBJECT *object_pointer) // pointer to the object being cached
{
//
// Create the cache line data structure
//
#if 0
typedef PlugOf<dpl_OBJECT*> DPLObjectCacheLine;
DPLObjectCacheLine *my_cache_line = new DPLObjectCacheLine(object_pointer);
#endif
DPLObjectCacheLine *my_cache_line =
new DPLObjectCacheLine(
object_name,
object_pointer);
Register_Object(my_cache_line);
//
// Store this cache line in the cache
//
dplObjectCacheSocket.AddValue(my_cache_line, object_name);
}
//-----------------------------------------------------------------------------
//--------------------------Projectile Speedup---------------------------------
//-----------------------------------------------------------------------------
InnerProjectileRenderable*
DPLRenderer::GetProjectile(
d3d_OBJECT *graphical_object, // object to hang on the DCS, may be a list later <NULL>
bool isDeathZone) // DPL Zone this stuff will live in (for culling)
{
//STUBBED: DPL RB 1/14/07
//InnerProjectileRenderable
// *return_projectile;
//dpl_INSTANCE
// *temp_instance;
////
//// Are there projectiles in the list?
////
//SChainIteratorOf<InnerProjectileRenderable*> iterator(&projectile_list);
//if ((return_projectile = iterator.GetCurrent()) != NULL)
//{
// // Yes, remove it from the list set it's instance and return it
// iterator.Remove();
// temp_instance = return_projectile->GetInstance();
// if(graphical_object != dpl_GetInstanceObject(temp_instance))
// {
// dpl_SetInstanceObject (temp_instance, graphical_object);
// dpl_FlushInstance (temp_instance);
// }
// return(return_projectile);
//}
//else
//{
// // no, make a new one and return that
InnerProjectileRenderable *projectile =
new InnerProjectileRenderable(
graphical_object, // object to hang on the DCS, may be a list later <NULL>
isDeathZone); // DPL Zone this stuff will live in (for culling)
return(projectile);
//}
}
void
DPLRenderer::ReleaseProjectile(
InnerProjectileRenderable* inner_projectile)
{
// add the projectile back to the list
//projectile_list.Add(inner_projectile);
}
//-----------------------------------------------------------------------------
//--------------------------Joint to DCS translator----------------------------
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DPLJointToDCSTranslator is a class that contains a joint number to dcs
// pointer translation table. This lets a renderer find the dcs that goes
// with a particular segment.
//
DPLJointToDCSTranslator::DPLJointToDCSTranslator(
Entity *entity, // The entity to translate
dpl_DCS *dcs_array[]) // Array of DCS's to translate
{
JointedMover
*my_jointed_mover;
//
// Make sure this is a jointed mover, then cast the entity pointer over
//
if (entity->IsDerivedFrom(*JointedMover::GetClassDerivations()))
{
my_jointed_mover = (JointedMover*)entity;
}
else
{
Fail("DPLJointToDCSTranslator was called on an entity NOT a JointedMover\n");
}
//
// Find out how many joints the entity has, then allocate enough RAM for a
// DCS pointer to every joint.
//
JointSubsystem* joint_subsystem = my_jointed_mover->GetJointSubsystem();
Check(joint_subsystem);
translation_array = new (dpl_DCS(*[joint_subsystem->GetJointCount()]));
Register_Pointer(translation_array);
//
// Setup to iterate the entity segment table
//
EntitySegment::SegmentTableIterator segment_iterator(my_jointed_mover->segmentTable);
EntitySegment *current_segment;
while ((current_segment = segment_iterator.ReadAndNext()) != NULL)
{
//
// For each segment, see if it has a joint index, if it does, put the
// DCS for that joint into the translation array
//
Check(current_segment);
int joint_index = current_segment->GetJointIndex();
if(joint_index != -1)
{
translation_array[joint_index] = dcs_array[current_segment->GetIndex()];
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
DPLJointToDCSTranslator::~DPLJointToDCSTranslator()
{
Unregister_Pointer(translation_array);
delete[] translation_array;
translation_array = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Logical
DPLJointToDCSTranslator::TestInstance() const
{
return True;
}
//-----------------------------------------------------------------------------
//--------------------------Resource creation support--------------------------
//-----------------------------------------------------------------------------
//#############################################################################
//############# DPLRenderer::CreateModelVideoStreamResource #############
//#############################################################################
ResourceDescription::ResourceID
DPLRenderer::CreateModelVideoStreamResource(
ResourceFile *resource_file,
const char *model_name,
NotationFile *model_file,
const ResourceDirectories * /*directories*/
)
{
ResourceDescription *res_description =
resource_file->FindResourceDescription(
model_name,
ResourceDescription::VideoModelResourceType
);
if (res_description == NULL)
{
NameList *video_entries = model_file->MakeEntryList("video");
if (video_entries != NULL)
{
Register_Object(video_entries);
//----------------------------------------------
// parse video model data and store in resource
//----------------------------------------------
Tell("Building video resource for model '" << model_name << "'.\n");
NameList::Entry
*entry,
*next_entry;
const char
*entry_name,
*entry_pointer,
*argument_start;
int
length;
char
argument[40],
*argument_pointer;
L4VideoObject::ResourceType
resource_type;
Enumeration //(see L4VideoObject::RendererModes)
renderer_modes;
enum
{
parsing_filename,
seeking_switch,
parsing_switch,
parsing_billboard
} state;
L4VideoObject
*video_object;
L4VideoObjectWrapper
*video_wrapper;
ChainOf<L4VideoObjectWrapper*>
video_chain(NULL);
char
*video_stream,
*video_pointer;
int
object_count;
long
object_size,
stream_length;
//------------------------------------------------
// parse each entry in [video] page of model file
//------------------------------------------------
next_entry = video_entries->GetFirstEntry();
while (next_entry)
{
entry = next_entry;
Check(entry);
next_entry = entry->GetNextEntry(); // so 'continue' works
entry_name = entry->GetName();
if (entry_name && *entry_name)
{
//--------------------------------------------
// could be "skeleton" or "object" or comment
//--------------------------------------------
if (strcmp(entry_name, "object") == 0)
{
resource_type = L4VideoObject::Object;
}
else if (strcmp(entry_name, "rubble") == 0)
{
resource_type = L4VideoObject::Rubble;
}
else if (strcmp(entry_name, "skeleton") == 0)
{
resource_type = L4VideoObject::Skeleton;
}
else if (Comment_Line(entry_name))
{
// do nothing - skip comments
continue;
}
else if (strncmp(entry_name, "skeleton", 8) == 0)
{
// do nothing - skip temporary skeleton entries
continue;
}
else if (strncmp(entry_name, "destroyed", 9) == 0)
{
// do nothing - skip destroyed skeleton entries
continue;
}
else if (strncmp(entry_name, "dzm", 3) == 0)
{
// do nothing - skip damage zone material entries
continue;
}
else
{
// resource_type = L4VideoObject::Unknown;
DEBUG_STREAM << "Unknown entry '" << entry_name <<
"' in model '" << model_name << "' ignored." << std::endl;
continue;
}
//---------------------------------
// parse and process each argument
//---------------------------------
entry_pointer = entry->GetChar();
while (entry_pointer && *entry_pointer)
{
//-------------------------
// skip leading whitespace
//-------------------------
while (*entry_pointer != '\0' && isspace(*entry_pointer))
{
++entry_pointer;
}
//---------------------------
// exit loop if nothing left
//---------------------------
if (*entry_pointer == '\0')
{
break;
}
//-------------------------------------------
// parse the next argument into local buffer
//-------------------------------------------
argument_start = entry_pointer;
while (*entry_pointer != '\0' && !isspace(*entry_pointer))
{
++entry_pointer;
}
length = entry_pointer - argument_start;
Verify( length < sizeof(argument) );
strncpy(argument, argument_start, length);
argument[length] = '\0';
//----------------------
// parse local argument
//----------------------
argument_pointer = argument;
renderer_modes = L4VideoObject::Normal;
state = parsing_filename;
while (*argument_pointer != '\0')
{
switch (state)
{
case parsing_filename:
switch (*argument_pointer)
{
#if 0
case '.':
if ((resource_type == L4VideoObject::Object) ||
(resource_type == L4VideoObject::Rubble))
{
*argument_pointer = '\0'; // terminate filename
state = seeking_switch;
}
break;
#endif
case '/':
*argument_pointer = '\0'; // terminate filename
state = parsing_switch;
break;
default:
// later check for valid filename character...
break;
}
break;
case seeking_switch:
if (*argument_pointer == '/')
{ state = parsing_switch; }
break;
case parsing_switch:
switch (*argument_pointer)
{
case 'b':
case 'B':
state = parsing_billboard;
break;
case 'i':
case 'I':
renderer_modes |= L4VideoObject::IntersectImmune;
state = seeking_switch;
break;
default:
state = seeking_switch;
break;
}
break;
case parsing_billboard:
switch (*argument_pointer)
{
case 'x':
case 'X':
renderer_modes |= L4VideoObject::BillboardXAxis;
break;
case 'y':
case 'Y':
renderer_modes |= L4VideoObject::BillboardYAxis;
break;
case 'z':
case 'Z':
renderer_modes |= L4VideoObject::BillboardZAxis;
break;
case '/':
state = parsing_switch;
break;
default:
state = seeking_switch;
break;
}
break;
// no default:
}
++argument_pointer;
}
//-----------------------------------------------
// create video resource object and add to chain
//-----------------------------------------------
video_object =
new L4VideoObject(
argument,
resource_type,
renderer_modes
);
Register_Pointer(video_object); // not _Object!
video_wrapper =
new L4VideoObjectWrapper(
video_object,
True // delete object when done
);
Register_Object(video_wrapper);
//Tell(" adding video object '"<<argument<<"' type ");
//Tell(resource_type<<" mode 0x"<<std::hex<<renderer_modes<<"\n");
video_chain.Add(video_wrapper);
} // arguments processed for this entry
}
} // all arguments processed
//----------------------------------------------
// convert chain of video resources into stream
//----------------------------------------------
ChainIteratorOf<L4VideoObjectWrapper*>
video_iterator(video_chain);
object_count = video_iterator.GetSize();
object_size = sizeof(L4VideoObject);
stream_length = sizeof(int) + object_count * object_size;
video_stream = new char[stream_length];
Register_Pointer(video_stream);
video_pointer = video_stream;
*((int *)video_pointer) = object_count;
video_pointer += sizeof(int);
Tell(" count " <<std::dec<< object_count << ", resource size " << stream_length << "\n");
while ((video_wrapper = video_iterator.ReadAndNext()) != NULL)
{
video_object = video_wrapper->GetVideoObject();
if (!stricmp(video_object->GetObjectFilename(), "bp1.bgf"))
video_pointer += 1 - 1;
*((L4VideoObject *)video_pointer) = *video_object;
video_pointer += object_size;
}
//--------------------------------------------------
// store stream of video resources in resource file
//--------------------------------------------------
res_description =
resource_file->AddResource(
model_name,
ResourceDescription::VideoModelResourceType,
1,
ResourceDescription::Preload,
video_stream,
stream_length
);
//--------------------------
// release allocated memory
//--------------------------
// video_chain
L4VideoObjectWrapper::DeleteVideoObjectChain(&video_chain);
// video_stream
Unregister_Pointer(video_stream);
delete video_stream;
// video_entries
Unregister_Object(video_entries);
delete video_entries;
}
else
{
//-------------------------------
// no [video] page in model file
//-------------------------------
return (ResourceDescription::NullResourceID);
}
}
return (res_description->resourceID);
}
//===========================================================================//