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.
This commit is contained in:
@@ -131,6 +131,17 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!model) return;
|
||||||
|
|
||||||
|
model.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [model]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.ebikeVisualGroup = groupRef;
|
window.ebikeVisualGroup = groupRef;
|
||||||
window.ebikeParkedPosition = restingPositionRef.current;
|
window.ebikeParkedPosition = restingPositionRef.current;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const CLOUD_DEFAULTS = {
|
|||||||
maxRotation: Math.PI * 2,
|
maxRotation: Math.PI * 2,
|
||||||
minSpeedMultiplier: 0.4,
|
minSpeedMultiplier: 0.4,
|
||||||
maxSpeedMultiplier: 1,
|
maxSpeedMultiplier: 1,
|
||||||
castShadow: false,
|
castShadow: true,
|
||||||
receiveShadow: false,
|
receiveShadow: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,3 +30,12 @@ export const SUN_Y_STEP = 1;
|
|||||||
export const SUN_Z_MIN = -100;
|
export const SUN_Z_MIN = -100;
|
||||||
export const SUN_Z_MAX = 100;
|
export const SUN_Z_MAX = 100;
|
||||||
export const SUN_Z_STEP = 1;
|
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;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export function useOctreeGraphNode(
|
|||||||
|
|
||||||
const octree = new Octree();
|
const octree = new Octree();
|
||||||
octree.fromGraphNode(graphNode);
|
octree.fromGraphNode(graphNode);
|
||||||
|
|
||||||
onOctreeReady(octree);
|
onOctreeReady(octree);
|
||||||
}, [enabled, graphNodeRef, onOctreeReady, rebuildKey]);
|
}, [enabled, graphNodeRef, onOctreeReady, rebuildKey]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,10 @@ interface UseWorldSceneLoadingOptions {
|
|||||||
interface UseWorldSceneLoadingResult {
|
interface UseWorldSceneLoadingResult {
|
||||||
octree: Octree | null;
|
octree: Octree | null;
|
||||||
gameplayReady: boolean;
|
gameplayReady: boolean;
|
||||||
shouldWarmUpShadows: boolean;
|
|
||||||
showGameStage: boolean;
|
showGameStage: boolean;
|
||||||
handleGameStageLoaded: () => void;
|
handleGameStageLoaded: () => void;
|
||||||
handleGameMapLoaded: () => void;
|
handleGameMapLoaded: () => void;
|
||||||
handleOctreeReady: (octree: Octree) => void;
|
handleOctreeReady: (octree: Octree) => void;
|
||||||
handleShadowWarmupReady: () => void;
|
|
||||||
handleShadowWarmupStarted: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWorldSceneLoading({
|
export function useWorldSceneLoading({
|
||||||
@@ -27,19 +24,13 @@ export function useWorldSceneLoading({
|
|||||||
const [octree, setOctree] = useState<Octree | null>(null);
|
const [octree, setOctree] = useState<Octree | null>(null);
|
||||||
const [gameMapLoaded, setGameMapLoaded] = useState(false);
|
const [gameMapLoaded, setGameMapLoaded] = useState(false);
|
||||||
const [gameStageLoaded, setGameStageLoaded] = useState(false);
|
const [gameStageLoaded, setGameStageLoaded] = useState(false);
|
||||||
const [shadowsReady, setShadowsReady] = useState(false);
|
|
||||||
const showGameStage = sceneMode === "game" && gameMapLoaded;
|
const showGameStage = sceneMode === "game" && gameMapLoaded;
|
||||||
const gameSceneReadyForShadows =
|
const gameplayReady = showGameStage && gameStageLoaded && octree !== null;
|
||||||
showGameStage && gameStageLoaded && octree !== null;
|
|
||||||
const shadowWarmupReady = sceneMode === "game" && gameSceneReadyForShadows;
|
|
||||||
const shouldWarmUpShadows = shadowWarmupReady && !shadowsReady;
|
|
||||||
const gameplayReady = gameSceneReadyForShadows && shadowsReady;
|
|
||||||
const sceneReady =
|
const sceneReady =
|
||||||
(sceneMode === "game" && gameplayReady) ||
|
(sceneMode === "game" && gameplayReady) ||
|
||||||
(sceneMode === "physics" && octree !== null);
|
(sceneMode === "physics" && octree !== null);
|
||||||
|
|
||||||
const handleGameMapLoaded = useCallback(() => {
|
const handleGameMapLoaded = useCallback(() => {
|
||||||
setShadowsReady(false);
|
|
||||||
setGameMapLoaded(true);
|
setGameMapLoaded(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -54,7 +45,6 @@ export function useWorldSceneLoading({
|
|||||||
|
|
||||||
const handleOctreeReady = useCallback(
|
const handleOctreeReady = useCallback(
|
||||||
(nextOctree: Octree) => {
|
(nextOctree: Octree) => {
|
||||||
setShadowsReady(false);
|
|
||||||
setOctree(nextOctree);
|
setOctree(nextOctree);
|
||||||
onLoadingStateChange?.({
|
onLoadingStateChange?.({
|
||||||
currentStep: "Collision prête",
|
currentStep: "Collision prête",
|
||||||
@@ -65,23 +55,6 @@ export function useWorldSceneLoading({
|
|||||||
[onLoadingStateChange],
|
[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(() => {
|
useEffect(() => {
|
||||||
onLoadingStateChange?.({
|
onLoadingStateChange?.({
|
||||||
currentStep: "Initialisation du jeu",
|
currentStep: "Initialisation du jeu",
|
||||||
@@ -115,12 +88,9 @@ export function useWorldSceneLoading({
|
|||||||
return {
|
return {
|
||||||
octree,
|
octree,
|
||||||
gameplayReady,
|
gameplayReady,
|
||||||
shouldWarmUpShadows,
|
|
||||||
showGameStage,
|
showGameStage,
|
||||||
handleGameStageLoaded,
|
handleGameStageLoaded,
|
||||||
handleGameMapLoaded,
|
handleGameMapLoaded,
|
||||||
handleOctreeReady,
|
handleOctreeReady,
|
||||||
handleShadowWarmupReady,
|
|
||||||
handleShadowWarmupStarted,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const DEBUG_FOLDER_ORDER = [
|
|||||||
"Hand Tracking",
|
"Hand Tracking",
|
||||||
"Map",
|
"Map",
|
||||||
"Personnages",
|
"Personnages",
|
||||||
|
"Debug",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
|||||||
@@ -15,24 +15,11 @@ import { SkyModel } from "@/components/three/world/SkyModel";
|
|||||||
import { CloudSystem } from "@/world/clouds/CloudSystem";
|
import { CloudSystem } from "@/world/clouds/CloudSystem";
|
||||||
import { FogSystem } from "@/world/fog/FogSystem";
|
import { FogSystem } from "@/world/fog/FogSystem";
|
||||||
import { GrassSystem } from "@/world/grass/GrassSystem";
|
import { GrassSystem } from "@/world/grass/GrassSystem";
|
||||||
import { SceneShadowWarmup } from "@/world/SceneShadowWarmup";
|
|
||||||
import { VegetationSystem } from "@/world/vegetation/VegetationSystem";
|
import { VegetationSystem } from "@/world/vegetation/VegetationSystem";
|
||||||
import { WaterSystem } from "@/world/water/WaterSystem";
|
import { WaterSystem } from "@/world/water/WaterSystem";
|
||||||
import { WorldPlane } from "@/world/WorldPlane";
|
import { WorldPlane } from "@/world/WorldPlane";
|
||||||
|
|
||||||
interface ShadowWarmupConfig {
|
export function Environment(): React.JSX.Element {
|
||||||
active: boolean;
|
|
||||||
onReady: () => void;
|
|
||||||
onStarted: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EnvironmentProps {
|
|
||||||
shadowWarmup?: ShadowWarmupConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Environment({
|
|
||||||
shadowWarmup,
|
|
||||||
}: EnvironmentProps): React.JSX.Element {
|
|
||||||
const sceneMode = useSceneMode();
|
const sceneMode = useSceneMode();
|
||||||
const groups = useMapPerformanceStore((state) => state.groups);
|
const groups = useMapPerformanceStore((state) => state.groups);
|
||||||
const models = useMapPerformanceStore((state) => state.models);
|
const models = useMapPerformanceStore((state) => state.models);
|
||||||
@@ -47,13 +34,6 @@ export function Environment({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FogSystem />
|
<FogSystem />
|
||||||
{shadowWarmup ? (
|
|
||||||
<SceneShadowWarmup
|
|
||||||
active={shadowWarmup.active}
|
|
||||||
onReady={shadowWarmup.onReady}
|
|
||||||
onStarted={shadowWarmup.onStarted}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{showSky ? (
|
{showSky ? (
|
||||||
<SkyModel
|
<SkyModel
|
||||||
fallbackColor={GAME_SCENE_FALLBACK_BACKGROUND_COLOR}
|
fallbackColor={GAME_SCENE_FALLBACK_BACKGROUND_COLOR}
|
||||||
|
|||||||
+11
-16
@@ -11,6 +11,7 @@ import {
|
|||||||
AMBIENT_INTENSITY_MAX,
|
AMBIENT_INTENSITY_MAX,
|
||||||
AMBIENT_INTENSITY_MIN,
|
AMBIENT_INTENSITY_MIN,
|
||||||
AMBIENT_INTENSITY_STEP,
|
AMBIENT_INTENSITY_STEP,
|
||||||
|
SHADOW_CONFIG,
|
||||||
SUN_INTENSITY_MAX,
|
SUN_INTENSITY_MAX,
|
||||||
SUN_INTENSITY_MIN,
|
SUN_INTENSITY_MIN,
|
||||||
SUN_INTENSITY_STEP,
|
SUN_INTENSITY_STEP,
|
||||||
@@ -28,30 +29,25 @@ import { LA_FABRIK_INTERIOR_LIGHT_POSITION } from "@/data/world/laFabrikConfig";
|
|||||||
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
||||||
import { LIGHTING_STATE } from "@/world/lightingState";
|
import { LIGHTING_STATE } from "@/world/lightingState";
|
||||||
|
|
||||||
const SHADOW_MAP_SIZE = 2048;
|
|
||||||
const SHADOW_CAMERA_SIZE = 95;
|
|
||||||
const SHADOW_CAMERA_NEAR = 0.5;
|
|
||||||
const SHADOW_CAMERA_FAR = 300;
|
|
||||||
|
|
||||||
function configureRendererShadows(gl: WebGLRenderer): void {
|
function configureRendererShadows(gl: WebGLRenderer): void {
|
||||||
gl.shadowMap.enabled = true;
|
gl.shadowMap.enabled = true;
|
||||||
gl.shadowMap.type = PCFShadowMap;
|
gl.shadowMap.type = PCFShadowMap;
|
||||||
gl.shadowMap.autoUpdate = true;
|
gl.shadowMap.autoUpdate = true;
|
||||||
gl.shadowMap.needsUpdate = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureSunShadow(sun: DirectionalLight, sunTarget: Object3D): void {
|
function configureSunShadow(sun: DirectionalLight, sunTarget: Object3D): void {
|
||||||
sun.target = sunTarget;
|
sun.target = sunTarget;
|
||||||
sun.shadow.autoUpdate = true;
|
sun.shadow.autoUpdate = true;
|
||||||
sun.shadow.needsUpdate = true;
|
sun.shadow.bias = SHADOW_CONFIG.bias;
|
||||||
sun.shadow.mapSize.width = SHADOW_MAP_SIZE;
|
sun.shadow.normalBias = SHADOW_CONFIG.normalBias;
|
||||||
sun.shadow.mapSize.height = SHADOW_MAP_SIZE;
|
sun.shadow.mapSize.width = SHADOW_CONFIG.mapSize;
|
||||||
sun.shadow.camera.left = -SHADOW_CAMERA_SIZE;
|
sun.shadow.mapSize.height = SHADOW_CONFIG.mapSize;
|
||||||
sun.shadow.camera.right = SHADOW_CAMERA_SIZE;
|
sun.shadow.camera.left = -SHADOW_CONFIG.cameraSize;
|
||||||
sun.shadow.camera.top = SHADOW_CAMERA_SIZE;
|
sun.shadow.camera.right = SHADOW_CONFIG.cameraSize;
|
||||||
sun.shadow.camera.bottom = -SHADOW_CAMERA_SIZE;
|
sun.shadow.camera.top = SHADOW_CONFIG.cameraSize;
|
||||||
sun.shadow.camera.near = SHADOW_CAMERA_NEAR;
|
sun.shadow.camera.bottom = -SHADOW_CONFIG.cameraSize;
|
||||||
sun.shadow.camera.far = SHADOW_CAMERA_FAR;
|
sun.shadow.camera.near = SHADOW_CONFIG.cameraNear;
|
||||||
|
sun.shadow.camera.far = SHADOW_CONFIG.cameraFar;
|
||||||
sun.shadow.camera.updateProjectionMatrix();
|
sun.shadow.camera.updateProjectionMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +114,6 @@ export function Lighting(): React.JSX.Element {
|
|||||||
sun.current.color.set(LIGHTING_STATE.sunColor);
|
sun.current.color.set(LIGHTING_STATE.sunColor);
|
||||||
sun.current.intensity = LIGHTING_STATE.sunIntensity;
|
sun.current.intensity = LIGHTING_STATE.sunIntensity;
|
||||||
sun.current.updateMatrixWorld();
|
sun.current.updateMatrixWorld();
|
||||||
sun.current.shadow.needsUpdate = true;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { useThree } from "@react-three/fiber";
|
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
interface SceneShadowWarmupProps {
|
|
||||||
active: boolean;
|
|
||||||
onReady: () => 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;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user