From 84b8a452a12ef0b68c2f1d3c7195a5d9159858d4 Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Sun, 24 May 2026 13:56:02 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Auto-deploy:=20melhoria=20no=20s?= =?UTF-8?q?nap=20e=20medi=C3=A7=C3=A3o=20AR=20em=2024/05/2026=2013:56:02?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/three/ModelViewer.tsx | 14 ++- src/components/three/XRHudInWorld.tsx | 110 ++++++++++++++------- src/components/three/XRSceneComponents.tsx | 88 +++++++++++++++-- src/stores/useModelStore.ts | 11 +++ 4 files changed, 175 insertions(+), 48 deletions(-) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 8a66776..f4edd9d 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -20,6 +20,7 @@ import { XRSnapHandler, XRGrid, XRGridAutoFollower, + XREnvironmentSelector, } from './XRSceneComponents'; import { XRGrabbable } from './XRGrabbable'; import { XRHitTestPlacement } from './XRHitTestPlacement'; @@ -1203,6 +1204,7 @@ function SceneInsideXR({ const models = useModelStore((s) => s.models); const activeModel = models.find(m => m.id === activeId); const isActiveLocked = !!activeModel?.locked; + const xrEnvironment = useModelStore((s) => s.xrEnvironment); const rigRef = useRef(null); const devkit = useDevKit(); @@ -1259,9 +1261,15 @@ function SceneInsideXR({ // Modo AR imersivo (ou SimXR) return ( <> - - - + {xrEnvironment === 'passthrough' && ( + <> + + + + + )} + + diff --git a/src/components/three/XRHudInWorld.tsx b/src/components/three/XRHudInWorld.tsx index fbf52f2..4f94c9c 100644 --- a/src/components/three/XRHudInWorld.tsx +++ b/src/components/three/XRHudInWorld.tsx @@ -11,7 +11,7 @@ import { stopRecording, captureScreenshot, formatElapsed, } from '@/hooks/useCanvasRecorder'; -type Tab = 'scene' | 'tools' | 'inspection' | 'share' | 'capture' | 'webxr'; +type Tab = 'scene' | 'tools' | 'inspection' | 'share' | 'capture' | 'env'; interface XRHudInWorldProps { freeMove: boolean; @@ -221,7 +221,7 @@ function FloatingPanel({ { id: 'inspection', label: 'Inspeção', icon: '✓' }, { id: 'capture', label: 'Captura', icon: '🎥' }, { id: 'share', label: 'Compart.', icon: '📡' }, - { id: 'webxr', label: 'WebXR', icon: '⚙' }, + { id: 'env', label: 'Ambiente', icon: '🏝' }, ]; const W = 0.5, H = 0.36; return ( @@ -261,7 +261,7 @@ function FloatingPanel({ {tab === 'inspection' && } {tab === 'capture' && } {tab === 'share' && } - {tab === 'webxr' && } + {tab === 'env' && } ); @@ -583,45 +583,79 @@ function ShareTab(p: XRHudInWorldProps) { ); } -function WebXRTab() { - const xrFeatures = useModelStore((s) => s.xrFeatures); - const setXRFeature = useModelStore((s) => s.setXRFeature); - const reset = useModelStore((s) => s.resetXRFeatures); - const keys: { k: keyof typeof xrFeatures; label: string }[] = [ - { k: 'handTracking', label: 'Hand' }, - { k: 'planeDetection', label: 'Planes' }, - { k: 'hitTest', label: 'HitTest' }, - { k: 'domOverlay', label: 'DOM Ovl' }, - { k: 'anchors', label: 'Anchors' }, - { k: 'meshDetection', label: 'Mesh' }, - { k: 'depthSensing', label: 'Depth' }, - { k: 'layers', label: 'Layers' }, - { k: 'bodyTracking', label: 'Body' }, - ]; +function EnvTab() { + const xrEnvironment = useModelStore((s) => s.xrEnvironment); + const setXrEnvironment = useModelStore((s) => s.setXrEnvironment); + const xrScaleMode = useModelStore((s) => s.xrScaleMode); + const setXrScaleMode = useModelStore((s) => s.setXrScaleMode); + return ( - - Desligue uma feature por vez para descobrir qual ativa o grid de segurança do Quest. + + Cenário Imersivo (VR / AR) - - Mudanças entram em vigor ao sair e re-entrar no AR. + + setXrEnvironment('passthrough')} + fontSize={0.008} + /> + { + setXrEnvironment('office'); + setXrScaleMode('tabletop'); + }} + fontSize={0.008} + /> + { + setXrEnvironment('field'); + setXrScaleMode('realscale'); + }} + fontSize={0.008} + /> + + + Escala de Apresentação do IFC + + + setXrScaleMode('realscale')} + fontSize={0.0085} + /> + setXrScaleMode('tabletop')} + fontSize={0.0085} + /> + + + • Passthrough: veja o mundo real à sua volta (Realidade Aumentada). + + + • Escritório: maquete interativa reduzida em cima de uma mesa virtual de reunião. + + + • Campo Aberto: terreno gramado em escala real para explorar estruturas e galpões. - {keys.map((it, i) => { - const col = i % 3; - const row = Math.floor(i / 3); - const x = -0.18 + col * 0.14; - const y = 0.05 - row * 0.045; - const on = xrFeatures[it.k]; - return ( - setXRFeature(it.k, !on)} - fontSize={0.0085} /> - ); - })} - ); } diff --git a/src/components/three/XRSceneComponents.tsx b/src/components/three/XRSceneComponents.tsx index 9b5669a..2ead4c9 100644 --- a/src/components/three/XRSceneComponents.tsx +++ b/src/components/three/XRSceneComponents.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useMemo, useState, useCallback } from 'react'; import { useFrame, useThree } from '@react-three/fiber'; -import { Grid, Billboard } from '@react-three/drei'; +import { Grid, Billboard, Sky } from '@react-three/drei'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import * as THREE from 'three'; import { useModelStore, type SceneModel } from '@/stores/useModelStore'; @@ -141,20 +141,39 @@ export function XRModel({ sceneModel }: { sceneModel: SceneModel }) { const s = fineTuning.scale ?? 1; const scaleRatio = useModelStore((st) => st.scaleRatio); const renderFactor = scaleRatio?.factor ?? 1; + const xrScaleMode = useModelStore((st) => st.xrScaleMode); + const isTabletop = xrScaleMode === 'tabletop'; if (!sceneModel.visible) return null; if (!scene) return null; + // Escala final: se for tabletop, escala de maquete proporcional ao tamanho do objeto + const maxDim = Math.max(modelInfo.size.x, modelInfo.size.y, modelInfo.size.z); + const finalScaleFactor = isTabletop + ? (maxDim > 0 ? 0.5 / maxDim : 0.05) + : renderFactor; + + // Posição local: no tabletop, eleva-se o modelo para que sua base coincida com Y=0 da origem do pai (topo da mesa) + const localPos: [number, number, number] = isTabletop + ? [ + -modelInfo.center.x + fineTuning.posX, + (-modelInfo.center.y + (modelInfo.size.y / 2)) + fineTuning.posY, + -modelInfo.center.z + fineTuning.posZ, + ] + : [ + -modelInfo.center.x + fineTuning.posX, + -modelInfo.center.y + fineTuning.posY, + -modelInfo.center.z + fineTuning.posZ, + ]; + + const parentPos: [number, number, number] = isTabletop ? [0, 0.85, 0] : [0, 0, 0]; + return ( - + @@ -541,3 +560,58 @@ export function XRGridAutoFollower() { }, [models, scene]); return null; } + +export function XREnvironmentSelector() { + const xrEnvironment = useModelStore((s) => s.xrEnvironment); + + if (xrEnvironment === 'passthrough') return null; + + return ( + + {/* Luzes adicionais para ambientes imersivos virtuais */} + + + + {/* Cenário Escritório */} + {xrEnvironment === 'office' && ( + + {/* Carpete do escritório */} + + + + + + {/* Tampo da Mesa */} + + + + + + {/* Perna da Mesa */} + + + + + + {/* Base da perna */} + + + + + + )} + + {/* Cenário Campo Aberto */} + {xrEnvironment === 'field' && ( + + + {/* Chão de grama */} + + + + + + )} + + ); +} diff --git a/src/stores/useModelStore.ts b/src/stores/useModelStore.ts index 1983c88..6ca1312 100644 --- a/src/stores/useModelStore.ts +++ b/src/stores/useModelStore.ts @@ -305,6 +305,12 @@ interface ModelStore { setXrHudOpen: (open: boolean) => void; xrHudTab: string; setXrHudTab: (tab: string) => void; + + // ── WebXR Cenários Imersivos e Escalonamento ──── + xrEnvironment: 'passthrough' | 'office' | 'field'; + setXrEnvironment: (env: 'passthrough' | 'office' | 'field') => void; + xrScaleMode: 'tabletop' | 'realscale'; + setXrScaleMode: (mode: 'tabletop' | 'realscale') => void; } /** Sync top-level legacy `model` / `fineTuning` from active SceneModel. */ @@ -684,4 +690,9 @@ export const useModelStore = create((set, get) => ({ setXrHudOpen: (open) => set({ xrHudOpen: open }), xrHudTab: 'scene', setXrHudTab: (tab) => set({ xrHudTab: tab }), + + xrEnvironment: 'passthrough', + setXrEnvironment: (env) => set({ xrEnvironment: env }), + xrScaleMode: 'realscale', + setXrScaleMode: (mode) => set({ xrScaleMode: mode }), }));