Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -243,7 +243,7 @@ export function findNearestEdgeSegment(
|
||||
camera: THREE.Camera,
|
||||
canvasSize: { width: number; height: number },
|
||||
thresholdPx: number = 12
|
||||
): { midpoint: THREE.Vector3; lengthMM: number } | null {
|
||||
): { midpoint: THREE.Vector3; lengthMM: number; a: THREE.Vector3; b: THREE.Vector3 } | null {
|
||||
// Look for edge line segments children
|
||||
let edgeLines: THREE.LineSegments | null = null;
|
||||
mesh.children.forEach(c => {
|
||||
@@ -263,9 +263,11 @@ export function findNearestEdgeSegment(
|
||||
const hitY = (-hitScreen.y * 0.5 + 0.5) * canvasSize.height;
|
||||
|
||||
const matrix = edgeLines ? edgeLines.matrixWorld.clone().premultiply(mesh.matrixWorld) : mesh.matrixWorld;
|
||||
|
||||
|
||||
let bestDist = Infinity;
|
||||
let bestMid: THREE.Vector3 | null = null;
|
||||
let bestA: THREE.Vector3 | null = null;
|
||||
let bestB: THREE.Vector3 | null = null;
|
||||
let bestLen = 0;
|
||||
|
||||
const segCount = posAttr.count / 2;
|
||||
@@ -278,7 +280,6 @@ export function findNearestEdgeSegment(
|
||||
a.fromBufferAttribute(posAttr, i * 2).applyMatrix4(matrix);
|
||||
b.fromBufferAttribute(posAttr, i * 2 + 1).applyMatrix4(matrix);
|
||||
|
||||
// Project to screen
|
||||
pa.copy(a).project(camera);
|
||||
pb.copy(b).project(camera);
|
||||
const ax = (pa.x * 0.5 + 0.5) * canvasSize.width;
|
||||
@@ -286,24 +287,112 @@ export function findNearestEdgeSegment(
|
||||
const bx = (pb.x * 0.5 + 0.5) * canvasSize.width;
|
||||
const by = (-pb.y * 0.5 + 0.5) * canvasSize.height;
|
||||
|
||||
// Distance from point to line segment in screen space
|
||||
const dist = pointToSegmentDist(hitX, hitY, ax, ay, bx, by);
|
||||
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestMid = a.clone().add(b).multiplyScalar(0.5);
|
||||
bestA = a.clone();
|
||||
bestB = b.clone();
|
||||
bestLen = a.distanceTo(b) * 1000; // mm
|
||||
}
|
||||
}
|
||||
|
||||
if (!edgeLines) geo.dispose();
|
||||
|
||||
if (bestDist <= thresholdPx && bestMid && bestLen > 0.5) {
|
||||
return { midpoint: bestMid, lengthMM: bestLen };
|
||||
if (bestDist <= thresholdPx && bestMid && bestA && bestB && bestLen > 0.5) {
|
||||
return { midpoint: bestMid, lengthMM: bestLen, a: bestA, b: bestB };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a circular hole by fitting a circle through edge vertices that lie
|
||||
* within `radiusPx` (screen space) around `worldPoint`. Returns center + diameter.
|
||||
*/
|
||||
export function detectCircularEdgeAtPoint(
|
||||
mesh: THREE.Mesh,
|
||||
worldPoint: THREE.Vector3,
|
||||
camera: THREE.Camera,
|
||||
canvasSize: { width: number; height: number },
|
||||
radiusPx: number = 60
|
||||
): { center: THREE.Vector3; diameterMM: number } | null {
|
||||
let edgeLines: THREE.LineSegments | null = null;
|
||||
mesh.children.forEach(c => {
|
||||
if (c.userData.__edgeLine && c instanceof THREE.LineSegments) edgeLines = c;
|
||||
});
|
||||
const geo = edgeLines?.geometry ?? new THREE.EdgesGeometry(mesh.geometry, 15);
|
||||
const posAttr = geo.attributes.position;
|
||||
if (!posAttr) return null;
|
||||
|
||||
const matrix = edgeLines ? edgeLines.matrixWorld.clone().premultiply(mesh.matrixWorld) : mesh.matrixWorld;
|
||||
|
||||
const hitScreen = worldPoint.clone().project(camera);
|
||||
const hitX = (hitScreen.x * 0.5 + 0.5) * canvasSize.width;
|
||||
const hitY = (-hitScreen.y * 0.5 + 0.5) * canvasSize.height;
|
||||
|
||||
const verts: THREE.Vector3[] = [];
|
||||
const seen = new Set<string>();
|
||||
const tmp = new THREE.Vector3();
|
||||
const proj = new THREE.Vector3();
|
||||
const count = posAttr.count;
|
||||
for (let i = 0; i < count; i++) {
|
||||
tmp.fromBufferAttribute(posAttr, i).applyMatrix4(matrix);
|
||||
proj.copy(tmp).project(camera);
|
||||
const sx = (proj.x * 0.5 + 0.5) * canvasSize.width;
|
||||
const sy = (-proj.y * 0.5 + 0.5) * canvasSize.height;
|
||||
if (Math.hypot(sx - hitX, sy - hitY) > radiusPx) continue;
|
||||
const key = `${tmp.x.toFixed(5)},${tmp.y.toFixed(5)},${tmp.z.toFixed(5)}`;
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
verts.push(tmp.clone());
|
||||
}
|
||||
if (!edgeLines) geo.dispose();
|
||||
|
||||
if (verts.length < 6) return null;
|
||||
|
||||
// Compute centroid + best-fit plane normal via covariance
|
||||
const centroid = new THREE.Vector3();
|
||||
verts.forEach(v => centroid.add(v));
|
||||
centroid.divideScalar(verts.length);
|
||||
|
||||
let xx = 0, xy = 0, xz = 0, yy = 0, yz = 0, zz = 0;
|
||||
for (const v of verts) {
|
||||
const dx = v.x - centroid.x, dy = v.y - centroid.y, dz = v.z - centroid.z;
|
||||
xx += dx * dx; xy += dx * dy; xz += dx * dz;
|
||||
yy += dy * dy; yz += dy * dz; zz += dz * dz;
|
||||
}
|
||||
// Normal = eigenvector with smallest eigenvalue. Approximate using cross of two
|
||||
// axes with largest variance.
|
||||
const axisA = new THREE.Vector3(xx, xy, xz).normalize();
|
||||
const axisB = new THREE.Vector3(xy, yy, yz).normalize();
|
||||
const normal = new THREE.Vector3().crossVectors(axisA, axisB);
|
||||
if (normal.lengthSq() < 1e-8) return null;
|
||||
normal.normalize();
|
||||
|
||||
// Build basis on plane
|
||||
const basisU = new THREE.Vector3();
|
||||
if (Math.abs(normal.x) < 0.9) basisU.crossVectors(normal, new THREE.Vector3(1, 0, 0)).normalize();
|
||||
else basisU.crossVectors(normal, new THREE.Vector3(0, 1, 0)).normalize();
|
||||
const basisV = new THREE.Vector3().crossVectors(normal, basisU).normalize();
|
||||
|
||||
const points2D = verts.map(v => {
|
||||
const rel = v.clone().sub(centroid);
|
||||
return { u: rel.dot(basisU), v: rel.dot(basisV) };
|
||||
});
|
||||
const fit = circleFitKasa(points2D);
|
||||
if (!fit) return null;
|
||||
|
||||
const diameterMM = fit.radius * 2 * 1000;
|
||||
if (diameterMM < 2 || diameterMM > 500) return null;
|
||||
|
||||
const center3D = centroid.clone()
|
||||
.add(basisU.clone().multiplyScalar(fit.cx))
|
||||
.add(basisV.clone().multiplyScalar(fit.cy));
|
||||
|
||||
return { center: center3D, diameterMM };
|
||||
}
|
||||
|
||||
function pointToSegmentDist(px: number, py: number, ax: number, ay: number, bx: number, by: number): number {
|
||||
const dx = bx - ax;
|
||||
const dy = by - ay;
|
||||
|
||||
Reference in New Issue
Block a user