From 777e51efeb01f35fbb98b9fb9de69c8f9a28a7e2 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Mon, 1 Jun 2026 14:14:14 +0200 Subject: [PATCH] fix(world): centralize shadow config and remove warmup - Extract SHADOW_CONFIG into lightingConfig.ts (bias=0, normalBias=0, cameraSize=95) matching the historically working values from develop. - Drop SceneShadowWarmup; rely on sun.shadow.autoUpdate=true for steady-state refresh. - Enable cloud castShadow and traverse Ebike meshes for cast/receive. --- src/components/ebike/Ebike.tsx | 11 +++ src/data/world/cloudConfig.ts | 2 +- src/data/world/lightingConfig.ts | 9 +++ src/hooks/three/useOctreeGraphNode.ts | 1 + src/hooks/world/useWorldSceneLoading.ts | 32 +-------- src/utils/debug/Debug.ts | 1 + src/world/Environment.tsx | 22 +----- src/world/Lighting.tsx | 27 +++----- src/world/SceneShadowWarmup.tsx | 92 ------------------------- 9 files changed, 36 insertions(+), 161 deletions(-) delete mode 100644 src/world/SceneShadowWarmup.tsx diff --git a/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index d5a32ac..6eb0455 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -131,6 +131,17 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { } }, [model]); + useEffect(() => { + if (!model) return; + + model.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.castShadow = true; + child.receiveShadow = true; + } + }); + }, [model]); + useEffect(() => { window.ebikeVisualGroup = groupRef; window.ebikeParkedPosition = restingPositionRef.current; diff --git a/src/data/world/cloudConfig.ts b/src/data/world/cloudConfig.ts index 07615c3..c86aa13 100644 --- a/src/data/world/cloudConfig.ts +++ b/src/data/world/cloudConfig.ts @@ -19,7 +19,7 @@ export const CLOUD_DEFAULTS = { maxRotation: Math.PI * 2, minSpeedMultiplier: 0.4, maxSpeedMultiplier: 1, - castShadow: false, + castShadow: true, receiveShadow: false, }; diff --git a/src/data/world/lightingConfig.ts b/src/data/world/lightingConfig.ts index a61c06a..bf91955 100644 --- a/src/data/world/lightingConfig.ts +++ b/src/data/world/lightingConfig.ts @@ -30,3 +30,12 @@ export const SUN_Y_STEP = 1; export const SUN_Z_MIN = -100; export const SUN_Z_MAX = 100; export const SUN_Z_STEP = 1; + +export const SHADOW_CONFIG = { + mapSize: 2048, + cameraSize: 95, + cameraNear: 0.5, + cameraFar: 300, + bias: 0, + normalBias: 0, +} as const; diff --git a/src/hooks/three/useOctreeGraphNode.ts b/src/hooks/three/useOctreeGraphNode.ts index f6a0d0a..5a1fe31 100644 --- a/src/hooks/three/useOctreeGraphNode.ts +++ b/src/hooks/three/useOctreeGraphNode.ts @@ -27,6 +27,7 @@ export function useOctreeGraphNode( const octree = new Octree(); octree.fromGraphNode(graphNode); + onOctreeReady(octree); }, [enabled, graphNodeRef, onOctreeReady, rebuildKey]); } diff --git a/src/hooks/world/useWorldSceneLoading.ts b/src/hooks/world/useWorldSceneLoading.ts index c629fa7..e82e92b 100644 --- a/src/hooks/world/useWorldSceneLoading.ts +++ b/src/hooks/world/useWorldSceneLoading.ts @@ -11,13 +11,10 @@ interface UseWorldSceneLoadingOptions { interface UseWorldSceneLoadingResult { octree: Octree | null; gameplayReady: boolean; - shouldWarmUpShadows: boolean; showGameStage: boolean; handleGameStageLoaded: () => void; handleGameMapLoaded: () => void; handleOctreeReady: (octree: Octree) => void; - handleShadowWarmupReady: () => void; - handleShadowWarmupStarted: () => void; } export function useWorldSceneLoading({ @@ -27,19 +24,13 @@ export function useWorldSceneLoading({ const [octree, setOctree] = useState(null); const [gameMapLoaded, setGameMapLoaded] = useState(false); const [gameStageLoaded, setGameStageLoaded] = useState(false); - const [shadowsReady, setShadowsReady] = useState(false); const showGameStage = sceneMode === "game" && gameMapLoaded; - const gameSceneReadyForShadows = - showGameStage && gameStageLoaded && octree !== null; - const shadowWarmupReady = sceneMode === "game" && gameSceneReadyForShadows; - const shouldWarmUpShadows = shadowWarmupReady && !shadowsReady; - const gameplayReady = gameSceneReadyForShadows && shadowsReady; + const gameplayReady = showGameStage && gameStageLoaded && octree !== null; const sceneReady = (sceneMode === "game" && gameplayReady) || (sceneMode === "physics" && octree !== null); const handleGameMapLoaded = useCallback(() => { - setShadowsReady(false); setGameMapLoaded(true); }, []); @@ -54,7 +45,6 @@ export function useWorldSceneLoading({ const handleOctreeReady = useCallback( (nextOctree: Octree) => { - setShadowsReady(false); setOctree(nextOctree); onLoadingStateChange?.({ currentStep: "Collision prĂȘte", @@ -65,23 +55,6 @@ export function useWorldSceneLoading({ [onLoadingStateChange], ); - const handleShadowWarmupStarted = useCallback(() => { - onLoadingStateChange?.({ - currentStep: "Activation des ombres", - progress: 0.97, - status: "loading", - }); - }, [onLoadingStateChange]); - - const handleShadowWarmupReady = useCallback(() => { - setShadowsReady(true); - onLoadingStateChange?.({ - currentStep: "Ombres prĂȘtes", - progress: 0.99, - status: "loading", - }); - }, [onLoadingStateChange]); - useEffect(() => { onLoadingStateChange?.({ currentStep: "Initialisation du jeu", @@ -115,12 +88,9 @@ export function useWorldSceneLoading({ return { octree, gameplayReady, - shouldWarmUpShadows, showGameStage, handleGameStageLoaded, handleGameMapLoaded, handleOctreeReady, - handleShadowWarmupReady, - handleShadowWarmupStarted, }; } diff --git a/src/utils/debug/Debug.ts b/src/utils/debug/Debug.ts index e848f15..7c9606b 100644 --- a/src/utils/debug/Debug.ts +++ b/src/utils/debug/Debug.ts @@ -26,6 +26,7 @@ const DEBUG_FOLDER_ORDER = [ "Hand Tracking", "Map", "Personnages", + "Debug", ] as const; function isRecord(value: unknown): value is Record { diff --git a/src/world/Environment.tsx b/src/world/Environment.tsx index 7333d0f..430bc66 100644 --- a/src/world/Environment.tsx +++ b/src/world/Environment.tsx @@ -15,24 +15,11 @@ import { SkyModel } from "@/components/three/world/SkyModel"; import { CloudSystem } from "@/world/clouds/CloudSystem"; import { FogSystem } from "@/world/fog/FogSystem"; import { GrassSystem } from "@/world/grass/GrassSystem"; -import { SceneShadowWarmup } from "@/world/SceneShadowWarmup"; import { VegetationSystem } from "@/world/vegetation/VegetationSystem"; import { WaterSystem } from "@/world/water/WaterSystem"; import { WorldPlane } from "@/world/WorldPlane"; -interface ShadowWarmupConfig { - active: boolean; - onReady: () => void; - onStarted: () => void; -} - -interface EnvironmentProps { - shadowWarmup?: ShadowWarmupConfig; -} - -export function Environment({ - shadowWarmup, -}: EnvironmentProps): React.JSX.Element { +export function Environment(): React.JSX.Element { const sceneMode = useSceneMode(); const groups = useMapPerformanceStore((state) => state.groups); const models = useMapPerformanceStore((state) => state.models); @@ -47,13 +34,6 @@ export function Environment({ return ( <> - {shadowWarmup ? ( - - ) : null} {showSky ? ( void; - onStarted: () => void; -} - -function markShadowLightForUpdate(object: THREE.Object3D): void { - if ( - !( - object instanceof THREE.DirectionalLight || - object instanceof THREE.PointLight || - object instanceof THREE.SpotLight - ) - ) { - return; - } - - if (!object.castShadow) return; - - object.updateMatrixWorld(true); - object.shadow.camera.updateProjectionMatrix(); - object.shadow.needsUpdate = true; -} - -function forceSceneShadowPass( - gl: THREE.WebGLRenderer, - scene: THREE.Scene, -): void { - gl.shadowMap.enabled = true; - gl.shadowMap.type = THREE.PCFShadowMap; - gl.shadowMap.autoUpdate = true; - gl.shadowMap.needsUpdate = true; - - scene.updateMatrixWorld(true); - scene.traverse((object) => { - if (object instanceof THREE.Mesh) { - object.updateMatrixWorld(true); - } - - markShadowLightForUpdate(object); - }); -} - -export function SceneShadowWarmup({ - active, - onReady, - onStarted, -}: SceneShadowWarmupProps): null { - const gl = useThree((state) => state.gl); - const scene = useThree((state) => state.scene); - const invalidate = useThree((state) => state.invalidate); - const isRunningRef = useRef(false); - - useEffect(() => { - if (!active) { - isRunningRef.current = false; - return undefined; - } - - if (isRunningRef.current) return undefined; - - isRunningRef.current = true; - onStarted(); - forceSceneShadowPass(gl, scene); - invalidate(); - - let firstFrame = 0; - let secondFrame = 0; - - firstFrame = window.requestAnimationFrame(() => { - forceSceneShadowPass(gl, scene); - invalidate(); - - secondFrame = window.requestAnimationFrame(() => { - forceSceneShadowPass(gl, scene); - invalidate(); - onReady(); - }); - }); - - return () => { - window.cancelAnimationFrame(firstFrame); - window.cancelAnimationFrame(secondFrame); - }; - }, [active, gl, invalidate, onReady, onStarted, scene]); - - return null; -}