c11d7ab93a
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
65 lines
3.6 KiB
Markdown
65 lines
3.6 KiB
Markdown
## 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.ts` — `isLocked: 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.
|