import * as THREE from 'three'; import { useCallback } from 'react'; import { useAisleStore } from '../../../../store/builder/useAisleStore'; import { useWallStore } from '../../../../store/builder/useWallStore'; const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters const CAN_POINT_SNAP = true; // Whether snapping is enabled or not const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping in meters const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not export const usePointSnapping = (currentPoint: { uuid: string, pointType: string, position: [number, number, number] } | null) => { const { aisles, getConnectedPoints: getConnectedAislePoints } = useAisleStore(); const { walls, getConnectedPoints: getConnectedWallPoints } = useWallStore(); // Wall Snapping const getAllOtherWallPoints = useCallback(() => { if (!currentPoint) return []; return walls.flatMap(wall => wall.points.filter(point => point.pointUuid !== currentPoint.uuid) ); }, [walls, currentPoint]); const snapWallPoint = useCallback((position: [number, number, number]) => { if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; const otherPoints = getAllOtherWallPoints(); const currentVec = new THREE.Vector3(...position); for (const point of otherPoints) { const pointVec = new THREE.Vector3(...point.position); const distance = currentVec.distanceTo(pointVec); if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Wall') { return { position: point.position, isSnapped: true, snappedPoint: point }; } } return { position: position, isSnapped: false, snappedPoint: null }; }, [currentPoint, getAllOtherWallPoints]); const snapWallAngle = useCallback((newPosition: [number, number, number]): { position: [number, number, number], isSnapped: boolean, snapSources: THREE.Vector3[] } => { if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; const connectedPoints = getConnectedWallPoints(currentPoint.uuid); if (connectedPoints.length === 0) { return { position: newPosition, isSnapped: false, snapSources: [] }; } const newPos = new THREE.Vector3(...newPosition); let closestX: { pos: THREE.Vector3, dist: number } | null = null; let closestZ: { pos: THREE.Vector3, dist: number } | null = null; for (const connectedPoint of connectedPoints) { const cPos = new THREE.Vector3(...connectedPoint.position); const xDist = Math.abs(newPos.x - cPos.x); const zDist = Math.abs(newPos.z - cPos.z); if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) { if (!closestX || xDist < closestX.dist) { closestX = { pos: cPos, dist: xDist }; } } if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) { if (!closestZ || zDist < closestZ.dist) { closestZ = { pos: cPos, dist: zDist }; } } } const snappedPos = newPos.clone(); const snapSources: THREE.Vector3[] = []; if (closestX) { snappedPos.x = closestX.pos.x; snapSources.push(closestX.pos.clone()); } if (closestZ) { snappedPos.z = closestZ.pos.z; snapSources.push(closestZ.pos.clone()); } const isSnapped = snapSources.length > 0; return { position: [snappedPos.x, snappedPos.y, snappedPos.z], isSnapped, snapSources }; }, [currentPoint, getConnectedAislePoints]); // Aisle Snapping const getAllOtherAislePoints = useCallback(() => { if (!currentPoint) return []; return aisles.flatMap(aisle => aisle.points.filter(point => point.pointUuid !== currentPoint.uuid) ); }, [aisles, currentPoint]); const snapAislePoint = useCallback((position: [number, number, number]) => { if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null }; const otherPoints = getAllOtherAislePoints(); const currentVec = new THREE.Vector3(...position); for (const point of otherPoints) { const pointVec = new THREE.Vector3(...point.position); const distance = currentVec.distanceTo(pointVec); if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Aisle') { return { position: point.position, isSnapped: true, snappedPoint: point }; } } return { position: position, isSnapped: false, snappedPoint: null }; }, [currentPoint, getAllOtherAislePoints]); const snapAisleAngle = useCallback((newPosition: [number, number, number]): { position: [number, number, number], isSnapped: boolean, snapSources: THREE.Vector3[] } => { if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] }; const connectedPoints = getConnectedAislePoints(currentPoint.uuid); if (connectedPoints.length === 0) { return { position: newPosition, isSnapped: false, snapSources: [] }; } const newPos = new THREE.Vector3(...newPosition); let closestX: { pos: THREE.Vector3, dist: number } | null = null; let closestZ: { pos: THREE.Vector3, dist: number } | null = null; for (const connectedPoint of connectedPoints) { const cPos = new THREE.Vector3(...connectedPoint.position); const xDist = Math.abs(newPos.x - cPos.x); const zDist = Math.abs(newPos.z - cPos.z); if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) { if (!closestX || xDist < closestX.dist) { closestX = { pos: cPos, dist: xDist }; } } if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) { if (!closestZ || zDist < closestZ.dist) { closestZ = { pos: cPos, dist: zDist }; } } } const snappedPos = newPos.clone(); const snapSources: THREE.Vector3[] = []; if (closestX) { snappedPos.x = closestX.pos.x; snapSources.push(closestX.pos.clone()); } if (closestZ) { snappedPos.z = closestZ.pos.z; snapSources.push(closestZ.pos.clone()); } const isSnapped = snapSources.length > 0; return { position: [snappedPos.x, snappedPos.y, snappedPos.z], isSnapped, snapSources }; }, [currentPoint, getConnectedAislePoints]); return { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, }; };