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

5.1 KiB
Raw Blame History

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