1f40184f01
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
79 lines
5.1 KiB
Markdown
79 lines
5.1 KiB
Markdown
## 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:
|
||
|
||
- Lê `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
|