rotationControls snapping bug fixed
This commit is contained in:
@@ -1,22 +1,15 @@
|
|||||||
import * as THREE from "three";
|
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({
|
export function handleAssetRotationSnap({
|
||||||
currentRotation,
|
object,
|
||||||
rotationDelta,
|
pointerDeltaX,
|
||||||
keyEvent,
|
keyEvent,
|
||||||
snapBaseRef,
|
snapBaseRef,
|
||||||
prevRotationRef,
|
prevRotationRef,
|
||||||
wasShiftHeldRef
|
wasShiftHeldRef
|
||||||
}: {
|
}: {
|
||||||
currentRotation: THREE.Euler;
|
object: THREE.Object3D;
|
||||||
rotationDelta: number;
|
pointerDeltaX: number;
|
||||||
keyEvent: string;
|
keyEvent: string;
|
||||||
snapBaseRef: React.MutableRefObject<number | null>;
|
snapBaseRef: React.MutableRefObject<number | null>;
|
||||||
prevRotationRef: React.MutableRefObject<number | null>;
|
prevRotationRef: React.MutableRefObject<number | null>;
|
||||||
@@ -31,37 +24,51 @@ export function handleAssetRotationSnap({
|
|||||||
const isCtrlHeld = keyEvent.includes("Ctrl");
|
const isCtrlHeld = keyEvent.includes("Ctrl");
|
||||||
|
|
||||||
const speedFactor = isShiftHeld ? SHIFT_SPEED : NORMAL_SPEED;
|
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;
|
const modifierChanged = isShiftHeld !== wasShiftHeldRef.current;
|
||||||
if (modifierChanged) {
|
if (modifierChanged) {
|
||||||
wasShiftHeldRef.current = isShiftHeld;
|
wasShiftHeldRef.current = isShiftHeld;
|
||||||
|
if (isCtrlHeld) snapBaseRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCtrlHeld) {
|
if (isCtrlHeld) {
|
||||||
const snapDeg = isShiftHeld ? CTRL_SHIFT_SNAP_DEG : CTRL_SNAP_DEG;
|
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) {
|
if (snapBaseRef.current === null) {
|
||||||
snapBaseRef.current = normalizeDegrees(THREE.MathUtils.radToDeg(currentRotation.y));
|
snapBaseRef.current = currentAngle;
|
||||||
} else {
|
prevRotationRef.current = currentAngle;
|
||||||
snapBaseRef.current = normalizeDegrees(snapBaseRef.current + newRotationDeltaDeg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accumulate the total rotation from the base
|
||||||
|
const totalRotation = snapBaseRef.current + deltaAngle;
|
||||||
|
|
||||||
// Snap to nearest increment
|
// 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]
|
// Calculate the delta needed to reach the snapped angle from current rotation
|
||||||
const normalizedSnappedDeg = normalizeDegrees(snappedDeg);
|
let targetDelta = snappedAngle - currentAngle;
|
||||||
|
|
||||||
// Convert back to radians for returning delta
|
// Handle wrapping around 360 degrees
|
||||||
newRotationDeltaDeg = normalizedSnappedDeg - THREE.MathUtils.radToDeg(currentRotation.y);
|
if (Math.abs(targetDelta) > Math.PI) {
|
||||||
|
targetDelta = targetDelta - Math.sign(targetDelta) * 2 * Math.PI;
|
||||||
} else {
|
|
||||||
snapBaseRef.current = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prevRotationRef.current = currentRotation.y;
|
// Update snap base for next frame
|
||||||
|
snapBaseRef.current = totalRotation;
|
||||||
|
|
||||||
return THREE.MathUtils.degToRad(newRotationDeltaDeg);
|
return targetDelta;
|
||||||
|
} else {
|
||||||
|
// Reset snapping when Ctrl is not held
|
||||||
|
snapBaseRef.current = null;
|
||||||
|
prevRotationRef.current = null;
|
||||||
|
return deltaAngle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -156,6 +156,7 @@ function RotateControls3D({
|
|||||||
}, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent]);
|
}, [camera, scene, toggleView, selectedAssets, rotatedObjects, pastedObjects, duplicatedObjects, movedObjects, keyEvent]);
|
||||||
|
|
||||||
const resetToInitialRotations = useCallback(() => {
|
const resetToInitialRotations = useCallback(() => {
|
||||||
|
setTimeout(() => {
|
||||||
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
||||||
const uuid = obj.uuid;
|
const uuid = obj.uuid;
|
||||||
if (obj.userData.modelUuid) {
|
if (obj.userData.modelUuid) {
|
||||||
@@ -176,6 +177,7 @@ function RotateControls3D({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}, 100)
|
||||||
}, [rotatedObjects, initialRotations, initialPositions, updateAsset]);
|
}, [rotatedObjects, initialRotations, initialPositions, updateAsset]);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
@@ -192,25 +194,25 @@ function RotateControls3D({
|
|||||||
const center = rotationCenter.current;
|
const center = rotationCenter.current;
|
||||||
const deltaX = currentPointer.x - prevPointerPosition.current.x;
|
const deltaX = currentPointer.x - prevPointerPosition.current.x;
|
||||||
|
|
||||||
const rawAngleDelta = deltaX;
|
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
||||||
|
if (obj.userData.modelUuid) {
|
||||||
const angleDelta = handleAssetRotationSnap({
|
const angleDelta = handleAssetRotationSnap({
|
||||||
currentRotation: rotatedObjects[0].rotation,
|
object: obj,
|
||||||
rotationDelta: rawAngleDelta,
|
pointerDeltaX: deltaX,
|
||||||
keyEvent,
|
keyEvent,
|
||||||
snapBaseRef,
|
snapBaseRef,
|
||||||
prevRotationRef,
|
prevRotationRef,
|
||||||
wasShiftHeldRef
|
wasShiftHeldRef
|
||||||
});
|
});
|
||||||
|
|
||||||
const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
|
|
||||||
|
|
||||||
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
|
||||||
if (obj.userData.modelUuid) {
|
|
||||||
const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
|
const relativePosition = new THREE.Vector3().subVectors(obj.position, center);
|
||||||
|
const rotationMatrix = new THREE.Matrix4().makeRotationY(angleDelta);
|
||||||
relativePosition.applyMatrix4(rotationMatrix);
|
relativePosition.applyMatrix4(rotationMatrix);
|
||||||
obj.position.copy(center).add(relativePosition);
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user