diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index e5ca7ab..fcac600 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -9,6 +9,7 @@ import { useParams } from "react-router-dom"; import { getUserData } from "../../../../../functions/getUserData"; import { useSceneContext } from "../../../sceneContext"; import { useVersionContext } from "../../../../builder/version/versionContext"; +import { handleAssetPositionSnap } from "./functions/handleAssetPositionSnap"; // import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; @@ -35,11 +36,15 @@ const DuplicationControls3D = ({ const { selectedVersion } = selectedVersionStore(); const { userId, organization } = getUserData(); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null); 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 fineMoveBaseRef = useRef(null); + const lastPointerPositionRef = useRef(null); + const wasShiftHeldRef = useRef(false); const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { const pointPosition = new THREE.Vector3().copy(point.position); @@ -78,6 +83,7 @@ const DuplicationControls3D = ({ removeAsset(obj.userData.modelUuid); }); } + setKeyEvent(""); }; const onKeyDown = (event: KeyboardEvent) => { @@ -96,6 +102,14 @@ const DuplicationControls3D = ({ } } + if (keyCombination !== keyEvent) { + if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } else { + setKeyEvent(""); + } + } + if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) { duplicateSelection(); } @@ -108,11 +122,34 @@ const DuplicationControls3D = ({ } }; + const onKeyUp = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (keyCombination === "") { + setKeyEvent(""); + } else if (keyCombination === "Ctrl" || keyCombination === "Ctrl+Shift" || keyCombination === "Shift") { + setKeyEvent(keyCombination); + } + if (duplicatedObjects[0] && keyEvent !== "" && keyCombination === '') { + const intersectionPoint = new THREE.Vector3(); + raycaster.setFromCamera(pointer, camera); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + if (hit) { + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + if (model) { + const newOffset = calculateDragOffset(model, intersectionPoint); + setDragOffset(newOffset); + } + } + } + }; + if (!toggleView) { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); canvasElement.addEventListener("keydown", onKeyDown); + canvasElement.addEventListener("keyup", onKeyUp); } return () => { @@ -120,8 +157,9 @@ const DuplicationControls3D = ({ canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); + canvasElement.addEventListener("keyup", onKeyUp); }; - }, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]); + }, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects, keyEvent]); useFrame(() => { if (!isDuplicating || duplicatedObjects.length === 0) return; @@ -141,28 +179,9 @@ const DuplicationControls3D = ({ } if (dragOffset) { - let adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - - if (axisConstraint) { - const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); - if (model) { - - const currentBasePosition = model.position.clone(); - if (axisConstraint === 'x') { - adjustedHit = new THREE.Vector3( - adjustedHit.x, - currentBasePosition.y, - currentBasePosition.z - ); - } else if (axisConstraint === 'z') { - adjustedHit = new THREE.Vector3( - currentBasePosition.x, - currentBasePosition.y, - adjustedHit.z - ); - } - } - } + const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); + const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid); + const baseNewPosition = handleAssetPositionSnap({ rawBasePosition, intersectionPoint, model, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => { if (duplicatedObject.userData.modelUuid) { @@ -176,7 +195,7 @@ const DuplicationControls3D = ({ ); const model = scene.getObjectByProperty("uuid", duplicatedObject.userData.modelUuid); - const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset); + const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; if (model) { diff --git a/app/src/utils/handleSnap.ts b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts similarity index 89% rename from app/src/utils/handleSnap.ts rename to app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts index d96bf14..00b6924 100644 --- a/app/src/utils/handleSnap.ts +++ b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetPositionSnap.ts @@ -1,9 +1,9 @@ import * as THREE from "three"; -export function getSnappedBasePosition({ +export function handleAssetPositionSnap({ rawBasePosition, intersectionPoint, - movedObjects, + model, axisConstraint, keyEvent, fineMoveBaseRef, @@ -12,7 +12,7 @@ export function getSnappedBasePosition({ }: { rawBasePosition: THREE.Vector3; intersectionPoint: THREE.Vector3; - movedObjects: THREE.Object3D[]; + model: THREE.Object3D | undefined; axisConstraint: "x" | "z" | null; keyEvent: string; fineMoveBaseRef: React.MutableRefObject; @@ -26,9 +26,9 @@ export function getSnappedBasePosition({ const isShiftHeld = keyEvent.includes("Shift"); // Handle Shift toggle state - if (isShiftHeld !== wasShiftHeldRef.current) { + if (isShiftHeld !== wasShiftHeldRef.current && model) { if (isShiftHeld) { - fineMoveBaseRef.current = movedObjects[0].position.clone(); + fineMoveBaseRef.current = model.position.clone(); lastPointerPositionRef.current = intersectionPoint.clone(); } else { fineMoveBaseRef.current = null; @@ -62,8 +62,8 @@ export function getSnappedBasePosition({ } // Apply axis constraint last - if (axisConstraint) { - const currentBasePosition = movedObjects[0].position.clone(); + if (axisConstraint && model) { + const currentBasePosition = model.position.clone(); if (axisConstraint === 'x') { baseNewPosition.set(baseNewPosition.x, currentBasePosition.y, currentBasePosition.z); } else if (axisConstraint === 'z') { @@ -72,4 +72,4 @@ export function getSnappedBasePosition({ } return baseNewPosition; -} +} \ No newline at end of file diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx index d578bc1..8fc9c63 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/moveControls3D.tsx @@ -5,7 +5,7 @@ import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../. import * as Types from "../../../../../types/world/worldTypes"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi"; -import { getSnappedBasePosition } from "../../../../../utils/handleSnap"; +import { handleAssetPositionSnap } from "./functions/handleAssetPositionSnap"; import DistanceFindingControls from "./distanceFindingControls"; import { useParams } from "react-router-dom"; import { useProductContext } from "../../../../simulation/products/productContext"; @@ -34,7 +34,6 @@ function MoveControls3D({ 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, undoRedo3DStore } = useSceneContext(); @@ -43,13 +42,13 @@ function MoveControls3D({ const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">(""); const [axisConstraint, setAxisConstraint] = useState<"x" | "z" | null>(null); 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, }); - // Add a ref to track fine-move base const fineMoveBaseRef = useRef(null); const lastPointerPositionRef = useRef(null); const wasShiftHeldRef = useRef(false); @@ -121,7 +120,6 @@ function MoveControls3D({ clearSelection(); setMovedObjects([]); } - setKeyEvent(""); }; const onKeyDown = (event: KeyboardEvent) => { @@ -247,8 +245,8 @@ function MoveControls3D({ if (dragOffset) { const rawBasePosition = new THREE.Vector3().addVectors(intersectionPoint, dragOffset); - - const baseNewPosition = getSnappedBasePosition({ rawBasePosition, intersectionPoint, movedObjects, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); + const model = movedObjects[0]; + const baseNewPosition = handleAssetPositionSnap({ rawBasePosition, intersectionPoint, model, axisConstraint, keyEvent, fineMoveBaseRef, lastPointerPositionRef, wasShiftHeldRef }); movedObjects.forEach((movedAsset: THREE.Object3D) => { if (movedAsset.userData.modelUuid) {