Files
SteelXR/.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

65 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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.