From 8b3f24b90bfc421204d8b038fe874901f851c040 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Wed, 29 Apr 2026 23:30:22 +0200 Subject: [PATCH] feat: add openable repair case model --- src/components/three/RepairCaseModel.tsx | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/components/three/RepairCaseModel.tsx diff --git a/src/components/three/RepairCaseModel.tsx b/src/components/three/RepairCaseModel.tsx new file mode 100644 index 0000000..4b9371c --- /dev/null +++ b/src/components/three/RepairCaseModel.tsx @@ -0,0 +1,58 @@ +import { useEffect, useMemo, useRef } from "react"; +import { useFrame } from "@react-three/fiber"; +import { useGLTF } from "@react-three/drei"; +import * as THREE from "three"; +import type { Vector3Tuple } from "@/types/three"; + +interface RepairCaseModelProps { + modelPath: string; + open: boolean; + position?: Vector3Tuple; + rotation?: Vector3Tuple; + scale?: number | Vector3Tuple; +} + +const CASE_LID_NODE_NAME = "partiesup"; +const CASE_OPEN_ANGLE = THREE.MathUtils.degToRad(115); +const CASE_OPEN_SPEED = 7; + +export function RepairCaseModel({ + modelPath, + open, + position = [0, 0, 0], + rotation = [0, 0, 0], + scale = 1, +}: RepairCaseModelProps): React.JSX.Element { + const { scene } = useGLTF(modelPath); + const model = useMemo(() => scene.clone(true), [scene]); + const lidRef = useRef(null); + const closedRotationX = 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; + }, [model]); + + useFrame((_, delta) => { + 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, + ); + }); + + return ( + + + + ); +}