import { useRef } from "react"; import * as THREE from "three"; import { useFrame } 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 { animateCameraTransition, 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, 6, 0], rotation: [-10, -90, 0], }; const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = { position: [3, 1.5, 0], 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); useFrame(() => { if (groupRef.current) { if (movementMode === "ebike") { // Follow player physical position (capsule end) const playerPos = (window as any).playerPos || [0, 10, 0]; groupRef.current.position.set(playerPos[0], playerPos[1] - PLAYER_EYE_HEIGHT, playerPos[2]); // Match the e-bike's actual physical steering heading const angle = (window as any).ebikeAngle || 0; groupRef.current.rotation.set(0, angle, 0); } else { // Reset to original position groupRef.current.position.set(...position); groupRef.current.rotation.set(0, 0, 0); } } }); const camPointPos: Vector3Tuple = [ position[0] + EBIKE_CAMERA_TRANSFORM.position[0], position[1] + EBIKE_CAMERA_TRANSFORM.position[1], position[2] + EBIKE_CAMERA_TRANSFORM.position[2], ]; const dropPointPos: Vector3Tuple = [ position[0] + EBIKE_DROP_PLAYER_TRANSFORM.position[0], position[1] + EBIKE_DROP_PLAYER_TRANSFORM.position[1], position[2] + EBIKE_DROP_PLAYER_TRANSFORM.position[2], ]; const handleInteract = (): void => { if (movementMode === "walk") { const targetCamPos: Vector3Tuple = [ position[0] + EBIKE_CAMERA_TRANSFORM.position[0], position[1] + EBIKE_CAMERA_TRANSFORM.position[1], position[2] + EBIKE_CAMERA_TRANSFORM.position[2], ]; animateCameraTransformTransition(targetCamPos, EBIKE_CAMERA_TRANSFORM.rotation, 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], ]; const targetLookAt: Vector3Tuple = [ currentPos.x, currentPos.y + 1, currentPos.z, ]; animateCameraTransition(targetCamPos, targetLookAt, 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 && ( <> )} ); }