rotationControls snapping bug fixed
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user