diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts index 89fb36b..ca16720 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts +++ b/app/src/modules/scene/controls/selectionControls/selection3D/functions/handleAssetRotationSnap.ts @@ -1,22 +1,15 @@ import * as THREE from "three"; -function normalizeDegrees(deg: number): number { - // Wrap into [-180, 180] - deg = (deg + 180) % 360; - if (deg < 0) deg += 360; - return deg - 180; -} - export function handleAssetRotationSnap({ - currentRotation, - rotationDelta, + object, + pointerDeltaX, keyEvent, snapBaseRef, prevRotationRef, wasShiftHeldRef }: { - currentRotation: THREE.Euler; - rotationDelta: number; + object: THREE.Object3D; + pointerDeltaX: number; keyEvent: string; snapBaseRef: React.MutableRefObject; prevRotationRef: React.MutableRefObject; @@ -31,37 +24,51 @@ export function handleAssetRotationSnap({ const isCtrlHeld = keyEvent.includes("Ctrl"); const speedFactor = isShiftHeld ? SHIFT_SPEED : NORMAL_SPEED; - let newRotationDeltaDeg = THREE.MathUtils.radToDeg(rotationDelta) * speedFactor; + let deltaAngle = pointerDeltaX * speedFactor; + // Track if modifier changed const modifierChanged = isShiftHeld !== wasShiftHeldRef.current; if (modifierChanged) { wasShiftHeldRef.current = isShiftHeld; + if (isCtrlHeld) snapBaseRef.current = null; } if (isCtrlHeld) { const snapDeg = isShiftHeld ? CTRL_SHIFT_SNAP_DEG : CTRL_SNAP_DEG; + const snapRad = THREE.MathUtils.degToRad(snapDeg); - // Store base rotation in degrees when Ctrl first pressed + // Get current Y rotation from object's quaternion + const euler = new THREE.Euler().setFromQuaternion(object.quaternion, 'YXZ'); + let currentAngle = euler.y; + + // Initialize snap base on first frame if (snapBaseRef.current === null) { - snapBaseRef.current = normalizeDegrees(THREE.MathUtils.radToDeg(currentRotation.y)); - } else { - snapBaseRef.current = normalizeDegrees(snapBaseRef.current + newRotationDeltaDeg); + snapBaseRef.current = currentAngle; + prevRotationRef.current = currentAngle; } + // Accumulate the total rotation from the base + const totalRotation = snapBaseRef.current + deltaAngle; + // Snap to nearest increment - const snappedDeg = Math.round(snapBaseRef.current / snapDeg) * snapDeg; + const snappedAngle = Math.round(totalRotation / snapRad) * snapRad; - // Normalize so it never goes beyond [-180, 180] - const normalizedSnappedDeg = normalizeDegrees(snappedDeg); + // Calculate the delta needed to reach the snapped angle from current rotation + let targetDelta = snappedAngle - currentAngle; - // Convert back to radians for returning delta - newRotationDeltaDeg = normalizedSnappedDeg - THREE.MathUtils.radToDeg(currentRotation.y); + // Handle wrapping around 360 degrees + if (Math.abs(targetDelta) > Math.PI) { + targetDelta = targetDelta - Math.sign(targetDelta) * 2 * Math.PI; + } + // Update snap base for next frame + snapBaseRef.current = totalRotation; + + return targetDelta; } else { + // Reset snapping when Ctrl is not held snapBaseRef.current = null; + prevRotationRef.current = null; + return deltaAngle; } - - prevRotationRef.current = currentRotation.y; - - return THREE.MathUtils.degToRad(newRotationDeltaDeg); -} +} \ 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 f82db88..255f8b0 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/rotateControls3D.tsx @@ -156,26 +156,28 @@ function RotateControls3D({ }, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent]); const resetToInitialRotations = useCallback(() => { - rotatedObjects.forEach((obj: THREE.Object3D) => { - const uuid = obj.uuid; - if (obj.userData.modelUuid) { - const initialRotation = initialRotations[uuid]; - const initialPosition = initialPositions[uuid]; + setTimeout(() => { + 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,]; + 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, - }); + updateAsset(obj.userData.modelUuid, { + rotation: rotationArray, + position: positionArray, + }); - obj.rotation.copy(initialRotation); - obj.position.copy(initialPosition); + obj.rotation.copy(initialRotation); + obj.position.copy(initialPosition); + } } - } - }); + }); + }, 100) }, [rotatedObjects, initialRotations, initialPositions, updateAsset]); useFrame(() => { @@ -192,25 +194,25 @@ function RotateControls3D({ const center = rotationCenter.current; const deltaX = currentPointer.x - prevPointerPosition.current.x; - const rawAngleDelta = deltaX; - - const angleDelta = handleAssetRotationSnap({ - currentRotation: rotatedObjects[0].rotation, - rotationDelta: rawAngleDelta, - keyEvent, - snapBaseRef, - prevRotationRef, - wasShiftHeldRef - }); - - const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta); - rotatedObjects.forEach((obj: THREE.Object3D) => { if (obj.userData.modelUuid) { + const angleDelta = handleAssetRotationSnap({ + object: obj, + pointerDeltaX: deltaX, + keyEvent, + snapBaseRef, + prevRotationRef, + wasShiftHeldRef + }); + const relativePosition = new THREE.Vector3().subVectors(obj.position, center); + const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta); relativePosition.applyMatrix4(rotationMatrix); obj.position.copy(center).add(relativePosition); - obj.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), angleDelta); + + const rotationQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angleDelta); + obj.quaternion.multiply(rotationQuat); + obj.rotation.setFromQuaternion(obj.quaternion); } });