@@ -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