rotationControls snapping bug fixed

This commit is contained in:
2025-08-20 10:39:29 +05:30
parent a8c6f8d80a
commit 64aadbb53d
2 changed files with 65 additions and 56 deletions

View File

@@ -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<number | null>;
prevRotationRef: React.MutableRefObject<number | null>;
@@ -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);
}
}

View File

@@ -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);
}
});