import { useEffect, useRef } from "react"; import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { InteractableObject } from "@/components/three/interaction/InteractableObject"; import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; import { useClonedObject } from "@/hooks/three/useClonedObject"; import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; import { animateCameraTransformTransition } from "@/world/GameCinematics"; import { useGameStore } from "@/managers/stores/useGameStore"; import { PLAYER_EYE_HEIGHT } from "@/data/player/playerConfig"; import type { Vector3Tuple } from "@/types/three/three"; const EBIKE_MODEL_PATH = "/models/ebike/model.gltf"; export interface CameraTransform { position: Vector3Tuple; rotation: Vector3Tuple; } export const EBIKE_CAMERA_TRANSFORM: CameraTransform = { position: [-3.5, 6, 0], rotation: [-10, -90, 0], }; const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = { position: [0, 1.5, 3], rotation: [0, 0, 0], }; interface EbikeProps { position: Vector3Tuple; } export function Ebike({ position }: EbikeProps): React.JSX.Element { const groupRef = useRef(null); const { scene } = useLoggedGLTF(EBIKE_MODEL_PATH, { scope: "Ebike", position: position, }); const model = useClonedObject(scene); const movementMode = useGameStore((state) => state.player.movementMode); const camera = useThree((state) => state.camera); const restingPosition = useRef([ position[0], position[1] - PLAYER_EYE_HEIGHT, position[2], ]); const restingRotation = useRef(0); useEffect(() => { (window as any).ebikeVisualGroup = groupRef; (window as any).ebikeParkedPosition = restingPosition.current; (window as any).ebikeParkedRotation = restingRotation.current; return () => { (window as any).ebikeVisualGroup = null; (window as any).ebikeParkedPosition = null; (window as any).ebikeParkedRotation = null; }; }, []); useFrame(() => { if (groupRef.current) { if (movementMode === "ebike") { restingPosition.current = [ groupRef.current.position.x, groupRef.current.position.y, groupRef.current.position.z, ]; restingRotation.current = groupRef.current.rotation.y; } else { groupRef.current.position.set(...restingPosition.current); groupRef.current.rotation.set(0, restingRotation.current, 0); } (window as any).ebikeParkedPosition = restingPosition.current; (window as any).ebikeParkedRotation = restingRotation.current; } }); const camPointPos: Vector3Tuple = [ restingPosition.current[0] + EBIKE_CAMERA_TRANSFORM.position[0], restingPosition.current[1] + EBIKE_CAMERA_TRANSFORM.position[1], restingPosition.current[2] + EBIKE_CAMERA_TRANSFORM.position[2], ]; const dropPointPos: Vector3Tuple = [ restingPosition.current[0] + EBIKE_DROP_PLAYER_TRANSFORM.position[0], restingPosition.current[1] + EBIKE_DROP_PLAYER_TRANSFORM.position[1], restingPosition.current[2] + EBIKE_DROP_PLAYER_TRANSFORM.position[2], ]; const handleInteract = (): void => { if (movementMode === "walk") { const cameraOffset = new THREE.Vector3(...EBIKE_CAMERA_TRANSFORM.position); cameraOffset.applyAxisAngle(new THREE.Vector3(0, 1, 0), restingRotation.current); const targetCamPos: Vector3Tuple = [ restingPosition.current[0] + cameraOffset.x, restingPosition.current[1] + cameraOffset.y, restingPosition.current[2] + cameraOffset.z, ]; const targetRotation: Vector3Tuple = [ EBIKE_CAMERA_TRANSFORM.rotation[0], EBIKE_CAMERA_TRANSFORM.rotation[1] + THREE.MathUtils.radToDeg(restingRotation.current), EBIKE_CAMERA_TRANSFORM.rotation[2], ]; animateCameraTransformTransition(targetCamPos, targetRotation, 1, () => { useGameStore.getState().setPlayerMovementMode("ebike"); }); } else { const currentPos = new THREE.Vector3(); if (groupRef.current) { groupRef.current.getWorldPosition(currentPos); } else { currentPos.set(...position); } const targetCamPos: Vector3Tuple = [ currentPos.x + EBIKE_DROP_PLAYER_TRANSFORM.position[0], currentPos.y + EBIKE_DROP_PLAYER_TRANSFORM.position[1], currentPos.z + EBIKE_DROP_PLAYER_TRANSFORM.position[2], ]; // Get camera's current rotation in degrees so we keep the exact orientation during dismount const currentEuler = new THREE.Euler().setFromQuaternion(camera.quaternion, "YXZ"); const targetRotation: Vector3Tuple = [ THREE.MathUtils.radToDeg(currentEuler.x), THREE.MathUtils.radToDeg(currentEuler.y), THREE.MathUtils.radToDeg(currentEuler.z), ]; animateCameraTransformTransition(targetCamPos, targetRotation, 1, () => { useGameStore.getState().setPlayerMovementMode("walk"); }); } }; const handleInteractRef = useRef(handleInteract); handleInteractRef.current = handleInteract; const debugRef = useRef({ showCameraPoints: true }); const debugActions = useRef({ toggleRide: () => { handleInteractRef.current(); } }); useDebugFolder("Ebike", (folder) => { folder .add(debugRef.current, "showCameraPoints") .name("Show Camera Points") .onChange((value: boolean) => { debugRef.current.showCameraPoints = value; }); folder .add(debugActions.current, "toggleRide") .name("Monter / Descendre"); }); return ( <> {debugRef.current.showCameraPoints && ( <> )} ); }