Reverted to commit c93e56640d
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
stopRecording, captureScreenshot, formatElapsed,
|
||||
} from '@/hooks/useCanvasRecorder';
|
||||
|
||||
type Tab = 'scene' | 'tools' | 'inspection' | 'share' | 'capture' | 'env';
|
||||
type Tab = 'scene' | 'tools' | 'inspection' | 'share' | 'capture' | 'webxr';
|
||||
|
||||
interface XRHudInWorldProps {
|
||||
freeMove: boolean;
|
||||
@@ -53,15 +53,11 @@ const WristToggle = forwardRef<THREE.Group, { open: boolean; onToggle: () => voi
|
||||
);
|
||||
|
||||
export function XRHudInWorld(props: XRHudInWorldProps) {
|
||||
const { camera, gl } = useThree();
|
||||
const { camera } = useThree();
|
||||
const leftCtrl = useXRInputSourceState('controller', 'left');
|
||||
const rightCtrl = useXRInputSourceState('controller', 'right');
|
||||
|
||||
const open = useModelStore((s) => s.xrHudOpen);
|
||||
const setOpen = useModelStore((s) => s.setXrHudOpen);
|
||||
const tab = useModelStore((s) => s.xrHudTab) as Tab;
|
||||
const setTab = useModelStore((s) => s.setXrHudTab) as unknown as (t: Tab) => void;
|
||||
|
||||
const [open, setOpen] = useState(true);
|
||||
const [tab, setTab] = useState<Tab>('scene');
|
||||
/** When pinned, the panel stays fixed in world space and does NOT follow the head. */
|
||||
const [pinned, setPinned] = useState(true);
|
||||
/** While true, the panel is glued to the right controller (drag mode). */
|
||||
@@ -74,32 +70,20 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
|
||||
const headLockRef = useRef<THREE.Group>(null);
|
||||
const targetPos = useRef(new THREE.Vector3());
|
||||
const targetQuat = useRef(new THREE.Quaternion());
|
||||
const [showQuickMenu, setShowQuickMenu] = useState(true);
|
||||
const lastXBtn = useRef(false);
|
||||
const lastYBtn = useRef(false);
|
||||
const lastABtn = useRef(false);
|
||||
/** Cached offset from controller to panel at drag start. */
|
||||
const dragOffsetPos = useRef(new THREE.Vector3());
|
||||
const dragOffsetQuat = useRef(new THREE.Quaternion());
|
||||
const dragInitialized = useRef(false);
|
||||
|
||||
useFrame(() => {
|
||||
// Toggle via left A button (Quest Touch buttons[4])
|
||||
const gp = leftCtrl?.inputSource?.gamepad;
|
||||
if (gp) {
|
||||
// Toggle quick menu via left X button (Quest Touch buttons[4] on left controller)
|
||||
const xBtn = gp.buttons[4];
|
||||
const pressedX = !!xBtn?.pressed;
|
||||
if (pressedX && !lastXBtn.current) {
|
||||
setShowQuickMenu((prev) => !prev);
|
||||
}
|
||||
lastXBtn.current = pressedX;
|
||||
|
||||
// Toggle main HUD via left Y button (Quest Touch buttons[5] on left controller)
|
||||
const yBtn = gp.buttons[5];
|
||||
const pressedY = !!yBtn?.pressed;
|
||||
if (pressedY && !lastYBtn.current) {
|
||||
setOpen(!open);
|
||||
}
|
||||
lastYBtn.current = pressedY;
|
||||
const aBtn = gp.buttons[4];
|
||||
const pressed = !!aBtn?.pressed;
|
||||
if (pressed && !lastABtn.current) setOpen((v) => !v);
|
||||
lastABtn.current = pressed;
|
||||
}
|
||||
|
||||
// Floating panel:
|
||||
@@ -166,11 +150,11 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
|
||||
}
|
||||
}
|
||||
|
||||
// Head-locked fallback toggle — always visible in lower-right of FOV,
|
||||
// Head-locked fallback toggle — always visible in lower-left of FOV,
|
||||
// so the user can reopen the menu even without controllers or if the
|
||||
// panel is positioned out of view.
|
||||
if (headLockRef.current) {
|
||||
const offset = new THREE.Vector3(0.18, -0.18, -0.5).applyQuaternion(camera.quaternion);
|
||||
const offset = new THREE.Vector3(-0.18, -0.18, -0.5).applyQuaternion(camera.quaternion);
|
||||
headLockRef.current.position.copy(camera.position).add(offset);
|
||||
headLockRef.current.quaternion.copy(camera.quaternion);
|
||||
}
|
||||
@@ -181,21 +165,16 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<WristToggle ref={wristRef} open={open} onToggle={() => setOpen(!open)} />
|
||||
{/* Head-locked fallback: always-visible quick controls in lower-right of FOV */}
|
||||
<group ref={headLockRef} renderOrder={999} visible={showQuickMenu}>
|
||||
<XR3DButton position={[0, 0.026, 0]} size={[0.07, 0.022]}
|
||||
<WristToggle ref={wristRef} open={open} onToggle={() => setOpen((v) => !v)} />
|
||||
{/* Head-locked fallback: always-visible quick controls in lower-left of FOV */}
|
||||
<group ref={headLockRef} renderOrder={999}>
|
||||
<XR3DButton position={[0, 0.018, 0]} size={[0.07, 0.022]}
|
||||
label={open ? '✕ Menu' : '☰ Menu'} active={open}
|
||||
onClick={() => { setOpen(!open); reanchorRequested.current = true; }}
|
||||
onClick={() => { setOpen((v) => !v); reanchorRequested.current = true; }}
|
||||
fontSize={0.0085} />
|
||||
<XR3DButton position={[0, 0, 0]} size={[0.07, 0.022]}
|
||||
<XR3DButton position={[0, -0.008, 0]} size={[0.07, 0.022]}
|
||||
label={showGrid ? 'Grid ON' : 'Grid OFF'} active={showGrid}
|
||||
onClick={() => setShowGrid(!showGrid)} fontSize={0.0085} />
|
||||
<XR3DButton position={[0, -0.026, 0]} size={[0.07, 0.022]}
|
||||
label="✕ Sair AR" active={false}
|
||||
onClick={() => {
|
||||
gl.xr.getSession()?.end();
|
||||
}} fontSize={0.0085} />
|
||||
<RecIndicator />
|
||||
</group>
|
||||
{open && (
|
||||
@@ -233,7 +212,7 @@ function FloatingPanel({
|
||||
{ id: 'inspection', label: 'Inspeção', icon: '✓' },
|
||||
{ id: 'capture', label: 'Captura', icon: '🎥' },
|
||||
{ id: 'share', label: 'Compart.', icon: '📡' },
|
||||
{ id: 'env', label: 'Ambiente', icon: '🏝' },
|
||||
{ id: 'webxr', label: 'WebXR', icon: '⚙' },
|
||||
];
|
||||
const W = 0.5, H = 0.36;
|
||||
return (
|
||||
@@ -273,7 +252,7 @@ function FloatingPanel({
|
||||
{tab === 'inspection' && <InspectionTab />}
|
||||
{tab === 'capture' && <CaptureTab />}
|
||||
{tab === 'share' && <ShareTab {...p} />}
|
||||
{tab === 'env' && <EnvTab />}
|
||||
{tab === 'webxr' && <WebXRTab />}
|
||||
</group>
|
||||
</XR3DPanel>
|
||||
);
|
||||
@@ -363,125 +342,77 @@ function ToolsTab(p: XRHudInWorldProps) {
|
||||
const resetScale = useModelStore((s) => s.resetScale);
|
||||
const clearMeasurements = useModelStore((s) => s.clearMeasurements);
|
||||
|
||||
const activeId = useModelStore((s) => s.activeModelId);
|
||||
const models = useModelStore((s) => s.models);
|
||||
const activeModel = models.find((m) => m.id === activeId);
|
||||
const setFineTuning = useModelStore((s) => s.setFineTuning);
|
||||
|
||||
const alignToFace = (face: string) => {
|
||||
let rotX = 0, rotY = 0, rotZ = 0;
|
||||
if (face === 'FRENTE') { rotX = 90; }
|
||||
else if (face === 'LADO') { rotX = 90; rotY = 90; }
|
||||
setFineTuning({ rotX, rotY, rotZ });
|
||||
};
|
||||
|
||||
const snapTo90 = () => {
|
||||
if (!activeModel) return;
|
||||
const ft = activeModel.fineTuning;
|
||||
setFineTuning({
|
||||
rotX: Math.round(ft.rotX / 90) * 90,
|
||||
rotY: Math.round(ft.rotY / 90) * 90,
|
||||
rotZ: Math.round(ft.rotZ / 90) * 90,
|
||||
});
|
||||
};
|
||||
|
||||
const resetRot = () => {
|
||||
setFineTuning({ rotX: 0, rotY: 0, rotZ: 0 });
|
||||
};
|
||||
|
||||
return (
|
||||
<group>
|
||||
{/* Fileira 1: Render */}
|
||||
<Text position={[-0.24, 0.125, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Render</Text>
|
||||
<XR3DButton position={[-0.16, 0.105, 0]} size={[0.075, 0.022]} label="Sólido"
|
||||
<Text position={[-0.24, 0.11, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Render</Text>
|
||||
<XR3DButton position={[-0.16, 0.085, 0]} size={[0.075, 0.022]} label="Sólido"
|
||||
active={renderMode === 'solid'} onClick={() => setRenderMode('solid')} />
|
||||
<XR3DButton position={[-0.08, 0.105, 0]} size={[0.075, 0.022]} label="Bordas"
|
||||
<XR3DButton position={[-0.08, 0.085, 0]} size={[0.075, 0.022]} label="Wire"
|
||||
active={renderMode === 'wireframe'} onClick={() => setRenderMode('wireframe')} />
|
||||
<XR3DButton position={[0.0, 0.085, 0]} size={[0.075, 0.022]} label="Bordas"
|
||||
active={renderMode === 'edges'} onClick={() => setRenderMode('edges')} />
|
||||
|
||||
{/* Fileira 2: Toggles */}
|
||||
<Text position={[-0.24, 0.075, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Toggles</Text>
|
||||
<XR3DButton position={[-0.16, 0.055, 0]} size={[0.075, 0.022]} label={`Grid ${showGrid ? 'ON' : 'OFF'}`}
|
||||
<Text position={[-0.24, 0.05, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Toggles</Text>
|
||||
<XR3DButton position={[-0.16, 0.025, 0]} size={[0.075, 0.022]} label={`Grid ${showGrid ? 'ON' : 'OFF'}`}
|
||||
active={showGrid} onClick={() => setShowGrid(!showGrid)} />
|
||||
<XR3DButton position={[-0.08, 0.055, 0]} size={[0.075, 0.022]} label={`Medir ${measureMode ? 'ON' : 'OFF'}`}
|
||||
<XR3DButton position={[-0.08, 0.025, 0]} size={[0.075, 0.022]} label={`Medir ${measureMode ? 'ON' : 'OFF'}`}
|
||||
active={measureMode} onClick={() => setMeasureMode(!measureMode)} />
|
||||
<XR3DButton position={[0.0, 0.055, 0]} size={[0.075, 0.022]} label={`Snap ${p.snapToPlanes ? 'ON' : 'OFF'}`}
|
||||
<XR3DButton position={[0.0, 0.025, 0]} size={[0.075, 0.022]} label={`Snap ${p.snapToPlanes ? 'ON' : 'OFF'}`}
|
||||
active={p.snapToPlanes} onClick={p.onToggleSnap} />
|
||||
<XR3DButton position={[0.08, 0.055, 0]} size={[0.075, 0.022]}
|
||||
<XR3DButton position={[0.08, 0.025, 0]} size={[0.075, 0.022]}
|
||||
label={p.placementMode ? 'Posic…' : 'Reposic.'}
|
||||
active={p.placementMode} onClick={p.onTogglePlacement} />
|
||||
<XR3DButton position={[0.16, 0.055, 0]} size={[0.075, 0.022]}
|
||||
<XR3DButton position={[0.16, 0.025, 0]} size={[0.075, 0.022]}
|
||||
label={`Zoom ${p.allowScale ? 'ON' : 'OFF'}`}
|
||||
active={p.allowScale} onClick={p.onToggleAllowScale} />
|
||||
|
||||
{/* Fileira 3: Selecionar */}
|
||||
<Text position={[-0.24, 0.025, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Selecionar</Text>
|
||||
<XR3DButton position={[-0.16, 0.005, 0]} size={[0.075, 0.022]}
|
||||
{/* Selecionar — paridade com a tela inicial */}
|
||||
<Text position={[-0.24, -0.005, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Selecionar</Text>
|
||||
<XR3DButton position={[-0.16, -0.03, 0]} size={[0.075, 0.022]}
|
||||
label={selectionMode ? (selectedCount > 0 ? `${selectedCount} sel.` : 'Selec…') : 'Selec'}
|
||||
active={selectionMode} onClick={() => setSelectionMode(!selectionMode)} />
|
||||
<XR3DButton position={[-0.08, 0.005, 0]} size={[0.075, 0.022]} label="Esconder"
|
||||
<XR3DButton position={[-0.08, -0.03, 0]} size={[0.075, 0.022]} label="Esconder"
|
||||
onClick={hideSelectedElements} />
|
||||
<XR3DButton position={[0.0, 0.005, 0]} size={[0.075, 0.022]} label="Isolar"
|
||||
<XR3DButton position={[0.0, -0.03, 0]} size={[0.075, 0.022]} label="Isolar"
|
||||
onClick={isolateSelectedElements} />
|
||||
<XR3DButton position={[0.08, 0.005, 0]} size={[0.075, 0.022]} label="Mostrar"
|
||||
<XR3DButton position={[0.08, -0.03, 0]} size={[0.075, 0.022]} label="Mostrar"
|
||||
active={hasHidden} onClick={showAllElements} />
|
||||
|
||||
{/* Fileira 4: Opacidade */}
|
||||
<Text position={[-0.24, -0.025, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||
<Text position={[-0.24, -0.06, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||
Opacidade {Math.round(opacity * 100)}%
|
||||
</Text>
|
||||
<XR3DButton position={[-0.16, -0.045, 0]} size={[0.05, 0.022]} label="−25%"
|
||||
<XR3DButton position={[-0.16, -0.085, 0]} size={[0.05, 0.022]} label="−25%"
|
||||
onClick={() => setOpacity(Math.max(0, opacity - 0.25))} />
|
||||
<XR3DButton position={[-0.105, -0.045, 0]} size={[0.05, 0.022]} label="−5%"
|
||||
<XR3DButton position={[-0.105, -0.085, 0]} size={[0.05, 0.022]} label="−5%"
|
||||
onClick={() => setOpacity(Math.max(0, opacity - 0.05))} />
|
||||
<XR3DButton position={[-0.05, -0.045, 0]} size={[0.05, 0.022]} label="+5%"
|
||||
<XR3DButton position={[-0.05, -0.085, 0]} size={[0.05, 0.022]} label="+5%"
|
||||
onClick={() => setOpacity(Math.min(1, opacity + 0.05))} />
|
||||
<XR3DButton position={[0.005, -0.045, 0]} size={[0.05, 0.022]} label="+25%"
|
||||
<XR3DButton position={[0.005, -0.085, 0]} size={[0.05, 0.022]} label="+25%"
|
||||
onClick={() => setOpacity(Math.min(1, opacity + 0.25))} />
|
||||
<XR3DButton position={[0.06, -0.045, 0]} size={[0.05, 0.022]} label="100%"
|
||||
<XR3DButton position={[0.06, -0.085, 0]} size={[0.05, 0.022]} label="100%"
|
||||
onClick={() => setOpacity(1)} />
|
||||
|
||||
{/* Fileira 5: Escala */}
|
||||
<Text position={[-0.24, -0.075, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||
<Text position={[-0.24, -0.115, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||
Escala {scaleRatio.label}
|
||||
</Text>
|
||||
{SCALE_LABELS.map((lbl, i) => {
|
||||
const preset = SCALE_PRESETS.find((sp) => sp.label === lbl)!;
|
||||
return (
|
||||
<XR3DButton key={lbl}
|
||||
position={[-0.16 + i * 0.06, -0.095, 0]} size={[0.055, 0.022]} label={lbl}
|
||||
position={[-0.16 + i * 0.06, -0.14, 0]} size={[0.055, 0.022]} label={lbl}
|
||||
active={scaleRatio.label === lbl}
|
||||
onClick={() => setScaleRatio(preset)} />
|
||||
);
|
||||
})}
|
||||
<XR3DButton position={[0.10, -0.095, 0]} size={[0.062, 0.022]}
|
||||
<XR3DButton position={[0.10, -0.14, 0]} size={[0.062, 0.022]}
|
||||
label="↺ Reset" color="#dc2626"
|
||||
onClick={resetScale} fontSize={0.0085} />
|
||||
<XR3DButton position={[0.17, -0.095, 0]} size={[0.062, 0.022]}
|
||||
<XR3DButton position={[0.17, -0.14, 0]} size={[0.062, 0.022]}
|
||||
label="✕ Medidas" color="#dc2626"
|
||||
onClick={clearMeasurements} fontSize={0.0085} />
|
||||
|
||||
{/* Fileira 6: Rotação / Alinhamento Ortogonal */}
|
||||
<Text position={[-0.24, -0.125, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||
Alinhar Rotação
|
||||
</Text>
|
||||
<XR3DButton position={[-0.16, -0.145, 0]} size={[0.075, 0.022]}
|
||||
label="Snap 90°" active={false}
|
||||
onClick={snapTo90} fontSize={0.0075} />
|
||||
<XR3DButton position={[-0.08, -0.145, 0]} size={[0.05, 0.022]}
|
||||
label="Topo" active={false}
|
||||
onClick={() => alignToFace('TOPO')} fontSize={0.0075} />
|
||||
<XR3DButton position={[-0.025, -0.145, 0]} size={[0.05, 0.022]}
|
||||
label="Frente" active={false}
|
||||
onClick={() => alignToFace('FRENTE')} fontSize={0.0075} />
|
||||
<XR3DButton position={[0.03, -0.145, 0]} size={[0.05, 0.022]}
|
||||
label="Lado" active={false}
|
||||
onClick={() => alignToFace('LADO')} fontSize={0.0075} />
|
||||
<XR3DButton position={[0.12, -0.145, 0]} size={[0.09, 0.022]}
|
||||
label="Zerar Rot." color="#dc2626"
|
||||
onClick={resetRot} fontSize={0.0075} />
|
||||
|
||||
{measureMode && (
|
||||
<Text position={[-0.24, -0.175, 0]} fontSize={0.0055} color="#a3e635" anchorX="left" maxWidth={0.5}>
|
||||
<Text position={[-0.24, -0.17, 0]} fontSize={0.0065} color="#a3e635" anchorX="left" maxWidth={0.5}>
|
||||
Gatilho D: marcar · A: desfazer · B: limpar · Gatilho E: alternar snap
|
||||
</Text>
|
||||
)}
|
||||
@@ -645,79 +576,45 @@ function ShareTab(p: XRHudInWorldProps) {
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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' },
|
||||
];
|
||||
return (
|
||||
<group>
|
||||
<Text position={[-0.24, 0.11, 0]} fontSize={0.0085} color="#94a3b8" anchorX="left">
|
||||
Cenário Imersivo (VR / AR)
|
||||
<Text position={[-0.24, 0.115, 0]} fontSize={0.0085} color="#94a3b8" anchorX="left" maxWidth={0.48}>
|
||||
Desligue uma feature por vez para descobrir qual ativa o grid de segurança do Quest.
|
||||
</Text>
|
||||
|
||||
<XR3DButton
|
||||
position={[-0.14, 0.075, 0]}
|
||||
size={[0.13, 0.026]}
|
||||
label="Passthrough (AR)"
|
||||
active={xrEnvironment === 'passthrough'}
|
||||
onClick={() => setXrEnvironment('passthrough')}
|
||||
fontSize={0.008}
|
||||
/>
|
||||
<XR3DButton
|
||||
position={[0.0, 0.075, 0]}
|
||||
size={[0.13, 0.026]}
|
||||
label="Escritório (VR)"
|
||||
active={xrEnvironment === 'office'}
|
||||
onClick={() => {
|
||||
setXrEnvironment('office');
|
||||
setXrScaleMode('tabletop');
|
||||
}}
|
||||
fontSize={0.008}
|
||||
/>
|
||||
<XR3DButton
|
||||
position={[0.14, 0.075, 0]}
|
||||
size={[0.13, 0.026]}
|
||||
label="Campo Aberto (VR)"
|
||||
active={xrEnvironment === 'field'}
|
||||
onClick={() => {
|
||||
setXrEnvironment('field');
|
||||
setXrScaleMode('realscale');
|
||||
}}
|
||||
fontSize={0.008}
|
||||
/>
|
||||
|
||||
<Text position={[-0.24, 0.02, 0]} fontSize={0.0085} color="#94a3b8" anchorX="left">
|
||||
Escala de Apresentação do IFC
|
||||
</Text>
|
||||
|
||||
<XR3DButton
|
||||
position={[-0.08, -0.015, 0]}
|
||||
size={[0.18, 0.026]}
|
||||
label="Escala Real 1:1 (Caminhar)"
|
||||
active={xrScaleMode === 'realscale'}
|
||||
onClick={() => setXrScaleMode('realscale')}
|
||||
fontSize={0.0085}
|
||||
/>
|
||||
<XR3DButton
|
||||
position={[0.11, -0.015, 0]}
|
||||
size={[0.18, 0.026]}
|
||||
label="Modo Maquete (Mesa)"
|
||||
active={xrScaleMode === 'tabletop'}
|
||||
onClick={() => setXrScaleMode('tabletop')}
|
||||
fontSize={0.0085}
|
||||
/>
|
||||
|
||||
<Text position={[-0.24, -0.065, 0]} fontSize={0.007} color="#94a3b8" anchorX="left" maxWidth={0.48}>
|
||||
• Passthrough: veja o mundo real à sua volta (Realidade Aumentada).
|
||||
</Text>
|
||||
<Text position={[-0.24, -0.085, 0]} fontSize={0.007} color="#94a3b8" anchorX="left" maxWidth={0.48}>
|
||||
• Escritório: maquete interativa reduzida em cima de uma mesa virtual de reunião.
|
||||
</Text>
|
||||
<Text position={[-0.24, -0.105, 0]} fontSize={0.007} color="#94a3b8" anchorX="left" maxWidth={0.48}>
|
||||
• Campo Aberto: terreno gramado em escala real para explorar estruturas e galpões.
|
||||
<Text position={[-0.24, 0.095, 0]} fontSize={0.0075} color="#f59e0b" anchorX="left" maxWidth={0.48}>
|
||||
Mudanças entram em vigor ao sair e re-entrar no AR.
|
||||
</Text>
|
||||
{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 (
|
||||
<XR3DButton key={it.k}
|
||||
position={[x, y, 0]} size={[0.125, 0.034]}
|
||||
label={`${it.label} ${on ? 'ON' : 'OFF'}`}
|
||||
active={on} onClick={() => setXRFeature(it.k, !on)}
|
||||
fontSize={0.0085} />
|
||||
);
|
||||
})}
|
||||
<XR3DButton position={[0, -0.14, 0]} size={[0.18, 0.028]}
|
||||
label="↺ Resetar (todas ON)" onClick={reset} fontSize={0.009} />
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user