Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-05-24 19:17:03 +00:00
parent f603823c91
commit 7203127a1d
+40 -5
View File
@@ -26,6 +26,15 @@ function isPickableModelMesh(o: THREE.Object3D): boolean {
return false; 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. * 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 laserMat = useRef(new THREE.LineBasicMaterial({ color: '#22c55e', transparent: true, opacity: 0.7, depthTest: false }));
const tipColor = useRef(new THREE.Color('#22c55e')); 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) // Dwell detection for hover-based smart measurement (1 s)
const dwellPos = useRef(new THREE.Vector3(Infinity, Infinity, Infinity)); const dwellPos = useRef(new THREE.Vector3(Infinity, Infinity, Infinity));
@@ -118,11 +128,13 @@ export function XRControllerMeasure() {
let snappedPoint: THREE.Vector3 | null = null; let snappedPoint: THREE.Vector3 | null = null;
let snapKind: 'vertex' | 'edge' | 'surface' | 'hole' = 'surface'; 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 hits = raycaster.current.intersectObjects(scene.children, true);
const hit = hits.find((h) => { const hit = hits.find((h) => {
const o = h.object; const o = h.object;
if (!(o instanceof THREE.Mesh)) return false;
if (!isPickableModelMesh(o)) return false; if (!isPickableModelMesh(o)) return false;
if (o.userData.__edgeLine) return false; if (o.userData.__edgeLine) return false;
if (o.geometry instanceof THREE.SphereGeometry) return false; if (o.geometry instanceof THREE.SphereGeometry) return false;
@@ -132,9 +144,10 @@ export function XRControllerMeasure() {
}); });
if (hit && hit.object instanceof THREE.Mesh) { if (hit && hit.object instanceof THREE.Mesh) {
hitModelId = getModelIdFromObject(hit.object);
const dist = tmpOrigin.current.distanceTo(hit.point); const dist = tmpOrigin.current.distanceTo(hit.point);
const vertexThreshold = Math.max(0.06, dist * 0.08); const vertexThreshold = Math.max(0.08, dist * 0.12);
const edgeThreshold = Math.max(0.08, dist * 0.10); const edgeThreshold = Math.max(0.10, dist * 0.14);
// Snap to existing registered hole centers first // Snap to existing registered hole centers first
const existing = useModelStore.getState().measurements; const existing = useModelStore.getState().measurements;
@@ -158,7 +171,7 @@ export function XRControllerMeasure() {
if (circle) { if (circle) {
snappedPoint = circle.center; snappedPoint = circle.center;
snapKind = 'hole'; snapKind = 'hole';
hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center }; hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center, modelId: hitModelId };
} else { } else {
const snap = resolveSnap3D(hit.object, hit.point, vertexThreshold, edgeThreshold); const snap = resolveSnap3D(hit.object, hit.point, vertexThreshold, edgeThreshold);
snappedPoint = snap.point; snappedPoint = snap.point;
@@ -167,7 +180,7 @@ export function XRControllerMeasure() {
const seg = findNearestEdgeSegment3D(hit.object, hit.point, edgeThreshold); const seg = findNearestEdgeSegment3D(hit.object, hit.point, edgeThreshold);
if (seg) { if (seg) {
const lenMM = seg.a.distanceTo(seg.b) * 1000; 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 ───── // ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
const measureModeNow = useModelStore.getState().measureMode; const measureModeNow = useModelStore.getState().measureMode;
const nowT = performance.now(); 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 }, 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 }, 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 triggerHits = raycaster.current.intersectObjects(scene.children, true);
const triggerHit = triggerHits.find((h) => { const triggerHit = triggerHits.find((h) => {
const o = h.object; const o = h.object;
if (!(o instanceof THREE.Mesh)) return false;
if (!isPickableModelMesh(o)) return false; if (!isPickableModelMesh(o)) return false;
if (o.userData.__edgeLine) return false; if (o.userData.__edgeLine) return false;
if (o.geometry instanceof THREE.SphereGeometry) 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); sendRemoteLog('info', 'Trigger físico pressionado no AR com snapPoint ativo', snappedPoint);
st.addMeasurePoint({ st.addMeasurePoint({
x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z, x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z,
modelId: hitModelId,
}); });
} }
} else if (trigState.current && trigVal < TRIG_OFF) { } else if (trigState.current && trigVal < TRIG_OFF) {