Skip to content

Commit 854a0c8

Browse files
fix(draw): per-mesh shadow-pass hide via mutate-around-call (#46)
draw_set_mesh_visible's mesh_type=2 sentinel correctly hides a mesh in the body pass but leaks into the shadow pass — the engine legitimately consumes type-2 meshes as shadow casters, so a hidden body mesh projects a wrong shadow. Maintain a per-mesh hidden set alongside the body-pass rewrite; in hook_DoDrawShadow3D, temporarily flip hidden meshes to a shadow- skip sentinel for the duration of the call and restore after. Co-authored-by: enderclem <enderclem@gmail.com>
1 parent f833d13 commit 854a0c8

1 file changed

Lines changed: 67 additions & 5 deletions

File tree

  • src/lua_extensions/bindings/hades

src/lua_extensions/bindings/hades/draw.cpp

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ namespace lua::hades::draw
4141
uint32_t texture_name_hash; // +0x40: feeds GetTexture
4242
uint32_t texture_handle; // +0x44: resolved bindless handle
4343
uint32_t mesh_name_hash; // +0x48: sgg::HashGuid of the mesh's name
44-
uint8_t mesh_type; // +0x4C: 0 main, 1 outline, 2 shadow/hidden
44+
uint8_t mesh_type; // +0x4C: 0 main, 1 outline, 2 shadow
4545
char pad_4D[3];
4646
};
4747
static_assert(offsetof(GrannyMeshData, texture_name_hash) == 0x40);
@@ -106,9 +106,11 @@ namespace lua::hades::draw
106106
constexpr int kBucketWalkGuard = 32;
107107
constexpr size_t kMaxMeshesPerEntry = 128;
108108

109-
// Sentinel mesh_type used to hide a single mesh inside an entry:
110-
// DoDraw3D's own mesh-type switch skips meshes with this value.
111-
constexpr uint8_t kMeshTypeHidden = 2;
109+
// Body-pass hide: DoDraw3D's case-2 branch skips body render.
110+
// Shadow-pass hide: 0xFF is the shadow switch's default case.
111+
constexpr uint8_t kMeshTypeHidden = 2;
112+
constexpr uint8_t kMeshTypeShadowSkip = 0xFF;
113+
static_assert(kMeshTypeHidden != kMeshTypeShadowSkip);
112114

113115
// ─── SEH-protected pointer reads ──────────────────────────────────
114116
// The mModelData walk touches pointers that can be null or
@@ -196,6 +198,24 @@ namespace lua::hades::draw
196198
static std::unordered_set<unsigned int> g_hidden_entries;
197199
static std::unordered_map<unsigned int, unsigned int> g_remap; // original → variant
198200

201+
// Per-mesh hide set keyed by (entry_hash, mesh_hash). Drives the
202+
// shadow-pass skip in hook_DoDrawShadow3D.
203+
struct MeshKey
204+
{
205+
uint32_t entry;
206+
uint32_t mesh;
207+
bool operator==(const MeshKey& o) const noexcept
208+
{ return entry == o.entry && mesh == o.mesh; }
209+
};
210+
struct MeshKeyHash
211+
{
212+
size_t operator()(const MeshKey& k) const noexcept
213+
{
214+
return (size_t)k.entry * 2654435761u ^ k.mesh;
215+
}
216+
};
217+
static std::unordered_set<MeshKey, MeshKeyHash> g_hidden_meshes;
218+
199219
// Fast-path flag for the code cave: avoids the function-call overhead
200220
// on every draw entry when nothing is active (hidden or remapped).
201221
static volatile uint8_t g_any_active = 0;
@@ -260,14 +280,50 @@ namespace lua::hades::draw
260280
// shadow3D=variant) that appears to wedge the render thread.
261281
// Keeping shadow/thumbnail on stock is the safe subset: the main
262282
// DoDraw3D swap carries the visual change on its own.
283+
//
284+
// Per-mesh hide for the shadow pass: temporarily rewrite each
285+
// hidden mesh's mesh_type to kMeshTypeShadowSkip, call the
286+
// original, restore. Safe because the render thread serialises
287+
// passes — no other pass observes the temporary value.
263288
static void hook_DoDrawShadow3D(void* vec_ref, unsigned int index, int param, sgg::HashGuid hash)
264289
{
265290
{
266291
std::shared_lock l(g_mutex);
267292
if (g_hidden_entries.count(hash.mId))
268293
return;
269294
}
295+
296+
struct SavedMeshType { GrannyMeshData* gmd; uint8_t orig; };
297+
std::vector<SavedMeshType> patched;
298+
{
299+
std::shared_lock l(g_mutex);
300+
if (!g_hidden_meshes.empty())
301+
{
302+
ModelDataNode* node = find_model_data_node(hash.mId);
303+
if (node)
304+
{
305+
GrannyMeshData* meshes = nullptr;
306+
size_t count = 0;
307+
if (get_entry_meshes(node, &meshes, &count))
308+
{
309+
for (size_t i = 0; i < count; i++)
310+
{
311+
uint32_t mhash = 0;
312+
if (!safe_read_u32(&meshes[i].mesh_name_hash, &mhash)) continue;
313+
if (!mhash) continue;
314+
if (!g_hidden_meshes.count({hash.mId, mhash})) continue;
315+
patched.push_back({&meshes[i], meshes[i].mesh_type});
316+
meshes[i].mesh_type = kMeshTypeShadowSkip;
317+
}
318+
}
319+
}
320+
}
321+
}
322+
270323
big::g_hooking->get_original<hook_DoDrawShadow3D>()(vec_ref, index, param, hash);
324+
325+
for (auto& s : patched)
326+
s.gmd->mesh_type = s.orig;
271327
}
272328

273329
static void hook_DoDraw3DThumbnail(void* vec_ref, unsigned int index, int param, sgg::HashGuid hash)
@@ -659,6 +715,13 @@ namespace lua::hades::draw
659715

660716
int matched = 0;
661717
std::lock_guard lk(g_saved_mutex);
718+
{
719+
std::unique_lock l(g_mutex);
720+
if (visible)
721+
g_hidden_meshes.erase({entry_guid.mId, mesh_guid.mId});
722+
else
723+
g_hidden_meshes.insert({entry_guid.mId, mesh_guid.mId});
724+
}
662725
for (size_t i = 0; i < mesh_count; i++)
663726
{
664727
GrannyMeshData& gmd = meshes[i];
@@ -686,7 +749,6 @@ namespace lua::hades::draw
686749
g_saved_mesh_type[key] = current_type;
687750
gmd.mesh_type = kMeshTypeHidden;
688751
}
689-
// else: already hidden: no-op
690752
}
691753
matched++;
692754
// Don't break: toggle main+outline+shadow variants sharing

0 commit comments

Comments
 (0)