🚀 Auto-deploy: melhoria no snap e medição AR em 24/05/2026 01:33:36
This commit is contained in:
@@ -9,6 +9,30 @@ import { registerModelLocalGroup, unregisterModelLocalGroup } from '@/lib/modelT
|
||||
import { parseIFCtoThree } from '@/lib/convertIFC';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||
|
||||
// WebXR imersivo
|
||||
import { XR, useXR, XROrigin } from '@react-three/xr';
|
||||
import { xrStore } from '@/lib/xrStore';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
XRBackgroundModels,
|
||||
XRActiveModel,
|
||||
XRMeasurementOverlay,
|
||||
XRSnapHandler,
|
||||
XRGrid,
|
||||
XRGridAutoFollower,
|
||||
} from './XRSceneComponents';
|
||||
import { XRGrabbable } from './XRGrabbable';
|
||||
import { XRHitTestPlacement } from './XRHitTestPlacement';
|
||||
import { XRControllerMeasure } from './XRControllerMeasure';
|
||||
import { ControllerLocomotion } from './ControllerLocomotion';
|
||||
import { XRHudInWorld } from './XRHudInWorld';
|
||||
import { XRBroadcastMirror } from './XRBroadcastMirror';
|
||||
import { XRHud } from '@/components/XRHud';
|
||||
import { ShareButton } from '@/components/ShareButton';
|
||||
import { useDevKit } from '@/devkit/useDevKit';
|
||||
import { DevPanel } from '@/devkit/DevPanel';
|
||||
import { FakeControllers } from '@/devkit/FakeControllers';
|
||||
|
||||
interface ModelViewerProps {
|
||||
url?: string; // legacy, ignored — uses store.models
|
||||
}
|
||||
@@ -1006,8 +1030,8 @@ function PositionDragHandler() {
|
||||
const ft = active.fineTuning;
|
||||
const factor = st.scaleRatio?.factor ?? 1;
|
||||
|
||||
if (button === 2) {
|
||||
// Right button: rotate
|
||||
if (button === 0) {
|
||||
// Left button (Trigger in Quest emulated mouse): rotate (unifies with AR / VR standard)
|
||||
const sens = 0.4; // deg per pixel
|
||||
if (shiftKey) {
|
||||
useModelStore.getState().setFineTuning({ rotZ: ft.rotZ + dx * sens });
|
||||
@@ -1034,8 +1058,8 @@ function PositionDragHandler() {
|
||||
} else {
|
||||
useModelStore.getState().setFineTuning({ rotZ: ft.rotZ + delta });
|
||||
}
|
||||
} else if (button === 0 && shiftKey) {
|
||||
// Shift+left: depth (Z in camera space)
|
||||
} else if (button === 2 && shiftKey) {
|
||||
// Shift+Right button: depth (Z in camera space)
|
||||
const worldDelta = dy * pixelsPerWorldUnit;
|
||||
const camDir = new THREE.Vector3();
|
||||
camera.getWorldDirection(camDir);
|
||||
@@ -1046,7 +1070,7 @@ function PositionDragHandler() {
|
||||
posZ: ft.posZ + move.z,
|
||||
});
|
||||
} else {
|
||||
// Left: translate in camera plane
|
||||
// Right button (Grip in Quest emulated mouse): translate in camera plane (matches AR pan-only grip)
|
||||
const right = new THREE.Vector3();
|
||||
const up = new THREE.Vector3();
|
||||
camera.matrixWorld.extractBasis(right, up, new THREE.Vector3());
|
||||
@@ -1088,10 +1112,193 @@ function PositionDragHandler() {
|
||||
|
||||
|
||||
|
||||
export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
function SceneInsideXR() {
|
||||
const isPresenting = useXR((state) => state.isPresenting);
|
||||
const positionMode = useModelStore((s) => s.positionMode);
|
||||
const measureMode = useModelStore((s) => s.measureMode);
|
||||
const selectionMode = useModelStore((s) => s.selectionMode);
|
||||
const activeId = useModelStore((s) => s.activeModelId);
|
||||
const models = useModelStore((s) => s.models);
|
||||
const activeModel = models.find(m => m.id === activeId);
|
||||
const isActiveLocked = !!activeModel?.locked;
|
||||
|
||||
// Estados locais para a sessão de XR
|
||||
const [freeMove, setFreeMove] = useState(true);
|
||||
const [placementMode, setPlacementMode] = useState(true);
|
||||
const [snapToPlanes, setSnapToPlanes] = useState(true);
|
||||
const [allowScale, setAllowScale] = useState(true);
|
||||
const [liveCode, setLiveCode] = useState<string | null>(null);
|
||||
const [liveViewers, setLiveViewers] = useState(0);
|
||||
|
||||
const rigRef = useRef<THREE.Group>(null);
|
||||
|
||||
// DEVKIT: simXR forces in-XR rendering on desktop without a real WebXR session
|
||||
const devkit = useDevKit();
|
||||
const [simXR, setSimXR] = useState(false);
|
||||
const effectiveInXR = isPresenting || simXR;
|
||||
|
||||
// Monitorar início e fim da sessão
|
||||
useEffect(() => {
|
||||
if (isPresenting) {
|
||||
toast.success('Sessão AR iniciada!');
|
||||
}
|
||||
}, [isPresenting]);
|
||||
|
||||
if (!effectiveInXR) {
|
||||
// Modo 2D normal (não imersivo)
|
||||
return (
|
||||
<>
|
||||
<ambientLight intensity={0.6} />
|
||||
<directionalLight position={[5, 10, 5]} intensity={1} castShadow />
|
||||
<directionalLight position={[-5, 5, -5]} intensity={0.3} />
|
||||
|
||||
<Suspense fallback={<LoadingFallback />}>
|
||||
<SceneModels />
|
||||
</Suspense>
|
||||
|
||||
<MeasurementOverlay />
|
||||
<MeasureClickHandler />
|
||||
<SmartSnapHandler />
|
||||
<HoverDetector />
|
||||
|
||||
<GridLayer />
|
||||
<GridAutoFollower />
|
||||
|
||||
<PositionDragHandler />
|
||||
<SelectionHandler />
|
||||
<VisibilityApplier />
|
||||
<SceneRefCapture />
|
||||
|
||||
<OrbitControls
|
||||
makeDefault
|
||||
enableDamping
|
||||
dampingFactor={0.1}
|
||||
minDistance={0.05}
|
||||
maxDistance={50}
|
||||
enabled={!positionMode}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Modo AR imersivo (ou SimXR)
|
||||
return (
|
||||
<>
|
||||
<ambientLight intensity={0.8} />
|
||||
<directionalLight position={[3, 5, 3]} intensity={1.2} />
|
||||
<directionalLight position={[-3, 3, -3]} intensity={0.4} />
|
||||
|
||||
<group ref={rigRef}>
|
||||
<XROrigin />
|
||||
</group>
|
||||
|
||||
{simXR ? (
|
||||
<group position={[0, 1.0, -1.2]}>
|
||||
<XRBackgroundModels />
|
||||
<XRGrabbable
|
||||
allowScale={allowScale}
|
||||
lockedActive={isActiveLocked}
|
||||
onGrabStart={() => { if (placementMode) setPlacementMode(false); }}
|
||||
>
|
||||
<XRActiveModel />
|
||||
</XRGrabbable>
|
||||
</group>
|
||||
) : (
|
||||
<XRHitTestPlacement
|
||||
placementMode={placementMode}
|
||||
snapToPlanes={snapToPlanes}
|
||||
onPlace={() => {
|
||||
setPlacementMode(false);
|
||||
toast.success('Modelo posicionado na superfície!');
|
||||
}}
|
||||
>
|
||||
<XRBackgroundModels />
|
||||
<XRGrabbable
|
||||
allowScale={allowScale}
|
||||
lockedActive={isActiveLocked}
|
||||
onGrabStart={() => { if (placementMode) setPlacementMode(false); }}
|
||||
>
|
||||
<XRActiveModel />
|
||||
</XRGrabbable>
|
||||
</XRHitTestPlacement>
|
||||
)}
|
||||
|
||||
<XRMeasurementOverlay />
|
||||
<XRControllerMeasure />
|
||||
<ControllerLocomotion rigRef={rigRef} />
|
||||
<VisibilityApplier />
|
||||
<SceneRefCapture />
|
||||
|
||||
<XRHudInWorld
|
||||
freeMove={freeMove}
|
||||
onToggleFreeMove={() => setFreeMove(!freeMove)}
|
||||
placementMode={placementMode}
|
||||
onTogglePlacement={() => setPlacementMode(!placementMode)}
|
||||
snapToPlanes={snapToPlanes}
|
||||
onToggleSnap={() => setSnapToPlanes(!snapToPlanes)}
|
||||
allowScale={allowScale}
|
||||
onToggleAllowScale={() => setAllowScale(!allowScale)}
|
||||
liveCode={liveCode}
|
||||
liveViewers={liveViewers}
|
||||
onStartLive={() => (window as unknown as { __trackSteelStartLive?: () => void }).__trackSteelStartLive?.()}
|
||||
onStopLive={() => (window as unknown as { __trackSteelStopLive?: () => void }).__trackSteelStopLive?.()}
|
||||
/>
|
||||
|
||||
<XRBroadcastMirror enabled />
|
||||
|
||||
{devkit && simXR && <FakeControllers />}
|
||||
{simXR && (
|
||||
<OrbitControls
|
||||
makeDefault
|
||||
enableDamping
|
||||
dampingFactor={0.1}
|
||||
minDistance={0.05}
|
||||
maxDistance={50}
|
||||
target={[0, 1.0, -1.2]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<XRSnapHandler />
|
||||
<XRGrid />
|
||||
<XRGridAutoFollower />
|
||||
|
||||
{/* Floating HUD overlay - visível apenas fora do headset passthrough */}
|
||||
<Html fullscreen style={{ pointerEvents: 'none', display: isPresenting ? 'none' : 'contents' }}>
|
||||
<div style={{ pointerEvents: 'auto' }}>
|
||||
<XRHud
|
||||
freeMove={freeMove}
|
||||
onToggleFreeMove={() => setFreeMove(!freeMove)}
|
||||
placementMode={placementMode}
|
||||
onTogglePlacement={() => setPlacementMode(!placementMode)}
|
||||
snapToPlanes={snapToPlanes}
|
||||
onToggleSnap={() => setSnapToPlanes(!snapToPlanes)}
|
||||
allowScale={allowScale}
|
||||
onToggleAllowScale={() => setAllowScale(!allowScale)}
|
||||
/>
|
||||
</div>
|
||||
</Html>
|
||||
|
||||
{/* Compartilhar botões ocultos para WebRTC e HUD de fora */}
|
||||
<Html style={{ display: 'none' }}>
|
||||
<div>
|
||||
<ShareButton
|
||||
onHandleChange={(h) => setLiveCode(h?.code ?? null)}
|
||||
onViewerCountChange={setLiveViewers}
|
||||
/>
|
||||
</div>
|
||||
</Html>
|
||||
|
||||
{/* DevPanel se devkit habilitado */}
|
||||
{devkit && (
|
||||
<Html fullscreen style={{ pointerEvents: 'none' }}>
|
||||
<div style={{ pointerEvents: 'auto' }}>
|
||||
<DevPanel simXR={simXR} onToggleSimXR={() => setSimXR((v) => !v)} />
|
||||
</div>
|
||||
</Html>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
return (
|
||||
<Canvas
|
||||
camera={{ position: [2, 2, 2], fov: 50, near: 0.001, far: 1000 }}
|
||||
@@ -1109,36 +1316,9 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
gl.setPixelRatio(Math.min(window.devicePixelRatio, maxRatio));
|
||||
}}
|
||||
>
|
||||
<ambientLight intensity={0.6} />
|
||||
<directionalLight position={[5, 10, 5]} intensity={1} castShadow />
|
||||
<directionalLight position={[-5, 5, -5]} intensity={0.3} />
|
||||
|
||||
<Suspense fallback={<LoadingFallback />}>
|
||||
<SceneModels />
|
||||
</Suspense>
|
||||
|
||||
<MeasurementOverlay />
|
||||
<MeasureClickHandler />
|
||||
<SmartSnapHandler />
|
||||
<HoverDetector />
|
||||
|
||||
<GridLayer />
|
||||
<GridAutoFollower />
|
||||
|
||||
<PositionDragHandler />
|
||||
<SelectionHandler />
|
||||
<VisibilityApplier />
|
||||
<SceneRefCapture />
|
||||
|
||||
<OrbitControls
|
||||
makeDefault
|
||||
enableDamping
|
||||
dampingFactor={0.1}
|
||||
minDistance={0.05}
|
||||
maxDistance={50}
|
||||
enabled={!positionMode}
|
||||
/>
|
||||
|
||||
<XR store={xrStore}>
|
||||
<SceneInsideXR />
|
||||
</XR>
|
||||
</Canvas>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user