From b97dcce3a23289475677cef953020644ab820881 Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 27 Jul 2026 14:40:53 -0500 Subject: [PATCH] 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 --- MUNGA_L4/L4D3D.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++- MUNGA_L4/L4D3D.h | 24 ++++++++++++++ MUNGA_L4/L4VIDEO.cpp | 59 ++++++++++++++++++++++++++++----- MUNGA_L4/L4VIDEO.h | 4 +++ 4 files changed, 157 insertions(+), 9 deletions(-) diff --git a/MUNGA_L4/L4D3D.cpp b/MUNGA_L4/L4D3D.cpp index 5ca1274..86fde4c 100644 --- a/MUNGA_L4/L4D3D.cpp +++ b/MUNGA_L4/L4D3D.cpp @@ -13,6 +13,7 @@ L4TEXOP::WrapType d3d_OBJECT::mLastWrapV = L4TEXOP::WrapType::REPEAT; bool d3d_OBJECT::mLastTexturingState = true; long d3d_OBJECT::mNextID = 1; stdext::hash_map d3d_OBJECT::mTextureCache; +LPDIRECT3DTEXTURE9 d3d_OBJECT::mNamePlateMarkers[d3d_OBJECT::kNamePlateCount + 1] = { NULL }; void chgext(char *filePath, const char *newExtension) { @@ -156,7 +157,36 @@ d3d_OBJECT *d3d_OBJECT::LoadObject(LPDIRECT3DDEVICE9 device, char *fileName) { sprintf(textureFilename, "VIDEO\\%s", materials[i].pTextureFilename); } - object->mDrawOps[i].texture = LoadTexture(device, textureFilename); + // + // 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) @@ -286,6 +316,53 @@ void d3d_OBJECT::FlushTextureCache() (*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) diff --git a/MUNGA_L4/L4D3D.h b/MUNGA_L4/L4D3D.h index 30275c7..57b876f 100644 --- a/MUNGA_L4/L4D3D.h +++ b/MUNGA_L4/L4D3D.h @@ -111,6 +111,27 @@ public: // race loop tears the renderer down between missions). static void FlushTextureCache(); + //------------------------------------------------------------------ + // Pilot name plates. + // + // The Winners Circle signs ask for textures called player1..player8, + // which are not files - the renderer draws each pilot's callsign into + // a texture at run time. Loading them fails and the plates come out + // blank, so the draw ops that wanted them are noted here as the + // geometry loads, and bound to the real textures once the finishing + // order is known. + // + // Cleared with the texture cache: the entries point at objects that + // belong to the mission being torn down. + //------------------------------------------------------------------ + // Each plate is given a distinct marker texture as it loads. Without + // one they are eight identical untextured draw ops, and consolidation + // merges every static mesh by material - all eight plates would + // collapse into a single shared op that can only ever show one name. + enum { kNamePlateCount = 8 }; + static LPDIRECT3DTEXTURE9 NamePlateMarker(LPDIRECT3DDEVICE9 device, int plate); + static int NamePlateFor(LPDIRECT3DTEXTURE9 texture); + private: static d3d_OBJECT* LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName); @@ -150,6 +171,9 @@ private: static long mNextID; static stdext::hash_map< std::string , L4TEXOP > mTextureCache; + + // index 1..8; [0] unused so the index is the plate number + static LPDIRECT3DTEXTURE9 mNamePlateMarkers[kNamePlateCount + 1]; }; extern int gNumBatches; diff --git a/MUNGA_L4/L4VIDEO.cpp b/MUNGA_L4/L4VIDEO.cpp index 2f868d8..4bb91ce 100644 --- a/MUNGA_L4/L4VIDEO.cpp +++ b/MUNGA_L4/L4VIDEO.cpp @@ -7056,13 +7056,8 @@ void // // Give the pilot a vehicle to look at. Their own is built insideEntity - a // cockpit and no hull - so on the podium they would be the one empty spot. -// Drop those renderables and build the entity again with Disconnected_Eye -// set, which is what makes NotifyOfNewInterestingEntity choose outsideEntity. -// -// Tearing renderables down and rebuilding is the same path the interest -// manager takes every time something drops out of range mid-race, so it is -// well travelled. The eye renderable goes with them, which is fine: the -// presentation camera supplies the view once the podium is up. +// outsideEntity is what builds the exterior, and the renderables for it are +// added to what is already there. // void DPLRenderer::ShowViewpointFromOutside() @@ -7172,8 +7167,56 @@ void DPLRenderer::SortAndReloadNameBitmaps() LoadBitSliceTexture(name_bitmap, mNameTextures[index]); } } - + LoadOrdinalBitmaps(); + + BindNamePlates(); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Put the callsigns on the Winners Circle plates. +// +// mNameTextures is indexed by finishing place (rank + 1), which is how the +// signs are numbered, so the plate beside each spot names whoever is standing +// on it. The plates are static geometry and have already been merged into the +// consolidated mesh by now, so it is the consolidated draw ops that have to be +// re-pointed - the objects they were built from are no longer drawn. +// +void DPLRenderer::BindNamePlates() +{ + int name_count = (int) (sizeof(mNameTextures) / sizeof(mNameTextures[0])); + int bound = 0; + int found = 0; + + std::list::const_iterator iter; + for (iter = mConsolidatedStaticObjects.begin(); + iter != mConsolidatedStaticObjects.end(); ++iter) + { + d3d_OBJECT *object = *iter; + if (object == NULL) + { + continue; + } + for (int op = 0; op < object->GetDrawOpCount(); ++op) + { + L4DRAWOP *draw_op = object->GetDrawOp(op); + int plate = d3d_OBJECT::NamePlateFor(draw_op->texture.texture); + if (plate == 0) + { + continue; + } + ++found; + if (plate < name_count && mNameTextures[plate] != NULL) + { + draw_op->texture.texture = mNameTextures[plate]; + ++bound; + } + } + } + + DEBUG_STREAM << "NamePlates: " << bound << " of " << found + << " plates given a callsign\n" << std::flush; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/MUNGA_L4/L4VIDEO.h b/MUNGA_L4/L4VIDEO.h index 6d0fa8d..c01fba5 100644 --- a/MUNGA_L4/L4VIDEO.h +++ b/MUNGA_L4/L4VIDEO.h @@ -357,6 +357,10 @@ public: void SortAndReloadNameBitmaps(); + // re-point the Winners Circle sign faces at the current callsign + // textures; the plates live in the consolidated static mesh + void BindNamePlates(); + void LoadNameBitmaps(); void LoadOrdinalBitmaps();