@xeokit/xeokit-sdk 2.6.95 → 2.6.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  /**
2
- * xeokit-sdk v2.6.95
3
- * Commit: ff66e348fc6786a85f8ff8732f9876d63e3782d6
4
- * Built: 2025-11-18T12:58:25.225Z
2
+ * xeokit-sdk v2.6.96
3
+ * Commit: c76eb4c08555d55fdd54313f5fb25481d28d1150
4
+ * Built: 2025-12-02T12:22:54.826Z
5
5
  */
6
6
 
7
7
  if (typeof window !== 'undefined') {
8
- window.__XEOKIT__ = { version: '2.6.95', commit: 'ff66e348fc6786a85f8ff8732f9876d63e3782d6', built: '2025-11-18T12:58:25.225Z' };
8
+ window.__XEOKIT__ = { version: '2.6.96', commit: 'c76eb4c08555d55fdd54313f5fb25481d28d1150', built: '2025-12-02T12:22:54.826Z' };
9
9
  }
10
10
 
11
11
  'use strict';
@@ -7437,9 +7437,12 @@ math.makeSectionPlaneSlicer = (function() {
7437
7437
  math.vec3();
7438
7438
 
7439
7439
  const triangle = [ math.vec3(), math.vec3(), math.vec3() ];
7440
+ const tmpIntersections = [ null, null, null ];
7441
+ const tmpPositions = [ null, null, null ];
7442
+ const tmpReferences = [ null, null, null ];
7440
7443
 
7441
- const worldUp = [0, 1, 0];
7442
7444
  const worldRight = [1, 0, 0];
7445
+ const worldUp = [0, 1, 0];
7443
7446
  const worldForward = [0, 0, 1];
7444
7447
 
7445
7448
  const sqDistVec3 = (function() {
@@ -7454,84 +7457,113 @@ math.makeSectionPlaneSlicer = (function() {
7454
7457
  return ret;
7455
7458
  };
7456
7459
 
7457
- const setCoord2D = (p, dst) => { dst[0] = p.coord2Dx; dst[1] = p.coord2Dy; return dst; };
7458
-
7459
7460
  const ccw = (a, b, c) => ((c[1] - a[1]) * (b[0] - a[0])) > ((b[1] - a[1]) * (c[0] - a[0]));
7460
7461
 
7461
- const isLoopInside = (inner, outer) => {
7462
- const bbI = inner.boundingBox;
7463
- const bbO = outer.boundingBox;
7464
- if ((bbI[0] < bbO[0]) || (bbI[1] < bbO[1]) || (bbI[2] > bbO[2]) || (bbI[3] > bbO[3])) {
7465
- return false;
7466
- }
7462
+ return function(plane) {
7463
+ const planeU = math.vec3ApplyQuaternion(plane.quaternion, worldRight, math.vec3());
7464
+ const planeV = math.vec3ApplyQuaternion(plane.quaternion, worldUp, math.vec3());
7465
+ const planeN = math.vec3ApplyQuaternion(plane.quaternion, worldForward, math.vec3());
7467
7466
 
7468
- const innerEndpoints = inner.endPoints;
7469
- const outerEndpoints = outer.endPoints;
7470
- for (let ii = 0, ij = innerEndpoints.length - 1; ii < innerEndpoints.length; ij = ii++) {
7471
- const i0 = setCoord2D(innerEndpoints[ii], tempVec2a);
7472
- const [i0x, i0y] = i0;
7473
- const i1 = setCoord2D(innerEndpoints[ij], tempVec2b);
7467
+ return function(mesh, cfg = { }) {
7468
+ const planeToMesh = math.subVec3(mesh.origin, plane.pos, math.vec3());
7469
+ const planeDist = math.dotVec3(planeN, planeToMesh);
7470
+
7471
+ const unsortedSegment = [ ];
7472
+ const indexedPositions = [ null ]; // to never return 0 from addPosition, so its result can be used as a predicate
7473
+ const addPosition = (p, addUV) => {
7474
+ const idx = indexedPositions.length;
7475
+ const P = new Float64Array(5);
7476
+ P.set(p);
7477
+ if (addUV) {
7478
+ math.addVec3(planeToMesh, p, tempVec3a);
7479
+ P[3] = -math.dotVec3(planeU, tempVec3a);
7480
+ P[4] = math.dotVec3(planeV, tempVec3a);
7481
+ }
7482
+ indexedPositions.push(P);
7483
+ return idx;
7484
+ };
7474
7485
 
7475
- let inside = false;
7476
- for (let i = 0, j = outerEndpoints.length - 1; i < outerEndpoints.length; j = i++) {
7477
- const o0 = setCoord2D(outerEndpoints[i], tempVec2c);
7478
- const o1 = setCoord2D(outerEndpoints[j], tempVec2d);
7486
+ const getCoord2D = (idx, dst) => { const P = indexedPositions[idx]; dst[0] = P[3]; dst[1] = P[4]; return dst; };
7479
7487
 
7480
- if ((ccw(i0, o0, o1) !== ccw(i1, o0, o1)) && (ccw(i0, i1, o0) !== ccw(i0, i1, o1))) {
7481
- return false; // segments intersect
7488
+ const isLoopInside = (inner, outer) => {
7489
+ const bbI = inner.boundingBox;
7490
+ const bbO = outer.boundingBox;
7491
+ if ((bbI[0] < bbO[0]) || (bbI[1] < bbO[1]) || (bbI[2] > bbO[2]) || (bbI[3] > bbO[3])) {
7492
+ return false;
7482
7493
  }
7483
7494
 
7484
- const [o0x, o0y] = o0;
7485
- const [o1x, o1y] = o1;
7495
+ const innerEndpoints = inner.endPoints;
7496
+ const outerEndpoints = outer.endPoints;
7497
+ for (let ii = 0, ij = innerEndpoints.length - 1; ii < innerEndpoints.length; ij = ii++) {
7498
+ const i0 = getCoord2D(innerEndpoints[ii], tempVec2a);
7499
+ const [i0x, i0y] = i0;
7500
+ const i1 = getCoord2D(innerEndpoints[ij], tempVec2b);
7486
7501
 
7487
- const dx = i0x - o0x;
7488
- const dy = i0y - o0y;
7502
+ let inside = false;
7503
+ for (let i = 0, j = outerEndpoints.length - 1; i < outerEndpoints.length; j = i++) {
7504
+ const o0 = getCoord2D(outerEndpoints[i], tempVec2c);
7505
+ const o1 = getCoord2D(outerEndpoints[j], tempVec2d);
7489
7506
 
7490
- const oDx = o1x - o0x;
7491
- const oDy = o1y - o0y;
7507
+ if ((ccw(i0, o0, o1) !== ccw(i1, o0, o1)) && (ccw(i0, i1, o0) !== ccw(i0, i1, o1))) {
7508
+ return false; // segments intersect
7509
+ }
7492
7510
 
7493
- const dot = (((oDx !== 0) || (oDy !== 0)) && (Math.abs(oDx * dy - oDy * dx) < 1e-10)) ? (dx * oDx + dy * oDy) : -1;
7494
- if ((dot >= 0) && (dot <= (Math.pow(oDx, 2) + Math.pow(oDy, 2)))) {
7495
- return false; // on edge
7496
- }
7511
+ const [o0x, o0y] = o0;
7512
+ const [o1x, o1y] = o1;
7513
+
7514
+ const dx = i0x - o0x;
7515
+ const dy = i0y - o0y;
7497
7516
 
7498
- if (((o0y > i0y) !== (o1y > i0y)) && (dx < (dy * oDx / oDy))) {
7499
- inside = !inside;
7517
+ const oDx = o1x - o0x;
7518
+ const oDy = o1y - o0y;
7519
+
7520
+ const dot = (((oDx !== 0) || (oDy !== 0)) && (Math.abs(oDx * dy - oDy * dx) < 1e-10)) ? (dx * oDx + dy * oDy) : -1;
7521
+ if ((dot >= 0) && (dot <= (Math.pow(oDx, 2) + Math.pow(oDy, 2)))) {
7522
+ return false; // on edge
7523
+ }
7524
+
7525
+ if (((o0y > i0y) !== (o1y > i0y)) && (dx < (dy * oDx / oDy))) {
7526
+ inside = !inside;
7527
+ }
7528
+ }
7529
+ if (! inside) {
7530
+ return false;
7531
+ }
7500
7532
  }
7501
- }
7502
- if (! inside) {
7503
- return false;
7504
- }
7505
- }
7506
7533
 
7507
- return true;
7508
- };
7534
+ return true;
7535
+ };
7509
7536
 
7510
- return function(planePos, planeRot) {
7511
- const planeU = math.vec3ApplyQuaternion(planeRot, worldRight, math.vec3());
7512
- const planeV = math.vec3ApplyQuaternion(planeRot, worldUp, math.vec3());
7513
- const planeN = math.vec3ApplyQuaternion(planeRot, worldForward, math.vec3());
7537
+ const pos = { indices: [ ], normals: [ ] };
7538
+ const neg = (! cfg.onlyPosSliceWithUV) && { indices: [ ], normals: [ ] };
7514
7539
 
7515
- return function(meshCenter, meshIndices, meshPositions) {
7516
- const planeToMesh = math.subVec3(meshCenter, planePos, math.vec3());
7517
- const planeDist = math.dotVec3(planeN, planeToMesh);
7540
+ const appendOnSide = (dst, indices, normal) => {
7541
+ if (dst) {
7542
+ const p0 = indexedPositions[indices[0]];
7543
+ normal ||= math.normalizeVec3(math.cross3Vec3(math.subVec3(indexedPositions[indices[1]], p0, tempVec3a),
7544
+ math.subVec3(indexedPositions[indices[2]], p0, tempVec3b),
7545
+ tempVec3a),
7546
+ tempVec3a);
7518
7547
 
7519
- const unsortedSegment = [ ];
7520
- const indexedPositions = [ null ]; // to never return 0 from addPosition, so its result can be used as a predicate
7521
- const addPosition = p => { const idx = indexedPositions.length; indexedPositions.push(math.vec3(p)); return idx; };
7548
+ for (let i = 0; i < 3; ++i) {
7549
+ dst.indices.push(indices[i]);
7550
+ dst.normals.push(normal[0], normal[1], normal[2]);
7551
+ }
7552
+ }
7553
+ };
7522
7554
 
7523
7555
  const setVertex = (i, dst) => {
7524
- const idx = meshIndices[i] * 3;
7525
- dst[0] = meshPositions[idx + 0];
7526
- dst[1] = meshPositions[idx + 1];
7527
- dst[2] = meshPositions[idx + 2];
7556
+ const idx = mesh.indices[i] * 3;
7557
+ dst[0] = mesh.positions[idx + 0];
7558
+ dst[1] = mesh.positions[idx + 1];
7559
+ dst[2] = mesh.positions[idx + 2];
7528
7560
  return dst;
7529
7561
  };
7530
7562
 
7531
- for (let meshIdx = 0; meshIdx < meshIndices.length; meshIdx += 3) {
7532
- const p0 = setVertex(meshIdx + 0, triangle[0]);
7533
- const p1 = setVertex(meshIdx + 1, triangle[1]);
7534
- const p2 = setVertex(meshIdx + 2, triangle[2]);
7563
+ for (let faceIdx = 0; faceIdx < mesh.indices.length; faceIdx += 3) {
7564
+ const p0 = setVertex(faceIdx + 0, triangle[0]);
7565
+ const p1 = setVertex(faceIdx + 1, triangle[1]);
7566
+ const p2 = setVertex(faceIdx + 2, triangle[2]);
7535
7567
 
7536
7568
  if (math.compareVec3(p0, p1) || math.compareVec3(p1, p2) || math.compareVec3(p2, p0)) {
7537
7569
  continue; // skip degenerate triangle
@@ -7542,12 +7574,60 @@ math.makeSectionPlaneSlicer = (function() {
7542
7574
  const d2 = planeDist + math.dotVec3(planeN, p2);
7543
7575
 
7544
7576
  if ((d0 !== 0) || (d1 !== 0) || (d2 !== 0)) {
7545
- const i0 = (d0 * d1 <= 0) && addPosition(math.lerpVec3(d0 / (d0 - d1), 0, 1, p0, p1, tempVec3a));
7546
- const i1 = (d1 * d2 <= 0) && addPosition(math.lerpVec3(d1 / (d1 - d2), 0, 1, p1, p2, tempVec3a));
7547
- const i2 = (d2 * d0 <= 0) && addPosition(math.lerpVec3(d2 / (d2 - d0), 0, 1, p2, p0, tempVec3a));
7577
+ const i0 = (d0 * d1 <= 0) && addPosition(math.lerpVec3(d0 / (d0 - d1), 0, 1, p0, p1, tempVec3a), true);
7578
+ const i1 = (d1 * d2 <= 0) && addPosition(math.lerpVec3(d1 / (d1 - d2), 0, 1, p1, p2, tempVec3a), true);
7579
+ const i2 = (d2 * d0 <= 0) && addPosition(math.lerpVec3(d2 / (d2 - d0), 0, 1, p2, p0, tempVec3a), true);
7580
+
7581
+ const p = (! cfg.onlyPosSliceWithUV) && tmpPositions;
7582
+ if (p) {
7583
+ p[0] = addPosition(p0);
7584
+ p[1] = addPosition(p1);
7585
+ p[2] = addPosition(p2);
7586
+ }
7548
7587
 
7549
7588
  if (i0 ? (i1 || i2) : (i1 && i2)) { // triangle intersected by the section plane
7550
7589
  unsortedSegment.push(i0 ? [ i0, i1 || i2 ] : [ i1, i2 ]);
7590
+
7591
+ if (p) {
7592
+ if ((d0 === 0) && (d1 === 0)) {
7593
+ appendOnSide((d2 > 0) ? pos : neg, p);
7594
+ } else if ((d0 === 0) && (d2 === 0)) {
7595
+ appendOnSide((d1 > 0) ? pos : neg, p);
7596
+ } else if ((d1 === 0) && (d2 === 0)) {
7597
+ appendOnSide((d0 > 0) ? pos : neg, p);
7598
+ } else {
7599
+ const isPos = (i0 ? d0 : d1) > 0;
7600
+ const dst0 = isPos ? pos : neg;
7601
+ const dst1 = isPos ? neg : pos;
7602
+ const i = tmpIntersections;
7603
+ i[0] = i0;
7604
+ i[1] = i1;
7605
+ i[2] = i2;
7606
+ const ref = tmpReferences;
7607
+ if (i0) {
7608
+ ref[0] = 0; ref[1] = 1; ref[2] = 2;
7609
+ } else {
7610
+ ref[0] = 1; ref[1] = 2; ref[2] = 0;
7611
+ }
7612
+ if (i[ref[1]]) {
7613
+ appendOnSide(dst0, [ p[ref[0]], i[ref[0]], p[ref[2]] ]);
7614
+ appendOnSide(dst0, [ i[ref[0]], i[ref[1]], p[ref[2]] ]);
7615
+ appendOnSide(dst1, [ i[ref[0]], p[ref[1]], i[ref[1]] ]);
7616
+ } else {
7617
+ appendOnSide(dst0, [ p[ref[0]], i[ref[0]], i[ref[2]] ]);
7618
+ appendOnSide(dst1, [ i[ref[0]], p[ref[1]], p[ref[2]] ]);
7619
+ appendOnSide(dst1, [ i[ref[0]], p[ref[2]], i[ref[2]] ]);
7620
+ }
7621
+ }
7622
+ }
7623
+ } else if (p) {
7624
+ if ((d0 >= 0) && (d1 >= 0) && (d2 >= 0)) {
7625
+ appendOnSide(pos, p);
7626
+ } else if ((d0 <= 0) && (d1 <= 0) && (d2 <= 0)) {
7627
+ appendOnSide(neg, p);
7628
+ } else {
7629
+ debugger;
7630
+ }
7551
7631
  }
7552
7632
  }
7553
7633
  }
@@ -7593,37 +7673,30 @@ math.makeSectionPlaneSlicer = (function() {
7593
7673
  }
7594
7674
 
7595
7675
  const loops = endpointLoops.filter(endPoints => endPoints.length > 2).map((endPoints, idx) => {
7596
- const planeEndpoints = endPoints.map(posIdx => {
7597
- const P = math.addVec3(planeToMesh, indexedPositions[posIdx], tempVec3a);
7598
- return { posIdx: posIdx, coord2Dx: math.dotVec3(planeU, P), coord2Dy: math.dotVec3(planeV, P) };
7599
- });
7600
7676
  let doubleArea = 0;
7601
7677
  const aabb = math.collapseAABB2(math.AABB2());
7602
- for (let i = 0; i < planeEndpoints.length; i++) {
7603
- const p0 = planeEndpoints[i];
7604
- tempVec2a[0] = p0.coord2Dx;
7605
- tempVec2a[1] = p0.coord2Dy;
7678
+ for (let i = 0; i < endPoints.length; i++) {
7679
+ getCoord2D(endPoints[i], tempVec2a);
7606
7680
  math.expandAABB2Point2(aabb, tempVec2a);
7607
- const p1 = planeEndpoints[(i + 1) % planeEndpoints.length];
7608
- doubleArea += (p0.coord2Dx * p1.coord2Dy - p1.coord2Dx * p0.coord2Dy);
7681
+ getCoord2D(endPoints[(i + 1) % endPoints.length], tempVec2b);
7682
+ doubleArea += (tempVec2a[0] * tempVec2b[1] - tempVec2b[0] * tempVec2a[1]);
7609
7683
  }
7610
7684
  return {
7611
7685
  boundingBox: aabb,
7612
7686
  doubleArea: Math.abs(doubleArea),
7613
- endPoints: planeEndpoints
7687
+ endPoints: endPoints
7614
7688
  };
7615
7689
  }).sort((a, b) => b.doubleArea - a.doubleArea);
7616
7690
 
7617
- const sliceGeometries = [ ];
7618
-
7619
7691
  while (loops.length > 0) {
7620
7692
  const vertices2D = [ ];
7621
7693
  const vertices3D = [ ];
7622
7694
 
7623
7695
  const appendLoopVertices = loop => loop.endPoints.forEach(endpoint2D => {
7624
- vertices2D.push(endpoint2D.coord2Dx, endpoint2D.coord2Dy);
7625
- vertices3D.push(endpoint2D.posIdx);
7626
- });
7696
+ getCoord2D(endpoint2D, tempVec2a);
7697
+ vertices2D.push(tempVec2a[0], tempVec2a[1]);
7698
+ vertices3D.push(endpoint2D);
7699
+ });
7627
7700
 
7628
7701
  const outerLoop = loops.shift();
7629
7702
  appendLoopVertices(outerLoop);
@@ -7645,41 +7718,33 @@ math.makeSectionPlaneSlicer = (function() {
7645
7718
  // Triangulate
7646
7719
  const triangles = earcut(vertices2D, innerLoops.map(loop => loop.index));
7647
7720
 
7648
- const positions = [ ];
7649
- const normals = [ ];
7650
- const uv = [ ];
7651
7721
  for (let i = 0; i < triangles.length; i += 3) {
7652
- const v0 = indexedPositions[vertices3D[triangles[i + 0]]];
7653
- const v1 = indexedPositions[vertices3D[triangles[i + 1]]];
7654
- const v2 = indexedPositions[vertices3D[triangles[i + 2]]];
7655
- math.subVec3(v1, v0, tempVec3b);
7656
- math.subVec3(v2, v0, tempVec3c);
7722
+ const ti = tmpIntersections;
7723
+ ti[0] = vertices3D[triangles[i + 0]];
7724
+ ti[1] = vertices3D[triangles[i + 1]];
7725
+ ti[2] = vertices3D[triangles[i + 2]];
7726
+ const v0 = indexedPositions[ti[0]];
7727
+ math.subVec3(indexedPositions[ti[1]], v0, tempVec3b);
7728
+ math.subVec3(indexedPositions[ti[2]], v0, tempVec3c);
7657
7729
  math.normalizeVec3(math.cross3Vec3(tempVec3b, tempVec3c, tempVec3c), tempVec3c);
7658
7730
  const facedPositively = math.dotVec3(tempVec3c, planeN) <= 0;
7659
- if (! facedPositively) {
7660
- math.negateVec3(tempVec3c, tempVec3c);
7661
- }
7662
- for (let j = 0; j < 3; ++j) {
7663
- const vIdx = triangles[i + (facedPositively ? j : (2 - j))];
7664
- const v = indexedPositions[vertices3D[vIdx]];
7665
- positions.push(v[0], v[1], v[2]);
7666
- normals.push(tempVec3c[0], tempVec3c[1], tempVec3c[2]);
7667
- const uvOff = 2 * vIdx;
7668
- uv.push(-vertices2D[uvOff], vertices2D[uvOff + 1]);
7669
- }
7670
- }
7671
-
7672
- if (positions.length > 0) {
7673
- sliceGeometries.push({
7674
- indices: iota(positions.length / 3),
7675
- positions: positions,
7676
- normals: normals,
7677
- uv: uv
7678
- });
7731
+ appendOnSide(facedPositively ? pos : neg, ti, tempVec3c);
7732
+ const tmp = ti[0]; ti[0] = ti[2]; ti[2] = tmp;
7733
+ appendOnSide(facedPositively ? neg : pos, ti, math.negateVec3(tempVec3c, tempVec3c));
7679
7734
  }
7680
7735
  }
7681
7736
 
7682
- return sliceGeometries;
7737
+ const side = src => src && (src.indices.length > 0) && (function() {
7738
+ const positions = [ ];
7739
+ const uv = cfg.onlyPosSliceWithUV && [ ];
7740
+ src.indices.forEach(idx => {
7741
+ const P = indexedPositions[idx];
7742
+ positions.push(P[0], P[1], P[2]);
7743
+ uv && uv.push(P[3], P[4]);
7744
+ });
7745
+ return { indices: iota(src.indices.length), positions: positions, normals: src.normals, uv: uv };
7746
+ })();
7747
+ return { pos: side(pos), neg: side(neg) };
7683
7748
  };
7684
7749
  };
7685
7750
  })();
@@ -31331,6 +31396,8 @@ class SceneModelMesh {
31331
31396
  */
31332
31397
  _destroy() {
31333
31398
  this.model.scene._renderer.putPickID(this.pickId);
31399
+
31400
+ this.layer = null;
31334
31401
  }
31335
31402
  }
31336
31403
 
@@ -31943,10 +32010,7 @@ const getRenderers = (function() {
31943
32010
  appendFragmentOutputs: programSetup.appendFragmentOutputs,
31944
32011
  cleanerEdges: programSetup.cleanerEdges,
31945
32012
  clipPos: clipPos,
31946
- clippableTest: (function() {
31947
- const vClippable = programVariables.createVarying("float", "vClippable", () => `${attributes.clippable} ? 1.0 : 0.0`, "flat");
31948
- return () => `${vClippable} > 0.0`;
31949
- })(),
32013
+ clippableTest: renderingAttributes.clippableTest,
31950
32014
  clippingCaps: programSetup.clippingCaps,
31951
32015
  crossSections: scene.crossSections,
31952
32016
  discardPoints: setupPoints && pointsMaterial.roundPoints,
@@ -33503,9 +33567,13 @@ const makeDTXRenderingAttributes = function(programVariables, isTriangle) {
33503
33567
  const colorsAndFlags = (offset) => perObjColsFlags(`ivec2(objectIndexCoords.x*8+${offset}, objectIndexCoords.y)`);
33504
33568
 
33505
33569
  return {
33570
+ clippableTest: (function() {
33571
+ const vClippable = programVariables.createVarying("uint", "vClippable", () => "flags2.r", "flat");
33572
+ return () => `${vClippable} > 0u`;
33573
+ })(),
33574
+
33506
33575
  geometryParameters: {
33507
33576
  attributes: {
33508
- clippable: "(flags2.r > 0u)",
33509
33577
  color: colorA,
33510
33578
  flags: iota$2(4).map(i => `int(flags[${i}])`),
33511
33579
  metallicRoughness: null,
@@ -33642,9 +33710,9 @@ function quantizePositions(positions, aabb, positionsDecodeMatrix) { // http://c
33642
33710
  const xmin = aabb[0];
33643
33711
  const ymin = aabb[1];
33644
33712
  const zmin = aabb[2];
33645
- const xwid = aabb[3] - xmin;
33646
- const ywid = aabb[4] - ymin;
33647
- const zwid = aabb[5] - zmin;
33713
+ const xwid = (aabb[3] - xmin) || 1;
33714
+ const ywid = (aabb[4] - ymin) || 1;
33715
+ const zwid = (aabb[5] - zmin) || 1;
33648
33716
  const maxInt = 65525;
33649
33717
  const xMultiplier = maxInt / xwid;
33650
33718
  const yMultiplier = maxInt / ywid;
@@ -33673,9 +33741,9 @@ function createPositionsDecodeMatrix(aabb, positionsDecodeMatrix) { // http://cg
33673
33741
  const xmin = aabb[0];
33674
33742
  const ymin = aabb[1];
33675
33743
  const zmin = aabb[2];
33676
- const xwid = aabb[3] - xmin;
33677
- const ywid = aabb[4] - ymin;
33678
- const zwid = aabb[5] - zmin;
33744
+ const xwid = (aabb[3] - xmin) || 1;
33745
+ const ywid = (aabb[4] - ymin) || 1;
33746
+ const zwid = (aabb[5] - zmin) || 1;
33679
33747
  const maxInt = 65525;
33680
33748
  math.identityMat4(translate$1);
33681
33749
  math.translationMat4v(aabb, translate$1);
@@ -34663,8 +34731,8 @@ class VBOLayer extends Layer {
34663
34731
  } else { // triangles
34664
34732
  if (subGeometry && subGeometry.vertices) {
34665
34733
  return drawPoints;
34666
- } else if (subGeometry && edgeIndicesBuf) {
34667
- return elementsDrawer(gl.LINES, edgeIndicesBuf);
34734
+ } else if (subGeometry) {
34735
+ return edgeIndicesBuf ? elementsDrawer(gl.LINES, edgeIndicesBuf) : (() => { });
34668
34736
  } else {
34669
34737
  return elementsDrawer(gl.TRIANGLES, indicesBuf);
34670
34738
  }
@@ -34746,9 +34814,13 @@ const makeVBORenderingAttributes = function(programVariables, instancing, entity
34746
34814
  return {
34747
34815
  dontCullOnAlphaZero: true,
34748
34816
 
34817
+ clippableTest: (function() {
34818
+ const vClippable = programVariables.createVarying("float", "vClippable", () => `${`((int(${attributes.flags}) >> 16 & 0xF) == 1)`} ? 1.0 : 0.0`); // Using `flat uint` for vClippable causes an instability - see XEOK-385
34819
+ return () => `${vClippable} != 0.0`;
34820
+ })(),
34821
+
34749
34822
  geometryParameters: {
34750
34823
  attributes: {
34751
- clippable: `((int(${attributes.flags}) >> 16 & 0xF) == 1)`,
34752
34824
  color: attributes.color,
34753
34825
  flags: iota$1(4).map(i => ({ toString: () => `(int(${attributes.flags}) >> ${i * 4} & 0xF)` })),
34754
34826
  metallicRoughness: attributes.metallicRoughness,
@@ -54014,7 +54086,7 @@ class SectionCaps {
54014
54086
  const visibleSceneModels = Object.values(scene.models).filter(sceneModel => (sceneModel.id in modelCaches) && sceneModel.visible);
54015
54087
  sectionPlanes.forEach((plane) => {
54016
54088
  if (plane.active) {
54017
- const sliceMesh = math.makeSectionPlaneSlicer(plane.pos, plane.quaternion);
54089
+ const sliceMesh = math.makeSectionPlaneSlicer(plane);
54018
54090
  visibleSceneModels.forEach(sceneModel => {
54019
54091
  const modelAABB = sceneModel.aabb;
54020
54092
  if (math.planeIntersectsAABB3(plane, modelAABB)) {
@@ -54034,10 +54106,11 @@ class SectionCaps {
54034
54106
  });
54035
54107
 
54036
54108
  entityCache.meshCaches.filter(meshCache => math.planeIntersectsAABB3(plane, meshCache.mesh.aabb)).forEach((meshCache, meshIdx) => {
54037
- sliceMesh(modelCenter, meshCache.meshIndices, meshCache.meshVertices).forEach((geo, geoIdx) => {
54109
+ const geo = sliceMesh({ origin: modelCenter, indices: meshCache.meshIndices, positions: meshCache.meshVertices }, { onlyPosSliceWithUV: true }).pos;
54110
+ if (geo) {
54038
54111
  entityCache.capMeshes.push(new Mesh(scene, {
54039
54112
  isObject: true,
54040
- id: `${plane.id}-${entityId}-${meshIdx}-${geoIdx}`,
54113
+ id: `${plane.id}-${entityId}-${meshIdx}`,
54041
54114
  material: entity.capMaterial,
54042
54115
  origin: math.addVec3(modelCenter, math.mulVec3Scalar(plane.dir, 0.001, tempVec3a$a), tempVec3a$a),
54043
54116
  geometry: new ReadableGeometry(scene, {
@@ -54048,7 +54121,7 @@ class SectionCaps {
54048
54121
  uv: geo.uv
54049
54122
  })
54050
54123
  }));
54051
- });
54124
+ }
54052
54125
  });
54053
54126
  }
54054
54127
  });
@@ -60460,7 +60533,7 @@ class CameraControl extends Component {
60460
60533
  *
60461
60534
  * See class docs for usage.
60462
60535
  *
60463
- * @param {{Number:Number}|String} value Either a set of new key mappings, or a string to select a keyboard layout,
60536
+ * @param {{Number:(Number | Number[])[]} | String} value Either a set of new key mappings, or a string to select a keyboard layout,
60464
60537
  * which causes ````CameraControl```` to use the default key mappings for that layout.
60465
60538
  */
60466
60539
  set keyMap(value) {
@@ -60539,7 +60612,7 @@ class CameraControl extends Component {
60539
60612
  /**
60540
60613
  * Gets custom mappings of keys to {@link CameraControl} actions.
60541
60614
  *
60542
- * @returns {{Number:Number}} Current key mappings.
60615
+ * @returns {{Number:(Number | Number[])[]}} Current key mappings.
60543
60616
  */
60544
60617
  get keyMap() {
60545
60618
  return this._keyMap;
@@ -131076,6 +131149,974 @@ class CxConverterIFCLoaderPlugin extends Plugin {
131076
131149
  }
131077
131150
  }
131078
131151
 
131152
+ /**
131153
+ * Default data access strategy for {@link IFCOpenShellLoaderPlugin}.
131154
+ *
131155
+ * This just loads assets using XMLHttpRequest.
131156
+ */
131157
+ class IFCOpenShellDefaultDataSource {
131158
+
131159
+ constructor(cfg = {}) {
131160
+ this.cacheBuster = (cfg.cacheBuster !== false);
131161
+ }
131162
+
131163
+ _cacheBusterURL(url) {
131164
+ if (!this.cacheBuster) {
131165
+ return url;
131166
+ }
131167
+ const timestamp = new Date().getTime();
131168
+ if (url.indexOf('?') > -1) {
131169
+ return url + '&_=' + timestamp;
131170
+ } else {
131171
+ return url + '?_=' + timestamp;
131172
+ }
131173
+ }
131174
+
131175
+ /**
131176
+ * Gets the contents of the given IFC file in an arraybuffer.
131177
+ *
131178
+ * @param {String|Number} src Path or ID of an IFC file.
131179
+ * @param {Function} ok Callback fired on success, argument is the IFC file in an arraybuffer.
131180
+ * @param {Function} error Callback fired on error.
131181
+ */
131182
+ getIFC(src, ok, error) {
131183
+ src = this._cacheBusterURL(src);
131184
+
131185
+ var defaultCallback = () => {
131186
+ };
131187
+ ok = ok || defaultCallback;
131188
+ error = error || defaultCallback;
131189
+ const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
131190
+ const dataUriRegexResult = src.match(dataUriRegex);
131191
+ if (dataUriRegexResult) { // Safari can't handle data URIs through XMLHttpRequest
131192
+ const isBase64 = !!dataUriRegexResult[2];
131193
+ var data = dataUriRegexResult[3];
131194
+ data = window.decodeURIComponent(data);
131195
+ if (isBase64) {
131196
+ data = window.atob(data);
131197
+ }
131198
+ try {
131199
+ const buffer = new ArrayBuffer(data.length);
131200
+ const view = new Uint8Array(buffer);
131201
+ for (var i = 0; i < data.length; i++) {
131202
+ view[i] = data.charCodeAt(i);
131203
+ }
131204
+ ok(buffer);
131205
+ } catch (errMsg) {
131206
+ error(errMsg);
131207
+ }
131208
+ } else {
131209
+ const request = new XMLHttpRequest();
131210
+ request.open('GET', src, true);
131211
+ request.responseType = 'text';
131212
+ request.onreadystatechange = function () {
131213
+ if (request.readyState === 4) {
131214
+ if (request.status === 200) {
131215
+ ok(request.response);
131216
+ } else {
131217
+ error('getIFC error : ' + request.response);
131218
+ }
131219
+ }
131220
+ };
131221
+ request.send(null);
131222
+ }
131223
+ }
131224
+ }
131225
+
131226
+ /**
131227
+ * {@link Viewer} plugin that uses [IfcOpenShell](https://ifcopenshell.org/) to load BIM models directly from IFC files.
131228
+ *
131229
+ * <a href="https://xeokit.github.io/xeokit-sdk/examples/index.html#BIMOffline_IFCOpenShellLoaderPlugin_Duplex"><img src="https://xeokit.io/img/docs/IFCOpenShellLoaderPlugin/IFCOpenShellLoaderPlugin.png"></a>
131230
+ *
131231
+ * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#BIMOffline_IFCOpenShellLoaderPlugin_Duplex)]
131232
+ *
131233
+ * ## Overview
131234
+ *
131235
+ * * Loads small-to-medium sized BIM models directly from IFC files.
131236
+ * * Uses [IfcOpenShell API](https://ifcopenshell.org/) to parse IFC files in the browser.
131237
+ * * Loads IFC geometry, element structure metadata, and property sets.
131238
+ * * Not for large models. For best performance with large models, we recommend using {@link XKTLoaderPlugin}.
131239
+ * * Loads double-precision coordinates, enabling models to be viewed at global coordinates without accuracy loss.
131240
+ * * Filter which IFC types don't get loaded.
131241
+ * * Configure initial appearances of specified IFC types.
131242
+ * * Set a custom data source for IFC files.
131243
+ *
131244
+ * ## Limitations
131245
+ *
131246
+ * Loading and parsing huge IFC STEP files can be slow, and can overwhelm the browser, however. To view your
131247
+ * largest IFC models, we recommend instead pre-converting those to xeokit's compressed native .XKT format, then
131248
+ * loading them with {@link XKTLoaderPlugin} instead.</p>
131249
+ *
131250
+ * ## Scene representation
131251
+ *
131252
+ * When loading a model, IFCOpenShellLoaderPlugin creates an {@link Entity} that represents the model, which
131253
+ * will have {@link Entity#isModel} set ````true```` and will be registered by {@link Entity#id}
131254
+ * in {@link Scene#models}. The IFCOpenShellLoaderPlugin also creates an {@link Entity} for each object within the
131255
+ * model. Those Entities will have {@link Entity#isObject} set ````true```` and will be registered
131256
+ * by {@link Entity#id} in {@link Scene#objects}.
131257
+ *
131258
+ * ## Metadata
131259
+ *
131260
+ * When loading a model, IFCOpenShellLoaderPlugin also creates a {@link MetaModel} that represents the model, which contains
131261
+ * a {@link MetaObject} for each IFC element, plus a {@link PropertySet} for each IFC property set. Loading metadata
131262
+ * can be very slow, so we can also optionally disable it if we don't need it.
131263
+ *
131264
+ * ## Usage
131265
+ *
131266
+ * In the example below we'll load the Duplex BIM model from
131267
+ * an [IFC file](https://github.com/xeokit/xeokit-sdk/tree/master/assets/models/ifc). Within our {@link Viewer}, this
131268
+ * will create a bunch of {@link Entity}s that represents the model and its objects, along with a {@link MetaModel},
131269
+ * {@link MetaObject}s and {@link PropertySet}s that hold their metadata.
131270
+ *
131271
+ * ````javascript
131272
+ * import {Viewer, IFCOpenShellLoaderPlugin, NavCubePlugin, TreeViewPlugin} from "../../dist/xeokit-sdk.es.js";
131273
+ *
131274
+ * //------------------------------------------------------------------------------------------------------------------
131275
+ * // 1. Create a Viewer,
131276
+ * // 2. Arrange the camera
131277
+ * //------------------------------------------------------------------------------------------------------------------
131278
+ *
131279
+ * // 1
131280
+ * const viewer = new Viewer({
131281
+ * canvasId: "myCanvas",
131282
+ * transparent: true
131283
+ * });
131284
+ *
131285
+ * // 2
131286
+ * viewer.camera.eye = [-3.933, 2.855, 27.018];
131287
+ * viewer.camera.look = [4.400, 3.724, 8.899];
131288
+ * viewer.camera.up = [-0.018, 0.999, 0.039];
131289
+ *
131290
+ * //------------------------------------------------------------------------------------------------------------------
131291
+ * // 1. Create the IFCOpenShellLoaderPlugin,
131292
+ * // 2. Load an IFC model
131293
+ * //------------------------------------------------------------------------------------------------------------------
131294
+ *
131295
+ * // 1
131296
+ *
131297
+ * const ifcLoader = new IFCOpenShellLoaderPlugin(viewer, {
131298
+ * workerSrc: "./my/directory/IFCOpenShellWorker.js",
131299
+ * ifcOpenShellURL: "./my/directory/ifcopenshell-0.8.3+34a1bc6-cp313-cp313-emscripten_4_0_9_wasm32.whl"
131300
+ * });
131301
+ *
131302
+ * // 2
131303
+ * const model = ifcLoader.load({ // Returns an Entity that represents the model
131304
+ * id: "myModel",
131305
+ * src: "../assets/models/ifc/Duplex.ifc",
131306
+ * excludeTypes: ["IfcSpace"],
131307
+ * edges: true
131308
+ * });
131309
+ *
131310
+ * model.on("loaded", () => {
131311
+ *
131312
+ * //----------------------------------------------------------------------------------------------------------
131313
+ * // 1. Find metadata on the bottom storey
131314
+ * // 2. X-ray all the objects except for the bottom storey
131315
+ * // 3. Fit the bottom storey in view
131316
+ * //----------------------------------------------------------------------------------------------------------
131317
+ *
131318
+ * // 1
131319
+ * const metaModel = viewer.metaScene.metaModels["myModel"]; // MetaModel with ID "myModel"
131320
+ * const metaObject
131321
+ * = viewer.metaScene.metaObjects["1xS3BCk291UvhgP2dvNsgp"]; // MetaObject with ID "1xS3BCk291UvhgP2dvNsgp"
131322
+ *
131323
+ * const name = metaObject.name; // "01 eerste verdieping"
131324
+ * const type = metaObject.type; // "IfcBuildingStorey"
131325
+ * const parent = metaObject.parent; // MetaObject with type "IfcBuilding"
131326
+ * const children = metaObject.children; // Array of child MetaObjects
131327
+ * const objectId = metaObject.id; // "1xS3BCk291UvhgP2dvNsgp"
131328
+ * const objectIds = viewer.metaScene.getObjectIDsInSubtree(objectId); // IDs of leaf sub-objects
131329
+ * const aabb = viewer.scene.getAABB(objectIds); // Axis-aligned boundary of the leaf sub-objects
131330
+ *
131331
+ * // 2
131332
+ * viewer.scene.setObjectsXRayed(viewer.scene.objectIds, true);
131333
+ * viewer.scene.setObjectsXRayed(objectIds, false);
131334
+ *
131335
+ * // 3
131336
+ * viewer.cameraFlight.flyTo(aabb);
131337
+ *
131338
+ * // Find the model Entity by ID
131339
+ * model = viewer.scene.models["myModel"];
131340
+ *
131341
+ * // Destroy the model
131342
+ * model.destroy();
131343
+ * });
131344
+ * ````
131345
+ *
131346
+ * ## Configuring a custom data source
131347
+ *
131348
+ * By default, IFCOpenShellLoaderPlugin will load IFC files over HTTP.
131349
+ *
131350
+ * In the example below, we'll customize the way IFCOpenShellLoaderPlugin loads the files by configuring it with our own data source
131351
+ * object. For simplicity, our custom data source example also uses HTTP, using a couple of xeokit utility functions.
131352
+ *
131353
+ * ````javascript
131354
+ * import {utils} from "xeokit-sdk.es.js";
131355
+ *
131356
+ * class MyDataSource {
131357
+ *
131358
+ * constructor() {
131359
+ * }
131360
+ *
131361
+ * // Gets the contents of the given IFC file in an arraybuffer
131362
+ * getIFC(src, ok, error) {
131363
+ * console.log("MyDataSource#getIFC(" + IFCSrc + ", ... )");
131364
+ * utils.loadArraybuffer(src,
131365
+ * (arraybuffer) => {
131366
+ * ok(arraybuffer);
131367
+ * },
131368
+ * function (errMsg) {
131369
+ * error(errMsg);
131370
+ * });
131371
+ * }
131372
+ * }
131373
+ *
131374
+ * const ifcLoader2 = new IFCOpenShellLoaderPlugin(viewer, {
131375
+ * dataSource: new MyDataSource()
131376
+ * });
131377
+ *
131378
+ * const model5 = ifcLoader2.load({
131379
+ * id: "myModel5",
131380
+ * src: "../assets/models/ifc/Duplex.ifc"
131381
+ * });
131382
+ * ````
131383
+ *
131384
+ * ## Loading multiple copies of a model, without object ID clashes
131385
+ *
131386
+ * Sometimes we need to load two or more instances of the same model, without having clashes
131387
+ * between the IDs of the equivalent objects in the model instances.
131388
+ *
131389
+ * As shown in the example below, we do this by setting {@link IFCOpenShellLoaderPlugin#globalizeObjectIds} ````true```` before we load our models.
131390
+ *
131391
+ * ````javascript
131392
+ * ifcLoader.globalizeObjectIds = true;
131393
+ *
131394
+ * const model = ifcLoader.load({
131395
+ * id: "model1",
131396
+ * src: "../assets/models/ifc/Duplex.ifc"
131397
+ * });
131398
+ *
131399
+ * const model2 = ifcLoader.load({
131400
+ * id: "model2",
131401
+ * src: "../assets/models/ifc/Duplex.ifc"
131402
+ * });
131403
+ * ````
131404
+ *
131405
+ * For each {@link Entity} loaded by these two calls, {@link Entity#id} and {@link MetaObject#id} will get prefixed by
131406
+ * the ID of their model, in order to avoid ID clashes between the two models.
131407
+ *
131408
+ * An Entity belonging to the first model will get an ID like this:
131409
+ *
131410
+ * ````
131411
+ * myModel1#0BTBFw6f90Nfh9rP1dlXrb
131412
+ * ````
131413
+ *
131414
+ * The equivalent Entity in the second model will get an ID like this:
131415
+ *
131416
+ * ````
131417
+ * myModel2#0BTBFw6f90Nfh9rP1dlXrb
131418
+ * ````
131419
+ *
131420
+ * Now, to update the visibility of both of those Entities collectively, using {@link Scene#setObjectsVisible}, we can
131421
+ * supply just the IFC product ID part to that method:
131422
+ *
131423
+ * ````javascript
131424
+ * myViewer.scene.setObjectVisibilities("0BTBFw6f90Nfh9rP1dlXrb", true);
131425
+ * ````
131426
+ *
131427
+ * The method, along with {@link Scene#setObjectsXRayed}, {@link Scene#setObjectsHighlighted} etc, will internally expand
131428
+ * the given ID to refer to the instances of that Entity in both models.
131429
+ *
131430
+ * We can also, of course, reference each Entity directly, using its globalized ID:
131431
+ *
131432
+ * ````javascript
131433
+ * myViewer.scene.setObjectVisibilities("myModel1#0BTBFw6f90Nfh9rP1dlXrb", true);
131434
+ *````
131435
+ *
131436
+ * @class IFCOpenShellLoaderPlugin
131437
+ * @since 2.6.90
131438
+ */
131439
+ class IFCOpenShellLoaderPlugin extends Plugin {
131440
+
131441
+ /**
131442
+ * @param {Viewer} viewer The {@link Viewer} that will own this plugin.
131443
+ * @param {Object} cfg Plugin configuration.
131444
+ * @param {String} [cfg.id="IFCOpenShellLoader"] Optional ID for this plugin instance.
131445
+ * @param {Object} [cfg.dataSource] Custom data source (defaults to {@link IFCOpenShellDefaultDataSource}).
131446
+ * @param {Object} cfg.ifcopenshell IfcOpenShell API object.
131447
+ * @param {Object} cfg.ifcopenshell_geom IfcOpenShell geometry API object.
131448
+ */
131449
+ constructor(viewer, cfg) {
131450
+
131451
+ super("IFCOpenShellLoader", viewer, cfg);
131452
+
131453
+ if (!cfg) {
131454
+ throw new Error("IFCOpenShellLoaderPlugin: No configuration given");
131455
+ }
131456
+
131457
+ if (!cfg.ifcopenshell) {
131458
+ throw new Error("IFCOpenShellLoaderPlugin: No ifcopenshell given");
131459
+ }
131460
+
131461
+ if (!cfg.ifcopenshell_geom) {
131462
+ throw new Error("IFCOpenShellLoaderPlugin: No ifcopenshell_geom given");
131463
+ }
131464
+
131465
+ this.ifcopenshell = cfg.ifcopenshell;
131466
+ this.ifcopenshell_geom = cfg.ifcopenshell_geom;
131467
+
131468
+ this.dataSource = cfg.dataSource;
131469
+ }
131470
+
131471
+ /**
131472
+ * Sets a custom data source for IFC files.
131473
+ * @param value
131474
+ */
131475
+ set dataSource(value) {
131476
+ this._dataSource = value || new IFCOpenShellDefaultDataSource();
131477
+ }
131478
+
131479
+ /**
131480
+ * Gets the data source for IFC files.
131481
+ * @returns {*|IFCOpenShellDefaultDataSource}
131482
+ */
131483
+ get dataSource() {
131484
+ return this._dataSource;
131485
+ }
131486
+
131487
+ /**
131488
+ * Gets whether IFCOpenShellLoaderPlugin globalizes each {@link Entity#id} and {@link MetaObject#id} as it loads a model.
131489
+ *
131490
+ * Default value is ````false````.
131491
+ *
131492
+ * @type {Boolean}
131493
+ */
131494
+ get globalizeObjectIds() {
131495
+ return this._globalizeObjectIds;
131496
+ }
131497
+
131498
+ /**
131499
+ * Sets whether IFCOpenShellLoaderPlugin globalizes each {@link Entity#id} and {@link MetaObject#id} as it loads a model.
131500
+ *
131501
+ * Set this ````true```` when you need to load multiple instances of the same model, to avoid ID clashes
131502
+ * between the objects in the different instances.
131503
+ *
131504
+ * When we load a model with this set ````true````, then each {@link Entity#id} and {@link MetaObject#id} will be
131505
+ * prefixed by the ID of the model, ie. ````<modelId>#<objectId>````.
131506
+ *
131507
+ * {@link Entity#originalSystemId} and {@link MetaObject#originalSystemId} will always hold the original, un-prefixed, ID values.
131508
+ *
131509
+ * Default value is ````false````.
131510
+ *
131511
+ * See the main {@link IFCOpenShellLoaderPlugin} class documentation for usage info.
131512
+ *
131513
+ * @type {Boolean}
131514
+ */
131515
+ set globalizeObjectIds(value) {
131516
+ this._globalizeObjectIds = !!value;
131517
+ }
131518
+
131519
+ /**
131520
+ * Loads an IFC model from a file or text into the {@link Viewer}.
131521
+ *
131522
+ * @param {Object} params
131523
+ * @param {String} [params.id] Optional root Entity ID.
131524
+ * @param {String} [params.src] IFC file path (alternative to `text`).
131525
+ * @param {String} [params.text] IFC text (alternative to `src`).
131526
+ * @param {{String:Object}} [params.objectDefaults]
131527
+ * @param {String[]} [params.excludeTypes] Array of IFC types to exclude.
131528
+ * @param {Number[]} [params.origin=[0,0,0]] Optional World-coordinate origin to apply to the model.
131529
+ * @param {Number[]} [params.position=[0,0,0]] Optional position offset to apply to the model.
131530
+ * @param {Number[]} [params.rotation=[0,0,0]] Optional XYZ Euler rotation (degrees) to apply to the model.
131531
+ * @param {Boolean} [params.backfaces=true] Whether to render backfaces.
131532
+ * @param {Boolean} [params.dtxEnabled=true] Whether to enable data texture storage for geometry buffers.
131533
+ * @param {Boolean} [params.loadMetadata=true] Whether to load metadata.
131534
+ * @param {Boolean} [params.loadMetadataPropertySets=true] Whether to load property sets within the metadata. Only works when `loadMetadata` is true.
131535
+ * @param {Boolean} [params.edges=false] Whether to generate edge lines for the model.
131536
+ * @param {Boolean} [params.saoEnabled=false] Whether to enable SAO for the model.
131537
+ * @param {Boolean} [params.globalizeObjectIds=false] Whether to globalize each {@link Entity#id} and {@link MetaObject#id} as it loads the model.
131538
+ * @returns {Entity}
131539
+ */
131540
+ async load(params = {}) {
131541
+
131542
+ let {
131543
+ id,
131544
+ backfaces = true,
131545
+ dtxEnabled = true,
131546
+ position,
131547
+ rotation,
131548
+ origin,
131549
+ loadMetadata,
131550
+ loadMetadataPropertySets,
131551
+ edges,
131552
+ saoEnabled,
131553
+ globalizeObjectIds,
131554
+ excludeTypes
131555
+ } = params;
131556
+
131557
+ if (id && this.viewer.scene.components[id]) {
131558
+ this.error(`Component with this ID already exists: ${id} - autogenerating SceneModel ID`);
131559
+ id = null;
131560
+ }
131561
+
131562
+ const sceneModel = new SceneModel(this.viewer.scene, {
131563
+ id,
131564
+ isModel: true,
131565
+ globalizeObjectIds,
131566
+ backfaces,
131567
+ dtxEnabled,
131568
+ position,
131569
+ rotation,
131570
+ origin,
131571
+ edges,
131572
+ saoEnabled
131573
+ });
131574
+
131575
+ const modelId = sceneModel.id;
131576
+
131577
+ if (!params.src && !params.text) {
131578
+ this.error("load() expected 'src' or 'text'");
131579
+ return sceneModel; // Return empty model
131580
+ }
131581
+
131582
+ const spinner = this.viewer.scene.canvas.spinner;
131583
+ spinner.processes++;
131584
+
131585
+ const loadIFC = (fileData) => {
131586
+ const ifc = this.ifcopenshell.file.from_string(fileData);
131587
+ const ctx = {
131588
+ loadMetadataPropertySets: (loadMetadataPropertySets !== false),
131589
+ globalizeObjectIds: globalizeObjectIds || this._globalizeObjectIds,
131590
+ geometryCache: new Map(),
131591
+ ifc,
131592
+ sceneModel
131593
+ };
131594
+ if (excludeTypes) {
131595
+ ctx.excludeTypes = excludeTypes;
131596
+ }
131597
+ this._loadIFCGeometry(ctx);
131598
+ if (loadMetadata !== false) {
131599
+ const metaModelData = this._loadIFCMetaModel(ctx, ifc);
131600
+ this.viewer.metaScene.createMetaModel(modelId, metaModelData);
131601
+ }
131602
+ this.viewer.scene.canvas.spinner.processes--;
131603
+ };
131604
+
131605
+ if (params.src) {
131606
+ this.viewer.scene.canvas.spinner.processes++;
131607
+ this._dataSource.getIFC(
131608
+ params.src,
131609
+ (fileData) => {
131610
+ loadIFC(fileData);
131611
+ this.viewer.scene.canvas.spinner.processes--;
131612
+ },
131613
+ (err) => {
131614
+ this.viewer.scene.canvas.spinner.processes--;
131615
+ this.error(err);
131616
+ }
131617
+ );
131618
+ } else {
131619
+ loadIFC(params.text);
131620
+ }
131621
+
131622
+ sceneModel.once("destroyed", () => {
131623
+ this.viewer.metaScene.destroyMetaModel(modelId);
131624
+ });
131625
+
131626
+ return sceneModel;
131627
+ }
131628
+
131629
+ _loadIFCGeometry(ctx) {
131630
+ const {ifc, sceneModel} = ctx;
131631
+ const {ifcopenshell_geom} = this;
131632
+
131633
+ const settings = ifcopenshell_geom.settings();
131634
+ settings.set(settings.WELD_VERTICES, false);
131635
+
131636
+ const iterator = ifcopenshell_geom.iterator.callKwargs({
131637
+ settings,
131638
+ file_or_filename: ifc,
131639
+ exclude: ctx.excludeTypes,
131640
+ geometry_library: "hybrid-cgal-simple-opencascade"
131641
+ });
131642
+
131643
+ if (iterator.initialize()) {
131644
+ do {
131645
+ const obj = iterator.get();
131646
+ if (obj) {
131647
+ const entity = ifc.by_id(obj.id);
131648
+ this._parseIFCEntity(ctx, obj, entity);
131649
+ }
131650
+ } while (iterator.next());
131651
+ }
131652
+
131653
+ sceneModel.finalize();
131654
+
131655
+ sceneModel.scene.once("tick", () => {
131656
+ if (!sceneModel.destroyed) {
131657
+ sceneModel.scene.fire("modelLoaded", sceneModel.id);
131658
+ sceneModel.fire("loaded", true, false);
131659
+ }
131660
+ });
131661
+ }
131662
+
131663
+ _parseIFCEntity(ctx, obj, ifcEntity) {
131664
+ const {sceneModel, geometryCache} = ctx;
131665
+ const geometry_id = obj.geometry.id;
131666
+
131667
+ const M = obj.transformation.data().components.toJs();
131668
+ const {origin, matrix} = extractRTCTransform(M);
131669
+
131670
+ if (!geometryCache.get(geometry_id)) {
131671
+
131672
+ const srcMaterials = obj.geometry.materials.toJs();
131673
+ const materials = srcMaterials.map((m) => ({
131674
+ diffuse: m.diffuse.components.toJs(),
131675
+ transparency: (m.transparency
131676
+ && !isNaN(m.transparency)) ? m.transparency : 0.0,
131677
+ }));
131678
+
131679
+ const materialIds = new Int32Array(obj.geometry.material_ids.toJs());
131680
+
131681
+ // Build mapping: materialIndex -> [faceIdx...]
131682
+ const mapping = buildMaterialMapping(materialIds);
131683
+
131684
+ // Create sub-geometry per materialIndex, once
131685
+ const subGeoms = new Map();
131686
+
131687
+ for (const [matIndexStr, faceList] of Object.entries(mapping)) {
131688
+
131689
+ if (!faceList || faceList.length === 0) {
131690
+ continue;
131691
+ }
131692
+
131693
+ // xeokit auto-generates normals on the GPU side
131694
+
131695
+ const positions = new Float32Array(obj.geometry.verts.toJs());
131696
+ const edgeIndices = new Uint32Array(obj.geometry.edges.toJs());
131697
+ const faces = new Uint32Array(obj.geometry.faces.toJs());
131698
+ const matIndex = Number(matIndexStr);
131699
+ const indices = buildIndicesForFaces(faceList, faces);
131700
+ const sceneGeometryId = makeSubGeometryId(geometry_id, matIndex); // deterministic
131701
+
131702
+ sceneModel.createGeometry({
131703
+ id: sceneGeometryId,
131704
+ primitive: "triangles",
131705
+ positions,
131706
+ indices,
131707
+ edgeIndices
131708
+ });
131709
+
131710
+ subGeoms.set(matIndex, sceneGeometryId);
131711
+ }
131712
+
131713
+ geometryCache.set(geometry_id, {
131714
+ subGeoms,
131715
+ materials,
131716
+ // store mapping as Map<number, Uint32Array> to avoid recomputing
131717
+ mapping: new Map(Object.entries(mapping).map(
131718
+ ([k, v]) => [Number(k), new Uint32Array(v)]
131719
+ ))
131720
+ });
131721
+ }
131722
+
131723
+ // Reuse cached sub-geometries to create per-object meshes
131724
+
131725
+ const cached = geometryCache.get(geometry_id);
131726
+ const meshIds = [];
131727
+
131728
+ for (const [matIndex, sceneGeometryId] of cached.subGeoms.entries()) {
131729
+ const material = cached.materials[matIndex] || {diffuse: [0.6, 0.6, 0.6], transparency: 0.0};
131730
+ const meshId = generateUUID();
131731
+ const diffuse = material.diffuse;
131732
+ sceneModel.createMesh({
131733
+ id: meshId,
131734
+ geometryId: sceneGeometryId,
131735
+ origin,
131736
+ matrix,
131737
+ color: [diffuse[0], diffuse[1], diffuse[2]],
131738
+ opacity: 1.0 - material.transparency
131739
+ });
131740
+ meshIds.push(meshId);
131741
+ }
131742
+
131743
+ sceneModel.createEntity({
131744
+ id: ctx.globalizeObjectIds ? math.globalizeObjectId(ctx.sceneModel.id, ifcEntity.GlobalId) : ifcEntity.GlobalId,
131745
+ isObject: true,
131746
+ meshIds
131747
+ });
131748
+ }
131749
+
131750
+ _loadIFCMetaModel(ctx, ifc) {
131751
+
131752
+ const visited = new Set();
131753
+ const metaObjects = [];
131754
+ const propertySets = [];
131755
+
131756
+ // ---- helpers -----------------------------------------------------------
131757
+ const toStr = (v) => (v === undefined || v === null) ? "" : String(v);
131758
+
131759
+ function getGlobalId(entity) {
131760
+ try {
131761
+ return String(entity.GlobalId);
131762
+ } catch {
131763
+ return null;
131764
+ }
131765
+ }
131766
+
131767
+ function addNode(entity, parent) {
131768
+ const id = getGlobalId(entity);
131769
+ if (!id || visited.has(id)) return false;
131770
+ visited.add(id);
131771
+ const globalizeObjectIds = ctx.globalizeObjectIds;
131772
+ const modelId = ctx.sceneModel.id;
131773
+ const propertySetIds = [];
131774
+ if (ctx.loadMetadataPropertySets) {
131775
+ // Try all possible association fields
131776
+ const associationFields = ["HasAssociations", "IsDefinedBy", "IsDecomposedBy", "ContainsElements"];
131777
+ for (const field of associationFields) {
131778
+ const associations = entity[field];
131779
+ if (associations && associations.length > 0) {
131780
+ for (let j = 0; j < associations.length; j++) {
131781
+ const rel = associations.get(j);
131782
+ if (rel.is_a && rel.is_a() === "IfcRelDefinesByProperties") {
131783
+ const propSet = rel.RelatingPropertyDefinition;
131784
+ if (propSet && propSet.is_a) {
131785
+ // Accept both IfcPropertySet and IfcElementQuantity
131786
+ if (["IfcPropertySet", "IfcElementQuantity"].includes(propSet.is_a())) {
131787
+ const propSetId = propSet.GlobalId ? String(propSet.GlobalId) : null;
131788
+ const propSetName = propSet.Name ? String(propSet.Name) : "";
131789
+ const propSetType = propSet.is_a ? String(propSet.is_a()) : "";
131790
+ const properties = [];
131791
+ const props = propSet.HasProperties || propSet.Quantities;
131792
+ if (props && props.length > 0) {
131793
+ for (let k = 0; k < props.length; k++) {
131794
+ const p = props.get(k);
131795
+ const propName = p.Name ? String(p.Name) : "";
131796
+ let propValue = "";
131797
+ let propType = p.is_a ? String(p.is_a()) : "";
131798
+ if (p.is_a && p.is_a() === "IfcPropertySingleValue") {
131799
+ try {
131800
+ propValue = p.NominalValue ? String(p.NominalValue.wrappedValue) : "";
131801
+ } catch {
131802
+ propValue = "";
131803
+ }
131804
+ } else if (p.is_a && p.is_a() === "IfcPropertyEnumeratedValue") {
131805
+ try {
131806
+ const values = p.EnumerationValues;
131807
+ if (values && values.length > 0) {
131808
+ const arr = [];
131809
+ for (let vi = 0; vi < values.length; vi++) {
131810
+ arr.push(String(values.get(vi).wrappedValue));
131811
+ }
131812
+ propValue = arr.join(", ");
131813
+ }
131814
+ } catch {
131815
+ propValue = "";
131816
+ }
131817
+ } else if (p.is_a && p.is_a() === "IfcQuantityArea") {
131818
+ propValue = p.AreaValue ? String(p.AreaValue) : "";
131819
+ } else if (p.is_a && p.is_a() === "IfcQuantityLength") {
131820
+ propValue = p.LengthValue ? String(p.LengthValue) : "";
131821
+ } else if (p.is_a && p.is_a() === "IfcQuantityVolume") {
131822
+ propValue = p.VolumeValue ? String(p.VolumeValue) : "";
131823
+ } else {
131824
+ try {
131825
+ propValue = p.NominalValue ? String(p.NominalValue) : "";
131826
+ } catch {
131827
+ propValue = "";
131828
+ }
131829
+ }
131830
+ properties.push({
131831
+ name: propName,
131832
+ value: propValue,
131833
+ type: propType
131834
+ });
131835
+ p.destroy?.();
131836
+ }
131837
+ props.destroy?.();
131838
+ }
131839
+ propertySets.push({
131840
+ id: propSetId,
131841
+ // objectId: ctx.globalizeObjectIds ? math.globalizeObjectId(ctx.sceneModel.id, objectId) : objectId,
131842
+ name: propSetName,
131843
+ type: propSetType,
131844
+ properties
131845
+ });
131846
+ propertySetIds.push(propSetId);
131847
+ propSet.destroy?.();
131848
+ }
131849
+ }
131850
+ rel.destroy?.();
131851
+ }
131852
+ }
131853
+ associations.destroy?.();
131854
+ }
131855
+ }
131856
+ }
131857
+ metaObjects.push({
131858
+ id: globalizeObjectIds ? math.globalizeObjectId(modelId, id) : id,
131859
+ type: String(entity.is_a()),
131860
+ parent: parent
131861
+ ? (globalizeObjectIds
131862
+ ? math.globalizeObjectId(modelId, getGlobalId(parent))
131863
+ : getGlobalId(parent))
131864
+ : null,
131865
+ propertySetIds
131866
+ });
131867
+ return true;
131868
+ }
131869
+
131870
+ function walk(entity, parent = null) {
131871
+ if (!entity) return;
131872
+
131873
+ // add current node (skip children if already visited)
131874
+ if (!addNode(entity, parent)) {
131875
+ entity.destroy?.();
131876
+ return;
131877
+ }
131878
+
131879
+ // 1) Decomposition (IfcRelAggregates / IfcRelNests)
131880
+ const decos = entity.IsDecomposedBy;
131881
+ if (decos) {
131882
+ for (let i = 0; i < decos.length; i++) {
131883
+ const rel = decos.get(i);
131884
+ const children = rel.RelatedObjects;
131885
+ if (children) {
131886
+ for (let j = 0; j < children.length; j++) {
131887
+ const child = children.get(j);
131888
+ walk(child, entity);
131889
+ child.destroy?.();
131890
+ }
131891
+ children.destroy?.();
131892
+ }
131893
+ rel.destroy?.();
131894
+ }
131895
+ }
131896
+
131897
+ // 2) Spatial containment (IfcRelContainedInSpatialStructure)
131898
+ const contains = entity.ContainsElements;
131899
+ if (contains) {
131900
+ for (let i = 0; i < contains.length; i++) {
131901
+ const rel = contains.get(i);
131902
+ const elems = rel.RelatedElements;
131903
+ if (elems) {
131904
+ for (let j = 0; j < elems.length; j++) {
131905
+ const elem = elems.get(j);
131906
+ walk(elem, entity);
131907
+ elem.destroy?.();
131908
+ }
131909
+ elems.destroy?.();
131910
+ }
131911
+ rel.destroy?.();
131912
+ }
131913
+ }
131914
+
131915
+ entity.destroy?.();
131916
+ }
131917
+
131918
+ // ---- metadata extraction ----------------------------------------------
131919
+ let projectId = "";
131920
+ let author = "";
131921
+ let createdAt = ""; // ISO string
131922
+ let schema = "";
131923
+ let creatingApplication = "";
131924
+
131925
+ // Schema (primary)
131926
+ try {
131927
+ // ifcopenshell.file usually exposes a `schema` string property
131928
+ schema = toStr(ifc.schema);
131929
+ } catch {
131930
+ }
131931
+ if (!schema) {
131932
+ // Fallback to STEP header
131933
+ try {
131934
+ const ids = ifc.wrapped_data.header.file_schema.schema_identifiers;
131935
+ if (ids && ids.length > 0) schema = toStr(ids.get(0));
131936
+ } catch {
131937
+ }
131938
+ }
131939
+
131940
+ // Project (also gives us OwnerHistory on many files)
131941
+ const projects = ifc.by_type("IfcProject");
131942
+ if (projects && projects.length > 0) {
131943
+ const project = projects.get(0);
131944
+ projectId = toStr(project.GlobalId);
131945
+
131946
+ // OwnerHistory path (preferred when present)
131947
+ try {
131948
+ const oh = project.OwnerHistory; // deprecated in newer IFC4.x, but present in many files
131949
+ if (oh) {
131950
+ // Author: IfcPersonAndOrganization → ThePerson (GivenName/FamilyName) and TheOrganization.Name
131951
+ try {
131952
+ const user = oh.OwningUser;
131953
+ const person = user?.ThePerson;
131954
+ const org = user?.TheOrganization;
131955
+ const gn = person?.GivenName ? toStr(person.GivenName) : "";
131956
+ const fn = person?.FamilyName ? toStr(person.FamilyName) : "";
131957
+ const personName = (gn || fn) ? [gn, fn].filter(Boolean).join(" ") : "";
131958
+ const orgName = org?.Name ? toStr(org.Name) : "";
131959
+ author = [personName, orgName].filter(Boolean).join(" / ");
131960
+ } catch {
131961
+ }
131962
+
131963
+ // Creation time (UNIX seconds)
131964
+ try {
131965
+ const ts = oh?.CreationDate;
131966
+ if (typeof ts === "number" && isFinite(ts) && ts > 0) {
131967
+ createdAt = new Date(ts * 1000).toISOString();
131968
+ }
131969
+ } catch {
131970
+ }
131971
+
131972
+ // Creating application
131973
+ try {
131974
+ const app = oh?.OwningApplication;
131975
+ const appName =
131976
+ app?.ApplicationFullName ? toStr(app.ApplicationFullName) :
131977
+ app?.ApplicationIdentifier ? toStr(app.ApplicationIdentifier) : "";
131978
+ const appVer = app?.Version ? toStr(app.Version) : "";
131979
+ creatingApplication = [appName, appVer].filter(Boolean).join(" ");
131980
+ } catch {
131981
+ }
131982
+ }
131983
+ } catch {
131984
+ }
131985
+
131986
+ // Clean first project (we’ll traverse below with a fresh pointer anyway)
131987
+ project.destroy?.();
131988
+ }
131989
+
131990
+ // Fallbacks via STEP header if OwnerHistory wasn’t there / incomplete
131991
+ try {
131992
+ const fileName = ifc.wrapped_data.header.file_name;
131993
+ if (!author) {
131994
+ try {
131995
+ const authors = fileName.author;
131996
+ if (authors && authors.length > 0) {
131997
+ // `author` is a LIST in the STEP header; join if multiple
131998
+ const parts = [];
131999
+ for (let i = 0; i < authors.length; i++) parts.push(toStr(authors.get(i)));
132000
+ author = parts.filter(Boolean).join(", ");
132001
+ }
132002
+ } catch {
132003
+ }
132004
+ }
132005
+ if (!createdAt) {
132006
+ const ts = toStr(fileName.time_stamp); // already a string like "2023-08-10T12:34:56"
132007
+ if (ts) {
132008
+ // normalize to ISO if possible
132009
+ const maybe = new Date(ts);
132010
+ if (!isNaN(maybe.getTime())) createdAt = maybe.toISOString();
132011
+ }
132012
+ }
132013
+ if (!creatingApplication) {
132014
+ // STEP header carries "originating_system" and "preprocessor_version"
132015
+ const orig = toStr(fileName.originating_system);
132016
+ const prep = toStr(fileName.preprocessor_version);
132017
+ creatingApplication = [orig, prep].filter(Boolean).join(" / ");
132018
+ }
132019
+ } catch {
132020
+ }
132021
+
132022
+ // If createdAt still missing, sweep for earliest OwnerHistory timestamp across roots
132023
+ if (!createdAt) {
132024
+ try {
132025
+ let minTs = Infinity;
132026
+ const roots = ifc.by_type("IfcRoot");
132027
+ for (let i = 0; i < roots.length; i++) {
132028
+ const r = roots.get(i);
132029
+ const oh = r?.OwnerHistory;
132030
+ const ts = oh?.CreationDate;
132031
+ if (typeof ts === "number" && isFinite(ts) && ts > 0 && ts < minTs) {
132032
+ minTs = ts;
132033
+ }
132034
+ r.destroy?.();
132035
+ }
132036
+ roots.destroy?.();
132037
+ if (isFinite(minTs)) createdAt = new Date(minTs * 1000).toISOString();
132038
+ } catch {
132039
+ }
132040
+ }
132041
+
132042
+ // ---- hierarchy walk ----------------------------------------------------
132043
+ // Re-query projects since we destroyed the first pointer above
132044
+ const projects2 = ifc.by_type("IfcProject");
132045
+ for (let i = 0; i < projects2.length; i++) {
132046
+ const project = projects2.get(i);
132047
+ walk(project, null);
132048
+ project.destroy?.();
132049
+ }
132050
+ projects2.destroy?.();
132051
+
132052
+ return {
132053
+ id: "",
132054
+ projectId,
132055
+ author,
132056
+ createdAt,
132057
+ schema,
132058
+ creatingApplication,
132059
+ metaObjects,
132060
+ propertySets
132061
+ };
132062
+ }
132063
+
132064
+ /**
132065
+ * Destroys this IFCOpenShellLoaderPlugin instance.
132066
+ */
132067
+ destroy() {
132068
+ super.destroy();
132069
+ }
132070
+ }
132071
+
132072
+ function makeSubGeometryId(geometry_id, matIndex) {
132073
+ return `${geometry_id}:${matIndex}#geom`;
132074
+ }
132075
+
132076
+ function buildMaterialMapping(materialIds) {
132077
+ const mapping = {};
132078
+ for (let faceIdx = 0; faceIdx < materialIds.length; faceIdx++) {
132079
+ const materialId = materialIds[faceIdx];
132080
+ if (materialId == null || materialId < 0) continue; // skip invalid / missing
132081
+ (mapping[materialId] ||= []).push(faceIdx);
132082
+ }
132083
+ return mapping;
132084
+ }
132085
+
132086
+ function buildIndicesForFaces(faceList, faceIndices) {
132087
+ const indices = new Uint32Array(faceList.length * 3);
132088
+ let k = 0;
132089
+ for (const faceIdx of faceList) {
132090
+ const base = faceIdx * 3;
132091
+ indices[k++] = faceIndices[base + 0];
132092
+ indices[k++] = faceIndices[base + 1];
132093
+ indices[k++] = faceIndices[base + 2];
132094
+ }
132095
+ return indices;
132096
+ }
132097
+
132098
+ function generateUUID() {
132099
+ return Math.random().toString(36).substr(2, 9);
132100
+ }
132101
+
132102
+ function extractRTCTransform(transform) {
132103
+ const matrix = flattenMatrixArray(transform);
132104
+ const origin = [];
132105
+ const worldOrigin = matrix.slice(12, 15); // translation xyz
132106
+ worldToRTCPositions(worldOrigin, worldOrigin, origin);
132107
+ matrix.set(worldOrigin, 12);
132108
+ return {origin, matrix};
132109
+ }
132110
+
132111
+ function flattenMatrixArray(m) {
132112
+ return new Float64Array([
132113
+ m[0][0], m[2][0], -m[1][0], m[3][0],
132114
+ m[0][1], m[2][1], -m[1][1], m[3][1],
132115
+ m[0][2], m[2][2], -m[1][2], m[3][2],
132116
+ m[0][3], m[2][3], -m[1][3], m[3][3]
132117
+ ]);
132118
+ }
132119
+
131079
132120
  /**
131080
132121
  * Default data access strategy for {@link LASLoaderPlugin}.
131081
132122
  */
@@ -135166,6 +136207,8 @@ exports.GIFMediaType = GIFMediaType;
135166
136207
  exports.GLTFDefaultDataSource = GLTFDefaultDataSource;
135167
136208
  exports.GLTFLoaderPlugin = GLTFLoaderPlugin;
135168
136209
  exports.HalfFloatType = HalfFloatType;
136210
+ exports.IFCOpenShellDefaultDataSource = IFCOpenShellDefaultDataSource;
136211
+ exports.IFCOpenShellLoaderPlugin = IFCOpenShellLoaderPlugin;
135169
136212
  exports.ImagePlane = ImagePlane;
135170
136213
  exports.IntType = IntType;
135171
136214
  exports.JPEGMediaType = JPEGMediaType;