Corrigiu botões e throttle AR

X-Lovable-Edit-ID: edt-087c5038-cfa1-4ba9-93fe-3ad396e4c693
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-21 21:26:14 +00:00
3 changed files with 103 additions and 97 deletions
+2 -24
View File
@@ -1,8 +1,8 @@
import { useState } from 'react';
import {
Eye, LayoutGrid, Grid3X3, Box, Ruler, Trash2, Camera,
Move, RotateCw, RefreshCw, ChevronUp, ChevronDown, Grip,
ClipboardCheck, X, Crosshair, Magnet, HelpCircle, Maximize2,
Move, RotateCw, RefreshCw, ChevronUp, ChevronDown,
ClipboardCheck, X, Crosshair, Magnet, HelpCircle,
MousePointerClick, EyeOff, Focus,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
@@ -310,19 +310,6 @@ export function XRHud({ freeMove, onToggleFreeMove, placementMode = false, onTog
</Button>
)}
{/* Two-hand scale toggle */}
{onToggleAllowScale && (
<Button
variant={allowScale ? 'default' : 'outline'}
size="sm" className="h-8 gap-1.5 text-[10px] font-mono"
onClick={onToggleAllowScale}
title="Permite escalar a peça com gesto de duas mãos (afastando os controles)"
>
<Maximize2 className="h-3.5 w-3.5" />
{allowScale ? 'Escala ON' : 'Escala'}
</Button>
)}
{/* Scale presets */}
<ScaleSelector variant="compact" />
@@ -332,15 +319,6 @@ export function XRHud({ freeMove, onToggleFreeMove, placementMode = false, onTog
{/* Share live screen */}
<ShareButton variant="compact" />
<Button
variant={freeMove ? 'default' : 'outline'}
size="sm" className="h-8 gap-1.5 text-[10px] font-mono"
onClick={onToggleFreeMove}
>
<Grip className="h-3.5 w-3.5" />
{freeMove ? 'Mover ON' : 'Mover'}
</Button>
<Button
variant={showGrid ? 'default' : 'outline'}
size="icon" className="h-8 w-8"
+38 -8
View File
@@ -49,12 +49,21 @@ export function XRControllerMeasure() {
const dwellStart = useRef(0);
const dwellFired = useRef(false);
// Throttling: raycast + snap analysis are heavy on dense IFC models and
// running them every frame at 72fps can stall the Quest right after
// toggling "Medir" from the Ferramentas tab. We cap heavy work to ~24Hz.
// Trigger/A/B/L-trigger polling continues every frame for responsiveness.
const heavyFrame = useRef(0);
const cachedSnappedPoint = useRef<THREE.Vector3 | null>(null);
const cachedSnapKind = useRef<'vertex' | 'edge' | 'surface' | 'hole'>('surface');
useFrame((_state, _dt, frame: XRFrame | undefined) => {
const measureMode = useModelStore.getState().measureMode;
const selectionMode = useModelStore.getState().selectionMode;
if (laserRef.current) laserRef.current.visible = false;
if (tipRef.current) tipRef.current.visible = false;
if ((!measureMode && !selectionMode) || !frame) return;
const doHeavy = (heavyFrame.current++ % 3) === 0;
const session = frame.session;
const refSpace = gl.xr.getReferenceSpace();
@@ -95,6 +104,11 @@ export function XRControllerMeasure() {
raycaster.current.set(tmpOrigin.current, tmpDir.current);
raycaster.current.far = MAX_RAY;
let snappedPoint: THREE.Vector3 | null = null;
let snapKind: 'vertex' | 'edge' | 'surface' | 'hole' = 'surface';
let hoverDetected: { kind: 'hole' | 'edge'; value: number; position: THREE.Vector3; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null;
if (doHeavy) {
const hits = raycaster.current.intersectObjects(scene.children, true);
const hit = hits.find((h) => {
const o = h.object;
@@ -106,10 +120,6 @@ export function XRControllerMeasure() {
return true;
});
let snappedPoint: THREE.Vector3 | null = null;
let snapKind: 'vertex' | 'edge' | 'surface' | 'hole' = 'surface';
let hoverDetected: { kind: 'hole' | 'edge'; value: number; position: THREE.Vector3; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null;
if (hit && hit.object instanceof THREE.Mesh) {
const size = gl.getSize(new THREE.Vector2());
const canvasSize = { width: size.x || 1024, height: size.y || 1024 };
@@ -162,6 +172,14 @@ export function XRControllerMeasure() {
snapKind = 'surface';
}
}
// Cache for non-heavy frames so the laser + trigger still have a target
cachedSnappedPoint.current = snappedPoint ? snappedPoint.clone() : null;
cachedSnapKind.current = snapKind;
} else {
// Light frame: reuse the last computed snap so visuals stay smooth
snappedPoint = cachedSnappedPoint.current ? cachedSnappedPoint.current.clone() : null;
snapKind = cachedSnapKind.current;
}
// ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
const measureModeNow = useModelStore.getState().measureMode;
@@ -221,9 +239,20 @@ export function XRControllerMeasure() {
if (!trigState.current && trigVal > TRIG_ON) {
trigState.current = true;
const st = useModelStore.getState();
if (st.selectionMode && hit && hit.object instanceof THREE.Mesh) {
// Walk up to find ifcElement + modelId
let cur: THREE.Object3D | null = hit.object;
if (st.selectionMode) {
// Fresh raycast on the press itself (not every frame) for selection
const triggerHits = raycaster.current.intersectObjects(scene.children, true);
const triggerHit = triggerHits.find((h) => {
const o = h.object;
if (!(o instanceof THREE.Mesh)) return false;
if (o.userData.__edgeLine) return false;
if (o.geometry instanceof THREE.SphereGeometry) return false;
if (o.geometry instanceof THREE.RingGeometry) return false;
if (o.geometry instanceof THREE.PlaneGeometry) return false;
return true;
});
if (triggerHit && triggerHit.object instanceof THREE.Mesh) {
let cur: THREE.Object3D | null = triggerHit.object;
let element: THREE.Object3D | null = null;
let modelId: string | null = null;
while (cur) {
@@ -231,12 +260,13 @@ export function XRControllerMeasure() {
if (!modelId && cur.userData?.modelId) modelId = cur.userData.modelId as string;
cur = cur.parent;
}
if (!element) element = hit.object;
if (!element) element = triggerHit.object;
if (!modelId) modelId = st.activeModelId;
if (modelId && element) {
const id = element.userData?.ifcId ?? element.name ?? element.uuid;
st.toggleElementSelection(`${modelId}:${id}`);
}
}
} else if (snappedPoint) {
st.addMeasurePoint({
x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z,
+1 -3
View File
@@ -350,9 +350,7 @@ function ToolsTab(p: XRHudInWorldProps) {
active={measureMode} onClick={() => setMeasureMode(!measureMode)} />
<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.025, 0]} size={[0.075, 0.022]} label={`Escala ${p.allowScale ? 'ON' : 'OFF'}`}
active={p.allowScale} onClick={p.onToggleAllowScale} />
<XR3DButton position={[0.16, 0.025, 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} />