d3d_OBJECT::LoadTexture never checked D3DXCreateTextureFromFileA, cached the NULL texture, and unconditionally AddRef()ed it - an access violation on any missing/unreadable texture, hit by every bare working copy because the pod skins (VIDEO\player1-8) come from the presets/replacement-material path, not the depot. Failures now log the filename+hr and the draw op renders untextured, matching the existing no-texture-filename path. Also guard the unchecked gReplacementData->find() in LoadObject (same latent UB one branch earlier). Verified in the sandbox working copy: the game now boots to a running RPL4 window with -windowed -egg TEST.EGG (RIO served by vRIO), logging the eight missing pod skins instead of dying in MakeEntityRenderables. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
478 lines
13 KiB
C++
478 lines
13 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;
|
|
|
|
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);
|
|
}
|
|
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), ¢er, &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;
|
|
}
|
|
|
|
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, ¢er, &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);
|
|
}
|
|
} |