@xeokit/xeokit-sdk 2.6.94 → 2.6.95

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeokit/xeokit-sdk",
3
- "version": "2.6.94",
3
+ "version": "2.6.95",
4
4
  "description": "3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision",
5
5
  "module": "./dist/xeokit-sdk.es.js",
6
6
  "main": "./dist/xeokit-sdk.cjs.js",
@@ -1,5 +1,7 @@
1
1
  // Some temporary vars to help avoid garbage collection
2
2
 
3
+ import earcut from '../libs/earcut.js';
4
+
3
5
  let doublePrecision = true;
4
6
  let FloatArrayType = doublePrecision ? Float64Array : Float32Array;
5
7
 
@@ -5411,6 +5413,313 @@ math.planeClipsPositions3 = function (pos, dir, positions, numElementsPerPositio
5411
5413
  }
5412
5414
  }
5413
5415
  return false;
5414
- }
5416
+ };
5417
+
5418
+ /**
5419
+ * Tests whether a plane intersects a bouding box.
5420
+ * @param plane SectionPlane
5421
+ * @param aabb Bounding box
5422
+ * @returns {boolean}
5423
+ */
5424
+ math.planeIntersectsAABB3 = (plane, aabb) => {
5425
+ const min = [aabb[0], aabb[1], aabb[2]];
5426
+ const max = [aabb[3], aabb[4], aabb[5]];
5427
+
5428
+ const corners = [
5429
+ [min[0], min[1], min[2]], // 000
5430
+ [max[0], min[1], min[2]], // 100
5431
+ [min[0], max[1], min[2]], // 010
5432
+ [max[0], max[1], min[2]], // 110
5433
+ [min[0], min[1], max[2]], // 001
5434
+ [max[0], min[1], max[2]], // 101
5435
+ [min[0], max[1], max[2]], // 011
5436
+ [max[0], max[1], max[2]] // 111
5437
+ ];
5438
+
5439
+ // Calculate distance from each corner to the plane
5440
+ let hasPositive = false;
5441
+ let hasNegative = false;
5442
+
5443
+ for (const corner of corners) {
5444
+ const distance = plane.dist + math.dotVec3(plane.dir, corner);
5445
+
5446
+ if (distance > 0) hasPositive = true;
5447
+ if (distance < 0) hasNegative = true;
5448
+
5449
+ // If we found points on both sides, the plane intersects the box
5450
+ if (hasPositive && hasNegative) return true;
5451
+ }
5452
+
5453
+ // If all points are on the same side, no intersection
5454
+ return false;
5455
+ };
5456
+
5457
+ /**
5458
+ * makeSectionPlaneSlicer returns a function that slices a given geometry with a given SectionPlane
5459
+ * The implementation finds segments where the SectionPlane intersects with the geometry's surface.
5460
+ * These segments form loops that are triangulated using the Earcut algorithm, taking internal holes into account.
5461
+ * @param {Number[]} pos SectionPlane position 3D vector
5462
+ * @param {Number[]} dir SectionPlane rotation quaternion
5463
+ * @returns {function}
5464
+ */
5465
+ math.makeSectionPlaneSlicer = (function() {
5466
+
5467
+ const tempVec2a = math.vec2();
5468
+ const tempVec2b = math.vec2();
5469
+ const tempVec2c = math.vec2();
5470
+ const tempVec2d = math.vec2();
5471
+
5472
+ const tempVec3a = math.vec3();
5473
+ const tempVec3b = math.vec3();
5474
+ const tempVec3c = math.vec3();
5475
+ const tempVec3d = math.vec3();
5476
+
5477
+ const triangle = [ math.vec3(), math.vec3(), math.vec3() ];
5478
+
5479
+ const worldUp = [0, 1, 0];
5480
+ const worldRight = [1, 0, 0];
5481
+ const worldForward = [0, 0, 1];
5482
+
5483
+ const sqDistVec3 = (function() {
5484
+ const tmp = math.vec3();
5485
+ return (a, b) => math.sqLenVec3(math.subVec3(a, b, tmp));
5486
+ })();
5487
+
5488
+ const iota = n => {
5489
+ const ret = [ ];
5490
+ for (let i = 0; i < n; ++i)
5491
+ ret.push(i);
5492
+ return ret;
5493
+ };
5494
+
5495
+ const setCoord2D = (p, dst) => { dst[0] = p.coord2Dx; dst[1] = p.coord2Dy; return dst; };
5496
+
5497
+ const ccw = (a, b, c) => ((c[1] - a[1]) * (b[0] - a[0])) > ((b[1] - a[1]) * (c[0] - a[0]));
5498
+
5499
+ const isLoopInside = (inner, outer) => {
5500
+ const bbI = inner.boundingBox;
5501
+ const bbO = outer.boundingBox;
5502
+ if ((bbI[0] < bbO[0]) || (bbI[1] < bbO[1]) || (bbI[2] > bbO[2]) || (bbI[3] > bbO[3])) {
5503
+ return false;
5504
+ }
5505
+
5506
+ const innerEndpoints = inner.endPoints;
5507
+ const outerEndpoints = outer.endPoints;
5508
+ for (let ii = 0, ij = innerEndpoints.length - 1; ii < innerEndpoints.length; ij = ii++) {
5509
+ const i0 = setCoord2D(innerEndpoints[ii], tempVec2a);
5510
+ const [i0x, i0y] = i0;
5511
+ const i1 = setCoord2D(innerEndpoints[ij], tempVec2b);
5512
+
5513
+ let inside = false;
5514
+ for (let i = 0, j = outerEndpoints.length - 1; i < outerEndpoints.length; j = i++) {
5515
+ const o0 = setCoord2D(outerEndpoints[i], tempVec2c);
5516
+ const o1 = setCoord2D(outerEndpoints[j], tempVec2d);
5517
+
5518
+ if ((ccw(i0, o0, o1) !== ccw(i1, o0, o1)) && (ccw(i0, i1, o0) !== ccw(i0, i1, o1))) {
5519
+ return false; // segments intersect
5520
+ }
5521
+
5522
+ const [o0x, o0y] = o0;
5523
+ const [o1x, o1y] = o1;
5524
+
5525
+ const dx = i0x - o0x;
5526
+ const dy = i0y - o0y;
5527
+
5528
+ const oDx = o1x - o0x;
5529
+ const oDy = o1y - o0y;
5530
+
5531
+ const dot = (((oDx !== 0) || (oDy !== 0)) && (Math.abs(oDx * dy - oDy * dx) < 1e-10)) ? (dx * oDx + dy * oDy) : -1;
5532
+ if ((dot >= 0) && (dot <= (Math.pow(oDx, 2) + Math.pow(oDy, 2)))) {
5533
+ return false; // on edge
5534
+ }
5535
+
5536
+ if (((o0y > i0y) !== (o1y > i0y)) && (dx < (dy * oDx / oDy))) {
5537
+ inside = !inside;
5538
+ }
5539
+ }
5540
+ if (! inside) {
5541
+ return false;
5542
+ }
5543
+ }
5544
+
5545
+ return true;
5546
+ };
5547
+
5548
+ return function(planePos, planeRot) {
5549
+ const planeU = math.vec3ApplyQuaternion(planeRot, worldRight, math.vec3());
5550
+ const planeV = math.vec3ApplyQuaternion(planeRot, worldUp, math.vec3());
5551
+ const planeN = math.vec3ApplyQuaternion(planeRot, worldForward, math.vec3());
5552
+
5553
+ return function(meshCenter, meshIndices, meshPositions) {
5554
+ const planeToMesh = math.subVec3(meshCenter, planePos, math.vec3());
5555
+ const planeDist = math.dotVec3(planeN, planeToMesh);
5556
+
5557
+ const unsortedSegment = [ ];
5558
+ const indexedPositions = [ null ]; // to never return 0 from addPosition, so its result can be used as a predicate
5559
+ const addPosition = p => { const idx = indexedPositions.length; indexedPositions.push(math.vec3(p)); return idx; };
5560
+
5561
+ const setVertex = (i, dst) => {
5562
+ const idx = meshIndices[i] * 3;
5563
+ dst[0] = meshPositions[idx + 0];
5564
+ dst[1] = meshPositions[idx + 1];
5565
+ dst[2] = meshPositions[idx + 2];
5566
+ return dst;
5567
+ };
5568
+
5569
+ for (let meshIdx = 0; meshIdx < meshIndices.length; meshIdx += 3) {
5570
+ const p0 = setVertex(meshIdx + 0, triangle[0]);
5571
+ const p1 = setVertex(meshIdx + 1, triangle[1]);
5572
+ const p2 = setVertex(meshIdx + 2, triangle[2]);
5573
+
5574
+ if (math.compareVec3(p0, p1) || math.compareVec3(p1, p2) || math.compareVec3(p2, p0)) {
5575
+ continue; // skip degenerate triangle
5576
+ }
5577
+
5578
+ const d0 = planeDist + math.dotVec3(planeN, p0);
5579
+ const d1 = planeDist + math.dotVec3(planeN, p1);
5580
+ const d2 = planeDist + math.dotVec3(planeN, p2);
5581
+
5582
+ if ((d0 !== 0) || (d1 !== 0) || (d2 !== 0)) {
5583
+ const i0 = (d0 * d1 <= 0) && addPosition(math.lerpVec3(d0 / (d0 - d1), 0, 1, p0, p1, tempVec3a));
5584
+ const i1 = (d1 * d2 <= 0) && addPosition(math.lerpVec3(d1 / (d1 - d2), 0, 1, p1, p2, tempVec3a));
5585
+ const i2 = (d2 * d0 <= 0) && addPosition(math.lerpVec3(d2 / (d2 - d0), 0, 1, p2, p0, tempVec3a));
5586
+
5587
+ if (i0 ? (i1 || i2) : (i1 && i2)) { // triangle intersected by the section plane
5588
+ unsortedSegment.push(i0 ? [ i0, i1 || i2 ] : [ i1, i2 ]);
5589
+ }
5590
+ }
5591
+ }
5592
+
5593
+ const endpointLoops = [ ];
5594
+ while (unsortedSegment.length > 0) {
5595
+ endpointLoops.push([ unsortedSegment[0][0], unsortedSegment[0][1] ]);
5596
+ const curEndpoints = endpointLoops[endpointLoops.length - 1];
5597
+ unsortedSegment.splice(0, 1);
5598
+ while (unsortedSegment.length > 0) {
5599
+ const lastPoint = indexedPositions[curEndpoints[curEndpoints.length - 1]];
5600
+ const closest = { distSq: sqDistVec3(indexedPositions[curEndpoints[0]], lastPoint), idx: -1, side: -1 };
5601
+ unsortedSegment.forEach((seg, i) => {
5602
+ const distSq0 = sqDistVec3(indexedPositions[seg[0]], lastPoint);
5603
+ const distSq1 = sqDistVec3(indexedPositions[seg[1]], lastPoint);
5604
+ const distSq = Math.min(distSq0, distSq1);
5605
+ if (closest.distSq > distSq) {
5606
+ closest.distSq = distSq;
5607
+ closest.idx = i;
5608
+ closest.side = (distSq1 < distSq0) ? 1 : 0;
5609
+ }
5610
+ });
5611
+
5612
+ if (closest.distSq < 1e-20) {
5613
+ const nextSegment = (closest.idx >= 0) && unsortedSegment[closest.idx];
5614
+ indexedPositions[nextSegment ? nextSegment[closest.side] : curEndpoints[0]] = lastPoint; // move the split face's (above) vertex to lastPoint, to not introduce gaps
5615
+ if (nextSegment) {
5616
+ unsortedSegment.splice(closest.idx, 1);
5617
+ const nextEnd = nextSegment[1 - closest.side];
5618
+ if (sqDistVec3(indexedPositions[nextEnd], indexedPositions[curEndpoints[0]]) > 1e-20) {
5619
+ curEndpoints.push(nextEnd);
5620
+ } else {
5621
+ break;
5622
+ }
5623
+ } else {
5624
+ break;
5625
+ }
5626
+ } else {
5627
+ endpointLoops.pop(); // Could not find a matching segment. Discard a loop that cannot be closed.
5628
+ break;
5629
+ }
5630
+ }
5631
+ }
5632
+
5633
+ const loops = endpointLoops.filter(endPoints => endPoints.length > 2).map((endPoints, idx) => {
5634
+ const planeEndpoints = endPoints.map(posIdx => {
5635
+ const P = math.addVec3(planeToMesh, indexedPositions[posIdx], tempVec3a);
5636
+ return { posIdx: posIdx, coord2Dx: math.dotVec3(planeU, P), coord2Dy: math.dotVec3(planeV, P) };
5637
+ });
5638
+ let doubleArea = 0;
5639
+ const aabb = math.collapseAABB2(math.AABB2());
5640
+ for (let i = 0; i < planeEndpoints.length; i++) {
5641
+ const p0 = planeEndpoints[i];
5642
+ tempVec2a[0] = p0.coord2Dx;
5643
+ tempVec2a[1] = p0.coord2Dy;
5644
+ math.expandAABB2Point2(aabb, tempVec2a);
5645
+ const p1 = planeEndpoints[(i + 1) % planeEndpoints.length];
5646
+ doubleArea += (p0.coord2Dx * p1.coord2Dy - p1.coord2Dx * p0.coord2Dy);
5647
+ }
5648
+ return {
5649
+ boundingBox: aabb,
5650
+ doubleArea: Math.abs(doubleArea),
5651
+ endPoints: planeEndpoints
5652
+ };
5653
+ }).sort((a, b) => b.doubleArea - a.doubleArea);
5654
+
5655
+ const sliceGeometries = [ ];
5656
+
5657
+ while (loops.length > 0) {
5658
+ const vertices2D = [ ];
5659
+ const vertices3D = [ ];
5660
+
5661
+ const appendLoopVertices = loop => loop.endPoints.forEach(endpoint2D => {
5662
+ vertices2D.push(endpoint2D.coord2Dx, endpoint2D.coord2Dy);
5663
+ vertices3D.push(endpoint2D.posIdx);
5664
+ });
5665
+
5666
+ const outerLoop = loops.shift();
5667
+ appendLoopVertices(outerLoop);
5668
+
5669
+ const innerLoops = [ ];
5670
+ let innerLoopIdx = 0;
5671
+ while (innerLoopIdx < loops.length) {
5672
+ const loop = loops[innerLoopIdx];
5673
+ if (isLoopInside(loop, outerLoop) && innerLoops.every(inner => !isLoopInside(loop, inner))) {
5674
+ loop.index = vertices2D.length / 2;
5675
+ appendLoopVertices(loop);
5676
+ innerLoops.push(loop);
5677
+ loops.splice(innerLoopIdx, 1);
5678
+ } else {
5679
+ ++innerLoopIdx;
5680
+ }
5681
+ }
5682
+
5683
+ // Triangulate
5684
+ const triangles = earcut(vertices2D, innerLoops.map(loop => loop.index));
5685
+
5686
+ const positions = [ ];
5687
+ const normals = [ ];
5688
+ const uv = [ ];
5689
+ for (let i = 0; i < triangles.length; i += 3) {
5690
+ const v0 = indexedPositions[vertices3D[triangles[i + 0]]];
5691
+ const v1 = indexedPositions[vertices3D[triangles[i + 1]]];
5692
+ const v2 = indexedPositions[vertices3D[triangles[i + 2]]];
5693
+ math.subVec3(v1, v0, tempVec3b);
5694
+ math.subVec3(v2, v0, tempVec3c);
5695
+ math.normalizeVec3(math.cross3Vec3(tempVec3b, tempVec3c, tempVec3c), tempVec3c);
5696
+ const facedPositively = math.dotVec3(tempVec3c, planeN) <= 0;
5697
+ if (! facedPositively) {
5698
+ math.negateVec3(tempVec3c, tempVec3c);
5699
+ }
5700
+ for (let j = 0; j < 3; ++j) {
5701
+ const vIdx = triangles[i + (facedPositively ? j : (2 - j))];
5702
+ const v = indexedPositions[vertices3D[vIdx]];
5703
+ positions.push(v[0], v[1], v[2]);
5704
+ normals.push(tempVec3c[0], tempVec3c[1], tempVec3c[2]);
5705
+ const uvOff = 2 * vIdx;
5706
+ uv.push(-vertices2D[uvOff], vertices2D[uvOff + 1]);
5707
+ }
5708
+ }
5709
+
5710
+ if (positions.length > 0) {
5711
+ sliceGeometries.push({
5712
+ indices: iota(positions.length / 3),
5713
+ positions: positions,
5714
+ normals: normals,
5715
+ uv: uv
5716
+ });
5717
+ }
5718
+ }
5719
+
5720
+ return sliceGeometries;
5721
+ };
5722
+ };
5723
+ })();
5415
5724
 
5416
5725
  export {math};
@@ -659,9 +659,12 @@ export class SceneModelEntity {
659
659
  * @type {Material}
660
660
  */
661
661
  set capMaterial(value) {
662
- if(!this.scene.readableGeometryEnabled) return;
663
- this._capMaterial = value instanceof Material ? value : null;
664
- this.scene._capMaterialUpdated(this.id, this.model.id);
662
+ if (this.scene.readableGeometryEnabled) {
663
+ this._capMaterial = value;
664
+ this.scene._sectionCaps._onCapMaterialUpdated(this);
665
+ } else {
666
+ throw "The `capMaterial` assignment requires `Viewer::readableGeometryEnabled` to be `true`";
667
+ }
665
668
  }
666
669
 
667
670
  /**
@@ -994,10 +994,6 @@ class Scene extends Component {
994
994
  // Scene. Violates Hollywood Principle, where we could just filter on type in _addComponent,
995
995
  // but this is faster than checking the type of each component in such a filter.
996
996
 
997
- _capMaterialUpdated(entityId, modelId) {
998
- this._sectionCaps._onCapMaterialUpdated(entityId, modelId);
999
- }
1000
-
1001
997
  _sectionPlaneCreated(sectionPlane) {
1002
998
  this.sectionPlanes[sectionPlane.id] = sectionPlane;
1003
999
  this.scene._sectionPlanesState.addSectionPlane(sectionPlane._state);