forgecad 0.9.5 → 0.9.7
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.
- package/dist/assets/{AdminPage-uTtcSXtn.js → AdminPage-DX0mpSZT.js} +1 -1
- package/dist/assets/{BlogPage-DYJMjWx3.js → BlogPage-CI_P0_Pf.js} +1 -1
- package/dist/assets/{DocsPage-C58f0K5v.js → DocsPage-DLhIIZyJ.js} +3 -3
- package/dist/assets/EditorApp-BujZvuwX.js +12874 -0
- package/dist/assets/{EditorApp-DS0AIUrZ.css → EditorApp-DfFT2Dn8.css} +1 -0
- package/dist/assets/{EmbedViewer-CMXWA2LX.js → EmbedViewer-0S0qXKog.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-CAu2OZFn.js → LandingPageProofDriven-O_yMtAri.js} +1 -1
- package/dist/assets/{PricingPage-BIgW7m3X.js → PricingPage-DGkX3Ahr.js} +1 -1
- package/dist/assets/{SettingsPage-N1l1tMXO.js → SettingsPage-DBsqTB_y.js} +82 -22
- package/dist/assets/{app-CFy7g5WP.js → app-BE2nD6Yz.js} +1246 -191
- package/dist/assets/cli/{render-BrVVdj_T.js → render-iP9qh475.js} +841 -586
- package/dist/assets/{evalWorker-c_SB9gg3.js → evalWorker-Ds5U4xtN.js} +2732 -112
- package/dist/assets/inspectWorker-Dll4eVyD.js +12620 -0
- package/dist/assets/{manifold-Dp6pvFr6.js → manifold-Bk26ViCr.js} +1 -1
- package/dist/assets/{manifold-CRoBhJKH.js → manifold-DjYsd7A_.js} +2 -2
- package/dist/assets/{manifold-Cjk7WhRs.js → manifold-sJ-axdXM.js} +1 -1
- package/dist/assets/{renderSceneState-3DfsSASX.js → renderSceneState-Bngp5MrQ.js} +1 -1
- package/dist/assets/{reportWorker-BLkuIoS8.js → reportWorker-CU8RZ4O0.js} +2715 -112
- package/dist/assets/{sectionPlaneMath-CykEnkvQ.js → sectionPlaneMath-BdTjyVfs.js} +3213 -252
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +1 -1
- package/dist/docs-raw/AI/usage.md +7 -2
- package/dist/docs-raw/CLI.md +82 -53
- package/dist/docs-raw/beta-operations.md +9 -0
- package/dist/docs-raw/coding.md +1 -1
- package/dist/docs-raw/deployment.md +38 -23
- package/dist/docs-raw/generated/concepts.md +141 -7
- package/dist/docs-raw/generated/core.md +206 -1
- package/dist/docs-raw/generated/curves.md +97 -5
- package/dist/docs-raw/generated/lib.md +17 -1
- package/dist/docs-raw/generated/sketch.md +9 -1
- package/dist/docs-raw/generated/viewport.md +1 -1
- package/dist/docs-raw/guides/inspection-bundles.md +45 -16
- package/dist/docs-raw/platform/auth.md +2 -0
- package/dist/docs-raw/platform/google-oauth-setup.md +4 -0
- package/dist/docs-raw/runbook.md +3 -3
- package/dist/docs-raw/skills/forgecad-make-a-model.md +87 -8
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +14 -6
- package/dist/docs-raw/skills/forgecad-render-inspect.md +1 -1
- package/dist/docs-raw/skills/index.md +2 -2
- package/dist/index.html +1 -1
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +8725 -4747
- package/dist-cli/forgecad.js.map +1 -1
- package/dist-skill/CONTEXT.md +375 -25
- package/dist-skill/docs/CLI.md +82 -53
- package/dist-skill/docs/generated/core.md +206 -1
- package/dist-skill/docs/generated/curves.md +97 -5
- package/dist-skill/docs/generated/lib.md +17 -1
- package/dist-skill/docs/generated/sketch.md +9 -1
- package/dist-skill/docs/generated/viewport.md +1 -1
- package/dist-skill/docs/guides/inspection-bundles.md +45 -16
- package/dist-skill/docs-dev/CLI.md +82 -53
- package/dist-skill/docs-dev/coding.md +1 -1
- package/dist-skill/docs-dev/generated/core.md +206 -1
- package/dist-skill/docs-dev/generated/curves.md +97 -5
- package/dist-skill/docs-dev/generated/lib.md +17 -1
- package/dist-skill/docs-dev/generated/sketch.md +9 -1
- package/dist-skill/docs-dev/generated/viewport.md +1 -1
- package/dist-skill/docs-dev/guides/inspection-bundles.md +45 -16
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +87 -8
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +14 -6
- package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +5 -3
- package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +7 -5
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +1 -1
- package/examples/api/bolted-service-cover.forge.js +17 -0
- package/examples/api/cable-gland-anchor.forge.js +14 -0
- package/examples/api/captured-cartridge-guide.forge.js +14 -0
- package/examples/api/captured-linear-slide.forge.js +13 -0
- package/examples/api/clevis-pin-joint.forge.js +13 -0
- package/examples/api/datum-enclosure.forge.js +16 -0
- package/examples/api/guided-loft-olive-oil-bottle.forge.js +135 -0
- package/examples/api/hose-barb-port.forge.js +14 -0
- package/examples/api/intentional-overlap-overmold.forge.js +16 -0
- package/examples/api/knuckled-hinge-assembly.forge.js +15 -0
- package/examples/api/living-hinge-cover.forge.js +14 -0
- package/examples/api/pcb-terminal-block.forge.js +22 -0
- package/examples/api/pinned-lever-pivot-stack.forge.js +14 -0
- package/examples/api/retained-shaft-knob-stack.forge.js +15 -0
- package/examples/api/routed-tube-clip.forge.js +15 -0
- package/examples/api/seated-bearing-stack.forge.js +30 -0
- package/examples/api/snap-latch-cover.forge.js +14 -0
- package/examples/api/static-assembly-connectors.forge.js +14 -16
- package/examples/api/thumb-screw-clamp.forge.js +15 -0
- package/package.json +20 -2
- package/dist/assets/EditorApp-DNH1TEz1.js +0 -12729
|
@@ -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,
|
|
5
|
-
import { m as mergeViewportRenderSceneStates, p as parseRenderSceneCliSpec } from "../renderSceneState-
|
|
4
|
+
import { D as DoubleSide, bI as initSolverWasm, bH as initKernel, S as Scene, bJ as BoxGeometry, be as MeshStandardMaterial, a4 as BackSide, b0 as PointLight, M as Mesh, aa as MeshBasicMaterial, bK as localAabbPlaneRelation, h as Vector2, bL as ShapeUtils, bM as aabbInteriorOverlaps, bN as aabbOverlapVolume, bO as AabbSpatialIndex, bP as aabbGaps, g as Vector3, R as Raycaster, aU as BufferAttribute, a0 as MathUtils, G as Box3, e as Color, aC as resolveForgeRenderStyle, ba as getRenderStylePreset, ax as setParamOverrides, b7 as runScript, bQ as Group, b3 as shapeToGeometry, b8 as MeshPhysicalMaterial, bb as AdditiveBlending, aH as LineBasicMaterial, b9 as LineSegments, aG as BufferGeometry, P as PerspectiveCamera, k as ShaderMaterial, bR as intersectWithPlane, W as WebGLRenderer, A as ACESFilmicToneMapping, c as SRGBColorSpace, bS as parseCameraCliSpec, bT as PMREMGenerator, aV as CanvasTexture, aW as Object3D, aX as FogExp2, aY as Fog, aZ as AmbientLight, b1 as DirectionalLight, a_ as HemisphereLight, bA as findJointAnimationClip, p as Plane, Y as Vector4, $ as Matrix4, bh as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bg as buildSdfRaymarchFragmentShader, O as OrthographicCamera, bB as resolveJointAnimation, bC as resolveJointViewValues, bU as PointsMaterial, bV as Points, b2 as analyzeCollisionIntersections, bW as serializeCollisionFinding, bX as worldAuthorPlaneToLocal, a$ as SpotLight } from "../sectionPlaneMath-BdTjyVfs.js";
|
|
5
|
+
import { m as mergeViewportRenderSceneStates, p as parseRenderSceneCliSpec } from "../renderSceneState-Bngp5MrQ.js";
|
|
6
6
|
const CAD_MATERIAL_PROPS = {
|
|
7
7
|
color: 6003669,
|
|
8
8
|
metalness: 0.05,
|
|
@@ -457,146 +457,13 @@ function computeMeshSectionCap(mesh, planeInput) {
|
|
|
457
457
|
warnings: stitched.warnings.length > 0 ? stitched.warnings : void 0
|
|
458
458
|
};
|
|
459
459
|
}
|
|
460
|
-
const DEFAULT_COLLISION_INSPECTION_OPTIONS = {
|
|
461
|
-
minOverlapVolume: 0.1
|
|
462
|
-
};
|
|
463
|
-
function cloneVec3$1(value) {
|
|
464
|
-
return [value[0], value[1], value[2]];
|
|
465
|
-
}
|
|
466
|
-
function isIdentityTransform(matrix) {
|
|
467
|
-
if (!matrix) return true;
|
|
468
|
-
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
469
|
-
return identity.every((value, index) => Math.abs(matrix[index] - value) <= 1e-12);
|
|
470
|
-
}
|
|
471
|
-
function transformPoint(matrix, point) {
|
|
472
|
-
const [x, y, z] = point;
|
|
473
|
-
return [
|
|
474
|
-
matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12],
|
|
475
|
-
matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13],
|
|
476
|
-
matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]
|
|
477
|
-
];
|
|
478
|
-
}
|
|
479
|
-
function transformBBox(min, max, matrix) {
|
|
480
|
-
const corners = [
|
|
481
|
-
[min[0], min[1], min[2]],
|
|
482
|
-
[min[0], min[1], max[2]],
|
|
483
|
-
[min[0], max[1], min[2]],
|
|
484
|
-
[min[0], max[1], max[2]],
|
|
485
|
-
[max[0], min[1], min[2]],
|
|
486
|
-
[max[0], min[1], max[2]],
|
|
487
|
-
[max[0], max[1], min[2]],
|
|
488
|
-
[max[0], max[1], max[2]]
|
|
489
|
-
];
|
|
490
|
-
const outMin = [Infinity, Infinity, Infinity];
|
|
491
|
-
const outMax = [-Infinity, -Infinity, -Infinity];
|
|
492
|
-
for (const corner of corners) {
|
|
493
|
-
const transformed = transformPoint(matrix, corner);
|
|
494
|
-
for (let axis = 0; axis < 3; axis += 1) {
|
|
495
|
-
outMin[axis] = Math.min(outMin[axis], transformed[axis]);
|
|
496
|
-
outMax[axis] = Math.max(outMax[axis], transformed[axis]);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return { min: outMin, max: outMax };
|
|
500
|
-
}
|
|
501
|
-
function prepareEntry$1(entry) {
|
|
502
|
-
if (isIdentityTransform(entry.transform)) {
|
|
503
|
-
return {
|
|
504
|
-
...entry,
|
|
505
|
-
min: cloneVec3$1(entry.min),
|
|
506
|
-
max: cloneVec3$1(entry.max)
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
const bbox = transformBBox(entry.min, entry.max, entry.transform);
|
|
510
|
-
return {
|
|
511
|
-
...entry,
|
|
512
|
-
shape: entry.shape.transform(entry.transform),
|
|
513
|
-
min: bbox.min,
|
|
514
|
-
max: bbox.max
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
function bboxOverlaps(a, b) {
|
|
518
|
-
return [0, 1, 2].every((axis) => a.min[axis] < b.max[axis] && a.max[axis] > b.min[axis]);
|
|
519
|
-
}
|
|
520
|
-
function collisionId(a, b) {
|
|
521
|
-
return `${a.id}__${b.id}`;
|
|
522
|
-
}
|
|
523
|
-
function serializeCollisionFinding(finding) {
|
|
524
|
-
return {
|
|
525
|
-
index: finding.index,
|
|
526
|
-
id: finding.id,
|
|
527
|
-
sourceIndex: finding.sourceIndex,
|
|
528
|
-
targetIndex: finding.targetIndex,
|
|
529
|
-
sourceId: finding.sourceId,
|
|
530
|
-
targetId: finding.targetId,
|
|
531
|
-
sourceName: finding.sourceName,
|
|
532
|
-
targetName: finding.targetName,
|
|
533
|
-
overlapVolume: finding.overlapVolume
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
function analyzeCollisionIntersections(entries, rawOptions = {}) {
|
|
537
|
-
const options = {
|
|
538
|
-
minOverlapVolume: rawOptions.minOverlapVolume ?? DEFAULT_COLLISION_INSPECTION_OPTIONS.minOverlapVolume
|
|
539
|
-
};
|
|
540
|
-
const warnings = [];
|
|
541
|
-
const collisions = [];
|
|
542
|
-
const preparedEntries = entries.map((entry) => prepareEntry$1(entry));
|
|
543
|
-
for (let i = 0; i < preparedEntries.length; i += 1) {
|
|
544
|
-
for (let j = i + 1; j < preparedEntries.length; j += 1) {
|
|
545
|
-
const a = preparedEntries[i];
|
|
546
|
-
const b = preparedEntries[j];
|
|
547
|
-
if (!bboxOverlaps(a, b)) continue;
|
|
548
|
-
try {
|
|
549
|
-
const hit = a.shape.intersect(b.shape);
|
|
550
|
-
if (hit.isEmpty()) continue;
|
|
551
|
-
const overlapVolume = hit.volume();
|
|
552
|
-
if (!Number.isFinite(overlapVolume) || overlapVolume <= options.minOverlapVolume) continue;
|
|
553
|
-
collisions.push({
|
|
554
|
-
index: collisions.length + 1,
|
|
555
|
-
id: collisionId(a, b),
|
|
556
|
-
sourceIndex: i,
|
|
557
|
-
targetIndex: j,
|
|
558
|
-
sourceId: a.id,
|
|
559
|
-
targetId: b.id,
|
|
560
|
-
sourceName: a.name,
|
|
561
|
-
targetName: b.name,
|
|
562
|
-
overlapVolume,
|
|
563
|
-
shape: hit
|
|
564
|
-
});
|
|
565
|
-
} catch (err) {
|
|
566
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
567
|
-
warnings.push(`Could not boolean-test ${a.name} against ${b.name}: ${message}`);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
const objects = preparedEntries.map((entry, index) => ({
|
|
572
|
-
index,
|
|
573
|
-
id: entry.id,
|
|
574
|
-
name: entry.name,
|
|
575
|
-
groupName: entry.groupName,
|
|
576
|
-
treePath: entry.treePath,
|
|
577
|
-
mock: entry.mock === true,
|
|
578
|
-
bbox: {
|
|
579
|
-
min: cloneVec3$1(entry.min),
|
|
580
|
-
max: cloneVec3$1(entry.max)
|
|
581
|
-
}
|
|
582
|
-
}));
|
|
583
|
-
return {
|
|
584
|
-
method: "boolean-intersection",
|
|
585
|
-
options,
|
|
586
|
-
objectCount: objects.length,
|
|
587
|
-
collisionCount: collisions.length,
|
|
588
|
-
objects,
|
|
589
|
-
collisions,
|
|
590
|
-
warnings
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
460
|
const DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS = {
|
|
594
461
|
contactTolerance: 0.05,
|
|
595
462
|
minOverlapVolume: 0.1,
|
|
596
|
-
exactGeometry:
|
|
463
|
+
exactGeometry: true
|
|
597
464
|
};
|
|
598
465
|
const AXIS_NAMES = ["x", "y", "z"];
|
|
599
|
-
class UnionFind {
|
|
466
|
+
let UnionFind$1 = class UnionFind {
|
|
600
467
|
constructor(size) {
|
|
601
468
|
__publicField(this, "parent");
|
|
602
469
|
__publicField(this, "rank");
|
|
@@ -625,103 +492,42 @@ class UnionFind {
|
|
|
625
492
|
this.parent[rootB] = rootA;
|
|
626
493
|
this.rank[rootA] += 1;
|
|
627
494
|
}
|
|
628
|
-
}
|
|
495
|
+
};
|
|
629
496
|
function cloneVec3(value) {
|
|
630
497
|
return [value[0], value[1], value[2]];
|
|
631
498
|
}
|
|
632
|
-
function emptyBBox() {
|
|
499
|
+
function emptyBBox$1() {
|
|
633
500
|
return {
|
|
634
501
|
min: [Infinity, Infinity, Infinity],
|
|
635
502
|
max: [-Infinity, -Infinity, -Infinity]
|
|
636
503
|
};
|
|
637
504
|
}
|
|
638
|
-
function expandBBox(target, min, max) {
|
|
505
|
+
function expandBBox$1(target, min, max) {
|
|
639
506
|
for (let axis = 0; axis < 3; axis += 1) {
|
|
640
507
|
target.min[axis] = Math.min(target.min[axis], min[axis]);
|
|
641
508
|
target.max[axis] = Math.max(target.max[axis], max[axis]);
|
|
642
509
|
}
|
|
643
510
|
}
|
|
644
|
-
function intervalGap$1(aMin, aMax, bMin, bMax) {
|
|
645
|
-
if (aMax < bMin) return bMin - aMax;
|
|
646
|
-
if (bMax < aMin) return aMin - bMax;
|
|
647
|
-
return 0;
|
|
648
|
-
}
|
|
649
511
|
function nearestBoundaryGap(a, b, axis) {
|
|
650
512
|
return Math.min(Math.abs(a.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a.min[axis]));
|
|
651
513
|
}
|
|
652
|
-
function bboxGaps(a, b) {
|
|
653
|
-
return [
|
|
654
|
-
intervalGap$1(a.min[0], a.max[0], b.min[0], b.max[0]),
|
|
655
|
-
intervalGap$1(a.min[1], a.max[1], b.min[1], b.max[1]),
|
|
656
|
-
intervalGap$1(a.min[2], a.max[2], b.min[2], b.max[2])
|
|
657
|
-
];
|
|
658
|
-
}
|
|
659
|
-
function maxGap(gaps) {
|
|
660
|
-
return Math.max(gaps[0], gaps[1], gaps[2]);
|
|
661
|
-
}
|
|
662
514
|
function hasPositiveGap(gaps) {
|
|
663
515
|
return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
|
|
664
516
|
}
|
|
665
|
-
function bboxInteriorOverlaps(a, b) {
|
|
666
|
-
for (let axis = 0; axis < 3; axis += 1) {
|
|
667
|
-
if (Math.min(a.max[axis], b.max[axis]) - Math.max(a.min[axis], b.min[axis]) <= 0) return false;
|
|
668
|
-
}
|
|
669
|
-
return true;
|
|
670
|
-
}
|
|
671
|
-
function bboxOverlapVolume(a, b) {
|
|
672
|
-
let volume = 1;
|
|
673
|
-
for (let axis = 0; axis < 3; axis += 1) {
|
|
674
|
-
volume *= Math.max(0, Math.min(a.max[axis], b.max[axis]) - Math.max(a.min[axis], b.min[axis]));
|
|
675
|
-
}
|
|
676
|
-
return volume;
|
|
677
|
-
}
|
|
678
|
-
function estimateSweepPairCount(entries, axis, tolerance) {
|
|
679
|
-
const ordered = entries.map((entry) => ({ min: entry.min[axis], max: entry.max[axis] })).sort((a, b) => a.min - b.min || a.max - b.max);
|
|
680
|
-
const endValues = ordered.map((entry) => entry.max + tolerance).sort((a, b) => a - b);
|
|
681
|
-
let expired = 0;
|
|
682
|
-
let count = 0;
|
|
683
|
-
for (let seen = 0; seen < ordered.length; seen += 1) {
|
|
684
|
-
const currentMin = ordered[seen].min;
|
|
685
|
-
while (expired < seen && endValues[expired] < currentMin) expired += 1;
|
|
686
|
-
count += seen - expired;
|
|
687
|
-
}
|
|
688
|
-
return count;
|
|
689
|
-
}
|
|
690
|
-
function chooseSweepAxis(entries, tolerance) {
|
|
691
|
-
let bestAxis = 0;
|
|
692
|
-
let bestCount = estimateSweepPairCount(entries, bestAxis, tolerance);
|
|
693
|
-
for (const axis of [1, 2]) {
|
|
694
|
-
const count = estimateSweepPairCount(entries, axis, tolerance);
|
|
695
|
-
if (count < bestCount) {
|
|
696
|
-
bestAxis = axis;
|
|
697
|
-
bestCount = count;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
return bestAxis;
|
|
701
|
-
}
|
|
702
517
|
function collectCandidatePairs(entries, tolerance) {
|
|
703
518
|
if (entries.length < 2) return [];
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
for (const candidate of active) {
|
|
711
|
-
const gaps = bboxGaps(candidate.entry, current.entry);
|
|
712
|
-
if (maxGap(gaps) > tolerance) continue;
|
|
713
|
-
const sourceIndex = Math.min(candidate.index, current.index);
|
|
714
|
-
const targetIndex = Math.max(candidate.index, current.index);
|
|
715
|
-
pairs.push({ sourceIndex, targetIndex, gaps });
|
|
716
|
-
}
|
|
717
|
-
active.push(current);
|
|
718
|
-
}
|
|
519
|
+
const index = new AabbSpatialIndex(entries);
|
|
520
|
+
const pairs = index.overlapPairs({ padding: tolerance }).pairs.map((pair) => ({
|
|
521
|
+
sourceIndex: pair.sourceIndex,
|
|
522
|
+
targetIndex: pair.targetIndex,
|
|
523
|
+
gaps: aabbGaps(entries[pair.sourceIndex], entries[pair.targetIndex])
|
|
524
|
+
})).filter((pair) => Math.max(pair.gaps[0], pair.gaps[1], pair.gaps[2]) <= tolerance);
|
|
719
525
|
pairs.sort((a, b) => a.sourceIndex - b.sourceIndex || a.targetIndex - b.targetIndex);
|
|
720
526
|
return pairs;
|
|
721
527
|
}
|
|
722
528
|
function contactFromBBoxes(a, b, tolerance) {
|
|
723
|
-
const gaps =
|
|
724
|
-
const largestGap =
|
|
529
|
+
const gaps = aabbGaps(a, b);
|
|
530
|
+
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
725
531
|
if (largestGap > tolerance) return { touching: false, gap: largestGap };
|
|
726
532
|
const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
|
|
727
533
|
if (separatedAxes.length > 0) {
|
|
@@ -768,21 +574,24 @@ function makeEdge(entries, sourceIndex, targetIndex, edge) {
|
|
|
768
574
|
};
|
|
769
575
|
}
|
|
770
576
|
function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
577
|
+
const exactGeometry = rawOptions.exactGeometry ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.exactGeometry;
|
|
771
578
|
const options = {
|
|
772
579
|
contactTolerance: rawOptions.contactTolerance ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance,
|
|
773
580
|
minOverlapVolume: rawOptions.minOverlapVolume ?? DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.minOverlapVolume,
|
|
774
|
-
exactGeometry
|
|
581
|
+
exactGeometry,
|
|
582
|
+
mergeOverlappingBBoxes: rawOptions.mergeOverlappingBBoxes ?? !exactGeometry,
|
|
583
|
+
mergeTouchingBBoxes: rawOptions.mergeTouchingBBoxes ?? !exactGeometry
|
|
775
584
|
};
|
|
776
585
|
const warnings = [];
|
|
777
586
|
const edges = [];
|
|
778
|
-
const unionFind = new UnionFind(entries.length);
|
|
587
|
+
const unionFind = new UnionFind$1(entries.length);
|
|
779
588
|
for (const pair of collectCandidatePairs(entries, options.contactTolerance)) {
|
|
780
589
|
const i = pair.sourceIndex;
|
|
781
590
|
const j = pair.targetIndex;
|
|
782
591
|
const a = entries[i];
|
|
783
592
|
const b = entries[j];
|
|
784
|
-
const
|
|
785
|
-
if (options.exactGeometry &&
|
|
593
|
+
const bboxOverlaps = !hasPositiveGap(pair.gaps) && aabbInteriorOverlaps(a, b);
|
|
594
|
+
if (options.exactGeometry && bboxOverlaps) {
|
|
786
595
|
const overlap = intersectionVolume(a, b);
|
|
787
596
|
if (overlap.warning) warnings.push(overlap.warning);
|
|
788
597
|
if (overlap.volume != null && overlap.volume > options.minOverlapVolume) {
|
|
@@ -797,32 +606,21 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
797
606
|
);
|
|
798
607
|
continue;
|
|
799
608
|
}
|
|
800
|
-
|
|
801
|
-
unionFind.union(i, j);
|
|
802
|
-
edges.push(
|
|
803
|
-
makeEdge(entries, i, j, {
|
|
804
|
-
kind: "touching",
|
|
805
|
-
method: "boolean-intersection",
|
|
806
|
-
gap: 0,
|
|
807
|
-
overlapVolume: overlap.volume
|
|
808
|
-
})
|
|
809
|
-
);
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
609
|
+
continue;
|
|
812
610
|
}
|
|
813
|
-
if (
|
|
611
|
+
if (bboxOverlaps && options.mergeOverlappingBBoxes) {
|
|
814
612
|
unionFind.union(i, j);
|
|
815
613
|
edges.push(
|
|
816
614
|
makeEdge(entries, i, j, {
|
|
817
615
|
kind: "overlap",
|
|
818
616
|
method: "bbox-overlap",
|
|
819
617
|
gap: 0,
|
|
820
|
-
overlapVolume:
|
|
618
|
+
overlapVolume: aabbOverlapVolume(a, b)
|
|
821
619
|
})
|
|
822
620
|
);
|
|
823
621
|
} else {
|
|
824
622
|
const contact = contactFromBBoxes(a, b, options.contactTolerance);
|
|
825
|
-
if (!contact.touching) continue;
|
|
623
|
+
if (!contact.touching || !options.mergeTouchingBBoxes) continue;
|
|
826
624
|
unionFind.union(i, j);
|
|
827
625
|
edges.push(
|
|
828
626
|
makeEdge(entries, i, j, {
|
|
@@ -861,7 +659,7 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
861
659
|
objectNames: [],
|
|
862
660
|
objectCount: 0,
|
|
863
661
|
bodyCount: 0,
|
|
864
|
-
bbox: emptyBBox()
|
|
662
|
+
bbox: emptyBBox$1()
|
|
865
663
|
};
|
|
866
664
|
componentByRoot.set(root, component);
|
|
867
665
|
rootToComponentIndex.set(root, component.index);
|
|
@@ -873,7 +671,7 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
873
671
|
component.objectNames.push(object.name);
|
|
874
672
|
component.objectCount += 1;
|
|
875
673
|
component.bodyCount += object.bodyCount;
|
|
876
|
-
expandBBox(component.bbox, object.bbox.min, object.bbox.max);
|
|
674
|
+
expandBBox$1(component.bbox, object.bbox.min, object.bbox.max);
|
|
877
675
|
}
|
|
878
676
|
const components = [...componentByRoot.values()];
|
|
879
677
|
return {
|
|
@@ -919,64 +717,38 @@ function defaultRootComponentIndex(components) {
|
|
|
919
717
|
if (components.length === 0) return null;
|
|
920
718
|
return components.reduce((best, component) => compareDefaultRoot(component, best) > 0 ? component : best, components[0]).index;
|
|
921
719
|
}
|
|
922
|
-
function buildGapEdges(components) {
|
|
923
|
-
const edges = [];
|
|
924
|
-
for (let i = 0; i < components.length; i += 1) {
|
|
925
|
-
for (let j = i + 1; j < components.length; j += 1) {
|
|
926
|
-
const source = components[i];
|
|
927
|
-
const target = components[j];
|
|
928
|
-
const gap = bboxGap(source, target);
|
|
929
|
-
if (!Number.isFinite(gap.gap)) continue;
|
|
930
|
-
edges.push({
|
|
931
|
-
sourceComponentIndex: source.index,
|
|
932
|
-
targetComponentIndex: target.index,
|
|
933
|
-
sourceObjectNames: [...source.objectNames],
|
|
934
|
-
targetObjectNames: [...target.objectNames],
|
|
935
|
-
gap: gap.gap,
|
|
936
|
-
axisGaps: gap.axisGaps
|
|
937
|
-
});
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
return edges;
|
|
941
|
-
}
|
|
942
720
|
function componentPositionByIndex(components) {
|
|
943
721
|
return new Map(components.map((component, position) => [component.index, position]));
|
|
944
722
|
}
|
|
945
|
-
function computeNearestComponents(components
|
|
723
|
+
function computeNearestComponents(components) {
|
|
946
724
|
const nearest = components.map(() => ({ nearestGap: null, nearestComponentIndex: null }));
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
source.nearestGap
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
target.nearestGap
|
|
960
|
-
|
|
725
|
+
for (let sourcePosition = 0; sourcePosition < components.length; sourcePosition += 1) {
|
|
726
|
+
for (let targetPosition = sourcePosition + 1; targetPosition < components.length; targetPosition += 1) {
|
|
727
|
+
const sourceComponent = components[sourcePosition];
|
|
728
|
+
const targetComponent = components[targetPosition];
|
|
729
|
+
const edge = bboxGap(sourceComponent, targetComponent);
|
|
730
|
+
if (!Number.isFinite(edge.gap)) continue;
|
|
731
|
+
const source = nearest[sourcePosition];
|
|
732
|
+
if (source.nearestGap == null || edge.gap < source.nearestGap - EPSILON || Math.abs(edge.gap - source.nearestGap) <= EPSILON && targetComponent.index < (source.nearestComponentIndex ?? Infinity)) {
|
|
733
|
+
source.nearestGap = edge.gap;
|
|
734
|
+
source.nearestComponentIndex = targetComponent.index;
|
|
735
|
+
}
|
|
736
|
+
const target = nearest[targetPosition];
|
|
737
|
+
if (target.nearestGap == null || edge.gap < target.nearestGap - EPSILON || Math.abs(edge.gap - target.nearestGap) <= EPSILON && sourceComponent.index < (target.nearestComponentIndex ?? Infinity)) {
|
|
738
|
+
target.nearestGap = edge.gap;
|
|
739
|
+
target.nearestComponentIndex = sourceComponent.index;
|
|
740
|
+
}
|
|
961
741
|
}
|
|
962
742
|
}
|
|
963
743
|
return nearest;
|
|
964
744
|
}
|
|
965
|
-
function computeRootDistances(components,
|
|
745
|
+
function computeRootDistances(components, rootComponentIndex) {
|
|
966
746
|
if (rootComponentIndex == null) return [];
|
|
967
747
|
const positions = componentPositionByIndex(components);
|
|
968
748
|
const rootPosition = positions.get(rootComponentIndex);
|
|
969
749
|
if (rootPosition == null) {
|
|
970
750
|
throw new Error(`rootComponentIndex ${rootComponentIndex} does not match any physical component`);
|
|
971
751
|
}
|
|
972
|
-
const adjacency = components.map(() => []);
|
|
973
|
-
for (const edge of gapEdges) {
|
|
974
|
-
const sourcePosition = positions.get(edge.sourceComponentIndex);
|
|
975
|
-
const targetPosition = positions.get(edge.targetComponentIndex);
|
|
976
|
-
if (sourcePosition == null || targetPosition == null) continue;
|
|
977
|
-
adjacency[sourcePosition].push({ to: targetPosition, gap: edge.gap });
|
|
978
|
-
adjacency[targetPosition].push({ to: sourcePosition, gap: edge.gap });
|
|
979
|
-
}
|
|
980
752
|
const visited = components.map(() => false);
|
|
981
753
|
const distances = components.map(() => Infinity);
|
|
982
754
|
const parents = components.map(() => null);
|
|
@@ -992,13 +764,15 @@ function computeRootDistances(components, gapEdges, rootComponentIndex) {
|
|
|
992
764
|
}
|
|
993
765
|
if (current === -1 || !Number.isFinite(distances[current])) break;
|
|
994
766
|
visited[current] = true;
|
|
995
|
-
for (
|
|
996
|
-
if (visited[
|
|
767
|
+
for (let targetPosition = 0; targetPosition < components.length; targetPosition += 1) {
|
|
768
|
+
if (visited[targetPosition] || targetPosition === current) continue;
|
|
769
|
+
const edge = bboxGap(components[current], components[targetPosition]);
|
|
770
|
+
if (!Number.isFinite(edge.gap)) continue;
|
|
997
771
|
const nextDistance = distances[current] + edge.gap;
|
|
998
|
-
if (nextDistance < distances[
|
|
999
|
-
distances[
|
|
1000
|
-
parents[
|
|
1001
|
-
parentGaps[
|
|
772
|
+
if (nextDistance < distances[targetPosition] - EPSILON || Math.abs(nextDistance - distances[targetPosition]) <= EPSILON && components[current].index < (parents[targetPosition] ?? Infinity)) {
|
|
773
|
+
distances[targetPosition] = nextDistance;
|
|
774
|
+
parents[targetPosition] = components[current].index;
|
|
775
|
+
parentGaps[targetPosition] = edge.gap;
|
|
1002
776
|
}
|
|
1003
777
|
}
|
|
1004
778
|
}
|
|
@@ -1008,12 +782,44 @@ function computeRootDistances(components, gapEdges, rootComponentIndex) {
|
|
|
1008
782
|
parentGap: parentGaps[position]
|
|
1009
783
|
}));
|
|
1010
784
|
}
|
|
785
|
+
function makeGapEdge(source, target) {
|
|
786
|
+
const gap = bboxGap(source, target);
|
|
787
|
+
return {
|
|
788
|
+
sourceComponentIndex: source.index,
|
|
789
|
+
targetComponentIndex: target.index,
|
|
790
|
+
sourceObjectNames: [...source.objectNames],
|
|
791
|
+
targetObjectNames: [...target.objectNames],
|
|
792
|
+
gap: gap.gap,
|
|
793
|
+
axisGaps: gap.axisGaps
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function compactGapEdges(components, nearest, rooted) {
|
|
797
|
+
const componentByIndex = new Map(components.map((component) => [component.index, component]));
|
|
798
|
+
const seen = /* @__PURE__ */ new Set();
|
|
799
|
+
const edges = [];
|
|
800
|
+
const add = (sourceIndex, targetIndex) => {
|
|
801
|
+
if (targetIndex == null || sourceIndex === targetIndex) return;
|
|
802
|
+
const source = componentByIndex.get(Math.min(sourceIndex, targetIndex));
|
|
803
|
+
const target = componentByIndex.get(Math.max(sourceIndex, targetIndex));
|
|
804
|
+
if (!source || !target) return;
|
|
805
|
+
const key = `${source.index}:${target.index}`;
|
|
806
|
+
if (seen.has(key)) return;
|
|
807
|
+
seen.add(key);
|
|
808
|
+
edges.push(makeGapEdge(source, target));
|
|
809
|
+
};
|
|
810
|
+
components.forEach((component, position) => {
|
|
811
|
+
var _a, _b;
|
|
812
|
+
add(component.index, ((_a = nearest[position]) == null ? void 0 : _a.nearestComponentIndex) ?? null);
|
|
813
|
+
add(component.index, ((_b = rooted[position]) == null ? void 0 : _b.parentComponentIndex) ?? null);
|
|
814
|
+
});
|
|
815
|
+
return edges.sort((a, b) => a.sourceComponentIndex - b.sourceComponentIndex || a.targetComponentIndex - b.targetComponentIndex);
|
|
816
|
+
}
|
|
1011
817
|
function analyzeDistanceInspection(entries, rawOptions = {}) {
|
|
1012
818
|
const connectivity = analyzePhysicalConnectivity(entries, rawOptions);
|
|
1013
819
|
const rootComponentIndex = rawOptions.rootComponentIndex ?? defaultRootComponentIndex(connectivity.components);
|
|
1014
|
-
const
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
820
|
+
const nearest = computeNearestComponents(connectivity.components);
|
|
821
|
+
const rooted = computeRootDistances(connectivity.components, rootComponentIndex);
|
|
822
|
+
const gapEdges = compactGapEdges(connectivity.components, nearest, rooted);
|
|
1017
823
|
const componentByIndex = /* @__PURE__ */ new Map();
|
|
1018
824
|
const components = connectivity.components.map((component, position) => {
|
|
1019
825
|
var _a, _b;
|
|
@@ -1059,6 +865,7 @@ function analyzeDistanceInspection(entries, rawOptions = {}) {
|
|
|
1059
865
|
componentCount: connectivity.componentCount,
|
|
1060
866
|
rootComponentIndex,
|
|
1061
867
|
maxRootDistance,
|
|
868
|
+
gapEdgeCount: connectivity.components.length * (connectivity.components.length - 1) / 2,
|
|
1062
869
|
objects,
|
|
1063
870
|
components,
|
|
1064
871
|
gapEdges,
|
|
@@ -1334,10 +1141,210 @@ function summarizeThicknessSamples(samples, options) {
|
|
|
1334
1141
|
unresolvedAreaPercent: percent(unresolvedArea, totalArea)
|
|
1335
1142
|
};
|
|
1336
1143
|
}
|
|
1144
|
+
const MIN_TRIANGLE_AREA = 1e-12;
|
|
1145
|
+
const R2_ALPHA = 0.7548776662466927;
|
|
1146
|
+
const R2_BETA = 0.5698402909980532;
|
|
1147
|
+
function readSurfaceTriangles(position) {
|
|
1148
|
+
const triangles = [];
|
|
1149
|
+
const a = new Vector3();
|
|
1150
|
+
const b = new Vector3();
|
|
1151
|
+
const c = new Vector3();
|
|
1152
|
+
const ac = new Vector3();
|
|
1153
|
+
const normal = new Vector3();
|
|
1154
|
+
const triangleCount = Math.floor(position.count / 3);
|
|
1155
|
+
for (let tri = 0; tri < triangleCount; tri += 1) {
|
|
1156
|
+
const offset = tri * 3;
|
|
1157
|
+
a.fromBufferAttribute(position, offset);
|
|
1158
|
+
b.fromBufferAttribute(position, offset + 1);
|
|
1159
|
+
c.fromBufferAttribute(position, offset + 2);
|
|
1160
|
+
normal.subVectors(b, a).cross(ac.subVectors(c, a));
|
|
1161
|
+
const areaTwice = normal.length();
|
|
1162
|
+
if (areaTwice <= MIN_TRIANGLE_AREA) continue;
|
|
1163
|
+
triangles.push({
|
|
1164
|
+
index: tri,
|
|
1165
|
+
a: a.clone(),
|
|
1166
|
+
b: b.clone(),
|
|
1167
|
+
c: c.clone(),
|
|
1168
|
+
normal: normal.clone().multiplyScalar(1 / areaTwice),
|
|
1169
|
+
area: areaTwice * 0.5
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
return triangles;
|
|
1173
|
+
}
|
|
1174
|
+
function allocateAreaSampleCounts(triangles, maxSamples) {
|
|
1175
|
+
const sampleBudget = Math.max(1, Math.floor(maxSamples));
|
|
1176
|
+
const counts = new Array(triangles.length).fill(0);
|
|
1177
|
+
if (triangles.length === 0) return counts;
|
|
1178
|
+
const totalArea = triangles.reduce((sum, triangle) => sum + triangle.area, 0);
|
|
1179
|
+
if (!(totalArea > 0)) return counts;
|
|
1180
|
+
const remainders = triangles.map((triangle, index) => {
|
|
1181
|
+
const quota = triangle.area / totalArea * sampleBudget;
|
|
1182
|
+
const whole = Math.floor(quota);
|
|
1183
|
+
counts[index] += whole;
|
|
1184
|
+
return { index, fraction: quota - whole, area: triangle.area };
|
|
1185
|
+
});
|
|
1186
|
+
let left = sampleBudget - counts.reduce((sum, count) => sum + count, 0);
|
|
1187
|
+
remainders.sort((a, b) => b.fraction - a.fraction || b.area - a.area || a.index - b.index);
|
|
1188
|
+
for (const entry of remainders) {
|
|
1189
|
+
if (left <= 0) break;
|
|
1190
|
+
counts[entry.index] += 1;
|
|
1191
|
+
left -= 1;
|
|
1192
|
+
}
|
|
1193
|
+
return counts;
|
|
1194
|
+
}
|
|
1195
|
+
function sampleSurfaceTriangles(triangles, maxSamples) {
|
|
1196
|
+
const counts = allocateAreaSampleCounts(triangles, maxSamples);
|
|
1197
|
+
const samples = [];
|
|
1198
|
+
const position = new Vector3();
|
|
1199
|
+
let sampleIndex = 0;
|
|
1200
|
+
triangles.forEach((triangle, triangleListIndex) => {
|
|
1201
|
+
const count = counts[triangleListIndex];
|
|
1202
|
+
if (count <= 0) return;
|
|
1203
|
+
for (let localIndex = 0; localIndex < count; localIndex += 1) {
|
|
1204
|
+
const barycentric = triangleBarycentricSample(triangle.index, localIndex, sampleIndex);
|
|
1205
|
+
position.copy(triangle.a).multiplyScalar(barycentric[0]).addScaledVector(triangle.b, barycentric[1]).addScaledVector(triangle.c, barycentric[2]);
|
|
1206
|
+
samples.push({
|
|
1207
|
+
triangle,
|
|
1208
|
+
position: position.clone(),
|
|
1209
|
+
normal: triangle.normal.clone(),
|
|
1210
|
+
area: triangle.area / count,
|
|
1211
|
+
barycentric,
|
|
1212
|
+
sampleIndex,
|
|
1213
|
+
triangleSampleIndex: localIndex,
|
|
1214
|
+
triangleSampleCount: count
|
|
1215
|
+
});
|
|
1216
|
+
sampleIndex += 1;
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
return samples;
|
|
1220
|
+
}
|
|
1221
|
+
function totalSurfaceArea(triangles) {
|
|
1222
|
+
return triangles.reduce((sum, triangle) => sum + triangle.area, 0);
|
|
1223
|
+
}
|
|
1224
|
+
function triangleBarycentricSample(triangleIndex, triangleSampleIndex, sampleIndex) {
|
|
1225
|
+
const seedA = hash01((triangleIndex + 1) * 1013);
|
|
1226
|
+
const seedB = hash01((triangleIndex + 1) * 9176);
|
|
1227
|
+
const u = clampUnit(fract(seedA + (sampleIndex + 1) * R2_ALPHA));
|
|
1228
|
+
const v = clampUnit(fract(seedB + (triangleSampleIndex + 1) * R2_BETA));
|
|
1229
|
+
const root = Math.sqrt(u);
|
|
1230
|
+
return [1 - root, root * (1 - v), root * v];
|
|
1231
|
+
}
|
|
1232
|
+
function hash01(value) {
|
|
1233
|
+
return fract(Math.sin(value * 12.9898) * 43758.5453123);
|
|
1234
|
+
}
|
|
1235
|
+
function fract(value) {
|
|
1236
|
+
return value - Math.floor(value);
|
|
1237
|
+
}
|
|
1238
|
+
function clampUnit(value) {
|
|
1239
|
+
return Math.min(1 - 1e-9, Math.max(1e-9, value));
|
|
1240
|
+
}
|
|
1241
|
+
function cloneGeometryForFaceColors(geometry) {
|
|
1242
|
+
return geometry.index ? geometry.toNonIndexed() : geometry.clone();
|
|
1243
|
+
}
|
|
1244
|
+
function geometryMaxDimension(geometry) {
|
|
1245
|
+
geometry.computeBoundingBox();
|
|
1246
|
+
const box = geometry.boundingBox;
|
|
1247
|
+
if (!box) return 1;
|
|
1248
|
+
const size = new Vector3();
|
|
1249
|
+
box.getSize(size);
|
|
1250
|
+
return Math.max(1, size.x, size.y, size.z);
|
|
1251
|
+
}
|
|
1252
|
+
function firstOppositeSurfaceDistance(raycaster, mesh, point, direction, epsilon, far) {
|
|
1253
|
+
const origin = point.clone().addScaledVector(direction, epsilon);
|
|
1254
|
+
raycaster.set(origin, direction);
|
|
1255
|
+
raycaster.near = epsilon;
|
|
1256
|
+
raycaster.far = far;
|
|
1257
|
+
const hit = raycaster.intersectObject(mesh, false).find((entry) => entry.distance > epsilon);
|
|
1258
|
+
return hit ? hit.distance + epsilon : null;
|
|
1259
|
+
}
|
|
1260
|
+
function triangleThickness(raycaster, mesh, centroid, normal, epsilon, far) {
|
|
1261
|
+
const forward = firstOppositeSurfaceDistance(raycaster, mesh, centroid, normal, epsilon, far);
|
|
1262
|
+
const backward = firstOppositeSurfaceDistance(raycaster, mesh, centroid, normal.clone().negate(), epsilon, far);
|
|
1263
|
+
if (forward == null) return backward;
|
|
1264
|
+
if (backward == null) return forward;
|
|
1265
|
+
return Math.min(forward, backward);
|
|
1266
|
+
}
|
|
1267
|
+
function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
1268
|
+
const options = resolveThicknessInspectionOptions(rawOptions);
|
|
1269
|
+
const geometry = cloneGeometryForFaceColors(sourceGeometry);
|
|
1270
|
+
const position = geometry.getAttribute("position");
|
|
1271
|
+
if (!position || position.count < 3) {
|
|
1272
|
+
return {
|
|
1273
|
+
geometry,
|
|
1274
|
+
samples: [],
|
|
1275
|
+
pointSamples: [],
|
|
1276
|
+
triangleCount: 0,
|
|
1277
|
+
sampledTriangleCount: 0,
|
|
1278
|
+
sampleStride: 1,
|
|
1279
|
+
warnings: ["No triangle geometry."]
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
const triangleCount = Math.floor(position.count / 3);
|
|
1283
|
+
const surfaceTriangles = readSurfaceTriangles(position);
|
|
1284
|
+
const surfaceSamples = sampleSurfaceTriangles(surfaceTriangles, options.maxSamplesPerObject);
|
|
1285
|
+
const maxDim = geometryMaxDimension(geometry);
|
|
1286
|
+
const epsilon = Math.max(1e-4, maxDim * 1e-6);
|
|
1287
|
+
const far = Math.max(maxDim * 4, options.maxThickness * 4, 1);
|
|
1288
|
+
const colors = new Float32Array(position.count * 3);
|
|
1289
|
+
const triangleThicknessValues = new Array(triangleCount).fill(void 0);
|
|
1290
|
+
const samples = [];
|
|
1291
|
+
const pointSamples = [];
|
|
1292
|
+
const warnings = [];
|
|
1293
|
+
const rayMaterial = new MeshBasicMaterial({ side: DoubleSide });
|
|
1294
|
+
const rayMesh = new Mesh(geometry, rayMaterial);
|
|
1295
|
+
const raycaster = new Raycaster();
|
|
1296
|
+
if (surfaceTriangles.length === 0) {
|
|
1297
|
+
warnings.push("No non-degenerate triangle surface was available for thickness sampling.");
|
|
1298
|
+
} else if (surfaceSamples.length < surfaceTriangles.length) {
|
|
1299
|
+
warnings.push(
|
|
1300
|
+
`Area sampling budget ${surfaceSamples.length} covers ${surfaceTriangles.length} surface triangles; increase --thickness-samples for denser analysis.`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
const sampledTriangleIndexes = /* @__PURE__ */ new Set();
|
|
1304
|
+
for (const sample of surfaceSamples) {
|
|
1305
|
+
sampledTriangleIndexes.add(sample.triangle.index);
|
|
1306
|
+
const thickness = triangleThickness(raycaster, rayMesh, sample.position, sample.normal, epsilon, far);
|
|
1307
|
+
samples.push({ thickness, area: sample.area });
|
|
1308
|
+
const previous = triangleThicknessValues[sample.triangle.index];
|
|
1309
|
+
if (previous === void 0 || previous == null || thickness != null && thickness < previous) {
|
|
1310
|
+
triangleThicknessValues[sample.triangle.index] = thickness;
|
|
1311
|
+
}
|
|
1312
|
+
pointSamples.push({
|
|
1313
|
+
position: [sample.position.x, sample.position.y, sample.position.z],
|
|
1314
|
+
normal: [sample.normal.x, sample.normal.y, sample.normal.z],
|
|
1315
|
+
value: thickness,
|
|
1316
|
+
className: thicknessClass(thickness, options),
|
|
1317
|
+
color: thicknessColor(thickness, options),
|
|
1318
|
+
area: sample.area
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
for (let tri = 0; tri < triangleCount; tri += 1) {
|
|
1322
|
+
const color = thicknessColor(triangleThicknessValues[tri], options);
|
|
1323
|
+
const offset = tri * 3;
|
|
1324
|
+
for (let vertex = 0; vertex < 3; vertex += 1) {
|
|
1325
|
+
const colorOffset = (offset + vertex) * 3;
|
|
1326
|
+
colors[colorOffset] = color[0] / 255;
|
|
1327
|
+
colors[colorOffset + 1] = color[1] / 255;
|
|
1328
|
+
colors[colorOffset + 2] = color[2] / 255;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
geometry.setAttribute("color", new BufferAttribute(colors, 3));
|
|
1332
|
+
rayMaterial.dispose();
|
|
1333
|
+
return {
|
|
1334
|
+
geometry,
|
|
1335
|
+
samples,
|
|
1336
|
+
pointSamples,
|
|
1337
|
+
triangleCount,
|
|
1338
|
+
sampledTriangleCount: sampledTriangleIndexes.size,
|
|
1339
|
+
sampleStride: Math.max(1, Math.ceil(Math.max(1, surfaceTriangles.length) / Math.max(1, sampledTriangleIndexes.size))),
|
|
1340
|
+
warnings
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1337
1343
|
const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
|
|
1338
1344
|
smoothAngleDeg: 5,
|
|
1339
1345
|
sharpAngleDeg: 30,
|
|
1340
|
-
harshAngleDeg: 90
|
|
1346
|
+
harshAngleDeg: 90,
|
|
1347
|
+
maxSamplesPerObject: 5e3
|
|
1341
1348
|
};
|
|
1342
1349
|
const ROUGHNESS_COLORS = {
|
|
1343
1350
|
smooth: [62, 72, 84],
|
|
@@ -1349,7 +1356,8 @@ function resolveRoughnessInspectionOptions(raw = {}) {
|
|
|
1349
1356
|
const options = {
|
|
1350
1357
|
smoothAngleDeg: raw.smoothAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.smoothAngleDeg,
|
|
1351
1358
|
sharpAngleDeg: raw.sharpAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.sharpAngleDeg,
|
|
1352
|
-
harshAngleDeg: raw.harshAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.harshAngleDeg
|
|
1359
|
+
harshAngleDeg: raw.harshAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.harshAngleDeg,
|
|
1360
|
+
maxSamplesPerObject: raw.maxSamplesPerObject ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.maxSamplesPerObject
|
|
1353
1361
|
};
|
|
1354
1362
|
if (!Number.isFinite(options.smoothAngleDeg) || options.smoothAngleDeg < 0) {
|
|
1355
1363
|
throw new Error(`smoothAngleDeg must be a finite non-negative angle (got ${options.smoothAngleDeg}).`);
|
|
@@ -1360,7 +1368,13 @@ function resolveRoughnessInspectionOptions(raw = {}) {
|
|
|
1360
1368
|
if (!Number.isFinite(options.harshAngleDeg) || options.harshAngleDeg <= options.sharpAngleDeg || options.harshAngleDeg > 180) {
|
|
1361
1369
|
throw new Error(`harshAngleDeg must be greater than sharpAngleDeg and <= 180 (got ${options.harshAngleDeg}).`);
|
|
1362
1370
|
}
|
|
1363
|
-
|
|
1371
|
+
if (!Number.isFinite(options.maxSamplesPerObject) || options.maxSamplesPerObject <= 0) {
|
|
1372
|
+
throw new Error(`maxSamplesPerObject must be a positive finite number (got ${options.maxSamplesPerObject}).`);
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
...options,
|
|
1376
|
+
maxSamplesPerObject: Math.max(1, Math.floor(options.maxSamplesPerObject))
|
|
1377
|
+
};
|
|
1364
1378
|
}
|
|
1365
1379
|
function roughnessClassForAngle(angleDeg, options) {
|
|
1366
1380
|
if (angleDeg >= options.harshAngleDeg) return "harsh";
|
|
@@ -1473,11 +1487,12 @@ function analyzeRoughnessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
1473
1487
|
const position = geometry.getAttribute("position");
|
|
1474
1488
|
const warnings = [];
|
|
1475
1489
|
if (!position || position.count < 3) {
|
|
1476
|
-
return { geometry, summary: emptyRoughnessSummary(), warnings: ["No triangle geometry."] };
|
|
1490
|
+
return { geometry, pointSamples: [], summary: emptyRoughnessSummary(), warnings: ["No triangle geometry."] };
|
|
1477
1491
|
}
|
|
1478
1492
|
const triangleCount = Math.floor(position.count / 3);
|
|
1479
1493
|
const normals = new Array(triangleCount);
|
|
1480
1494
|
const triangles = new Array(triangleCount);
|
|
1495
|
+
const triangleEdgeAngles = Array.from({ length: triangleCount }, () => [0, 0, 0]);
|
|
1481
1496
|
const edges = /* @__PURE__ */ new Map();
|
|
1482
1497
|
const colors = new Float32Array(position.count * 3);
|
|
1483
1498
|
const scores = new Float32Array(position.count);
|
|
@@ -1497,18 +1512,19 @@ function analyzeRoughnessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
1497
1512
|
const areaTwice = normal.length();
|
|
1498
1513
|
triangles[tri] = { area: areaTwice * 0.5, maxAngleDeg: 0 };
|
|
1499
1514
|
normals[tri] = areaTwice > 1e-12 ? normal.multiplyScalar(1 / areaTwice).clone() : new Vector3(0, 0, 1);
|
|
1500
|
-
const keys = [vertexKey(a, snap), vertexKey(b, snap), vertexKey(c, snap)];
|
|
1515
|
+
const keys = [vertexKey$1(a, snap), vertexKey$1(b, snap), vertexKey$1(c, snap)];
|
|
1501
1516
|
for (let edge = 0; edge < 3; edge += 1) {
|
|
1502
1517
|
const key = edgeKey(keys[edge], keys[(edge + 1) % 3]);
|
|
1503
1518
|
let record = edges.get(key);
|
|
1504
1519
|
if (!record) {
|
|
1505
|
-
record = { triangles: [] };
|
|
1520
|
+
record = { triangles: [], triangleEdges: [] };
|
|
1506
1521
|
edges.set(key, record);
|
|
1507
1522
|
}
|
|
1508
1523
|
record.triangles.push(tri);
|
|
1524
|
+
record.triangleEdges.push({ triangle: tri, edge });
|
|
1509
1525
|
}
|
|
1510
1526
|
}
|
|
1511
|
-
const { boundaryEdgeCount, nonManifoldEdgeCount, edgeAngles } = markTriangleRoughness(edges, triangles, normals);
|
|
1527
|
+
const { boundaryEdgeCount, nonManifoldEdgeCount, edgeAngles } = markTriangleRoughness(edges, triangles, normals, triangleEdgeAngles);
|
|
1512
1528
|
if (boundaryEdgeCount > 0) warnings.push(`${boundaryEdgeCount} boundary edge(s) were treated as harsh roughness.`);
|
|
1513
1529
|
if (nonManifoldEdgeCount > 0) warnings.push(`${nonManifoldEdgeCount} non-manifold edge(s) were treated as harsh roughness.`);
|
|
1514
1530
|
for (let tri = 0; tri < triangleCount; tri += 1) {
|
|
@@ -1524,36 +1540,57 @@ function analyzeRoughnessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
1524
1540
|
scores[offset + vertex] = score;
|
|
1525
1541
|
}
|
|
1526
1542
|
}
|
|
1543
|
+
const surfaceTriangles = readSurfaceTriangles(position);
|
|
1544
|
+
const surfaceSamples = sampleSurfaceTriangles(surfaceTriangles, options.maxSamplesPerObject);
|
|
1545
|
+
const edgeInfluenceRadius = roughnessEdgeInfluenceRadius(surfaceTriangles, surfaceSamples.length);
|
|
1546
|
+
const pointSamples = surfaceSamples.map((sample) => {
|
|
1547
|
+
const angleDeg = roughnessAngleAtSample(
|
|
1548
|
+
sample.position,
|
|
1549
|
+
sample.triangle,
|
|
1550
|
+
triangleEdgeAngles[sample.triangle.index],
|
|
1551
|
+
edgeInfluenceRadius
|
|
1552
|
+
);
|
|
1553
|
+
const color = roughnessColorForAngle(angleDeg, options);
|
|
1554
|
+
return {
|
|
1555
|
+
position: [sample.position.x, sample.position.y, sample.position.z],
|
|
1556
|
+
normal: [sample.normal.x, sample.normal.y, sample.normal.z],
|
|
1557
|
+
value: angleDeg,
|
|
1558
|
+
className: roughnessClassForAngle(angleDeg, options),
|
|
1559
|
+
color,
|
|
1560
|
+
area: sample.area
|
|
1561
|
+
};
|
|
1562
|
+
});
|
|
1527
1563
|
geometry.setAttribute("color", new BufferAttribute(colors, 3));
|
|
1528
1564
|
geometry.setAttribute("roughnessScore", new BufferAttribute(scores, 1));
|
|
1529
1565
|
geometry.computeBoundingBox();
|
|
1530
1566
|
return {
|
|
1531
1567
|
geometry,
|
|
1568
|
+
pointSamples,
|
|
1532
1569
|
summary: summarizeRoughnessTriangles(triangles, edgeAngles, edges.size, boundaryEdgeCount, nonManifoldEdgeCount, options),
|
|
1533
1570
|
warnings
|
|
1534
1571
|
};
|
|
1535
1572
|
}
|
|
1536
|
-
function markTriangleRoughness(edges, triangles, normals) {
|
|
1573
|
+
function markTriangleRoughness(edges, triangles, normals, triangleEdgeAngles) {
|
|
1537
1574
|
const edgeAngles = [];
|
|
1538
1575
|
let boundaryEdgeCount = 0;
|
|
1539
1576
|
let nonManifoldEdgeCount = 0;
|
|
1540
1577
|
for (const edge of edges.values()) {
|
|
1541
1578
|
if (edge.triangles.length === 1) {
|
|
1542
1579
|
boundaryEdgeCount += 1;
|
|
1543
|
-
|
|
1580
|
+
markEdge(edge, triangles, triangleEdgeAngles, 180);
|
|
1544
1581
|
edgeAngles.push(180);
|
|
1545
1582
|
continue;
|
|
1546
1583
|
}
|
|
1547
1584
|
if (edge.triangles.length > 2) {
|
|
1548
1585
|
nonManifoldEdgeCount += 1;
|
|
1549
|
-
|
|
1586
|
+
markEdge(edge, triangles, triangleEdgeAngles, 180);
|
|
1550
1587
|
edgeAngles.push(180);
|
|
1551
1588
|
continue;
|
|
1552
1589
|
}
|
|
1553
1590
|
const [first, second] = edge.triangles;
|
|
1554
1591
|
const dot = MathUtils.clamp(normals[first].dot(normals[second]), -1, 1);
|
|
1555
1592
|
const angleDeg = Math.acos(dot) * DEG_PER_RAD;
|
|
1556
|
-
|
|
1593
|
+
markEdge(edge, triangles, triangleEdgeAngles, angleDeg);
|
|
1557
1594
|
edgeAngles.push(angleDeg);
|
|
1558
1595
|
}
|
|
1559
1596
|
return { boundaryEdgeCount, nonManifoldEdgeCount, edgeAngles };
|
|
@@ -1561,17 +1598,218 @@ function markTriangleRoughness(edges, triangles, normals) {
|
|
|
1561
1598
|
function readVertex(position, index, target) {
|
|
1562
1599
|
target.set(position.getX(index), position.getY(index), position.getZ(index));
|
|
1563
1600
|
}
|
|
1564
|
-
function vertexKey(point, snap) {
|
|
1601
|
+
function vertexKey$1(point, snap) {
|
|
1565
1602
|
return `${Math.round(point.x / snap)},${Math.round(point.y / snap)},${Math.round(point.z / snap)}`;
|
|
1566
1603
|
}
|
|
1567
1604
|
function edgeKey(a, b) {
|
|
1568
1605
|
return a < b ? `${a}|${b}` : `${b}|${a}`;
|
|
1569
1606
|
}
|
|
1570
|
-
function
|
|
1571
|
-
for (const
|
|
1572
|
-
triangles[
|
|
1607
|
+
function markEdge(edge, triangles, triangleEdgeAngles, angleDeg) {
|
|
1608
|
+
for (const { triangle, edge: edgeIndex } of edge.triangleEdges) {
|
|
1609
|
+
triangles[triangle].maxAngleDeg = Math.max(triangles[triangle].maxAngleDeg, angleDeg);
|
|
1610
|
+
triangleEdgeAngles[triangle][edgeIndex] = Math.max(triangleEdgeAngles[triangle][edgeIndex], angleDeg);
|
|
1573
1611
|
}
|
|
1574
1612
|
}
|
|
1613
|
+
function roughnessEdgeInfluenceRadius(triangles, sampleCount) {
|
|
1614
|
+
if (triangles.length === 0 || sampleCount <= 0) return 0;
|
|
1615
|
+
return Math.sqrt(totalSurfaceArea(triangles) / sampleCount);
|
|
1616
|
+
}
|
|
1617
|
+
function roughnessAngleAtSample(point, triangle, edgeAngles, influenceRadius) {
|
|
1618
|
+
let angle = 0;
|
|
1619
|
+
for (let edge = 0; edge < 3; edge += 1) {
|
|
1620
|
+
const edgeAngle = edgeAngles[edge] ?? 0;
|
|
1621
|
+
if (edgeAngle <= 0) continue;
|
|
1622
|
+
const distance = pointDistanceToTriangleEdge(point, triangle, edge);
|
|
1623
|
+
if (distance <= influenceRadius) angle = Math.max(angle, edgeAngle);
|
|
1624
|
+
}
|
|
1625
|
+
return angle;
|
|
1626
|
+
}
|
|
1627
|
+
function pointDistanceToTriangleEdge(point, triangle, edge) {
|
|
1628
|
+
if (edge === 0) return pointSegmentDistance(point, triangle.a, triangle.b);
|
|
1629
|
+
if (edge === 1) return pointSegmentDistance(point, triangle.b, triangle.c);
|
|
1630
|
+
return pointSegmentDistance(point, triangle.c, triangle.a);
|
|
1631
|
+
}
|
|
1632
|
+
function pointSegmentDistance(point, start, end) {
|
|
1633
|
+
const segment = end.clone().sub(start);
|
|
1634
|
+
const lengthSq = segment.lengthSq();
|
|
1635
|
+
if (lengthSq <= 1e-18) return point.distanceTo(start);
|
|
1636
|
+
const t = MathUtils.clamp(point.clone().sub(start).dot(segment) / lengthSq, 0, 1);
|
|
1637
|
+
return point.distanceTo(segment.multiplyScalar(t).add(start));
|
|
1638
|
+
}
|
|
1639
|
+
const DEFAULT_VERTEX_TOLERANCE = 1e-5;
|
|
1640
|
+
class UnionFind2 {
|
|
1641
|
+
constructor(size) {
|
|
1642
|
+
__publicField(this, "parent");
|
|
1643
|
+
__publicField(this, "rank");
|
|
1644
|
+
this.parent = Array.from({ length: size }, (_, index) => index);
|
|
1645
|
+
this.rank = Array.from({ length: size }, () => 0);
|
|
1646
|
+
}
|
|
1647
|
+
find(value) {
|
|
1648
|
+
const parent = this.parent[value];
|
|
1649
|
+
if (parent === value) return value;
|
|
1650
|
+
const root = this.find(parent);
|
|
1651
|
+
this.parent[value] = root;
|
|
1652
|
+
return root;
|
|
1653
|
+
}
|
|
1654
|
+
union(a, b) {
|
|
1655
|
+
const rootA = this.find(a);
|
|
1656
|
+
const rootB = this.find(b);
|
|
1657
|
+
if (rootA === rootB) return;
|
|
1658
|
+
if (this.rank[rootA] < this.rank[rootB]) {
|
|
1659
|
+
this.parent[rootA] = rootB;
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
if (this.rank[rootA] > this.rank[rootB]) {
|
|
1663
|
+
this.parent[rootB] = rootA;
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
this.parent[rootB] = rootA;
|
|
1667
|
+
this.rank[rootA] += 1;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
function emptyBBox() {
|
|
1671
|
+
return {
|
|
1672
|
+
min: [Infinity, Infinity, Infinity],
|
|
1673
|
+
max: [-Infinity, -Infinity, -Infinity]
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
function expandBBox(bbox, x, y, z) {
|
|
1677
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return;
|
|
1678
|
+
bbox.min[0] = Math.min(bbox.min[0], x);
|
|
1679
|
+
bbox.min[1] = Math.min(bbox.min[1], y);
|
|
1680
|
+
bbox.min[2] = Math.min(bbox.min[2], z);
|
|
1681
|
+
bbox.max[0] = Math.max(bbox.max[0], x);
|
|
1682
|
+
bbox.max[1] = Math.max(bbox.max[1], y);
|
|
1683
|
+
bbox.max[2] = Math.max(bbox.max[2], z);
|
|
1684
|
+
}
|
|
1685
|
+
function vertexKey(positions, vertexIndex, tolerance) {
|
|
1686
|
+
const base = vertexIndex * 3;
|
|
1687
|
+
const x = positions[base];
|
|
1688
|
+
const y = positions[base + 1];
|
|
1689
|
+
const z = positions[base + 2];
|
|
1690
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return null;
|
|
1691
|
+
return `${Math.round(x / tolerance)},${Math.round(y / tolerance)},${Math.round(z / tolerance)}`;
|
|
1692
|
+
}
|
|
1693
|
+
function analyzeMeshConnectedComponents(positions, vertexTolerance = DEFAULT_VERTEX_TOLERANCE) {
|
|
1694
|
+
const vertexCount = Math.floor(positions.length / 3);
|
|
1695
|
+
const triangleCount = Math.floor(vertexCount / 3);
|
|
1696
|
+
const vertexComponentIndexes = new Int32Array(vertexCount);
|
|
1697
|
+
if (triangleCount === 0) return { components: [], vertexComponentIndexes };
|
|
1698
|
+
const tolerance = Number.isFinite(vertexTolerance) && vertexTolerance > 0 ? vertexTolerance : DEFAULT_VERTEX_TOLERANCE;
|
|
1699
|
+
const unionFind = new UnionFind2(triangleCount);
|
|
1700
|
+
const vertexOwnerByKey = /* @__PURE__ */ new Map();
|
|
1701
|
+
for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
1702
|
+
for (let corner = 0; corner < 3; corner += 1) {
|
|
1703
|
+
const key = vertexKey(positions, triangleIndex * 3 + corner, tolerance);
|
|
1704
|
+
if (key == null) continue;
|
|
1705
|
+
const owner = vertexOwnerByKey.get(key);
|
|
1706
|
+
if (owner === void 0) {
|
|
1707
|
+
vertexOwnerByKey.set(key, triangleIndex);
|
|
1708
|
+
} else {
|
|
1709
|
+
unionFind.union(triangleIndex, owner);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
const componentIndexByRoot = /* @__PURE__ */ new Map();
|
|
1714
|
+
const components = [];
|
|
1715
|
+
for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
1716
|
+
const root = unionFind.find(triangleIndex);
|
|
1717
|
+
let componentIndex = componentIndexByRoot.get(root);
|
|
1718
|
+
if (componentIndex === void 0) {
|
|
1719
|
+
componentIndex = components.length;
|
|
1720
|
+
componentIndexByRoot.set(root, componentIndex);
|
|
1721
|
+
components.push({
|
|
1722
|
+
index: componentIndex,
|
|
1723
|
+
triangleIndexes: [],
|
|
1724
|
+
vertexCount: 0,
|
|
1725
|
+
bbox: emptyBBox()
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
const component = components[componentIndex];
|
|
1729
|
+
component.triangleIndexes.push(triangleIndex);
|
|
1730
|
+
component.vertexCount += 3;
|
|
1731
|
+
for (let corner = 0; corner < 3; corner += 1) {
|
|
1732
|
+
const vertexIndex = triangleIndex * 3 + corner;
|
|
1733
|
+
const base = vertexIndex * 3;
|
|
1734
|
+
vertexComponentIndexes[vertexIndex] = componentIndex;
|
|
1735
|
+
expandBBox(component.bbox, positions[base], positions[base + 1], positions[base + 2]);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return { components, vertexComponentIndexes };
|
|
1739
|
+
}
|
|
1740
|
+
function isFiniteComponent(component) {
|
|
1741
|
+
return component.bbox.min.every(Number.isFinite) && component.bbox.max.every(Number.isFinite);
|
|
1742
|
+
}
|
|
1743
|
+
function objectConnectivityEntry(object) {
|
|
1744
|
+
return {
|
|
1745
|
+
id: object.id,
|
|
1746
|
+
name: object.name,
|
|
1747
|
+
shape: object.shape,
|
|
1748
|
+
min: object.min,
|
|
1749
|
+
max: object.max,
|
|
1750
|
+
groupName: object.groupName,
|
|
1751
|
+
treePath: object.treePath,
|
|
1752
|
+
mock: object.mock,
|
|
1753
|
+
bodyCount: 1
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
function buildMeshBodyConnectivityInput(objects, options) {
|
|
1757
|
+
const entries = [];
|
|
1758
|
+
const bodyIdsByObjectId = /* @__PURE__ */ new Map();
|
|
1759
|
+
const vertexComponentIndexesByObjectId = /* @__PURE__ */ new Map();
|
|
1760
|
+
for (const object of objects) {
|
|
1761
|
+
if (!object.positions || object.positions.length < 9) {
|
|
1762
|
+
entries.push(objectConnectivityEntry(object));
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
const meshComponents = analyzeMeshConnectedComponents(object.positions);
|
|
1766
|
+
const finiteComponents = meshComponents.components.filter(isFiniteComponent);
|
|
1767
|
+
if (finiteComponents.length <= 1) {
|
|
1768
|
+
entries.push(objectConnectivityEntry(object));
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
const bodyIds = Array.from({ length: meshComponents.components.length }, (_, index) => `${object.id}:body:${index + 1}`);
|
|
1772
|
+
for (const component of finiteComponents) {
|
|
1773
|
+
entries.push({
|
|
1774
|
+
id: bodyIds[component.index],
|
|
1775
|
+
name: `${object.name} body ${component.index + 1}`,
|
|
1776
|
+
shape: options.bodyShape(object, component),
|
|
1777
|
+
min: component.bbox.min,
|
|
1778
|
+
max: component.bbox.max,
|
|
1779
|
+
groupName: object.groupName,
|
|
1780
|
+
treePath: object.treePath,
|
|
1781
|
+
mock: object.mock,
|
|
1782
|
+
bodyCount: 1
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
bodyIdsByObjectId.set(object.id, bodyIds);
|
|
1786
|
+
vertexComponentIndexesByObjectId.set(object.id, meshComponents.vertexComponentIndexes);
|
|
1787
|
+
}
|
|
1788
|
+
return { entries, bodyIdsByObjectId, vertexComponentIndexesByObjectId };
|
|
1789
|
+
}
|
|
1790
|
+
function meshVertexColorBuffersFor(input, colorForEntryId) {
|
|
1791
|
+
const out = /* @__PURE__ */ new Map();
|
|
1792
|
+
const colorByEntryId = /* @__PURE__ */ new Map();
|
|
1793
|
+
for (const [objectId, vertexComponentIndexes] of input.vertexComponentIndexesByObjectId) {
|
|
1794
|
+
const bodyIds = input.bodyIdsByObjectId.get(objectId);
|
|
1795
|
+
if (!bodyIds) continue;
|
|
1796
|
+
const colors = new Float32Array(vertexComponentIndexes.length * 3);
|
|
1797
|
+
for (let vertexIndex = 0; vertexIndex < vertexComponentIndexes.length; vertexIndex += 1) {
|
|
1798
|
+
const bodyId = bodyIds[vertexComponentIndexes[vertexIndex]];
|
|
1799
|
+
let color = colorByEntryId.get(bodyId);
|
|
1800
|
+
if (!color) {
|
|
1801
|
+
color = colorForEntryId(bodyId);
|
|
1802
|
+
colorByEntryId.set(bodyId, color);
|
|
1803
|
+
}
|
|
1804
|
+
const offset = vertexIndex * 3;
|
|
1805
|
+
colors[offset] = color[0];
|
|
1806
|
+
colors[offset + 1] = color[1];
|
|
1807
|
+
colors[offset + 2] = color[2];
|
|
1808
|
+
}
|
|
1809
|
+
out.set(objectId, colors);
|
|
1810
|
+
}
|
|
1811
|
+
return out;
|
|
1812
|
+
}
|
|
1575
1813
|
const canvas = document.getElementById("canvas");
|
|
1576
1814
|
const exportCanvas = document.createElement("canvas");
|
|
1577
1815
|
const exportCtx = exportCanvas.getContext("2d");
|
|
@@ -1605,6 +1843,14 @@ function createCaptureDebugLogger(enabled) {
|
|
|
1605
1843
|
console.info(`[forge-capture:debug] +${sinceStart}ms Δ${delta}ms ${phase}${detailText}`);
|
|
1606
1844
|
};
|
|
1607
1845
|
}
|
|
1846
|
+
class EmptyInspectionShape {
|
|
1847
|
+
intersect() {
|
|
1848
|
+
return {
|
|
1849
|
+
isEmpty: () => true,
|
|
1850
|
+
volume: () => 0
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1608
1854
|
const COLLISION_SOURCE_OPACITY = 0.22;
|
|
1609
1855
|
const COLLISION_SOURCE_COLOR = [180, 200, 220];
|
|
1610
1856
|
const COLLISION_HIGHLIGHT_COLOR = [255, 68, 16];
|
|
@@ -1648,6 +1894,31 @@ const MASK_PALETTE = [
|
|
|
1648
1894
|
[0, 0, 128],
|
|
1649
1895
|
[128, 128, 128]
|
|
1650
1896
|
];
|
|
1897
|
+
function cloneShapePositions(shape) {
|
|
1898
|
+
const geometry = shapeToGeometry(shape);
|
|
1899
|
+
try {
|
|
1900
|
+
const position = geometry.solid.getAttribute("position");
|
|
1901
|
+
if (!position) return void 0;
|
|
1902
|
+
return new Float32Array(position.array);
|
|
1903
|
+
} finally {
|
|
1904
|
+
geometry.solid.dispose();
|
|
1905
|
+
geometry.edges.dispose();
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
function meshConnectivitySource(entry) {
|
|
1909
|
+
const bb = entry.shape.boundingBox();
|
|
1910
|
+
return {
|
|
1911
|
+
id: entry.source.id,
|
|
1912
|
+
name: entry.source.name,
|
|
1913
|
+
shape: entry.shape,
|
|
1914
|
+
min: [bb.min[0], bb.min[1], bb.min[2]],
|
|
1915
|
+
max: [bb.max[0], bb.max[1], bb.max[2]],
|
|
1916
|
+
groupName: entry.source.groupName,
|
|
1917
|
+
treePath: entry.source.treePath,
|
|
1918
|
+
mock: entry.source.mock,
|
|
1919
|
+
positions: cloneShapePositions(entry.shape)
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1651
1922
|
function summarizeSceneGeometry(entries) {
|
|
1652
1923
|
const min = [Infinity, Infinity, Infinity];
|
|
1653
1924
|
const max = [-Infinity, -Infinity, -Infinity];
|
|
@@ -2021,91 +2292,16 @@ function distanceColorForRootDistance(distance, maxDistance) {
|
|
|
2021
2292
|
if (t <= 0.5) return lerpColor(DISTANCE_NEAR_COLOR, DISTANCE_MID_COLOR, t * 2);
|
|
2022
2293
|
return lerpColor(DISTANCE_MID_COLOR, DISTANCE_FAR_COLOR, (t - 0.5) * 2);
|
|
2023
2294
|
}
|
|
2024
|
-
function
|
|
2025
|
-
return
|
|
2026
|
-
}
|
|
2027
|
-
function geometryMaxDimension(geometry) {
|
|
2028
|
-
geometry.computeBoundingBox();
|
|
2029
|
-
const box = geometry.boundingBox;
|
|
2030
|
-
if (!box) return 1;
|
|
2031
|
-
const size = new Vector3();
|
|
2032
|
-
box.getSize(size);
|
|
2033
|
-
return Math.max(1, size.x, size.y, size.z);
|
|
2034
|
-
}
|
|
2035
|
-
function firstOppositeSurfaceDistance(raycaster, mesh, point, direction, epsilon, far) {
|
|
2036
|
-
const origin = point.clone().addScaledVector(direction, epsilon);
|
|
2037
|
-
raycaster.set(origin, direction);
|
|
2038
|
-
raycaster.near = epsilon;
|
|
2039
|
-
raycaster.far = far;
|
|
2040
|
-
const hit = raycaster.intersectObject(mesh, false).find((entry) => entry.distance > epsilon);
|
|
2041
|
-
return hit ? hit.distance + epsilon : null;
|
|
2042
|
-
}
|
|
2043
|
-
function triangleThickness(raycaster, mesh, centroid, normal, epsilon, far) {
|
|
2044
|
-
const forward = firstOppositeSurfaceDistance(raycaster, mesh, centroid, normal, epsilon, far);
|
|
2045
|
-
const backward = firstOppositeSurfaceDistance(raycaster, mesh, centroid, normal.clone().negate(), epsilon, far);
|
|
2046
|
-
if (forward == null) return backward;
|
|
2047
|
-
if (backward == null) return forward;
|
|
2048
|
-
return Math.min(forward, backward);
|
|
2295
|
+
function rgbFloats(color) {
|
|
2296
|
+
return [color[0] / 255, color[1] / 255, color[2] / 255];
|
|
2049
2297
|
}
|
|
2050
|
-
function
|
|
2051
|
-
|
|
2052
|
-
const position = geometry.getAttribute("position");
|
|
2053
|
-
if (!position || position.count
|
|
2054
|
-
|
|
2055
|
-
}
|
|
2056
|
-
const triangleCount = Math.floor(position.count / 3);
|
|
2057
|
-
const sampleStride = Math.max(1, Math.ceil(triangleCount / options.maxSamplesPerObject));
|
|
2058
|
-
const maxDim = geometryMaxDimension(geometry);
|
|
2059
|
-
const epsilon = Math.max(1e-4, maxDim * 1e-6);
|
|
2060
|
-
const far = Math.max(maxDim * 4, options.maxThickness * 4, 1);
|
|
2061
|
-
const colors = new Float32Array(position.count * 3);
|
|
2062
|
-
const samples = [];
|
|
2063
|
-
const warnings = [];
|
|
2064
|
-
const rayMaterial = new MeshBasicMaterial({ side: DoubleSide });
|
|
2065
|
-
const rayMesh = new Mesh(geometry, rayMaterial);
|
|
2066
|
-
const raycaster = new Raycaster();
|
|
2067
|
-
if (sampleStride > 1) {
|
|
2068
|
-
warnings.push(`Triangle sampling stride ${sampleStride}; increase --thickness-samples for denser analysis.`);
|
|
2069
|
-
}
|
|
2070
|
-
const a = new Vector3();
|
|
2071
|
-
const b = new Vector3();
|
|
2072
|
-
const c = new Vector3();
|
|
2073
|
-
const normal = new Vector3();
|
|
2074
|
-
const centroid = new Vector3();
|
|
2075
|
-
let sampledTriangleCount = 0;
|
|
2076
|
-
let lastThickness = null;
|
|
2077
|
-
for (let tri = 0; tri < triangleCount; tri += 1) {
|
|
2078
|
-
const offset = tri * 3;
|
|
2079
|
-
a.fromBufferAttribute(position, offset);
|
|
2080
|
-
b.fromBufferAttribute(position, offset + 1);
|
|
2081
|
-
c.fromBufferAttribute(position, offset + 2);
|
|
2082
|
-
normal.subVectors(b, a).cross(new Vector3().subVectors(c, a));
|
|
2083
|
-
const areaTwice = normal.length();
|
|
2084
|
-
const area = areaTwice * 0.5;
|
|
2085
|
-
let thickness = lastThickness;
|
|
2086
|
-
if (tri % sampleStride === 0) {
|
|
2087
|
-
sampledTriangleCount += 1;
|
|
2088
|
-
if (areaTwice <= 1e-12) {
|
|
2089
|
-
thickness = null;
|
|
2090
|
-
} else {
|
|
2091
|
-
normal.multiplyScalar(1 / areaTwice);
|
|
2092
|
-
centroid.copy(a).add(b).add(c).multiplyScalar(1 / 3);
|
|
2093
|
-
thickness = triangleThickness(raycaster, rayMesh, centroid, normal, epsilon, far);
|
|
2094
|
-
}
|
|
2095
|
-
lastThickness = thickness;
|
|
2096
|
-
samples.push({ thickness, area });
|
|
2097
|
-
}
|
|
2098
|
-
const color = thicknessColor(thickness, options);
|
|
2099
|
-
for (let vertex = 0; vertex < 3; vertex += 1) {
|
|
2100
|
-
const colorOffset = (offset + vertex) * 3;
|
|
2101
|
-
colors[colorOffset] = color[0] / 255;
|
|
2102
|
-
colors[colorOffset + 1] = color[1] / 255;
|
|
2103
|
-
colors[colorOffset + 2] = color[2] / 255;
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2298
|
+
function geometryWithVertexColors(renderable, colors) {
|
|
2299
|
+
if (!colors) return null;
|
|
2300
|
+
const position = renderable.solid.geometry.getAttribute("position");
|
|
2301
|
+
if (!position || colors.length !== position.count * 3) return null;
|
|
2302
|
+
const geometry = renderable.solid.geometry.clone();
|
|
2106
2303
|
geometry.setAttribute("color", new BufferAttribute(colors, 3));
|
|
2107
|
-
|
|
2108
|
-
return { geometry, samples, triangleCount, sampledTriangleCount, sampleStride, warnings };
|
|
2304
|
+
return geometry;
|
|
2109
2305
|
}
|
|
2110
2306
|
function buildMaskEntries(session) {
|
|
2111
2307
|
const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
|
|
@@ -2196,6 +2392,7 @@ function decorateConnectivityReport(report) {
|
|
|
2196
2392
|
return {
|
|
2197
2393
|
method: report.method,
|
|
2198
2394
|
options: report.options,
|
|
2395
|
+
broadphase: report.broadphase,
|
|
2199
2396
|
objectCount: report.objectCount,
|
|
2200
2397
|
componentCount: report.componentCount,
|
|
2201
2398
|
objects,
|
|
@@ -2208,30 +2405,26 @@ function renderCurrentConnectivity(session, rawReport) {
|
|
|
2208
2405
|
const r = getRenderer(session.size, session.pixelRatio);
|
|
2209
2406
|
const report = rawReport ?? analyzeSessionConnectivity(session);
|
|
2210
2407
|
const byId = new Map(report.objects.map((object) => [object.id, object]));
|
|
2408
|
+
const vertexColorsById = session.connectivityBodyInput ? meshVertexColorBuffersFor(session.connectivityBodyInput, (entryId) => {
|
|
2409
|
+
const object = byId.get(entryId);
|
|
2410
|
+
return rgbFloats(object ? maskColorForIndex(object.componentIndex) : [128, 128, 128]);
|
|
2411
|
+
}) : /* @__PURE__ */ new Map();
|
|
2211
2412
|
const replacements = session.renderables.map((renderable) => {
|
|
2212
2413
|
const object = byId.get(renderable.id);
|
|
2213
2414
|
const color = object ? maskColorForIndex(object.componentIndex) : [128, 128, 128];
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
`,
|
|
2223
|
-
fragmentShader: `
|
|
2224
|
-
precision highp float;
|
|
2225
|
-
uniform vec3 connectivityColor;
|
|
2226
|
-
void main() {
|
|
2227
|
-
gl_FragColor = vec4(connectivityColor, 1.0);
|
|
2228
|
-
}
|
|
2229
|
-
`
|
|
2415
|
+
const previousMaterial = renderable.solid.material;
|
|
2416
|
+
const previousGeometry = renderable.solid.geometry;
|
|
2417
|
+
const vertexGeometry = geometryWithVertexColors(renderable, vertexColorsById.get(renderable.id));
|
|
2418
|
+
const material = new MeshBasicMaterial({
|
|
2419
|
+
color: vertexGeometry ? 16777215 : new Color(color[0] / 255, color[1] / 255, color[2] / 255),
|
|
2420
|
+
vertexColors: Boolean(vertexGeometry),
|
|
2421
|
+
side: DoubleSide,
|
|
2422
|
+
clippingPlanes: renderable.solidMaterial.clippingPlanes ?? null
|
|
2230
2423
|
});
|
|
2231
2424
|
material.toneMapped = false;
|
|
2232
|
-
|
|
2425
|
+
if (vertexGeometry) renderable.solid.geometry = vertexGeometry;
|
|
2233
2426
|
renderable.solid.material = material;
|
|
2234
|
-
return { renderable, previousMaterial, material };
|
|
2427
|
+
return { renderable, previousMaterial, previousGeometry, vertexGeometry, material };
|
|
2235
2428
|
});
|
|
2236
2429
|
try {
|
|
2237
2430
|
const png = withSolidOnlyVisibility(
|
|
@@ -2244,8 +2437,10 @@ function renderCurrentConnectivity(session, rawReport) {
|
|
|
2244
2437
|
);
|
|
2245
2438
|
return { png, report: decorateConnectivityReport(report) };
|
|
2246
2439
|
} finally {
|
|
2247
|
-
replacements.forEach(({ renderable, previousMaterial, material }) => {
|
|
2440
|
+
replacements.forEach(({ renderable, previousMaterial, previousGeometry, vertexGeometry, material }) => {
|
|
2248
2441
|
renderable.solid.material = previousMaterial;
|
|
2442
|
+
renderable.solid.geometry = previousGeometry;
|
|
2443
|
+
vertexGeometry == null ? void 0 : vertexGeometry.dispose();
|
|
2249
2444
|
material.dispose();
|
|
2250
2445
|
});
|
|
2251
2446
|
}
|
|
@@ -2284,6 +2479,7 @@ function decorateDistanceReport(report) {
|
|
|
2284
2479
|
maxRootDistance: report.maxRootDistance,
|
|
2285
2480
|
objects,
|
|
2286
2481
|
components,
|
|
2482
|
+
gapEdgeCount: report.gapEdgeCount,
|
|
2287
2483
|
gapEdges: report.gapEdges,
|
|
2288
2484
|
connectivity: report.connectivity,
|
|
2289
2485
|
warnings: report.warnings,
|
|
@@ -2303,30 +2499,26 @@ function renderCurrentDistance(session) {
|
|
|
2303
2499
|
const r = getRenderer(session.size, session.pixelRatio);
|
|
2304
2500
|
const report = decorateDistanceReport(analyzeSessionDistance(session));
|
|
2305
2501
|
const byId = new Map(report.objects.map((object) => [object.id, object]));
|
|
2502
|
+
const vertexColorsById = session.connectivityBodyInput ? meshVertexColorBuffersFor(session.connectivityBodyInput, (entryId) => {
|
|
2503
|
+
var _a;
|
|
2504
|
+
return rgbFloats(((_a = byId.get(entryId)) == null ? void 0 : _a.color) ?? DISTANCE_UNKNOWN_COLOR);
|
|
2505
|
+
}) : /* @__PURE__ */ new Map();
|
|
2306
2506
|
const replacements = session.renderables.map((renderable) => {
|
|
2307
2507
|
const object = byId.get(renderable.id);
|
|
2308
2508
|
const color = (object == null ? void 0 : object.color) ?? DISTANCE_UNKNOWN_COLOR;
|
|
2309
|
-
const
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
`,
|
|
2318
|
-
fragmentShader: `
|
|
2319
|
-
precision highp float;
|
|
2320
|
-
uniform vec3 distanceColor;
|
|
2321
|
-
void main() {
|
|
2322
|
-
gl_FragColor = vec4(distanceColor, 1.0);
|
|
2323
|
-
}
|
|
2324
|
-
`
|
|
2509
|
+
const previousMaterial = renderable.solid.material;
|
|
2510
|
+
const previousGeometry = renderable.solid.geometry;
|
|
2511
|
+
const vertexGeometry = geometryWithVertexColors(renderable, vertexColorsById.get(renderable.id));
|
|
2512
|
+
const material = new MeshBasicMaterial({
|
|
2513
|
+
color: vertexGeometry ? 16777215 : new Color(color[0] / 255, color[1] / 255, color[2] / 255),
|
|
2514
|
+
vertexColors: Boolean(vertexGeometry),
|
|
2515
|
+
side: DoubleSide,
|
|
2516
|
+
clippingPlanes: renderable.solidMaterial.clippingPlanes ?? null
|
|
2325
2517
|
});
|
|
2326
2518
|
material.toneMapped = false;
|
|
2327
|
-
|
|
2519
|
+
if (vertexGeometry) renderable.solid.geometry = vertexGeometry;
|
|
2328
2520
|
renderable.solid.material = material;
|
|
2329
|
-
return { renderable, previousMaterial, material };
|
|
2521
|
+
return { renderable, previousMaterial, previousGeometry, vertexGeometry, material };
|
|
2330
2522
|
});
|
|
2331
2523
|
try {
|
|
2332
2524
|
const png = withSolidOnlyVisibility(
|
|
@@ -2339,8 +2531,10 @@ function renderCurrentDistance(session) {
|
|
|
2339
2531
|
);
|
|
2340
2532
|
return { png, report };
|
|
2341
2533
|
} finally {
|
|
2342
|
-
replacements.forEach(({ renderable, previousMaterial, material }) => {
|
|
2534
|
+
replacements.forEach(({ renderable, previousMaterial, previousGeometry, vertexGeometry, material }) => {
|
|
2343
2535
|
renderable.solid.material = previousMaterial;
|
|
2536
|
+
renderable.solid.geometry = previousGeometry;
|
|
2537
|
+
vertexGeometry == null ? void 0 : vertexGeometry.dispose();
|
|
2344
2538
|
material.dispose();
|
|
2345
2539
|
});
|
|
2346
2540
|
}
|
|
@@ -2440,26 +2634,113 @@ function renderCurrentCollisions(session) {
|
|
|
2440
2634
|
collisionMaterials.forEach((material) => material.dispose());
|
|
2441
2635
|
}
|
|
2442
2636
|
}
|
|
2443
|
-
function
|
|
2637
|
+
function inspectionOptionsKey(value) {
|
|
2638
|
+
return JSON.stringify(value ?? {});
|
|
2639
|
+
}
|
|
2640
|
+
function bboxFromGeometry(geometry) {
|
|
2641
|
+
geometry.computeBoundingBox();
|
|
2642
|
+
const bbox = geometry.boundingBox;
|
|
2643
|
+
return {
|
|
2644
|
+
min: bbox ? [bbox.min.x, bbox.min.y, bbox.min.z] : [0, 0, 0],
|
|
2645
|
+
max: bbox ? [bbox.max.x, bbox.max.y, bbox.max.z] : [0, 0, 0]
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
function pointBuffersFromSamples(samples, offset = 0.025) {
|
|
2649
|
+
const positions = new Float32Array(samples.length * 3);
|
|
2650
|
+
const colors = new Float32Array(samples.length * 3);
|
|
2651
|
+
samples.forEach((sample, index) => {
|
|
2652
|
+
const base = index * 3;
|
|
2653
|
+
positions[base] = sample.position[0] + sample.normal[0] * offset;
|
|
2654
|
+
positions[base + 1] = sample.position[1] + sample.normal[1] * offset;
|
|
2655
|
+
positions[base + 2] = sample.position[2] + sample.normal[2] * offset;
|
|
2656
|
+
colors[base] = sample.color[0] / 255;
|
|
2657
|
+
colors[base + 1] = sample.color[1] / 255;
|
|
2658
|
+
colors[base + 2] = sample.color[2] / 255;
|
|
2659
|
+
});
|
|
2660
|
+
return { positions, colors };
|
|
2661
|
+
}
|
|
2662
|
+
function renderScalarPointOverlays(session, overlays) {
|
|
2444
2663
|
const r = getRenderer(session.size, session.pixelRatio);
|
|
2664
|
+
const overlayById = new Map(overlays.map((overlay) => [overlay.renderable.id, overlay]));
|
|
2665
|
+
const replacements = session.renderables.map((renderable) => {
|
|
2666
|
+
const previousMaterial = renderable.solid.material;
|
|
2667
|
+
const ghostMaterial = new MeshBasicMaterial({
|
|
2668
|
+
color: new Color(2502970),
|
|
2669
|
+
transparent: true,
|
|
2670
|
+
opacity: 0.24,
|
|
2671
|
+
depthWrite: true,
|
|
2672
|
+
depthTest: true,
|
|
2673
|
+
side: DoubleSide,
|
|
2674
|
+
clippingPlanes: renderable.solidMaterial.clippingPlanes ?? void 0
|
|
2675
|
+
});
|
|
2676
|
+
ghostMaterial.toneMapped = false;
|
|
2677
|
+
renderable.solid.material = ghostMaterial;
|
|
2678
|
+
const overlay = overlayById.get(renderable.id);
|
|
2679
|
+
if (!overlay || overlay.positions.length === 0)
|
|
2680
|
+
return { renderable, previousMaterial, ghostMaterial, points: null, geometry: null, material: null };
|
|
2681
|
+
const geometry = new BufferGeometry();
|
|
2682
|
+
geometry.setAttribute("position", new BufferAttribute(overlay.positions, 3));
|
|
2683
|
+
geometry.setAttribute("color", new BufferAttribute(overlay.colors, 3));
|
|
2684
|
+
const material = new PointsMaterial({
|
|
2685
|
+
size: 3,
|
|
2686
|
+
sizeAttenuation: false,
|
|
2687
|
+
vertexColors: true,
|
|
2688
|
+
depthTest: true,
|
|
2689
|
+
depthWrite: false,
|
|
2690
|
+
clippingPlanes: renderable.solidMaterial.clippingPlanes ?? void 0
|
|
2691
|
+
});
|
|
2692
|
+
material.toneMapped = false;
|
|
2693
|
+
const points = new Points(geometry, material);
|
|
2694
|
+
points.renderOrder = 5;
|
|
2695
|
+
points.raycast = () => null;
|
|
2696
|
+
renderable.root.add(points);
|
|
2697
|
+
return { renderable, previousMaterial, ghostMaterial, points, geometry, material };
|
|
2698
|
+
});
|
|
2699
|
+
try {
|
|
2700
|
+
return withSolidOnlyVisibility(
|
|
2701
|
+
session,
|
|
2702
|
+
() => withTemporarySceneBackground(session, new Color(0), () => {
|
|
2703
|
+
updateSdfRaymarchUniforms(session);
|
|
2704
|
+
r.render(session.scene, session.camera);
|
|
2705
|
+
return captureRenderedPng(session.size);
|
|
2706
|
+
})
|
|
2707
|
+
);
|
|
2708
|
+
} finally {
|
|
2709
|
+
replacements.forEach(({ renderable, previousMaterial, ghostMaterial, points, geometry, material }) => {
|
|
2710
|
+
if (points) renderable.root.remove(points);
|
|
2711
|
+
renderable.solid.material = previousMaterial;
|
|
2712
|
+
ghostMaterial.dispose();
|
|
2713
|
+
geometry == null ? void 0 : geometry.dispose();
|
|
2714
|
+
material == null ? void 0 : material.dispose();
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
function getSessionThicknessInspection(session, rawOptions) {
|
|
2719
|
+
var _a;
|
|
2445
2720
|
const options = resolveThicknessInspectionOptions(rawOptions);
|
|
2446
|
-
const
|
|
2447
|
-
|
|
2721
|
+
const optionsKey = inspectionOptionsKey(options);
|
|
2722
|
+
if (((_a = session.thicknessInspection) == null ? void 0 : _a.optionsKey) === optionsKey) return session.thicknessInspection;
|
|
2448
2723
|
const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
|
|
2449
2724
|
const warnings = [];
|
|
2450
2725
|
const objects = [];
|
|
2451
|
-
const
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
const bbox = analysis.geometry.boundingBox;
|
|
2456
|
-
const summary = summarizeThicknessSamples(analysis.samples, options);
|
|
2726
|
+
const cloudObjects = [];
|
|
2727
|
+
const points = [];
|
|
2728
|
+
const overlays = [];
|
|
2729
|
+
session.renderables.forEach((renderable, index) => {
|
|
2457
2730
|
const sourceObject = byId.get(renderable.id);
|
|
2731
|
+
if (renderable.sdfRaymarch) {
|
|
2732
|
+
warnings.push(`${renderable.name}: SDF raymarch objects are not included in mesh thickness inspection.`);
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
const analysis = analyzeThicknessGeometry(renderable.solid.geometry, options);
|
|
2736
|
+
const bbox = bboxFromGeometry(analysis.geometry);
|
|
2737
|
+
const summary = summarizeThicknessSamples(analysis.samples, options);
|
|
2458
2738
|
if (analysis.warnings.length > 0) {
|
|
2459
2739
|
analysis.warnings.forEach((warning) => warnings.push(`${renderable.name}: ${warning}`));
|
|
2460
2740
|
}
|
|
2741
|
+
const objectIndex = index + 1;
|
|
2461
2742
|
objects.push({
|
|
2462
|
-
index:
|
|
2743
|
+
index: objectIndex,
|
|
2463
2744
|
id: renderable.id,
|
|
2464
2745
|
name: renderable.name,
|
|
2465
2746
|
groupName: renderable.groupName,
|
|
@@ -2468,174 +2749,154 @@ function renderCurrentThickness(session, rawOptions) {
|
|
|
2468
2749
|
triangleCount: analysis.triangleCount,
|
|
2469
2750
|
sampledTriangleCount: analysis.sampledTriangleCount,
|
|
2470
2751
|
sampleStride: analysis.sampleStride,
|
|
2471
|
-
bbox
|
|
2472
|
-
min: bbox ? [bbox.min.x, bbox.min.y, bbox.min.z] : [0, 0, 0],
|
|
2473
|
-
max: bbox ? [bbox.max.x, bbox.max.y, bbox.max.z] : [0, 0, 0]
|
|
2474
|
-
},
|
|
2752
|
+
bbox,
|
|
2475
2753
|
...summary
|
|
2476
2754
|
});
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2755
|
+
cloudObjects.push({
|
|
2756
|
+
index: objectIndex,
|
|
2757
|
+
id: renderable.id,
|
|
2758
|
+
name: renderable.name,
|
|
2759
|
+
groupName: renderable.groupName,
|
|
2760
|
+
treePath: sourceObject == null ? void 0 : sourceObject.treePath,
|
|
2761
|
+
mock: (sourceObject == null ? void 0 : sourceObject.mock) === true,
|
|
2762
|
+
sampleCount: analysis.pointSamples.length,
|
|
2763
|
+
bbox
|
|
2764
|
+
});
|
|
2765
|
+
analysis.pointSamples.forEach((sample) => {
|
|
2766
|
+
points.push({
|
|
2767
|
+
objectIndex,
|
|
2768
|
+
objectId: renderable.id,
|
|
2769
|
+
objectName: renderable.name,
|
|
2770
|
+
...sample
|
|
2771
|
+
});
|
|
2772
|
+
});
|
|
2773
|
+
overlays.push({ renderable, ...pointBuffersFromSamples(analysis.pointSamples) });
|
|
2774
|
+
analysis.geometry.dispose();
|
|
2480
2775
|
});
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2776
|
+
const state = {
|
|
2777
|
+
optionsKey,
|
|
2778
|
+
overlays,
|
|
2779
|
+
pointCloud: {
|
|
2780
|
+
schemaVersion: 1,
|
|
2781
|
+
property: "thickness",
|
|
2782
|
+
coordinateSpace: "object-local",
|
|
2783
|
+
units: "model",
|
|
2784
|
+
sampleCount: points.length,
|
|
2785
|
+
objects: cloudObjects,
|
|
2786
|
+
points
|
|
2787
|
+
},
|
|
2788
|
+
report: {
|
|
2789
|
+
method: "mesh-normal-raycast",
|
|
2790
|
+
options,
|
|
2791
|
+
objectCount: objects.length,
|
|
2792
|
+
objects,
|
|
2793
|
+
warnings,
|
|
2794
|
+
style: {
|
|
2795
|
+
criticalColor: THICKNESS_COLORS.critical,
|
|
2796
|
+
warningColor: THICKNESS_COLORS.warning,
|
|
2797
|
+
okColor: THICKNESS_COLORS.ok,
|
|
2798
|
+
thickColor: THICKNESS_COLORS.thick,
|
|
2799
|
+
unknownColor: THICKNESS_COLORS.unknown
|
|
2505
2800
|
}
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
}
|
|
2801
|
+
}
|
|
2802
|
+
};
|
|
2803
|
+
session.thicknessInspection = state;
|
|
2804
|
+
return state;
|
|
2805
|
+
}
|
|
2806
|
+
function renderCurrentThickness(session, rawOptions) {
|
|
2807
|
+
const state = getSessionThicknessInspection(session, rawOptions);
|
|
2808
|
+
return { png: renderScalarPointOverlays(session, state.overlays), report: state.report, pointCloud: state.pointCloud };
|
|
2515
2809
|
}
|
|
2516
2810
|
const ROUGHNESS_SMOOTH_OPACITY = 0.16;
|
|
2517
2811
|
const ROUGHNESS_HARSH_OPACITY = 1;
|
|
2518
|
-
function createRoughnessMaterial(clippingPlanes) {
|
|
2519
|
-
const material = new ShaderMaterial({
|
|
2520
|
-
transparent: true,
|
|
2521
|
-
depthTest: true,
|
|
2522
|
-
depthWrite: true,
|
|
2523
|
-
clippingPlanes: clippingPlanes ?? void 0,
|
|
2524
|
-
uniforms: {
|
|
2525
|
-
smoothOpacity: { value: ROUGHNESS_SMOOTH_OPACITY },
|
|
2526
|
-
harshOpacity: { value: ROUGHNESS_HARSH_OPACITY }
|
|
2527
|
-
},
|
|
2528
|
-
vertexShader: `
|
|
2529
|
-
attribute vec3 color;
|
|
2530
|
-
attribute float roughnessScore;
|
|
2531
|
-
varying vec3 vColor;
|
|
2532
|
-
varying float vRoughnessScore;
|
|
2533
|
-
varying vec3 vViewNormal;
|
|
2534
|
-
|
|
2535
|
-
void main() {
|
|
2536
|
-
vColor = color;
|
|
2537
|
-
vRoughnessScore = roughnessScore;
|
|
2538
|
-
vViewNormal = normalize(normalMatrix * normal);
|
|
2539
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
2540
|
-
}
|
|
2541
|
-
`,
|
|
2542
|
-
fragmentShader: `
|
|
2543
|
-
precision highp float;
|
|
2544
|
-
uniform float smoothOpacity;
|
|
2545
|
-
uniform float harshOpacity;
|
|
2546
|
-
varying vec3 vColor;
|
|
2547
|
-
varying float vRoughnessScore;
|
|
2548
|
-
varying vec3 vViewNormal;
|
|
2549
|
-
|
|
2550
|
-
void main() {
|
|
2551
|
-
vec3 shadowLight = normalize(vec3(0.32, 0.42, 0.84));
|
|
2552
|
-
float shade = 0.34 + 0.48 * max(dot(normalize(vViewNormal), shadowLight), 0.0);
|
|
2553
|
-
vec3 smoothShadow = vec3(0.26, 0.30, 0.35) * shade;
|
|
2554
|
-
float colorMix = smoothstep(0.04, 0.35, vRoughnessScore);
|
|
2555
|
-
vec3 finalColor = mix(smoothShadow, vColor, colorMix);
|
|
2556
|
-
float alpha = mix(smoothOpacity, harshOpacity, smoothstep(0.02, 0.55, vRoughnessScore));
|
|
2557
|
-
gl_FragColor = vec4(finalColor, alpha);
|
|
2558
|
-
}
|
|
2559
|
-
`
|
|
2560
|
-
});
|
|
2561
|
-
material.toneMapped = false;
|
|
2562
|
-
return material;
|
|
2563
|
-
}
|
|
2564
2812
|
function renderCurrentRoughness(session, rawOptions) {
|
|
2565
|
-
const
|
|
2813
|
+
const state = getSessionRoughnessInspection(session, rawOptions);
|
|
2814
|
+
return { png: renderScalarPointOverlays(session, state.overlays), report: state.report, pointCloud: state.pointCloud };
|
|
2815
|
+
}
|
|
2816
|
+
function getSessionRoughnessInspection(session, rawOptions) {
|
|
2817
|
+
var _a;
|
|
2566
2818
|
const options = resolveRoughnessInspectionOptions(rawOptions);
|
|
2819
|
+
const optionsKey = inspectionOptionsKey(options);
|
|
2820
|
+
if (((_a = session.roughnessInspection) == null ? void 0 : _a.optionsKey) === optionsKey) return session.roughnessInspection;
|
|
2567
2821
|
const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
|
|
2568
2822
|
const warnings = [];
|
|
2569
2823
|
const objects = [];
|
|
2570
|
-
const
|
|
2571
|
-
|
|
2572
|
-
|
|
2824
|
+
const cloudObjects = [];
|
|
2825
|
+
const points = [];
|
|
2826
|
+
const overlays = [];
|
|
2827
|
+
session.renderables.forEach((renderable, index) => {
|
|
2573
2828
|
if (renderable.sdfRaymarch) {
|
|
2574
|
-
const material2 = new MeshBasicMaterial({ transparent: true, opacity: 0, depthWrite: false });
|
|
2575
|
-
material2.toneMapped = false;
|
|
2576
|
-
renderable.solid.material = material2;
|
|
2577
2829
|
warnings.push(`${renderable.name}: SDF raymarch objects are not included in mesh roughness inspection.`);
|
|
2578
|
-
return
|
|
2830
|
+
return;
|
|
2579
2831
|
}
|
|
2580
|
-
const analysis = analyzeRoughnessGeometry(
|
|
2581
|
-
const bbox = analysis.geometry
|
|
2832
|
+
const analysis = analyzeRoughnessGeometry(renderable.solid.geometry, options);
|
|
2833
|
+
const bbox = bboxFromGeometry(analysis.geometry);
|
|
2582
2834
|
const sourceObject = byId.get(renderable.id);
|
|
2583
2835
|
if (analysis.warnings.length > 0) {
|
|
2584
2836
|
analysis.warnings.forEach((warning) => warnings.push(`${renderable.name}: ${warning}`));
|
|
2585
2837
|
}
|
|
2838
|
+
const objectIndex = index + 1;
|
|
2586
2839
|
objects.push({
|
|
2587
|
-
index:
|
|
2840
|
+
index: objectIndex,
|
|
2588
2841
|
id: renderable.id,
|
|
2589
2842
|
name: renderable.name,
|
|
2590
2843
|
groupName: renderable.groupName,
|
|
2591
2844
|
treePath: sourceObject == null ? void 0 : sourceObject.treePath,
|
|
2592
2845
|
mock: (sourceObject == null ? void 0 : sourceObject.mock) === true,
|
|
2593
|
-
bbox
|
|
2594
|
-
min: bbox ? [bbox.min.x, bbox.min.y, bbox.min.z] : [0, 0, 0],
|
|
2595
|
-
max: bbox ? [bbox.max.x, bbox.max.y, bbox.max.z] : [0, 0, 0]
|
|
2596
|
-
},
|
|
2846
|
+
bbox,
|
|
2597
2847
|
...analysis.summary
|
|
2598
2848
|
});
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2849
|
+
cloudObjects.push({
|
|
2850
|
+
index: objectIndex,
|
|
2851
|
+
id: renderable.id,
|
|
2852
|
+
name: renderable.name,
|
|
2853
|
+
groupName: renderable.groupName,
|
|
2854
|
+
treePath: sourceObject == null ? void 0 : sourceObject.treePath,
|
|
2855
|
+
mock: (sourceObject == null ? void 0 : sourceObject.mock) === true,
|
|
2856
|
+
sampleCount: analysis.pointSamples.length,
|
|
2857
|
+
bbox
|
|
2858
|
+
});
|
|
2859
|
+
analysis.pointSamples.forEach((sample) => {
|
|
2860
|
+
points.push({
|
|
2861
|
+
objectIndex,
|
|
2862
|
+
objectId: renderable.id,
|
|
2863
|
+
objectName: renderable.name,
|
|
2864
|
+
...sample
|
|
2865
|
+
});
|
|
2866
|
+
});
|
|
2867
|
+
overlays.push({ renderable, ...pointBuffersFromSamples(analysis.pointSamples) });
|
|
2868
|
+
analysis.geometry.dispose();
|
|
2603
2869
|
});
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2870
|
+
const state = {
|
|
2871
|
+
optionsKey,
|
|
2872
|
+
overlays,
|
|
2873
|
+
pointCloud: {
|
|
2874
|
+
schemaVersion: 1,
|
|
2875
|
+
property: "roughness",
|
|
2876
|
+
coordinateSpace: "object-local",
|
|
2877
|
+
units: "degrees",
|
|
2878
|
+
sampleCount: points.length,
|
|
2879
|
+
objects: cloudObjects,
|
|
2880
|
+
points
|
|
2881
|
+
},
|
|
2882
|
+
report: {
|
|
2883
|
+
method: "mesh-dihedral-angle",
|
|
2884
|
+
options,
|
|
2885
|
+
objectCount: objects.length,
|
|
2886
|
+
objects,
|
|
2887
|
+
warnings,
|
|
2888
|
+
style: {
|
|
2889
|
+
smoothColor: ROUGHNESS_COLORS.smooth,
|
|
2890
|
+
moderateColor: ROUGHNESS_COLORS.moderate,
|
|
2891
|
+
sharpColor: ROUGHNESS_COLORS.sharp,
|
|
2892
|
+
harshColor: ROUGHNESS_COLORS.harsh,
|
|
2893
|
+
smoothOpacity: ROUGHNESS_SMOOTH_OPACITY,
|
|
2894
|
+
harshOpacity: ROUGHNESS_HARSH_OPACITY
|
|
2629
2895
|
}
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
renderable.solid.material = previousMaterial;
|
|
2635
|
-
geometry == null ? void 0 : geometry.dispose();
|
|
2636
|
-
material.dispose();
|
|
2637
|
-
});
|
|
2638
|
-
}
|
|
2896
|
+
}
|
|
2897
|
+
};
|
|
2898
|
+
session.roughnessInspection = state;
|
|
2899
|
+
return state;
|
|
2639
2900
|
}
|
|
2640
2901
|
function emptySectionSvg() {
|
|
2641
2902
|
return {
|
|
@@ -3524,19 +3785,8 @@ function createSession(code, opts) {
|
|
|
3524
3785
|
name: entry.source.name,
|
|
3525
3786
|
shape: entry.shape
|
|
3526
3787
|
}));
|
|
3527
|
-
const
|
|
3528
|
-
|
|
3529
|
-
return {
|
|
3530
|
-
id: entry.source.id,
|
|
3531
|
-
name: entry.source.name,
|
|
3532
|
-
shape: entry.shape,
|
|
3533
|
-
min: [bb2.min[0], bb2.min[1], bb2.min[2]],
|
|
3534
|
-
max: [bb2.max[0], bb2.max[1], bb2.max[2]],
|
|
3535
|
-
groupName: entry.source.groupName,
|
|
3536
|
-
treePath: entry.source.treePath,
|
|
3537
|
-
mock: entry.source.mock
|
|
3538
|
-
};
|
|
3539
|
-
}) : [];
|
|
3788
|
+
const connectivityBodyInput = (opts == null ? void 0 : opts.includeConnectivity) ? buildMeshBodyConnectivityInput(shapeVisibleObjs.map(meshConnectivitySource), { bodyShape: () => new EmptyInspectionShape() }) : null;
|
|
3789
|
+
const connectivityEntries = (connectivityBodyInput == null ? void 0 : connectivityBodyInput.entries) ?? [];
|
|
3540
3790
|
const collisionEntries = (opts == null ? void 0 : opts.includeCollisions) ? shapeVisibleObjs.map((entry) => {
|
|
3541
3791
|
const bb2 = entry.shape.boundingBox();
|
|
3542
3792
|
return {
|
|
@@ -3610,11 +3860,7 @@ function createSession(code, opts) {
|
|
|
3610
3860
|
requestedSceneState = mergeViewportRenderSceneStates(requestedSceneState, {
|
|
3611
3861
|
camera: {
|
|
3612
3862
|
projectionMode: "perspective",
|
|
3613
|
-
position: [
|
|
3614
|
-
center.x + dir[0] * tokenDistance,
|
|
3615
|
-
center.y + dir[1] * tokenDistance,
|
|
3616
|
-
center.z + dir[2] * tokenDistance
|
|
3617
|
-
],
|
|
3863
|
+
position: [center.x + dir[0] * tokenDistance, center.y + dir[1] * tokenDistance, center.z + dir[2] * tokenDistance],
|
|
3618
3864
|
target: [center.x, center.y, center.z],
|
|
3619
3865
|
up: [0, 0, 1],
|
|
3620
3866
|
fov: cameraFov
|
|
@@ -3843,10 +4089,13 @@ function createSession(code, opts) {
|
|
|
3843
4089
|
renderables,
|
|
3844
4090
|
sectionShapes,
|
|
3845
4091
|
connectivityEntries,
|
|
4092
|
+
connectivityBodyInput,
|
|
3846
4093
|
physicalConnectivity: null,
|
|
3847
4094
|
distanceReport: null,
|
|
3848
4095
|
collisionEntries,
|
|
3849
4096
|
collisionReport: null,
|
|
4097
|
+
thicknessInspection: null,
|
|
4098
|
+
roughnessInspection: null,
|
|
3850
4099
|
joints,
|
|
3851
4100
|
jointCouplings,
|
|
3852
4101
|
animationClips,
|
|
@@ -3951,6 +4200,8 @@ window.__forgeRender = async (code, opts) => {
|
|
|
3951
4200
|
let collisionReport = null;
|
|
3952
4201
|
let thicknessReport = null;
|
|
3953
4202
|
let roughnessReport = null;
|
|
4203
|
+
let thicknessPointCloud = null;
|
|
4204
|
+
let roughnessPointCloud = null;
|
|
3954
4205
|
let sectionAtlas = null;
|
|
3955
4206
|
const channelViewCounts = /* @__PURE__ */ new Map();
|
|
3956
4207
|
const markChannelViewStart = async (channel, view) => {
|
|
@@ -3996,6 +4247,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
3996
4247
|
const roughness = renderCurrentRoughness(session, opts == null ? void 0 : opts.roughness);
|
|
3997
4248
|
roughnessRenders[label] = roughness.png;
|
|
3998
4249
|
roughnessReport = roughness.report;
|
|
4250
|
+
roughnessPointCloud = roughness.pointCloud;
|
|
3999
4251
|
await markChannelViewDone("roughness", label);
|
|
4000
4252
|
}
|
|
4001
4253
|
if (requestedChannels.has("mask")) {
|
|
@@ -4034,6 +4286,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4034
4286
|
const thickness = renderCurrentThickness(session, opts == null ? void 0 : opts.thickness);
|
|
4035
4287
|
thicknessRenders[label] = thickness.png;
|
|
4036
4288
|
thicknessReport = thickness.report;
|
|
4289
|
+
thicknessPointCloud = thickness.pointCloud;
|
|
4037
4290
|
await markChannelViewDone("thickness", label);
|
|
4038
4291
|
}
|
|
4039
4292
|
} catch (e) {
|
|
@@ -4060,6 +4313,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4060
4313
|
normals: normalRenders,
|
|
4061
4314
|
roughness: roughnessReport ? {
|
|
4062
4315
|
...roughnessReport,
|
|
4316
|
+
pointCloud: roughnessPointCloud,
|
|
4063
4317
|
views: roughnessRenders
|
|
4064
4318
|
} : {
|
|
4065
4319
|
views: roughnessRenders
|
|
@@ -4088,6 +4342,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4088
4342
|
},
|
|
4089
4343
|
thickness: thicknessReport ? {
|
|
4090
4344
|
...thicknessReport,
|
|
4345
|
+
pointCloud: thicknessPointCloud,
|
|
4091
4346
|
views: thicknessRenders
|
|
4092
4347
|
} : {
|
|
4093
4348
|
views: thicknessRenders
|