forgecad 0.9.8 → 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { D as DoubleSide, bT as initSolverWasm, bS as initKernel, S as Scene, bU as BoxGeometry, bo as MeshStandardMaterial, a4 as BackSide, b0 as PointLight, M as Mesh, aa as MeshBasicMaterial, bV as localAabbPlaneRelation, h as Vector2, bW as ShapeUtils, bX as aabbGaps, bY as aabbInteriorOverlaps, bZ as aabbOverlapVolume, b_ as AabbSpatialIndex, g as Vector3, R as Raycaster, aU as BufferAttribute, a0 as MathUtils, G as Box3, e as Color, aC as resolveForgeRenderStyle, bb as getRenderStylePreset, ax as setParamOverrides, b7 as runScript, b$ as Group, b3 as shapeToGeometry, b8 as MeshPhysicalMaterial, bc as AdditiveBlending, aH as LineBasicMaterial, b9 as LineSegments, aG as BufferGeometry, P as PerspectiveCamera, k as ShaderMaterial, bi as ZEBRA_STRIPE_FRAGMENT_SHADER, bj as ZEBRA_STRIPE_VERTEX_SHADER, bd as ZEBRA_STRIPE_SOFTNESS, be as ZEBRA_STRIPE_SCALE, bf as ZEBRA_LIGHT_COLOR, bg as ZEBRA_DARK_COLOR, bh as ZEBRA_ACCENT_COLOR, ba as geometryWithVisibleVertexColors, c0 as intersectWithPlane, c1 as setActiveBackend, W as WebGLRenderer, A as ACESFilmicToneMapping, c as SRGBColorSpace, c2 as parseCameraCliSpec, c3 as PMREMGenerator, aV as CanvasTexture, aW as Object3D, aX as FogExp2, aY as Fog, aZ as AmbientLight, b1 as DirectionalLight, a_ as HemisphereLight, bK as findJointAnimationClip, p as Plane, bk as SURFACE_FIELD_FRAGMENT_SHADER, bl as SURFACE_FIELD_VERTEX_SHADER, Y as Vector4, $ as Matrix4, br as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bq as buildSdfRaymarchFragmentShader, O as OrthographicCamera, bL as resolveJointAnimation, bM as resolveJointViewValues, c4 as PointsMaterial, c5 as Points, b2 as analyzeCollisionIntersections, c6 as serializeCollisionFinding, c7 as worldAuthorPlaneToLocal, a$ as SpotLight, bR as resolveScalarSceneSampleBudget } from "../scalar-sampling-budget-iBAeF8RM.js";
5
- import { m as mergeViewportRenderSceneStates, p as parseRenderSceneCliSpec } from "../renderSceneState-BIvOkPK3.js";
4
+ import { D as DoubleSide, bX as initSolverWasm, bW as initKernel, S as Scene, bY as BoxGeometry, bs as MeshStandardMaterial, a4 as BackSide, b0 as PointLight, M as Mesh, aa as MeshBasicMaterial, bZ as localAabbPlaneRelation, h as Vector2, b_ as ShapeUtils, b$ as analyzePhysicalConnectivity, c0 as meshContactDataFor, c1 as AabbSpatialIndex, c2 as detectPhysicalContact, g as Vector3, c3 as resolveThicknessInspectionOptions, R as Raycaster, c4 as thicknessColor, c5 as thicknessClass, aU as BufferAttribute, c6 as roughnessClassForAngle, a0 as MathUtils, c7 as resolveRoughnessInspectionOptions, G as Box3, c8 as roughnessColorForAngle, c9 as roughnessScoreForAngle, e as Color, aC as resolveForgeRenderStyle, bb as getRenderStylePreset, ax as setParamOverrides, b7 as runScript, ca as Group, b3 as shapeToGeometry, b8 as MeshPhysicalMaterial, bc as AdditiveBlending, aH as LineBasicMaterial, b9 as LineSegments, aG as BufferGeometry, P as PerspectiveCamera, k as ShaderMaterial, bi as ZEBRA_STRIPE_FRAGMENT_SHADER, bj as ZEBRA_STRIPE_VERTEX_SHADER, bd as ZEBRA_STRIPE_SOFTNESS, be as ZEBRA_STRIPE_SCALE, bf as ZEBRA_LIGHT_COLOR, bg as ZEBRA_DARK_COLOR, bh as ZEBRA_ACCENT_COLOR, ba as geometryWithVisibleVertexColors, cb as intersectWithPlane, cc as setActiveBackend, W as WebGLRenderer, A as ACESFilmicToneMapping, c as SRGBColorSpace, cd as parseCameraCliSpec, ce as PMREMGenerator, aV as CanvasTexture, aW as Object3D, aX as FogExp2, aY as Fog, aZ as AmbientLight, b1 as DirectionalLight, a_ as HemisphereLight, bO as findJointAnimationClip, p as Plane, bk as SURFACE_FIELD_FRAGMENT_SHADER, bl as SURFACE_FIELD_VERTEX_SHADER, Y as Vector4, $ as Matrix4, bv as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bu as buildSdfRaymarchFragmentShader, O as OrthographicCamera, bP as resolveJointAnimation, bQ as resolveJointViewValues, bo as ROUGHNESS_COLORS, cf as PointsMaterial, cg as Points, b2 as analyzeCollisionIntersections, ch as serializeCollisionFinding, ci as summarizeThicknessSamples, br as THICKNESS_COLORS, cj as worldAuthorPlaneToLocal, a$ as SpotLight, bV as resolveScalarSceneSampleBudget } from "../scalar-sampling-budget-Bmewod18.js";
5
+ import { m as mergeViewportRenderSceneStates, p as parseRenderSceneCliSpec } from "../renderSceneState-CWO8rHlt.js";
6
6
  const CAD_MATERIAL_PROPS = {
7
7
  color: 6003669,
8
8
  metalness: 0.05,
@@ -457,489 +457,6 @@ function computeMeshSectionCap(mesh, planeInput) {
457
457
  warnings: stitched.warnings.length > 0 ? stitched.warnings : void 0
458
458
  };
459
459
  }
460
- const AXIS_NAMES = ["x", "y", "z"];
461
- function nearestBoundaryGap(a, b, axis) {
462
- return Math.min(Math.abs(a.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a.min[axis]));
463
- }
464
- function hasPositiveGap(gaps) {
465
- return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
466
- }
467
- function contactFromBBoxes(a, b, tolerance) {
468
- const gaps = aabbGaps(a, b);
469
- const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
470
- if (largestGap > tolerance) return { touching: false, gap: largestGap };
471
- const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
472
- if (separatedAxes.length > 0) {
473
- const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
474
- return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
475
- }
476
- const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
477
- axis,
478
- axisName,
479
- gap: nearestBoundaryGap(a, b, axis)
480
- })).filter((entry) => entry.gap <= tolerance);
481
- if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
482
- const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
483
- return { touching: true, gap: nearest.gap, axis: nearest.axisName };
484
- }
485
- function isFiniteTriangle(positions, offset) {
486
- for (let index = 0; index < 9; index += 1) {
487
- if (!Number.isFinite(positions[offset + index])) return false;
488
- }
489
- return true;
490
- }
491
- function triangleRef(positions, offset) {
492
- if (!isFiniteTriangle(positions, offset)) return null;
493
- const ax = positions[offset];
494
- const ay = positions[offset + 1];
495
- const az = positions[offset + 2];
496
- const bx = positions[offset + 3];
497
- const by = positions[offset + 4];
498
- const bz = positions[offset + 5];
499
- const cx = positions[offset + 6];
500
- const cy = positions[offset + 7];
501
- const cz = positions[offset + 8];
502
- return {
503
- offset,
504
- min: [Math.min(ax, bx, cx), Math.min(ay, by, cy), Math.min(az, bz, cz)],
505
- max: [Math.max(ax, bx, cx), Math.max(ay, by, cy), Math.max(az, bz, cz)]
506
- };
507
- }
508
- function meshContactDataFor(entry) {
509
- const positions = entry.positions;
510
- if (!positions || positions.length < 9) return null;
511
- const triangleCount = Math.floor(positions.length / 9);
512
- const triangles = [];
513
- for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
514
- const triangle = triangleRef(positions, triangleIndex * 9);
515
- if (triangle) triangles.push(triangle);
516
- }
517
- if (triangles.length === 0) return null;
518
- return {
519
- positions,
520
- vertexCount: triangleCount * 3,
521
- triangles
522
- };
523
- }
524
- function distSq(px, py, pz, qx, qy, qz) {
525
- const dx = px - qx;
526
- const dy = py - qy;
527
- const dz = pz - qz;
528
- return dx * dx + dy * dy + dz * dz;
529
- }
530
- function pointSegmentDistanceSq(point, start, end) {
531
- const dx = end[0] - start[0];
532
- const dy = end[1] - start[1];
533
- const dz = end[2] - start[2];
534
- const lengthSq = dx * dx + dy * dy + dz * dz;
535
- if (lengthSq <= 1e-20) return distSq(point[0], point[1], point[2], start[0], start[1], start[2]);
536
- const t = Math.max(0, Math.min(1, ((point[0] - start[0]) * dx + (point[1] - start[1]) * dy + (point[2] - start[2]) * dz) / lengthSq));
537
- return distSq(point[0], point[1], point[2], start[0] + t * dx, start[1] + t * dy, start[2] + t * dz);
538
- }
539
- function pointTriangleDistanceSq(px, py, pz, ax, ay, az, bx, by, bz, cx, cy, cz) {
540
- const abx = bx - ax;
541
- const aby = by - ay;
542
- const abz = bz - az;
543
- const acx = cx - ax;
544
- const acy = cy - ay;
545
- const acz = cz - az;
546
- const apx = px - ax;
547
- const apy = py - ay;
548
- const apz = pz - az;
549
- const d1 = abx * apx + aby * apy + abz * apz;
550
- const d2 = acx * apx + acy * apy + acz * apz;
551
- if (d1 <= 0 && d2 <= 0) return distSq(px, py, pz, ax, ay, az);
552
- const bpx = px - bx;
553
- const bpy = py - by;
554
- const bpz = pz - bz;
555
- const d3 = abx * bpx + aby * bpy + abz * bpz;
556
- const d4 = acx * bpx + acy * bpy + acz * bpz;
557
- if (d3 >= 0 && d4 <= d3) return distSq(px, py, pz, bx, by, bz);
558
- const vc = d1 * d4 - d3 * d2;
559
- if (vc <= 0 && d1 >= 0 && d3 <= 0) {
560
- const v2 = d1 / (d1 - d3);
561
- return distSq(px, py, pz, ax + v2 * abx, ay + v2 * aby, az + v2 * abz);
562
- }
563
- const cpx = px - cx;
564
- const cpy = py - cy;
565
- const cpz = pz - cz;
566
- const d5 = abx * cpx + aby * cpy + abz * cpz;
567
- const d6 = acx * cpx + acy * cpy + acz * cpz;
568
- if (d6 >= 0 && d5 <= d6) return distSq(px, py, pz, cx, cy, cz);
569
- const vb = d5 * d2 - d1 * d6;
570
- if (vb <= 0 && d2 >= 0 && d6 <= 0) {
571
- const w2 = d2 / (d2 - d6);
572
- return distSq(px, py, pz, ax + w2 * acx, ay + w2 * acy, az + w2 * acz);
573
- }
574
- const va = d3 * d6 - d5 * d4;
575
- if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) {
576
- const bcx = cx - bx;
577
- const bcy = cy - by;
578
- const bcz = cz - bz;
579
- const w2 = (d4 - d3) / (d4 - d3 + d5 - d6);
580
- return distSq(px, py, pz, bx + w2 * bcx, by + w2 * bcy, bz + w2 * bcz);
581
- }
582
- const barycentricTotal = va + vb + vc;
583
- if (!Number.isFinite(barycentricTotal) || Math.abs(barycentricTotal) < 1e-20) {
584
- return Math.min(distSq(px, py, pz, ax, ay, az), distSq(px, py, pz, bx, by, bz), distSq(px, py, pz, cx, cy, cz));
585
- }
586
- const denom = 1 / barycentricTotal;
587
- const v = vb * denom;
588
- const w = vc * denom;
589
- return distSq(px, py, pz, ax + abx * v + acx * w, ay + aby * v + acy * w, az + abz * v + acz * w);
590
- }
591
- function segmentSegmentDistanceSq(a0, a1, b0, b1) {
592
- const ux = a1[0] - a0[0];
593
- const uy = a1[1] - a0[1];
594
- const uz = a1[2] - a0[2];
595
- const vx = b1[0] - b0[0];
596
- const vy = b1[1] - b0[1];
597
- const vz = b1[2] - b0[2];
598
- const wx = a0[0] - b0[0];
599
- const wy = a0[1] - b0[1];
600
- const wz = a0[2] - b0[2];
601
- const a = ux * ux + uy * uy + uz * uz;
602
- const b = ux * vx + uy * vy + uz * vz;
603
- const c = vx * vx + vy * vy + vz * vz;
604
- const d = ux * wx + uy * wy + uz * wz;
605
- const e = vx * wx + vy * wy + vz * wz;
606
- const denom = a * c - b * b;
607
- let sNumerator = denom;
608
- let sDenominator = denom;
609
- let tNumerator = denom;
610
- let tDenominator = denom;
611
- if (a <= 1e-20) return pointSegmentDistanceSq(a0, b0, b1);
612
- if (c <= 1e-20) return pointSegmentDistanceSq(b0, a0, a1);
613
- if (denom < 1e-20) {
614
- sNumerator = 0;
615
- sDenominator = 1;
616
- tNumerator = e;
617
- tDenominator = c;
618
- } else {
619
- sNumerator = b * e - c * d;
620
- tNumerator = a * e - b * d;
621
- if (sNumerator < 0) {
622
- sNumerator = 0;
623
- tNumerator = e;
624
- tDenominator = c;
625
- } else if (sNumerator > sDenominator) {
626
- sNumerator = sDenominator;
627
- tNumerator = e + b;
628
- tDenominator = c;
629
- }
630
- }
631
- if (tNumerator < 0) {
632
- tNumerator = 0;
633
- if (-d < 0) {
634
- sNumerator = 0;
635
- } else if (-d > a) {
636
- sNumerator = sDenominator;
637
- } else {
638
- sNumerator = -d;
639
- sDenominator = a;
640
- }
641
- } else if (tNumerator > tDenominator) {
642
- tNumerator = tDenominator;
643
- if (-d + b < 0) {
644
- sNumerator = 0;
645
- } else if (-d + b > a) {
646
- sNumerator = sDenominator;
647
- } else {
648
- sNumerator = -d + b;
649
- sDenominator = a;
650
- }
651
- }
652
- const s = Math.abs(sNumerator) < 1e-20 ? 0 : sNumerator / sDenominator;
653
- const t = Math.abs(tNumerator) < 1e-20 ? 0 : tNumerator / tDenominator;
654
- const dx = wx + s * ux - t * vx;
655
- const dy = wy + s * uy - t * vy;
656
- const dz = wz + s * uz - t * vz;
657
- return dx * dx + dy * dy + dz * dz;
658
- }
659
- function trianglePoint(positions, triangle, corner) {
660
- const offset = triangle.offset + corner * 3;
661
- return [positions[offset], positions[offset + 1], positions[offset + 2]];
662
- }
663
- function bboxDistanceSq(a, b) {
664
- let total = 0;
665
- for (let axis = 0; axis < 3; axis += 1) {
666
- const gap = Math.max(0, a.min[axis] - b.max[axis], b.min[axis] - a.max[axis]);
667
- total += gap * gap;
668
- }
669
- return total;
670
- }
671
- function triangleDistanceSq(aPositions, a, bPositions, b) {
672
- const a0 = trianglePoint(aPositions, a, 0);
673
- const a1 = trianglePoint(aPositions, a, 1);
674
- const a2 = trianglePoint(aPositions, a, 2);
675
- const b0 = trianglePoint(bPositions, b, 0);
676
- const b1 = trianglePoint(bPositions, b, 1);
677
- const b2 = trianglePoint(bPositions, b, 2);
678
- return Math.min(
679
- pointTriangleDistanceSq(a0[0], a0[1], a0[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
680
- pointTriangleDistanceSq(a1[0], a1[1], a1[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
681
- pointTriangleDistanceSq(a2[0], a2[1], a2[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
682
- pointTriangleDistanceSq(b0[0], b0[1], b0[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
683
- pointTriangleDistanceSq(b1[0], b1[1], b1[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
684
- pointTriangleDistanceSq(b2[0], b2[1], b2[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
685
- segmentSegmentDistanceSq(a0, a1, b0, b1),
686
- segmentSegmentDistanceSq(a0, a1, b1, b2),
687
- segmentSegmentDistanceSq(a0, a1, b2, b0),
688
- segmentSegmentDistanceSq(a1, a2, b0, b1),
689
- segmentSegmentDistanceSq(a1, a2, b1, b2),
690
- segmentSegmentDistanceSq(a1, a2, b2, b0),
691
- segmentSegmentDistanceSq(a2, a0, b0, b1),
692
- segmentSegmentDistanceSq(a2, a0, b1, b2),
693
- segmentSegmentDistanceSq(a2, a0, b2, b0)
694
- );
695
- }
696
- function meshEntriesContactDistance(a, b, tolerance) {
697
- const toleranceSq = tolerance * tolerance;
698
- let bestDistanceSq = Infinity;
699
- for (const aTriangle of a.triangles) {
700
- for (const bTriangle of b.triangles) {
701
- if (bboxDistanceSq(aTriangle, bTriangle) > toleranceSq) continue;
702
- const distanceSq = triangleDistanceSq(a.positions, aTriangle, b.positions, bTriangle);
703
- if (distanceSq > toleranceSq) continue;
704
- if (distanceSq <= 1e-20) return 0;
705
- bestDistanceSq = Math.min(bestDistanceSq, distanceSq);
706
- }
707
- }
708
- return Number.isFinite(bestDistanceSq) ? Math.sqrt(bestDistanceSq) : null;
709
- }
710
- function intersectionVolume(a, b) {
711
- try {
712
- const hit = a.shape.intersect(b.shape);
713
- if (hit.isEmpty()) return { volume: 0 };
714
- const volume = hit.volume();
715
- return { volume: Number.isFinite(volume) ? volume : 0 };
716
- } catch (err) {
717
- const message = err instanceof Error ? err.message : String(err);
718
- return { volume: null, warning: `Could not boolean-test ${a.name} against ${b.name}: ${message}` };
719
- }
720
- }
721
- function detectPhysicalContact(a, b, options, meshCache = {}) {
722
- const gaps = aabbGaps(a, b);
723
- const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
724
- if (largestGap > options.contactTolerance) return { contact: null };
725
- const sourceMesh = meshCache.sourceMesh !== void 0 ? meshCache.sourceMesh : meshContactDataFor(a);
726
- const targetMesh = meshCache.targetMesh !== void 0 ? meshCache.targetMesh : meshContactDataFor(b);
727
- if (sourceMesh && targetMesh) {
728
- const distance = meshEntriesContactDistance(sourceMesh, targetMesh, options.contactTolerance);
729
- if (distance != null) {
730
- return {
731
- contact: {
732
- kind: "touching",
733
- method: "mesh-surface-distance",
734
- gap: distance
735
- }
736
- };
737
- }
738
- }
739
- const bboxOverlaps = !hasPositiveGap(gaps) && aabbInteriorOverlaps(a, b);
740
- if (options.exactGeometry && bboxOverlaps) {
741
- if (aabbOverlapVolume(a, b) <= options.minOverlapVolume) return { contact: null };
742
- const overlap = intersectionVolume(a, b);
743
- if (overlap.volume != null && overlap.volume > options.minOverlapVolume) {
744
- return {
745
- contact: {
746
- kind: "overlap",
747
- method: "boolean-intersection",
748
- gap: 0,
749
- overlapVolume: overlap.volume
750
- },
751
- warning: overlap.warning
752
- };
753
- }
754
- return { contact: null, warning: overlap.warning };
755
- }
756
- if (bboxOverlaps && options.mergeOverlappingBBoxes) {
757
- return {
758
- contact: {
759
- kind: "overlap",
760
- method: "bbox-overlap",
761
- gap: 0,
762
- overlapVolume: aabbOverlapVolume(a, b)
763
- }
764
- };
765
- }
766
- if (options.mergeTouchingBBoxes) {
767
- const contact = contactFromBBoxes(a, b, options.contactTolerance);
768
- if (contact.touching) {
769
- return {
770
- contact: {
771
- kind: "touching",
772
- method: "bbox-contact",
773
- gap: contact.gap,
774
- axis: contact.axis
775
- }
776
- };
777
- }
778
- }
779
- return { contact: null };
780
- }
781
- const DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS = {
782
- contactTolerance: 0.05,
783
- minOverlapVolume: 0.1,
784
- exactGeometry: true
785
- };
786
- let UnionFind$2 = class UnionFind {
787
- constructor(size) {
788
- __publicField(this, "parent");
789
- __publicField(this, "rank");
790
- this.parent = Array.from({ length: size }, (_, index) => index);
791
- this.rank = Array.from({ length: size }, () => 0);
792
- }
793
- find(value) {
794
- const parent = this.parent[value];
795
- if (parent === value) return value;
796
- const root = this.find(parent);
797
- this.parent[value] = root;
798
- return root;
799
- }
800
- union(a, b) {
801
- const rootA = this.find(a);
802
- const rootB = this.find(b);
803
- if (rootA === rootB) return;
804
- if (this.rank[rootA] < this.rank[rootB]) {
805
- this.parent[rootA] = rootB;
806
- return;
807
- }
808
- if (this.rank[rootA] > this.rank[rootB]) {
809
- this.parent[rootB] = rootA;
810
- return;
811
- }
812
- this.parent[rootB] = rootA;
813
- this.rank[rootA] += 1;
814
- }
815
- };
816
- function cloneVec3$1(value) {
817
- return [value[0], value[1], value[2]];
818
- }
819
- function emptyBBox$2() {
820
- return {
821
- min: [Infinity, Infinity, Infinity],
822
- max: [-Infinity, -Infinity, -Infinity]
823
- };
824
- }
825
- function expandBBox$2(target, min, max) {
826
- for (let axis = 0; axis < 3; axis += 1) {
827
- target.min[axis] = Math.min(target.min[axis], min[axis]);
828
- target.max[axis] = Math.max(target.max[axis], max[axis]);
829
- }
830
- }
831
- function collectCandidatePairs(entries, tolerance) {
832
- if (entries.length < 2) return [];
833
- const index = new AabbSpatialIndex(entries);
834
- const pairs = index.overlapPairs({ padding: tolerance }).pairs.map((pair) => ({
835
- sourceIndex: pair.sourceIndex,
836
- targetIndex: pair.targetIndex,
837
- gaps: aabbGaps(entries[pair.sourceIndex], entries[pair.targetIndex])
838
- })).filter((pair) => Math.max(pair.gaps[0], pair.gaps[1], pair.gaps[2]) <= tolerance);
839
- pairs.sort((a, b) => a.sourceIndex - b.sourceIndex || a.targetIndex - b.targetIndex);
840
- return pairs;
841
- }
842
- function bodyCountForEntry$1(entry) {
843
- if (typeof entry.bodyCount === "number" && Number.isFinite(entry.bodyCount)) {
844
- return Math.max(0, Math.round(entry.bodyCount));
845
- }
846
- return 1;
847
- }
848
- function makeEdge(entries, sourceIndex, targetIndex, edge) {
849
- const source = entries[sourceIndex];
850
- const target = entries[targetIndex];
851
- return {
852
- sourceIndex,
853
- targetIndex,
854
- sourceId: source.id,
855
- targetId: target.id,
856
- sourceName: source.name,
857
- targetName: target.name,
858
- ...edge
859
- };
860
- }
861
- function analyzePhysicalConnectivity(entries, rawOptions = {}) {
862
- const exactGeometry = rawOptions.exactGeometry ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.exactGeometry;
863
- const options = {
864
- contactTolerance: rawOptions.contactTolerance ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance,
865
- minOverlapVolume: rawOptions.minOverlapVolume ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.minOverlapVolume,
866
- exactGeometry,
867
- mergeOverlappingBBoxes: rawOptions.mergeOverlappingBBoxes ?? !exactGeometry,
868
- mergeTouchingBBoxes: rawOptions.mergeTouchingBBoxes ?? !exactGeometry
869
- };
870
- const warnings = [];
871
- const edges = [];
872
- const unionFind = new UnionFind$2(entries.length);
873
- const meshDataByIndex = /* @__PURE__ */ new Map();
874
- entries.forEach((entry, index) => {
875
- const meshData = meshContactDataFor(entry);
876
- if (meshData) meshDataByIndex.set(index, meshData);
877
- });
878
- for (const pair of collectCandidatePairs(entries, options.contactTolerance)) {
879
- const i = pair.sourceIndex;
880
- const j = pair.targetIndex;
881
- const detection = detectPhysicalContact(entries[i], entries[j], options, {
882
- sourceMesh: meshDataByIndex.get(i) ?? null,
883
- targetMesh: meshDataByIndex.get(j) ?? null
884
- });
885
- if (detection.warning) warnings.push(detection.warning);
886
- if (!detection.contact) continue;
887
- unionFind.union(i, j);
888
- edges.push(makeEdge(entries, i, j, detection.contact));
889
- }
890
- const objects = entries.map((entry, index) => ({
891
- index,
892
- id: entry.id,
893
- name: entry.name,
894
- groupName: entry.groupName,
895
- treePath: entry.treePath,
896
- mock: entry.mock === true,
897
- bodyCount: bodyCountForEntry$1(entry),
898
- bbox: {
899
- min: cloneVec3$1(entry.min),
900
- max: cloneVec3$1(entry.max)
901
- },
902
- componentIndex: 0
903
- }));
904
- const componentByRoot = /* @__PURE__ */ new Map();
905
- const rootToComponentIndex = /* @__PURE__ */ new Map();
906
- for (let objectIndex = 0; objectIndex < objects.length; objectIndex += 1) {
907
- const root = unionFind.find(objectIndex);
908
- let component = componentByRoot.get(root);
909
- if (!component) {
910
- component = {
911
- index: componentByRoot.size + 1,
912
- objectIndexes: [],
913
- objectIds: [],
914
- objectNames: [],
915
- objectCount: 0,
916
- bodyCount: 0,
917
- bbox: emptyBBox$2()
918
- };
919
- componentByRoot.set(root, component);
920
- rootToComponentIndex.set(root, component.index);
921
- }
922
- const object = objects[objectIndex];
923
- object.componentIndex = rootToComponentIndex.get(root) ?? component.index;
924
- component.objectIndexes.push(object.index);
925
- component.objectIds.push(object.id);
926
- component.objectNames.push(object.name);
927
- component.objectCount += 1;
928
- component.bodyCount += object.bodyCount;
929
- expandBBox$2(component.bbox, object.bbox.min, object.bbox.max);
930
- }
931
- const components = [...componentByRoot.values()];
932
- return {
933
- method: options.exactGeometry ? "mesh-contact-plus-boolean-overlap" : "bbox-neighborhood",
934
- options,
935
- objectCount: objects.length,
936
- componentCount: components.length,
937
- objects,
938
- components,
939
- edges,
940
- warnings
941
- };
942
- }
943
460
  const EPSILON = 1e-9;
944
461
  function intervalGap(aMin, aMax, bMin, bMax) {
945
462
  if (aMax < bMin) return bMin - aMax;
@@ -1135,7 +652,7 @@ const DEFAULT_CONTACT_TOLERANCE = 0.05;
1135
652
  const DEFAULT_BED_TOLERANCE = 0.05;
1136
653
  const DEFAULT_GROUND_Z = 0;
1137
654
  const MIN_SHAPE_OVERLAP_VOLUME = 1e-9;
1138
- let UnionFind$1 = class UnionFind2 {
655
+ let UnionFind$1 = class UnionFind {
1139
656
  constructor(size) {
1140
657
  __publicField(this, "parent");
1141
658
  __publicField(this, "rank");
@@ -1481,150 +998,6 @@ ${body}
1481
998
  pathCount
1482
999
  };
1483
1000
  }
1484
- const DEFAULT_THICKNESS_INSPECTION_OPTIONS = {
1485
- minThickness: 1.2,
1486
- warnThickness: 2,
1487
- maxThickness: 6,
1488
- maxSamplesPerObject: 5e3,
1489
- contactTolerance: DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance
1490
- };
1491
- const THICKNESS_COLORS = {
1492
- critical: [255, 28, 28],
1493
- warning: [255, 150, 0],
1494
- ok: [60, 220, 90],
1495
- thick: [70, 145, 255],
1496
- unknown: [90, 90, 90]
1497
- };
1498
- function finitePositive(value, fallback, label) {
1499
- if (value === void 0) return fallback;
1500
- if (!Number.isFinite(value) || value <= 0) {
1501
- throw new Error(`${label} must be a positive finite number.`);
1502
- }
1503
- return value;
1504
- }
1505
- function finiteNonNegative(value, fallback, label) {
1506
- if (value === void 0) return fallback;
1507
- if (!Number.isFinite(value) || value < 0) {
1508
- throw new Error(`${label} must be a non-negative finite number.`);
1509
- }
1510
- return value;
1511
- }
1512
- function resolveThicknessInspectionOptions(raw = {}) {
1513
- const minThickness = finitePositive(raw.minThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.minThickness, "minThickness");
1514
- const warnThickness = finitePositive(raw.warnThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.warnThickness, "warnThickness");
1515
- const maxThickness = finitePositive(raw.maxThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxThickness, "maxThickness");
1516
- const maxSamplesPerObject = finitePositive(
1517
- raw.maxSamplesPerObject,
1518
- DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxSamplesPerObject,
1519
- "maxSamplesPerObject"
1520
- );
1521
- const contactTolerance = finiteNonNegative(
1522
- raw.contactTolerance,
1523
- DEFAULT_THICKNESS_INSPECTION_OPTIONS.contactTolerance,
1524
- "contactTolerance"
1525
- );
1526
- if (minThickness > warnThickness) {
1527
- throw new Error("minThickness must be less than or equal to warnThickness.");
1528
- }
1529
- if (warnThickness > maxThickness) {
1530
- throw new Error("warnThickness must be less than or equal to maxThickness.");
1531
- }
1532
- return {
1533
- minThickness,
1534
- warnThickness,
1535
- maxThickness,
1536
- maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject)),
1537
- contactTolerance
1538
- };
1539
- }
1540
- function lerp(a, b, t) {
1541
- return a + (b - a) * Math.max(0, Math.min(1, t));
1542
- }
1543
- function lerpColor$1(a, b, t) {
1544
- return [Math.round(lerp(a[0], b[0], t)), Math.round(lerp(a[1], b[1], t)), Math.round(lerp(a[2], b[2], t))];
1545
- }
1546
- function thicknessClass(thickness, options) {
1547
- if (thickness == null || !Number.isFinite(thickness) || thickness <= 0) return "unknown";
1548
- if (thickness <= options.minThickness) return "critical";
1549
- if (thickness <= options.warnThickness) return "warning";
1550
- if (thickness <= options.maxThickness) return "ok";
1551
- return "thick";
1552
- }
1553
- function thicknessColor(thickness, options) {
1554
- const cls = thicknessClass(thickness, options);
1555
- if (cls === "unknown") return THICKNESS_COLORS.unknown;
1556
- if (cls === "critical") return THICKNESS_COLORS.critical;
1557
- if (cls === "warning") {
1558
- const span = Math.max(1e-9, options.warnThickness - options.minThickness);
1559
- return lerpColor$1(THICKNESS_COLORS.critical, THICKNESS_COLORS.warning, ((thickness ?? 0) - options.minThickness) / span);
1560
- }
1561
- if (cls === "ok") {
1562
- const span = Math.max(1e-9, options.maxThickness - options.warnThickness);
1563
- return lerpColor$1(THICKNESS_COLORS.ok, THICKNESS_COLORS.thick, ((thickness ?? 0) - options.warnThickness) / span);
1564
- }
1565
- return THICKNESS_COLORS.thick;
1566
- }
1567
- function sampleArea(sample) {
1568
- const area = sample.area ?? 1;
1569
- return Number.isFinite(area) && area > 0 ? area : 1;
1570
- }
1571
- function weightedQuantile(samples, q) {
1572
- if (samples.length === 0) return null;
1573
- const sorted = [...samples].sort((a, b) => a.thickness - b.thickness);
1574
- const totalArea = sorted.reduce((sum, sample) => sum + sample.area, 0);
1575
- const target = totalArea * Math.max(0, Math.min(1, q));
1576
- let cumulative = 0;
1577
- for (const sample of sorted) {
1578
- cumulative += sample.area;
1579
- if (cumulative >= target) return sample.thickness;
1580
- }
1581
- return sorted[sorted.length - 1].thickness;
1582
- }
1583
- function percent(part, total) {
1584
- if (total <= 0) return 0;
1585
- return part / total * 100;
1586
- }
1587
- function summarizeThicknessSamples(samples, options) {
1588
- const resolved = [];
1589
- let totalArea = 0;
1590
- let resolvedArea = 0;
1591
- let unresolvedArea = 0;
1592
- let criticalArea = 0;
1593
- let warningArea = 0;
1594
- let weightedSum = 0;
1595
- for (const sample of samples) {
1596
- const area = sampleArea(sample);
1597
- totalArea += area;
1598
- const value = sample.thickness;
1599
- if (value == null || !Number.isFinite(value) || value <= 0) {
1600
- unresolvedArea += area;
1601
- continue;
1602
- }
1603
- resolved.push({ thickness: value, area });
1604
- resolvedArea += area;
1605
- weightedSum += value * area;
1606
- if (value <= options.minThickness) {
1607
- criticalArea += area;
1608
- } else if (value <= options.warnThickness) {
1609
- warningArea += area;
1610
- }
1611
- }
1612
- const values = resolved.map((sample) => sample.thickness);
1613
- return {
1614
- sampleCount: samples.length,
1615
- resolvedCount: resolved.length,
1616
- unresolvedCount: samples.length - resolved.length,
1617
- minThickness: values.length > 0 ? Math.min(...values) : null,
1618
- p05Thickness: weightedQuantile(resolved, 0.05),
1619
- medianThickness: weightedQuantile(resolved, 0.5),
1620
- meanThickness: resolvedArea > 0 ? weightedSum / resolvedArea : null,
1621
- maxThickness: values.length > 0 ? Math.max(...values) : null,
1622
- criticalAreaPercent: percent(criticalArea, resolvedArea),
1623
- warningAreaPercent: percent(warningArea, resolvedArea),
1624
- belowWarnAreaPercent: percent(criticalArea + warningArea, resolvedArea),
1625
- unresolvedAreaPercent: percent(unresolvedArea, totalArea)
1626
- };
1627
- }
1628
1001
  const MIN_TRIANGLE_AREA = 1e-12;
1629
1002
  const R2_ALPHA = 0.7548776662466927;
1630
1003
  const R2_BETA = 0.5698402909980532;
@@ -1865,79 +1238,6 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}, context = {})
1865
1238
  warnings
1866
1239
  };
1867
1240
  }
1868
- const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
1869
- smoothAngleDeg: 5,
1870
- sharpAngleDeg: 30,
1871
- harshAngleDeg: 90,
1872
- maxSamplesPerObject: 5e3
1873
- };
1874
- const ROUGHNESS_COLORS = {
1875
- smooth: [62, 72, 84],
1876
- moderate: [255, 214, 0],
1877
- sharp: [255, 124, 34],
1878
- harsh: [255, 42, 96]
1879
- };
1880
- function resolveRoughnessInspectionOptions(raw = {}) {
1881
- const options = {
1882
- smoothAngleDeg: raw.smoothAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.smoothAngleDeg,
1883
- sharpAngleDeg: raw.sharpAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.sharpAngleDeg,
1884
- harshAngleDeg: raw.harshAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.harshAngleDeg,
1885
- maxSamplesPerObject: raw.maxSamplesPerObject ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.maxSamplesPerObject
1886
- };
1887
- if (!Number.isFinite(options.smoothAngleDeg) || options.smoothAngleDeg < 0) {
1888
- throw new Error(`smoothAngleDeg must be a finite non-negative angle (got ${options.smoothAngleDeg}).`);
1889
- }
1890
- if (!Number.isFinite(options.sharpAngleDeg) || options.sharpAngleDeg <= options.smoothAngleDeg) {
1891
- throw new Error(`sharpAngleDeg must be greater than smoothAngleDeg (got ${options.sharpAngleDeg}).`);
1892
- }
1893
- if (!Number.isFinite(options.harshAngleDeg) || options.harshAngleDeg <= options.sharpAngleDeg || options.harshAngleDeg > 180) {
1894
- throw new Error(`harshAngleDeg must be greater than sharpAngleDeg and <= 180 (got ${options.harshAngleDeg}).`);
1895
- }
1896
- if (!Number.isFinite(options.maxSamplesPerObject) || options.maxSamplesPerObject <= 0) {
1897
- throw new Error(`maxSamplesPerObject must be a positive finite number (got ${options.maxSamplesPerObject}).`);
1898
- }
1899
- return {
1900
- ...options,
1901
- maxSamplesPerObject: Math.max(1, Math.floor(options.maxSamplesPerObject))
1902
- };
1903
- }
1904
- function roughnessClassForAngle(angleDeg, options) {
1905
- if (angleDeg >= options.harshAngleDeg) return "harsh";
1906
- if (angleDeg >= options.sharpAngleDeg) return "sharp";
1907
- if (angleDeg >= options.smoothAngleDeg) return "moderate";
1908
- return "smooth";
1909
- }
1910
- function roughnessScoreForAngle(angleDeg, options) {
1911
- if (angleDeg < options.sharpAngleDeg) return 0;
1912
- if (angleDeg < options.harshAngleDeg) {
1913
- return MathUtils.lerp(0.48, 0.82, (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg));
1914
- }
1915
- return 1;
1916
- }
1917
- function roughnessColorForAngle(angleDeg, options) {
1918
- const cls = roughnessClassForAngle(angleDeg, options);
1919
- if (cls === "smooth" || cls === "harsh") return ROUGHNESS_COLORS[cls];
1920
- if (cls === "moderate") {
1921
- return lerpRgb(
1922
- ROUGHNESS_COLORS.moderate,
1923
- ROUGHNESS_COLORS.sharp,
1924
- (angleDeg - options.smoothAngleDeg) / (options.sharpAngleDeg - options.smoothAngleDeg)
1925
- );
1926
- }
1927
- return lerpRgb(
1928
- ROUGHNESS_COLORS.sharp,
1929
- ROUGHNESS_COLORS.harsh,
1930
- (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg)
1931
- );
1932
- }
1933
- function lerpRgb(a, b, t) {
1934
- const clamped = MathUtils.clamp(t, 0, 1);
1935
- return [
1936
- Math.round(MathUtils.lerp(a[0], b[0], clamped)),
1937
- Math.round(MathUtils.lerp(a[1], b[1], clamped)),
1938
- Math.round(MathUtils.lerp(a[2], b[2], clamped))
1939
- ];
1940
- }
1941
1241
  function emptyRoughnessSummary() {
1942
1242
  return {
1943
1243
  triangleCount: 0,
@@ -2162,7 +1462,7 @@ function pointSegmentDistance(point, start, end) {
2162
1462
  return point.distanceTo(segment.multiplyScalar(t).add(start));
2163
1463
  }
2164
1464
  const DEFAULT_VERTEX_TOLERANCE = 1e-5;
2165
- class UnionFind3 {
1465
+ class UnionFind2 {
2166
1466
  constructor(size) {
2167
1467
  __publicField(this, "parent");
2168
1468
  __publicField(this, "rank");
@@ -2221,7 +1521,7 @@ function analyzeMeshConnectedComponents(positions, vertexTolerance = DEFAULT_VER
2221
1521
  const vertexComponentIndexes = new Int32Array(vertexCount);
2222
1522
  if (triangleCount === 0) return { components: [], vertexComponentIndexes };
2223
1523
  const tolerance = Number.isFinite(vertexTolerance) && vertexTolerance > 0 ? vertexTolerance : DEFAULT_VERTEX_TOLERANCE;
2224
- const unionFind = new UnionFind3(triangleCount);
1524
+ const unionFind = new UnionFind2(triangleCount);
2225
1525
  const vertexOwnerByKey = /* @__PURE__ */ new Map();
2226
1526
  for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
2227
1527
  for (let corner = 0; corner < 3; corner += 1) {