import { useEffect, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import * as THREE from "three"; import { isDebugEnabled } from "@/utils/debug/isDebugEnabled"; import type { ZoneConfig } from "@/types/gameplay/zone"; interface ZoneDetectionProps { zone: ZoneConfig; onEnter: () => void; height?: number; } const _cameraPos = new THREE.Vector3(); function ZoneDebugVisual({ zone, active, }: { zone: ZoneConfig; active: boolean; }): React.JSX.Element | null { if (!isDebugEnabled()) return null; return ( ); } export function ZoneDetection({ zone, onEnter, height, }: ZoneDetectionProps): React.JSX.Element { const camera = useThree((state) => state.camera); const hasTriggeredRef = useRef(false); const onEnterRef = useRef(onEnter); const [isActive, setIsActive] = useState(false); useEffect(() => { onEnterRef.current = onEnter; }, [onEnter]); useFrame(() => { if (hasTriggeredRef.current) return; camera.getWorldPosition(_cameraPos); const dx = _cameraPos.x - zone.position[0]; const dz = _cameraPos.z - zone.position[2]; const horizontalDist = Math.sqrt(dx * dx + dz * dz); if (horizontalDist > zone.radius) return; const zoneHeight = height ?? zone.height; if (_cameraPos.y < zone.position[1] - zoneHeight / 2) return; if (_cameraPos.y > zone.position[1] + zoneHeight / 2) return; hasTriggeredRef.current = true; setIsActive(true); onEnterRef.current(); }); return ; }