Files
RP412/MUNGA_L4/L4D3D.cpp
T
CydandClaude Fable 5 b97dcce3a2 Pilot callsigns on the Winners Circle plates
The plates beside each spot came out blank. They ask for textures called
player1..player8, which are not files - the renderer draws each pilot's
callsign into a texture at run time - so the load failed and left them
untextured. Nothing bound the two together.

SortAndReloadNameBitmaps already builds those textures indexed by
finishing place, which is exactly how the plates are numbered, so the
plate beside each spot wants mNameTextures[place]. Binding them is the
whole fix, but it takes two steps rather than one.

The plates have to survive mesh consolidation first. Static geometry is
merged with D3DXConcatenateMeshes and its draw ops deduped by material -
and eight failed texture loads leave eight identical untextured ops, so
all eight plates collapse into one that could only ever carry a single
name. Each plate now gets a distinct 1x1 marker texture as it loads,
which keeps it a subset of its own. The marker is never seen.

Then the binding runs against the consolidated mesh, not the objects the
plates were loaded from - by podium time those have been merged away and
are no longer drawn, which is why re-pointing them changed nothing.

Verified on a race: 8 plates found in the consolidated mesh, 1 bound,
and the winner's plate reads their callsign under their vehicle. One
bound of eight is right for a single-pod race - the rest of the places
have nobody in them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:40:53 -05:00

566 lines
15 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4d3d.h"
#include "L4VIDEO.h"
#include <algorithm>
int gNumBatches = 0;
bool d3d_OBJECT::mLastTextureScrollState = false;
L4TEXOP::WrapType d3d_OBJECT::mLastWrapU = L4TEXOP::WrapType::REPEAT;
L4TEXOP::WrapType d3d_OBJECT::mLastWrapV = L4TEXOP::WrapType::REPEAT;
bool d3d_OBJECT::mLastTexturingState = true;
long d3d_OBJECT::mNextID = 1;
stdext::hash_map<std::string, L4TEXOP> d3d_OBJECT::mTextureCache;
LPDIRECT3DTEXTURE9 d3d_OBJECT::mNamePlateMarkers[d3d_OBJECT::kNamePlateCount + 1] = { NULL };
void chgext(char *filePath, const char *newExtension)
{
int len = strlen(filePath);
for (int i=len-1; i>=0; i--)
{
if (filePath[i] == '.')
{
strcpy(&filePath[i + 1], newExtension);
return;
}
}
}
void d3d_OBJECT::ResetState(LPDIRECT3DDEVICE9 device)
{
d3d_OBJECT::mLastTextureScrollState = false;
device->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
d3d_OBJECT::mLastWrapU = L4TEXOP::WrapType::REPEAT;
device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
d3d_OBJECT::mLastWrapV = L4TEXOP::WrapType::REPEAT;
device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
d3d_OBJECT::mLastTexturingState = true;
device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
}
d3d_OBJECT *d3d_OBJECT::LoadObject(LPDIRECT3DDEVICE9 device, char *fileName)
{
char fullPath[512];
sprintf(fullPath, "VIDEO\\%s", fileName);
chgext(fullPath, "x");
//Get a ref to the renderer
DPLRenderer *renderer = l4_application->GetVideoRenderer();
LPD3DXMESH mesh;
LPD3DXBUFFER materialBuffer, adjacencyBuffer;
DWORD materialCount;
HRESULT hr = D3DXLoadMeshFromXA(fullPath, D3DXMESH_MANAGED, device, &adjacencyBuffer, &materialBuffer, NULL, &materialCount, &mesh);
if (FAILED(hr))
{
chgext(fullPath, "sph");
return d3d_OBJECT::LoadSpheres(device, fullPath);
}
chgext(fullPath, "det");
FILE *detFile = fopen(fullPath, "rb");
int numDetailOps = 0;
int *detailOps = NULL;
bool isSky = false;
if (detFile != NULL)
{
fread(&numDetailOps, sizeof(int), 1, detFile);
detailOps = new int[numDetailOps];
fread(detailOps, sizeof(int), numDetailOps, detFile);
fclose(detFile);
}
chgext(fullPath, "sky");
FILE *skyFile = fopen(fullPath, "rb");
if (skyFile != NULL)
{
isSky = true;
fclose(skyFile);
}
d3d_OBJECT *object = new d3d_OBJECT(device, mesh, (DWORD*)adjacencyBuffer->GetBufferPointer(), materialCount);
D3DXMATERIAL *materials = (D3DXMATERIAL*)materialBuffer->GetBufferPointer();
int nextDetailOp = 0;
for (DWORD i = 0; i < materialCount; i++)
{
chgext(fullPath, "BGF");
string path(fullPath);
string actualPath = path.substr(path.find("\\") + 1);
std::transform(actualPath.begin(), actualPath.end(), actualPath.begin(), toupper);
const char *matName = opMaterialName(actualPath.c_str(), i);
char *replaceMatName = NULL;
char *replaceTexName = NULL;
if (matName != NULL)
{
char *tmpMatName = new char[strlen(matName) + 1];
strcpy_s(tmpMatName, strlen(matName) + 1, matName);
replaceMatName = substituteMaterial(tmpMatName);
}
object->mDrawOps[i].material = materials[i].MatD3D;
if (replaceMatName != NULL)
{
hash_map<string, ReplacementMaterialData>::const_iterator iter = gReplacementData->find(string(replaceMatName));
if (iter != gReplacementData->end())
{
ReplacementMaterialData data = (*iter).second;
object->mDrawOps[i].material.Diffuse.r = data.r;
object->mDrawOps[i].material.Diffuse.g = data.g;
object->mDrawOps[i].material.Diffuse.b = data.b;
replaceTexName = new char[data.texName.size() + 1];
strcpy_s(replaceTexName, data.texName.size() + 1, data.texName.c_str());
}
else
{
DEBUG_STREAM << "L4D3D.cpp no replacement material data for " << replaceMatName << "\n" << std::flush;
}
delete [] replaceMatName;
}
//TODO: set ambient of material; doesn't come back from .X file
object->mDrawOps[i].material.Ambient = object->mDrawOps[i].material.Diffuse;
object->mDrawOps[i].texture.texture = NULL;
if (materials[i].pTextureFilename || replaceTexName)
{
char textureFilename[512];
if (replaceTexName)
{
sprintf(textureFilename, "VIDEO\\%s.png", replaceTexName);
delete [] replaceTexName;
} else
{
sprintf(textureFilename, "VIDEO\\%s", materials[i].pTextureFilename);
}
//
// playerN is not a file - it is a pilot's callsign, drawn into
// a texture at run time, so loading it always fails. Give the
// plate its marker texture instead: it keeps the eight sign
// faces as eight separate draw ops through consolidation, and
// the Winners Circle swaps the real callsign in later.
//
const char *base = strrchr(textureFilename, '\\');
base = (base != NULL) ? base + 1 : textureFilename;
int plate = 0;
if (_strnicmp(base, "player", 6) == 0 &&
base[6] >= '1' && base[6] <= '8')
{
plate = base[6] - '0';
}
if (plate != 0)
{
memset(&object->mDrawOps[i].texture, 0, sizeof(L4TEXOP));
object->mDrawOps[i].texture.texture =
NamePlateMarker(device, plate);
if (object->mDrawOps[i].texture.texture != NULL)
{
object->mDrawOps[i].texture.texture->AddRef();
}
}
else
{
object->mDrawOps[i].texture = LoadTexture(device, textureFilename);
}
}
if (nextDetailOp < numDetailOps && detailOps[nextDetailOp] == i)
{
object->mDrawOps[i].drawAsDecal = true;
nextDetailOp++;
}
if (abs(object->mDrawOps[i].material.Diffuse.a - 1.0f) > 0.0001f)
{
object->mDrawOps[i].alphaTest = true;
}
object->mDrawOps[i].drawAsSky = isSky;
if (isSky)
{
object->mDrawOps[i].material.Ambient.r *= renderer->GetCloudRed();
object->mDrawOps[i].material.Ambient.g *= renderer->GetCloudGreen();
object->mDrawOps[i].material.Ambient.b *= renderer->GetCloudBlue();
object->mDrawOps[i].material.Diffuse.r *= renderer->GetCloudRed();
object->mDrawOps[i].material.Diffuse.g *= renderer->GetCloudGreen();
object->mDrawOps[i].material.Diffuse.b *= renderer->GetCloudBlue();
object->mDrawOps[i].material.Emissive.r = renderer->GetCloudEmitRed();
object->mDrawOps[i].material.Emissive.g = renderer->GetCloudEmitGreen();
object->mDrawOps[i].material.Emissive.b = renderer->GetCloudEmitBlue();
}
}
materialBuffer->Release();
return object;
}
d3d_OBJECT* d3d_OBJECT::LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName)
{
FILE *file;
float emissionColor[3];
int vertexCount;
if ((file = fopen(fileName, "rb")) == NULL)
return NULL;
fread(emissionColor, sizeof(float), 3, file);
fread(&vertexCount, sizeof(int), 1, file);
d3d_OBJECT *object = new d3d_OBJECT(device, vertexCount);
void *buffer = NULL;
object->mVB->Lock(0, 0, &buffer, 0);
fread(buffer, sizeof(L4POINTVERTEX), vertexCount, file);
D3DXVECTOR3 center(0, 0, 0);
D3DXComputeBoundingSphere((D3DXVECTOR3*)buffer, vertexCount, sizeof(L4POINTVERTEX), &center, &object->mRadius);
object->mVB->Unlock();
object->mDrawOps[0].material.Emissive.r = emissionColor[0];
object->mDrawOps[0].material.Emissive.g = emissionColor[1];
object->mDrawOps[0].material.Emissive.b = emissionColor[2];
object->mDrawOps[0].material.Emissive.a = 1.0f;
fclose(file);
return object;
}
L4TEXOP d3d_OBJECT::LoadTexture(LPDIRECT3DDEVICE9 device, const char *fileName)
{
L4TEXOP texOp;
FILE *file;
if (mTextureCache.find(fileName) == mTextureCache.end())
{
struct
{ unsigned char minify;
unsigned char magnify;
unsigned char alpha;
unsigned char wrap_u;
unsigned char wrap_v;
bool doScroll;
float scrollUDelta;
float scrollVDelta;
} fileTexOp;
char metFileName[512];
strcpy(metFileName, fileName);
chgext(metFileName, "met");
memset(&texOp, 0, sizeof(L4TEXOP));
if ((file = fopen(metFileName, "rb")) != NULL)
{
fread(&fileTexOp, sizeof(fileTexOp), 1, file);
fclose(file);
texOp.wrap_u = (L4TEXOP::WrapType)fileTexOp.wrap_u;
texOp.wrap_v = (L4TEXOP::WrapType)fileTexOp.wrap_v;
texOp.doScroll = fileTexOp.doScroll;
texOp.scrollUDelta = fileTexOp.scrollUDelta;
texOp.scrollVDelta = fileTexOp.scrollVDelta;
}
HRESULT hr = D3DXCreateTextureFromFileA(device, fileName, &texOp.texture);
if (FAILED(hr))
{
// texOp.texture stays NULL (memset above); draw ops render untextured,
// matching the "no texture filename" path in LoadObject.
DEBUG_STREAM << "L4D3D.cpp couldn't load texture " << fileName << " (hr=0x" << std::hex << hr << std::dec << ")\n" << std::flush;
}
mTextureCache.insert(std::pair<std::string,L4TEXOP>(std::string(fileName), texOp));
}
else
texOp = mTextureCache[std::string(fileName)];
if (texOp.texture != NULL)
texOp.texture->AddRef();
return texOp;
}
void d3d_OBJECT::FlushTextureCache()
{
stdext::hash_map<std::string, L4TEXOP>::iterator iter;
for (iter = mTextureCache.begin(); iter != mTextureCache.end(); ++iter)
{
if ((*iter).second.texture != NULL)
(*iter).second.texture->Release();
}
mTextureCache.clear();
for (int plate = 0; plate <= kNamePlateCount; ++plate)
{
if (mNamePlateMarkers[plate] != NULL)
{
mNamePlateMarkers[plate]->Release();
mNamePlateMarkers[plate] = NULL;
}
}
}
//
// A distinct 1x1 texture per plate, so the eight sign faces stay eight
// separate draw ops through mesh consolidation instead of merging into one.
// Its content never shows - the callsign replaces it before the podium.
//
LPDIRECT3DTEXTURE9 d3d_OBJECT::NamePlateMarker(LPDIRECT3DDEVICE9 device, int plate)
{
if (plate < 1 || plate > kNamePlateCount || device == NULL)
{
return NULL;
}
if (mNamePlateMarkers[plate] == NULL)
{
device->CreateTexture(1, 1, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED,
&mNamePlateMarkers[plate], NULL);
}
return mNamePlateMarkers[plate];
}
//
// Which plate a texture is the marker for, or 0 if it is not one.
//
int d3d_OBJECT::NamePlateFor(LPDIRECT3DTEXTURE9 texture)
{
if (texture == NULL)
{
return 0;
}
for (int plate = 1; plate <= kNamePlateCount; ++plate)
{
if (mNamePlateMarkers[plate] == texture)
{
return plate;
}
}
return 0;
}
d3d_OBJECT::d3d_OBJECT(LPDIRECT3DDEVICE9 device, int vertCount)
: mDevice(device),
mVertCount(vertCount),
mDrawOpCount(1),
mImmune(false),
mVB(NULL),
mMesh(NULL),
mDrawOps(NULL),
mLocalToWorld(),
mOrigin(),
mRadius(0.0f),
mID(mNextID++)
{
HRESULT hr;
hr = device->CreateVertexBuffer(vertCount * sizeof(L4POINTVERTEX), D3DUSAGE_WRITEONLY, L4POINTVERTEX_FVF, D3DPOOL_MANAGED, &mVB, NULL);
mDrawOps = new L4DRAWOP();
memset(mDrawOps, 0, sizeof(L4DRAWOP));
memset(mNext, 0, sizeof(mNext));
memset(mPrev, 0, sizeof(mPrev));
D3DXMatrixIdentity(&mLocalToWorld);
}
d3d_OBJECT::d3d_OBJECT(LPDIRECT3DDEVICE9 device, LPD3DXMESH mesh, DWORD *adjacencyBuffer, int drawOpCount)
: mDevice(device),
mVertCount(-1),
mDrawOpCount(drawOpCount),
mImmune(false),
mVB(NULL),
mMesh(mesh),
mLocalToWorld(),
mOrigin(),
mRadius(0.0f),
mID(mNextID++)
{
mDrawOps = new L4DRAWOP[drawOpCount];
memset(mDrawOps, 0, sizeof(L4DRAWOP) * drawOpCount);
if (adjacencyBuffer != NULL)
{
//Null adjacency buffer means the mesh should have already been optimized
mesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, adjacencyBuffer, NULL, NULL, NULL);
}
DWORD vertCount = mesh->GetNumVertices();
DWORD vertStride = mesh->GetNumBytesPerVertex();
D3DXVECTOR3 *data;
D3DXVECTOR3 center(0, 0, 0);
mesh->LockVertexBuffer(D3DLOCK_READONLY, (LPVOID*)&data);
D3DXComputeBoundingSphere(data, vertCount, vertStride, &center, &mRadius);
mesh->UnlockVertexBuffer();
memset(mNext, 0, sizeof(mNext));
memset(mPrev, 0, sizeof(mPrev));
D3DXMatrixIdentity(&mLocalToWorld);
}
d3d_OBJECT::~d3d_OBJECT()
{
if (mVB)
{
mVB->Release();
mVB = NULL;
}
if (mDrawOps)
{
delete[] mDrawOps;
mDrawOps = NULL;
}
}
void d3d_OBJECT::Draw(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame)
{
// set our world transform
mDevice->SetTransform(D3DTS_WORLD, &mLocalToWorld);
if (mMesh != NULL && pass != PASS_SPHERE)
return DrawMesh(pass, viewTransform, targetRenderFrame);
else if (mMesh == NULL && mVB != NULL && pass == PASS_SPHERE)
return DrawSpheres(pass, viewTransform);
}
void d3d_OBJECT::DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame)
{
for (int iOp = 0; iOp < mDrawOpCount; iOp++)
{
L4DRAWOP *drawOp = &mDrawOps[iOp];
if (drawOp->alphaTest != (pass == PASS_ALPHABLEND))
continue;
if (drawOp->drawAsDecal != (pass == PASS_DECAL))
continue;
if (drawOp->drawAsSky != (pass == PASS_SKY))
continue;
mDevice->SetMaterial(&drawOp->material);
#ifndef RP3_EMULATE
SetTextureScrolling(&(drawOp->texture), targetRenderFrame);
SetTexture(drawOp->texture.texture);
#endif
gNumBatches++;
mMesh->DrawSubset(iOp);
}
}
void d3d_OBJECT::DrawSpheres(int pass, const D3DXMATRIX *viewTransform)
{
mDevice->SetFVF(L4POINTVERTEX_FVF);
mDevice->SetStreamSource(0, mVB, 0, sizeof(L4POINTVERTEX));
mDevice->SetMaterial(&mDrawOps[0].material);
SetTexture(NULL);
mDevice->DrawPrimitive(D3DPT_POINTLIST, 0, mVertCount);
}
void d3d_OBJECT::SetTextureScrolling(const L4TEXOP *texture, Time targetRenderFrame)
{
if (d3d_OBJECT::mLastTextureScrollState != texture->doScroll)
{
// the texture scrolling state has changed
if (texture->doScroll)
{
mDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
SetTextureAddressing(texture->wrap_u, texture->wrap_v);
}
else
{
mDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
SetTextureAddressing(L4TEXOP::WrapType::REPEAT, L4TEXOP::WrapType::REPEAT);
}
d3d_OBJECT::mLastTextureScrollState = texture->doScroll;
}
// if the texture is supposed to be scrolled then
// we have to set the transforms regardless of state
if (texture->doScroll)
{
Scalar time = (Scalar)targetRenderFrame;
D3DXMATRIX translate;
D3DXMatrixIdentity(&translate);
translate._31 = -texture->scrollUDelta * time;
translate._32 = texture->scrollVDelta * time;
mDevice->SetTransform(D3DTS_TEXTURE0, &translate);
}
}
void d3d_OBJECT::SetTextureAddressing(L4TEXOP::WrapType wrap_u, L4TEXOP::WrapType wrap_v)
{
if (wrap_u != d3d_OBJECT::mLastWrapU)
{
d3d_OBJECT::mLastWrapU = wrap_u;
if (wrap_u == L4TEXOP::REPEAT)
mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
else if (wrap_u == L4TEXOP::CLAMP)
mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
else if (wrap_u == L4TEXOP::SELECT)
mDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
}
if (wrap_v != d3d_OBJECT::mLastWrapV)
{
d3d_OBJECT::mLastWrapV = wrap_v;
if (wrap_v == L4TEXOP::REPEAT)
mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
else if (wrap_v == L4TEXOP::CLAMP)
mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
else if (wrap_v == L4TEXOP::SELECT)
mDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
}
}
void d3d_OBJECT::SetTexture(LPDIRECT3DTEXTURE9 texture)
{
bool texturingOn = (texture != NULL);
if (texturingOn != d3d_OBJECT::mLastTexturingState)
{
d3d_OBJECT::mLastTexturingState = texturingOn;
if (texturingOn)
{
mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
mDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
}
else
mDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_DISABLE);
}
if (texture != NULL)
{
mDevice->SetTexture(0, texture);
}
}