refactor: prepare main feature gameplay object and use GLB sky model
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import { TriggerObject } from "@/components/three/TriggerObject";
|
||||
import { RepairCaseModel } from "@/components/three/RepairCaseModel";
|
||||
import { AudioManager } from "@/managers/AudioManager";
|
||||
import type { Vector3Tuple } from "@/types/three";
|
||||
|
||||
interface MainFeatureObjectProps {
|
||||
position: Vector3Tuple;
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
const CASE_MODEL_PATH = "/models/packderelance/model.gltf";
|
||||
const CASE_SOUND_PATH = "/sounds/effect/fa.mp3";
|
||||
const CASE_OPEN_SOUND_RATE = 1.08;
|
||||
const CASE_CLOSE_SOUND_RATE = 0.82;
|
||||
|
||||
export function MainFeatureObject({
|
||||
position,
|
||||
open,
|
||||
onToggle,
|
||||
}: MainFeatureObjectProps): React.JSX.Element {
|
||||
return (
|
||||
<TriggerObject
|
||||
position={position}
|
||||
colliders="cuboid"
|
||||
label={open ? "Fermer la mallette" : "Ouvrir la mallette"}
|
||||
onTrigger={() => {
|
||||
AudioManager.getInstance().playSound(CASE_SOUND_PATH, 1, {
|
||||
playbackRate: open ? CASE_CLOSE_SOUND_RATE : CASE_OPEN_SOUND_RATE,
|
||||
});
|
||||
onToggle();
|
||||
}}
|
||||
>
|
||||
<RepairCaseModel
|
||||
modelPath={CASE_MODEL_PATH}
|
||||
open={open}
|
||||
position={[0, -0.45, 0]}
|
||||
scale={1.5}
|
||||
/>
|
||||
</TriggerObject>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { Text } from "@react-three/drei";
|
||||
import { TriggerObject } from "@/components/three/TriggerObject";
|
||||
import { MainFeatureObject } from "@/components/three/MainFeatureObject";
|
||||
import { ModelSelectorPlaceholder } from "@/components/three/ModelSelectorPlaceholder";
|
||||
import { RepairCaseModel } from "@/components/three/RepairCaseModel";
|
||||
|
||||
const ZONE_ORIGIN = [10, 0.4, -8] as const;
|
||||
const CASE_MODEL_PATH = "/models/packderelance/model.gltf";
|
||||
const ZONE_RADIUS = 4.2;
|
||||
|
||||
export function MainFeatureZone(): React.JSX.Element {
|
||||
@@ -44,19 +42,11 @@ export function MainFeatureZone(): React.JSX.Element {
|
||||
Pack de Relance Feature
|
||||
</Text>
|
||||
|
||||
<TriggerObject
|
||||
<MainFeatureObject
|
||||
position={[ZONE_ORIGIN[0], ZONE_ORIGIN[1], ZONE_ORIGIN[2]]}
|
||||
colliders="cuboid"
|
||||
label={caseOpen ? "Fermer la mallette" : "Ouvrir la mallette"}
|
||||
onTrigger={() => setCaseOpen((value) => !value)}
|
||||
>
|
||||
<RepairCaseModel
|
||||
modelPath={CASE_MODEL_PATH}
|
||||
open={caseOpen}
|
||||
position={[0, -0.45, 0]}
|
||||
scale={0.35}
|
||||
/>
|
||||
</TriggerObject>
|
||||
open={caseOpen}
|
||||
onToggle={() => setCaseOpen((value) => !value)}
|
||||
/>
|
||||
|
||||
<ModelSelectorPlaceholder
|
||||
label="Module A"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { useGLTF } from "@react-three/drei";
|
||||
import gsap from "gsap";
|
||||
import * as THREE from "three";
|
||||
import type { Vector3Tuple } from "@/types/three";
|
||||
|
||||
@@ -13,8 +13,9 @@ interface RepairCaseModelProps {
|
||||
}
|
||||
|
||||
const CASE_LID_NODE_NAME = "partiesup";
|
||||
const CASE_OPEN_ANGLE = THREE.MathUtils.degToRad(115);
|
||||
const CASE_OPEN_SPEED = 7;
|
||||
const CASE_OPEN_ROTATION_OFFSET_Z = 0;
|
||||
const CASE_CLOSED_ROTATION_OFFSET_Z = THREE.MathUtils.degToRad(-115);
|
||||
const CASE_ANIMATION_DURATION = 1.2;
|
||||
|
||||
export function RepairCaseModel({
|
||||
modelPath,
|
||||
@@ -26,29 +27,39 @@ export function RepairCaseModel({
|
||||
const { scene } = useGLTF(modelPath);
|
||||
const model = useMemo(() => scene.clone(true), [scene]);
|
||||
const lidRef = useRef<THREE.Object3D | null>(null);
|
||||
const closedRotationX = useRef(0);
|
||||
const initialOpen = useRef(open);
|
||||
const openedRotationZ = useRef(0);
|
||||
const parsedScale =
|
||||
typeof scale === "number" ? ([scale, scale, scale] as Vector3Tuple) : scale;
|
||||
|
||||
useEffect(() => {
|
||||
const lid = model.getObjectByName(CASE_LID_NODE_NAME);
|
||||
lidRef.current = lid ?? null;
|
||||
closedRotationX.current = lid?.rotation.x ?? 0;
|
||||
openedRotationZ.current = lid?.rotation.z ?? 0;
|
||||
|
||||
if (lid && !initialOpen.current) {
|
||||
lid.rotation.z = openedRotationZ.current + CASE_CLOSED_ROTATION_OFFSET_Z;
|
||||
}
|
||||
}, [model]);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
useEffect(() => {
|
||||
const lid = lidRef.current;
|
||||
if (!lid) return;
|
||||
|
||||
const targetRotation =
|
||||
closedRotationX.current - (open ? CASE_OPEN_ANGLE : 0);
|
||||
lid.rotation.x = THREE.MathUtils.damp(
|
||||
lid.rotation.x,
|
||||
targetRotation,
|
||||
CASE_OPEN_SPEED,
|
||||
delta,
|
||||
);
|
||||
});
|
||||
openedRotationZ.current +
|
||||
(open ? CASE_OPEN_ROTATION_OFFSET_Z : CASE_CLOSED_ROTATION_OFFSET_Z);
|
||||
gsap.to(lid.rotation, {
|
||||
z: targetRotation,
|
||||
duration: CASE_ANIMATION_DURATION,
|
||||
ease: "power2.inOut",
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
gsap.killTweensOf(lid.rotation);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<group position={position} rotation={rotation} scale={parsedScale}>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { useGLTF } from "@react-three/drei";
|
||||
import { useMemo, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
|
||||
interface SkyModelProps {
|
||||
modelPath: string;
|
||||
}
|
||||
|
||||
const SKY_MODEL_SCALE = 1;
|
||||
|
||||
export function SkyModel({ modelPath }: SkyModelProps): React.JSX.Element {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const { scene } = useGLTF(modelPath);
|
||||
const model = useMemo(() => scene.clone(true), [scene]);
|
||||
|
||||
useFrame(() => {
|
||||
groupRef.current?.position.copy(camera.position);
|
||||
});
|
||||
|
||||
return (
|
||||
<group ref={groupRef} scale={SKY_MODEL_SCALE} frustumCulled={false}>
|
||||
<primitive object={model} />
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
useGLTF.preload("/models/sky/model.glb");
|
||||
@@ -6,6 +6,7 @@ export type { SimpleModelConfig } from "./SimpleModel";
|
||||
|
||||
export { ExplodableModel } from "./ExplodableModel";
|
||||
export { MainFeatureZone } from "./MainFeatureZone";
|
||||
export { MainFeatureObject } from "./MainFeatureObject";
|
||||
export { ModelSelectorPlaceholder } from "./ModelSelectorPlaceholder";
|
||||
export { RepairCaseModel } from "./RepairCaseModel";
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
HandTrackingContext,
|
||||
} from "@/hooks/useHandTrackingSnapshot";
|
||||
import { useRemoteHandTracking } from "@/hooks/useRemoteHandTracking";
|
||||
import { isDebugEnabled } from "@/utils/debug/isDebugEnabled";
|
||||
|
||||
export function HandTrackingProvider({
|
||||
children,
|
||||
@@ -15,10 +14,7 @@ export function HandTrackingProvider({
|
||||
}): React.JSX.Element {
|
||||
const sceneMode = useSceneMode();
|
||||
const { nearby, holding, handHolding } = useInteraction();
|
||||
const enabled =
|
||||
isDebugEnabled() &&
|
||||
sceneMode === "physics" &&
|
||||
(nearby || holding || handHolding);
|
||||
const enabled = sceneMode === "physics" && (nearby || holding || handHolding);
|
||||
const snapshot = useRemoteHandTracking({ enabled });
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user