Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-20 18:48:16 +00:00
parent 7231a6d302
commit cffa66c6df
+35 -36
View File
@@ -1,44 +1,43 @@
# Plano: Mini-menu de Captura (Foto + Vídeo) no Modo AR
## Objetivo ## Modo Posicionar (desktop)
Adicionar dentro do HUD flutuante do modo AR um pequeno menu de captura com:
- **📸 Foto** — screenshot do canvas (já existe lógica, expor no AR)
- **● Gravar / ⏸ Pausar / ▶ Retomar / ■ Parar** — gravação de vídeo do canvas WebXR
## O que será implementado Permite que, antes de entrar no AR, o usuário mova e gire a peça **ativa** com o mouse — exatamente como faria com o controle no AR. As outras peças permanecem travadas. Ao desativar o botão, todas ficam fixas nas posições atuais e o estado persiste (já temos `savePlacement` por `fileName`).
### 1. Novo hook `useCanvasRecorder` (`src/hooks/useCanvasRecorder.ts`) ### Comportamento
- Usa `canvas.captureStream(30)` + `MediaRecorder` (codec `video/webm;codecs=vp9` com fallback p/ vp8).
- Estados expostos: `status` (`idle | recording | paused`), `elapsedMs`.
- Métodos: `start()`, `pause()`, `resume()`, `stop()`.
- Ao parar: monta `Blob` webm, gera `URL.createObjectURL`, dispara download automático (`tracksteel-AR-<timestamp>.webm`) e também salva uma referência opcional para galeria futura.
- Captura screenshot via `canvas.toDataURL('image/png')` reutilizando o store (`addScreenshot`) — mesma lógica já presente em `XRHud.tsx`.
### 2. Nova aba "Captura" no HUD 3D (`src/components/three/XRHudInWorld.tsx`) - Botão **Posicionar** (ícone Move3D) na barra `ViewerControls`, ao lado de Medir.
- Adicionar tab `capture` (ícone 🎥) na barra de tabs (junto com Cena, Ferram., Inspeção, Compart., WebXR). - Quando ativo:
- Componente `CaptureTab` com botões 3D (`XR3DButton`): - `OrbitControls` é desabilitado (não conflitar com o arrasto da peça).
- **📸 Foto** — chama screenshot, mostra toast "Foto capturada". - Cursor vira `move`.
- **● REC** (vermelho quando gravando) — inicia/para gravação. - Apenas a peça com `activeModelId` responde ao mouse. Clicar em outra peça troca a ativa e passa a manipulá-la (as outras voltam a ficar travadas).
- **⏸ Pause / ▶ Resume** — alterna pausa (desabilitado quando idle). - **Arrastar com botão esquerdo** → translada no plano da câmera (X/Y da tela projetados para o mundo).
- **■ Stop** — finaliza e baixa o arquivo. - **Shift + arrastar esquerdo** ou **arrastar com botão do meio** → translada no eixo Z (profundidade) usando o movimento vertical do mouse.
- Texto de status: `● 00:12` em vermelho enquanto grava, amarelo quando pausado. - **Arrastar com botão direito** → rotaciona: movimento horizontal = rotY (yaw), movimento vertical = rotX (pitch). **Shift + direito** = rotZ (roll).
- Indicador visual flutuante (pequeno círculo vermelho pulsante) visível mesmo com painel fechado quando estiver gravando — anexado ao `headLockRef`. - Cada movimento atualiza `fineTuning` via `setFineTuning` da peça ativa, o que já dispara `savePlacement` (persistência entre sessões/entrar-sair do AR — mesma chave usada hoje).
- Quando desativado: tudo volta ao normal (Orbit ativo, peças travadas onde foram deixadas). Modo Medir e Posicionar são mutuamente exclusivos.
### 3. Integração ### Arquivos a alterar
- O hook recebe o `<canvas>` via `document.querySelector('canvas')` (mesmo padrão usado em `XRHud.tsx`/`handleScreenshot`).
- Nenhuma mudança no store global é necessária — o estado de gravação vive no hook, instanciado no nível do `XRSession.tsx` e passado por props ao `XRHudInWorld`, OU mantido dentro do próprio `CaptureTab` via singleton simples.
## Detalhes técnicos 1. **`src/stores/useModelStore.ts`**
- `MediaRecorder` com `canvas.captureStream(30)` funciona dentro de sessão WebXR no Quest 3 (Chromium-based browser do Quest suporta). - Adicionar `positionMode: boolean` + `setPositionMode(on)`.
- Áudio do microfone **não** será incluído (pode ser adicionado depois se solicitado). - Em `setPositionMode(true)` desligar `measureMode`; e vice-versa em `setMeasureMode`.
- Vídeo gerado é `.webm`; o usuário pode converter para `.mp4` fora do app se quiser.
- O download é feito ao parar a gravação — o arquivo aparece no armazenamento do Quest (pasta Downloads do browser).
## Arquivos afetados 2. **`src/components/three/ModelViewer.tsx`**
- `src/hooks/useCanvasRecorder.ts` (novo) - Novo componente `PositionDragHandler` (dentro do Canvas) que:
- `src/components/three/XRHudInWorld.tsx` (nova tab + CaptureTab + indicador REC) - Escuta `pointerdown/move/up` no `gl.domElement` quando `positionMode` está on e existe `activeModelId`.
- Converte delta de pixels em delta de mundo usando a distância câmera↔peça e o FOV (`2 * dist * tan(fov/2) / canvasHeight`), depois divide pelo `scaleRatio.factor` para gravar em coordenadas locais coerentes com `fineTuning`.
- Aplica via `setFineTuning({ posX: ..., posY: ..., posZ: ..., rotX: ..., rotY: ..., rotZ: ... })`.
- Em `OrbitControls`, definir `enabled={!positionMode && !measureMode}` (medir já não rotaciona hoje só por cursor, mas reforça).
- Cursor: quando `positionMode`, setar `gl.domElement.style.cursor = 'move'`.
## Fora do escopo 3. **`src/components/ViewerControls.tsx`**
- Galeria de vídeos dentro do app (apenas download direto por enquanto). - Importar ícone `Move3D` do lucide.
- Áudio/microfone na gravação. - Novo botão **Posicionar** com `variant={positionMode ? 'default' : 'outline'}` chamando `setPositionMode(!positionMode)`. Label dinâmica: `Posicionar` / `Posicionando — [nome da peça ativa]`.
- Streaming/upload para nuvem.
### Detalhes técnicos
- A translação é gravada em `fineTuning.pos{X,Y,Z}` (espaço local da peça, antes do `renderFactor` da escala). Para que 1 pixel ↔ 1 mm visual fique coerente, o cálculo divide o delta-mundo por `scaleRatio.factor` (ex: em 1:50, mover 100 px arrasta o objeto 100 px de tela tanto na visualização quanto na coordenada interna).
- Rotação em graus (já é o formato de `fineTuning.rotX/Y/Z`), sensibilidade ~`0.4°/px`.
- O bloqueio de outras peças é automático: o handler só atualiza a `activeModelId`. Clicar em outra peça (handler `onClick` já existente em `GLBModel`) muda a ativa.
- Persistência: `setFineTuning` já chama `savePlacement(fileName, fineTuning)`, então sair/voltar do AR mantém posições.
- Sem mudanças no AR; apenas no viewer desktop.