update : put every constante in the data folder
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user