Files
SteelXR2/.lovable/plan.md
T
gpt-engineer-app[bot] c11d7ab93a Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
2026-05-12 12:04:01 +00:00

3.6 KiB
Raw Blame History

Objetivo

Eliminar a "briga de matrizes" entre XRHitTestPlacement (pai) e XRGrabbable (filho) e adicionar Fixar/Desfixar a peça no espaço, para o fluxo de alinhar quina-com-quina sobre o objeto real.

O que muda

1. Reparent on grab/release (causa-raiz)

No XRGrabbable, no instante em que o grab começa, o grupo do modelo é transferido para a Scene preservando a transform de mundo. Enquanto está "na mão", o pai (XRHitTestPlacement) não consegue mais influenciar a matriz — fim do drift/salto.

  • Início do grab (one-hand ou two-hand): guardar originalParent e chamar scene.attach(group).
  • Fim do grab (todas as mãos soltaram): originalParent.attach(group) para devolver, mantendo a pose final.
  • Vale para os dois modos (single e dual). A captura de offset/startGroupWorld passa a acontecer depois do attach, garantindo math consistente.

2. Lock / Unlock (fixar peça)

Nova flag global isLocked no useModelStore:

  • Quando true: useControllerGrab ignora o grip (não inicia grab) e XRGrabbable mostra um halo dourado discreto sinalizando "travado".
  • Quando travado, a peça permanece reparentada na Scene com a pose congelada — não volta para o XRHitTestPlacement (impede que o reticle reposicione).
  • Botão "🔒 Fixar / 🔓 Liberar" no XR HUD (ao lado dos toggles existentes) e atalho L no DevKit (desktop).

3. Logs DevKit

  • [grab] attach→scene (hand=left|right|dual)
  • [grab] release→<parentName>
  • [lock] ON / [lock] OFF

Visíveis na aba Logs do DevPanel, úteis tanto no desktop quanto refletidos no overlay quando o testador usar o Quest.

4. Smoke test

Estender src/devkit/SmokeTest.tsx com um caso que:

  1. Cria um group filho de outro group, simula attach→scene, valida que matrixWorld antes/depois é igual (preserve transform).
  2. Toggle de lock impede useControllerGrab de marcar isGrabbing=true.

Arquivos afetados

  • src/components/three/XRGrabbable.tsx — reparent on start/end, halo de lock.
  • src/hooks/useControllerGrab.ts — respeitar isLocked (early return).
  • src/stores/useModelStore.tsisLocked: boolean + setLocked.
  • src/components/XRHud.tsx — botão Fixar/Liberar.
  • src/devkit/FakeControllers.tsx — atalho L.
  • src/devkit/SmokeTest.tsx — novos asserts.
  • src/pages/XRSession.tsx — passar a Scene para o XRGrabbable (via useThree interno; sem mudanças na árvore JSX).

Detalhes técnicos

  • THREE.Object3D.attach(child) preserva world-transform automaticamente (decompõe parent.matrixWorld⁻¹ × child.matrixWorld). É a primitiva certa, não add.
  • O re-attach final usa o parent original capturado no grab start, não o parent atual — protege contra mudanças de árvore React entre frames.
  • O applyWorldMatrixToLocal continua igual, mas como o parent passa a ser a Scene (matrixWorld = identidade), o cálculo fica trivial e estável.
  • isLocked não persiste em localStorage por enquanto (decisão de sessão).

Fora de escopo (fica para depois)

  • Snap visual quina-a-quina (destacar vértice mais próximo do controlador durante o grab).
  • Migração para RayGrab nativo.
  • Persistir lock + pose entre sessões.

Como validar

  1. Desktop + DevKit: ?devkit=1 → SimXR ON → Q segura com a esquerda → mover → soltar. Não pode haver salto. Apertar L → tentar segurar → não deve agarrar.
  2. Quest 3 (testador): pegar peça, mover, soltar — pose final sem drift. Apertar Fixar no HUD → tentar pegar → não move.
  3. Aba Logs do DevPanel mostra a sequência attach→scene / release→<parent> em todo grab.