import { useEffect, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import * as THREE from "three"; import { ZONES } from "@/data/zones"; import { GameStepManager } from "@/stateManager/GameStepManager"; import { Debug } from "@/utils/debug/Debug"; const _playerPos = new THREE.Vector3(); const _zonePos = new THREE.Vector3(); export function ZoneDetection(): null { const camera = useThree((state) => state.camera); const manager = GameStepManager.getInstance(); const triggeredZones = useRef>(new Set()); const debug = Debug.getInstance(); useEffect(() => { if (!debug.active) return; const folder = debug.createFolder("Game"); if (!folder) return; const gameState = { step: manager.getStep() }; const playerPos = { x: 0, y: 0, z: 0 }; folder .add(gameState, "step", ["intro", "bike"]) .name("Game Step") .disable(); folder.add(playerPos, "x").name("Player X").listen().disable(); folder.add(playerPos, "y").name("Player Y").listen().disable(); folder.add(playerPos, "z").name("Player Z").listen().disable(); const unsubManager = manager.subscribe(() => { gameState.step = manager.getStep(); folder.controllersRecursive().forEach((c) => c.updateDisplay()); }); let frameId: number; const updatePlayerPos = (): void => { camera.getWorldPosition(_playerPos); playerPos.x = Math.round(_playerPos.x * 100) / 100; playerPos.y = Math.round(_playerPos.y * 100) / 100; playerPos.z = Math.round(_playerPos.z * 100) / 100; folder.controllersRecursive().forEach((c) => c.updateDisplay()); frameId = requestAnimationFrame(updatePlayerPos); }; updatePlayerPos(); return () => { cancelAnimationFrame(frameId); debug.destroyFolder("Game"); unsubManager(); }; }, [debug, manager, camera]); useFrame(() => { camera.getWorldPosition(_playerPos); for (const zone of ZONES) { if (triggeredZones.current.has(zone.id)) continue; _zonePos.set(...zone.position); const distanceSq = _playerPos.distanceToSquared(_zonePos); if (distanceSq <= zone.radius * zone.radius) { manager.transitionTo(zone.targetStep); triggeredZones.current.add(zone.id); break; } } }); return null; } interface ZoneVisualProps { position: [number, number, number]; radius: number; height: number; triggered: boolean; } function ZoneVisual({ position, radius, height, triggered, }: ZoneVisualProps): React.JSX.Element { const color = triggered ? "#00ff00" : "#ff0000"; return ( ); } export function ZoneDebugVisuals(): React.JSX.Element | null { const debug = Debug.getInstance(); const camera = useThree((state) => state.camera); const [triggeredZones, setTriggeredZones] = useState>(new Set()); useFrame(() => { camera.getWorldPosition(_playerPos); for (const zone of ZONES) { if (triggeredZones.has(zone.id)) continue; _zonePos.set(...zone.position); const distanceSq = _playerPos.distanceToSquared(_zonePos); if (distanceSq <= zone.radius * zone.radius) { setTriggeredZones((prev) => new Set(prev).add(zone.id)); break; } } }); if (!debug.active) return null; return ( <> {ZONES.map((zone) => ( ))} ); }