import * as THREE from 'three'; import { useThree } from '@react-three/fiber'; import { useEffect, useMemo, useState } from "react"; import { DragControls, Tube } from '@react-three/drei'; import { useSocketStore, useToolMode } from '../../../store/builder/store'; import { useBuilderStore } from '../../../store/builder/useBuilderStore'; import { useSceneContext } from '../../scene/sceneContext'; import * as Constants from '../../../types/world/worldConstants'; import { useVersionContext } from '../version/versionContext'; import { useParams } from 'react-router-dom'; import { getUserData } from '../../../functions/getUserData'; import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors'; import { useSelectedPoints } from '../../../store/simulation/useSimulationStore'; import { calculateAssetTransformationOnWall } from '../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall'; 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 { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi'; import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi'; import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi'; import { deleteWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi'; interface LineProps { points: [Point, Point]; } function Line({ points }: Readonly) { const [isHovered, setIsHovered] = useState(false); const { raycaster, camera, pointer } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const [isDeletable, setIsDeletable] = useState(false); const { socket } = useSocketStore(); const { toolMode } = useToolMode(); const { wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore } = useSceneContext(); const { push2D } = undoRedo2DStore(); const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore(); const { removeWallByPoints, setPosition: setWallPosition, getWallByPoints, getConnectedWallsByWallId } = wallStore(); const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore(); const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore(); const { userId, organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const [dragOffset, setDragOffset] = useState(null); const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore(); const { selectedPoints } = useSelectedPoints(); const [initialPositions, setInitialPositions] = useState<{ aisles?: Aisle[], walls?: Wall[], floors?: Floor[], zones?: Zone[] }>({}); const path = useMemo(() => { const [start, end] = points.map(p => new THREE.Vector3(...p.position)); return new THREE.LineCurve3(start, end); }, [points]); const colors = getColor(points[0]); function getColor(point: Point) { if (point.pointType === 'Aisle') { return { defaultLineColor: Constants.lineConfig.aisleColor, defaultDeleteColor: Constants.lineConfig.deleteColor, } } else if (point.pointType === 'Floor') { return { defaultLineColor: Constants.lineConfig.floorColor, defaultDeleteColor: Constants.lineConfig.deleteColor, } } else if (point.pointType === 'Wall') { return { defaultLineColor: Constants.lineConfig.wallColor, defaultDeleteColor: Constants.lineConfig.deleteColor, } } else if (point.pointType === 'Zone') { return { defaultLineColor: Constants.lineConfig.zoneColor, defaultDeleteColor: Constants.lineConfig.deleteColor, } } else { return { defaultLineColor: Constants.lineConfig.defaultColor, defaultDeleteColor: Constants.lineConfig.deleteColor, } } } useEffect(() => { if (toolMode === '2D-Delete') { if (isHovered && !hoveredPoint) { setIsDeletable(true); } else { setIsDeletable(false); } } else { setIsDeletable(false); } }, [isHovered, colors.defaultLineColor, colors.defaultDeleteColor, toolMode, hoveredPoint]); useEffect(() => { if (hoveredLine && (hoveredLine[0].pointUuid !== points[0].pointUuid || hoveredLine[1].pointUuid !== points[1].pointUuid)) { setIsHovered(false); } }, [hoveredLine]) const handlePointClick = (points: [Point, Point]) => { if (toolMode === '2D-Delete') { if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') { const removedWall = removeWallByPoints(points); if (removedWall && projectId) { const assetsOnWall = getWallAssetsByWall(removedWall.wallUuid); assetsOnWall.forEach((asset) => { if (projectId && asset) { removeWallAsset(asset.modelUuid); if (!socket?.active) { // API deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid); } else { // SOCKET const data = { projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization, modelUuid: asset.modelUuid, wallUuid: asset.wallUuid } socket.emit('v1:wall-asset:delete', data); } } }) if (!socket?.active) { // API deleteWallApi(projectId, selectedVersion?.versionId || '', removedWall.wallUuid); } else { // SOCKET const data = { wallUuid: removedWall.wallUuid, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:model-Wall:delete', data); } push2D({ type: 'Draw', actions: [ { actionType: 'Line-Delete', point: { type: 'Wall', lineData: removedWall, timeStamp: new Date().toISOString(), } } ] }); } setHoveredLine(null); } if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { const Floors = getFloorsByPoints(points); const { removedFloors, updatedFloors } = removeFloorByPoints(points); if (removedFloors.length > 0) { removedFloors.forEach(floor => { if (projectId) { if (!socket?.active) { // API deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid); } else { // 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) { if (!socket?.active) { // API upsertFloorApi(projectId, selectedVersion?.versionId || '', floor); } else { // 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 } ] }); } setHoveredLine(null); } if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') { const Zones = getZonesByPoints(points); const { removedZones, updatedZones } = removeZoneByPoints(points); if (removedZones.length > 0) { removedZones.forEach(zone => { if (projectId) { if (!socket?.active) { // API deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid); } else { // 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) { if (!socket?.active) { // API upsertZoneApi(projectId, selectedVersion?.versionId || '', zone); } else { // 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 } ] }); } setHoveredLine(null); } handleCanvasCursors('default'); } } const handleDrag = (points: [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 start = new THREE.Vector3(...points[0].position); const end = new THREE.Vector3(...points[1].position); const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); const delta = new THREE.Vector3().subVectors(positionWithOffset, midPoint); const newStart = new THREE.Vector3().addVectors(start, delta); const newEnd = new THREE.Vector3().addVectors(end, delta); if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') { setWallPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); setWallPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); } if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { setFloorPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); setFloorPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); } if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') { setZonePosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]); setZonePosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]); } } } }; const handleDragStart = (points: [Point, Point]) => { raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit && !hoveredPoint) { const start = new THREE.Vector3(...points[0].position); const end = new THREE.Vector3(...points[1].position); const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5); const offset = new THREE.Vector3().subVectors(midPoint, hit); setDragOffset(offset); if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') { const wall = getWallByPoints(points); if (wall) { const walls = getConnectedWallsByWallId(wall.wallUuid, false); setInitialPositions({ walls }); } } else if (points[0].pointType === 'Floor' && points[0].pointType === 'Floor') { const floors = getFloorsByPointId(points[0].pointUuid); setInitialPositions({ floors }); } else if (points[0].pointType === 'Zone' && points[0].pointType === 'Zone') { const zones = getZonesByPointId(points[0].pointUuid); setInitialPositions({ zones }); } } }; const handleDragEnd = (points: [Point, Point]) => { if (toolMode !== 'move' || !dragOffset) return; handleCanvasCursors('default'); setDragOffset(null); if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') { const wall = getWallByPoints(points); if (!wall) return; const updatedWalls = getConnectedWallsByWallId(wall.wallUuid, false); if (updatedWalls.length > 0 && projectId) { updatedWalls.forEach(updatedWall => { const initialWall = initialPositions.walls?.find(w => w.wallUuid === updatedWall.wallUuid); if (initialWall) { const assetsOnWall = getWallAssetsByWall(updatedWall.wallUuid); assetsOnWall.forEach(asset => { const { position, rotation } = calculateAssetTransformationOnWall(asset, initialWall, updatedWall); const updatedWallAsset = updateWallAsset(asset.modelUuid, { position: [position[0], asset.position[1], position[2]], rotation: rotation }); if (projectId && updatedWallAsset) { if (!socket?.active) { // API upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset); } else { // SOCKET const data = { wallAssetData: updatedWallAsset, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:wall-asset:add', data); } } }); } if (!socket?.active) { // API upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); } else { // 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 (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { const updatedFloors1 = getFloorsByPointId(points[0].pointUuid); const updatedFloors2 = getFloorsByPointId(points[1].pointUuid); const updatedFloors = [...updatedFloors1, ...updatedFloors2].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid)); if (updatedFloors.length > 0 && projectId) { updatedFloors.forEach(updatedFloor => { if (!socket?.active) { // API upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); } else { // 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 (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') { const updatedZones1 = getZonesByPointId(points[0].pointUuid); const updatedZones2 = getZonesByPointId(points[1].pointUuid); const updatedZones = [...updatedZones1, ...updatedZones2].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid)); if (updatedZones.length > 0 && projectId) { updatedZones.forEach(updatedZone => { if (!socket?.active) { // API upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone); } else { // 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, }] }); } } } } return ( handleDragStart(points)} onDrag={() => handleDrag(points)} onDragEnd={() => handleDragEnd(points)} > { handlePointClick(points); }} onPointerOver={(e) => { if (selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) { setHoveredLine(points); setIsHovered(true) if (toolMode === 'move' && !hoveredPoint) { handleCanvasCursors('grab'); } } }} onPointerOut={() => { if (hoveredLine && isHovered) { setHoveredLine(null); if (!hoveredPoint) { handleCanvasCursors('default'); } } setIsHovered(false) }} > ); } export default Line;