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
+139
View File
@@ -0,0 +1,139 @@
import { useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { RigidBody } from "@react-three/rapier";
import type { RapierRigidBody } from "@react-three/rapier";
import * as THREE from "three";
import { InteractableObject } from "@/components/3d/InteractableObject";
import {
GRAB_DEFAULT_COLLIDERS,
GRAB_DEFAULT_LABEL,
GRAB_HOLD_DISTANCE_DEFAULT,
GRAB_HOLD_DISTANCE_MAX,
GRAB_HOLD_DISTANCE_MIN,
GRAB_HOLD_DISTANCE_STEP,
GRAB_STIFFNESS_DEFAULT,
GRAB_STIFFNESS_MAX,
GRAB_STIFFNESS_MIN,
GRAB_STIFFNESS_STEP,
GRAB_THROW_BOOST_DEFAULT,
GRAB_THROW_BOOST_MAX,
GRAB_THROW_BOOST_MIN,
GRAB_THROW_BOOST_STEP,
} from "@/data/grabConfig";
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
interface GrabbableObjectProps {
position: [number, number, number];
children: React.ReactNode;
colliders?: "cuboid" | "ball" | "hull";
label?: string;
}
// Shared mutable params — one debug folder controls all instances.
const params = {
stiffness: GRAB_STIFFNESS_DEFAULT,
throwBoost: GRAB_THROW_BOOST_DEFAULT,
holdDistance: GRAB_HOLD_DISTANCE_DEFAULT,
};
const ZERO_ANGULAR_VELOCITY = { x: 0, y: 0, z: 0 };
const _holdTarget = new THREE.Vector3();
const _currentPos = new THREE.Vector3();
const _velocity = new THREE.Vector3();
export function GrabbableObject({
position,
children,
colliders = GRAB_DEFAULT_COLLIDERS,
label = GRAB_DEFAULT_LABEL,
}: GrabbableObjectProps): React.JSX.Element {
const camera = useThree((state) => state.camera);
const rbRef = useRef<RapierRigidBody>(null);
const isHolding = useRef(false);
useDebugFolder("GrabbableObject", (folder) => {
folder
.add(
params,
"stiffness",
GRAB_STIFFNESS_MIN,
GRAB_STIFFNESS_MAX,
GRAB_STIFFNESS_STEP,
)
.name("Hold stiffness");
folder
.add(
params,
"throwBoost",
GRAB_THROW_BOOST_MIN,
GRAB_THROW_BOOST_MAX,
GRAB_THROW_BOOST_STEP,
)
.name("Throw boost");
folder
.add(
params,
"holdDistance",
GRAB_HOLD_DISTANCE_MIN,
GRAB_HOLD_DISTANCE_MAX,
GRAB_HOLD_DISTANCE_STEP,
)
.name("Hold distance");
});
useFrame(() => {
if (!isHolding.current || !rbRef.current) return;
camera.getWorldDirection(_holdTarget);
_holdTarget.multiplyScalar(params.holdDistance).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(ZERO_ANGULAR_VELOCITY, true);
});
return (
<RigidBody
ref={rbRef}
type="dynamic"
colliders={colliders}
position={position}
>
<InteractableObject
kind="grab"
label={label}
position={position}
bodyRef={rbRef}
onPress={() => {
isHolding.current = true;
}}
onRelease={() => {
isHolding.current = false;
if (!rbRef.current || params.throwBoost === GRAB_THROW_BOOST_DEFAULT)
return;
const v = rbRef.current.linvel();
rbRef.current.setLinvel(
{
x: v.x * params.throwBoost,
y: v.y * params.throwBoost,
z: v.z * params.throwBoost,
},
true,
);
}}
>
{children}
</InteractableObject>
</RigidBody>
);
}
+27 -32
View File
@@ -1,9 +1,13 @@
import { useEffect, useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { RigidBody } from "@react-three/rapier";
import type { RapierRigidBody } from "@react-three/rapier";
import * as THREE from "three";
import type { RefObject } from "react";
import {
INTERACTION_DEBUG_SPHERE_COLOR,
INTERACTION_DEBUG_SPHERE_OPACITY,
INTERACTION_DEBUG_SPHERE_SEGMENTS,
} from "@/data/debugConfig";
import { Debug } from "@/utils/debug/Debug";
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
import {
@@ -17,9 +21,7 @@ interface InteractableObjectProps {
kind: InteractableKind;
label: string;
position: [number, number, number];
rigidBodyType?: "dynamic" | "fixed";
colliders?: "cuboid" | "ball" | "hull";
rbRef?: RefObject<RapierRigidBody | null>;
bodyRef?: RefObject<RapierRigidBody | null>;
onPress: () => void;
onRelease?: () => void;
children: React.ReactNode;
@@ -34,16 +36,12 @@ export function InteractableObject({
kind,
label,
position,
rigidBodyType = "dynamic",
colliders = "cuboid",
rbRef,
bodyRef,
onPress,
onRelease = () => {},
children,
}: InteractableObjectProps): React.JSX.Element {
const camera = useThree((state) => state.camera);
const internalRef = useRef<RapierRigidBody>(null);
const bodyRef = rbRef ?? internalRef;
const groupRef = useRef<THREE.Group>(null);
const debugSphereRef = useRef<THREE.Mesh>(null);
@@ -67,7 +65,6 @@ export function InteractableObject({
});
useFrame(() => {
const body = bodyRef.current;
const group = groupRef.current;
const debug = Debug.getInstance();
const manager = InteractionManager.getInstance();
@@ -77,8 +74,8 @@ export function InteractableObject({
debug.active && debug.getShowInteractionSpheres();
}
if (body) {
const t = body.translation();
if (bodyRef?.current) {
const t = bodyRef.current.translation();
_objectPos.set(t.x, t.y, t.z);
} else {
_objectPos.set(...position);
@@ -99,7 +96,6 @@ export function InteractableObject({
_raycaster.far = INTERACTION_RADIUS;
const hits = group ? _raycaster.intersectObject(group, true) : [];
const validHit = hits.find((h) => h.object !== debugSphereRef.current);
if (validHit) {
@@ -110,24 +106,23 @@ export function InteractableObject({
});
return (
<RigidBody
ref={bodyRef}
type={rigidBodyType}
colliders={colliders}
position={position}
>
<group ref={groupRef}>
{children}
<mesh ref={debugSphereRef} visible={false}>
<sphereGeometry args={[INTERACTION_RADIUS, 16, 16]} />
<meshBasicMaterial
color="#facc15"
wireframe
transparent
opacity={0.25}
/>
</mesh>
</group>
</RigidBody>
<group ref={groupRef}>
{children}
<mesh ref={debugSphereRef} visible={false}>
<sphereGeometry
args={[
INTERACTION_RADIUS,
INTERACTION_DEBUG_SPHERE_SEGMENTS,
INTERACTION_DEBUG_SPHERE_SEGMENTS,
]}
/>
<meshBasicMaterial
color={INTERACTION_DEBUG_SPHERE_COLOR}
wireframe
transparent
opacity={INTERACTION_DEBUG_SPHERE_OPACITY}
/>
</mesh>
</group>
);
}
+94
View File
@@ -0,0 +1,94 @@
import { useRef, useState } from "react";
import { useGLTF } from "@react-three/drei";
import { RigidBody } from "@react-three/rapier";
import { InteractableObject } from "@/components/3d/InteractableObject";
import {
TRIGGER_DEFAULT_COLLIDERS,
TRIGGER_DEFAULT_LABEL,
TRIGGER_DEFAULT_SOUND_VOLUME,
TRIGGER_DEFAULT_SPAWN_OFFSET,
} from "@/data/triggerConfig";
import { AudioManager } from "@/stateManager/AudioManager";
interface SpawnedModel {
id: number;
position: [number, number, number];
}
interface TriggerObjectProps {
position: [number, number, number];
children: React.ReactNode;
colliders?: "cuboid" | "ball" | "hull";
label?: string;
soundPath?: string;
soundVolume?: number;
spawnModel?: string;
spawnOffset?: [number, number, number];
}
let _spawnCounter = 0;
function SpawnedModelInstance({
path,
position,
}: {
path: string;
position: [number, number, number];
}): React.JSX.Element {
const { scene } = useGLTF(path);
return <primitive object={scene.clone()} position={position} />;
}
export function TriggerObject({
position,
children,
colliders = TRIGGER_DEFAULT_COLLIDERS,
label = TRIGGER_DEFAULT_LABEL,
soundPath,
soundVolume = TRIGGER_DEFAULT_SOUND_VOLUME,
spawnModel,
spawnOffset = TRIGGER_DEFAULT_SPAWN_OFFSET,
}: TriggerObjectProps): React.JSX.Element {
const [spawned, setSpawned] = useState<SpawnedModel[]>([]);
const positionRef = useRef(position);
return (
<>
<RigidBody type="fixed" colliders={colliders} position={position}>
<InteractableObject
kind="trigger"
label={label}
position={position}
onPress={() => {
if (soundPath) {
AudioManager.getInstance().playSound(soundPath, soundVolume);
}
if (spawnModel) {
const spawnPos: [number, number, number] = [
positionRef.current[0] + spawnOffset[0],
positionRef.current[1] + spawnOffset[1],
positionRef.current[2] + spawnOffset[2],
];
setSpawned((prev) => [
...prev,
{ id: ++_spawnCounter, position: spawnPos },
]);
}
}}
>
{children}
</InteractableObject>
</RigidBody>
{spawnModel &&
spawned.map((s) => (
<SpawnedModelInstance
key={s.id}
path={spawnModel}
position={s.position}
/>
))}
</>
);
}