From c62101d17bc6e726bb1cfbc637b19388e0840b44 Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Wed, 10 Jun 2026 23:57:45 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Auto-deploy:=20melhoria=20no=20s?= =?UTF-8?q?nap=20e=20medi=C3=A7=C3=A3o=20AR=20em=2010/06/2026=2023:57:45?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/three/XRGrabbable.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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) ────────