forgecad 0.9.7 → 0.9.8
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/README.md +1 -0
- package/dist/assets/{AdminPage-DX0mpSZT.js → AdminPage-CXaVLMiV.js} +1 -1
- package/dist/assets/{BlogPage-CI_P0_Pf.js → BlogPage-Crpr3JjH.js} +1 -1
- package/dist/assets/{DocsPage-DLhIIZyJ.js → DocsPage-CNBKuitP.js} +2 -2
- package/dist/assets/{EditorApp-DfFT2Dn8.css → EditorApp-D11wL4Qn.css} +51 -0
- package/dist/assets/{EditorApp-BujZvuwX.js → EditorApp-DVMnXOmO.js} +151 -9
- package/dist/assets/{EmbedViewer-0S0qXKog.js → EmbedViewer-KXFLSnpo.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-O_yMtAri.js → LandingPageProofDriven-2q2sn7aW.js} +1 -1
- package/dist/assets/{PricingPage-DGkX3Ahr.js → PricingPage-CVvgdv0i.js} +1 -1
- package/dist/assets/{SettingsPage-DBsqTB_y.js → SettingsPage-BVj1FtEv.js} +1 -1
- package/dist/assets/__vite-browser-external-Dhvy_jtL.js +4 -0
- package/dist/assets/{app-BE2nD6Yz.js → app-Dn4EwHhN.js} +707 -458
- package/dist/assets/cli/{render-iP9qh475.js → render-BI3gLMXz.js} +1011 -145
- package/dist/assets/constructionHistoryWorker-z9_LGiRd.js +42984 -0
- package/dist/assets/{evalWorker-Ds5U4xtN.js → evalWorker-CtO7GsJR.js} +42 -9
- package/dist/assets/{inspectWorker-Dll4eVyD.js → inspectWorker-BZ2CkQZr.js} +785 -111
- package/dist/assets/{manifold-sJ-axdXM.js → manifold-BVi4_OeB.js} +1 -1
- package/dist/assets/{manifold-DjYsd7A_.js → manifold-C6-sZYQN.js} +2 -2
- package/dist/assets/manifold-Cp_dCC7i.js +3018 -0
- package/dist/assets/{manifold-Bk26ViCr.js → manifold-DAzn2Fsa.js} +1 -1
- package/dist/assets/{renderSceneState-Bngp5MrQ.js → renderSceneState-BIvOkPK3.js} +1 -1
- package/dist/assets/{reportWorker-CU8RZ4O0.js → reportWorker-Bz9tGiHb.js} +42 -9
- package/dist/assets/{sectionPlaneMath-BdTjyVfs.js → scalar-sampling-budget-iBAeF8RM.js} +483 -71
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +1 -1
- package/dist/docs-raw/CLI.md +10 -10
- package/dist/docs-raw/coding-best-practices.md +1 -1
- package/dist/docs-raw/guides/inspection-bundles.md +77 -19
- package/dist/docs-raw/guides/skill-maintenance.md +1 -1
- package/dist/docs-raw/runbook.md +2 -2
- package/dist/docs-raw/skills/forgecad-make-a-model.md +11 -0
- package/dist/docs-raw/skills/forgecad-render-inspect.md +12 -6
- package/dist/docs-raw/skills/index.md +1 -1
- package/dist/index.html +1 -1
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +596 -354
- package/dist-cli/forgecad.js.map +1 -1
- package/dist-skill/CONTEXT.md +77 -19
- package/dist-skill/docs/CLI.md +10 -10
- package/dist-skill/docs/guides/inspection-bundles.md +77 -19
- package/dist-skill/docs-dev/CLI.md +10 -10
- package/dist-skill/docs-dev/coding-best-practices.md +1 -1
- package/dist-skill/docs-dev/guides/inspection-bundles.md +77 -19
- package/dist-skill/docs-dev/guides/skill-maintenance.md +1 -1
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +11 -0
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +12 -6
- package/package.json +6 -3
|
@@ -11132,7 +11132,7 @@ if (typeof window !== "undefined") {
|
|
|
11132
11132
|
}
|
|
11133
11133
|
}
|
|
11134
11134
|
const DEFAULT_LEAF_SIZE = 8;
|
|
11135
|
-
function cloneVec3$
|
|
11135
|
+
function cloneVec3$2(value) {
|
|
11136
11136
|
return [value[0], value[1], value[2]];
|
|
11137
11137
|
}
|
|
11138
11138
|
function emptyBounds() {
|
|
@@ -11200,14 +11200,14 @@ class AabbSpatialIndex {
|
|
|
11200
11200
|
nodeCount += 1;
|
|
11201
11201
|
const bounds = boundsFor(entries, indexes);
|
|
11202
11202
|
if (indexes.length <= this.leafSize) {
|
|
11203
|
-
return { min: cloneVec3$
|
|
11203
|
+
return { min: cloneVec3$2(bounds.min), max: cloneVec3$2(bounds.max), left: null, right: null, indexes };
|
|
11204
11204
|
}
|
|
11205
11205
|
const axis = longestAxis(bounds);
|
|
11206
11206
|
indexes.sort((a, b) => centerOnAxis(entries[a], axis) - centerOnAxis(entries[b], axis) || a - b);
|
|
11207
11207
|
const mid = Math.max(1, Math.floor(indexes.length / 2));
|
|
11208
11208
|
return {
|
|
11209
|
-
min: cloneVec3$
|
|
11210
|
-
max: cloneVec3$
|
|
11209
|
+
min: cloneVec3$2(bounds.min),
|
|
11210
|
+
max: cloneVec3$2(bounds.max),
|
|
11211
11211
|
left: build(indexes.slice(0, mid)),
|
|
11212
11212
|
right: build(indexes.slice(mid)),
|
|
11213
11213
|
indexes: null
|
|
@@ -11270,13 +11270,333 @@ class AabbSpatialIndex {
|
|
|
11270
11270
|
};
|
|
11271
11271
|
}
|
|
11272
11272
|
}
|
|
11273
|
+
const AXIS_NAMES = ["x", "y", "z"];
|
|
11274
|
+
function nearestBoundaryGap(a, b, axis) {
|
|
11275
|
+
return Math.min(Math.abs(a.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a.min[axis]));
|
|
11276
|
+
}
|
|
11277
|
+
function hasPositiveGap(gaps) {
|
|
11278
|
+
return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
|
|
11279
|
+
}
|
|
11280
|
+
function contactFromBBoxes(a, b, tolerance) {
|
|
11281
|
+
const gaps = aabbGaps(a, b);
|
|
11282
|
+
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
11283
|
+
if (largestGap > tolerance) return { touching: false, gap: largestGap };
|
|
11284
|
+
const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
|
|
11285
|
+
if (separatedAxes.length > 0) {
|
|
11286
|
+
const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
|
|
11287
|
+
return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
|
|
11288
|
+
}
|
|
11289
|
+
const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
|
|
11290
|
+
axis,
|
|
11291
|
+
axisName,
|
|
11292
|
+
gap: nearestBoundaryGap(a, b, axis)
|
|
11293
|
+
})).filter((entry) => entry.gap <= tolerance);
|
|
11294
|
+
if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
|
|
11295
|
+
const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
|
|
11296
|
+
return { touching: true, gap: nearest.gap, axis: nearest.axisName };
|
|
11297
|
+
}
|
|
11298
|
+
function isFiniteTriangle(positions, offset) {
|
|
11299
|
+
for (let index = 0; index < 9; index += 1) {
|
|
11300
|
+
if (!Number.isFinite(positions[offset + index])) return false;
|
|
11301
|
+
}
|
|
11302
|
+
return true;
|
|
11303
|
+
}
|
|
11304
|
+
function triangleRef(positions, offset) {
|
|
11305
|
+
if (!isFiniteTriangle(positions, offset)) return null;
|
|
11306
|
+
const ax = positions[offset];
|
|
11307
|
+
const ay = positions[offset + 1];
|
|
11308
|
+
const az = positions[offset + 2];
|
|
11309
|
+
const bx = positions[offset + 3];
|
|
11310
|
+
const by = positions[offset + 4];
|
|
11311
|
+
const bz = positions[offset + 5];
|
|
11312
|
+
const cx = positions[offset + 6];
|
|
11313
|
+
const cy = positions[offset + 7];
|
|
11314
|
+
const cz = positions[offset + 8];
|
|
11315
|
+
return {
|
|
11316
|
+
offset,
|
|
11317
|
+
min: [Math.min(ax, bx, cx), Math.min(ay, by, cy), Math.min(az, bz, cz)],
|
|
11318
|
+
max: [Math.max(ax, bx, cx), Math.max(ay, by, cy), Math.max(az, bz, cz)]
|
|
11319
|
+
};
|
|
11320
|
+
}
|
|
11321
|
+
function meshContactDataFor(entry) {
|
|
11322
|
+
const positions = entry.positions;
|
|
11323
|
+
if (!positions || positions.length < 9) return null;
|
|
11324
|
+
const triangleCount = Math.floor(positions.length / 9);
|
|
11325
|
+
const triangles = [];
|
|
11326
|
+
for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
11327
|
+
const triangle = triangleRef(positions, triangleIndex * 9);
|
|
11328
|
+
if (triangle) triangles.push(triangle);
|
|
11329
|
+
}
|
|
11330
|
+
if (triangles.length === 0) return null;
|
|
11331
|
+
return {
|
|
11332
|
+
positions,
|
|
11333
|
+
vertexCount: triangleCount * 3,
|
|
11334
|
+
triangles
|
|
11335
|
+
};
|
|
11336
|
+
}
|
|
11337
|
+
function distSq(px, py, pz, qx, qy, qz) {
|
|
11338
|
+
const dx = px - qx;
|
|
11339
|
+
const dy = py - qy;
|
|
11340
|
+
const dz = pz - qz;
|
|
11341
|
+
return dx * dx + dy * dy + dz * dz;
|
|
11342
|
+
}
|
|
11343
|
+
function pointSegmentDistanceSq(point, start, end) {
|
|
11344
|
+
const dx = end[0] - start[0];
|
|
11345
|
+
const dy = end[1] - start[1];
|
|
11346
|
+
const dz = end[2] - start[2];
|
|
11347
|
+
const lengthSq = dx * dx + dy * dy + dz * dz;
|
|
11348
|
+
if (lengthSq <= 1e-20) return distSq(point[0], point[1], point[2], start[0], start[1], start[2]);
|
|
11349
|
+
const t = Math.max(0, Math.min(1, ((point[0] - start[0]) * dx + (point[1] - start[1]) * dy + (point[2] - start[2]) * dz) / lengthSq));
|
|
11350
|
+
return distSq(point[0], point[1], point[2], start[0] + t * dx, start[1] + t * dy, start[2] + t * dz);
|
|
11351
|
+
}
|
|
11352
|
+
function pointTriangleDistanceSq(px, py, pz, ax, ay, az, bx, by, bz, cx, cy, cz) {
|
|
11353
|
+
const abx = bx - ax;
|
|
11354
|
+
const aby = by - ay;
|
|
11355
|
+
const abz = bz - az;
|
|
11356
|
+
const acx = cx - ax;
|
|
11357
|
+
const acy = cy - ay;
|
|
11358
|
+
const acz = cz - az;
|
|
11359
|
+
const apx = px - ax;
|
|
11360
|
+
const apy = py - ay;
|
|
11361
|
+
const apz = pz - az;
|
|
11362
|
+
const d1 = abx * apx + aby * apy + abz * apz;
|
|
11363
|
+
const d2 = acx * apx + acy * apy + acz * apz;
|
|
11364
|
+
if (d1 <= 0 && d2 <= 0) return distSq(px, py, pz, ax, ay, az);
|
|
11365
|
+
const bpx = px - bx;
|
|
11366
|
+
const bpy = py - by;
|
|
11367
|
+
const bpz = pz - bz;
|
|
11368
|
+
const d3 = abx * bpx + aby * bpy + abz * bpz;
|
|
11369
|
+
const d4 = acx * bpx + acy * bpy + acz * bpz;
|
|
11370
|
+
if (d3 >= 0 && d4 <= d3) return distSq(px, py, pz, bx, by, bz);
|
|
11371
|
+
const vc = d1 * d4 - d3 * d2;
|
|
11372
|
+
if (vc <= 0 && d1 >= 0 && d3 <= 0) {
|
|
11373
|
+
const v2 = d1 / (d1 - d3);
|
|
11374
|
+
return distSq(px, py, pz, ax + v2 * abx, ay + v2 * aby, az + v2 * abz);
|
|
11375
|
+
}
|
|
11376
|
+
const cpx = px - cx;
|
|
11377
|
+
const cpy = py - cy;
|
|
11378
|
+
const cpz = pz - cz;
|
|
11379
|
+
const d5 = abx * cpx + aby * cpy + abz * cpz;
|
|
11380
|
+
const d6 = acx * cpx + acy * cpy + acz * cpz;
|
|
11381
|
+
if (d6 >= 0 && d5 <= d6) return distSq(px, py, pz, cx, cy, cz);
|
|
11382
|
+
const vb = d5 * d2 - d1 * d6;
|
|
11383
|
+
if (vb <= 0 && d2 >= 0 && d6 <= 0) {
|
|
11384
|
+
const w2 = d2 / (d2 - d6);
|
|
11385
|
+
return distSq(px, py, pz, ax + w2 * acx, ay + w2 * acy, az + w2 * acz);
|
|
11386
|
+
}
|
|
11387
|
+
const va = d3 * d6 - d5 * d4;
|
|
11388
|
+
if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) {
|
|
11389
|
+
const bcx = cx - bx;
|
|
11390
|
+
const bcy = cy - by;
|
|
11391
|
+
const bcz = cz - bz;
|
|
11392
|
+
const w2 = (d4 - d3) / (d4 - d3 + d5 - d6);
|
|
11393
|
+
return distSq(px, py, pz, bx + w2 * bcx, by + w2 * bcy, bz + w2 * bcz);
|
|
11394
|
+
}
|
|
11395
|
+
const barycentricTotal = va + vb + vc;
|
|
11396
|
+
if (!Number.isFinite(barycentricTotal) || Math.abs(barycentricTotal) < 1e-20) {
|
|
11397
|
+
return Math.min(distSq(px, py, pz, ax, ay, az), distSq(px, py, pz, bx, by, bz), distSq(px, py, pz, cx, cy, cz));
|
|
11398
|
+
}
|
|
11399
|
+
const denom = 1 / barycentricTotal;
|
|
11400
|
+
const v = vb * denom;
|
|
11401
|
+
const w = vc * denom;
|
|
11402
|
+
return distSq(px, py, pz, ax + abx * v + acx * w, ay + aby * v + acy * w, az + abz * v + acz * w);
|
|
11403
|
+
}
|
|
11404
|
+
function segmentSegmentDistanceSq(a0, a1, b0, b1) {
|
|
11405
|
+
const ux = a1[0] - a0[0];
|
|
11406
|
+
const uy = a1[1] - a0[1];
|
|
11407
|
+
const uz = a1[2] - a0[2];
|
|
11408
|
+
const vx = b1[0] - b0[0];
|
|
11409
|
+
const vy = b1[1] - b0[1];
|
|
11410
|
+
const vz = b1[2] - b0[2];
|
|
11411
|
+
const wx = a0[0] - b0[0];
|
|
11412
|
+
const wy = a0[1] - b0[1];
|
|
11413
|
+
const wz = a0[2] - b0[2];
|
|
11414
|
+
const a = ux * ux + uy * uy + uz * uz;
|
|
11415
|
+
const b = ux * vx + uy * vy + uz * vz;
|
|
11416
|
+
const c = vx * vx + vy * vy + vz * vz;
|
|
11417
|
+
const d = ux * wx + uy * wy + uz * wz;
|
|
11418
|
+
const e = vx * wx + vy * wy + vz * wz;
|
|
11419
|
+
const denom = a * c - b * b;
|
|
11420
|
+
let sNumerator = denom;
|
|
11421
|
+
let sDenominator = denom;
|
|
11422
|
+
let tNumerator = denom;
|
|
11423
|
+
let tDenominator = denom;
|
|
11424
|
+
if (a <= 1e-20) return pointSegmentDistanceSq(a0, b0, b1);
|
|
11425
|
+
if (c <= 1e-20) return pointSegmentDistanceSq(b0, a0, a1);
|
|
11426
|
+
if (denom < 1e-20) {
|
|
11427
|
+
sNumerator = 0;
|
|
11428
|
+
sDenominator = 1;
|
|
11429
|
+
tNumerator = e;
|
|
11430
|
+
tDenominator = c;
|
|
11431
|
+
} else {
|
|
11432
|
+
sNumerator = b * e - c * d;
|
|
11433
|
+
tNumerator = a * e - b * d;
|
|
11434
|
+
if (sNumerator < 0) {
|
|
11435
|
+
sNumerator = 0;
|
|
11436
|
+
tNumerator = e;
|
|
11437
|
+
tDenominator = c;
|
|
11438
|
+
} else if (sNumerator > sDenominator) {
|
|
11439
|
+
sNumerator = sDenominator;
|
|
11440
|
+
tNumerator = e + b;
|
|
11441
|
+
tDenominator = c;
|
|
11442
|
+
}
|
|
11443
|
+
}
|
|
11444
|
+
if (tNumerator < 0) {
|
|
11445
|
+
tNumerator = 0;
|
|
11446
|
+
if (-d < 0) {
|
|
11447
|
+
sNumerator = 0;
|
|
11448
|
+
} else if (-d > a) {
|
|
11449
|
+
sNumerator = sDenominator;
|
|
11450
|
+
} else {
|
|
11451
|
+
sNumerator = -d;
|
|
11452
|
+
sDenominator = a;
|
|
11453
|
+
}
|
|
11454
|
+
} else if (tNumerator > tDenominator) {
|
|
11455
|
+
tNumerator = tDenominator;
|
|
11456
|
+
if (-d + b < 0) {
|
|
11457
|
+
sNumerator = 0;
|
|
11458
|
+
} else if (-d + b > a) {
|
|
11459
|
+
sNumerator = sDenominator;
|
|
11460
|
+
} else {
|
|
11461
|
+
sNumerator = -d + b;
|
|
11462
|
+
sDenominator = a;
|
|
11463
|
+
}
|
|
11464
|
+
}
|
|
11465
|
+
const s = Math.abs(sNumerator) < 1e-20 ? 0 : sNumerator / sDenominator;
|
|
11466
|
+
const t = Math.abs(tNumerator) < 1e-20 ? 0 : tNumerator / tDenominator;
|
|
11467
|
+
const dx = wx + s * ux - t * vx;
|
|
11468
|
+
const dy = wy + s * uy - t * vy;
|
|
11469
|
+
const dz = wz + s * uz - t * vz;
|
|
11470
|
+
return dx * dx + dy * dy + dz * dz;
|
|
11471
|
+
}
|
|
11472
|
+
function trianglePoint(positions, triangle, corner) {
|
|
11473
|
+
const offset = triangle.offset + corner * 3;
|
|
11474
|
+
return [positions[offset], positions[offset + 1], positions[offset + 2]];
|
|
11475
|
+
}
|
|
11476
|
+
function bboxDistanceSq(a, b) {
|
|
11477
|
+
let total = 0;
|
|
11478
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
11479
|
+
const gap = Math.max(0, a.min[axis] - b.max[axis], b.min[axis] - a.max[axis]);
|
|
11480
|
+
total += gap * gap;
|
|
11481
|
+
}
|
|
11482
|
+
return total;
|
|
11483
|
+
}
|
|
11484
|
+
function triangleDistanceSq(aPositions, a, bPositions, b) {
|
|
11485
|
+
const a0 = trianglePoint(aPositions, a, 0);
|
|
11486
|
+
const a1 = trianglePoint(aPositions, a, 1);
|
|
11487
|
+
const a2 = trianglePoint(aPositions, a, 2);
|
|
11488
|
+
const b0 = trianglePoint(bPositions, b, 0);
|
|
11489
|
+
const b1 = trianglePoint(bPositions, b, 1);
|
|
11490
|
+
const b2 = trianglePoint(bPositions, b, 2);
|
|
11491
|
+
return Math.min(
|
|
11492
|
+
pointTriangleDistanceSq(a0[0], a0[1], a0[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
|
|
11493
|
+
pointTriangleDistanceSq(a1[0], a1[1], a1[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
|
|
11494
|
+
pointTriangleDistanceSq(a2[0], a2[1], a2[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
|
|
11495
|
+
pointTriangleDistanceSq(b0[0], b0[1], b0[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
11496
|
+
pointTriangleDistanceSq(b1[0], b1[1], b1[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
11497
|
+
pointTriangleDistanceSq(b2[0], b2[1], b2[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
11498
|
+
segmentSegmentDistanceSq(a0, a1, b0, b1),
|
|
11499
|
+
segmentSegmentDistanceSq(a0, a1, b1, b2),
|
|
11500
|
+
segmentSegmentDistanceSq(a0, a1, b2, b0),
|
|
11501
|
+
segmentSegmentDistanceSq(a1, a2, b0, b1),
|
|
11502
|
+
segmentSegmentDistanceSq(a1, a2, b1, b2),
|
|
11503
|
+
segmentSegmentDistanceSq(a1, a2, b2, b0),
|
|
11504
|
+
segmentSegmentDistanceSq(a2, a0, b0, b1),
|
|
11505
|
+
segmentSegmentDistanceSq(a2, a0, b1, b2),
|
|
11506
|
+
segmentSegmentDistanceSq(a2, a0, b2, b0)
|
|
11507
|
+
);
|
|
11508
|
+
}
|
|
11509
|
+
function meshEntriesContactDistance(a, b, tolerance) {
|
|
11510
|
+
const toleranceSq = tolerance * tolerance;
|
|
11511
|
+
let bestDistanceSq = Infinity;
|
|
11512
|
+
for (const aTriangle of a.triangles) {
|
|
11513
|
+
for (const bTriangle of b.triangles) {
|
|
11514
|
+
if (bboxDistanceSq(aTriangle, bTriangle) > toleranceSq) continue;
|
|
11515
|
+
const distanceSq = triangleDistanceSq(a.positions, aTriangle, b.positions, bTriangle);
|
|
11516
|
+
if (distanceSq > toleranceSq) continue;
|
|
11517
|
+
if (distanceSq <= 1e-20) return 0;
|
|
11518
|
+
bestDistanceSq = Math.min(bestDistanceSq, distanceSq);
|
|
11519
|
+
}
|
|
11520
|
+
}
|
|
11521
|
+
return Number.isFinite(bestDistanceSq) ? Math.sqrt(bestDistanceSq) : null;
|
|
11522
|
+
}
|
|
11523
|
+
function intersectionVolume(a, b) {
|
|
11524
|
+
try {
|
|
11525
|
+
const hit = a.shape.intersect(b.shape);
|
|
11526
|
+
if (hit.isEmpty()) return { volume: 0 };
|
|
11527
|
+
const volume = hit.volume();
|
|
11528
|
+
return { volume: Number.isFinite(volume) ? volume : 0 };
|
|
11529
|
+
} catch (err) {
|
|
11530
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11531
|
+
return { volume: null, warning: `Could not boolean-test ${a.name} against ${b.name}: ${message}` };
|
|
11532
|
+
}
|
|
11533
|
+
}
|
|
11534
|
+
function detectPhysicalContact(a, b, options, meshCache = {}) {
|
|
11535
|
+
const gaps = aabbGaps(a, b);
|
|
11536
|
+
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
11537
|
+
if (largestGap > options.contactTolerance) return { contact: null };
|
|
11538
|
+
const sourceMesh = meshCache.sourceMesh !== void 0 ? meshCache.sourceMesh : meshContactDataFor(a);
|
|
11539
|
+
const targetMesh = meshCache.targetMesh !== void 0 ? meshCache.targetMesh : meshContactDataFor(b);
|
|
11540
|
+
if (sourceMesh && targetMesh) {
|
|
11541
|
+
const distance = meshEntriesContactDistance(sourceMesh, targetMesh, options.contactTolerance);
|
|
11542
|
+
if (distance != null) {
|
|
11543
|
+
return {
|
|
11544
|
+
contact: {
|
|
11545
|
+
kind: "touching",
|
|
11546
|
+
method: "mesh-surface-distance",
|
|
11547
|
+
gap: distance
|
|
11548
|
+
}
|
|
11549
|
+
};
|
|
11550
|
+
}
|
|
11551
|
+
}
|
|
11552
|
+
const bboxOverlaps = !hasPositiveGap(gaps) && aabbInteriorOverlaps(a, b);
|
|
11553
|
+
if (options.exactGeometry && bboxOverlaps) {
|
|
11554
|
+
if (aabbOverlapVolume(a, b) <= options.minOverlapVolume) return { contact: null };
|
|
11555
|
+
const overlap = intersectionVolume(a, b);
|
|
11556
|
+
if (overlap.volume != null && overlap.volume > options.minOverlapVolume) {
|
|
11557
|
+
return {
|
|
11558
|
+
contact: {
|
|
11559
|
+
kind: "overlap",
|
|
11560
|
+
method: "boolean-intersection",
|
|
11561
|
+
gap: 0,
|
|
11562
|
+
overlapVolume: overlap.volume
|
|
11563
|
+
},
|
|
11564
|
+
warning: overlap.warning
|
|
11565
|
+
};
|
|
11566
|
+
}
|
|
11567
|
+
return { contact: null, warning: overlap.warning };
|
|
11568
|
+
}
|
|
11569
|
+
if (bboxOverlaps && options.mergeOverlappingBBoxes) {
|
|
11570
|
+
return {
|
|
11571
|
+
contact: {
|
|
11572
|
+
kind: "overlap",
|
|
11573
|
+
method: "bbox-overlap",
|
|
11574
|
+
gap: 0,
|
|
11575
|
+
overlapVolume: aabbOverlapVolume(a, b)
|
|
11576
|
+
}
|
|
11577
|
+
};
|
|
11578
|
+
}
|
|
11579
|
+
if (options.mergeTouchingBBoxes) {
|
|
11580
|
+
const contact = contactFromBBoxes(a, b, options.contactTolerance);
|
|
11581
|
+
if (contact.touching) {
|
|
11582
|
+
return {
|
|
11583
|
+
contact: {
|
|
11584
|
+
kind: "touching",
|
|
11585
|
+
method: "bbox-contact",
|
|
11586
|
+
gap: contact.gap,
|
|
11587
|
+
axis: contact.axis
|
|
11588
|
+
}
|
|
11589
|
+
};
|
|
11590
|
+
}
|
|
11591
|
+
}
|
|
11592
|
+
return { contact: null };
|
|
11593
|
+
}
|
|
11273
11594
|
const DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS = {
|
|
11274
11595
|
contactTolerance: 0.05,
|
|
11275
11596
|
minOverlapVolume: 0.1,
|
|
11276
11597
|
exactGeometry: true
|
|
11277
11598
|
};
|
|
11278
|
-
|
|
11279
|
-
let UnionFind$1 = class UnionFind {
|
|
11599
|
+
let UnionFind$2 = class UnionFind {
|
|
11280
11600
|
constructor(size) {
|
|
11281
11601
|
__publicField(this, "parent");
|
|
11282
11602
|
__publicField(this, "rank");
|
|
@@ -11306,27 +11626,21 @@ let UnionFind$1 = class UnionFind {
|
|
|
11306
11626
|
this.rank[rootA] += 1;
|
|
11307
11627
|
}
|
|
11308
11628
|
};
|
|
11309
|
-
function cloneVec3(value) {
|
|
11629
|
+
function cloneVec3$1(value) {
|
|
11310
11630
|
return [value[0], value[1], value[2]];
|
|
11311
11631
|
}
|
|
11312
|
-
function emptyBBox$
|
|
11632
|
+
function emptyBBox$2() {
|
|
11313
11633
|
return {
|
|
11314
11634
|
min: [Infinity, Infinity, Infinity],
|
|
11315
11635
|
max: [-Infinity, -Infinity, -Infinity]
|
|
11316
11636
|
};
|
|
11317
11637
|
}
|
|
11318
|
-
function expandBBox$
|
|
11638
|
+
function expandBBox$2(target, min, max) {
|
|
11319
11639
|
for (let axis = 0; axis < 3; axis += 1) {
|
|
11320
11640
|
target.min[axis] = Math.min(target.min[axis], min[axis]);
|
|
11321
11641
|
target.max[axis] = Math.max(target.max[axis], max[axis]);
|
|
11322
11642
|
}
|
|
11323
11643
|
}
|
|
11324
|
-
function nearestBoundaryGap(a, b, axis) {
|
|
11325
|
-
return Math.min(Math.abs(a.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a.min[axis]));
|
|
11326
|
-
}
|
|
11327
|
-
function hasPositiveGap(gaps) {
|
|
11328
|
-
return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
|
|
11329
|
-
}
|
|
11330
11644
|
function collectCandidatePairs(entries, tolerance) {
|
|
11331
11645
|
if (entries.length < 2) return [];
|
|
11332
11646
|
const index = new AabbSpatialIndex(entries);
|
|
@@ -11338,36 +11652,7 @@ function collectCandidatePairs(entries, tolerance) {
|
|
|
11338
11652
|
pairs.sort((a, b) => a.sourceIndex - b.sourceIndex || a.targetIndex - b.targetIndex);
|
|
11339
11653
|
return pairs;
|
|
11340
11654
|
}
|
|
11341
|
-
function
|
|
11342
|
-
const gaps = aabbGaps(a, b);
|
|
11343
|
-
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
11344
|
-
if (largestGap > tolerance) return { touching: false, gap: largestGap };
|
|
11345
|
-
const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
|
|
11346
|
-
if (separatedAxes.length > 0) {
|
|
11347
|
-
const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
|
|
11348
|
-
return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
|
|
11349
|
-
}
|
|
11350
|
-
const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
|
|
11351
|
-
axis,
|
|
11352
|
-
axisName,
|
|
11353
|
-
gap: nearestBoundaryGap(a, b, axis)
|
|
11354
|
-
})).filter((entry) => entry.gap <= tolerance);
|
|
11355
|
-
if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
|
|
11356
|
-
const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
|
|
11357
|
-
return { touching: true, gap: nearest.gap, axis: nearest.axisName };
|
|
11358
|
-
}
|
|
11359
|
-
function intersectionVolume(a, b) {
|
|
11360
|
-
try {
|
|
11361
|
-
const hit = a.shape.intersect(b.shape);
|
|
11362
|
-
if (hit.isEmpty()) return { volume: 0 };
|
|
11363
|
-
const volume = hit.volume();
|
|
11364
|
-
return { volume: Number.isFinite(volume) ? volume : 0 };
|
|
11365
|
-
} catch (err) {
|
|
11366
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
11367
|
-
return { volume: null, warning: `Could not boolean-test ${a.name} against ${b.name}: ${message}` };
|
|
11368
|
-
}
|
|
11369
|
-
}
|
|
11370
|
-
function bodyCountForEntry(entry) {
|
|
11655
|
+
function bodyCountForEntry$1(entry) {
|
|
11371
11656
|
if (typeof entry.bodyCount === "number" && Number.isFinite(entry.bodyCount)) {
|
|
11372
11657
|
return Math.max(0, Math.round(entry.bodyCount));
|
|
11373
11658
|
}
|
|
@@ -11397,53 +11682,23 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
11397
11682
|
};
|
|
11398
11683
|
const warnings = [];
|
|
11399
11684
|
const edges = [];
|
|
11400
|
-
const unionFind = new UnionFind$
|
|
11685
|
+
const unionFind = new UnionFind$2(entries.length);
|
|
11686
|
+
const meshDataByIndex = /* @__PURE__ */ new Map();
|
|
11687
|
+
entries.forEach((entry, index) => {
|
|
11688
|
+
const meshData = meshContactDataFor(entry);
|
|
11689
|
+
if (meshData) meshDataByIndex.set(index, meshData);
|
|
11690
|
+
});
|
|
11401
11691
|
for (const pair of collectCandidatePairs(entries, options.contactTolerance)) {
|
|
11402
11692
|
const i = pair.sourceIndex;
|
|
11403
11693
|
const j = pair.targetIndex;
|
|
11404
|
-
const
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
edges.push(
|
|
11413
|
-
makeEdge(entries, i, j, {
|
|
11414
|
-
kind: "overlap",
|
|
11415
|
-
method: "boolean-intersection",
|
|
11416
|
-
gap: 0,
|
|
11417
|
-
overlapVolume: overlap.volume
|
|
11418
|
-
})
|
|
11419
|
-
);
|
|
11420
|
-
continue;
|
|
11421
|
-
}
|
|
11422
|
-
continue;
|
|
11423
|
-
}
|
|
11424
|
-
if (bboxOverlaps && options.mergeOverlappingBBoxes) {
|
|
11425
|
-
unionFind.union(i, j);
|
|
11426
|
-
edges.push(
|
|
11427
|
-
makeEdge(entries, i, j, {
|
|
11428
|
-
kind: "overlap",
|
|
11429
|
-
method: "bbox-overlap",
|
|
11430
|
-
gap: 0,
|
|
11431
|
-
overlapVolume: aabbOverlapVolume(a, b)
|
|
11432
|
-
})
|
|
11433
|
-
);
|
|
11434
|
-
} else {
|
|
11435
|
-
const contact = contactFromBBoxes(a, b, options.contactTolerance);
|
|
11436
|
-
if (!contact.touching || !options.mergeTouchingBBoxes) continue;
|
|
11437
|
-
unionFind.union(i, j);
|
|
11438
|
-
edges.push(
|
|
11439
|
-
makeEdge(entries, i, j, {
|
|
11440
|
-
kind: "touching",
|
|
11441
|
-
method: "bbox-contact",
|
|
11442
|
-
gap: contact.gap,
|
|
11443
|
-
axis: contact.axis
|
|
11444
|
-
})
|
|
11445
|
-
);
|
|
11446
|
-
}
|
|
11694
|
+
const detection = detectPhysicalContact(entries[i], entries[j], options, {
|
|
11695
|
+
sourceMesh: meshDataByIndex.get(i) ?? null,
|
|
11696
|
+
targetMesh: meshDataByIndex.get(j) ?? null
|
|
11697
|
+
});
|
|
11698
|
+
if (detection.warning) warnings.push(detection.warning);
|
|
11699
|
+
if (!detection.contact) continue;
|
|
11700
|
+
unionFind.union(i, j);
|
|
11701
|
+
edges.push(makeEdge(entries, i, j, detection.contact));
|
|
11447
11702
|
}
|
|
11448
11703
|
const objects = entries.map((entry, index) => ({
|
|
11449
11704
|
index,
|
|
@@ -11452,10 +11707,10 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
11452
11707
|
groupName: entry.groupName,
|
|
11453
11708
|
treePath: entry.treePath,
|
|
11454
11709
|
mock: entry.mock === true,
|
|
11455
|
-
bodyCount: bodyCountForEntry(entry),
|
|
11710
|
+
bodyCount: bodyCountForEntry$1(entry),
|
|
11456
11711
|
bbox: {
|
|
11457
|
-
min: cloneVec3(entry.min),
|
|
11458
|
-
max: cloneVec3(entry.max)
|
|
11712
|
+
min: cloneVec3$1(entry.min),
|
|
11713
|
+
max: cloneVec3$1(entry.max)
|
|
11459
11714
|
},
|
|
11460
11715
|
componentIndex: 0
|
|
11461
11716
|
}));
|
|
@@ -11472,7 +11727,7 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
11472
11727
|
objectNames: [],
|
|
11473
11728
|
objectCount: 0,
|
|
11474
11729
|
bodyCount: 0,
|
|
11475
|
-
bbox: emptyBBox$
|
|
11730
|
+
bbox: emptyBBox$2()
|
|
11476
11731
|
};
|
|
11477
11732
|
componentByRoot.set(root, component);
|
|
11478
11733
|
rootToComponentIndex.set(root, component.index);
|
|
@@ -11484,11 +11739,11 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
11484
11739
|
component.objectNames.push(object.name);
|
|
11485
11740
|
component.objectCount += 1;
|
|
11486
11741
|
component.bodyCount += object.bodyCount;
|
|
11487
|
-
expandBBox$
|
|
11742
|
+
expandBBox$2(component.bbox, object.bbox.min, object.bbox.max);
|
|
11488
11743
|
}
|
|
11489
11744
|
const components = [...componentByRoot.values()];
|
|
11490
11745
|
return {
|
|
11491
|
-
method: options.exactGeometry ? "
|
|
11746
|
+
method: options.exactGeometry ? "mesh-contact-plus-boolean-overlap" : "bbox-neighborhood",
|
|
11492
11747
|
options,
|
|
11493
11748
|
objectCount: objects.length,
|
|
11494
11749
|
componentCount: components.length,
|
|
@@ -11689,6 +11944,185 @@ function analyzeDistanceInspection(entries, rawOptions = {}) {
|
|
|
11689
11944
|
warnings: [...connectivity.warnings]
|
|
11690
11945
|
};
|
|
11691
11946
|
}
|
|
11947
|
+
const DEFAULT_CONTACT_TOLERANCE = 0.05;
|
|
11948
|
+
const DEFAULT_BED_TOLERANCE = 0.05;
|
|
11949
|
+
const DEFAULT_GROUND_Z = 0;
|
|
11950
|
+
const MIN_SHAPE_OVERLAP_VOLUME = 1e-9;
|
|
11951
|
+
let UnionFind$1 = class UnionFind2 {
|
|
11952
|
+
constructor(size) {
|
|
11953
|
+
__publicField(this, "parent");
|
|
11954
|
+
__publicField(this, "rank");
|
|
11955
|
+
this.parent = Array.from({ length: size }, (_, index) => index);
|
|
11956
|
+
this.rank = Array.from({ length: size }, () => 0);
|
|
11957
|
+
}
|
|
11958
|
+
find(value) {
|
|
11959
|
+
const parent = this.parent[value];
|
|
11960
|
+
if (parent === value) return value;
|
|
11961
|
+
const root = this.find(parent);
|
|
11962
|
+
this.parent[value] = root;
|
|
11963
|
+
return root;
|
|
11964
|
+
}
|
|
11965
|
+
union(a, b) {
|
|
11966
|
+
const rootA = this.find(a);
|
|
11967
|
+
const rootB = this.find(b);
|
|
11968
|
+
if (rootA === rootB) return;
|
|
11969
|
+
if (this.rank[rootA] < this.rank[rootB]) {
|
|
11970
|
+
this.parent[rootA] = rootB;
|
|
11971
|
+
return;
|
|
11972
|
+
}
|
|
11973
|
+
if (this.rank[rootA] > this.rank[rootB]) {
|
|
11974
|
+
this.parent[rootB] = rootA;
|
|
11975
|
+
return;
|
|
11976
|
+
}
|
|
11977
|
+
this.parent[rootB] = rootA;
|
|
11978
|
+
this.rank[rootA] += 1;
|
|
11979
|
+
}
|
|
11980
|
+
};
|
|
11981
|
+
function cloneVec3(value) {
|
|
11982
|
+
return [value[0], value[1], value[2]];
|
|
11983
|
+
}
|
|
11984
|
+
function emptyBBox$1() {
|
|
11985
|
+
return {
|
|
11986
|
+
min: [Infinity, Infinity, Infinity],
|
|
11987
|
+
max: [-Infinity, -Infinity, -Infinity]
|
|
11988
|
+
};
|
|
11989
|
+
}
|
|
11990
|
+
function expandBBox$1(target, min, max) {
|
|
11991
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
11992
|
+
target.min[axis] = Math.min(target.min[axis], min[axis]);
|
|
11993
|
+
target.max[axis] = Math.max(target.max[axis], max[axis]);
|
|
11994
|
+
}
|
|
11995
|
+
}
|
|
11996
|
+
function bodyCountForEntry(entry) {
|
|
11997
|
+
if (typeof entry.bodyCount === "number" && Number.isFinite(entry.bodyCount)) {
|
|
11998
|
+
return Math.max(0, Math.round(entry.bodyCount));
|
|
11999
|
+
}
|
|
12000
|
+
return 1;
|
|
12001
|
+
}
|
|
12002
|
+
function resolveNonNegative(value, fallback) {
|
|
12003
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
12004
|
+
}
|
|
12005
|
+
function resolveFinite(value, fallback) {
|
|
12006
|
+
return Number.isFinite(value) ? value : fallback;
|
|
12007
|
+
}
|
|
12008
|
+
function analyzeFloatingInspection(entries, rawOptions = {}) {
|
|
12009
|
+
const options = {
|
|
12010
|
+
contactTolerance: resolveNonNegative(rawOptions.contactTolerance, DEFAULT_CONTACT_TOLERANCE),
|
|
12011
|
+
bedTolerance: resolveNonNegative(rawOptions.bedTolerance, DEFAULT_BED_TOLERANCE),
|
|
12012
|
+
groundZ: resolveFinite(rawOptions.groundZ, DEFAULT_GROUND_Z)
|
|
12013
|
+
};
|
|
12014
|
+
const unionFind = new UnionFind$1(entries.length);
|
|
12015
|
+
const contacts = [];
|
|
12016
|
+
const warnings = [];
|
|
12017
|
+
const meshDataByIndex = /* @__PURE__ */ new Map();
|
|
12018
|
+
entries.forEach((entry, index2) => {
|
|
12019
|
+
const meshData = meshContactDataFor(entry);
|
|
12020
|
+
if (meshData) meshDataByIndex.set(index2, meshData);
|
|
12021
|
+
});
|
|
12022
|
+
const candidateTolerance = options.contactTolerance > 0 ? options.contactTolerance : Number.EPSILON;
|
|
12023
|
+
const index = new AabbSpatialIndex(entries);
|
|
12024
|
+
const contactOptions = {
|
|
12025
|
+
contactTolerance: options.contactTolerance,
|
|
12026
|
+
minOverlapVolume: MIN_SHAPE_OVERLAP_VOLUME,
|
|
12027
|
+
exactGeometry: true,
|
|
12028
|
+
mergeOverlappingBBoxes: false,
|
|
12029
|
+
mergeTouchingBBoxes: false
|
|
12030
|
+
};
|
|
12031
|
+
for (const pair of index.overlapPairs({ padding: candidateTolerance }).pairs) {
|
|
12032
|
+
const source = entries[pair.sourceIndex];
|
|
12033
|
+
const target = entries[pair.targetIndex];
|
|
12034
|
+
const detection = detectPhysicalContact(source, target, contactOptions, {
|
|
12035
|
+
sourceMesh: meshDataByIndex.get(pair.sourceIndex) ?? null,
|
|
12036
|
+
targetMesh: meshDataByIndex.get(pair.targetIndex) ?? null
|
|
12037
|
+
});
|
|
12038
|
+
if (detection.warning) warnings.push(detection.warning);
|
|
12039
|
+
if (!detection.contact) continue;
|
|
12040
|
+
unionFind.union(pair.sourceIndex, pair.targetIndex);
|
|
12041
|
+
contacts.push({
|
|
12042
|
+
sourceIndex: pair.sourceIndex,
|
|
12043
|
+
targetIndex: pair.targetIndex,
|
|
12044
|
+
sourceId: source.id,
|
|
12045
|
+
targetId: target.id,
|
|
12046
|
+
sourceName: source.name,
|
|
12047
|
+
targetName: target.name,
|
|
12048
|
+
gap: detection.contact.gap
|
|
12049
|
+
});
|
|
12050
|
+
}
|
|
12051
|
+
const objects = entries.map((entry, entryIndex) => ({
|
|
12052
|
+
index: entryIndex,
|
|
12053
|
+
id: entry.id,
|
|
12054
|
+
name: entry.name,
|
|
12055
|
+
groupName: entry.groupName,
|
|
12056
|
+
treePath: entry.treePath,
|
|
12057
|
+
mock: entry.mock === true,
|
|
12058
|
+
bodyCount: bodyCountForEntry(entry),
|
|
12059
|
+
bbox: {
|
|
12060
|
+
min: cloneVec3(entry.min),
|
|
12061
|
+
max: cloneVec3(entry.max)
|
|
12062
|
+
},
|
|
12063
|
+
componentIndex: 0,
|
|
12064
|
+
isRootComponent: false,
|
|
12065
|
+
isBedSupported: entry.min[2] <= options.groundZ + options.bedTolerance,
|
|
12066
|
+
isFloating: false
|
|
12067
|
+
}));
|
|
12068
|
+
const componentByRoot = /* @__PURE__ */ new Map();
|
|
12069
|
+
const rootToComponentIndex = /* @__PURE__ */ new Map();
|
|
12070
|
+
for (let objectIndex = 0; objectIndex < objects.length; objectIndex += 1) {
|
|
12071
|
+
const root = unionFind.find(objectIndex);
|
|
12072
|
+
let component = componentByRoot.get(root);
|
|
12073
|
+
if (!component) {
|
|
12074
|
+
component = {
|
|
12075
|
+
index: componentByRoot.size + 1,
|
|
12076
|
+
objectIndexes: [],
|
|
12077
|
+
objectIds: [],
|
|
12078
|
+
objectNames: [],
|
|
12079
|
+
objectCount: 0,
|
|
12080
|
+
bodyCount: 0,
|
|
12081
|
+
bbox: emptyBBox$1(),
|
|
12082
|
+
isRoot: false,
|
|
12083
|
+
isBedSupported: false,
|
|
12084
|
+
isFloating: false
|
|
12085
|
+
};
|
|
12086
|
+
componentByRoot.set(root, component);
|
|
12087
|
+
rootToComponentIndex.set(root, component.index);
|
|
12088
|
+
}
|
|
12089
|
+
const object = objects[objectIndex];
|
|
12090
|
+
object.componentIndex = rootToComponentIndex.get(root) ?? component.index;
|
|
12091
|
+
component.objectIndexes.push(object.index);
|
|
12092
|
+
component.objectIds.push(object.id);
|
|
12093
|
+
component.objectNames.push(object.name);
|
|
12094
|
+
component.objectCount += 1;
|
|
12095
|
+
component.bodyCount += object.bodyCount;
|
|
12096
|
+
component.isBedSupported || (component.isBedSupported = object.isBedSupported);
|
|
12097
|
+
expandBBox$1(component.bbox, object.bbox.min, object.bbox.max);
|
|
12098
|
+
}
|
|
12099
|
+
const components = [...componentByRoot.values()];
|
|
12100
|
+
for (const component of components) {
|
|
12101
|
+
component.isRoot = false;
|
|
12102
|
+
component.isFloating = !component.isBedSupported;
|
|
12103
|
+
}
|
|
12104
|
+
const componentByIndex = new Map(components.map((component) => [component.index, component]));
|
|
12105
|
+
for (const object of objects) {
|
|
12106
|
+
const component = componentByIndex.get(object.componentIndex);
|
|
12107
|
+
object.isRootComponent = false;
|
|
12108
|
+
object.isBedSupported = (component == null ? void 0 : component.isBedSupported) ?? object.isBedSupported;
|
|
12109
|
+
object.isFloating = (component == null ? void 0 : component.isFloating) ?? false;
|
|
12110
|
+
}
|
|
12111
|
+
return {
|
|
12112
|
+
method: "mesh-contact-ground-reachability",
|
|
12113
|
+
options,
|
|
12114
|
+
rootComponentIndex: null,
|
|
12115
|
+
objectCount: objects.length,
|
|
12116
|
+
componentCount: components.length,
|
|
12117
|
+
floatingComponentCount: components.filter((component) => component.isFloating).length,
|
|
12118
|
+
floatingObjectCount: objects.filter((object) => object.isFloating).length,
|
|
12119
|
+
floatingBodyCount: components.filter((component) => component.isFloating).reduce((total, component) => total + component.bodyCount, 0),
|
|
12120
|
+
contacts,
|
|
12121
|
+
objects,
|
|
12122
|
+
components,
|
|
12123
|
+
warnings
|
|
12124
|
+
};
|
|
12125
|
+
}
|
|
11692
12126
|
const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
|
|
11693
12127
|
smoothAngleDeg: 5,
|
|
11694
12128
|
sharpAngleDeg: 30,
|
|
@@ -12086,7 +12520,8 @@ const DEFAULT_THICKNESS_INSPECTION_OPTIONS = {
|
|
|
12086
12520
|
minThickness: 1.2,
|
|
12087
12521
|
warnThickness: 2,
|
|
12088
12522
|
maxThickness: 6,
|
|
12089
|
-
maxSamplesPerObject: 5e3
|
|
12523
|
+
maxSamplesPerObject: 5e3,
|
|
12524
|
+
contactTolerance: DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance
|
|
12090
12525
|
};
|
|
12091
12526
|
const THICKNESS_COLORS = {
|
|
12092
12527
|
critical: [255, 28, 28],
|
|
@@ -12102,6 +12537,13 @@ function finitePositive(value, fallback, label) {
|
|
|
12102
12537
|
}
|
|
12103
12538
|
return value;
|
|
12104
12539
|
}
|
|
12540
|
+
function finiteNonNegative(value, fallback, label) {
|
|
12541
|
+
if (value === void 0) return fallback;
|
|
12542
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
12543
|
+
throw new Error(`${label} must be a non-negative finite number.`);
|
|
12544
|
+
}
|
|
12545
|
+
return value;
|
|
12546
|
+
}
|
|
12105
12547
|
function resolveThicknessInspectionOptions(raw = {}) {
|
|
12106
12548
|
const minThickness = finitePositive(raw.minThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.minThickness, "minThickness");
|
|
12107
12549
|
const warnThickness = finitePositive(raw.warnThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.warnThickness, "warnThickness");
|
|
@@ -12111,6 +12553,11 @@ function resolveThicknessInspectionOptions(raw = {}) {
|
|
|
12111
12553
|
DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxSamplesPerObject,
|
|
12112
12554
|
"maxSamplesPerObject"
|
|
12113
12555
|
);
|
|
12556
|
+
const contactTolerance = finiteNonNegative(
|
|
12557
|
+
raw.contactTolerance,
|
|
12558
|
+
DEFAULT_THICKNESS_INSPECTION_OPTIONS.contactTolerance,
|
|
12559
|
+
"contactTolerance"
|
|
12560
|
+
);
|
|
12114
12561
|
if (minThickness > warnThickness) {
|
|
12115
12562
|
throw new Error("minThickness must be less than or equal to warnThickness.");
|
|
12116
12563
|
}
|
|
@@ -12121,7 +12568,8 @@ function resolveThicknessInspectionOptions(raw = {}) {
|
|
|
12121
12568
|
minThickness,
|
|
12122
12569
|
warnThickness,
|
|
12123
12570
|
maxThickness,
|
|
12124
|
-
maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject))
|
|
12571
|
+
maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject)),
|
|
12572
|
+
contactTolerance
|
|
12125
12573
|
};
|
|
12126
12574
|
}
|
|
12127
12575
|
function lerp$1(a, b, t) {
|
|
@@ -12162,22 +12610,45 @@ function geometryMaxDimension(geometry) {
|
|
|
12162
12610
|
box.getSize(size);
|
|
12163
12611
|
return Math.max(1, size.x, size.y, size.z);
|
|
12164
12612
|
}
|
|
12165
|
-
function firstOppositeSurfaceDistance(raycaster,
|
|
12613
|
+
function firstOppositeSurfaceDistance(raycaster, rayTargetMeshes, jumpableMeshes, point, direction, epsilon, far, contactTolerance) {
|
|
12166
12614
|
const origin = point.clone().addScaledVector(direction, epsilon);
|
|
12167
12615
|
raycaster.set(origin, direction);
|
|
12168
12616
|
raycaster.near = epsilon;
|
|
12169
12617
|
raycaster.far = far;
|
|
12170
|
-
const
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12618
|
+
const hits = raycaster.intersectObjects(rayTargetMeshes, false);
|
|
12619
|
+
for (const hit of hits) {
|
|
12620
|
+
if (hit.distance <= epsilon) continue;
|
|
12621
|
+
if (hit.distance <= contactTolerance && jumpableMeshes.has(hit.object)) continue;
|
|
12622
|
+
return hit.distance + epsilon;
|
|
12623
|
+
}
|
|
12624
|
+
return null;
|
|
12625
|
+
}
|
|
12626
|
+
function triangleThickness(raycaster, rayTargetMeshes, jumpableMeshes, centroid, normal, epsilon, far, contactTolerance) {
|
|
12627
|
+
const forward = firstOppositeSurfaceDistance(
|
|
12628
|
+
raycaster,
|
|
12629
|
+
rayTargetMeshes,
|
|
12630
|
+
jumpableMeshes,
|
|
12631
|
+
centroid,
|
|
12632
|
+
normal,
|
|
12633
|
+
epsilon,
|
|
12634
|
+
far,
|
|
12635
|
+
contactTolerance
|
|
12636
|
+
);
|
|
12637
|
+
const backward = firstOppositeSurfaceDistance(
|
|
12638
|
+
raycaster,
|
|
12639
|
+
rayTargetMeshes,
|
|
12640
|
+
jumpableMeshes,
|
|
12641
|
+
centroid,
|
|
12642
|
+
normal.clone().negate(),
|
|
12643
|
+
epsilon,
|
|
12644
|
+
far,
|
|
12645
|
+
contactTolerance
|
|
12646
|
+
);
|
|
12176
12647
|
if (forward == null) return backward;
|
|
12177
12648
|
if (backward == null) return forward;
|
|
12178
12649
|
return Math.min(forward, backward);
|
|
12179
12650
|
}
|
|
12180
|
-
function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
12651
|
+
function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}, context = {}) {
|
|
12181
12652
|
const options = resolveThicknessInspectionOptions(rawOptions);
|
|
12182
12653
|
const geometry = cloneGeometryForFaceColors(sourceGeometry);
|
|
12183
12654
|
const position = geometry.getAttribute("position");
|
|
@@ -12195,7 +12666,8 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
12195
12666
|
const triangleCount = Math.floor(position.count / 3);
|
|
12196
12667
|
const surfaceTriangles = readSurfaceTriangles(position);
|
|
12197
12668
|
const surfaceSamples = sampleSurfaceTriangles(surfaceTriangles, options.maxSamplesPerObject);
|
|
12198
|
-
const
|
|
12669
|
+
const connectedGeometries = context.connectedGeometries ?? [];
|
|
12670
|
+
const maxDim = Math.max(geometryMaxDimension(geometry), ...connectedGeometries.map(geometryMaxDimension));
|
|
12199
12671
|
const epsilon = Math.max(1e-4, maxDim * 1e-6);
|
|
12200
12672
|
const far = Math.max(maxDim * 4, options.maxThickness * 4, 1);
|
|
12201
12673
|
const colors = new Float32Array(position.count * 3);
|
|
@@ -12204,7 +12676,15 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
12204
12676
|
const pointSamples = [];
|
|
12205
12677
|
const warnings = [];
|
|
12206
12678
|
const rayMaterial = new MeshBasicMaterial({ side: DoubleSide });
|
|
12207
|
-
const
|
|
12679
|
+
const rayTargets = [
|
|
12680
|
+
{ mesh: new Mesh(geometry, rayMaterial), jumpable: false },
|
|
12681
|
+
...connectedGeometries.map((connectedGeometry) => ({
|
|
12682
|
+
mesh: new Mesh(connectedGeometry, rayMaterial),
|
|
12683
|
+
jumpable: true
|
|
12684
|
+
}))
|
|
12685
|
+
];
|
|
12686
|
+
const rayTargetMeshes = rayTargets.map((target) => target.mesh);
|
|
12687
|
+
const jumpableMeshes = new Set(rayTargets.filter((target) => target.jumpable).map((target) => target.mesh));
|
|
12208
12688
|
const raycaster = new Raycaster();
|
|
12209
12689
|
if (surfaceTriangles.length === 0) {
|
|
12210
12690
|
warnings.push("No non-degenerate triangle surface was available for thickness sampling.");
|
|
@@ -12216,7 +12696,16 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
12216
12696
|
const sampledTriangleIndexes = /* @__PURE__ */ new Set();
|
|
12217
12697
|
for (const sample of surfaceSamples) {
|
|
12218
12698
|
sampledTriangleIndexes.add(sample.triangle.index);
|
|
12219
|
-
const thickness = triangleThickness(
|
|
12699
|
+
const thickness = triangleThickness(
|
|
12700
|
+
raycaster,
|
|
12701
|
+
rayTargetMeshes,
|
|
12702
|
+
jumpableMeshes,
|
|
12703
|
+
sample.position,
|
|
12704
|
+
sample.normal,
|
|
12705
|
+
epsilon,
|
|
12706
|
+
far,
|
|
12707
|
+
options.contactTolerance
|
|
12708
|
+
);
|
|
12220
12709
|
samples.push({ thickness, area: sample.area });
|
|
12221
12710
|
const previous = triangleThicknessValues[sample.triangle.index];
|
|
12222
12711
|
if (previous === void 0 || previous == null || thickness != null && thickness < previous) {
|
|
@@ -12254,7 +12743,7 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
12254
12743
|
};
|
|
12255
12744
|
}
|
|
12256
12745
|
const DEFAULT_VERTEX_TOLERANCE = 1e-5;
|
|
12257
|
-
class
|
|
12746
|
+
class UnionFind3 {
|
|
12258
12747
|
constructor(size) {
|
|
12259
12748
|
__publicField(this, "parent");
|
|
12260
12749
|
__publicField(this, "rank");
|
|
@@ -12313,7 +12802,7 @@ function analyzeMeshConnectedComponents(positions, vertexTolerance = DEFAULT_VER
|
|
|
12313
12802
|
const vertexComponentIndexes = new Int32Array(vertexCount);
|
|
12314
12803
|
if (triangleCount === 0) return { components: [], vertexComponentIndexes };
|
|
12315
12804
|
const tolerance = Number.isFinite(vertexTolerance) && vertexTolerance > 0 ? vertexTolerance : DEFAULT_VERTEX_TOLERANCE;
|
|
12316
|
-
const unionFind = new
|
|
12805
|
+
const unionFind = new UnionFind3(triangleCount);
|
|
12317
12806
|
const vertexOwnerByKey = /* @__PURE__ */ new Map();
|
|
12318
12807
|
for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
12319
12808
|
for (let corner = 0; corner < 3; corner += 1) {
|
|
@@ -12364,12 +12853,25 @@ function objectConnectivityEntry(object) {
|
|
|
12364
12853
|
shape: object.shape,
|
|
12365
12854
|
min: object.min,
|
|
12366
12855
|
max: object.max,
|
|
12856
|
+
positions: object.positions,
|
|
12367
12857
|
groupName: object.groupName,
|
|
12368
12858
|
treePath: object.treePath,
|
|
12369
12859
|
mock: object.mock,
|
|
12370
12860
|
bodyCount: 1
|
|
12371
12861
|
};
|
|
12372
12862
|
}
|
|
12863
|
+
function componentPositions(sourcePositions, component) {
|
|
12864
|
+
const out = new Float32Array(component.triangleIndexes.length * 9);
|
|
12865
|
+
let outOffset = 0;
|
|
12866
|
+
for (const triangleIndex of component.triangleIndexes) {
|
|
12867
|
+
const sourceOffset = triangleIndex * 9;
|
|
12868
|
+
for (let offset = 0; offset < 9; offset += 1) {
|
|
12869
|
+
out[outOffset + offset] = sourcePositions[sourceOffset + offset];
|
|
12870
|
+
}
|
|
12871
|
+
outOffset += 9;
|
|
12872
|
+
}
|
|
12873
|
+
return out;
|
|
12874
|
+
}
|
|
12373
12875
|
function buildMeshBodyConnectivityInput(objects, options) {
|
|
12374
12876
|
const entries = [];
|
|
12375
12877
|
const bodyIdsByObjectId = /* @__PURE__ */ new Map();
|
|
@@ -12393,6 +12895,7 @@ function buildMeshBodyConnectivityInput(objects, options) {
|
|
|
12393
12895
|
shape: options.bodyShape(object, component),
|
|
12394
12896
|
min: component.bbox.min,
|
|
12395
12897
|
max: component.bbox.max,
|
|
12898
|
+
positions: componentPositions(object.positions, component),
|
|
12396
12899
|
groupName: object.groupName,
|
|
12397
12900
|
treePath: object.treePath,
|
|
12398
12901
|
mock: object.mock,
|
|
@@ -12427,6 +12930,8 @@ function meshVertexColorBuffersFor(input, colorForEntryId) {
|
|
|
12427
12930
|
}
|
|
12428
12931
|
return out;
|
|
12429
12932
|
}
|
|
12933
|
+
const FLOATING_HIGHLIGHT_COLOR = [255, 68, 16];
|
|
12934
|
+
const FLOATING_HIDDEN_COLOR = [0, 0, 0];
|
|
12430
12935
|
const MASK_PALETTE = [
|
|
12431
12936
|
[230, 25, 75],
|
|
12432
12937
|
[60, 180, 75],
|
|
@@ -12475,6 +12980,149 @@ function distanceColorForRootDistance(distance, maxDistance) {
|
|
|
12475
12980
|
if (t <= 0.5) return rgbToHex(lerpRgb(DISTANCE_NEAR_COLOR, DISTANCE_MID_COLOR, t * 2));
|
|
12476
12981
|
return rgbToHex(lerpRgb(DISTANCE_MID_COLOR, DISTANCE_FAR_COLOR, (t - 0.5) * 2));
|
|
12477
12982
|
}
|
|
12983
|
+
const MIN_FIELD_GRID_SIZE = 18;
|
|
12984
|
+
const MAX_FIELD_GRID_SIZE = 32;
|
|
12985
|
+
const MAX_BLEND_SAMPLES = 12;
|
|
12986
|
+
const MAX_SEARCH_RADIUS = 5;
|
|
12987
|
+
const SAMPLE_RING_OFFSETS = [];
|
|
12988
|
+
for (let radius = 0; radius <= MAX_SEARCH_RADIUS; radius += 1) {
|
|
12989
|
+
const ring = [];
|
|
12990
|
+
for (let x = -radius; x <= radius; x += 1) {
|
|
12991
|
+
for (let y = -radius; y <= radius; y += 1) {
|
|
12992
|
+
for (let z = -radius; z <= radius; z += 1) {
|
|
12993
|
+
if (Math.max(Math.abs(x), Math.abs(y), Math.abs(z)) === radius) ring.push([x, y, z]);
|
|
12994
|
+
}
|
|
12995
|
+
}
|
|
12996
|
+
}
|
|
12997
|
+
SAMPLE_RING_OFFSETS.push(ring);
|
|
12998
|
+
}
|
|
12999
|
+
function gridKey(x, y, z) {
|
|
13000
|
+
return `${x},${y},${z}`;
|
|
13001
|
+
}
|
|
13002
|
+
function buildGeometryBounds(geometry) {
|
|
13003
|
+
const position = geometry.getAttribute("position");
|
|
13004
|
+
if (!position || position.count === 0) return null;
|
|
13005
|
+
const bounds = new Box3().setFromBufferAttribute(position);
|
|
13006
|
+
if (bounds.isEmpty()) return null;
|
|
13007
|
+
const size = bounds.getSize(new Vector3());
|
|
13008
|
+
const pad = Math.max(size.x, size.y, size.z, 1) * 1e-5;
|
|
13009
|
+
bounds.expandByScalar(pad);
|
|
13010
|
+
return bounds;
|
|
13011
|
+
}
|
|
13012
|
+
function buildSampleGrid(pointCloud) {
|
|
13013
|
+
const sampleCount = Math.floor(pointCloud.positions.length / 3);
|
|
13014
|
+
if (sampleCount <= 0) return null;
|
|
13015
|
+
const bounds = new Box3();
|
|
13016
|
+
const point = new Vector3();
|
|
13017
|
+
for (let sample = 0; sample < sampleCount; sample += 1) {
|
|
13018
|
+
const offset = sample * 3;
|
|
13019
|
+
bounds.expandByPoint(point.set(pointCloud.positions[offset], pointCloud.positions[offset + 1], pointCloud.positions[offset + 2]));
|
|
13020
|
+
}
|
|
13021
|
+
const size = bounds.getSize(new Vector3());
|
|
13022
|
+
const maxDim = Math.max(size.x, size.y, size.z);
|
|
13023
|
+
const cellSize = maxDim > 0 ? Math.max(maxDim / Math.cbrt(sampleCount), 1e-6) : 1;
|
|
13024
|
+
const cells = /* @__PURE__ */ new Map();
|
|
13025
|
+
for (let sample = 0; sample < sampleCount; sample += 1) {
|
|
13026
|
+
const offset = sample * 3;
|
|
13027
|
+
const x = Math.floor((pointCloud.positions[offset] - bounds.min.x) / cellSize);
|
|
13028
|
+
const y = Math.floor((pointCloud.positions[offset + 1] - bounds.min.y) / cellSize);
|
|
13029
|
+
const z = Math.floor((pointCloud.positions[offset + 2] - bounds.min.z) / cellSize);
|
|
13030
|
+
const key = gridKey(x, y, z);
|
|
13031
|
+
const entries = cells.get(key);
|
|
13032
|
+
if (entries) {
|
|
13033
|
+
entries.push(sample);
|
|
13034
|
+
} else {
|
|
13035
|
+
cells.set(key, [sample]);
|
|
13036
|
+
}
|
|
13037
|
+
}
|
|
13038
|
+
return { origin: bounds.min, cellSize, cells };
|
|
13039
|
+
}
|
|
13040
|
+
function fieldGridSizeForSampleCount(sampleCount) {
|
|
13041
|
+
if (sampleCount <= 0) return MIN_FIELD_GRID_SIZE;
|
|
13042
|
+
const scaled = Math.ceil(Math.cbrt(sampleCount) * 2.6);
|
|
13043
|
+
return Math.max(MIN_FIELD_GRID_SIZE, Math.min(MAX_FIELD_GRID_SIZE, scaled));
|
|
13044
|
+
}
|
|
13045
|
+
function distanceSquaredToSample(pointCloud, sample, point) {
|
|
13046
|
+
const offset = sample * 3;
|
|
13047
|
+
return (pointCloud.positions[offset] - point.x) ** 2 + (pointCloud.positions[offset + 1] - point.y) ** 2 + (pointCloud.positions[offset + 2] - point.z) ** 2;
|
|
13048
|
+
}
|
|
13049
|
+
function nearestSamples(point, pointCloud, sampleGrid) {
|
|
13050
|
+
const baseX = Math.floor((point.x - sampleGrid.origin.x) / sampleGrid.cellSize);
|
|
13051
|
+
const baseY = Math.floor((point.y - sampleGrid.origin.y) / sampleGrid.cellSize);
|
|
13052
|
+
const baseZ = Math.floor((point.z - sampleGrid.origin.z) / sampleGrid.cellSize);
|
|
13053
|
+
const found = [];
|
|
13054
|
+
for (const ring of SAMPLE_RING_OFFSETS) {
|
|
13055
|
+
for (const [dx, dy, dz] of ring) {
|
|
13056
|
+
const entries = sampleGrid.cells.get(gridKey(baseX + dx, baseY + dy, baseZ + dz));
|
|
13057
|
+
if (!entries) continue;
|
|
13058
|
+
for (const sample of entries) {
|
|
13059
|
+
found.push({ sample, distSq: distanceSquaredToSample(pointCloud, sample, point) });
|
|
13060
|
+
}
|
|
13061
|
+
}
|
|
13062
|
+
if (found.length >= MAX_BLEND_SAMPLES) break;
|
|
13063
|
+
}
|
|
13064
|
+
if (found.length === 0) {
|
|
13065
|
+
const sampleCount = Math.floor(pointCloud.positions.length / 3);
|
|
13066
|
+
for (let sample = 0; sample < sampleCount; sample += 1) {
|
|
13067
|
+
found.push({ sample, distSq: distanceSquaredToSample(pointCloud, sample, point) });
|
|
13068
|
+
}
|
|
13069
|
+
}
|
|
13070
|
+
found.sort((a, b) => a.distSq - b.distSq);
|
|
13071
|
+
return found.slice(0, MAX_BLEND_SAMPLES);
|
|
13072
|
+
}
|
|
13073
|
+
function blendedColorAt(point, pointCloud, sampleGrid) {
|
|
13074
|
+
const samples = nearestSamples(point, pointCloud, sampleGrid);
|
|
13075
|
+
if (samples.length === 0) return [90, 90, 90];
|
|
13076
|
+
const sigma = Math.max(sampleGrid.cellSize * 1.65, 1e-6);
|
|
13077
|
+
const sigmaSq = sigma * sigma;
|
|
13078
|
+
let weightSum = 0;
|
|
13079
|
+
let r = 0;
|
|
13080
|
+
let g = 0;
|
|
13081
|
+
let b = 0;
|
|
13082
|
+
for (const { sample, distSq: distSq2 } of samples) {
|
|
13083
|
+
const colorOffset = sample * 3;
|
|
13084
|
+
const weight = Math.exp(-distSq2 / (2 * sigmaSq)) + 1e-4 / Math.max(distSq2, 1e-8);
|
|
13085
|
+
weightSum += weight;
|
|
13086
|
+
r += (pointCloud.colors[colorOffset] ?? 0.35) * 255 * weight;
|
|
13087
|
+
g += (pointCloud.colors[colorOffset + 1] ?? 0.35) * 255 * weight;
|
|
13088
|
+
b += (pointCloud.colors[colorOffset + 2] ?? 0.35) * 255 * weight;
|
|
13089
|
+
}
|
|
13090
|
+
if (weightSum <= 0) return [90, 90, 90];
|
|
13091
|
+
return [r / weightSum, g / weightSum, b / weightSum];
|
|
13092
|
+
}
|
|
13093
|
+
function buildInspectHeatmapFieldData(geometry, pointCloud) {
|
|
13094
|
+
const bounds = buildGeometryBounds(geometry);
|
|
13095
|
+
const sampleGrid = buildSampleGrid(pointCloud);
|
|
13096
|
+
if (!bounds || !sampleGrid) return null;
|
|
13097
|
+
const gridSize = fieldGridSizeForSampleCount(Math.floor(pointCloud.positions.length / 3));
|
|
13098
|
+
const boundsSize = bounds.getSize(new Vector3());
|
|
13099
|
+
const data = new Uint8Array(gridSize * gridSize * gridSize * 4);
|
|
13100
|
+
const point = new Vector3();
|
|
13101
|
+
let dataOffset = 0;
|
|
13102
|
+
for (let y = 0; y < gridSize; y += 1) {
|
|
13103
|
+
for (let z = 0; z < gridSize; z += 1) {
|
|
13104
|
+
for (let x = 0; x < gridSize; x += 1) {
|
|
13105
|
+
point.set(
|
|
13106
|
+
bounds.min.x + boundsSize.x * x / (gridSize - 1),
|
|
13107
|
+
bounds.min.y + boundsSize.y * y / (gridSize - 1),
|
|
13108
|
+
bounds.min.z + boundsSize.z * z / (gridSize - 1)
|
|
13109
|
+
);
|
|
13110
|
+
const [r, g, b] = blendedColorAt(point, pointCloud, sampleGrid);
|
|
13111
|
+
data[dataOffset] = Math.max(0, Math.min(255, Math.round(r)));
|
|
13112
|
+
data[dataOffset + 1] = Math.max(0, Math.min(255, Math.round(g)));
|
|
13113
|
+
data[dataOffset + 2] = Math.max(0, Math.min(255, Math.round(b)));
|
|
13114
|
+
data[dataOffset + 3] = 255;
|
|
13115
|
+
dataOffset += 4;
|
|
13116
|
+
}
|
|
13117
|
+
}
|
|
13118
|
+
}
|
|
13119
|
+
return {
|
|
13120
|
+
data,
|
|
13121
|
+
boundsMin: [bounds.min.x, bounds.min.y, bounds.min.z],
|
|
13122
|
+
boundsSize: [boundsSize.x, boundsSize.y, boundsSize.z],
|
|
13123
|
+
gridSize
|
|
13124
|
+
};
|
|
13125
|
+
}
|
|
12478
13126
|
const workerScope = self;
|
|
12479
13127
|
class EmptyIntersectionShape {
|
|
12480
13128
|
intersect() {
|
|
@@ -12524,6 +13172,7 @@ function objectColorsFor(objects, input, colorByEntryId) {
|
|
|
12524
13172
|
}
|
|
12525
13173
|
function analyzeScalarChannel(request) {
|
|
12526
13174
|
const pointObjects = [];
|
|
13175
|
+
const heatmapFieldObjects = [];
|
|
12527
13176
|
const warnings = [];
|
|
12528
13177
|
for (const object of request.objects) {
|
|
12529
13178
|
if (!object.positions || object.positions.length < 9) continue;
|
|
@@ -12532,6 +13181,13 @@ function analyzeScalarChannel(request) {
|
|
|
12532
13181
|
const analysis = request.channel === "thickness" ? analyzeThicknessGeometry(geometry, request.thickness) : analyzeRoughnessGeometry(geometry, request.roughness);
|
|
12533
13182
|
analysis.warnings.forEach((warning) => warnings.push(`${object.name}: ${warning}`));
|
|
12534
13183
|
const buffers = pointBuffers(analysis.pointSamples);
|
|
13184
|
+
const field = buildInspectHeatmapFieldData(analysis.geometry, buffers);
|
|
13185
|
+
if (field) {
|
|
13186
|
+
heatmapFieldObjects.push({
|
|
13187
|
+
objectId: object.id,
|
|
13188
|
+
...field
|
|
13189
|
+
});
|
|
13190
|
+
}
|
|
12535
13191
|
pointObjects.push({
|
|
12536
13192
|
objectId: object.id,
|
|
12537
13193
|
sampleCount: analysis.pointSamples.length,
|
|
@@ -12547,6 +13203,7 @@ function analyzeScalarChannel(request) {
|
|
|
12547
13203
|
objectColors: {},
|
|
12548
13204
|
pointObjects,
|
|
12549
13205
|
meshColorObjects: [],
|
|
13206
|
+
heatmapFieldObjects,
|
|
12550
13207
|
warnings
|
|
12551
13208
|
};
|
|
12552
13209
|
}
|
|
@@ -12576,6 +13233,21 @@ function analyzeConnectivityChannel(request) {
|
|
|
12576
13233
|
objectColors: objectColorsFor(request.objects, bodyInput, colorByEntryId2),
|
|
12577
13234
|
pointObjects: [],
|
|
12578
13235
|
meshColorObjects: meshColorObjectsFor(bodyInput, colorByEntryId2),
|
|
13236
|
+
heatmapFieldObjects: [],
|
|
13237
|
+
warnings: report2.warnings
|
|
13238
|
+
};
|
|
13239
|
+
}
|
|
13240
|
+
if (request.channel === "floating") {
|
|
13241
|
+
const report2 = analyzeFloatingInspection(entries, { groundZ: request.groundZ });
|
|
13242
|
+
const colorByEntryId2 = new Map(
|
|
13243
|
+
report2.objects.map((object) => [object.id, rgbToHex(object.isFloating ? FLOATING_HIGHLIGHT_COLOR : FLOATING_HIDDEN_COLOR)])
|
|
13244
|
+
);
|
|
13245
|
+
return {
|
|
13246
|
+
channel: request.channel,
|
|
13247
|
+
objectColors: objectColorsFor(request.objects, bodyInput, colorByEntryId2),
|
|
13248
|
+
pointObjects: [],
|
|
13249
|
+
meshColorObjects: meshColorObjectsFor(bodyInput, colorByEntryId2),
|
|
13250
|
+
heatmapFieldObjects: [],
|
|
12579
13251
|
warnings: report2.warnings
|
|
12580
13252
|
};
|
|
12581
13253
|
}
|
|
@@ -12586,13 +13258,15 @@ function analyzeConnectivityChannel(request) {
|
|
|
12586
13258
|
objectColors: objectColorsFor(request.objects, bodyInput, colorByEntryId),
|
|
12587
13259
|
pointObjects: [],
|
|
12588
13260
|
meshColorObjects: meshColorObjectsFor(bodyInput, colorByEntryId),
|
|
13261
|
+
heatmapFieldObjects: [],
|
|
12589
13262
|
warnings: report.warnings
|
|
12590
13263
|
};
|
|
12591
13264
|
}
|
|
12592
13265
|
function transferFor(result) {
|
|
12593
13266
|
return [
|
|
12594
13267
|
...result.pointObjects.flatMap((object) => [object.positions.buffer, object.colors.buffer]),
|
|
12595
|
-
...result.meshColorObjects.map((object) => object.colors.buffer)
|
|
13268
|
+
...result.meshColorObjects.map((object) => object.colors.buffer),
|
|
13269
|
+
...result.heatmapFieldObjects.map((object) => object.data.buffer)
|
|
12596
13270
|
];
|
|
12597
13271
|
}
|
|
12598
13272
|
self.onmessage = (event) => {
|