import * as THREE from "three"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { useParams } from "react-router-dom"; import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore"; import useModuleStore from "../../../../../store/useModuleStore"; import { calculateAssetTransformationOnWall } from "../../../../builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall"; // import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; // import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi"; // import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi"; // import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; function MoveControls2D({ movedObjects, setMovedObjects, pastedObjects, setPastedObjects, duplicatedObjects, setDuplicatedObjects, rotatedObjects, setRotatedObjects, }: any) { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); const { activeModule } = useModuleStore(); const { selectedPoints, clearSelectedPoints } = useSelectedPoints(); const { socket } = useSocketStore(); const { userId, organization } = getUserData(); const { projectId } = useParams(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore } = useSceneContext(); const { getWallAssetsByWall, updateWallAsset } = wallAssetStore(); const { push2D } = undoRedo2DStore(); const { setPosition: setAislePosition, getAislesByPointId, getAisleById } = aisleStore(); const { setPosition: setWallPosition, getWallsByPointId, getWallById } = wallStore(); const { setPosition: setFloorPosition, getFloorsByPointId, getFloorById } = floorStore(); const { setPosition: setZonePosition, getZonesByPointId, getZoneById } = zoneStore(); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [initialStates, setInitialStates] = useState>({}); const [initial, setInitial] = useState<{ aisles?: Aisle[], walls?: Wall[], floors?: Floor[], zones?: Zone[] }>({}); const [isMoving, setIsMoving] = useState(false); useEffect(() => { if (!camera || !scene || !toggleView) return; const canvasElement = gl.domElement; canvasElement.tabIndex = 0; let isMoving = false; const onPointerDown = () => { isMoving = false; }; const onPointerMove = () => { isMoving = true; }; const onPointerUp = (event: PointerEvent) => { if (!isMoving && movedObjects.length > 0 && event.button === 0) { event.preventDefault(); placeMovedPoints(); } if (!isMoving && movedObjects.length > 0 && event.button === 2) { event.preventDefault(); clearSelection(); setMovedObjects([]); resetToInitialPositions(); } }; const onKeyDown = (event: KeyboardEvent) => { const keyCombination = detectModifierKeys(event); if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; if (keyCombination === "G") { if (selectedPoints.length > 0) { movePoints(); } } if (keyCombination === "ESCAPE") { event.preventDefault(); clearSelection(); setMovedObjects([]); resetToInitialPositions(); } }; if (toggleView && selectedPoints.length > 0) { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); } return () => { canvasElement.removeEventListener("pointerdown", onPointerDown); canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); }; }, [camera, controls, scene, toggleView, selectedPoints, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, initial]); useEffect(() => { if (toolMode !== 'move' || !toggleView) { if (movedObjects.length > 0) { resetToInitialPositions(); } clearSelection(); } }, [activeModule, toolMode, toggleView, movedObjects]); useFrame(() => { if (!isMoving || movedObjects.length === 0 || !dragOffset) return; raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit) { const baseNewPosition = new THREE.Vector3().addVectors(hit, dragOffset); movedObjects.forEach((movedPoint: THREE.Object3D) => { if (movedPoint.userData.pointUuid) { const point: Point = movedPoint.userData as Point; const initialPosition = initialPositions[movedPoint.uuid]; if (initialPosition) { const relativeOffset = new THREE.Vector3().subVectors( initialPosition, initialPositions[movedObjects[0].uuid] ); const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; if (point.pointType === 'Aisle') { setAislePosition(point.pointUuid, positionArray); } else if (point.pointType === 'Wall') { setWallPosition(point.pointUuid, positionArray); } else if (point.pointType === 'Floor') { setFloorPosition(point.pointUuid, positionArray); } else if (point.pointType === 'Zone') { setZonePosition(point.pointUuid, positionArray); } } } }); } }); const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { const pointPosition = new THREE.Vector3().copy(point.position); return new THREE.Vector3().subVectors(pointPosition, hitPoint); }, []); const movePoints = (() => { if (selectedPoints.length === 0) return; const states: Record = {}; const initials: { aisles?: Aisle[] | undefined; walls?: Wall[]; floors?: Floor[]; zones?: Zone[]; } = {} selectedPoints.forEach((point: THREE.Object3D) => { states[point.uuid] = { position: new THREE.Vector3().copy(point.position), rotation: point.rotation ? new THREE.Euler().copy(point.rotation) : undefined }; if (point.userData.pointType === "Aisle") { const aisles = getAislesByPointId(point.userData.pointUuid); initials.aisles = [...(initials.aisles ?? []), ...aisles,].filter((aisle, index, self) => index === self.findIndex((a) => a.aisleUuid === aisle.aisleUuid)); } else if (point.userData.pointType === "Wall") { const walls = getWallsByPointId(point.userData.pointUuid); initials.walls = [...(initials.walls ?? []), ...walls,].filter((wall, index, self) => index === self.findIndex((w) => w.wallUuid === wall.wallUuid)); } else if (point.userData.pointType === "Floor") { const floors = getFloorsByPointId(point.userData.pointUuid); initials.floors = [...(initials.floors ?? []), ...floors,].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid)); } else if (point.userData.pointType === "Zone") { const zones = getZonesByPointId(point.userData.pointUuid); initials.zones = [...(initials.zones ?? []), ...zones,].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid)); } }); setInitial(initials) setInitialStates(states); const positions: Record = {}; selectedPoints.forEach((point: THREE.Object3D) => { positions[point.uuid] = new THREE.Vector3().copy(point.position); }); setInitialPositions(positions); raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit && selectedPoints[0]) { const offset = calculateDragOffset(selectedPoints[0], hit); setDragOffset(offset); } setMovedObjects(selectedPoints); setIsMoving(true); }); const resetToInitialPositions = () => { setTimeout(() => { movedObjects.forEach((movedPoint: THREE.Object3D) => { if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) { const point: Point = movedPoint.userData as Point; const initialState = initialStates[movedPoint.uuid]; const positionArray: [number, number, number] = [ initialState.position.x, initialState.position.y, initialState.position.z ]; if (point.pointType === 'Aisle') { setAislePosition(point.pointUuid, positionArray); } else if (point.pointType === 'Wall') { setWallPosition(point.pointUuid, positionArray); } else if (point.pointType === 'Floor') { setFloorPosition(point.pointUuid, positionArray); } else if (point.pointType === 'Zone') { setZonePosition(point.pointUuid, positionArray); } } }); }, 0) }; const placeMovedPoints = () => { if (movedObjects.length === 0) return; const undoPoints: UndoRedo2DDataTypeSchema[] = []; const processedAisles: UndoRedo2DDataTypeSchema[] = []; const processedWalls: UndoRedo2DDataTypeSchema[] = []; const processedFloors: UndoRedo2DDataTypeSchema[] = []; const processedZones: UndoRedo2DDataTypeSchema[] = []; const wallAssetUpdates: WallAsset[] = []; movedObjects.forEach((movedObject: THREE.Object3D) => { if (movedObject.userData.pointUuid) { const point: Point = movedObject.userData as Point; 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, versionId: selectedVersion?.versionId || '', userId, organization, aisleUuid: updatedAisle.aisleUuid, points: updatedAisle.points, type: updatedAisle.type }); const old = initialStates[movedObject.uuid]; if (old) { processedAisles.push({ type: 'Aisle', lineData: { ...updatedAisle, points: [ initialStates[updatedAisle.points[0].pointUuid] ? { ...updatedAisle.points[0], position: initialStates[updatedAisle.points[0].pointUuid].position } : updatedAisle.points[0], initialStates[updatedAisle.points[1].pointUuid] ? { ...updatedAisle.points[1], position: initialStates[updatedAisle.points[1].pointUuid].position } : updatedAisle.points[1] ] as [Point, Point], }, newData: updatedAisle, timeStamp: new Date().toISOString(), }); } }); } } else if (point.pointType === 'Wall') { const updatedWalls = getWallsByPointId(point.pointUuid); if (updatedWalls?.length && projectId) { updatedWalls.forEach(updatedWall => { const initialWall = initial.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: WallAsset = { ...asset, position: [position[0], asset.position[1], position[2]], rotation, }; wallAssetUpdates.push(updatedWallAsset); }); } // API // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); // SOCKET socket.emit('v1:model-Wall:add', { wallData: updatedWall, projectId, versionId: selectedVersion?.versionId || '', userId, organization }); const old = initialStates[movedObject.uuid]; if (old) { processedWalls.push({ type: 'Wall', lineData: { ...updatedWall, points: [ initialStates[updatedWall.points[0].pointUuid] ? { ...updatedWall.points[0], position: initialStates[updatedWall.points[0].pointUuid].position } : updatedWall.points[0], initialStates[updatedWall.points[1].pointUuid] ? { ...updatedWall.points[1], position: initialStates[updatedWall.points[1].pointUuid].position } : updatedWall.points[1] ] as [Point, Point], }, newData: updatedWall, timeStamp: new Date().toISOString(), }); } }); } } else if (point.pointType === 'Floor') { const Floors = getFloorsByPointId(point.pointUuid); const updatedFloors = getFloorsByPointId(point.pointUuid); if (updatedFloors?.length && projectId) { updatedFloors.forEach(updatedFloor => { // API // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); // SOCKET socket.emit('v1:model-Floor:add', { floorData: updatedFloor, projectId, versionId: selectedVersion?.versionId || '', userId, organization }); const updatedFloorsData = updatedFloors.map((floor) => { const originalFloor = Floors.find(f => f.floorUuid === floor.floorUuid) || floor; const updatedPoints = originalFloor.points.map((pt: Point) => { const init = initialStates[pt.pointUuid]; return init ? { ...pt, position: [init.position.x, init.position.y, init.position.z] } : pt; }) as [Point, Point]; return { type: "Floor" as const, lineData: { ...originalFloor, points: updatedPoints }, newData: floor, timeStamp: new Date().toISOString(), }; }); processedFloors.push(...updatedFloorsData); }); } } else if (point.pointType === 'Zone') { const Zones = getZonesByPointId(point.pointUuid); const updatedZones = getZonesByPointId(point.pointUuid); if (updatedZones?.length && projectId) { updatedZones.forEach(updatedZone => { // API // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone); // SOCKET socket.emit('v1:zone:add', { zoneData: updatedZone, projectId, versionId: selectedVersion?.versionId || '', userId, organization }); const updatedZonesData = updatedZones.map((zone) => { const originalZone = Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone; const updatedPoints = originalZone.points.map((pt: Point) => { const init = initialStates[pt.pointUuid]; return init ? { ...pt, position: [init.position.x, init.position.y, init.position.z] } : pt; }) as [Point, Point]; return { type: "Zone" as const, lineData: { ...originalZone, points: updatedPoints }, newData: zone, timeStamp: new Date().toISOString(), }; }); processedZones.push(...updatedZonesData); }); } } } }); setTimeout(() => { if (wallAssetUpdates.length > 0) { wallAssetUpdates.filter((wallAssets, index, self) => index === self.findIndex((w) => w.modelUuid === wallAssets.modelUuid)); wallAssetUpdates.forEach((updatedWallAsset) => { if (projectId && updatedWallAsset) { updateWallAsset(updatedWallAsset.modelUuid, { position: updatedWallAsset.position, rotation: updatedWallAsset.rotation }); // API // upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset); // SOCKET const data = { wallAssetData: updatedWallAsset, projectId: projectId, versionId: selectedVersion?.versionId || '', userId: userId, organization: organization } socket.emit('v1:wall-asset:add', data); } }); } if (processedWalls.length > 0) { const wallMap = new Map(); for (const wall of processedWalls) { if (wall.type !== 'Wall' || !wall.lineData.wallUuid) continue; const uuid = wall.lineData.wallUuid; if (!wallMap.has(uuid)) wallMap.set(uuid, []); wallMap.get(uuid)!.push(wall); } wallMap.forEach((actions, uuid) => { const hasUpdate = actions.some(action => 'newData' in action); if (hasUpdate) { const wallData = getWallById(uuid); if (wallData) { undoPoints.push({ type: 'Wall', lineData: actions[0].lineData as Wall, newData: wallData as Wall, timeStamp: new Date().toISOString() }); } } }); } if (processedAisles.length > 0) { const aisleMap = new Map(); for (const aisle of processedAisles) { if (aisle.type !== 'Aisle' || !aisle.lineData.aisleUuid) continue; const uuid = aisle.lineData.aisleUuid; if (!aisleMap.has(uuid)) aisleMap.set(uuid, []); aisleMap.get(uuid)!.push(aisle); } aisleMap.forEach((actions, uuid) => { const hasUpdate = actions.some(action => 'newData' in action); if (hasUpdate) { const aisleData = getAisleById(uuid); if (aisleData) { undoPoints.push({ type: 'Aisle', lineData: actions[0].lineData as Aisle, newData: aisleData as Aisle, timeStamp: new Date().toISOString() }); } } }); } if (processedFloors.length > 0) { const floorMap = new Map(); for (const floor of processedFloors) { if (floor.type !== 'Floor' || !floor.lineData.floorUuid) continue; const uuid = floor.lineData.floorUuid; if (!floorMap.has(uuid)) { floorMap.set(uuid, []); } floorMap.get(uuid)!.push(floor); } floorMap.forEach((actions, uuid) => { const hasUpdate = actions.some(action => 'newData' in action); if (hasUpdate) { const floorData = getFloorById(uuid); if (floorData) { undoPoints.push({ type: 'Floor', lineData: actions[0].lineData as Floor, newData: floorData as Floor, timeStamp: new Date().toISOString() }); } } }); } if (processedZones.length > 0) { const zoneMap = new Map(); for (const zone of processedZones) { if (zone.type !== 'Zone' || !zone.lineData.zoneUuid) continue; const uuid = zone.lineData.zoneUuid; if (!zoneMap.has(uuid)) { zoneMap.set(uuid, []); } zoneMap.get(uuid)!.push(zone); } zoneMap.forEach((actions, uuid) => { const hasUpdate = actions.some(action => 'newData' in action); if (hasUpdate) { const zoneData = getZoneById(uuid); if (zoneData) { undoPoints.push({ type: 'Zone', lineData: actions[0].lineData as Zone, newData: zoneData as Zone, timeStamp: new Date().toISOString() }); } } }); } if (undoPoints.length > 0) { push2D({ type: 'Draw', actions: [ { actionType: 'Lines-Update', points: undoPoints } ] }); } }, 0); echo.success("Object moved!"); clearSelection(); }; const clearSelection = () => { setPastedObjects([]); setDuplicatedObjects([]); setMovedObjects([]); setRotatedObjects([]); clearSelectedPoints(); }; return ( <> ); } export default MoveControls2D;