Files
SteelXR/.lovable/plan.md
T
gpt-engineer-app[bot] 1f40184f01 Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
2026-05-09 13:36:46 +00:00

79 lines
5.1 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.
## Plano: Grab nativo dos controles Touch Plus (Quest 3) para "vestir" a peça virtual
Objetivo: permitir que o usuário **agarre a peça virtual com o grip** do Touch Plus e a movimente em 6 graus de liberdade junto com a mão (translação + rotação simultâneas), e use **as duas mãos juntas** para girar/escalar com naturalidade. Joystick atual continua existindo como ajuste fino.
### 1. Camada de input nova: `useControllerGrab`
Novo hook em `src/hooks/useControllerGrab.ts` que encapsula tudo que falta hoje:
-`session.inputSources` a cada frame e identifica os `XRInputSource` "left" e "right".
- Para cada controle, expõe:
- `gripValue` (0..1, analógico, vindo de `gamepad.buttons[2].value`).
- `triggerValue` (0..1, `buttons[1].value`).
- `isGrabbing` (boolean com histerese: começa em `value > 0.7`, termina em `< 0.3` — evita flicker).
- `gripPose`: matrix4 da pose do `inputSource.gripSpace` no `referenceSpace` atual, atualizada por frame via `XRFrame.getPose()`.
- Também escuta os eventos nativos `squeezestart` / `squeezeend` da `XRSession` como fallback caso o gamepad não exponha valor analógico.
### 2. Componente `XRGrabbable` em volta da peça
Novo arquivo `src/components/three/XRGrabbable.tsx`. Wrappa o `<XRModel/>` em um `<group>` controlado e implementa as três modalidades de grab:
- **One-hand grab**: quando `isGrabbing` de uma mão dispara, calcula matrix relativa `M_offset = gripPose⁻¹ · modelPose`. A cada frame, enquanto segura, faz `modelPose = gripPose · M_offset`. Resultado: a peça vira "extensão da mão", com translação + rotação simultâneas.
- **Two-hand grab** (quando ambas as mãos estão segurando):
- Pivô = ponto médio entre os dois `gripPose`.
- Rotação = orientação do vetor entre as duas mãos (mantém o cabo da viga sempre alinhado com o eixo entre os punhos).
- Escala = razão entre a distância atual e a distância no momento em que a 2ª mão agarrou (com clamp 0.1× a 10×). **Opcional**: pode ser desabilitada na HUD para usos onde a escala 1:1 é obrigatória (caso "vestir" sobre peça real).
- Ao soltar (`isGrabbing` cai), grava a pose final em `useModelStore.fineTuning` (decompõe em `posX/Y/Z`, `rotX/Y/Z`, e `scale` se two-hand estiver ativo) — assim o estado fica consistente com o joystick e os controles do HUD.
### 3. Joystick continua, agora modulado pelo grip analógico
Em `ControllerFineTuning` (dentro de `XRSession.tsx`):
- Mantém o mapeamento atual mas **só ativa quando NENHUM grip está apertado** (para não brigar com o grab).
- `posStep` e `rotStep` ganham multiplicador derivado de `triggerValue` da mesma mão: trigger leve = passo fino (×0.3), trigger forte = passo rápido (×3). Resolve a queixa de "movimento mecânico".
- Mapeamento corrigido para o padrão VR (já discutido):
- Esquerdo: X horizontal / Z vertical (frente-trás).
- Direito: rotY horizontal / Y altura vertical.
- Adiciona aceleração proporcional (curva quadrática no input do thumbstick) e deadzone de 0.15.
### 4. Escala no store
`src/stores/useModelStore.ts`: adicionar `scale: number` ao `FineTuning` (default 1). `XRModel` e `ModelViewer` aplicam `scale={[s,s,s]}` no `<group>`.
### 5. UI: feedback visual e ajuda
- `XRGrabbable` mostra um halo sutil ao redor da peça quando `gripValue > 0.1` em qualquer mão (intensidade proporcional → o usuário sente a pressão analógica).
- `src/components/XRHud.tsx`: na seção de Ajuda (?), adicionar bloco "Agarrar a peça":
- "Aperte o grip lateral de um controle perto da peça → ela vira extensão da sua mão."
- "Agarre com os dois grips ao mesmo tempo → rotacione girando os punhos / aproxime-afaste para escalar."
- "Solte o grip para fixar."
- "Trigger modula a velocidade do joystick (leve = fino, forte = rápido)."
- Adicionar toggle "Permitir escala (two-hand)" — default OFF para preservar 1:1.
### 6. Integração e ordem de aplicação
No `XRSession.tsx`, a árvore vira:
```
<XRHitTestPlacement> (posicionamento inicial)
<XRGrabbable> (grab 6DOF + two-hand)
<XRModel/>
```
Hit-test só atua enquanto `placementMode = true`. Quando o usuário agarra pela primeira vez, `placementMode` é desligado automaticamente.
### Detalhes técnicos
- Tudo via WebXR Gamepad API + `XRFrame.getPose(gripSpace, referenceSpace)`. Sem dependências novas.
- Histerese do grip evita perder o grab por tremor de mão.
- Decomposição de matrix usa `THREE.Matrix4.decompose()` para gravar de volta no store sem perder rotação composta.
- Plane snapping continua ativo durante o grab (snap acontece no `onRelease`, não a cada frame, para não travar o movimento).
- Logs `[XR][grab]` para diagnóstico (start/end, qual mão, valor analógico).
### Arquivos afetados
- **Novo**: `src/hooks/useControllerGrab.ts`
- **Novo**: `src/components/three/XRGrabbable.tsx`
- **Editado**: `src/pages/XRSession.tsx` — envolver modelo, ajustar `ControllerFineTuning` (mapeamento + modulação por trigger + bypass quando grip ativo)
- **Editado**: `src/stores/useModelStore.ts` — campo `scale`
- **Editado**: `src/components/three/ModelViewer.tsx` — aplicar `scale`
- **Editado**: `src/components/XRHud.tsx` — toggle "permitir escala", ajuda nova