Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
import { useRef, useState, useEffect } from 'react';
|
import { useRef, useState, forwardRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
|
||||||
import { useFrame, useThree } from '@react-three/fiber';
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
import { useXRInputSourceState } from '@react-three/xr';
|
import { useXRInputSourceState } from '@react-three/xr';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { XR3DButton, XR3DPanel } from './XRPanel3D';
|
|
||||||
import { Text } from '@react-three/drei';
|
import { Text } from '@react-three/drei';
|
||||||
|
import { XR3DButton, XR3DPanel } from './XRPanel3D';
|
||||||
import { useModelStore, SCALE_PRESETS } from '@/stores/useModelStore';
|
import { useModelStore, SCALE_PRESETS } from '@/stores/useModelStore';
|
||||||
import { getBroadcastSource } from '@/lib/webrtc/broadcastSource';
|
import { getBroadcastSource } from '@/lib/webrtc/broadcastSource';
|
||||||
|
|
||||||
@@ -29,11 +28,26 @@ const POS_STEP = 0.001;
|
|||||||
const ROT_STEP = 1;
|
const ROT_STEP = 1;
|
||||||
const SCALE_LABELS = ['1:50', '1:20', '1:10', '1:1'];
|
const SCALE_LABELS = ['1:50', '1:20', '1:10', '1:1'];
|
||||||
|
|
||||||
/**
|
const WristToggle = forwardRef<THREE.Group, { open: boolean; onToggle: () => void }>(
|
||||||
* In-world AR HUD: a floating panel anchored in front of the user with all
|
function WristToggle({ open, onToggle }, ref) {
|
||||||
* tools, plus a tiny "wrist toggle" attached to the left controller. Works
|
return (
|
||||||
* inside the WebXR passthrough, where DOM overlays are invisible.
|
<group ref={ref}>
|
||||||
*/
|
<mesh position={[0, 0, -0.0005]}>
|
||||||
|
<planeGeometry args={[0.064, 0.032]} />
|
||||||
|
<meshBasicMaterial color="#3b82f6" transparent opacity={0.6} />
|
||||||
|
</mesh>
|
||||||
|
<mesh onClick={(e) => { e.stopPropagation(); onToggle(); }}>
|
||||||
|
<planeGeometry args={[0.06, 0.028]} />
|
||||||
|
<meshBasicMaterial color={open ? '#3b82f6' : '#1a1a1a'} transparent opacity={0.95} />
|
||||||
|
</mesh>
|
||||||
|
<Text position={[0, 0, 0.001]} fontSize={0.0085} color="#ffffff" anchorX="center" anchorY="middle">
|
||||||
|
{open ? '✕ Fechar' : '☰ Menu'}
|
||||||
|
</Text>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export function XRHudInWorld(props: XRHudInWorldProps) {
|
export function XRHudInWorld(props: XRHudInWorldProps) {
|
||||||
const { camera } = useThree();
|
const { camera } = useThree();
|
||||||
const leftCtrl = useXRInputSourceState('controller', 'left');
|
const leftCtrl = useXRInputSourceState('controller', 'left');
|
||||||
@@ -44,10 +58,10 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
|
|||||||
const wristRef = useRef<THREE.Group>(null);
|
const wristRef = useRef<THREE.Group>(null);
|
||||||
const targetPos = useRef(new THREE.Vector3());
|
const targetPos = useRef(new THREE.Vector3());
|
||||||
const targetQuat = useRef(new THREE.Quaternion());
|
const targetQuat = useRef(new THREE.Quaternion());
|
||||||
|
|
||||||
// Toggle panel via left A button (buttons[4] on Quest Touch)
|
|
||||||
const lastABtn = useRef(false);
|
const lastABtn = useRef(false);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
|
// Toggle via left A button (Quest Touch buttons[4])
|
||||||
const gp = leftCtrl?.inputSource?.gamepad;
|
const gp = leftCtrl?.inputSource?.gamepad;
|
||||||
if (gp) {
|
if (gp) {
|
||||||
const aBtn = gp.buttons[4];
|
const aBtn = gp.buttons[4];
|
||||||
@@ -56,18 +70,17 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
|
|||||||
lastABtn.current = pressed;
|
lastABtn.current = pressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position the floating panel ~70cm in front of the camera, with damping.
|
// Floating panel ~70cm in front of head, yaw-locked, damped
|
||||||
if (panelRef.current && open) {
|
if (panelRef.current && open) {
|
||||||
const fwd = new THREE.Vector3(0, -0.05, -0.7).applyQuaternion(camera.quaternion);
|
const fwd = new THREE.Vector3(0, -0.05, -0.7).applyQuaternion(camera.quaternion);
|
||||||
targetPos.current.copy(camera.position).add(fwd);
|
targetPos.current.copy(camera.position).add(fwd);
|
||||||
// Face camera (yaw only, keep panel upright)
|
|
||||||
const euler = new THREE.Euler().setFromQuaternion(camera.quaternion, 'YXZ');
|
const euler = new THREE.Euler().setFromQuaternion(camera.quaternion, 'YXZ');
|
||||||
targetQuat.current.setFromEuler(new THREE.Euler(0, euler.y, 0, 'YXZ'));
|
targetQuat.current.setFromEuler(new THREE.Euler(0, euler.y, 0, 'YXZ'));
|
||||||
panelRef.current.position.lerp(targetPos.current, 0.12);
|
panelRef.current.position.lerp(targetPos.current, 0.12);
|
||||||
panelRef.current.quaternion.slerp(targetQuat.current, 0.18);
|
panelRef.current.quaternion.slerp(targetQuat.current, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position wrist toggle above the left controller
|
// Wrist toggle follows left controller
|
||||||
if (wristRef.current) {
|
if (wristRef.current) {
|
||||||
const ctrlObj = leftCtrl?.object;
|
const ctrlObj = leftCtrl?.object;
|
||||||
if (ctrlObj) {
|
if (ctrlObj) {
|
||||||
@@ -86,7 +99,7 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WristToggleReal ref={wristRef} open={open} onToggle={() => setOpen((v) => !v)} />
|
<WristToggle ref={wristRef} open={open} onToggle={() => setOpen((v) => !v)} />
|
||||||
{open && (
|
{open && (
|
||||||
<group ref={panelRef}>
|
<group ref={panelRef}>
|
||||||
<FloatingPanel tab={tab} setTab={setTab} {...props} />
|
<FloatingPanel tab={tab} setTab={setTab} {...props} />
|
||||||
@@ -96,43 +109,17 @@ export function XRHudInWorld(props: XRHudInWorldProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const WristToggleReal = forwardRef<THREE.Group, { open: boolean; onToggle: () => void }>(
|
|
||||||
function WristToggleReal({ open, onToggle }, ref) {
|
|
||||||
return (
|
|
||||||
<group ref={ref}>
|
|
||||||
<mesh
|
|
||||||
onClick={(e) => { e.stopPropagation(); onToggle(); }}
|
|
||||||
onPointerOver={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<planeGeometry args={[0.06, 0.028]} />
|
|
||||||
<meshBasicMaterial color={open ? '#3b82f6' : '#1a1a1a'} transparent opacity={0.95} />
|
|
||||||
</mesh>
|
|
||||||
<mesh position={[0, 0, -0.0005]}>
|
|
||||||
<planeGeometry args={[0.064, 0.032]} />
|
|
||||||
<meshBasicMaterial color="#3b82f6" transparent opacity={0.6} />
|
|
||||||
</mesh>
|
|
||||||
<Text position={[0, 0, 0.001]} fontSize={0.0085} color="#ffffff" anchorX="center" anchorY="middle">
|
|
||||||
{open ? '✕ Fechar' : '☰ Menu'}
|
|
||||||
</Text>
|
|
||||||
</group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
function FloatingPanel({ tab, setTab, ...p }: { tab: Tab; setTab: (t: Tab) => void } & XRHudInWorldProps) {
|
function FloatingPanel({ tab, setTab, ...p }: { tab: Tab; setTab: (t: Tab) => void } & XRHudInWorldProps) {
|
||||||
const tabs: { id: Tab; label: string; icon: string }[] = [
|
const tabs: { id: Tab; label: string; icon: string }[] = [
|
||||||
{ id: 'scene', label: 'Cena', icon: '🧩' },
|
{ id: 'scene', label: 'Cena', icon: '🧩' },
|
||||||
{ id: 'tools', label: 'Ferram.', icon: '🛠' },
|
{ id: 'tools', label: 'Ferram.', icon: '🛠' },
|
||||||
{ id: 'transform', label: 'Mover', icon: '↔' },
|
{ id: 'transform', label: 'Mover', icon: '↔' },
|
||||||
{ id: 'inspection', label: 'Inspeção', icon: '✓' },
|
{ id: 'inspection', label: 'Inspeção', icon: '✓' },
|
||||||
{ id: 'share', label: 'Compart.', icon: '📡' },
|
{ id: 'share', label: 'Compart.', icon: '📡' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const W = 0.5, H = 0.36;
|
const W = 0.5, H = 0.36;
|
||||||
return (
|
return (
|
||||||
<XR3DPanel size={[W, H]}>
|
<XR3DPanel size={[W, H]}>
|
||||||
{/* Title */}
|
|
||||||
<Text position={[-W / 2 + 0.012, H / 2 - 0.018, 0.002]} fontSize={0.011} color="#3b82f6"
|
<Text position={[-W / 2 + 0.012, H / 2 - 0.018, 0.002]} fontSize={0.011} color="#3b82f6"
|
||||||
anchorX="left" anchorY="middle">
|
anchorX="left" anchorY="middle">
|
||||||
TrackSteelXR · HUD AR
|
TrackSteelXR · HUD AR
|
||||||
@@ -142,23 +129,15 @@ function FloatingPanel({ tab, setTab, ...p }: { tab: Tab; setTab: (t: Tab) => vo
|
|||||||
Botão A (esq) abre/fecha
|
Botão A (esq) abre/fecha
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{/* Tabs row */}
|
|
||||||
<group position={[0, H / 2 - 0.045, 0.001]}>
|
<group position={[0, H / 2 - 0.045, 0.001]}>
|
||||||
{tabs.map((t, i) => (
|
{tabs.map((t, i) => (
|
||||||
<XR3DButton
|
<XR3DButton key={t.id}
|
||||||
key={t.id}
|
position={[-W / 2 + 0.045 + i * 0.092, 0, 0]} size={[0.085, 0.022]}
|
||||||
position={[-W / 2 + 0.045 + i * 0.092, 0, 0]}
|
label={t.label} icon={t.icon} active={tab === t.id}
|
||||||
size={[0.085, 0.022]}
|
onClick={() => setTab(t.id)} fontSize={0.0075} />
|
||||||
label={t.label}
|
|
||||||
icon={t.icon}
|
|
||||||
active={tab === t.id}
|
|
||||||
onClick={() => setTab(t.id)}
|
|
||||||
fontSize={0.0075}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
{/* Content area */}
|
|
||||||
<group position={[0, -0.012, 0.001]}>
|
<group position={[0, -0.012, 0.001]}>
|
||||||
{tab === 'scene' && <SceneTab />}
|
{tab === 'scene' && <SceneTab />}
|
||||||
{tab === 'tools' && <ToolsTab {...p} />}
|
{tab === 'tools' && <ToolsTab {...p} />}
|
||||||
@@ -181,8 +160,8 @@ function SceneTab() {
|
|||||||
|
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Text fontSize={0.01} color="#94a3b8" anchorX="center" anchorY="middle">
|
<Text fontSize={0.011} color="#94a3b8" anchorX="center" anchorY="middle" maxWidth={0.45}>
|
||||||
Nenhuma peça na cena. Saia do AR para carregar arquivos.
|
Nenhuma peça na cena. Saia do AR para carregar arquivos no Viewer.
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -190,19 +169,17 @@ function SceneTab() {
|
|||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
<Text position={[-0.24, 0.11, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
<Text position={[-0.24, 0.11, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||||
Toque para selecionar peça ativa
|
Toque no nome para tornar a peça ativa
|
||||||
</Text>
|
</Text>
|
||||||
{models.slice(0, 5).map((m, i) => {
|
{models.slice(0, 5).map((m, i) => {
|
||||||
const y = 0.085 - i * 0.04;
|
const y = 0.085 - i * 0.04;
|
||||||
const isActive = m.id === activeId;
|
const isActive = m.id === activeId;
|
||||||
return (
|
return (
|
||||||
<group key={m.id} position={[0, y, 0]}>
|
<group key={m.id} position={[0, y, 0]}>
|
||||||
{/* Color dot */}
|
|
||||||
<mesh position={[-0.235, 0, 0.001]}>
|
<mesh position={[-0.235, 0, 0.001]}>
|
||||||
<circleGeometry args={[0.005, 16]} />
|
<circleGeometry args={[0.005, 16]} />
|
||||||
<meshBasicMaterial color={m.color} />
|
<meshBasicMaterial color={m.color} />
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Name (tappable to make active) */}
|
|
||||||
<group onClick={(e) => { e.stopPropagation(); setActive(m.id); }}>
|
<group onClick={(e) => { e.stopPropagation(); setActive(m.id); }}>
|
||||||
<mesh position={[-0.11, 0, 0]}>
|
<mesh position={[-0.11, 0, 0]}>
|
||||||
<planeGeometry args={[0.22, 0.026]} />
|
<planeGeometry args={[0.22, 0.026]} />
|
||||||
@@ -214,7 +191,6 @@ function SceneTab() {
|
|||||||
{m.fileName}
|
{m.fileName}
|
||||||
</Text>
|
</Text>
|
||||||
</group>
|
</group>
|
||||||
{/* Action buttons */}
|
|
||||||
<XR3DButton position={[0.04, 0, 0]} size={[0.04, 0.024]}
|
<XR3DButton position={[0.04, 0, 0]} size={[0.04, 0.024]}
|
||||||
label={m.visible ? '👁' : '⊘'} fontSize={0.012}
|
label={m.visible ? '👁' : '⊘'} fontSize={0.012}
|
||||||
onClick={() => toggleVisible(m.id)} />
|
onClick={() => toggleVisible(m.id)} />
|
||||||
@@ -248,29 +224,27 @@ function ToolsTab(p: XRHudInWorldProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
{/* Render mode */}
|
|
||||||
<Text position={[-0.24, 0.11, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Render</Text>
|
<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"
|
<XR3DButton position={[-0.16, 0.085, 0]} size={[0.075, 0.022]} label="Sólido"
|
||||||
active={renderMode === 'solid'} onClick={() => setRenderMode('solid')} />
|
active={renderMode === 'solid'} onClick={() => setRenderMode('solid')} />
|
||||||
<XR3DButton position={[-0.08, 0.085, 0]} size={[0.075, 0.022]} label="Wire"
|
<XR3DButton position={[-0.08, 0.085, 0]} size={[0.075, 0.022]} label="Wire"
|
||||||
active={renderMode === 'wireframe'} onClick={() => setRenderMode('wireframe')} />
|
active={renderMode === 'wireframe'} onClick={() => setRenderMode('wireframe')} />
|
||||||
<XR3DButton position={[0.0, 0.085, 0]} size={[0.075, 0.022]} label="Bordas"
|
<XR3DButton position={[0.0, 0.085, 0]} size={[0.075, 0.022]} label="Bordas"
|
||||||
active={renderMode === 'edges'} onClick={() => setRenderMode('edges')} />
|
active={renderMode === 'edges'} onClick={() => setRenderMode('edges')} />
|
||||||
|
|
||||||
{/* Toggles */}
|
|
||||||
<Text position={[-0.24, 0.05, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">Toggles</Text>
|
<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'}`}
|
<XR3DButton position={[-0.16, 0.025, 0]} size={[0.075, 0.022]} label={`Grid ${showGrid ? 'ON' : 'OFF'}`}
|
||||||
active={showGrid} onClick={() => setShowGrid(!showGrid)} />
|
active={showGrid} onClick={() => setShowGrid(!showGrid)} />
|
||||||
<XR3DButton position={[-0.08, 0.025, 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)} />
|
active={measureMode} onClick={() => setMeasureMode(!measureMode)} />
|
||||||
<XR3DButton position={[0.0, 0.025, 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} />
|
active={p.snapToPlanes} onClick={p.onToggleSnap} />
|
||||||
<XR3DButton position={[0.08, 0.025, 0]} size={[0.075, 0.022]} label={`Escala ${p.allowScale ? 'ON' : 'OFF'}`}
|
<XR3DButton position={[0.08, 0.025, 0]} size={[0.075, 0.022]} label={`Escala ${p.allowScale ? 'ON' : 'OFF'}`}
|
||||||
active={p.allowScale} onClick={p.onToggleAllowScale} />
|
active={p.allowScale} onClick={p.onToggleAllowScale} />
|
||||||
<XR3DButton position={[0.16, 0.025, 0]} size={[0.075, 0.022]} label={p.placementMode ? 'Posic…' : 'Reposic.'}
|
<XR3DButton position={[0.16, 0.025, 0]} size={[0.075, 0.022]}
|
||||||
|
label={p.placementMode ? 'Posic…' : 'Reposic.'}
|
||||||
active={p.placementMode} onClick={p.onTogglePlacement} />
|
active={p.placementMode} onClick={p.onTogglePlacement} />
|
||||||
|
|
||||||
{/* Opacity slider buttons */}
|
|
||||||
<Text position={[-0.24, -0.02, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
<Text position={[-0.24, -0.02, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||||
Opacidade {Math.round(opacity * 100)}%
|
Opacidade {Math.round(opacity * 100)}%
|
||||||
</Text>
|
</Text>
|
||||||
@@ -285,12 +259,11 @@ function ToolsTab(p: XRHudInWorldProps) {
|
|||||||
<XR3DButton position={[0.06, -0.045, 0]} size={[0.05, 0.022]} label="100%"
|
<XR3DButton position={[0.06, -0.045, 0]} size={[0.05, 0.022]} label="100%"
|
||||||
onClick={() => setOpacity(1)} />
|
onClick={() => setOpacity(1)} />
|
||||||
|
|
||||||
{/* Scale presets */}
|
|
||||||
<Text position={[-0.24, -0.085, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
<Text position={[-0.24, -0.085, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||||
Escala {scaleRatio.label}
|
Escala {scaleRatio.label}
|
||||||
</Text>
|
</Text>
|
||||||
{SCALE_LABELS.map((lbl, i) => {
|
{SCALE_LABELS.map((lbl, i) => {
|
||||||
const preset = SCALE_PRESETS.find(p => p.label === lbl)!;
|
const preset = SCALE_PRESETS.find((sp) => sp.label === lbl)!;
|
||||||
return (
|
return (
|
||||||
<XR3DButton key={lbl}
|
<XR3DButton key={lbl}
|
||||||
position={[-0.16 + i * 0.06, -0.11, 0]} size={[0.055, 0.022]} label={lbl}
|
position={[-0.16 + i * 0.06, -0.11, 0]} size={[0.055, 0.022]} label={lbl}
|
||||||
@@ -307,7 +280,9 @@ function TransformTab() {
|
|||||||
const setFineTuning = useModelStore((s) => s.setFineTuning);
|
const setFineTuning = useModelStore((s) => s.setFineTuning);
|
||||||
const reset = useModelStore((s) => s.resetFineTuning);
|
const reset = useModelStore((s) => s.resetFineTuning);
|
||||||
|
|
||||||
const Row = ({ label, axis, isRot, y }: { label: string; axis: 'posX'|'posY'|'posZ'|'rotX'|'rotY'|'rotZ'; isRot: boolean; y: number }) => {
|
const Row = ({
|
||||||
|
label, axis, isRot, y,
|
||||||
|
}: { label: string; axis: 'posX'|'posY'|'posZ'|'rotX'|'rotY'|'rotZ'; isRot: boolean; y: number }) => {
|
||||||
const step = isRot ? ROT_STEP : POS_STEP;
|
const step = isRot ? ROT_STEP : POS_STEP;
|
||||||
const val = fineTuning[axis];
|
const val = fineTuning[axis];
|
||||||
const display = isRot ? `${val.toFixed(1)}°` : `${(val * 1000).toFixed(1)}mm`;
|
const display = isRot ? `${val.toFixed(1)}°` : `${(val * 1000).toFixed(1)}mm`;
|
||||||
@@ -317,16 +292,16 @@ function TransformTab() {
|
|||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
<XR3DButton position={[-0.10, 0, 0]} size={[0.04, 0.024]} label="−−"
|
<XR3DButton position={[-0.10, 0, 0]} size={[0.04, 0.024]} label="−−"
|
||||||
onClick={() => setFineTuning({ [axis]: val - step * 10 } as Partial<typeof fineTuning>)} />
|
onClick={() => setFineTuning({ [axis]: val - step * 10 })} />
|
||||||
<XR3DButton position={[-0.05, 0, 0]} size={[0.04, 0.024]} label="−"
|
<XR3DButton position={[-0.05, 0, 0]} size={[0.04, 0.024]} label="−"
|
||||||
onClick={() => setFineTuning({ [axis]: val - step } as Partial<typeof fineTuning>)} />
|
onClick={() => setFineTuning({ [axis]: val - step })} />
|
||||||
<Text position={[0.0, 0, 0]} fontSize={0.009} color="#3b82f6" anchorX="center" anchorY="middle">
|
<Text position={[0.0, 0, 0]} fontSize={0.009} color="#3b82f6" anchorX="center" anchorY="middle">
|
||||||
{display}
|
{display}
|
||||||
</Text>
|
</Text>
|
||||||
<XR3DButton position={[0.05, 0, 0]} size={[0.04, 0.024]} label="+"
|
<XR3DButton position={[0.05, 0, 0]} size={[0.04, 0.024]} label="+"
|
||||||
onClick={() => setFineTuning({ [axis]: val + step } as Partial<typeof fineTuning>)} />
|
onClick={() => setFineTuning({ [axis]: val + step })} />
|
||||||
<XR3DButton position={[0.10, 0, 0]} size={[0.04, 0.024]} label="++"
|
<XR3DButton position={[0.10, 0, 0]} size={[0.04, 0.024]} label="++"
|
||||||
onClick={() => setFineTuning({ [axis]: val + step * 10 } as Partial<typeof fineTuning>)} />
|
onClick={() => setFineTuning({ [axis]: val + step * 10 })} />
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -336,11 +311,10 @@ function TransformTab() {
|
|||||||
<Row label="Pos X" axis="posX" isRot={false} y={0.105} />
|
<Row label="Pos X" axis="posX" isRot={false} y={0.105} />
|
||||||
<Row label="Pos Y" axis="posY" isRot={false} y={0.075} />
|
<Row label="Pos Y" axis="posY" isRot={false} y={0.075} />
|
||||||
<Row label="Pos Z" axis="posZ" isRot={false} y={0.045} />
|
<Row label="Pos Z" axis="posZ" isRot={false} y={0.045} />
|
||||||
<Row label="Rot X" axis="rotX" isRot={true} y={0.005} />
|
<Row label="Rot X" axis="rotX" isRot={true} y={0.005} />
|
||||||
<Row label="Rot Y" axis="rotY" isRot={true} y={-0.025} />
|
<Row label="Rot Y" axis="rotY" isRot={true} y={-0.025} />
|
||||||
<Row label="Rot Z" axis="rotZ" isRot={true} y={-0.055} />
|
<Row label="Rot Z" axis="rotZ" isRot={true} y={-0.055} />
|
||||||
<XR3DButton position={[0, -0.105, 0]} size={[0.12, 0.026]} label="↺ Reset"
|
<XR3DButton position={[0, -0.105, 0]} size={[0.12, 0.026]} label="↺ Reset" onClick={reset} />
|
||||||
onClick={reset} />
|
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -348,7 +322,6 @@ function TransformTab() {
|
|||||||
function InspectionTab() {
|
function InspectionTab() {
|
||||||
const checklist = useModelStore((s) => s.checklist);
|
const checklist = useModelStore((s) => s.checklist);
|
||||||
const setStatus = useModelStore((s) => s.setChecklistItemStatus);
|
const setStatus = useModelStore((s) => s.setChecklistItemStatus);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
<Text position={[-0.24, 0.11, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
<Text position={[-0.24, 0.11, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||||
@@ -356,7 +329,8 @@ function InspectionTab() {
|
|||||||
</Text>
|
</Text>
|
||||||
{checklist.map((item, i) => {
|
{checklist.map((item, i) => {
|
||||||
const y = 0.075 - i * 0.045;
|
const y = 0.075 - i * 0.045;
|
||||||
const color = item.status === 'approved' ? '#22c55e' : item.status === 'rejected' ? '#dc2626' : '#94a3b8';
|
const color = item.status === 'approved' ? '#22c55e'
|
||||||
|
: item.status === 'rejected' ? '#dc2626' : '#94a3b8';
|
||||||
return (
|
return (
|
||||||
<group key={item.id} position={[0, y, 0]}>
|
<group key={item.id} position={[0, y, 0]}>
|
||||||
<mesh position={[-0.235, 0, 0]}>
|
<mesh position={[-0.235, 0, 0]}>
|
||||||
@@ -367,14 +341,10 @@ function InspectionTab() {
|
|||||||
anchorX="left" anchorY="middle" maxWidth={0.18}>
|
anchorX="left" anchorY="middle" maxWidth={0.18}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</Text>
|
</Text>
|
||||||
<XR3DButton position={[0.06, 0, 0]} size={[0.05, 0.024]}
|
<XR3DButton position={[0.06, 0, 0]} size={[0.05, 0.024]} label="✓ OK" color="#16a34a"
|
||||||
label="✓ OK" color="#16a34a"
|
active={item.status === 'approved'} onClick={() => setStatus(item.id, 'approved')} />
|
||||||
active={item.status === 'approved'}
|
<XR3DButton position={[0.115, 0, 0]} size={[0.05, 0.024]} label="✗ NOK" color="#dc2626"
|
||||||
onClick={() => setStatus(item.id, 'approved')} />
|
active={item.status === 'rejected'} onClick={() => setStatus(item.id, 'rejected')} />
|
||||||
<XR3DButton position={[0.115, 0, 0]} size={[0.05, 0.024]}
|
|
||||||
label="✗ NOK" color="#dc2626"
|
|
||||||
active={item.status === 'rejected'}
|
|
||||||
onClick={() => setStatus(item.id, 'rejected')} />
|
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -390,7 +360,7 @@ function ShareTab(p: XRHudInWorldProps) {
|
|||||||
anchorX="center" anchorY="middle">
|
anchorX="center" anchorY="middle">
|
||||||
{isLive ? '● TRANSMITINDO AO VIVO' : '○ Transmissão desligada'}
|
{isLive ? '● TRANSMITINDO AO VIVO' : '○ Transmissão desligada'}
|
||||||
</Text>
|
</Text>
|
||||||
{isLive && (
|
{isLive ? (
|
||||||
<>
|
<>
|
||||||
<Text position={[0, 0.07, 0]} fontSize={0.010} color="#94a3b8" anchorX="center" anchorY="middle">
|
<Text position={[0, 0.07, 0]} fontSize={0.010} color="#94a3b8" anchorX="center" anchorY="middle">
|
||||||
Código da sala
|
Código da sala
|
||||||
@@ -409,8 +379,7 @@ function ShareTab(p: XRHudInWorldProps) {
|
|||||||
label="✕ Encerrar transmissão" color="#dc2626"
|
label="✕ Encerrar transmissão" color="#dc2626"
|
||||||
onClick={() => p.onStopLive?.()} />
|
onClick={() => p.onStopLive?.()} />
|
||||||
</>
|
</>
|
||||||
)}
|
) : (
|
||||||
{!isLive && (
|
|
||||||
<>
|
<>
|
||||||
<Text position={[0, 0.04, 0]} fontSize={0.010} color="#cbd5e1" anchorX="center" anchorY="middle"
|
<Text position={[0, 0.04, 0]} fontSize={0.010} color="#cbd5e1" anchorX="center" anchorY="middle"
|
||||||
maxWidth={0.45}>
|
maxWidth={0.45}>
|
||||||
|
|||||||
Reference in New Issue
Block a user