🚀 Auto-deploy: melhoria no snap e medição AR em 22/05/2026 21:20:32

This commit is contained in:
2026-05-22 21:20:32 +00:00
parent 63ad53edfb
commit c85dc85936
6 changed files with 181 additions and 78 deletions
+9 -7
View File
@@ -114,6 +114,7 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
const checklist = useModelStore((s) => s.checklist);
const setActive = useModelStore((s) => s.setActiveModel);
const selectionMode = useModelStore((s) => s.selectionMode);
const measureMode = useModelStore((s) => s.measureMode);
const fineTuning = sceneModel.fineTuning;
const modelInfo = useMemo(() => {
@@ -160,12 +161,13 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
originalColors.current.set(mat, mat.color.clone());
}
if (renderMode === 'edges') {
if (renderMode === 'edges' && !measureMode) {
mat.visible = false;
} else {
mat.visible = true;
mat.transparent = opacity < 1;
mat.opacity = opacity;
const targetOpacity = measureMode ? 0.25 : opacity;
mat.transparent = targetOpacity < 1;
mat.opacity = targetOpacity;
mat.wireframe = renderMode === 'wireframe';
if (renderMode === 'wireframe') {
mat.wireframeLinewidth = wireframeThickness;
@@ -183,11 +185,11 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
}
});
if (renderMode === 'edges' && child.geometry) {
if ((renderMode === 'edges' || measureMode) && child.geometry) {
const edgesGeo = new THREE.EdgesGeometry(child.geometry, edgeThresholdAngle);
const lineMat = new THREE.LineBasicMaterial({
color: wireframeColor,
linewidth: wireframeThickness,
color: measureMode ? '#00f3ff' : wireframeColor,
linewidth: measureMode ? 2 : wireframeThickness,
});
const lineSegments = new THREE.LineSegments(edgesGeo, lineMat);
lineSegments.userData.__edgeLine = true;
@@ -195,7 +197,7 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive:
}
}
});
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color]);
}, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color, measureMode]);
const rotXRad = (fineTuning.rotX * Math.PI) / 180;
const rotYRad = (fineTuning.rotY * Math.PI) / 180;
+51 -65
View File
@@ -69,17 +69,12 @@ export function XRControllerMeasure() {
// 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();
@@ -124,70 +119,61 @@ export function XRControllerMeasure() {
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;
if (!isPickableModelMesh(o)) 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;
});
const hits = raycaster.current.intersectObjects(scene.children, true);
const hit = hits.find((h) => {
const o = h.object;
if (!isPickableModelMesh(o)) 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 (hit && hit.object instanceof THREE.Mesh) {
// Snap to existing registered hole centers first (within ~4cm)
const existing = useModelStore.getState().measurements;
let bestHoleCenter: THREE.Vector3 | null = null;
let bestHoleDist = Infinity;
for (const m of existing) {
if (m.kind !== 'hole') continue;
const c = new THREE.Vector3(m.pointA.x, m.pointA.y, m.pointA.z);
const d = hit.point.distanceTo(c);
if (d < 0.04 && d < bestHoleDist) {
bestHoleDist = d;
bestHoleCenter = c;
}
}
if (hit && hit.object instanceof THREE.Mesh) {
const dist = tmpOrigin.current.distanceTo(hit.point);
const vertexThreshold = Math.max(0.06, dist * 0.08);
const edgeThreshold = Math.max(0.08, dist * 0.10);
if (snapEnabled && bestHoleCenter) {
snappedPoint = bestHoleCenter;
snapKind = 'hole';
} else if (snapEnabled) {
// Try detecting a circular edge (hole) at hit (8cm radius)
const circle = detectCircularEdgeAtPoint3D(hit.object, hit.point, 0.08);
if (circle) {
snappedPoint = circle.center;
snapKind = 'hole';
hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center };
} else {
// Snap 3D físico puro (7cm para vértices, 9cm para arestas)
const snap = resolveSnap3D(hit.object, hit.point, 0.07, 0.09);
snappedPoint = snap.point;
snapKind = snap.type;
if (snap.type === 'edge') {
const seg = findNearestEdgeSegment3D(hit.object, hit.point, 0.09);
if (seg) {
const lenMM = seg.a.distanceTo(seg.b) * 1000;
hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, endpoints: { a: seg.a, b: seg.b } };
}
}
}
} else {
snappedPoint = hit.point.clone();
snapKind = 'surface';
// Snap to existing registered hole centers first
const existing = useModelStore.getState().measurements;
let bestHoleCenter: THREE.Vector3 | null = null;
let bestHoleDist = Infinity;
for (const m of existing) {
if (m.kind !== 'hole') continue;
const c = new THREE.Vector3(m.pointA.x, m.pointA.y, m.pointA.z);
const d = hit.point.distanceTo(c);
if (d < 0.05 && d < bestHoleDist) {
bestHoleDist = d;
bestHoleCenter = c;
}
}
// Cache for non-heavy frames so the laser + trigger still have a target.
// Sticky: se conseguimos um snap "forte" (vertex/edge/hole), preservamos
// ele entre frames leves para evitar cintilação.
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;
if (snapEnabled && bestHoleCenter) {
snappedPoint = bestHoleCenter;
snapKind = 'hole';
} else if (snapEnabled) {
const circle = detectCircularEdgeAtPoint3D(hit.object, hit.point, Math.max(0.08, dist * 0.12));
if (circle) {
snappedPoint = circle.center;
snapKind = 'hole';
hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center };
} else {
const snap = resolveSnap3D(hit.object, hit.point, vertexThreshold, edgeThreshold);
snappedPoint = snap.point;
snapKind = snap.type;
if (snap.type === 'edge') {
const seg = findNearestEdgeSegment3D(hit.object, hit.point, edgeThreshold);
if (seg) {
const lenMM = seg.a.distanceTo(seg.b) * 1000;
hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, endpoints: { a: seg.a, b: seg.b } };
}
}
}
} else {
snappedPoint = hit.point.clone();
snapKind = 'surface';
}
}
// ── Dwell detection (1 s) to auto-register hovered hole/edge ─────