update : put every constante in the data folder

This commit is contained in:
Tom Boullay
2026-04-17 15:42:10 +02:00
parent 4b14295749
commit 7e72f1e803
21 changed files with 570 additions and 209 deletions
+24 -2
View File
@@ -1,3 +1,25 @@
export function Environment(): React.JSX.Element {
return <color attach="background" args={["#0b1018"]} />;
import * as THREE from "three";
import { useLoader } from "@react-three/fiber";
import {
PHYSICS_SCENE_BACKGROUND_COLOR,
SKYBOX_FACES,
} from "@/data/environmentConfig";
import { useSceneMode } from "@/hooks/debug/useSceneMode";
function SkyBox(): React.JSX.Element {
const texture = useLoader(THREE.CubeTextureLoader, [...SKYBOX_FACES]);
return <primitive attach="background" object={texture} />;
}
export function Environment(): React.JSX.Element {
const sceneMode = useSceneMode();
if (sceneMode === "physics") {
return (
<color attach="background" args={[PHYSICS_SCENE_BACKGROUND_COLOR]} />
);
}
return <SkyBox />;
}
+50 -14
View File
@@ -1,6 +1,26 @@
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import type { AmbientLight, DirectionalLight } from "three";
import {
AMBIENT_INTENSITY_MAX,
AMBIENT_INTENSITY_MIN,
AMBIENT_INTENSITY_STEP,
AMBIENT_LIGHT_COLOR,
LIGHTING_DEFAULTS,
SUN_INTENSITY_MAX,
SUN_INTENSITY_MIN,
SUN_INTENSITY_STEP,
SUN_LIGHT_COLOR,
SUN_X_MAX,
SUN_X_MIN,
SUN_X_STEP,
SUN_Y_MAX,
SUN_Y_MIN,
SUN_Y_STEP,
SUN_Z_MAX,
SUN_Z_MIN,
SUN_Z_STEP,
} from "@/data/lightingConfig";
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
type LightingState = {
@@ -11,24 +31,40 @@ type LightingState = {
sunZ: number;
};
const LIGHTING_STATE: LightingState = {
ambientIntensity: 1.8,
sunIntensity: 2.8,
sunX: 60,
sunY: 80,
sunZ: 30,
};
const LIGHTING_STATE: LightingState = { ...LIGHTING_DEFAULTS };
export function Lighting(): React.JSX.Element {
const ambient = useRef<AmbientLight>(null);
const sun = useRef<DirectionalLight>(null);
useDebugFolder("Lighting", (folder) => {
folder.add(LIGHTING_STATE, "ambientIntensity", 0, 5, 0.1).name("Ambient");
folder.add(LIGHTING_STATE, "sunIntensity", 0, 8, 0.1).name("Sun Intensity");
folder.add(LIGHTING_STATE, "sunX", -100, 100, 1).name("Sun X");
folder.add(LIGHTING_STATE, "sunY", 0, 150, 1).name("Sun Y");
folder.add(LIGHTING_STATE, "sunZ", -100, 100, 1).name("Sun Z");
folder
.add(
LIGHTING_STATE,
"ambientIntensity",
AMBIENT_INTENSITY_MIN,
AMBIENT_INTENSITY_MAX,
AMBIENT_INTENSITY_STEP,
)
.name("Ambient");
folder
.add(
LIGHTING_STATE,
"sunIntensity",
SUN_INTENSITY_MIN,
SUN_INTENSITY_MAX,
SUN_INTENSITY_STEP,
)
.name("Sun Intensity");
folder
.add(LIGHTING_STATE, "sunX", SUN_X_MIN, SUN_X_MAX, SUN_X_STEP)
.name("Sun X");
folder
.add(LIGHTING_STATE, "sunY", SUN_Y_MIN, SUN_Y_MAX, SUN_Y_STEP)
.name("Sun Y");
folder
.add(LIGHTING_STATE, "sunZ", SUN_Z_MIN, SUN_Z_MAX, SUN_Z_STEP)
.name("Sun Z");
});
useFrame(() => {
@@ -51,7 +87,7 @@ export function Lighting(): React.JSX.Element {
<ambientLight
ref={ambient}
intensity={LIGHTING_STATE.ambientIntensity}
color="#dbeafe"
color={AMBIENT_LIGHT_COLOR}
/>
<directionalLight
ref={sun}
@@ -61,7 +97,7 @@ export function Lighting(): React.JSX.Element {
LIGHTING_STATE.sunZ,
]}
intensity={LIGHTING_STATE.sunIntensity}
color="#fff7ed"
color={SUN_LIGHT_COLOR}
castShadow
/>
</>
+2 -1
View File
@@ -3,6 +3,7 @@ import { useThree } from "@react-three/fiber";
import { useGLTF } from "@react-three/drei";
import * as THREE from "three";
import { Octree } from "three/addons/math/Octree.js";
import { MAP_DEBUG_BOX_HELPER_COLOR } from "@/data/debugConfig";
import { Debug } from "@/utils/debug/Debug";
const MAP_PATH = "/models/map/model.gltf";
@@ -38,7 +39,7 @@ export function Map({ onOctreeReady }: MapProps): React.JSX.Element {
groupRef.current.traverse((child) => {
if (!(child instanceof THREE.Mesh)) return;
const helper = new THREE.BoxHelper(child, 0x00ff88);
const helper = new THREE.BoxHelper(child, MAP_DEBUG_BOX_HELPER_COLOR);
scene.add(helper);
helpers.push(helper);
});
+7 -1
View File
@@ -1,5 +1,9 @@
import { useState, useCallback } from "react";
import type { Octree } from "three/addons/math/Octree.js";
import {
PLAYER_SPAWN_Y_GAME,
PLAYER_SPAWN_Y_PHYSICS,
} from "@/data/playerConfig";
import { useCameraMode } from "@/hooks/debug/useCameraMode";
import { useSceneMode } from "@/hooks/debug/useSceneMode";
import { DebugCameraControls } from "@/utils/debug/scene/DebugCameraControls";
@@ -32,7 +36,9 @@ export function World(): React.JSX.Element {
{cameraMode !== "debug" ? (
<PlayerComponent
octree={octree}
spawnY={sceneMode === "game" ? 100 : 3}
spawnY={
sceneMode === "game" ? PLAYER_SPAWN_Y_GAME : PLAYER_SPAWN_Y_PHYSICS
}
/>
) : null}
</>
+59 -9
View File
@@ -2,8 +2,25 @@ import { useEffect, useRef } from "react";
import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier";
import * as THREE from "three";
import { Octree } from "three/addons/math/Octree.js";
import { GrabCube } from "@/world/objects/GrabCube";
import { TriggerSphere } from "@/world/objects/TriggerSphere";
import { GrabbableObject } from "@/components/3d/GrabbableObject";
import { TriggerObject } from "@/components/3d/TriggerObject";
import {
TEST_SCENE_FLOOR_COLLIDER_HALF_EXTENTS,
TEST_SCENE_FLOOR_POSITION,
TEST_SCENE_FLOOR_SIZE,
TEST_SCENE_GRABBABLE_BOX_SIZE,
TEST_SCENE_GRABBABLE_COLOR,
TEST_SCENE_GRABBABLE_METALNESS,
TEST_SCENE_GRABBABLE_POSITION,
TEST_SCENE_GRABBABLE_ROUGHNESS,
TEST_SCENE_TRIGGER_COLOR,
TEST_SCENE_TRIGGER_METALNESS,
TEST_SCENE_TRIGGER_POSITION,
TEST_SCENE_TRIGGER_RADIUS,
TEST_SCENE_TRIGGER_ROUGHNESS,
TEST_SCENE_TRIGGER_SEGMENTS,
TEST_SCENE_TRIGGER_SOUND_PATH,
} from "@/data/testSceneConfig";
interface TestSceneProps {
onOctreeReady: (octree: Octree) => void;
@@ -28,21 +45,54 @@ export function TestScene({
return (
<>
{/* Invisible floor mesh for Octree player collision */}
<group ref={floorRef}>
<mesh visible={false} position={[0, -0.5, 0]}>
<boxGeometry args={[200, 1, 200]} />
<mesh visible={false} position={TEST_SCENE_FLOOR_POSITION}>
<boxGeometry args={TEST_SCENE_FLOOR_SIZE} />
<meshBasicMaterial />
</mesh>
</group>
{/* Rapier physics for interactable objects */}
<Physics>
<RigidBody type="fixed">
<CuboidCollider args={[100, 0.5, 100]} position={[0, -0.5, 0]} />
<CuboidCollider
args={TEST_SCENE_FLOOR_COLLIDER_HALF_EXTENTS}
position={TEST_SCENE_FLOOR_POSITION}
/>
</RigidBody>
<GrabCube />
<TriggerSphere />
<GrabbableObject
position={TEST_SCENE_GRABBABLE_POSITION}
colliders="cuboid"
>
<mesh castShadow receiveShadow>
<boxGeometry args={TEST_SCENE_GRABBABLE_BOX_SIZE} />
<meshStandardMaterial
color={TEST_SCENE_GRABBABLE_COLOR}
roughness={TEST_SCENE_GRABBABLE_ROUGHNESS}
metalness={TEST_SCENE_GRABBABLE_METALNESS}
/>
</mesh>
</GrabbableObject>
<TriggerObject
position={TEST_SCENE_TRIGGER_POSITION}
soundPath={TEST_SCENE_TRIGGER_SOUND_PATH}
>
<mesh castShadow receiveShadow>
<sphereGeometry
args={[
TEST_SCENE_TRIGGER_RADIUS,
TEST_SCENE_TRIGGER_SEGMENTS,
TEST_SCENE_TRIGGER_SEGMENTS,
]}
/>
<meshStandardMaterial
color={TEST_SCENE_TRIGGER_COLOR}
roughness={TEST_SCENE_TRIGGER_ROUGHNESS}
metalness={TEST_SCENE_TRIGGER_METALNESS}
/>
</mesh>
</TriggerObject>
</Physics>
</>
);
-80
View File
@@ -1,80 +0,0 @@
import { useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import type { RapierRigidBody } from "@react-three/rapier";
import * as THREE from "three";
import { InteractableObject } from "@/components/3d/InteractableObject";
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
const CUBE_SIZE = 0.5;
const HOLD_DISTANCE = 2;
const SPAWN_POSITION: [number, number, number] = [0, 1, -3];
const params = { stiffness: 15, throwBoost: 1.0 };
const _holdTarget = new THREE.Vector3();
const _currentPos = new THREE.Vector3();
const _velocity = new THREE.Vector3();
export function GrabCube(): React.JSX.Element {
const camera = useThree((state) => state.camera);
const rbRef = useRef<RapierRigidBody>(null);
const isHolding = useRef(false);
useDebugFolder("GrabCube", (folder) => {
folder.add(params, "stiffness", 1, 50, 1).name("Hold stiffness");
folder.add(params, "throwBoost", 0.5, 3.0, 0.1).name("Throw boost");
});
useFrame(() => {
if (!isHolding.current || !rbRef.current) return;
camera.getWorldDirection(_holdTarget);
_holdTarget.multiplyScalar(HOLD_DISTANCE).add(camera.position);
const t = rbRef.current.translation();
_currentPos.set(t.x, t.y, t.z);
_velocity
.subVectors(_holdTarget, _currentPos)
.multiplyScalar(params.stiffness);
rbRef.current.setLinvel(
{ x: _velocity.x, y: _velocity.y, z: _velocity.z },
true,
);
rbRef.current.setAngvel({ x: 0, y: 0, z: 0 }, true);
});
return (
<InteractableObject
kind="grab"
label="Prendre"
position={SPAWN_POSITION}
rigidBodyType="dynamic"
colliders="cuboid"
rbRef={rbRef}
onPress={() => {
isHolding.current = true;
}}
onRelease={() => {
isHolding.current = false;
if (rbRef.current && params.throwBoost !== 1.0) {
const v = rbRef.current.linvel();
rbRef.current.setLinvel(
{
x: v.x * params.throwBoost,
y: v.y * params.throwBoost,
z: v.z * params.throwBoost,
},
true,
);
}
}}
>
<mesh castShadow receiveShadow>
<boxGeometry args={[CUBE_SIZE, CUBE_SIZE, CUBE_SIZE]} />
<meshStandardMaterial color="#e07b39" roughness={0.6} metalness={0.1} />
</mesh>
</InteractableObject>
);
}
-32
View File
@@ -1,32 +0,0 @@
import { AudioManager } from "@/stateManager/AudioManager";
import { InteractableObject } from "@/components/3d/InteractableObject";
const SPHERE_RADIUS = 0.4;
const SPAWN_POSITION: [number, number, number] = [3, 2, -3];
const SOUND_PATH = "/sounds/fa.mp3";
interface TriggerSphereProps {
soundPath?: string;
}
export function TriggerSphere({
soundPath = SOUND_PATH,
}: TriggerSphereProps): React.JSX.Element {
return (
<InteractableObject
kind="trigger"
label="Interagir"
position={SPAWN_POSITION}
rigidBodyType="fixed"
colliders="ball"
onPress={() => {
AudioManager.getInstance().playSound(soundPath);
}}
>
<mesh castShadow receiveShadow>
<sphereGeometry args={[SPHERE_RADIUS, 32, 32]} />
<meshStandardMaterial color="#3b82f6" roughness={0.3} metalness={0.5} />
</mesh>
</InteractableObject>
);
}
-3
View File
@@ -1,9 +1,6 @@
import { useEffect } from "react";
import { PointerLockControls } from "@react-three/drei";
export const PLAYER_EYE_HEIGHT = 1.75;
export const PLAYER_CAPSULE_RADIUS = 0.35;
export function PlayerCamera(): React.JSX.Element {
useEffect(() => {
return () => {
+7 -2
View File
@@ -1,6 +1,11 @@
import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
import type { Octree } from "three/addons/math/Octree.js";
import {
PLAYER_SPAWN_X,
PLAYER_SPAWN_Y_DEFAULT,
PLAYER_SPAWN_Z,
} from "@/data/playerConfig";
import { PlayerCamera } from "@/world/player/PlayerCamera";
import { PlayerController } from "@/world/player/PlayerController";
@@ -11,12 +16,12 @@ interface PlayerComponentProps {
export function PlayerComponent({
octree = null,
spawnY = 100,
spawnY = PLAYER_SPAWN_Y_DEFAULT,
}: PlayerComponentProps): React.JSX.Element {
const camera = useThree((state) => state.camera);
useEffect(() => {
camera.position.set(0, spawnY, 0);
camera.position.set(PLAYER_SPAWN_X, spawnY, PLAYER_SPAWN_Z);
}, [camera, spawnY]);
return (
+48 -33
View File
@@ -3,16 +3,29 @@ import { useFrame, useThree } from "@react-three/fiber";
import * as THREE from "three";
import { Capsule } from "three/addons/math/Capsule.js";
import type { Octree } from "three/addons/math/Octree.js";
import { InteractionManager } from "@/stateManager/InteractionManager";
import {
PLAYER_EYE_HEIGHT,
INTERACT_KEY,
JUMP_KEY,
MOVE_BACKWARD_KEY,
MOVE_FORWARD_KEY,
MOVE_LEFT_KEY,
MOVE_RIGHT_KEY,
PRIMARY_INTERACT_MOUSE_BUTTON,
} from "@/data/keybindings";
import {
PLAYER_ACCELERATION_MULTIPLIER,
PLAYER_AIR_CONTROL_FACTOR,
PLAYER_CAPSULE_RADIUS,
} from "@/world/player/PlayerCamera";
const WALK_SPEED = 11;
const AIR_CONTROL = 0.35;
const JUMP_SPEED = 9;
const GRAVITY = 30;
PLAYER_EYE_HEIGHT,
PLAYER_GRAVITY,
PLAYER_JUMP_SPEED,
PLAYER_MAX_DELTA,
PLAYER_SPAWN_X,
PLAYER_SPAWN_Z,
PLAYER_WALK_SPEED,
PLAYER_XZ_DAMPING_FACTOR,
} from "@/data/playerConfig";
import { InteractionManager } from "@/stateManager/InteractionManager";
type Keys = {
forward: boolean;
@@ -60,11 +73,11 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
useEffect(() => {
const spawnY = camera.position.y;
capsule.current.start.set(
0,
PLAYER_SPAWN_X,
spawnY - PLAYER_EYE_HEIGHT + PLAYER_CAPSULE_RADIUS,
0,
PLAYER_SPAWN_Z,
);
capsule.current.end.set(0, spawnY, 0);
capsule.current.end.set(PLAYER_SPAWN_X, spawnY, PLAYER_SPAWN_Z);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -73,22 +86,22 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
const handleKeyDown = (event: KeyboardEvent): void => {
switch (event.key.toLowerCase()) {
case "z":
case MOVE_FORWARD_KEY:
keys.current.forward = true;
break;
case "s":
case MOVE_BACKWARD_KEY:
keys.current.backward = true;
break;
case "q":
case MOVE_LEFT_KEY:
keys.current.left = true;
break;
case "d":
case MOVE_RIGHT_KEY:
keys.current.right = true;
break;
case " ":
case JUMP_KEY:
wantsJump.current = true;
break;
case "e":
case INTERACT_KEY:
if (interaction.getState().focused?.kind === "trigger") {
interaction.pressInteract();
}
@@ -101,19 +114,19 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
const handleKeyUp = (event: KeyboardEvent): void => {
switch (event.key.toLowerCase()) {
case "z":
case MOVE_FORWARD_KEY:
keys.current.forward = false;
break;
case "s":
case MOVE_BACKWARD_KEY:
keys.current.backward = false;
break;
case "q":
case MOVE_LEFT_KEY:
keys.current.left = false;
break;
case "d":
case MOVE_RIGHT_KEY:
keys.current.right = false;
break;
case "e":
case INTERACT_KEY:
if (interaction.getState().focused?.kind === "trigger") {
interaction.releaseInteract();
}
@@ -125,14 +138,14 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
};
const handleMouseDown = (event: MouseEvent): void => {
if (event.button !== 0) return;
if (event.button !== PRIMARY_INTERACT_MOUSE_BUTTON) return;
if (interaction.getState().focused?.kind === "grab") {
interaction.pressInteract();
}
};
const handleMouseUp = (event: MouseEvent): void => {
if (event.button !== 0) return;
if (event.button !== PRIMARY_INTERACT_MOUSE_BUTTON) return;
if (interaction.getState().holding) {
interaction.releaseInteract();
}
@@ -153,8 +166,7 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
}, []);
useFrame((_, delta) => {
// Clamp delta so physics don't explode on tab focus regain
const dt = Math.min(delta, 0.05);
const dt = Math.min(delta, PLAYER_MAX_DELTA);
// Compute wish direction from camera yaw (XZ only)
camera.getWorldDirection(_forward);
@@ -172,12 +184,15 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
if (_wishDir.lengthSq() > 0) _wishDir.normalize();
// Accelerate horizontally
const accel = onFloor.current ? WALK_SPEED : WALK_SPEED * AIR_CONTROL;
velocity.current.x += _wishDir.x * accel * dt * 9;
velocity.current.z += _wishDir.z * accel * dt * 9;
const accel = onFloor.current
? PLAYER_WALK_SPEED
: PLAYER_WALK_SPEED * PLAYER_AIR_CONTROL_FACTOR;
velocity.current.x +=
_wishDir.x * accel * dt * PLAYER_ACCELERATION_MULTIPLIER;
velocity.current.z +=
_wishDir.z * accel * dt * PLAYER_ACCELERATION_MULTIPLIER;
// Exponential damping on XZ
const damping = Math.exp(-8 * dt);
const damping = Math.exp(-PLAYER_XZ_DAMPING_FACTOR * dt);
velocity.current.x *= damping;
velocity.current.z *= damping;
@@ -185,11 +200,11 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
if (onFloor.current) {
velocity.current.y = Math.max(0, velocity.current.y);
if (wantsJump.current) {
velocity.current.y = JUMP_SPEED;
velocity.current.y = PLAYER_JUMP_SPEED;
onFloor.current = false;
}
} else {
velocity.current.y -= GRAVITY * dt;
velocity.current.y -= PLAYER_GRAVITY * dt;
}
wantsJump.current = false;