Skip to content

Commit f64482d

Browse files
author
mmiscool
committed
Make tubes faster
1 parent be65872 commit f64482d

File tree

1 file changed

+96
-59
lines changed

1 file changed

+96
-59
lines changed

src/BREP/Tube.js

Lines changed: 96 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -402,44 +402,51 @@ function buildHullTube(points, radius, resolution, closed, keepSpheres = false,
402402
return { manifold: hull, spheres };
403403
}
404404

405-
function rebuildSolidFromManifold(target, manifold, faceMap) {
406-
const rebuilt = Solid._fromManifold(manifold, faceMap);
407-
408-
// Copy authoring buffers and metadata without clobbering THREE.Object3D fields
409-
target._numProp = rebuilt._numProp;
410-
target._vertProperties = rebuilt._vertProperties;
411-
target._triVerts = rebuilt._triVerts;
412-
target._triIDs = rebuilt._triIDs;
413-
target._vertKeyToIndex = new Map(rebuilt._vertKeyToIndex);
414-
415-
target._idToFaceName = new Map(rebuilt._idToFaceName);
416-
target._faceNameToID = new Map(rebuilt._faceNameToID);
417-
target._faceMetadata = new Map(rebuilt._faceMetadata);
418-
target._edgeMetadata = new Map(rebuilt._edgeMetadata);
419-
420-
target._manifold = rebuilt._manifold;
421-
target._dirty = false;
422-
target._faceIndex = null;
423-
return target;
424-
}
425-
426-
function distanceToSegmentSquared(p, a, b) {
427-
const ab = b.clone().sub(a);
428-
const ap = p.clone().sub(a);
429-
const t = THREE.MathUtils.clamp(ap.dot(ab) / ab.lengthSq(), 0, 1);
430-
const closest = a.clone().addScaledVector(ab, t);
431-
return p.distanceToSquared(closest);
432-
}
433-
434-
function minDistanceToPolyline(points, polyline) {
435-
if (!Array.isArray(polyline) || polyline.length < 2) return Infinity;
436-
let minSq = Infinity;
405+
function buildPolylineSegmentCache(polyline) {
406+
if (!Array.isArray(polyline) || polyline.length < 2) return [];
407+
const segments = [];
437408
for (let i = 0; i < polyline.length - 1; i++) {
438409
const a = polyline[i];
439410
const b = polyline[i + 1];
440-
minSq = Math.min(minSq, distanceToSegmentSquared(points, a, b));
411+
if (!a || !b) continue;
412+
const ax = a.x;
413+
const ay = a.y;
414+
const az = a.z;
415+
const dx = b.x - ax;
416+
const dy = b.y - ay;
417+
const dz = b.z - az;
418+
const lenSq = dx * dx + dy * dy + dz * dz;
419+
segments.push({ ax, ay, az, dx, dy, dz, lenSq });
420+
}
421+
return segments;
422+
}
423+
424+
function minDistanceToPolylineSq(px, py, pz, segments, breakAtSq = Infinity) {
425+
if (!Array.isArray(segments) || segments.length === 0) return Infinity;
426+
let minSq = Infinity;
427+
for (let i = 0; i < segments.length; i++) {
428+
const seg = segments[i];
429+
if (!seg) continue;
430+
if (seg.lenSq <= EPS_SQ) continue;
431+
const apx = px - seg.ax;
432+
const apy = py - seg.ay;
433+
const apz = pz - seg.az;
434+
let t = (apx * seg.dx + apy * seg.dy + apz * seg.dz) / seg.lenSq;
435+
if (t < 0) t = 0;
436+
else if (t > 1) t = 1;
437+
const cx = seg.ax + (seg.dx * t);
438+
const cy = seg.ay + (seg.dy * t);
439+
const cz = seg.az + (seg.dz * t);
440+
const ddx = px - cx;
441+
const ddy = py - cy;
442+
const ddz = pz - cz;
443+
const distSq = ddx * ddx + ddy * ddy + ddz * ddz;
444+
if (distSq < minSq) {
445+
minSq = distSq;
446+
if (minSq <= breakAtSq) break;
447+
}
441448
}
442-
return Math.sqrt(minSq);
449+
return minSq;
443450
}
444451

445452
function relabelFaces(solid, pathPoints, startNormal, endNormal, outerRadius, innerRadius, closed, faceTag) {
@@ -455,9 +462,12 @@ function relabelFaces(solid, pathPoints, startNormal, endNormal, outerRadius, in
455462

456463
const nStart = startNormal ? startNormal.clone().normalize() : null;
457464
const nEnd = endNormal ? endNormal.clone().normalize() : null;
458-
const startOffset = nStart ? nStart.dot(pathPoints[0]) : 0;
459-
const endOffset = nEnd ? nEnd.dot(pathPoints[pathPoints.length - 1]) : 0;
465+
const pathStart = pathPoints[0];
466+
const pathEnd = pathPoints[pathPoints.length - 1];
467+
const startOffset = nStart ? nStart.dot(pathStart) : 0;
468+
const endOffset = nEnd ? nEnd.dot(pathEnd) : 0;
460469
const capTol = Math.max(outerRadius * 1e-2, 1e-5);
470+
const capReachSq = (outerRadius + capTol) * (outerRadius + capTol);
461471

462472
const idOuter = solid._getOrCreateID(`${faceTag}_Outer`);
463473
const idInner = innerRadius > 0 ? solid._getOrCreateID(`${faceTag}_Inner`) : idOuter;
@@ -467,8 +477,15 @@ function relabelFaces(solid, pathPoints, startNormal, endNormal, outerRadius, in
467477
const newIDs = new Array(triCount);
468478
const vp = solid._vertProperties;
469479
const tv = solid._triVerts;
470-
const polyline = pathPoints;
471480
const innerOuterThreshold = innerRadius > 0 ? (innerRadius + outerRadius) * 0.5 : outerRadius * 0.5;
481+
const innerOuterThresholdSq = innerOuterThreshold * innerOuterThreshold;
482+
const segmentCache = innerRadius > 0 ? buildPolylineSegmentCache(pathPoints) : null;
483+
const nStartX = nStart ? nStart.x : 0;
484+
const nStartY = nStart ? nStart.y : 0;
485+
const nStartZ = nStart ? nStart.z : 0;
486+
const nEndX = nEnd ? nEnd.x : 0;
487+
const nEndY = nEnd ? nEnd.y : 0;
488+
const nEndZ = nEnd ? nEnd.z : 0;
472489

473490
for (let t = 0; t < triCount; t++) {
474491
const i0 = tv[t * 3 + 0] * 3;
@@ -477,18 +494,36 @@ function relabelFaces(solid, pathPoints, startNormal, endNormal, outerRadius, in
477494
const cx = (vp[i0 + 0] + vp[i1 + 0] + vp[i2 + 0]) / 3;
478495
const cy = (vp[i0 + 1] + vp[i1 + 1] + vp[i2 + 1]) / 3;
479496
const cz = (vp[i0 + 2] + vp[i1 + 2] + vp[i2 + 2]) / 3;
480-
const centroid = new THREE.Vector3(cx, cy, cz);
481497

482498
let assigned = idOuter;
483-
const distToStart = centroid.distanceTo(pathPoints[0]);
484-
const distToEnd = centroid.distanceTo(pathPoints[pathPoints.length - 1]);
485-
if (!closed && nStart && Math.abs(nStart.dot(centroid) - startOffset) <= capTol && distToStart <= outerRadius + capTol) {
486-
assigned = idCapStart;
487-
} else if (!closed && nEnd && Math.abs(nEnd.dot(centroid) - endOffset) <= capTol && distToEnd <= outerRadius + capTol) {
488-
assigned = idCapEnd;
489-
} else if (innerRadius > 0) {
490-
const dist = minDistanceToPolyline(centroid, polyline);
491-
assigned = dist <= innerOuterThreshold ? idInner : idOuter;
499+
if (!closed && nStart) {
500+
const planeDistStart = Math.abs((nStartX * cx + nStartY * cy + nStartZ * cz) - startOffset);
501+
if (planeDistStart <= capTol) {
502+
const dsx = cx - pathStart.x;
503+
const dsy = cy - pathStart.y;
504+
const dsz = cz - pathStart.z;
505+
if ((dsx * dsx + dsy * dsy + dsz * dsz) <= capReachSq) {
506+
assigned = idCapStart;
507+
}
508+
}
509+
}
510+
if (assigned === idOuter && !closed && nEnd) {
511+
const planeDistEnd = Math.abs((nEndX * cx + nEndY * cy + nEndZ * cz) - endOffset);
512+
if (planeDistEnd <= capTol) {
513+
const dex = cx - pathEnd.x;
514+
const dey = cy - pathEnd.y;
515+
const dez = cz - pathEnd.z;
516+
if ((dex * dex + dey * dey + dez * dez) <= capReachSq) {
517+
assigned = idCapEnd;
518+
}
519+
}
520+
}
521+
if (assigned === idOuter && innerRadius > 0) {
522+
const distSq = minDistanceToPolylineSq(cx, cy, cz, segmentCache, innerOuterThresholdSq);
523+
assigned = distSq <= innerOuterThresholdSq ? idInner : idOuter;
524+
}
525+
if (assigned !== idOuter && assigned !== idInner && assigned !== idCapStart && assigned !== idCapEnd) {
526+
assigned = idOuter;
492527
}
493528
newIDs[t] = assigned;
494529
}
@@ -824,13 +859,18 @@ export class Tube extends Solid {
824859

825860
if (inner > 0) {
826861
const { manifold: innerManifold, spheres: innerSpheres } = buildHullTube(cleanPoints, inner, segs, isClosed, keepSpheres, trimPlanes);
827-
828-
const outerSolid = Solid._fromManifold(outerManifold, new Map([[0, `${faceTag}_Outer`]]));
829-
const innerSolid = Solid._fromManifold(innerManifold, new Map([[0, `${faceTag}_Inner`]]));
830-
finalSolid = outerSolid.subtract(innerSolid);
831-
try { outerSolid.free(); } catch { }
832-
try { innerSolid.free(); } catch { }
833-
try { if (innerManifold && typeof innerManifold.delete === 'function') innerManifold.delete(); } catch { }
862+
let differenceManifold = null;
863+
try {
864+
differenceManifold = outerManifold.subtract(innerManifold);
865+
} finally {
866+
if (outerManifold && outerManifold !== differenceManifold) {
867+
try { if (typeof outerManifold.delete === 'function') outerManifold.delete(); } catch { }
868+
}
869+
if (innerManifold && innerManifold !== differenceManifold) {
870+
try { if (typeof innerManifold.delete === 'function') innerManifold.delete(); } catch { }
871+
}
872+
}
873+
finalSolid = Solid._fromManifold(differenceManifold, new Map([[0, `${faceTag}_Outer`]]));
834874
if (keepSpheres) {
835875
this.debugSphereSolids = [
836876
...(this.debugSphereSolids || []),
@@ -845,11 +885,8 @@ export class Tube extends Solid {
845885
}
846886
}
847887

848-
let relabeled = relabelFaces(finalSolid, cleanPoints, startNormal, endCutNormal, radius, inner, isClosed, faceTag);
849-
// Ensure we have a manifold to copy from; if rebuild failed, fall back
850-
const manifoldForCopy = relabeled?._manifold || finalSolid._manifold;
851-
const faceMapForCopy = relabeled?._idToFaceName || finalSolid._idToFaceName;
852-
rebuildSolidFromManifold(this, manifoldForCopy, faceMapForCopy);
888+
const relabeled = relabelFaces(finalSolid, cleanPoints, startNormal, endCutNormal, radius, inner, isClosed, faceTag);
889+
copySolidState(this, relabeled || finalSolid);
853890
this.name = name;
854891
this.params.closed = isClosed;
855892

0 commit comments

Comments
 (0)