🚀 Auto-deploy: melhoria no snap e medição AR em 24/05/2026 02:00:33

This commit is contained in:
2026-05-24 02:00:33 +00:00
parent c839653a97
commit c8d51e0e1b
7 changed files with 170 additions and 26 deletions
@@ -26,6 +26,31 @@ function isPickableModelMesh(o: THREE.Object3D): boolean {
return false;
}
function findModelId(obj: THREE.Object3D): string | undefined {
let cur: THREE.Object3D | null = obj;
while (cur) {
if (cur.userData?.modelId) return cur.userData.modelId as string;
cur = cur.parent;
}
return undefined;
}
function triggerHaptic(session: XRSession | null, handedness: 'left' | 'right', intensity = 0.5, duration = 40) {
if (!session) return;
try {
for (const source of session.inputSources) {
if (source.handedness === handedness) {
const gp = source.gamepad;
if (gp && gp.hapticActuators && gp.hapticActuators.length > 0) {
gp.hapticActuators[0].pulse(intensity, duration).catch(() => {});
}
}
}
} catch (e) {
console.warn('[XR][Haptic] erro ao vibrar controle:', e);
}
}
/**
* Right-controller trigger driven measurement for AR.
*
@@ -65,6 +90,7 @@ export function XRControllerMeasure() {
const dwellPos = useRef(new THREE.Vector3(Infinity, Infinity, Infinity));
const dwellStart = useRef(0);
const dwellFired = useRef(false);
const lastSnapKind = useRef<string | null>(null);
// Throttling: raycast + snap analysis are heavy on dense IFC models and
// running them every frame at 72fps can stall the Quest right after
@@ -177,6 +203,12 @@ export function XRControllerMeasure() {
}
}
// ── Haptic feedback on snap state changes ────────────────────────
if (snappedPoint && snapKind !== 'surface' && lastSnapKind.current !== snapKind) {
triggerHaptic(session, 'right', 0.4, 15);
}
lastSnapKind.current = snappedPoint ? snapKind : null;
// ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
const measureModeNow = useModelStore.getState().measureMode;
const nowT = performance.now();
@@ -189,15 +221,18 @@ export function XRControllerMeasure() {
} else if (!dwellFired.current && nowT - dwellStart.current > 1000) {
dwellFired.current = true;
if (measureModeNow) {
const hitModelId = hit ? findModelId(hit.object) : undefined;
useModelStore.getState().registerHoverMeasurement({
type: hoverDetected.kind,
value: hoverDetected.value,
position: [hoverDetected.position.x, hoverDetected.position.y, hoverDetected.position.z],
modelId: hitModelId,
endpoints: hoverDetected.endpoints && {
a: { x: hoverDetected.endpoints.a.x, y: hoverDetected.endpoints.a.y, z: hoverDetected.endpoints.a.z },
b: { x: hoverDetected.endpoints.b.x, y: hoverDetected.endpoints.b.y, z: hoverDetected.endpoints.b.z },
},
});
triggerHaptic(session, 'right', 0.6, 50);
}
}
} else {
@@ -270,10 +305,13 @@ export function XRControllerMeasure() {
}
}
} else if (snappedPoint) {
const hitModelId = hit ? findModelId(hit.object) : undefined;
sendRemoteLog('info', 'Trigger físico pressionado no AR com snapPoint ativo', snappedPoint);
st.addMeasurePoint({
x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z,
modelId: hitModelId,
});
triggerHaptic(session, 'right', 0.8, 25);
}
} else if (trigState.current && trigVal < TRIG_OFF) {
trigState.current = false;
@@ -285,6 +323,7 @@ export function XRControllerMeasure() {
if (!aState.current && aVal > BTN_ON) {
aState.current = true;
useModelStore.getState().undoLastMeasurement();
triggerHaptic(session, 'right', 0.5, 30);
} else if (aState.current && aVal < TRIG_OFF) {
aState.current = false;
}
@@ -295,6 +334,7 @@ export function XRControllerMeasure() {
if (!bState.current && bVal > BTN_ON) {
bState.current = true;
useModelStore.getState().clearMeasurements();
triggerHaptic(session, 'right', 0.7, 60);
} else if (bState.current && bVal < TRIG_OFF) {
bState.current = false;
}