diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index a05e423..85c0f88 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -44,9 +44,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); - dracoLoader.setDecoderPath( - "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/" - ); + dracoLoader.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"); loader.setDRACOLoader(dracoLoader); useEffect(() => { diff --git a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx index d74f37d..b26485d 100644 --- a/app/src/modules/builder/asset/functions/assetBoundingBox.tsx +++ b/app/src/modules/builder/asset/functions/assetBoundingBox.tsx @@ -1,20 +1,46 @@ -import { Box3, BoxGeometry, EdgesGeometry, Vector3 } from "three"; +import { Line } from "@react-three/drei"; +import { Box3, Vector3 } from "three"; +import { useMemo } from "react"; + +export const AssetBoundingBox = ({ name, boundingBox, color, lineWidth, }: { name: string; boundingBox: Box3 | null; color: string; lineWidth: number; }) => { + const { points } = useMemo(() => { + if (!boundingBox) return { points: [], center: new Vector3() }; + + const min = boundingBox.min; + const max = boundingBox.max; + const center = boundingBox.getCenter(new Vector3()); + + const edges: Array<[number, number, number]> = [ + [min.x, min.y, min.z], [max.x, min.y, min.z], + [max.x, min.y, min.z], [max.x, max.y, min.z], + [max.x, max.y, min.z], [min.x, max.y, min.z], + [min.x, max.y, min.z], [min.x, min.y, min.z], + + [min.x, min.y, max.z], [max.x, min.y, max.z], + [max.x, min.y, max.z], [max.x, max.y, max.z], + [max.x, max.y, max.z], [min.x, max.y, max.z], + [min.x, max.y, max.z], [min.x, min.y, max.z], + + [min.x, min.y, min.z], [min.x, min.y, max.z], + [max.x, min.y, min.z], [max.x, min.y, max.z], + [max.x, max.y, min.z], [max.x, max.y, max.z], + [min.x, max.y, min.z], [min.x, max.y, max.z], + ]; + + return { points: edges, center }; + }, [boundingBox]); -export const AssetBoundingBox = ({ boundingBox }: { boundingBox: Box3 | null }) => { if (!boundingBox) return null; - const size = boundingBox.getSize(new Vector3()); - const center = boundingBox.getCenter(new Vector3()); - - const boxGeometry = new BoxGeometry(size.x, size.y, size.z); - const edges = new EdgesGeometry(boxGeometry); - return ( - - - - - + + ); -}; \ No newline at end of file +}; diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 6819cb4..429a9c3 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -4,7 +4,7 @@ import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; -import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; +import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; import { CameraControls } from '@react-three/drei'; import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; @@ -22,6 +22,7 @@ import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIK function Model({ asset }: { readonly asset: Asset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; + const savedTheme: string = localStorage.getItem("theme") || "light"; const { camera, controls, gl } = useThree(); const { activeTool } = useActiveTool(); const { toolMode } = useToolMode(); @@ -51,6 +52,7 @@ function Model({ asset }: { readonly asset: Asset }) { const [isRendered, setIsRendered] = useState(false); const [gltfScene, setGltfScene] = useState(null); const [boundingBox, setBoundingBox] = useState(null); + const [isSelected, setIsSelected] = useState(false); const groupRef = useRef(null); const mixerRef = useRef(); const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); @@ -62,6 +64,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); const { projectId } = useParams(); + const { selectedAssets } = useSelectedAssets(); const updateBackend = ( productName: string, @@ -456,6 +459,18 @@ function Model({ asset }: { readonly asset: Asset }) { }, [gl]) + useEffect(() => { + if (selectedAssets.length > 0) { + if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) { + setIsSelected(true); + } else { + setIsSelected(false); + } + } else { + setIsSelected(false); + } + }, [selectedAssets]) + return ( {gltfScene && ( - isRendered ? ( - - ) : ( - - ) + <> + {isRendered ? ( + + ) : ( + + )} + {isSelected && + + } + )} ); diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx index 790a330..d91cebb 100644 --- a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx @@ -83,7 +83,7 @@ function MoveControls2D({ if (keyCombination === "G") { if (selectedPoints.length > 0) { - moveAssets(); + movePoints(); } } @@ -164,7 +164,7 @@ function MoveControls2D({ return new THREE.Vector3().subVectors(pointPosition, hitPoint); }, []); - const moveAssets = useCallback(() => { + const movePoints = useCallback(() => { if (selectedPoints.length === 0) return; const states: Record = {}; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx index fa3e8c7..8593758 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/copyPasteControls3D.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { SkeletonUtils } from "three-stdlib"; import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; @@ -10,20 +10,16 @@ import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; -// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; - const CopyPasteControls3D = ({ copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, - selectionGroup, setDuplicatedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, - boundingBoxRef }: any) => { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); @@ -33,31 +29,67 @@ const CopyPasteControls3D = ({ const { assetStore, eventStore } = useSceneContext(); const { addEvent } = eventStore(); const { projectId } = useParams(); - const { assets, addAsset } = assetStore(); + const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); + const [isPasting, setIsPasting] = useState(false); + const [relativePositions, setRelativePositions] = useState([]); + const [centerOffset, setCenterOffset] = useState(null); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ + left: false, + right: false, + }); + + const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => { + if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] }; + + const box = new THREE.Box3(); + objects.forEach(obj => box.expandByObject(obj)); + const center = new THREE.Vector3(); + box.getCenter(center); + + const relatives = objects.map(obj => { + const relativePos = new THREE.Vector3().subVectors(obj.position, center); + return relativePos; + }); + + return { center, relatives }; + }, []); + useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; canvasElement.tabIndex = 0; - let isMoving = false; + let isPointerMoving = false; - const onPointerDown = () => { - isMoving = false; + const onPointerDown = (event: PointerEvent) => { + isPointerMoving = false; + if (event.button === 0) mouseButtonsDown.current.left = true; + if (event.button === 2) mouseButtonsDown.current.right = true; }; const onPointerMove = () => { - isMoving = true; + isPointerMoving = true; }; const onPointerUp = (event: PointerEvent) => { - if (!isMoving && pastedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { + if (event.button === 0) mouseButtonsDown.current.left = false; + if (event.button === 2) mouseButtonsDown.current.right = false; + + if (!isPointerMoving && pastedObjects.length > 0 && event.button === 0) { event.preventDefault(); addPastedObjects(); } + if (!isPointerMoving && pastedObjects.length > 0 && event.button === 2) { + event.preventDefault(); + clearSelection(); + pastedObjects.forEach((obj: THREE.Object3D) => { + removeAsset(obj.userData.modelUuid); + }); + } }; const onKeyDown = (event: KeyboardEvent) => { @@ -69,6 +101,13 @@ const CopyPasteControls3D = ({ if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { pasteCopiedObjects(); } + if (keyCombination === "ESCAPE" && pastedObjects.length > 0) { + event.preventDefault(); + clearSelection(); + pastedObjects.forEach((obj: THREE.Object3D) => { + removeAsset(obj.userData.modelUuid); + }); + } }; if (!toggleView) { @@ -84,27 +123,22 @@ const CopyPasteControls3D = ({ canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); }; - }, [assets, camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, rotatedObjects]); useFrame(() => { - if (pastedObjects.length > 0) { - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - const position = new THREE.Vector3(); - if (boundingBoxRef.current) { - boundingBoxRef.current?.getWorldPosition(position) - selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z)); - } else { - const box = new THREE.Box3(); - pastedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone())); - const center = new THREE.Vector3(); - box.getCenter(center); - selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z)); - } - } + if (!isPasting || pastedObjects.length === 0) return; + if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) return; + + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit && centerOffset) { + pastedObjects.forEach((pastedObject: THREE.Object3D, index: number) => { + const newPos = new THREE.Vector3().addVectors(hit, relativePositions[index]); + setPosition(pastedObject.userData.modelUuid, [newPos.x, 0, newPos.z]); + }); + } }); @@ -122,31 +156,41 @@ const CopyPasteControls3D = ({ const pasteCopiedObjects = () => { if (copiedObjects.length > 0 && pastedObjects.length === 0) { - const newClones = copiedObjects.map((obj: THREE.Object3D) => { - const clone = obj.clone(); - clone.position.copy(obj.position); + const { center, relatives } = calculateRelativePositions(copiedObjects); + setRelativePositions(relatives); + + const newPastedObjects = copiedObjects.map((obj: any) => { + const clone = SkeletonUtils.clone(obj); + clone.userData.modelUuid = THREE.MathUtils.generateUUID(); return clone; }); - selectionGroup.current.add(...newClones); - setpastedObjects([...newClones]); - setSelectedAssets([...newClones]); - const intersectionPoint = new THREE.Vector3(); + setpastedObjects(newPastedObjects); + raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - const position = new THREE.Vector3(); - if (boundingBoxRef.current) { - boundingBoxRef.current?.getWorldPosition(position) - selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z)); - } else { - const box = new THREE.Box3(); - newClones.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone())); - const center = new THREE.Vector3(); - box.getCenter(center); - selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z)); - } + if (hit) { + const offset = new THREE.Vector3().subVectors(center, hit); + setCenterOffset(offset); + setIsPasting(true); + + newPastedObjects.forEach((obj: THREE.Object3D, index: number) => { + const initialPos = new THREE.Vector3().addVectors(hit, relatives[index]); + const asset: Asset = { + modelUuid: obj.userData.modelUuid, + modelName: obj.userData.modelName, + assetId: obj.userData.assetId, + position: initialPos.toArray(), + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + isLocked: false, + isVisible: true, + isCollidable: false, + opacity: 0.5, + }; + addAsset(asset); + }); } } }; @@ -154,33 +198,33 @@ const CopyPasteControls3D = ({ const addPastedObjects = () => { if (pastedObjects.length === 0) return; - pastedObjects.forEach(async (obj: THREE.Object3D) => { - if (obj) { - const worldPosition = new THREE.Vector3(); - obj.getWorldPosition(worldPosition); - obj.position.copy(worldPosition); + pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => { + if (pastedAsset) { + const assetUuid = pastedAsset.userData.modelUuid; + const asset = getAssetById(assetUuid); + if (!asset) return; const newFloorItem: Types.FloorItemType = { - modelUuid: THREE.MathUtils.generateUUID(), - modelName: obj.userData.modelName, - assetId: obj.userData.assetId, - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, }, + modelUuid: pastedAsset.userData.modelUuid, + modelName: pastedAsset.userData.modelName, + assetId: pastedAsset.userData.assetId, + position: asset.position, + rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] }, isLocked: false, isVisible: true, }; let updatedEventData = null; - if (obj.userData.eventData) { - updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData)); + if (pastedAsset.userData.eventData) { + updatedEventData = JSON.parse(JSON.stringify(pastedAsset.userData.eventData)); updatedEventData.modelUuid = newFloorItem.modelUuid; const eventData: any = { - type: obj.userData.eventData.type, + type: pastedAsset.userData.eventData.type, }; - if (obj.userData.eventData.type === "Conveyor") { + if (pastedAsset.userData.eventData.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -212,7 +256,7 @@ const CopyPasteControls3D = ({ rotation: point.rotation })); - } else if (obj.userData.eventData.type === "Vehicle") { + } else if (pastedAsset.userData.eventData.type === "Vehicle") { const vehicleEvent: VehicleEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -250,7 +294,7 @@ const CopyPasteControls3D = ({ rotation: vehicleEvent.point.rotation }; - } else if (obj.userData.eventData.type === "ArmBot") { + } else if (pastedAsset.userData.eventData.type === "ArmBot") { const roboticArmEvent: RoboticArmEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -284,7 +328,7 @@ const CopyPasteControls3D = ({ rotation: roboticArmEvent.point.rotation }; - } else if (obj.userData.eventData.type === "StaticMachine") { + } else if (pastedAsset.userData.eventData.type === "StaticMachine") { const machineEvent: MachineEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -312,7 +356,7 @@ const CopyPasteControls3D = ({ position: machineEvent.point.position, rotation: machineEvent.point.rotation }; - } else if (obj.userData.eventData.type === "Storage") { + } else if (pastedAsset.userData.eventData.type === "Storage") { const storageEvent: StorageEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -339,7 +383,7 @@ const CopyPasteControls3D = ({ position: storageEvent.point.position, rotation: storageEvent.point.rotation }; - } else if (obj.userData.eventData.type === "Human") { + } else if (pastedAsset.userData.eventData.type === "Human") { const humanEvent: HumanEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -374,15 +418,16 @@ const CopyPasteControls3D = ({ } newFloorItem.eventData = eventData; + //REST // await setAssetsApi( // organization, - // obj.uuid, - // obj.userData.name, + // pastedAsset.uuid, + // pastedAsset.userData.name, // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, + // { "x": pastedAsset.rotation.x, "y": pastedAsset.rotation.y, "z": pastedAsset.rotation.z }, + // pastedAsset.userData.modelId, // false, // true, // ); @@ -395,7 +440,7 @@ const CopyPasteControls3D = ({ modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, @@ -405,16 +450,8 @@ const CopyPasteControls3D = ({ projectId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); - obj.userData = { - name: newFloorItem.modelName, - modelId: newFloorItem.assetId, - modelUuid: newFloorItem.modelUuid, - eventData: JSON.parse(JSON.stringify(eventData)) - }; - const asset: Asset = { modelUuid: data.modelUuid, modelName: data.modelName, @@ -426,33 +463,17 @@ const CopyPasteControls3D = ({ isVisible: data.isVisible, opacity: 1, eventData: data.eventData - } - - addAsset(asset); + }; + updateAsset(asset.modelUuid, asset); } else { - - //REST - - // await setAssetsApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); - - //SOCKET const data = { organization, modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: pastedAsset.rotation.x, y: pastedAsset.rotation.y, z: pastedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, @@ -461,7 +482,6 @@ const CopyPasteControls3D = ({ userId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); const asset: Asset = { @@ -474,9 +494,9 @@ const CopyPasteControls3D = ({ isCollidable: false, isVisible: data.isVisible, opacity: 1, - } + }; - addAsset(asset); + updateAsset(asset.modelUuid, asset); } } }); @@ -486,15 +506,14 @@ const CopyPasteControls3D = ({ }; const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setMovedObjects([]); setpastedObjects([]); setDuplicatedObjects([]); setRotatedObjects([]); setSelectedAssets([]); - } + setIsPasting(false); + setCenterOffset(null); + }; return null; }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index ec92d6d..4f08cc7 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useFrame, useThree } from "@react-three/fiber"; import { SkeletonUtils } from "three-stdlib"; import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store"; @@ -10,18 +10,14 @@ import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; -// import { setAssetsApi } from '../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; - const DuplicationControls3D = ({ duplicatedObjects, setDuplicatedObjects, setpastedObjects, - selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, - boundingBoxRef }: any) => { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); const { toggleView } = useToggleView(); @@ -31,39 +27,71 @@ const DuplicationControls3D = ({ const { assetStore, eventStore } = useSceneContext(); const { addEvent } = eventStore(); const { projectId } = useParams(); - const { assets, addAsset } = assetStore(); + const { assets, addAsset, setPosition, updateAsset, removeAsset, getAssetById } = assetStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); + const [dragOffset, setDragOffset] = useState(null); + const [initialPositions, setInitialPositions] = useState>({}); + const [isDuplicating, setIsDuplicating] = useState(false); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ + left: false, + right: false, + }); + + const calculateDragOffset = useCallback((point: THREE.Vector3, hitPoint: THREE.Vector3) => { + const pointPosition = new THREE.Vector3().copy(point); + return new THREE.Vector3().subVectors(pointPosition, hitPoint); + }, []); + useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; canvasElement.tabIndex = 0; - let isMoving = false; + let isPointerMoving = false; - const onPointerDown = () => { - isMoving = false; + const onPointerDown = (event: PointerEvent) => { + isPointerMoving = false; + if (event.button === 0) mouseButtonsDown.current.left = true; + if (event.button === 2) mouseButtonsDown.current.right = true; }; const onPointerMove = () => { - isMoving = true; + isPointerMoving = true; }; const onPointerUp = (event: PointerEvent) => { - if (!isMoving && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { + if (event.button === 0) mouseButtonsDown.current.left = false; + if (event.button === 2) mouseButtonsDown.current.right = false; + + if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 0) { event.preventDefault(); addDuplicatedAssets(); } + if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 2) { + event.preventDefault(); + clearSelection(); + duplicatedObjects.forEach((obj: THREE.Object3D) => { + removeAsset(obj.userData.modelUuid); + }); + } }; const onKeyDown = (event: KeyboardEvent) => { const keyCombination = detectModifierKeys(event); - if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { + if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) { duplicateSelection(); } + if (keyCombination === "ESCAPE" && duplicatedObjects.length > 0) { + event.preventDefault(); + clearSelection(); + duplicatedObjects.forEach((obj: THREE.Object3D) => { + removeAsset(obj.userData.modelUuid); + }); + } }; if (!toggleView) { @@ -79,82 +107,125 @@ const DuplicationControls3D = ({ canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); }; - }, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]); useFrame(() => { - if (duplicatedObjects.length > 0) { - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - const position = new THREE.Vector3(); - if (boundingBoxRef.current) { - boundingBoxRef.current?.getWorldPosition(position) - selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z)); - } else { - const box = new THREE.Box3(); - duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone())); - const center = new THREE.Vector3(); - box.getCenter(center); - selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z)); + if (!isDuplicating || duplicatedObjects.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) { + const assetUuid = duplicatedObjects[0].userData.modelUuid; + const asset = getAssetById(assetUuid); + if (!asset) return; + if (duplicatedObjects[0]) { + const newOffset = calculateDragOffset(new THREE.Vector3(...asset.position), intersectionPoint); + setDragOffset(newOffset); } + return; + } + + if (dragOffset) { + const adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + + duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => { + if (duplicatedObject.userData.modelUuid) { + const initialPosition = initialPositions[duplicatedObject.userData.modelUuid]; + + if (initialPosition) { + + const relativeOffset = new THREE.Vector3().subVectors( + initialPosition, + initialPositions[duplicatedObjects[0].userData.modelUuid] + ); + + const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset); + const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; + + setPosition(duplicatedObject.userData.modelUuid, positionArray); + } + } + }); } } }); - const duplicateSelection = () => { + const duplicateSelection = useCallback(() => { if (selectedAssets.length > 0 && duplicatedObjects.length === 0) { - const newClones = selectedAssets.map((asset: any) => { - const clone = SkeletonUtils.clone(asset); - clone.position.copy(asset.position); + const positions: Record = {}; + + const newDuplicatedObjects = selectedAssets.map((obj: any) => { + const clone = SkeletonUtils.clone(obj); + clone.userData.modelUuid = THREE.MathUtils.generateUUID(); + positions[clone.userData.modelUuid] = new THREE.Vector3().copy(obj.position); return clone; }); - selectionGroup.current.add(...newClones); - setDuplicatedObjects(newClones); + setDuplicatedObjects(newDuplicatedObjects); + setInitialPositions(positions); - const intersectionPoint = new THREE.Vector3(); raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); - if (point) { - const position = new THREE.Vector3(); - boundingBoxRef.current?.getWorldPosition(position) - selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z)); + if (hit) { + const offset = calculateDragOffset(selectedAssets[0].position, hit); + setDragOffset(offset); } + + setIsDuplicating(true); + + newDuplicatedObjects.forEach((obj: THREE.Object3D) => { + const asset: Asset = { + modelUuid: obj.userData.modelUuid, + modelName: obj.userData.modelName, + assetId: obj.userData.assetId, + position: [obj.position.x, 0, obj.position.z], + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + isLocked: false, + isVisible: true, + isCollidable: false, + opacity: 0.5, + }; + addAsset(asset); + }); } - }; + }, [selectedAssets, duplicatedObjects]); + const addDuplicatedAssets = () => { if (duplicatedObjects.length === 0) return; - duplicatedObjects.forEach(async (obj: THREE.Object3D) => { - if (obj) { - const worldPosition = new THREE.Vector3(); - obj.getWorldPosition(worldPosition); - obj.position.copy(worldPosition); + duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => { + if (duplicatedAsset) { + const assetUuid = duplicatedAsset.userData.modelUuid; + const asset = getAssetById(assetUuid); + if (!asset) return; const newFloorItem: Types.FloorItemType = { - modelUuid: THREE.MathUtils.generateUUID(), - modelName: obj.userData.modelName, - assetId: obj.userData.assetId, - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, }, + modelUuid: duplicatedAsset.userData.modelUuid, + modelName: duplicatedAsset.userData.modelName, + assetId: duplicatedAsset.userData.assetId, + position: asset.position, + rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] }, isLocked: false, isVisible: true, }; let updatedEventData = null; - if (obj.userData.eventData) { - updatedEventData = JSON.parse(JSON.stringify(obj.userData.eventData)); + + if (duplicatedAsset.userData.eventData) { + updatedEventData = JSON.parse(JSON.stringify(duplicatedAsset.userData.eventData)); updatedEventData.modelUuid = newFloorItem.modelUuid; const eventData: any = { - type: obj.userData.eventData.type, + type: duplicatedAsset.userData.eventData.type, }; - if (obj.userData.eventData.type === "Conveyor") { + if (duplicatedAsset.userData.eventData.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -186,7 +257,7 @@ const DuplicationControls3D = ({ rotation: point.rotation })); - } else if (obj.userData.eventData.type === "Vehicle") { + } else if (duplicatedAsset.userData.eventData.type === "Vehicle") { const vehicleEvent: VehicleEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -224,7 +295,7 @@ const DuplicationControls3D = ({ rotation: vehicleEvent.point.rotation }; - } else if (obj.userData.eventData.type === "ArmBot") { + } else if (duplicatedAsset.userData.eventData.type === "ArmBot") { const roboticArmEvent: RoboticArmEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -258,7 +329,7 @@ const DuplicationControls3D = ({ rotation: roboticArmEvent.point.rotation }; - } else if (obj.userData.eventData.type === "StaticMachine") { + } else if (duplicatedAsset.userData.eventData.type === "StaticMachine") { const machineEvent: MachineEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -286,7 +357,7 @@ const DuplicationControls3D = ({ position: machineEvent.point.position, rotation: machineEvent.point.rotation }; - } else if (obj.userData.eventData.type === "Storage") { + } else if (duplicatedAsset.userData.eventData.type === "Storage") { const storageEvent: StorageEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -313,7 +384,7 @@ const DuplicationControls3D = ({ position: storageEvent.point.position, rotation: storageEvent.point.rotation }; - } else if (obj.userData.eventData.type === "Human") { + } else if (duplicatedAsset.userData.eventData.type === "Human") { const humanEvent: HumanEventSchema = { modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, @@ -353,11 +424,11 @@ const DuplicationControls3D = ({ // await setAssetsApi( // organization, - // obj.uuid, - // obj.userData.name, + // pastedAsset.uuid, + // pastedAsset.userData.name, // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, + // { "x": pastedAsset.rotation.x, "y": pastedAsset.rotation.y, "z": pastedAsset.rotation.z }, + // pastedAsset.userData.modelId, // false, // true, // ); @@ -370,17 +441,16 @@ const DuplicationControls3D = ({ modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, eventData: eventData, versionId: selectedVersion?.versionId || '', - projectId, - userId + userId, + projectId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); const asset: Asset = { @@ -394,43 +464,25 @@ const DuplicationControls3D = ({ isVisible: data.isVisible, opacity: 1, eventData: data.eventData - } - - addAsset(asset); + }; + updateAsset(asset.modelUuid, asset); } else { - - //REST - - // await setAssetsApi( - // organization, - // obj.uuid, - // obj.userData.name, - // [worldPosition.x, worldPosition.y, worldPosition.z], - // { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z }, - // obj.userData.modelId, - // false, - // true, - // ); - - //SOCKET - const data = { organization, modelUuid: newFloorItem.modelUuid, modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, versionId: selectedVersion?.versionId || '', - userId, - projectId + projectId, + userId }; - // console.log('data: ', data);/ socket.emit("v1:model-asset:add", data); const asset: Asset = { @@ -443,27 +495,26 @@ const DuplicationControls3D = ({ isCollidable: false, isVisible: data.isVisible, opacity: 1, - } + }; - addAsset(asset); + updateAsset(asset.modelUuid, asset); } } }); echo.success("Object duplicated!"); clearSelection(); - } + }; const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setMovedObjects([]); setpastedObjects([]); setDuplicatedObjects([]); setRotatedObjects([]); setSelectedAssets([]); - } + setIsDuplicating(false); + setDragOffset(null); + }; return null; }; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index 6658f62..d6732b3 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useMemo, useRef, useState } from "react"; +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"; @@ -22,7 +22,6 @@ function MoveControls3D({ setpastedObjects, duplicatedObjects, setDuplicatedObjects, - selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef, @@ -39,11 +38,19 @@ function MoveControls3D({ const { userId, organization } = getUserData(); const { projectId } = useParams(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { updateAsset } = assetStore(); - const AssetGroup = useRef(undefined); + const { updateAsset, setPosition, 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, @@ -65,22 +72,10 @@ function MoveControls3D({ const canvasElement = gl.domElement; canvasElement.tabIndex = 0; - const itemsGroup: any = scene.getObjectByName("Asset Group"); - AssetGroup.current = itemsGroup; - - if (!AssetGroup.current) { - console.error("Asset Group not found in the scene."); - return; - } - - let isMoving = false; - - const onPointerDown = () => { - isMoving = false; - }; + let isPointerMoving = false; const onPointerMove = () => { - isMoving = true; + isPointerMoving = true; }; const onKeyUp = (event: KeyboardEvent) => { @@ -91,21 +86,25 @@ function MoveControls3D({ } }; + 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 (!isMoving && movedObjects.length > 0 && event.button === 0) { + 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 (!isMoving && movedObjects.length > 0 && event.button === 2) { + if (!isPointerMoving && movedObjects.length > 0 && event.button === 2) { event.preventDefault(); - + resetToInitialPositions(); clearSelection(); - movedObjects.forEach((asset: any) => { - if (AssetGroup.current) { - AssetGroup.current.attach(asset); - } - }); - setMovedObjects([]); } setKeyEvent(""); @@ -131,14 +130,8 @@ function MoveControls3D({ if (keyCombination === "ESCAPE") { event.preventDefault(); - + resetToInitialPositions(); clearSelection(); - movedObjects.forEach((asset: any) => { - if (AssetGroup.current) { - AssetGroup.current.attach(asset); - } - }); - setMovedObjects([]); } }; @@ -158,118 +151,154 @@ function MoveControls3D({ canvasElement.removeEventListener("keydown", onKeyDown); canvasElement?.removeEventListener("keyup", onKeyUp); }; - }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent,]); + }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent]); - let moveSpeed = keyEvent === "Ctrl" || "Ctrl+Shift" ? 1 : 0.25; + 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 (movedObjects.length > 0) { - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - let point = raycaster.ray.intersectPlane(plane, intersectionPoint); - const floorsGroup = scene.getObjectByName("Floors-Group") as Types.Group | null; - const floorChildren = floorsGroup?.children ?? []; - const floorIntersections = raycaster.intersectObjects([...floorChildren], true); - const intersectedFloor = floorIntersections.find((intersect) => intersect.object.name.includes("Floor")); + if (!isMoving || movedObjects.length === 0) return; - if (intersectedFloor && selectedAssets.length === 1) { - if (intersectedFloor.object.userData.floorUuid) { - point = new THREE.Vector3(intersectedFloor.point.x, intersectedFloor.object.userData.floorDepth, intersectedFloor.point.z); + 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 (point) { - let targetX = point.x; - let targetY = point.y; - let targetZ = point.z; + if (dragOffset) { + const baseNewPosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + + let moveSpeed = keyEvent === "Shift" || "Ctrl+Shift" ? 0.25 : 1; if (keyEvent === "Ctrl") { - targetX = snapControls(targetX, "Ctrl"); - targetZ = snapControls(targetZ, "Ctrl"); + baseNewPosition.x = snapControls(baseNewPosition.x, "Ctrl"); + baseNewPosition.z = snapControls(baseNewPosition.z, "Ctrl"); } - // else if (keyEvent === "Ctrl+Shift") { - // targetX = snapControls(targetX, "Ctrl+Shift"); - // targetZ = snapControls(targetZ, "Ctrl+Shift"); - // } else if (keyEvent === "Shift") { - // targetX = snapControls(targetX, "Shift"); - // targetZ = snapControls(targetZ, "Shift"); - // } else { - // } + 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 newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); + const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; + + setPosition(movedAsset.userData.modelUuid, positionArray); + } + } + }); const position = new THREE.Vector3(); - if (boundingBoxRef.current) { boundingBoxRef.current.getWorldPosition(position); - selectionGroup.current.position.lerp( - new THREE.Vector3( - targetX - (position.x - selectionGroup.current.position.x), - targetY - (position.y - selectionGroup.current.position.y), - targetZ - (position.z - selectionGroup.current.position.z) - ), - moveSpeed - ); } else { const box = new THREE.Box3(); - movedObjects.forEach((obj: THREE.Object3D) => - box.expandByObject(obj) - ); + movedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj)); const center = new THREE.Vector3(); box.getCenter(center); - - selectionGroup.current.position.lerp( - new THREE.Vector3( - targetX - (center.x - selectionGroup.current.position.x), - selectionGroup.current.position.y, - targetZ - (center.z - selectionGroup.current.position.z) - ), - moveSpeed - ); } } } }); const moveAssets = () => { - setMovedObjects(selectedAssets); - selectedAssets.forEach((asset: any) => { - selectionGroup.current.attach(asset); + 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 (obj: THREE.Object3D) => { - if (obj && AssetGroup.current) { - let worldPosition = new THREE.Vector3(); - obj.getWorldPosition(worldPosition); - - if (worldPosition.y < 0) { - worldPosition.y = 0; - } - - selectionGroup.current.remove(obj); - obj.position.copy(worldPosition); + movedObjects.forEach(async (movedAsset: THREE.Object3D) => { + if (movedAsset) { + const assetUuid = movedAsset.userData.modelUuid; + const asset = getAssetById(assetUuid); + if (!asset) return; const newFloorItem: Types.FloorItemType = { - modelUuid: obj.userData.modelUuid, - modelName: obj.userData.modelName, - assetId: obj.userData.assetId, - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + modelUuid: movedAsset.userData.modelUuid, + modelName: movedAsset.userData.modelName, + assetId: movedAsset.userData.assetId, + position: asset.position, + rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z }, isLocked: false, isVisible: true, }; - if (obj.userData.eventData) { - const eventData = eventStore.getState().getEventByModelUuid(obj.userData.modelUuid); - const productData = productStore.getState().getEventByModelUuid(selectedProduct.productUuid, obj.userData.modelUuid); + 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(obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + eventStore.getState().updateEvent(movedAsset.userData.modelUuid, { + position: asset.position, + rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); } @@ -278,10 +307,10 @@ function MoveControls3D({ .getState() .updateEvent( selectedProduct.productUuid, - obj.userData.modelUuid, + movedAsset.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + position: asset.position, + rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], } ); @@ -298,9 +327,9 @@ function MoveControls3D({ } } - updateAsset(obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + updateAsset(movedAsset.userData.modelUuid, { + position: asset.position, + rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z], }); //REST @@ -324,7 +353,7 @@ function MoveControls3D({ modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { x: movedAsset.rotation.x, y: movedAsset.rotation.y, z: movedAsset.rotation.z }, isLocked: false, isVisible: true, socketId: socket.id, @@ -333,22 +362,16 @@ function MoveControls3D({ userId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); - - AssetGroup.current.add(obj); } }); echo.success("Object moved!"); - + setIsMoving(false); clearSelection(); }; const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setpastedObjects([]); setDuplicatedObjects([]); setMovedObjects([]); @@ -365,4 +388,4 @@ function MoveControls3D({ ); } -export default MoveControls3D; +export default MoveControls3D; \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx index fdde56a..f31bc9a 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useMemo, useRef } from "react"; +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"; @@ -20,11 +20,9 @@ function RotateControls3D({ pastedObjects, setpastedObjects, duplicatedObjects, - setDuplicatedObjects, - selectionGroup + setDuplicatedObjects }: any) { - - const { camera, controls, gl, scene, pointer, raycaster } = useThree(); + const { camera, gl, scene, pointer, raycaster } = useThree(); const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); const { toggleView } = useToggleView(); @@ -36,26 +34,33 @@ function RotateControls3D({ const { projectId } = useParams(); const { assetStore, eventStore, productStore } = useSceneContext(); const { updateAsset } = assetStore(); - const AssetGroup = useRef(undefined); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); - const updateBackend = ( + const [initialRotations, setInitialRotations] = useState>({}); + const [initialPositions, setInitialPositions] = useState>({}); + const [isRotating, setIsRotating] = useState(false); + const prevPointerPosition = useRef(null); + const rotationCenter = useRef(null); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ + left: false, + right: false, + }); + + const updateBackend = useCallback(( productName: string, productUuid: string, projectId: string, eventData: EventsSchema ) => { upsertProductOrEventApi({ - productName: productName, - productUuid: productUuid, - projectId: projectId, + productName, + productUuid, + projectId, eventDatas: eventData, versionId: selectedVersion?.versionId || '', - }) - } - - const prevPointerPosition = useRef(null); + }); + }, [selectedVersion]); useEffect(() => { if (!camera || !scene || toggleView) return; @@ -63,45 +68,37 @@ function RotateControls3D({ const canvasElement = gl.domElement; canvasElement.tabIndex = 0; - const itemsGroup: any = scene.getObjectByName("Asset Group"); - AssetGroup.current = itemsGroup; - - if (!AssetGroup.current) { - console.error("Asset Group not found in the scene."); - return; - } - - let isMoving = false; - - const onPointerDown = () => { - isMoving = false; - }; + let isPointerMoving = false; const onPointerMove = () => { - isMoving = true; + isPointerMoving = true; + }; + + 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 (!isMoving && rotatedObjects.length > 0 && event.button === 0) { + if (event.button === 0) mouseButtonsDown.current.left = false; + if (event.button === 2) mouseButtonsDown.current.right = false; + + if (!isPointerMoving && rotatedObjects.length > 0 && event.button === 0) { event.preventDefault(); placeRotatedAssets(); } - if (!isMoving && rotatedObjects.length > 0 && event.button === 2) { + if (!isPointerMoving && rotatedObjects.length > 0 && event.button === 2) { event.preventDefault(); - + resetToInitialRotations(); clearSelection(); - rotatedObjects.forEach((asset: any) => { - if (AssetGroup.current) { - AssetGroup.current.attach(asset); - } - }); - setRotatedObjects([]); } }; const onKeyDown = (event: KeyboardEvent) => { if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return; + if (event.key.toLowerCase() === "r") { if (selectedAssets.length > 0) { rotateAssets(); @@ -109,15 +106,8 @@ function RotateControls3D({ } if (event.key.toLowerCase() === "escape") { event.preventDefault(); - + resetToInitialRotations(); clearSelection(); - rotatedObjects.forEach((asset: any) => { - if (AssetGroup.current) { - AssetGroup.current.attach(asset); - } - }); - - setRotatedObjects([]); } }; @@ -135,41 +125,85 @@ function RotateControls3D({ canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); }; - }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, rotatedObjects, movedObjects]); + }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects]); + + const resetToInitialRotations = useCallback(() => { + rotatedObjects.forEach((obj: THREE.Object3D) => { + const uuid = obj.uuid; + if (obj.userData.modelUuid) { + const initialRotation = initialRotations[uuid]; + const initialPosition = initialPositions[uuid]; + + if (initialRotation && initialPosition) { + const rotationArray: [number, number, number] = [initialRotation.x, initialRotation.y, initialRotation.z,]; + const positionArray: [number, number, number] = [initialPosition.x, initialPosition.y, initialPosition.z,]; + + updateAsset(obj.userData.modelUuid, { + rotation: rotationArray, + position: positionArray, + }); + + obj.rotation.copy(initialRotation); + obj.position.copy(initialPosition); + } + } + }); + }, [rotatedObjects, initialRotations, initialPositions, updateAsset]); useFrame(() => { - if (rotatedObjects.length > 0) { - const intersectionPoint = new THREE.Vector3(); - raycaster.setFromCamera(pointer, camera); - const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (!isRotating || rotatedObjects.length === 0) return; - if (point && prevPointerPosition.current) { - const box = new THREE.Box3(); - rotatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj)); - const center = new THREE.Vector3(); - box.getCenter(center); - - const delta = new THREE.Vector3().subVectors(point, center); - const prevPointerPosition3D = new THREE.Vector3(prevPointerPosition.current.x, 0, prevPointerPosition.current.y); - - const angle = Math.atan2(delta.z, delta.x) - Math.atan2(prevPointerPosition3D.z - center.z, prevPointerPosition3D.x - center.x); - - selectionGroup.current.rotation.y += -angle; - - selectionGroup.current.position.sub(center); - selectionGroup.current.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), -angle); - selectionGroup.current.position.add(center); + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const point = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) { + if (point) { prevPointerPosition.current = new THREE.Vector2(point.x, point.z); } + return; + } + + if (point && prevPointerPosition.current && rotationCenter.current) { + const center = rotationCenter.current; + + const currentAngle = Math.atan2(point.z - center.z, point.x - center.x); + const prevAngle = Math.atan2( + prevPointerPosition.current.y - center.z, + prevPointerPosition.current.x - center.x + ); + const angleDelta = prevAngle - currentAngle; + + rotatedObjects.forEach((obj: THREE.Object3D) => { + if (obj.userData.modelUuid) { + const relativePos = new THREE.Vector3().subVectors(obj.position, center); + relativePos.applyAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta); + obj.position.copy(center).add(relativePos); + obj.rotation.y += angleDelta; + } + }); + + prevPointerPosition.current = new THREE.Vector2(point.x, point.z); } }); - const rotateAssets = () => { + const rotateAssets = useCallback(() => { + const rotations: Record = {}; + const positions: Record = {}; + const box = new THREE.Box3(); - selectedAssets.forEach((asset: any) => box.expandByObject(asset)); + selectedAssets.forEach((obj: THREE.Object3D) => box.expandByObject(obj)); const center = new THREE.Vector3(); box.getCenter(center); + rotationCenter.current = center; + + selectedAssets.forEach((obj: THREE.Object3D) => { + rotations[obj.uuid] = new THREE.Euler().copy(obj.rotation); + positions[obj.uuid] = new THREE.Vector3().copy(obj.position); + }); + + setInitialRotations(rotations); + setInitialPositions(positions); const intersectionPoint = new THREE.Vector3(); raycaster.setFromCamera(pointer, camera); @@ -179,56 +213,54 @@ function RotateControls3D({ prevPointerPosition.current = new THREE.Vector2(point.x, point.z); } - selectedAssets.forEach((asset: any) => { - selectionGroup.current.attach(asset); - }); - setRotatedObjects(selectedAssets); - }; + setIsRotating(true); + }, [selectedAssets, camera, pointer, raycaster, plane]); - const placeRotatedAssets = () => { + const placeRotatedAssets = useCallback(() => { if (rotatedObjects.length === 0) return; - rotatedObjects.forEach(async (obj: THREE.Object3D) => { - if (obj && AssetGroup.current) { - const worldPosition = new THREE.Vector3(); - const worldQuaternion = new THREE.Quaternion(); + rotatedObjects.forEach((obj: THREE.Object3D) => { + if (obj && obj.userData.modelUuid) { + const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z]; - obj.getWorldPosition(worldPosition); - obj.getWorldQuaternion(worldQuaternion); - - selectionGroup.current.remove(obj); - - obj.position.copy(worldPosition); - obj.quaternion.copy(worldQuaternion); + const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z]; const newFloorItem: Types.FloorItemType = { modelUuid: obj.userData.modelUuid, modelName: obj.userData.modelName, assetId: obj.userData.assetId, - position: [worldPosition.x, worldPosition.y, worldPosition.z], + position: positionArray, rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, isLocked: false, - isVisible: true + isVisible: true, }; if (obj.userData.eventData) { const eventData = eventStore.getState().getEventByModelUuid(obj.userData.modelUuid); - const productData = productStore.getState().getEventByModelUuid(selectedProductStore.getState().selectedProduct.productUuid, obj.userData.modelUuid); + const productData = productStore.getState().getEventByModelUuid( + selectedProduct.productUuid, + obj.userData.modelUuid + ); if (eventData) { eventStore.getState().updateEvent(obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], - }) + position: positionArray, + rotation: rotationArray, + }); } - if (productData) { - const event = productStore.getState().updateEvent(selectedProductStore.getState().selectedProduct.productUuid, obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], - }) - if (event && organization) { + if (productData) { + const event = productStore.getState().updateEvent( + selectedProduct.productUuid, + obj.userData.modelUuid, + { + position: positionArray, + rotation: rotationArray, + } + ); + + if (event) { updateBackend( selectedProduct.productName, selectedProduct.productUuid, @@ -242,8 +274,8 @@ function RotateControls3D({ } updateAsset(obj.userData.modelUuid, { - position: [worldPosition.x, worldPosition.y, worldPosition.z], - rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + position: positionArray, + rotation: rotationArray, }); //REST @@ -267,7 +299,11 @@ function RotateControls3D({ modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z }, + rotation: { + x: obj.rotation.x, + y: obj.rotation.y, + z: obj.rotation.z + }, isLocked: false, isVisible: true, socketId: socket.id, @@ -276,29 +312,23 @@ function RotateControls3D({ userId }; - // console.log('data: ', data); socket.emit("v1:model-asset:add", data); - - AssetGroup.current.add(obj); } }); - echo.success("Object rotated!"); + setIsRotating(false); clearSelection(); - } + }, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]); const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setpastedObjects([]); setDuplicatedObjects([]); setMovedObjects([]); setRotatedObjects([]); setSelectedAssets([]); - } + }; return null; } -export default RotateControls3D \ No newline at end of file +export default RotateControls3D; \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index 080a5d8..eb8bdae 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -1,9 +1,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import * as THREE from "three"; -import { useFrame, useThree } from "@react-three/fiber"; +import { useThree } from "@react-three/fiber"; import { SelectionHelper } from "../selectionHelper"; import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; -import * as Types from "../../../../../types/world/worldTypes"; import useModuleStore from "../../../../../store/useModuleStore"; import { useParams } from "react-router-dom"; @@ -13,7 +12,6 @@ import { useVersionContext } from "../../../../builder/version/versionContext"; import { useProductContext } from "../../../../simulation/products/productContext"; import { useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; -import BoundingBox from "./boundingBoxHelper3D"; import DuplicationControls3D from "./duplicationControls3D"; import CopyPasteControls3D from "./copyPasteControls3D"; import MoveControls3D from "./moveControls3D"; @@ -23,7 +21,6 @@ import RotateControls3D from "./rotateControls3D"; const SelectionControls3D: React.FC = () => { const { camera, controls, gl, scene, raycaster, pointer } = useThree(); - const selectionGroup = useRef() as Types.RefGroup; const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const [movedObjects, setMovedObjects] = useState([]); @@ -229,12 +226,6 @@ const SelectionControls3D: React.FC = () => { } }, [activeModule, toolMode, toggleView]); - useFrame(() => { - if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { - selectionGroup.current.position.set(0, 0, 0); - } - }); - const selectAssets = useCallback(() => { selectionBox.endPoint.set(pointer.x, pointer.y, 0); if (controls) (controls as any).enabled = true; @@ -267,9 +258,6 @@ const SelectionControls3D: React.FC = () => { }, [selectionBox, pointer, controls, selectedAssets, setSelectedAssets]); const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setpastedObjects([]); setDuplicatedObjects([]); setSelectedAssets([]); @@ -345,19 +333,13 @@ const SelectionControls3D: React.FC = () => { return ( <> - - - - - + - + - + - - - + ); };