import * as THREE from 'three'; import * as Constants from '../../../types/world/worldConstants'; import { useRef, useState, useEffect, useMemo } from 'react'; import { useSocketStore, useToolMode } from '../../../store/builder/store'; import { DragControls } from '@react-three/drei'; import { useThree } from '@react-three/fiber'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { useSelectedPoints } from '../../../store/simulation/useSimulationStore'; import { usePointSnapping } from './helpers/usePointSnapping'; import { useParams } from 'react-router-dom'; import { useVersionContext } from '../version/versionContext'; import { useSceneContext } from '../../scene/sceneContext'; // import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi'; // import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi'; // import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi'; // import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi'; // import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi'; // import { deleteFloorApi } from '../../../services/factoryBuilder/floor/deleteFloorApi'; // import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi'; // import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi'; import { getUserData } from '../../../functions/getUserData'; import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors'; function Point({ point }: { readonly point: Point }) { const materialRef = useRef(null); const { raycaster, camera, pointer } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const [isHovered, setIsHovered] = useState(false); const [isSelected, setIsSelected] = useState(false); const [dragOffset, setDragOffset] = useState(null); const { socket } = useSocketStore(); const { toolMode } = useToolMode(); const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext(); const { push2D } = undoRedo2DStore(); const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore(); const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore(); const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore(); const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore(); const { snapAislePoint, snapAisleAngle, snapWallPoint, snapWallAngle, snapFloorPoint, snapFloorAngle, snapZonePoint, snapZoneAngle } = usePointSnapping({ uuid: point.pointUuid, pointType: point.pointType, position: point.position }); const { hoveredPoint, hoveredLine, setHoveredPoint } = useBuilderStore(); const { selectedPoints } = useSelectedPoints(); const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const boxScale: [number, number, number] = Constants.pointConfig.boxScale; const colors = getColor(point); const [initialPositions, setInitialPositions] = useState<{ aisles?: Aisle[], walls?: Wall[], floors?: Floor[], zones?: Zone[] }>({}); useEffect(() => { handleCanvasCursors('default'); }, [toolMode]) function getColor(point: Point) { if (point.pointType === 'Aisle') { return { defaultInnerColor: Constants.pointConfig.defaultInnerColor, defaultOuterColor: Constants.pointConfig.aisleOuterColor, defaultDeleteColor: Constants.pointConfig.deleteColor, } } else if (point.pointType === 'Floor') { return { defaultInnerColor: Constants.pointConfig.defaultInnerColor, defaultOuterColor: Constants.pointConfig.floorOuterColor, defaultDeleteColor: Constants.pointConfig.deleteColor, } } else if (point.pointType === 'Wall') { return { defaultInnerColor: Constants.pointConfig.defaultInnerColor, defaultOuterColor: Constants.pointConfig.wallOuterColor, defaultDeleteColor: Constants.pointConfig.deleteColor, } } else if (point.pointType === 'Zone') { return { defaultInnerColor: Constants.pointConfig.defaultInnerColor, defaultOuterColor: Constants.pointConfig.zoneOuterColor, defaultDeleteColor: Constants.pointConfig.deleteColor, } } else { return { defaultInnerColor: Constants.pointConfig.defaultInnerColor, defaultOuterColor: Constants.pointConfig.defaultOuterColor, defaultDeleteColor: Constants.pointConfig.deleteColor, } } } useEffect(() => { if (materialRef.current && (toolMode === 'move' || toolMode === '2D-Delete')) { let innerColor; let outerColor; if (isHovered) { innerColor = toolMode === '2D-Delete' ? colors.defaultDeleteColor : colors.defaultOuterColor; outerColor = toolMode === '2D-Delete' ? colors.defaultDeleteColor : colors.defaultOuterColor; } else { innerColor = colors.defaultInnerColor; outerColor = colors.defaultOuterColor; } materialRef.current.uniforms.uInnerColor.value.set(innerColor); materialRef.current.uniforms.uOuterColor.value.set(outerColor); materialRef.current.uniformsNeedUpdate = true; } else if (materialRef.current && toolMode !== 'move') { materialRef.current.uniforms.uInnerColor.value.set(colors.defaultInnerColor); materialRef.current.uniforms.uOuterColor.value.set(colors.defaultOuterColor); materialRef.current.uniformsNeedUpdate = true; } }, [isHovered, colors.defaultInnerColor, colors.defaultOuterColor, colors.defaultDeleteColor, toolMode]); const uniforms = useMemo(() => ({ uOuterColor: { value: new THREE.Color(colors.defaultOuterColor) }, uInnerColor: { value: new THREE.Color(colors.defaultInnerColor) }, }), [colors.defaultInnerColor, colors.defaultOuterColor]); const handleDrag = (point: Point) => { if (toolMode === 'move' && isHovered && dragOffset) { raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit) { handleCanvasCursors('grabbing'); const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset); const newPosition: [number, number, number] = [positionWithOffset.x, positionWithOffset.y, positionWithOffset.z]; if (point.pointType === 'Aisle') { const aisleSnapped = snapAisleAngle(newPosition); const finalSnapped = snapAislePoint(aisleSnapped.position); setAislePosition(point.pointUuid, finalSnapped.position); } else if (point.pointType === 'Wall') { const wallSnapped = snapWallAngle(newPosition); const finalSnapped = snapWallPoint(wallSnapped.position); setWallPosition(point.pointUuid, finalSnapped.position); } else if (point.pointType === 'Floor') { const floorSnapped = snapFloorAngle(newPosition); const finalSnapped = snapFloorPoint(floorSnapped.position); setFloorPosition(point.pointUuid, finalSnapped.position); } else if (point.pointType === 'Zone') { const zoneSnapped = snapZoneAngle(newPosition); const finalSnapped = snapZonePoint(zoneSnapped.position); setZonePosition(point.pointUuid, finalSnapped.position); } } } }; const handleDragStart = (point: Point) => { raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit) { const currentPosition = new THREE.Vector3(...point.position); const offset = new THREE.Vector3().subVectors(currentPosition, hit); setDragOffset(offset); if (point.pointType === 'Aisle') { const aisles = getAislesByPointId(point.pointUuid); setInitialPositions({ aisles }); } else if (point.pointType === 'Wall') { const walls = getWallsByPointId(point.pointUuid); setInitialPositions({ walls }); } else if (point.pointType === 'Floor') { const floors = getFloorsByPointId(point.pointUuid); setInitialPositions({ floors }); } else if (point.pointType === 'Zone') { const zones = getZonesByPointId(point.pointUuid); setInitialPositions({ zones }); } } }; const handleDragEnd = (point: Point) => { handleCanvasCursors('default'); setDragOffset(null); if (toolMode !== 'move') return; if (point.pointType === 'Aisle') { const updatedAisles = getAislesByPointId(point.pointUuid); if (updatedAisles.length > 0 && projectId) { updatedAisles.forEach((updatedAisle) => { // API // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ''); // SOCKET socket.emit('v1:model-aisle:add', { projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization, aisleUuid: updatedAisle.aisleUuid, points: updatedAisle.points, type: updatedAisle.type }) }) if (initialPositions.aisles && initialPositions.aisles.length > 0) { const updatedPoints = initialPositions.aisles.map((aisle) => ({ type: "Aisle" as const, lineData: aisle, newData: updatedAisles.find(a => a.aisleUuid === aisle.aisleUuid), timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [{ actionType: 'Lines-Update', points: updatedPoints, }] }); } } } else if (point.pointType === 'Wall') { const updatedWalls = getWallsByPointId(point.pointUuid); if (updatedWalls && updatedWalls.length > 0 && projectId) { updatedWalls.forEach((updatedWall) => { // API // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); // SOCKET const data = { wallData: updatedWall, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:model-Wall:add', data); }); } if (initialPositions.walls && initialPositions.walls.length > 0) { const updatedPoints = initialPositions.walls.map((wall) => ({ type: "Wall" as const, lineData: wall, newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid), timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [{ actionType: 'Lines-Update', points: updatedPoints, }] }); } } else if (point.pointType === 'Floor') { const updatedFloors = getFloorsByPointId(point.pointUuid); if (updatedFloors && updatedFloors.length > 0 && projectId) { updatedFloors.forEach((updatedFloor) => { // API // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); // SOCKET const data = { floorData: updatedFloor, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:model-Floor:add', data); }); } if (initialPositions.floors && initialPositions.floors.length > 0) { const updatedPoints = initialPositions.floors.map((floor) => ({ type: "Floor" as const, lineData: floor, newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid), timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [{ actionType: 'Lines-Update', points: updatedPoints, }] }); } } else if (point.pointType === 'Zone') { const updatedZones = getZonesByPointId(point.pointUuid); if (updatedZones && updatedZones.length > 0 && projectId) { updatedZones.forEach((updatedZone) => { // API // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone); // SOCKET const data = { zoneData: updatedZone, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:zone:add', data); }); } if (initialPositions.zones && initialPositions.zones.length > 0) { const updatedPoints = initialPositions.zones.map((zone) => ({ type: "Zone" as const, lineData: zone, newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid), timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [{ actionType: 'Lines-Update', points: updatedPoints, }] }); } } setInitialPositions({}); } const handlePointClick = (point: Point) => { if (toolMode === '2D-Delete') { if (point.pointType === 'Aisle') { const removedAisles = removeAislePoint(point.pointUuid); setHoveredPoint(null); if (removedAisles.length > 0) { removedAisles.forEach(aisle => { if (projectId) { // API // deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || ''); // SOCKET const data = { projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization, aisleUuid: aisle.aisleUuid } socket.emit('v1:model-aisle:delete', data); } }); const removedAislesData = removedAisles.map((aisle) => ({ type: "Aisle" as const, lineData: aisle, timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Delete', points: removedAislesData } ] }); } } if (point.pointType === 'Wall') { const removedWalls = removeWallPoint(point.pointUuid); if (removedWalls.length > 0) { setHoveredPoint(null); removedWalls.forEach(wall => { if (projectId) { // API // deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid); // SOCKET const data = { wallUuid: wall.wallUuid, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:model-Wall:delete', data); } }); const removedWallsData = removedWalls.map((wall) => ({ type: "Wall" as const, lineData: wall, timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Delete', points: removedWallsData } ] }); } } if (point.pointType === 'Floor') { const Floors = getFloorsByPointId(point.pointUuid); const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid); setHoveredPoint(null); if (removedFloors.length > 0) { removedFloors.forEach(floor => { if (projectId) { // API // deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid); // SOCKET const data = { floorUuid: floor.floorUuid, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:model-Floor:delete', data); } }); const removedFloorsData = removedFloors.map((floor) => ({ type: "Floor" as const, lineData: floor, timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Delete', points: removedFloorsData } ] }); } if (updatedFloors.length > 0) { updatedFloors.forEach(floor => { if (projectId) { // API // upsertFloorApi(projectId, selectedVersion?.versionId || '', floor); // SOCKET const data = { floorData: floor, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:model-Floor:add', data); } }); const updatedFloorsData = updatedFloors.map((floor) => ({ type: "Floor" as const, lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor, newData: floor, timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Update', points: updatedFloorsData } ] }); } } if (point.pointType === 'Zone') { const Zones = getZonesByPointId(point.pointUuid); const { removedZones, updatedZones } = removeZonePoint(point.pointUuid); setHoveredPoint(null); if (removedZones.length > 0) { removedZones.forEach(zone => { if (projectId) { // API // deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid); // SOCKET const data = { zoneUuid: zone.zoneUuid, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:zone:delete', data); } }); const removedZonesData = removedZones.map((zone) => ({ type: "Zone" as const, lineData: zone, timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Delete', points: removedZonesData } ] }); } if (updatedZones.length > 0) { updatedZones.forEach(zone => { if (projectId) { // API // upsertZoneApi(projectId, selectedVersion?.versionId || '', zone); // SOCKET const data = { zoneData: zone, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:zone:add', data); } }); const updatedZonesData = updatedZones.map((zone) => ({ type: "Zone" as const, lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone, newData: zone, timeStamp: new Date().toISOString(), })); push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Update', points: updatedZonesData } ] }); } } handleCanvasCursors('default'); } } useEffect(() => { if (hoveredPoint && hoveredPoint.pointUuid !== point.pointUuid) { setIsHovered(false); } }, [hoveredPoint]) useEffect(() => { if (selectedPoints.length > 0 && selectedPoints.some((selectedPoint) => (selectedPoint.userData.pointUuid && selectedPoint.userData.pointUuid === point.pointUuid))) { setIsSelected(true); } else { setIsSelected(false); } }, [selectedPoints]) if (!point) { return null; } return ( <> {!isSelected ? handleDragStart(point)} onDrag={() => handleDrag(point)} onDragEnd={() => handleDragEnd(point)} > { handlePointClick(point); }} onPointerOver={(e) => { if (!hoveredPoint && selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) { setHoveredPoint(point); setIsHovered(true); if (toolMode === 'move') { handleCanvasCursors('grab'); } } }} onPointerOut={() => { if (hoveredPoint) { setHoveredPoint(null); if (!hoveredLine) { handleCanvasCursors('default'); } } setIsHovered(false) }} userData={point} > borderThickness && vUv.x < 1.0 - borderThickness && vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) { gl_FragColor = vec4(uInnerColor, 1.0); // Inner square } else { gl_FragColor = vec4(uOuterColor, 1.0); // Border } } ` } /> : } ); } export default Point;