fix(world): reallocate shadow map after Suspense + clear LaFabrik doorway
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled

Shadows occasionally failed to render on initial load and the Fabrik
doorway sometimes blocked the player. Both issues are tracked down to
geometry that mounts after Lighting:

- Shadows: GLTFs and the merged static map mount imperatively after
  Lighting, so materials get compiled against a renderer state that
  pre-dates the final scene and bake a 'no shadow map' permutation,
  silently dropping shadows. A WebGL context-restore cycle fixes it,
  but is too invasive. New 'useShadowMapWarmup' hook replays it
  cheaply: once the scene mesh count has been stable for ~1s, it
  disposes the directional shadow map (three.js reallocates it on
  the next render) and marks every material 'needsUpdate' so shaders
  rebind to the freshly created shadow sampler.
- Doorway: the door slab + its Solidify-modifier frame (children of
  the 'Thicken' parent in the LaFabrik GLTF) sat inside the doorway
  AABB and prevented the player from walking through. Stripped from
  the collision octree alongside the existing 'porte' slab; visual
  rendering is unaffected.

Also: extract sun-relative-to-camera placement into a small helper,
remove the temporary diagnostic logs, and document the shadow warmup
in three-debugging.md.
This commit is contained in:
Tom Boullay
2026-06-01 23:37:57 +02:00
parent b144dc1c18
commit 134c0aecb7
5 changed files with 178 additions and 190 deletions
+16 -23
View File
@@ -272,37 +272,30 @@ function CollisionModelInstance({
});
const sceneInstance = useClonedObject(scene);
useEffect(() => {
// Strip the door slab from the la fabrik collision octree so the player
// can walk through the doorway. The visual model is rendered separately
// by MergedStaticMapModel and is unaffected.
// Strip the door slab AND its Solidify-modifier frame from the la fabrik
// collision octree so the player can walk through the doorway. The visual
// model is rendered separately by `MergedStaticMapModel` and is unaffected.
//
// - `porte` (+ Blender suffixes `porte.001` / `porte_001`): the door slab
// itself. We exclude unrelated names like `porte stock` (a shelf of
// stocked doors) by requiring an exact match or a numeric suffix only.
// - Children of a `Thicken` parent: the doorway frame produced by
// Blender's Solidify modifier. Its world AABBs sit right inside the
// doorway and otherwise prevent the player from entering / exiting.
if (node.name !== "lafabrik") return;
// Strip the door slab (and any Blender-suffixed variant like `porte.001`,
// `porte_001`) from the la fabrik collision octree so the player can walk
// through the doorway. The visual model is rendered separately by
// MergedStaticMapModel and is unaffected. We exclude unrelated names like
// `porte stock` (a shelf of stocked doors) by requiring an exact match or
// a numeric suffix only.
const isDoorSlab = (name: string): boolean =>
name === "porte" || /^porte[._]\d+$/i.test(name);
const isDoorFrameThickenChild = (child: THREE.Object3D): boolean =>
child.parent?.name === "Thicken";
// [diag] temporary — collect all door-like candidate names to debug stripping
const candidates: string[] = [];
const removed: THREE.Object3D[] = [];
const doorMeshes: THREE.Object3D[] = [];
sceneInstance.traverse((child) => {
if (/porte/i.test(child.name)) {
candidates.push(child.name);
}
if (isDoorSlab(child.name)) {
removed.push(child);
if (isDoorSlab(child.name) || isDoorFrameThickenChild(child)) {
doorMeshes.push(child);
}
});
console.log("[lafabrik:porte-strip]", {
candidates,
strippedCount: removed.length,
strippedNames: removed.map((c) => c.name),
});
for (const child of removed) {
for (const child of doorMeshes) {
child.removeFromParent();
}
}, [node.name, sceneInstance]);