Reverted to commit c93e56640d

This commit is contained in:
gpt-engineer-app[bot]
2026-05-24 18:56:17 +00:00
parent 13c1ad315d
commit 79b95ce3c8
17 changed files with 1069 additions and 1878 deletions
+3 -82
View File
@@ -1,25 +1,9 @@
import { useRef, ReactNode, useEffect } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
import { useControllerGrab, ControllerGrabSnapshot } from '@/hooks/useControllerGrab';
import { useModelStore } from '@/stores/useModelStore';
function triggerHaptic(session: XRSession | null, handedness: 'left' | 'right', intensity = 0.5, duration = 40) {
if (!session) return;
try {
for (const source of session.inputSources) {
if (source.handedness === handedness) {
const gp = source.gamepad;
if (gp && gp.hapticActuators && gp.hapticActuators.length > 0) {
gp.hapticActuators[0].pulse(intensity, duration).catch(() => {});
}
}
}
} catch (e) {
console.warn('[XR][Haptic] erro ao vibrar controle no grab:', e);
}
}
interface XRGrabbableProps {
/**
* Quando true (padrão), o grip duplo aplica ZOOM uniforme além da rotação.
@@ -75,7 +59,6 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
const dual = useRef<DualGrabRecord | null>(null);
const haloRef = useRef<THREE.Mesh>(null);
const everGrabbed = useRef(false);
const pendingReset = useRef(false);
// Reset scale to 1 (position/rotation kept) whenever resetScale() is called.
const scaleResetNonce = useModelStore((s) => s.scaleResetNonce);
@@ -87,21 +70,9 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
single.current = null;
}, [scaleResetNonce]);
const { gl } = useThree();
useFrame((_state) => {
useFrame(() => {
const group = groupRef.current;
if (!group) return;
if (pendingReset.current) {
group.position.set(0, 0, 0);
group.rotation.set(0, 0, 0);
group.scale.set(1, 1, 1);
pendingReset.current = false;
return;
}
const session = gl.xr.getSession();
const snap: ControllerGrabSnapshot = grab.current;
const L = snap.left;
const R = snap.right;
@@ -124,50 +95,6 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
haloRef.current.visible = maxGrip > 0.05;
}
const commitGrabTransforms = () => {
const activeId = useModelStore.getState().activeModelId;
if (!activeId) return;
const active = useModelStore.getState().models.find(m => m.id === activeId);
if (!active) return;
const ft = { ...active.fineTuning };
// 1. ESCALA DO MODELO: Lê o fator de escala do filho imediato para converter a translação do mundo real
const child = group.children[0];
const finalScaleFactor = child ? child.scale.x : 1.0;
ft.posX += group.position.x / finalScaleFactor;
ft.posY += group.position.y / finalScaleFactor;
ft.posZ += group.position.z / finalScaleFactor;
// 2. ROTAÇÃO TRIDIMENSIONAL: Combina quaternions de rotação para evitar gimbal lock ao soltar
const currentQuat = new THREE.Quaternion().setFromEuler(
new THREE.Euler(
(active.fineTuning.rotX * Math.PI) / 180,
(active.fineTuning.rotY * Math.PI) / 180,
(active.fineTuning.rotZ * Math.PI) / 180,
'XYZ'
)
);
const finalQuat = group.quaternion.clone().multiply(currentQuat);
const finalEuler = new THREE.Euler().setFromQuaternion(finalQuat, 'XYZ');
ft.rotX = (finalEuler.x * 180) / Math.PI;
ft.rotY = (finalEuler.y * 180) / Math.PI;
ft.rotZ = (finalEuler.z * 180) / Math.PI;
// 3. ESCALA LOCAL DO GRAB: Multiplica a escala anterior
ft.scale = (ft.scale ?? 1) * group.scale.x;
useModelStore.getState().setFineTuning(ft);
// Sinaliza reset pendente para o próximo frame
pendingReset.current = true;
triggerHaptic(session, 'left', 0.25, 12);
triggerHaptic(session, 'right', 0.25, 12);
};
// ─── Two-hand mode (orbit + zoom, sempre) ────────────────────
if (lActive && rActive) {
_tmpVecL.setFromMatrixPosition(L.gripWorld);
@@ -189,13 +116,12 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
};
single.current = null;
if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
triggerHaptic(session, 'left', 0.5, 30);
triggerHaptic(session, 'right', 0.5, 30);
console.log('[XR][grab] ◆ TWO-HAND start (orbit+zoom)');
}
const d = dual.current;
const deltaQuat = new THREE.Quaternion().setFromUnitVectors(d.startAxis, axisNow);
// ZOOM só quando allowScale=true. Caso contrário, mantém escala 1× (só orbita).
const scaleRatio = allowScale
? THREE.MathUtils.clamp(distNow / d.startDist, 0.1, 10)
: 1;
@@ -215,7 +141,6 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
return;
} else if (dual.current) {
console.log('[XR][grab] ◆ TWO-HAND end');
commitGrabTransforms();
dual.current = null;
}
@@ -231,7 +156,6 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
const offsetWorld = new THREE.Vector3().subVectors(groupWorldPos, ctrlPos);
single.current = { hand: activeHand, offsetWorld };
if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
triggerHaptic(session, activeHand, 0.45, 25);
console.log(`[XR][grab] ● ONE-HAND start (${activeHand}) — pan only`);
}
@@ -244,11 +168,8 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta
const invParent = new THREE.Matrix4().copy(group.parent.matrixWorld).invert();
targetWorldPos.applyMatrix4(invParent);
}
group.position.copy(targetWorldPos);
} else if (single.current) {
console.log('[XR][grab] ● ONE-HAND end');
commitGrabTransforms();
single.current = null;
}
});