diff --git a/src/components/three/XRGrabbable.tsx b/src/components/three/XRGrabbable.tsx index 6895e05..e3c5fd2 100644 --- a/src/components/three/XRGrabbable.tsx +++ b/src/components/three/XRGrabbable.tsx @@ -177,7 +177,10 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta if (!dual.current) { group.updateMatrixWorld(); - // Restrição: Apontar ambos os controles para a caixa delimitadora do modelo + // Se o usuário já estava segurando a peça com uma das mãos, transita para o modo dual imediatamente. + // Caso contrário (peça solta), exige que ambos os controles apontem para a bounding box da peça. + const wasSingleGrabbing = !!single.current; + const box = new THREE.Box3().setFromObject(group); const checkPointerCollision = (slot: typeof L) => { const origin = new THREE.Vector3().setFromMatrixPosition(slot.gripWorld); @@ -187,7 +190,7 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta return ray.intersectsBox(box); }; - if (checkPointerCollision(L) && checkPointerCollision(R)) { + if (wasSingleGrabbing || (checkPointerCollision(L) && checkPointerCollision(R))) { dual.current = { startGroupWorld: group.matrixWorld.clone(), startMid: midNow.clone(), @@ -197,7 +200,7 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta }; single.current = null; if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); } - console.log('[XR][grab] ◆ TWO-HAND start (orbit+zoom) - bounding box hit'); + console.log('[XR][grab] ◆ TWO-HAND start (orbit+zoom) - ' + (wasSingleGrabbing ? 'from active single grab' : 'bounding box hit')); } else { // Se não apontavam para o modelo, ignora e permite o pan de uma mão (da primeira mão a engajar) return; @@ -230,7 +233,12 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta } else if (dual.current) { console.log('[XR][grab] ◆ TWO-HAND end'); dual.current = null; - syncGrabToStore(); + // Se a outra mão ainda estiver segurando, NÃO sincronizamos/resetamos o group local ainda. + // A transição de duas mãos para uma mão herdará o groupWorldPos correto de forma contínua. + const activeHand = lActive ? 'left' : rActive ? 'right' : null; + if (!activeHand) { + syncGrabToStore(); + } } // ─── Modo de Uma Mão: Panorâmica Livre (Arrastar em 3D) ────────