From 7203127a1d685bb0c6e82f207d641e31114cfdd6 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 19:17:03 +0000 Subject: [PATCH] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/XRControllerMeasure.tsx | 45 +++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/components/three/XRControllerMeasure.tsx b/src/components/three/XRControllerMeasure.tsx index de52e40..d90cbe4 100644 --- a/src/components/three/XRControllerMeasure.tsx +++ b/src/components/three/XRControllerMeasure.tsx @@ -26,6 +26,15 @@ function isPickableModelMesh(o: THREE.Object3D): boolean { return false; } +function getModelIdFromObject(o: THREE.Object3D): string | undefined { + let cur: THREE.Object3D | null = o; + while (cur) { + if (cur.userData?.modelId) return cur.userData.modelId as string; + cur = cur.parent; + } + return undefined; +} + /** * Right-controller trigger driven measurement for AR. * @@ -60,6 +69,7 @@ export function XRControllerMeasure() { ); const laserMat = useRef(new THREE.LineBasicMaterial({ color: '#22c55e', transparent: true, opacity: 0.7, depthTest: false })); const tipColor = useRef(new THREE.Color('#22c55e')); + const lockedSnap = useRef<{ point: THREE.Vector3; kind: 'vertex' | 'edge' | 'hole'; modelId?: string; lastSeen: number } | null>(null); // Dwell detection for hover-based smart measurement (1 s) const dwellPos = useRef(new THREE.Vector3(Infinity, Infinity, Infinity)); @@ -118,11 +128,13 @@ export function XRControllerMeasure() { 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; + let hoverDetected: { kind: 'hole' | 'edge'; value: number; position: THREE.Vector3; modelId?: string; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null; + let hitModelId: string | undefined; const hits = raycaster.current.intersectObjects(scene.children, true); const hit = hits.find((h) => { const o = h.object; + if (!(o instanceof THREE.Mesh)) return false; if (!isPickableModelMesh(o)) return false; if (o.userData.__edgeLine) return false; if (o.geometry instanceof THREE.SphereGeometry) return false; @@ -132,9 +144,10 @@ export function XRControllerMeasure() { }); if (hit && hit.object instanceof THREE.Mesh) { + hitModelId = getModelIdFromObject(hit.object); 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); + const vertexThreshold = Math.max(0.08, dist * 0.12); + const edgeThreshold = Math.max(0.10, dist * 0.14); // Snap to existing registered hole centers first const existing = useModelStore.getState().measurements; @@ -158,7 +171,7 @@ export function XRControllerMeasure() { if (circle) { snappedPoint = circle.center; snapKind = 'hole'; - hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center }; + hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center, modelId: hitModelId }; } else { const snap = resolveSnap3D(hit.object, hit.point, vertexThreshold, edgeThreshold); snappedPoint = snap.point; @@ -167,7 +180,7 @@ export function XRControllerMeasure() { 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 } }; + hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, modelId: hitModelId, endpoints: { a: seg.a, b: seg.b } }; } } } @@ -177,6 +190,25 @@ export function XRControllerMeasure() { } } + const strongSnap = snapKind === 'vertex' || snapKind === 'edge' || snapKind === 'hole'; + if (snappedPoint && strongSnap) { + const locked = lockedSnap.current; + const sameTarget = locked && locked.kind === snapKind && locked.modelId === hitModelId; + if (sameTarget && locked.point.distanceTo(snappedPoint) < (snapKind === 'edge' ? 0.08 : 0.12)) { + if (snapKind === 'edge') locked.point.lerp(snappedPoint, 0.25); + snappedPoint = locked.point.clone(); + locked.lastSeen = nowT; + } else { + lockedSnap.current = { point: snappedPoint.clone(), kind: snapKind, modelId: hitModelId, lastSeen: nowT }; + } + } else if (snappedPoint && lockedSnap.current && lockedSnap.current.point.distanceTo(snappedPoint) < 0.12 && nowT - lockedSnap.current.lastSeen < 600) { + snapKind = lockedSnap.current.kind; + snappedPoint = lockedSnap.current.point.clone(); + lockedSnap.current.lastSeen = nowT; + } else if (!snappedPoint || (lockedSnap.current && nowT - lockedSnap.current.lastSeen > 600)) { + lockedSnap.current = null; + } + // ── Dwell detection (1 s) to auto-register hovered hole/edge ───── const measureModeNow = useModelStore.getState().measureMode; const nowT = performance.now(); @@ -197,6 +229,7 @@ export function XRControllerMeasure() { 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 }, }, + modelId: hoverDetected.modelId, }); } } @@ -246,6 +279,7 @@ export function XRControllerMeasure() { 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 (!isPickableModelMesh(o)) return false; if (o.userData.__edgeLine) return false; if (o.geometry instanceof THREE.SphereGeometry) return false; @@ -273,6 +307,7 @@ export function XRControllerMeasure() { 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, }); } } else if (trigState.current && trigVal < TRIG_OFF) {