import * as THREE from "three"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store"; import * as Types from "../../../../../types/world/worldTypes"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; import { snapControls } from "../../../../../utils/handleSnap"; import DistanceFindingControls from "./distanceFindingControls"; import { useParams } from "react-router-dom"; import { useProductContext } from "../../../../simulation/products/productContext"; import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; // import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; function MoveControls3D({ movedObjects, setMovedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef, }: any) { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { socket } = useSocketStore(); const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const { userId, organization } = getUserData(); const { projectId } = useParams(); const { assetStore, eventStore, productStore } = useSceneContext(); const { updateAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [initialStates, setInitialStates] = useState>({}); const [isMoving, setIsMoving] = useState(false); const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const updateBackend = ( productName: string, productUuid: string, projectId: string, eventData: EventsSchema ) => { upsertProductOrEventApi({ productName: productName, productUuid: productUuid, projectId: projectId, eventDatas: eventData, versionId: selectedVersion?.versionId || '', }); }; useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; canvasElement.tabIndex = 0; let isPointerMoving = false; const onPointerMove = () => { isPointerMoving = true; }; const onKeyUp = (event: KeyboardEvent) => { const isModifierKey = event.key === "Control" || event.key === "Shift"; if (isModifierKey) { setKeyEvent(""); } }; const onPointerDown = (event: PointerEvent) => { isPointerMoving = false; if (event.button === 0) mouseButtonsDown.current.left = true; if (event.button === 2) mouseButtonsDown.current.right = true; }; const onPointerUp = (event: PointerEvent) => { if (event.button === 0) mouseButtonsDown.current.left = false; if (event.button === 2) mouseButtonsDown.current.right = false; if (!isPointerMoving && movedObjects.length > 0 && event.button === 0) { event.preventDefault(); placeMovedAssets(); } if (!isPointerMoving && movedObjects.length > 0 && event.button === 2) { event.preventDefault(); resetToInitialPositions(); clearSelection(); setMovedObjects([]); } setKeyEvent(""); }; const onKeyDown = (event: KeyboardEvent) => { const keyCombination = detectModifierKeys(event); if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; if (keyCombination !== keyEvent) { if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { setKeyEvent(keyCombination); } else { setKeyEvent(""); } } if (keyCombination === "G") { if (selectedAssets.length > 0) { moveAssets(); } } if (keyCombination === "ESCAPE") { event.preventDefault(); resetToInitialPositions(); clearSelection(); setMovedObjects([]); } }; if (!toggleView) { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); canvasElement?.addEventListener("keyup", onKeyUp); } return () => { canvasElement.removeEventListener("pointerdown", onPointerDown); canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); canvasElement?.removeEventListener("keyup", onKeyUp); }; }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]); 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 resetToInitialPositions = useCallback(() => { setTimeout(() => { movedObjects.forEach((movedObject: THREE.Object3D) => { if (movedObject.userData.modelUuid && initialStates[movedObject.uuid]) { const initialState = initialStates[movedObject.uuid]; const positionArray: [number, number, number] = [ initialState.position.x, initialState.position.y, initialState.position.z ]; updateAsset(movedObject.userData.modelUuid, { position: positionArray, rotation: [ initialState.rotation?.x || 0, initialState.rotation?.y || 0, initialState.rotation?.z || 0 ], }); movedObject.position.copy(initialState.position); if (initialState.rotation) { movedObject.rotation.copy(initialState.rotation); } } }); }, 0) }, [movedObjects, initialStates, updateAsset]); useFrame(() => { if (!isMoving || movedObjects.length === 0) return; const intersectionPoint = new THREE.Vector3(); raycaster.setFromCamera(pointer, camera); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit) { if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { if (movedObjects[0]) { const newOffset = calculateDragOffset(movedObjects[0], intersectionPoint); setDragOffset(newOffset); } return; } if (dragOffset) { const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); let moveSpeed = keyEvent.includes("Shift") ? 0.05 : 1; const initialBasePosition = initialPositions[movedObjects[0].uuid]; const positionDifference = new THREE.Vector3().subVectors(rawBasePosition, initialBasePosition); const adjustedDifference = positionDifference.multiplyScalar(moveSpeed); const baseNewPosition = new THREE.Vector3().addVectors(initialBasePosition, adjustedDifference); if (keyEvent.includes("Ctrl")) { baseNewPosition.x = snapControls(baseNewPosition.x, "Ctrl"); baseNewPosition.z = snapControls(baseNewPosition.z, "Ctrl"); } movedObjects.forEach((movedAsset: THREE.Object3D) => { if (movedAsset.userData.modelUuid) { const initialPosition = initialPositions[movedAsset.userData.modelUuid]; if (initialPosition) { const relativeOffset = new THREE.Vector3().subVectors( initialPosition, initialPositions[movedObjects[0].uuid] ); const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid); const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; if (model) { model.position.set(...positionArray); } } } }); const position = new THREE.Vector3(); if (boundingBoxRef.current) { boundingBoxRef.current.getWorldPosition(position); } else { const box = new THREE.Box3(); movedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj)); const center = new THREE.Vector3(); box.getCenter(center); } } } }); const moveAssets = () => { const states: Record = {}; const positions: Record = {}; selectedAssets.forEach((asset: THREE.Object3D) => { states[asset.uuid] = { position: new THREE.Vector3().copy(asset.position), rotation: asset.rotation ? new THREE.Euler().copy(asset.rotation) : undefined }; positions[asset.uuid] = new THREE.Vector3().copy(asset.position); }); setInitialStates(states); setInitialPositions(positions); raycaster.setFromCamera(pointer, camera); const intersectionPoint = new THREE.Vector3(); const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); if (hit && selectedAssets[0]) { const offset = calculateDragOffset(selectedAssets[0], hit); setDragOffset(offset); } setMovedObjects(selectedAssets); setIsMoving(true); }; const placeMovedAssets = () => { if (movedObjects.length === 0) return; movedObjects.forEach(async (movedAsset: THREE.Object3D) => { if (movedAsset) { const assetUuid = movedAsset.userData.modelUuid; const asset = getAssetById(assetUuid); const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid); if (!asset || !model) return; const position = new THREE.Vector3().copy(model.position); const newFloorItem: Types.FloorItemType = { modelUuid: movedAsset.userData.modelUuid, modelName: movedAsset.userData.modelName, assetId: movedAsset.userData.assetId, position: [position.x, position.y, position.z], rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z }, isLocked: false, isVisible: true, }; if (movedAsset.userData.eventData) { const eventData = eventStore.getState().getEventByModelUuid(movedAsset.userData.modelUuid); const productData = productStore.getState().getEventByModelUuid(selectedProduct.productUuid, movedAsset.userData.modelUuid); if (eventData) { eventStore.getState().updateEvent(movedAsset.userData.modelUuid, { position: [position.x, position.y, position.z], rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); } if (productData) { const event = productStore .getState() .updateEvent( selectedProduct.productUuid, movedAsset.userData.modelUuid, { position: [position.x, position.y, position.z], rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], } ); if (event) { updateBackend( selectedProduct.productName, selectedProduct.productUuid, projectId || '', event ); } newFloorItem.eventData = eventData; } } updateAsset(movedAsset.userData.modelUuid, { position: asset.position, rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); const data = { organization, modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, versionId: selectedVersion?.versionId || '', projectId, userId }; //REST // await setAssetsApi(data); //SOCKET socket.emit("v1:model-asset:add", data); } }); echo.success("Object moved!"); setIsMoving(false); clearSelection(); }; const clearSelection = () => { setpastedObjects([]); setDuplicatedObjects([]); setMovedObjects([]); setRotatedObjects([]); setSelectedAssets([]); setKeyEvent(""); }; return ( ); } export default MoveControls3D;