@xeokit/xeokit-sdk 2.6.72 → 2.6.75

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.
Files changed (60) hide show
  1. package/dist/xeokit-sdk.cjs.js +985 -400
  2. package/dist/xeokit-sdk.es.js +980 -401
  3. package/dist/xeokit-sdk.es5.js +734 -714
  4. package/dist/xeokit-sdk.min.cjs.js +8 -8
  5. package/dist/xeokit-sdk.min.es.js +8 -8
  6. package/dist/xeokit-sdk.min.es5.js +7 -7
  7. package/package.json +1 -1
  8. package/src/extras/ContextMenu/ContextMenu.js +25 -16
  9. package/src/plugins/FastNavPlugin/FastNavPlugin.js +10 -0
  10. package/src/plugins/SkyboxesPlugin/SkyboxesPlugin.js +24 -49
  11. package/src/plugins/TreeViewPlugin/RenderService.js +13 -4
  12. package/src/plugins/TreeViewPlugin/TreeViewPlugin.js +1 -1
  13. package/src/viewer/Viewer.js +0 -16
  14. package/src/viewer/scene/CameraControl/lib/controllers/PivotController.js +29 -8
  15. package/src/viewer/scene/LineSet/LineSet.js +7 -2
  16. package/src/viewer/scene/skybox/Skybox.js +110 -9
  17. package/types/plugins/SkyboxesPlugin/SkyboxesPlugin.d.ts +18 -4
  18. package/types/viewer/scene/CameraControl/CameraControl.d.ts +168 -0
  19. package/types/viewer/scene/LineSet/LineSet.d.ts +1 -1
  20. package/types/viewer/scene/camera/CameraPath.d.ts +86 -6
  21. package/types/viewer/scene/camera/CameraPathAnimation.d.ts +2 -0
  22. package/types/viewer/scene/geometry/ReadableGeometry.d.ts +14 -0
  23. package/types/viewer/scene/geometry/VBOGeometry.d.ts +2 -0
  24. package/types/viewer/scene/geometry/index.d.ts +2 -1
  25. package/types/viewer/scene/geometry/loaders/index.d.ts +2 -0
  26. package/types/viewer/scene/geometry/loaders/load3DSGeometry.d.ts +17 -0
  27. package/types/viewer/scene/geometry/loaders/loadOBJGeometry.d.ts +18 -0
  28. package/types/viewer/scene/index.d.ts +2 -0
  29. package/types/viewer/scene/input/input.d.ts +117 -0
  30. package/types/viewer/scene/lights/AmbientLight.d.ts +41 -0
  31. package/types/viewer/scene/lights/CubeTexture.d.ts +27 -0
  32. package/types/viewer/scene/lights/DirLight.d.ts +77 -0
  33. package/types/viewer/scene/lights/LightMap.d.ts +27 -0
  34. package/types/viewer/scene/lights/PointLight.d.ts +135 -0
  35. package/types/viewer/scene/lights/ReflectionMap.d.ts +26 -0
  36. package/types/viewer/scene/lights/index.d.ts +4 -1
  37. package/types/viewer/scene/marker/Marker.d.ts +5 -0
  38. package/types/viewer/scene/materials/EmphasisMaterial.d.ts +5 -0
  39. package/types/viewer/scene/materials/Fresnel.d.ts +25 -62
  40. package/types/viewer/scene/materials/LambertMaterial.d.ts +192 -0
  41. package/types/viewer/scene/materials/MetallicMaterial.d.ts +359 -0
  42. package/types/viewer/scene/materials/PhongMaterial.d.ts +5 -0
  43. package/types/viewer/scene/materials/PointsMaterial.d.ts +5 -0
  44. package/types/viewer/scene/materials/SpecularMaterial.d.ts +342 -0
  45. package/types/viewer/scene/materials/Texture.d.ts +36 -78
  46. package/types/viewer/scene/materials/index.d.ts +2 -0
  47. package/types/viewer/scene/models/SceneModel.d.ts +134 -7
  48. package/types/viewer/scene/models/SceneModelEntity.d.ts +65 -0
  49. package/types/viewer/scene/models/SceneModelMesh.d.ts +12 -0
  50. package/types/viewer/scene/paths/CubicBezierCurve.d.ts +146 -0
  51. package/types/viewer/scene/paths/Curve.d.ts +75 -0
  52. package/types/viewer/scene/paths/Path.d.ts +119 -0
  53. package/types/viewer/scene/paths/QuadraticBezierCurve.d.ts +124 -0
  54. package/types/viewer/scene/paths/SplineCurve.d.ts +84 -0
  55. package/types/viewer/scene/paths/index.d.ts +5 -0
  56. package/types/viewer/scene/scene/Scene.d.ts +102 -0
  57. package/types/viewer/scene/sectionPlane/SectionPlane.d.ts +41 -0
  58. package/types/viewer/scene/skybox/Skybox.d.ts +59 -0
  59. package/types/viewer/scene/skybox/index.d.ts +1 -0
  60. package/types/viewer/scene/webgl/PickResult.d.ts +19 -0
@@ -1,7 +1,7 @@
1
1
  /**
2
- * xeokit-sdk v2.6.71
3
- * Commit: 7443626ab66b3eab0018b1ed0ee05d3bfa9f8eae
4
- * Built: 2025-03-21T09:02:29.217Z
2
+ * xeokit-sdk v2.6.75
3
+ * Commit: c9b6b9ca6ba5bea44b4e84678d4f9ffbf152086d
4
+ * Built: 2025-04-17T10:44:28.149Z
5
5
  */
6
6
 
7
7
  'use strict';
@@ -334,6 +334,7 @@ class ContextMenu {
334
334
  * @param {Boolean} [cfg.enabled=true] Whether this ````ContextMenu```` is initially enabled. {@link ContextMenu#show} does nothing while this is ````false````.
335
335
  * @param {Boolean} [cfg.hideOnMouseDown=true] Whether this ````ContextMenu```` automatically hides whenever we mouse-down or tap anywhere in the page.
336
336
  * @param {Boolean} [cfg.hideOnAction=true] Whether this ````ContextMenu```` automatically hides after we select a menu item. Se false if we want the menu to remain shown and show any updates to its item titles, after we've selected an item.
337
+ * @param {Node | undefined} [cfg.parentNode] Optional reference to an existing DOM Node (e.g. ShadowRoot), to which the menu's HTML element will be appended, defaults to ````document.body````.
337
338
  */
338
339
  constructor(cfg = {}) {
339
340
 
@@ -348,7 +349,9 @@ class ContextMenu {
348
349
  this._itemMap = {}; // Items mapped to their IDs
349
350
  this._shown = false; // True when the ContextMenu is visible
350
351
  this._nextId = 0;
351
-
352
+ this._parentNode = cfg.parentNode || document.body;
353
+ this._offsetParent = (this._parentNode instanceof ShadowRoot) ? this._parentNode.host : this._parentNode;
354
+
352
355
  /**
353
356
  * Subscriptions to events fired at this ContextMenu.
354
357
  * @private
@@ -356,12 +359,12 @@ class ContextMenu {
356
359
  this._eventSubs = {};
357
360
 
358
361
  if (cfg.hideOnMouseDown !== false) {
359
- document.addEventListener("mousedown", (event) => {
362
+ this._parentNode.addEventListener("mousedown", (event) => {
360
363
  if (!event.target.classList.contains("xeokit-context-menu-item")) {
361
364
  this.hide();
362
365
  }
363
366
  });
364
- document.addEventListener("touchstart", this._canvasTouchStartHandler = (event) => {
367
+ this._parentNode.addEventListener("touchstart", this._canvasTouchStartHandler = (event) => {
365
368
  if (!event.target.classList.contains("xeokit-context-menu-item")) {
366
369
  this.hide();
367
370
  }
@@ -560,7 +563,7 @@ class ContextMenu {
560
563
  for (let i = 0, len = this._menuList.length; i < len; i++) {
561
564
  const menu = this._menuList[i];
562
565
  const menuElement = menu.menuElement;
563
- menuElement.parentElement.removeChild(menuElement);
566
+ menuElement.remove();
564
567
  }
565
568
  this._itemsCfg = [];
566
569
  this._rootMenu = null;
@@ -665,7 +668,10 @@ class ContextMenu {
665
668
  const groups = menu.groups;
666
669
  const html = [];
667
670
 
668
- html.push('<div class="xeokit-context-menu ' + menu.id + '" style="z-index:300000; position: absolute;">');
671
+ const menuElement= document.createElement("div");
672
+ menuElement.classList.add("xeokit-context-menu", menu.id);
673
+ menuElement.style.zIndex = 300000;
674
+ menuElement.style.position = "absolute";
669
675
 
670
676
  html.push('<ul>');
671
677
 
@@ -716,13 +722,10 @@ class ContextMenu {
716
722
  }
717
723
 
718
724
  html.push('</ul>');
719
- html.push('</div>');
720
725
 
721
726
  const htmlString = html.join("");
722
-
723
- document.body.insertAdjacentHTML('beforeend', htmlString);
724
-
725
- const menuElement = document.querySelector("." + menu.id);
727
+ menuElement.innerHTML = htmlString;
728
+ this._parentNode.appendChild(menuElement);
726
729
 
727
730
  menu.menuElement = menuElement;
728
731
 
@@ -756,7 +759,7 @@ class ContextMenu {
756
759
  const item = groupItems[j];
757
760
  const itemSubMenu = item.subMenu;
758
761
 
759
- item.itemElement = document.getElementById(item.id);
762
+ item.itemElement = this._parentNode.querySelector(`#${item.id}`);
760
763
 
761
764
  if (!item.itemElement) {
762
765
  console.error("ContextMenu item element not found: " + item.id);
@@ -788,21 +791,22 @@ class ContextMenu {
788
791
 
789
792
  const itemRect = itemElement.getBoundingClientRect();
790
793
  subMenuElement.getBoundingClientRect();
794
+ const offsetRect = self._offsetParent.getBoundingClientRect();
791
795
 
792
796
  const subMenuWidth = 200; // TODO
793
- const showOnRight = (itemRect.right + subMenuWidth) < window.innerWidth;
794
- const showOnLeft = (itemRect.left - subMenuWidth) > 0;
795
-
797
+ const showOnRight = (itemRect.right + subMenuWidth) < offsetRect.right;
798
+ const showOnLeft = (itemRect.left - subMenuWidth) > offsetRect.left;
799
+
796
800
  if(showOnRight)
797
- self._showMenu(subMenu.id, itemRect.right - 5, itemRect.top - 1);
801
+ self._showMenu(subMenu.id, itemRect.right + window.scrollX - 5, itemRect.top + window.scrollY - 1);
798
802
  else if (showOnLeft)
799
- self._showMenu(subMenu.id, itemRect.left - subMenuWidth, itemRect.top - 1);
803
+ self._showMenu(subMenu.id, itemRect.left - subMenuWidth + window.scrollX, itemRect.top + window.scrollY - 1);
800
804
  else {
801
- const spaceOnLeft = itemRect.left, spaceOnRight = window.innerWidth - itemRect.right;
805
+ const spaceOnLeft = itemRect.left - offsetRect.left, spaceOnRight = offsetRect.right - itemRect.right;
802
806
  if(spaceOnRight > spaceOnLeft)
803
- self._showMenu(subMenu.id, itemRect.right - 5 - (subMenuWidth - spaceOnRight), itemRect.top - 1);
807
+ self._showMenu(subMenu.id, itemRect.right - 5 - (subMenuWidth - spaceOnRight), itemRect.top + window.scrollY - 1);
804
808
  else
805
- self._showMenu(subMenu.id, itemRect.left - spaceOnLeft, itemRect.top - 1);
809
+ self._showMenu(subMenu.id, itemRect.left - spaceOnLeft, itemRect.top + window.scrollY - 1);
806
810
  }
807
811
 
808
812
  lastSubMenu = subMenu;
@@ -995,14 +999,21 @@ class ContextMenu {
995
999
  menuElement.style.display = 'block';
996
1000
  const menuHeight = menuElement.offsetHeight;
997
1001
  const menuWidth = menuElement.offsetWidth;
998
- if ((pageY + menuHeight) > window.innerHeight) {
999
- pageY = window.innerHeight - menuHeight;
1002
+
1003
+ const offsetRect = this._offsetParent.getBoundingClientRect();
1004
+
1005
+ const bottomContainerBorder = offsetRect.bottom + window.scrollY;
1006
+ const rightContainerBorder = offsetRect.right + window.scrollX;
1007
+ if ((pageY + menuHeight) > bottomContainerBorder) {
1008
+ pageY = bottomContainerBorder- menuHeight;
1000
1009
  }
1001
- if ((pageX + menuWidth) > window.innerWidth) {
1002
- pageX = window.innerWidth - menuWidth;
1010
+ if ((pageX + menuWidth) > rightContainerBorder) {
1011
+ pageX = rightContainerBorder - menuWidth;
1003
1012
  }
1004
- menuElement.style.left = pageX + 'px';
1005
- menuElement.style.top = pageY + 'px';
1013
+
1014
+
1015
+ menuElement.style.left = pageX - offsetRect.left - window.scrollX + 'px';
1016
+ menuElement.style.top = pageY - offsetRect.top - window.scrollY + 'px';
1006
1017
  }
1007
1018
 
1008
1019
  _hideMenuElement(menuElement) {
@@ -11659,9 +11670,28 @@ const tmpVec4b = math.vec4();
11659
11670
  const tmpVec4c = math.vec4();
11660
11671
  const tmpVec4d = math.vec4();
11661
11672
  const tmpMat4 = math.mat4();
11673
+ const tmpRay = {
11674
+ origin: math.vec3(),
11675
+ direction: math.vec3()
11676
+ };
11662
11677
 
11663
11678
  const nop = () => { };
11664
11679
 
11680
+ const planeIntersect$1 = function(p0, n, origin, direction) {
11681
+ const t = (p0 - math.dotVec3(origin, n)) / math.dotVec3(direction, n);
11682
+ if (t < 0)
11683
+ {
11684
+ return null;
11685
+ }
11686
+ else
11687
+ {
11688
+ const worldPos = math.vec3();
11689
+ math.mulVec3Scalar(direction, t, worldPos);
11690
+ math.addVec3(origin, worldPos, worldPos);
11691
+ return worldPos;
11692
+ }
11693
+ };
11694
+
11665
11695
  function transformToNode(from, to, vec) {
11666
11696
  const fromRec = from.getBoundingClientRect();
11667
11697
  const toRec = to.getBoundingClientRect();
@@ -11923,9 +11953,15 @@ class Label3D {
11923
11953
  this._mid = math.vec3();
11924
11954
  this._end = math.vec3();
11925
11955
  this._yOff = 0;
11926
- this._betweenWires = false;
11927
11956
  this.__visible = true;
11928
11957
 
11958
+ this._updatePos = () => {
11959
+ toClipSpace(camera, this._start, tmpVec4a);
11960
+ math.mulVec4Scalar(tmpVec4a, 1.0 / tmpVec4a[3]);
11961
+ toCanvasSpace(scene.canvas.canvas, parentElement, tmpVec4a, tmpVec2a);
11962
+ this._label.setPos(tmpVec2a[0], tmpVec2a[1]);
11963
+ };
11964
+
11929
11965
  const setPosOnWire = (p0, p1, yOff) => {
11930
11966
  p0[0] += p1[0];
11931
11967
  p0[1] += p1[1];
@@ -11933,36 +11969,44 @@ class Label3D {
11933
11969
  this._label.setPos(p0[0], p0[1] + yOff);
11934
11970
  };
11935
11971
 
11936
- this._updatePositions = () => {
11937
- if (! this.__visible) {
11938
- return;
11972
+ this._updatePosBetween = () => {
11973
+ const visibleA = clipSegment(scene, parentElement, this._start, this._mid, tmpVec2a, tmpVec2b);
11974
+ const visibleB = clipSegment(scene, parentElement, this._end, this._mid, tmpVec2c, tmpVec2d);
11975
+ this._label.setCulled(! (visibleA || visibleB));
11976
+ if (visibleA && visibleB) {
11977
+ tmpVec2b[0] += tmpVec2d[0];
11978
+ tmpVec2b[1] += tmpVec2d[1];
11979
+ math.mulVec2Scalar(tmpVec2b, .5);
11980
+
11981
+ tmpVec2b[0] += tmpVec2a[0] + tmpVec2c[0];
11982
+ tmpVec2b[1] += tmpVec2a[1] + tmpVec2c[1];
11983
+ math.mulVec2Scalar(tmpVec2b, 1/3);
11984
+ this._label.setPos(tmpVec2b[0], tmpVec2b[1]);
11985
+ } else if (visibleA) {
11986
+ setPosOnWire(tmpVec2a, tmpVec2b, 0);
11987
+ } else if (visibleB) {
11988
+ setPosOnWire(tmpVec2c, tmpVec2d, 0);
11939
11989
  }
11940
- if (this._betweenWires) {
11941
- const visibleA = clipSegment(scene, parentElement, this._start, this._mid, tmpVec2a, tmpVec2b);
11942
- const visibleB = clipSegment(scene, parentElement, this._end, this._mid, tmpVec2c, tmpVec2d);
11943
- this._label.setCulled(! (visibleA || visibleB));
11944
- if (visibleA && visibleB) {
11945
- tmpVec2b[0] += tmpVec2d[0];
11946
- tmpVec2b[1] += tmpVec2d[1];
11947
- math.mulVec2Scalar(tmpVec2b, .5);
11948
-
11949
- tmpVec2b[0] += tmpVec2a[0] + tmpVec2c[0];
11950
- tmpVec2b[1] += tmpVec2a[1] + tmpVec2c[1];
11951
- math.mulVec2Scalar(tmpVec2b, 1/3);
11952
- this._label.setPos(tmpVec2b[0], tmpVec2b[1]);
11953
- } else if (visibleA) {
11954
- setPosOnWire(tmpVec2a, tmpVec2b, 0);
11955
- } else if (visibleB) {
11956
- setPosOnWire(tmpVec2c, tmpVec2d, 0);
11957
- }
11958
- } else {
11959
- const visible = (clipSegment(scene, parentElement, this._start, this._end, tmpVec2a, tmpVec2b)
11960
- &&
11961
- (math.distVec2(tmpVec2a, tmpVec2b) >= this._labelMinAxisLength));
11962
- this._label.setCulled(!visible);
11963
- if (visible) {
11964
- setPosOnWire(tmpVec2a, tmpVec2b, this._yOff);
11965
- }
11990
+ };
11991
+
11992
+ this._updatePosOnWire = () => {
11993
+ const visible = (clipSegment(scene, parentElement, this._start, this._end, tmpVec2a, tmpVec2b)
11994
+ &&
11995
+ (math.distVec2(tmpVec2a, tmpVec2b) >= this._labelMinAxisLength));
11996
+ this._label.setCulled(!visible);
11997
+ if (visible) {
11998
+ setPosOnWire(tmpVec2a, tmpVec2b, this._yOff);
11999
+ }
12000
+ };
12001
+
12002
+ let posUpdate = () => { };
12003
+ this._setUpdatePositions = (_posUpdate) => {
12004
+ posUpdate = _posUpdate;
12005
+ this._updatePositions();
12006
+ };
12007
+ this._updatePositions = () => {
12008
+ if (this.__visible) {
12009
+ posUpdate();
11966
12010
  }
11967
12011
  };
11968
12012
 
@@ -11980,21 +12024,24 @@ class Label3D {
11980
12024
  };
11981
12025
  }
11982
12026
 
12027
+ setPos(p0) {
12028
+ this._start.set(p0);
12029
+ this._setUpdatePositions(this._updatePos);
12030
+ }
12031
+
11983
12032
  setPosOnWire(p0, p1, yOff, labelMinAxisLength) {
11984
12033
  this._start.set(p0);
11985
12034
  this._end.set(p1);
11986
12035
  this._yOff = yOff;
11987
12036
  this._labelMinAxisLength = labelMinAxisLength;
11988
- this._betweenWires = false;
11989
- this._updatePositions();
12037
+ this._setUpdatePositions(this._updatePosOnWire);
11990
12038
  }
11991
12039
 
11992
12040
  setPosBetween(p0, p1, p2) {
11993
12041
  this._start.set(p0);
11994
12042
  this._mid.set(p1);
11995
12043
  this._end.set(p2);
11996
- this._betweenWires = true;
11997
- this._updatePositions();
12044
+ this._setUpdatePositions(this._updatePosBetween);
11998
12045
  }
11999
12046
 
12000
12047
  setFillColor(value) {
@@ -12097,6 +12144,51 @@ class Wire3D {
12097
12144
 
12098
12145
  }
12099
12146
 
12147
+ const marker3D = function(scene, color) {
12148
+ const marker = new Dot3D(scene, {}, scene.canvas.canvas.parentNode, {
12149
+ borderColor: "white",
12150
+ fillColor: color,
12151
+ zIndex: 100
12152
+ });
12153
+
12154
+ return {
12155
+ update: function(worldPos) {
12156
+ if (worldPos)
12157
+ {
12158
+ marker.worldPos = worldPos;
12159
+ }
12160
+ marker.setVisible(!!worldPos);
12161
+ },
12162
+
12163
+ setFillColor: c => marker.setFillColor(c),
12164
+ setHighlighted: h => marker.setHighlighted(h),
12165
+ getCanvasPos: () => marker.canvasPos,
12166
+ getWorldPos: () => marker.worldPos,
12167
+ destroy: () => marker.destroy()
12168
+ };
12169
+ };
12170
+
12171
+ const wire3D = function(scene, color) {
12172
+ const wire = new Wire3D(scene, scene.canvas.canvas.ownerDocument.body, {
12173
+ color: color,
12174
+ thickness: 1,
12175
+ thicknessClickable: 6 // TODO: Remove to make not clickable?
12176
+ });
12177
+ wire.setVisible(false);
12178
+ return {
12179
+ update: (startWorldPos, endWorldPos) => {
12180
+ if (endWorldPos)
12181
+ {
12182
+ wire.setEnds(startWorldPos, endWorldPos);
12183
+ }
12184
+ wire.setVisible(!!endWorldPos);
12185
+ },
12186
+
12187
+ setColor: c => wire.setColor(c),
12188
+ destroy: () => wire.destroy()
12189
+ };
12190
+ };
12191
+
12100
12192
  function activateDraggableDot(dot, cfg) {
12101
12193
  const extractCFG = function(propName, defaultValue) {
12102
12194
  if (propName in cfg) {
@@ -12411,6 +12503,482 @@ const touchPointSelector = function(viewer, pointerCircle, ray2WorldPos) {
12411
12503
  };
12412
12504
  };
12413
12505
 
12506
+ const triangulateEarClipping = function(planeCoords) {
12507
+ const polygonVertices = [ ];
12508
+ for (let i = 0; i < planeCoords.length; ++i)
12509
+ polygonVertices.push(i);
12510
+
12511
+ const isCCW = (function() {
12512
+ const ba = math.vec2();
12513
+ const bc = math.vec2();
12514
+
12515
+ let anglesSum = 0;
12516
+
12517
+ for (let i = 0; i < polygonVertices.length; ++i)
12518
+ {
12519
+ const a = planeCoords[polygonVertices[i]];
12520
+ const b = planeCoords[polygonVertices[(i + 1) % polygonVertices.length]];
12521
+ const c = planeCoords[polygonVertices[(i + 2) % polygonVertices.length]];
12522
+
12523
+ math.subVec2(a, b, ba);
12524
+ math.subVec2(c, b, bc);
12525
+
12526
+ const theta = math.dotVec2(ba, bc) / Math.sqrt(math.sqLenVec2(ba) * math.sqLenVec2(bc));
12527
+ const angle = Math.acos(Math.max(-1, Math.min(theta, 1)));
12528
+ const convex = (ba[0] * bc[1] - ba[1] * bc[0]) >= 0;
12529
+ anglesSum += convex ? angle : (2 * Math.PI - angle);
12530
+ }
12531
+
12532
+ return anglesSum < (polygonVertices.length * Math.PI);
12533
+ })();
12534
+
12535
+ const pointInTriangle = (function() {
12536
+ const sign = (p1, p2, p3) => {
12537
+ return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]);
12538
+ };
12539
+
12540
+ return (pt, v1, v2, v3) => {
12541
+ const d1 = sign(pt, v1, v2);
12542
+ const d2 = sign(pt, v2, v3);
12543
+ const d3 = sign(pt, v3, v1);
12544
+
12545
+ const has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
12546
+ const has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
12547
+
12548
+ return !(has_neg && has_pos);
12549
+ };
12550
+ })();
12551
+
12552
+ const baseTriangles = [ ];
12553
+
12554
+ const vertices = (isCCW ? polygonVertices : polygonVertices.slice(0).reverse()).map(i => ({ idx: i }));
12555
+ vertices.forEach((v, i) => {
12556
+ v.prev = vertices[(i - 1 + vertices.length) % vertices.length];
12557
+ v.next = vertices[(i + 1) % vertices.length];
12558
+ });
12559
+
12560
+ const ba = math.vec2();
12561
+ const bc = math.vec2();
12562
+
12563
+ while (vertices.length > 2) {
12564
+ let earIdx = 0;
12565
+ while (true) {
12566
+ if (earIdx >= vertices.length)
12567
+ {
12568
+ throw `isCCW = ${isCCW}; earIdx = ${earIdx}; len = ${vertices.length}`;
12569
+ }
12570
+ const v = vertices[earIdx];
12571
+
12572
+ const a = planeCoords[v.prev.idx];
12573
+ const b = planeCoords[v.idx];
12574
+ const c = planeCoords[v.next.idx];
12575
+
12576
+ math.subVec2(a, b, ba);
12577
+ math.subVec2(c, b, bc);
12578
+
12579
+ if (((ba[0] * bc[1] - ba[1] * bc[0]) >= 0) // a convex vertex
12580
+ &&
12581
+ vertices.every( // no other vertices inside
12582
+ vv => ((vv === v)
12583
+ ||
12584
+ (vv === v.prev)
12585
+ ||
12586
+ (vv === v.next)
12587
+ ||
12588
+ !pointInTriangle(planeCoords[vv.idx], a, b, c))))
12589
+ break;
12590
+ ++earIdx;
12591
+ }
12592
+
12593
+ const ear = vertices[earIdx];
12594
+ vertices.splice(earIdx, 1);
12595
+
12596
+ baseTriangles.push([ ear.idx, ear.next.idx, ear.prev.idx ]);
12597
+
12598
+ const prev = ear.prev;
12599
+ prev.next = ear.next;
12600
+ const next = ear.next;
12601
+ next.prev = ear.prev;
12602
+ }
12603
+
12604
+ return [ planeCoords, baseTriangles, isCCW ];
12605
+ };
12606
+
12607
+ const addMousePressListener = function(element, onChange) {
12608
+ const moveTolerance = 4;
12609
+
12610
+ const copyElementPos = (event, out) => {
12611
+ out[0] = event.clientX;
12612
+ out[1] = event.clientY;
12613
+ transformToNode(element.ownerDocument.documentElement, element, out);
12614
+ return out;
12615
+ };
12616
+
12617
+ let buttonDown = false;
12618
+
12619
+ const cleanups = [ ];
12620
+ const cleanup = () => cleanups.forEach(c => c());
12621
+
12622
+ const addElementEventListener = (type, listener) => {
12623
+ element.addEventListener(type, listener);
12624
+ cleanups.push(() => element.removeEventListener(type, listener));
12625
+ };
12626
+
12627
+ const downPos = math.vec2();
12628
+ addElementEventListener("mousedown", function(event) {
12629
+ if (event.which === 1) {
12630
+ buttonDown = true;
12631
+ onChange(copyElementPos(event, downPos));
12632
+ }
12633
+ });
12634
+
12635
+ addElementEventListener("mousemove", function(event) {
12636
+ copyElementPos(event, tmpVec2a);
12637
+ if (buttonDown && (math.distVec2(downPos, tmpVec2a) > moveTolerance)) {
12638
+ buttonDown = false;
12639
+ }
12640
+ if (buttonDown || (! event.buttons & 1)) {
12641
+ onChange(tmpVec2a);
12642
+ }
12643
+ });
12644
+
12645
+ addElementEventListener("mouseup", function(event) {
12646
+ if ((event.which === 1) && buttonDown) {
12647
+ const commit = onChange(copyElementPos(event, tmpVec2a));
12648
+ if (commit) {
12649
+ cleanup();
12650
+ commit();
12651
+ }
12652
+ }
12653
+ });
12654
+
12655
+ return cleanup;
12656
+ };
12657
+
12658
+ const addTouchPressListener = function(element, cameraControl, pointerCircle, onChange) {
12659
+ const longTouchTimeoutMs = 300;
12660
+ const moveTolerance = 20;
12661
+ const startPos = math.vec2();
12662
+
12663
+ const copyElementPos = (event, out) => {
12664
+ out[0] = event.clientX;
12665
+ out[1] = event.clientY;
12666
+ transformToNode(element.ownerDocument.documentElement, element, out);
12667
+ return out;
12668
+ };
12669
+
12670
+ let longTouchTimeout = null;
12671
+ let onSingleTouchMove = nop;
12672
+ let startTouchIdentifier;
12673
+
12674
+ const resetAction = function() {
12675
+ clearTimeout(longTouchTimeout);
12676
+ pointerCircle.stop();
12677
+ cameraControl.active = true;
12678
+ onSingleTouchMove = nop;
12679
+ startTouchIdentifier = null;
12680
+ };
12681
+
12682
+ const cleanups = [ ];
12683
+ const cleanup = () => cleanups.forEach(c => c());
12684
+
12685
+ const addElementEventListener = (type, listener) => {
12686
+ element.addEventListener(type, listener, {passive: true});
12687
+ cleanups.push(() => element.removeEventListener(type, listener));
12688
+ };
12689
+
12690
+ addElementEventListener("touchstart", function(event) {
12691
+ const touches = event.touches;
12692
+
12693
+ if (touches.length !== 1)
12694
+ {
12695
+ resetAction();
12696
+ onChange(null);
12697
+ }
12698
+ else
12699
+ {
12700
+ const touch = touches[0];
12701
+ copyElementPos(touch, startPos);
12702
+
12703
+ startTouchIdentifier = touch.identifier;
12704
+
12705
+ onSingleTouchMove = elementPos => {
12706
+ if (math.distVec2(startPos, elementPos) > moveTolerance)
12707
+ {
12708
+ resetAction();
12709
+ }
12710
+ };
12711
+
12712
+ longTouchTimeout = setTimeout(
12713
+ function() {
12714
+ pointerCircle.start(startPos);
12715
+
12716
+ longTouchTimeout = setTimeout(
12717
+ function() {
12718
+ pointerCircle.stop();
12719
+ cameraControl.active = false;
12720
+ onSingleTouchMove = onChange;
12721
+ onSingleTouchMove(startPos);
12722
+ },
12723
+ longTouchTimeoutMs);
12724
+ },
12725
+ 250);
12726
+ }
12727
+ });
12728
+
12729
+ // element.addEventListener("touchcancel", e => console.log("touchcancel", e), {passive: true});
12730
+
12731
+ addElementEventListener("touchmove", function(event) {
12732
+ const touch = [...event.changedTouches].find(e => e.identifier === startTouchIdentifier);
12733
+ if (touch)
12734
+ {
12735
+ onSingleTouchMove(copyElementPos(touch, tmpVec2a));
12736
+ }
12737
+ });
12738
+
12739
+ addElementEventListener("touchend", function(event) {
12740
+ const touch = [...event.changedTouches].find(e => e.identifier === startTouchIdentifier);
12741
+ if (touch)
12742
+ {
12743
+ const commit = onChange(copyElementPos(touch, tmpVec2a));
12744
+ resetAction();
12745
+ if (commit) {
12746
+ cleanup();
12747
+ commit();
12748
+ } else {
12749
+ onChange(null);
12750
+ }
12751
+ }
12752
+ });
12753
+
12754
+ return () => {
12755
+ resetAction();
12756
+ cleanup();
12757
+ };
12758
+ };
12759
+
12760
+ const startPolygonCreate = function(scene, pointerLens, addPressListener, pickRayResult, onChange, onConclude) {
12761
+ const canvas = scene.canvas.canvas;
12762
+
12763
+ const updatePointerLens = (pointerLens
12764
+ ? function(canvasPos, isSnapped) {
12765
+ pointerLens.visible = !! canvasPos;
12766
+ if (canvasPos)
12767
+ {
12768
+ pointerLens.canvasPos = canvasPos;
12769
+ pointerLens.snapped = !! isSnapped;
12770
+ }
12771
+ }
12772
+ : () => { });
12773
+
12774
+ const testLastSegmentIntersects = (function() {
12775
+ const onSegment = (p, q, r) => ((q[0] <= Math.max(p[0], r[0])) &&
12776
+ (q[0] >= Math.min(p[0], r[0])) &&
12777
+ (q[1] <= Math.max(p[1], r[1])) &&
12778
+ (q[1] >= Math.min(p[1], r[1])));
12779
+
12780
+ const orient = (p, q, r) => {
12781
+ const val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
12782
+ // collinear
12783
+ // clockwise
12784
+ // counterclockwise
12785
+ return ((val === 0) ? 0 : ((val > 0) ? 1 : 2));
12786
+ };
12787
+
12788
+ return function(pos2D, lastSegmentClosesLoop) {
12789
+ const s = lastSegmentClosesLoop ? 1 : 0;
12790
+ const a = pos2D[(pos2D.length - 2 + s) % pos2D.length];
12791
+ const b = pos2D[(pos2D.length - 1 + s) % pos2D.length];
12792
+
12793
+ for (let i = s; i < pos2D.length - 2 - 1 + s; ++i) {
12794
+ const c = pos2D[i];
12795
+ const d = pos2D[i + 1];
12796
+
12797
+ const o1 = orient(a, b, c);
12798
+ const o2 = orient(a, b, d);
12799
+ const o3 = orient(c, d, a);
12800
+ const o4 = orient(c, d, b);
12801
+
12802
+ if (((o1 !== o2) && (o3 !== o4)) || // General case
12803
+ ((o1 === 0) && onSegment(a, c, b)) || // a, b and c are collinear and c lies on segment ab
12804
+ ((o2 === 0) && onSegment(a, d, b)) || // a, b and d are collinear and d lies on segment ab
12805
+ ((o3 === 0) && onSegment(c, a, d)) || // c, d and a are collinear and a lies on segment cd
12806
+ ((o4 === 0) && onSegment(c, b, d))) // c, d and b are collinear and b lies on segment cd
12807
+ {
12808
+ return true;
12809
+ }
12810
+ }
12811
+
12812
+ return false;
12813
+ };
12814
+ })();
12815
+
12816
+ const getPlane = (points) => (points.length >= 3) && (function() {
12817
+ const u = math.normalizeVec3(math.subVec3(points[1], points[0], math.vec3()));
12818
+ const v20 = math.normalizeVec3(math.subVec3(points[2], points[0], tmpVec3a$2));
12819
+ const normal = math.normalizeVec3(math.cross3Vec3(u, v20, math.vec3()));
12820
+ const v = math.normalizeVec3(math.cross3Vec3(normal, u, math.vec3()));
12821
+ return {
12822
+ normal: normal,
12823
+ origin: math.vec3(points[0]),
12824
+ u: u,
12825
+ v: v
12826
+ };
12827
+ })();
12828
+
12829
+ const vertices = [ ];
12830
+ let currentInteraction;
12831
+
12832
+ (function selectNextPoint() {
12833
+ const plane = getPlane(vertices);
12834
+
12835
+ const canvasPos2Ray = (canvasPos, dst) => (math.canvasPosToWorldRay(canvas, scene.camera.viewMatrix, scene.camera.projMatrix, scene.camera.projection, canvasPos, dst.origin, dst.direction), dst);
12836
+
12837
+ const pickRay = (ray) => {
12838
+ const pickResult = pickRayResult(ray);
12839
+ const snapWorldPos = pickResult && pickResult.entity && ((! plane) || pickResult.snapped) && pickResult.worldPos;
12840
+ if (plane) {
12841
+ const originDistance = math.dotVec3(plane.normal, plane.origin);
12842
+ const snapPlaneDist = (snapWorldPos
12843
+ ? ((originDistance - math.dotVec3(snapWorldPos, plane.normal)))
12844
+ : window.Infinity);
12845
+ if (Math.abs(snapPlaneDist) < 0.0001) {
12846
+ const ret = math.vec3();
12847
+ return {
12848
+ worldPos: math.addVec3(snapWorldPos, math.mulVec3Scalar(plane.normal, snapPlaneDist, ret), ret),
12849
+ snapped: true
12850
+ };
12851
+ } else {
12852
+ return {
12853
+ worldPos: planeIntersect$1(originDistance, plane.normal, ray.origin, ray.direction),
12854
+ snapped: false
12855
+ };
12856
+ }
12857
+ } else {
12858
+ return {
12859
+ worldPos: snapWorldPos,
12860
+ snapped: pickResult && pickResult.snapped
12861
+ };
12862
+ }
12863
+ };
12864
+
12865
+ let placeVertex = false;
12866
+ const removePressListener = addPressListener(
12867
+ (inputCanvasPos) => {
12868
+ const rayPick = inputCanvasPos && pickRay(canvasPos2Ray(inputCanvasPos, tmpRay));
12869
+ const worldPos = rayPick && rayPick.worldPos;
12870
+ return onWorldPos(worldPos, inputCanvasPos, rayPick && rayPick.snapped);
12871
+ });
12872
+
12873
+ const onWorldPos = (worldPos, inputCanvasPos, rayPickSnapped) => {
12874
+ const canvasPos = worldPos ? scene.camera.projectWorldPos(worldPos) : inputCanvasPos;
12875
+ const firstMarker = (vertices.length > 0) && (function() {
12876
+ const v = vertices[0];
12877
+ return {
12878
+ canvasPos: scene.camera.projectWorldPos(v),
12879
+ worldPos: v
12880
+ };
12881
+ })();
12882
+ const snapToFirst = (vertices.length >= 3) && ((canvasPos && (math.distVec2(canvasPos, firstMarker.canvasPos) < 10))
12883
+ ||
12884
+ (worldPos && math.compareVec3(worldPos, firstMarker.worldPos)));
12885
+
12886
+ updatePointerLens(snapToFirst ? firstMarker.canvasPos : canvasPos, snapToFirst || rayPickSnapped);
12887
+
12888
+ const lastPointOverlaps = (inputCanvasPos
12889
+ &&
12890
+ ((! worldPos)
12891
+ ||
12892
+ ((vertices.length < 3)
12893
+ ? vertices.some(v => math.compareVec3(v, worldPos))
12894
+ : math.compareVec3(vertices[vertices.length - 1], worldPos))));
12895
+
12896
+ const points = vertices.concat(((! snapToFirst) && worldPos && (! lastPointOverlaps)) ? [worldPos] : []);
12897
+
12898
+ const curPlane = (! lastPointOverlaps) && (plane || getPlane(points));
12899
+
12900
+ const uvs = curPlane && points.map(p => {
12901
+ math.subVec3(p, curPlane.origin, tmpVec3a$2);
12902
+ return math.vec2([ math.dotVec3(tmpVec3a$2, curPlane.u), math.dotVec3(tmpVec3a$2, curPlane.v) ]);
12903
+ });
12904
+
12905
+ const isValid = (! lastPointOverlaps) && (! (uvs && testLastSegmentIntersects(uvs, snapToFirst)));
12906
+ const geometry = isValid && uvs && (snapToFirst || (! testLastSegmentIntersects(uvs, true))) && (function() {
12907
+ try {
12908
+ const [ baseVertices, baseTriangles, isCCW ] = triangulateEarClipping(uvs);
12909
+ return {
12910
+ faces: baseTriangles,
12911
+ vertices: baseVertices.map(uv => math.addVec3(
12912
+ curPlane.origin,
12913
+ math.addVec3(
12914
+ math.mulVec3Scalar(curPlane.u, uv[0], tmpVec3a$2),
12915
+ math.mulVec3Scalar(curPlane.v, uv[1], tmpVec3b$2),
12916
+ tmpVec3a$2),
12917
+ math.vec3()))
12918
+ };
12919
+ } catch (e) {
12920
+ console.warn("e", e);
12921
+ return false;
12922
+ }
12923
+ })();
12924
+
12925
+ onChange(points, snapToFirst, isValid, geometry);
12926
+
12927
+ return placeVertex = (isValid && (snapToFirst
12928
+ ? () => {
12929
+ updatePointerLens(null);
12930
+ onConclude();
12931
+ }
12932
+ : () => {
12933
+ vertices.push(math.vec3(worldPos));
12934
+ updatePointerLens(null);
12935
+ selectNextPoint();
12936
+ }));
12937
+ };
12938
+
12939
+ currentInteraction = {
12940
+ closePolygon: (() => {
12941
+ const commit = (vertices.length >= 3) && onWorldPos(vertices[0]);
12942
+ if (commit) {
12943
+ removePressListener();
12944
+ commit();
12945
+ }
12946
+ return !! commit;
12947
+ }),
12948
+ placeVertex: () => {
12949
+ if (placeVertex) {
12950
+ removePressListener();
12951
+ placeVertex();
12952
+ }
12953
+ return !!placeVertex;
12954
+ },
12955
+ popVertex: () => {
12956
+ if (vertices.length > 0) {
12957
+ removePressListener();
12958
+ vertices.pop();
12959
+ selectNextPoint();
12960
+ return true;
12961
+ } else {
12962
+ return false;
12963
+ }
12964
+ },
12965
+ removePressListener: removePressListener,
12966
+ updateOnChange: () => onWorldPos()
12967
+ };
12968
+ })();
12969
+
12970
+ return {
12971
+ cancel: () => {
12972
+ currentInteraction.removePressListener();
12973
+ updatePointerLens(null);
12974
+ },
12975
+ closePolygon: () => currentInteraction.closePolygon(),
12976
+ placeVertex: () => currentInteraction.placeVertex(),
12977
+ popVertex: () => currentInteraction.popVertex(),
12978
+ updateOnChange: () => currentInteraction.updateOnChange()
12979
+ };
12980
+ };
12981
+
12414
12982
  const tmpVec3a$1 = math.vec3();
12415
12983
  const tmpVec3b$1 = math.vec3();
12416
12984
 
@@ -14830,6 +15398,8 @@ const tempVec3a$M = math.vec3();
14830
15398
  const tempVec3b$B = math.vec3();
14831
15399
  const tempVec3c$w = math.vec3();
14832
15400
 
15401
+ const px = x => x + "px";
15402
+
14833
15403
  /**
14834
15404
  * A {@link Marker} with an HTML label attached to it, managed by an {@link AnnotationsPlugin}.
14835
15405
  *
@@ -15036,22 +15606,29 @@ class Annotation extends Marker {
15036
15606
  * @private
15037
15607
  */
15038
15608
  _updateWithCurWidths() {
15039
- const px = x => x + "px";
15040
15609
  const boundary = this.scene.canvas.boundary;
15041
15610
  const left = boundary[0] + this.canvasPos[0];
15042
15611
  const top = boundary[1] + this.canvasPos[1];
15043
- const markerWidth = this._curMarkerWidth;
15044
- const markerDir = (this._markerAlign === "right") ? -1 : ((this._markerAlign === "center") ? 0 : 1);
15045
- const markerCenter = left + markerDir * (markerWidth / 2 - 12);
15046
- this._marker.style.left = px(markerCenter - markerWidth / 2);
15047
- this._marker.style.top = px(top - 12);
15048
- this._marker.style["z-index"] = 90005 + Math.floor(this._viewPos[2]) + 1;
15049
-
15050
- const labelWidth = this._curLabelWidth;
15051
- const labelDir = Math.sign(this._labelPosition);
15052
- this._label.style.left = px(markerCenter + labelDir * (markerWidth / 2 + Math.abs(this._labelPosition) + labelWidth / 2) - labelWidth / 2);
15612
+ this._marker.style.top = px(top - 12);
15053
15613
  this._label.style.top = px(top - 17);
15054
- this._label.style["z-index"] = 90005 + Math.floor(this._viewPos[2]) + 1;
15614
+
15615
+ if (this._markerAlign === "legacy") {
15616
+ this._marker.style.left = px(left - 12);
15617
+ this._label.style.left = px(left + 40);
15618
+ } else {
15619
+ const markerWidth = this._curMarkerWidth;
15620
+ const markerDir = (this._markerAlign === "right") ? -1 : ((this._markerAlign === "center") ? 0 : 1);
15621
+ const markerCenter = left + markerDir * (markerWidth / 2 - 12);
15622
+ this._marker.style.left = px(markerCenter - markerWidth / 2);
15623
+
15624
+ const labelWidth = this._curLabelWidth;
15625
+ const labelDir = Math.sign(this._labelPosition);
15626
+ this._label.style.left = px(markerCenter + labelDir * (markerWidth / 2 + Math.abs(this._labelPosition) + labelWidth / 2) - labelWidth / 2);
15627
+ }
15628
+
15629
+ const zIndex = 90005 + Math.floor(this._viewPos[2]) + 1;
15630
+ this._marker.style["z-index"] = zIndex;
15631
+ this._label.style["z-index"] = zIndex;
15055
15632
  }
15056
15633
 
15057
15634
  /**
@@ -15082,7 +15659,8 @@ class Annotation extends Marker {
15082
15659
  * @private
15083
15660
  */
15084
15661
  _updatePosition() {
15085
- if (this._curMarkerWidth === undefined) {
15662
+ const isLegacy = this._markerAlign === "legacy";
15663
+ if ((! isLegacy) && (this._curMarkerWidth === undefined)) {
15086
15664
  this._updateIfWidthsChanged();
15087
15665
  } else {
15088
15666
  // Update position with cached width values
@@ -15090,7 +15668,9 @@ class Annotation extends Marker {
15090
15668
  // so they don't interfere with e.g. interactive scene manipulation
15091
15669
  this._updateWithCurWidths();
15092
15670
  window.clearTimeout(this._widthTimeout);
15093
- this._widthTimeout = window.setTimeout(() => this._updateIfWidthsChanged(), 500);
15671
+ if (! isLegacy) {
15672
+ this._widthTimeout = window.setTimeout(() => this._updateIfWidthsChanged(), 500);
15673
+ }
15094
15674
  }
15095
15675
  }
15096
15676
 
@@ -15128,10 +15708,10 @@ class Annotation extends Marker {
15128
15708
  /**
15129
15709
  * Sets the horizontal alignment of the Annotation's marker HTML.
15130
15710
  *
15131
- * @param {String} align Either "left", "center", "right" (default "left")
15711
+ * @param {String} align Either "left", "center", "right", "legacy" (default "left")
15132
15712
  */
15133
15713
  setMarkerAlign(align) {
15134
- const valid = [ "left", "center", "right" ];
15714
+ const valid = [ "left", "center", "right", "legacy" ];
15135
15715
  if (! valid.includes(align)) {
15136
15716
  this.error("Param 'align' should be one of: " + JSON.stringify(valid));
15137
15717
  } else {
@@ -26514,6 +27094,65 @@ class ReadableGeometry extends Geometry {
26514
27094
  return this._obb;
26515
27095
  }
26516
27096
 
27097
+ _getMetrics() {
27098
+ if (! ("_metrics" in this)) {
27099
+ switch (this._state.primitiveName) {
27100
+ case "solid":
27101
+ case "surface":
27102
+ case "triangles": {
27103
+ const indices = this._state.indices;
27104
+ const positions = this._state.positions;
27105
+ const getPos = (i, out) => {
27106
+ const idx = indices[i] * 3;
27107
+ for (let j = 0; j < 3; ++j) {
27108
+ out[j] = positions[idx + j];
27109
+ } return out;
27110
+ };
27111
+ const tmp = [ math.vec3(), math.vec3(), math.vec3(), math.vec3() ];
27112
+ let totalArea = 0;
27113
+ const centroid = math.vec3([ 0, 0, 0 ]);
27114
+ for (let i = 0; i < indices.length; i += 3) {
27115
+ const v0 = getPos(i, tmp[0]);
27116
+ const v1 = getPos(i+1, tmp[1]);
27117
+ const v2 = getPos(i+2, tmp[2]);
27118
+ math.addVec3(v0, v1, tmp[3]);
27119
+ math.addVec3(v2, tmp[3], tmp[3]);
27120
+ const faceArea = math.lenVec3(
27121
+ math.cross3Vec3(
27122
+ math.subVec3(v1, v0, tmp[1]),
27123
+ math.subVec3(v2, v0, tmp[2]),
27124
+ tmp[0])) / 2;
27125
+ totalArea += faceArea;
27126
+ math.mulVec3Scalar(tmp[3], faceArea, tmp[3]);
27127
+ math.addVec3(centroid, tmp[3], centroid);
27128
+ }
27129
+ this._metrics = { surfaceArea: totalArea, centroid: math.mulVec3Scalar(centroid, 1 / totalArea / 3, centroid) };
27130
+ break;
27131
+ }
27132
+ default:
27133
+ this._metrics = { surfaceArea: 0 };
27134
+ break;
27135
+ }
27136
+ }
27137
+ return this._metrics;
27138
+ }
27139
+
27140
+ /**
27141
+ * Returns the surface area of this Mesh.
27142
+ * @returns {number}
27143
+ */
27144
+ get surfaceArea() {
27145
+ return this._getMetrics().surfaceArea;
27146
+ }
27147
+
27148
+ /**
27149
+ * Returns the centroid of this Mesh.
27150
+ * @returns {number}
27151
+ */
27152
+ get centroid() {
27153
+ return this._getMetrics().centroid;
27154
+ }
27155
+
26517
27156
  /**
26518
27157
  * Approximate number of triangles in this ReadableGeometry.
26519
27158
  *
@@ -40062,7 +40701,7 @@ function buildPlaneGeometry(cfg = {}) {
40062
40701
  positions[offset + 1] = centerY;
40063
40702
  positions[offset + 2] = -z + centerZ;
40064
40703
 
40065
- normals[offset + 2] = -1;
40704
+ normals[offset + 1] = 1;
40066
40705
 
40067
40706
  uvs[offset2] = (ix) / planeX;
40068
40707
  uvs[offset2 + 1] = ((planeZ - iz) / planeZ);
@@ -43360,8 +43999,7 @@ class SectionCaps {
43360
43999
  if(!this._resourcesAllocated) {
43361
44000
  this._resourcesAllocated = true;
43362
44001
  this._sectionPlanes = [];
43363
- this._verticesMap = {};
43364
- this._indicesMap = {};
44002
+ this._sceneModelsData = {};
43365
44003
  this._dirtyMap = {};
43366
44004
  this._prevIntersectionModelsMap = {};
43367
44005
  this._sectionPlaneTimeout = null;
@@ -43376,7 +44014,8 @@ class SectionCaps {
43376
44014
  this._sectionPlanes.push(sectionPlane);
43377
44015
  sectionPlane.on('pos', onSectionPlaneUpdated);
43378
44016
  sectionPlane.on('dir', onSectionPlaneUpdated);
43379
- sectionPlane.once('destroyed', ((sectionPlane) => {
44017
+ sectionPlane.on('active', onSectionPlaneUpdated);
44018
+ sectionPlane.once('destroyed', (() => {
43380
44019
  const sectionPlaneId = sectionPlane.id;
43381
44020
  if (sectionPlaneId) {
43382
44021
  this._sectionPlanes = this._sectionPlanes.filter((sectionPlane) => sectionPlane.id !== sectionPlaneId);
@@ -43393,13 +44032,19 @@ class SectionCaps {
43393
44032
 
43394
44033
  this._onTick = this.scene.on("tick", () => {
43395
44034
  //on ticks we only check if there is a model that we have saved vertices for,
43396
- //but it's no more available on the scene
43397
- for(const key in this._verticesMap) {
43398
- if(!this.scene.models[key]){
43399
- delete this._verticesMap[key];
43400
- delete this._indicesMap[key];
43401
- this._update();
43402
- }
44035
+ //but it's no more available on the scene, or if its visibility changed
44036
+ let dirty = false;
44037
+ for(const sceneModelId in this._sceneModelsData) {
44038
+ if(!this.scene.models[sceneModelId]){
44039
+ delete this._sceneModelsData[sceneModelId];
44040
+ dirty = true;
44041
+ } else if (this._sceneModelsData[sceneModelId].visible !== (!!this.scene.models[sceneModelId].visible)) {
44042
+ this._sceneModelsData[sceneModelId].visible = !!this.scene.models[sceneModelId].visible;
44043
+ dirty = true;
44044
+ }
44045
+ }
44046
+ if (dirty) {
44047
+ this._update();
43403
44048
  }
43404
44049
  });
43405
44050
  }
@@ -43416,8 +44061,8 @@ class SectionCaps {
43416
44061
  this._deletePreviousModels();
43417
44062
  this._updateTimeout = setTimeout(() => {
43418
44063
  clearTimeout(this._updateTimeout);
43419
- const sceneModels = Object.keys(this.scene.models).map((key) => this.scene.models[key]);
43420
- this._addHatches(sceneModels, this._sectionPlanes);
44064
+ const sceneModels = Object.values(this.scene.models).filter(sceneModel => sceneModel.visible);
44065
+ this._addHatches(sceneModels, this._sectionPlanes.filter(sectionPlane => sectionPlane.active));
43421
44066
  this._setAllDirty(false);
43422
44067
  }, 100);
43423
44068
  }
@@ -43430,21 +44075,9 @@ class SectionCaps {
43430
44075
 
43431
44076
  _addHatches(sceneModels, planes) {
43432
44077
 
43433
- if (planes.length <= 0) return;
43434
-
43435
44078
  planes.forEach((plane) => {
43436
44079
  sceneModels.forEach((sceneModel) => {
43437
- //#region creating a plane equation
43438
- //we create a plane equation that will be used to slice through each triangle
43439
- const planeEquation = {
43440
- A: plane.dir[0],
43441
- B: plane.dir[1],
43442
- C: plane.dir[2],
43443
- D: -(plane.dir[0] * plane.pos[0] + plane.dir[1] * plane.pos[1] + plane.dir[2] * plane.pos[2])
43444
- };
43445
- //#endregion
43446
-
43447
- if(!this._doesPlaneIntersectBoundingBox(sceneModel.aabb, planeEquation)) return;
44080
+ if(!this._doesPlaneIntersectBoundingBox(sceneModel.aabb, plane)) return;
43448
44081
 
43449
44082
  if(!this._dirtyMap[sceneModel.id]) return;
43450
44083
 
@@ -43466,39 +44099,35 @@ class SectionCaps {
43466
44099
 
43467
44100
  const object = objects[objectId];
43468
44101
 
43469
- if(!this._doesPlaneIntersectBoundingBox(object.aabb, planeEquation)) return;
44102
+ if(!this._doesPlaneIntersectBoundingBox(object.aabb, plane)) return;
43470
44103
 
43471
- if(!this._verticesMap[sceneModel.id]) {
43472
- this._verticesMap[sceneModel.id] = new Map();
43473
- this._indicesMap[sceneModel.id] = new Map();
44104
+ if(!this._sceneModelsData[sceneModel.id]) {
44105
+ this._sceneModelsData[sceneModel.id] = {
44106
+ verticesMap: new Map(),
44107
+ indicesMap: new Map()
44108
+ };
43474
44109
  }
43475
44110
 
43476
- let vertices = [], indices = [];
44111
+ const sceneModelData = this._sceneModelsData[sceneModel.id];
43477
44112
 
43478
- if(!this._verticesMap[sceneModel.id].has(objectId)) {
44113
+ if(!sceneModelData.verticesMap.has(objectId)) {
43479
44114
  const isSolid = object.meshes[0].isSolid();
44115
+ const vertices = [ ];
44116
+ const indices = [ ];
43480
44117
  if(isSolid && object.capMaterial) {
43481
- object.getEachVertex((_vertices) => {
43482
- vertices.push(_vertices[0], _vertices[1], _vertices[2]);
43483
- });
43484
- object.getEachIndex((_indices) => {
43485
- indices.push(_indices);
43486
- });
44118
+ object.getEachVertex(v => vertices.push(v[0], v[1], v[2]));
44119
+ object.getEachIndex(i => indices.push(i));
43487
44120
  }
43488
- this._verticesMap[sceneModel.id].set(objectId, vertices);
43489
- this._indicesMap[sceneModel.id].set(objectId, indices);
43490
- }
43491
- else {
43492
- vertices = this._verticesMap[sceneModel.id].get(objectId);
43493
- indices = this._indicesMap[sceneModel.id].get(objectId);
44121
+ sceneModelData.verticesMap.set(objectId, vertices);
44122
+ sceneModelData.indicesMap.set(objectId, indices);
43494
44123
  }
43495
-
44124
+
44125
+ const vertices = sceneModelData.verticesMap.get(objectId);
44126
+ const indices = sceneModelData.indicesMap.get(objectId);
44127
+
43496
44128
  const capSegments = [];
43497
44129
  const vertCount = indices.length;
43498
-
43499
- // Preallocate intersection result array
43500
- const intersectionBuffer = new Float32Array(3);
43501
-
44130
+
43502
44131
  for (let i = 0; i < vertCount; i += 3) {
43503
44132
  // Reuse triangle buffer instead of creating new arrays
43504
44133
  for (let j = 0; j < 3; j++) {
@@ -43515,21 +44144,15 @@ class SectionCaps {
43515
44144
  for (let i = 0; i < 3; i++) {
43516
44145
  const p1 = triangle[i];
43517
44146
  const p2 = triangle[(i + 1) % 3];
43518
-
43519
- // Inline the distance calculations to avoid function calls
43520
- const d1 = planeEquation.A * p1[0] + planeEquation.B * p1[1] + planeEquation.C * p1[2] + planeEquation.D;
43521
- const d2 = planeEquation.A * p2[0] + planeEquation.B * p2[1] + planeEquation.C * p2[2] + planeEquation.D;
43522
-
44147
+
44148
+ const d1 = plane.dist + math.dotVec3(plane.dir, p1);
44149
+ const d2 = plane.dist + math.dotVec3(plane.dir, p2);
44150
+
43523
44151
  if (d1 * d2 > 0) continue;
43524
-
44152
+
43525
44153
  const t = -d1 / (d2 - d1);
43526
- // Reuse intersection buffer
43527
- intersectionBuffer[0] = p1[0] + t * (p2[0] - p1[0]);
43528
- intersectionBuffer[1] = p1[1] + t * (p2[1] - p1[1]);
43529
- intersectionBuffer[2] = p1[2] + t * (p2[2] - p1[2]);
43530
-
43531
- // Clone the buffer for storage
43532
- intersections.push(new Float32Array(intersectionBuffer));
44154
+
44155
+ intersections.push(math.lerpVec3(t, 0, 1, p1, p2, math.vec3()));
43533
44156
  }
43534
44157
 
43535
44158
  if(intersections.length === 2) capSegments.push(intersections);
@@ -43809,7 +44432,7 @@ class SectionCaps {
43809
44432
 
43810
44433
  }
43811
44434
 
43812
- _doesPlaneIntersectBoundingBox(bb, planeEquation) {
44435
+ _doesPlaneIntersectBoundingBox(bb, plane) {
43813
44436
  const min = [bb[0], bb[1], bb[2]];
43814
44437
  const max = [bb[3], bb[4], bb[5]];
43815
44438
 
@@ -43829,10 +44452,7 @@ class SectionCaps {
43829
44452
  let hasNegative = false;
43830
44453
 
43831
44454
  for (const corner of corners) {
43832
- const distance = planeEquation.A * corner[0] +
43833
- planeEquation.B * corner[1] +
43834
- planeEquation.C * corner[2] +
43835
- planeEquation.D;
44455
+ const distance = plane.dist + math.dotVec3(plane.dir, corner);
43836
44456
 
43837
44457
  if (distance > 0) hasPositive = true;
43838
44458
  if (distance < 0) hasNegative = true;
@@ -44327,13 +44947,12 @@ class Scene extends Component {
44327
44947
  * configures renderer logic for the specified number of SectionPlanes, eliminating the need for setting up logic with each SectionPlane creation and thereby enhancing
44328
44948
  * responsiveness. It is important to consider that each SectionPlane imposes rendering performance, so it is recommended to set this value to a quantity that aligns with
44329
44949
  * your expected usage.
44330
- * @throws {String} Throws an exception when both canvasId or canvasElement are missing or they aren't pointing to a valid HTMLCanvasElement.
44950
+ * @throws {String} Throws an exception when canvasId or canvasElement are missing or they aren't pointing to a valid HTMLCanvasElement.
44331
44951
  */
44332
44952
  constructor(viewer, cfg = {}) {
44333
44953
 
44334
44954
  super(null, cfg);
44335
-
44336
- const canvas = cfg.canvasElement || document.getElementById(cfg.canvasId);
44955
+ const canvas = cfg.canvasElement || document.querySelector(`#${cfg.canvasId}`);
44337
44956
 
44338
44957
  if (!(canvas instanceof HTMLCanvasElement)) {
44339
44958
  throw "Mandatory config expected: valid canvasId or canvasElement";
@@ -88573,7 +89192,7 @@ class LineSet extends Component {
88573
89192
  * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.
88574
89193
  * @param {Number[]} cfg.positions World-space 3D vertex positions.
88575
89194
  * @param {Number[]} [cfg.indices] Indices to connect ````positions```` into line segments. Note that these are separate line segments, not a polyline.
88576
- * @param {Number[]} [cfg.color=[0,0,0]] The color of this ````LineSet````. This is both emissive and diffuse.
89195
+ * @param {Number[]} [cfg.color=[1,1,1]] The color of this ````LineSet````. This is both emissive and diffuse.
88577
89196
  * @param {Boolean} [cfg.visible=true] Indicates whether or not this ````LineSet```` is visible.
88578
89197
  * @param {Number} [cfg.opacity=1.0] ````LineSet````'s initial opacity factor.
88579
89198
  */
@@ -88593,6 +89212,9 @@ class LineSet extends Component {
88593
89212
  }
88594
89213
  }
88595
89214
 
89215
+ this._color = cfg.color || [1, 1, 1];
89216
+ this._opacity = cfg.opacity || 1.0;
89217
+
88596
89218
  this._sceneModel = new SceneModel(this, {
88597
89219
  isModel: false // Don't register in Scene.models
88598
89220
  });
@@ -88601,7 +89223,9 @@ class LineSet extends Component {
88601
89223
  id: "linesMesh",
88602
89224
  primitive: "lines",
88603
89225
  positions: this._positions,
88604
- indices: this._indices
89226
+ indices: this._indices,
89227
+ color: this._color,
89228
+ opacity: this._opacity,
88605
89229
  });
88606
89230
 
88607
89231
  this._sceneModel.createEntity({
@@ -92329,6 +92953,7 @@ class FastNavPlugin extends Plugin {
92329
92953
  * @param {Number} [cfg.scaleCanvasResolutionFactor=0.6] The factor by which we downscale the canvas resolution whenever we interact with the Viewer.
92330
92954
  * @param {Boolean} [cfg.delayBeforeRestore=true] Whether to temporarily have a delay before restoring normal rendering after we stop interacting with the Viewer.
92331
92955
  * @param {Number} [cfg.delayBeforeRestoreSeconds=0.5] Delay in seconds before restoring normal rendering after we stop interacting with the Viewer.
92956
+ * @param {Function} [cfg.onMoved] Optional callback function fired during moving mode, should return the callback function that will be fired when the interaction stops.
92332
92957
  */
92333
92958
  constructor(viewer, cfg = {}) {
92334
92959
 
@@ -92344,6 +92969,8 @@ class FastNavPlugin extends Plugin {
92344
92969
  this._scaleCanvasResolutionFactor = cfg.scaleCanvasResolutionFactor || 0.6;
92345
92970
  this._delayBeforeRestore = (cfg.delayBeforeRestore !== false);
92346
92971
  this._delayBeforeRestoreSeconds = cfg.delayBeforeRestoreSeconds || 0.5;
92972
+ this._onMoved = cfg.onMoved;
92973
+ this._onStopped = null;
92347
92974
 
92348
92975
  let timer = this._delayBeforeRestoreSeconds * 1000;
92349
92976
  let fastMode = false;
@@ -92362,6 +92989,10 @@ class FastNavPlugin extends Plugin {
92362
92989
  } else {
92363
92990
  viewer.scene.canvas.resolutionScale = this._defaultScaleCanvasResolutionFactor;
92364
92991
  }
92992
+ if (this._onMoved) {
92993
+ this._onStopped = this._onMoved();
92994
+ }
92995
+
92365
92996
  fastMode = true;
92366
92997
  }
92367
92998
  };
@@ -92373,6 +93004,9 @@ class FastNavPlugin extends Plugin {
92373
93004
  viewer.scene._renderer.setPBREnabled(true);
92374
93005
  viewer.scene._renderer.setSAOEnabled(true);
92375
93006
  viewer.scene._renderer.setTransparentEnabled(true);
93007
+ if (this._onStopped) {
93008
+ this._onStopped();
93009
+ }
92376
93010
  fastMode = false;
92377
93011
  };
92378
93012
 
@@ -97699,8 +98333,8 @@ class Skybox extends Component {
97699
98333
  * @param {Component} owner Owner component. When destroyed, the owner will destroy this PointLight as well.
97700
98334
  * @param {*} [cfg] Skybox configuration
97701
98335
  * @param {String} [cfg.id] Optional ID, unique among all components in the parent {Scene}, generated automatically when omitted.
97702
- * @param {String} [cfg.src=null] Path to skybox texture
97703
- * @param {String} [cfg.encoding="linear"] Texture encoding format. See the {@link Texture#encoding} property for more info.
98336
+ * @param {String | String[]} [cfg.src=null] Path to skybox texture
98337
+ * @param {Number} [cfg.encoding=LinearEncoding] Texture encoding format. See the {@link Texture#encoding} property for more info.
97704
98338
  * @param {Number} [cfg.size=1000] Size of this Skybox, given as the distance from the center at ````[0,0,0]```` to each face.
97705
98339
  * @param {Boolean} [cfg.active=true] True when this Skybox is visible.
97706
98340
  */
@@ -97708,6 +98342,32 @@ class Skybox extends Component {
97708
98342
 
97709
98343
  super(owner, cfg);
97710
98344
 
98345
+ const useMultipleTexture = typeof cfg.src === "object" && cfg.src !== null;
98346
+
98347
+ if (useMultipleTexture) {
98348
+ this._createCombinedTexture(cfg).then(combinedTexture => {
98349
+ this._createSkyboxMesh(cfg, combinedTexture);
98350
+ }).catch(e => this.error(e));
98351
+ } else {
98352
+ this._createSkyboxMesh(cfg);
98353
+ }
98354
+ }
98355
+
98356
+ /**
98357
+ *
98358
+ * @private
98359
+ * @param {*} cfg
98360
+ * @param {Texture} combinedTexture
98361
+ */
98362
+ _createSkyboxMesh(cfg, combinedTexture = null) {
98363
+ const texture = combinedTexture || new Texture(this, {
98364
+ src: cfg.src,
98365
+ flipY: true,
98366
+ wrapS: ClampToEdgeWrapping,
98367
+ wrapT: ClampToEdgeWrapping,
98368
+ encoding: cfg.encoding || LinearEncoding
98369
+ });
98370
+
97711
98371
  this._skyboxMesh = new Mesh(this, {
97712
98372
 
97713
98373
  geometry: new ReadableGeometry(this, { // Box-shaped geometry
@@ -97738,13 +98398,7 @@ class Skybox extends Component {
97738
98398
  diffuse: [0, 0, 0],
97739
98399
  specular: [0, 0, 0],
97740
98400
  emissive: [1, 1, 1],
97741
- emissiveMap: new Texture(this, {
97742
- src: cfg.src,
97743
- flipY: true,
97744
- wrapS: "clampToEdge",
97745
- wrapT: "clampToEdge",
97746
- encoding: cfg.encoding || "sRGB"
97747
- }),
98401
+ emissiveMap: texture,
97748
98402
  backfaces: true // Show interior faces of our skybox geometry
97749
98403
  }),
97750
98404
  // stationary: true,
@@ -97758,6 +98412,86 @@ class Skybox extends Component {
97758
98412
  this.active = cfg.active;
97759
98413
  }
97760
98414
 
98415
+ /**
98416
+ *
98417
+ * @private
98418
+ * @param {*} cfg
98419
+ * @returns {Texture}
98420
+ */
98421
+ async _createCombinedTexture(cfg) {
98422
+ const [
98423
+ posX,
98424
+ negX,
98425
+ posY,
98426
+ negY,
98427
+ posZ,
98428
+ negZ
98429
+ ] = cfg.src;
98430
+
98431
+ if (!posX || !negX || !posY || !negY || !posZ || !negZ) {
98432
+ throw new Error("All six skybox textures must be provided");
98433
+ }
98434
+
98435
+ const canvas = document.createElement('canvas');
98436
+ const ctx = canvas.getContext('2d');
98437
+
98438
+ const loadImage = src => {
98439
+ return new Promise((resolve, reject) => {
98440
+ const img = new Image();
98441
+ img.crossOrigin = "anonymous";
98442
+ img.onload = () => resolve(img);
98443
+ img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
98444
+ img.src = src;
98445
+ });
98446
+ };
98447
+
98448
+ try {
98449
+ const [imgPosX, imgNegX, imgPosY, imgNegY, imgPosZ, imgNegZ] = await Promise.all([
98450
+ loadImage(posX),
98451
+ loadImage(negX),
98452
+ loadImage(posY),
98453
+ loadImage(negY),
98454
+ loadImage(posZ),
98455
+ loadImage(negZ)
98456
+ ]);
98457
+
98458
+ const imageSize = imgPosX.width;
98459
+ if (
98460
+ imgNegX.width !== imageSize || imgPosY.width !== imageSize ||
98461
+ imgNegY.width !== imageSize || imgPosZ.width !== imageSize ||
98462
+ imgNegZ.width !== imageSize
98463
+ ) {
98464
+ throw new Error("All skybox textures must have the same dimensions");
98465
+ }
98466
+
98467
+ // Set canvas size for the Christ cross layout (3×4 grid)
98468
+ canvas.width = imageSize * 4;
98469
+ canvas.height = imageSize * 3;
98470
+
98471
+ ctx.fillStyle = 'black';
98472
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
98473
+
98474
+ ctx.drawImage(imgNegX, imageSize * 0, imageSize * 1, imageSize, imageSize); // -X (left)
98475
+ ctx.drawImage(imgPosX, imageSize * 2, imageSize * 1, imageSize, imageSize); // +X (right)
98476
+ ctx.drawImage(imgPosY, imageSize * 1, imageSize * 0, imageSize, imageSize); // +Y (top)
98477
+ ctx.drawImage(imgNegY, imageSize * 1, imageSize * 2, imageSize, imageSize); // -Y (bottom)
98478
+ ctx.drawImage(imgPosZ, imageSize * 1, imageSize * 1, imageSize, imageSize); // +Z (front)
98479
+ ctx.drawImage(imgNegZ, imageSize * 3, imageSize * 1, imageSize, imageSize); // -Z (back)
98480
+
98481
+ const combinedTexture = new Texture(this, {
98482
+ image: canvas,
98483
+ flipY: true,
98484
+ wrapS: ClampToEdgeWrapping,
98485
+ wrapT: ClampToEdgeWrapping,
98486
+ encoding: cfg.encoding || LinearEncoding
98487
+ });
98488
+
98489
+ return combinedTexture;
98490
+ } catch (error) {
98491
+ console.error("Error creating combined skybox texture:", error);
98492
+ throw error;
98493
+ }
98494
+ }
97761
98495
 
97762
98496
  /**
97763
98497
  * Sets the size of this Skybox, given as the distance from the center at [0,0,0] to each face.
@@ -97952,6 +98686,9 @@ const tempVec4a$5 = math.vec4();
97952
98686
  const tempVec4b$5 = math.vec4();
97953
98687
  const tempVec4c = math.vec4();
97954
98688
 
98689
+ const TOP_LIMIT = 0.001;
98690
+ const BOTTOM_LIMIT = Math.PI - 0.001;
98691
+
97955
98692
 
97956
98693
  /** @private */
97957
98694
  class PivotController {
@@ -98127,14 +98864,9 @@ class PivotController {
98127
98864
  */
98128
98865
  startPivot() {
98129
98866
 
98130
- if (this._cameraLookingDownwards()) {
98131
- this._pivoting = false;
98132
- return false;
98133
- }
98134
-
98135
98867
  const camera = this._scene.camera;
98136
98868
 
98137
- let lookat = math.lookAtMat4v(camera.eye, camera.look, camera.worldUp);
98869
+ let lookat = math.lookAtMat4v(camera.eye, camera.look, camera.up);
98138
98870
  math.transformPoint3(lookat, this.getPivotPos(), this._cameraOffset);
98139
98871
 
98140
98872
  const pivotPos = this.getPivotPos();
@@ -98237,8 +98969,31 @@ class PivotController {
98237
98969
  dx = -dx;
98238
98970
  }
98239
98971
  this._azimuth += -dx * .01;
98240
- this._polar += dy * .01;
98241
- this._polar = math.clamp(this._polar, .001, Math.PI - .001);
98972
+
98973
+ const isMovingUp = dy < 0;
98974
+ const isMovingDown = dy > 0;
98975
+
98976
+ // Track if we're at limits - only check if we're very close to the limit
98977
+ const atTopLimit = Math.abs(this._polar - TOP_LIMIT) < 0.005;
98978
+ const atBottomLimit = Math.abs(this._polar - BOTTOM_LIMIT) < 0.005;
98979
+
98980
+ let newPolar = this._polar + dy * .01;
98981
+
98982
+ // Case 1: At top limit and trying to go beyond
98983
+ if (atTopLimit && isMovingUp) {
98984
+ newPolar = TOP_LIMIT;
98985
+ }
98986
+ // Case 2: At bottom limit and trying to go beyond
98987
+ else if (atBottomLimit && isMovingDown) {
98988
+ newPolar = BOTTOM_LIMIT;
98989
+ }
98990
+ // Case 3: Normal rotation or moving away from a limit
98991
+ else {
98992
+ newPolar = math.clamp(newPolar, TOP_LIMIT, BOTTOM_LIMIT);
98993
+ }
98994
+
98995
+ this._polar = newPolar;
98996
+
98242
98997
  const pos = [
98243
98998
  this._radius * Math.sin(this._polar) * Math.sin(this._azimuth),
98244
98999
  this._radius * Math.cos(this._polar),
@@ -111788,22 +112543,6 @@ class Viewer {
111788
112543
  this.sendToPlugins("snapshotStarting"); // Tells plugins to hide things that shouldn't be in snapshot
111789
112544
  }
111790
112545
 
111791
- const captured = {};
111792
- for (let i = 0, len = this._plugins.length; i < len; i++) {
111793
- const plugin = this._plugins[i];
111794
- if (plugin.getContainerElement) {
111795
- const container = plugin.getContainerElement();
111796
- if (container !== document.body) {
111797
- if (!captured[container.id]) {
111798
- captured[container.id] = true;
111799
- html2canvas(container).then(function (canvas) {
111800
- document.body.appendChild(canvas);
111801
- });
111802
- }
111803
- }
111804
- }
111805
- }
111806
-
111807
112546
  // firing "rendering" is necessary to trigger DTX{Lines,Triangles}Layer::_uploadDeferredFlags
111808
112547
  this.scene.fire("rendering", { }, true);
111809
112548
 
@@ -123581,6 +124320,7 @@ function CubeTextureCanvas(viewer, navCubeScene, cfg = {}) {
123581
124320
  const cubeColor = "lightgrey";
123582
124321
  const cubeHighlightColor = cfg.hoverColor || "rgba(0,0,0,0.4)";
123583
124322
  const textColor = cfg.textColor || "black";
124323
+ const parentNode = cfg.canvasElement || document.body;
123584
124324
 
123585
124325
  const height = 500;
123586
124326
  const width = height + (height / 3);
@@ -123690,8 +124430,7 @@ function CubeTextureCanvas(viewer, navCubeScene, cfg = {}) {
123690
124430
  this._textureCanvas.style.visibility = "hidden";
123691
124431
  this._textureCanvas.style["z-index"] = 2000000;
123692
124432
 
123693
- const body = document.getElementsByTagName("body")[0];
123694
- body.appendChild(this._textureCanvas);
124433
+ parentNode.appendChild(this._textureCanvas);
123695
124434
 
123696
124435
  const context = this._textureCanvas.getContext("2d");
123697
124436
 
@@ -123961,7 +124700,7 @@ class NavCubePlugin extends Plugin {
123961
124700
  this._navCubeScene = new Scene(viewer, {
123962
124701
  canvasId: cfg.canvasId,
123963
124702
  canvasElement: cfg.canvasElement,
123964
- transparent: true
124703
+ transparent: true,
123965
124704
  });
123966
124705
 
123967
124706
  this._navCubeCanvas = this._navCubeScene.canvas.canvas;
@@ -124194,7 +124933,7 @@ class NavCubePlugin extends Plugin {
124194
124933
  }
124195
124934
  });
124196
124935
 
124197
- document.addEventListener("mouseup", self._onMouseUp = function (e) {
124936
+ self._navCubeCanvas.addEventListener("mouseup", self._onMouseUp = function (e) {
124198
124937
  if (e.which !== 1) {// Left button
124199
124938
  return;
124200
124939
  }
@@ -124211,7 +124950,7 @@ class NavCubePlugin extends Plugin {
124211
124950
  if (hit.uv) {
124212
124951
  var areaId = self._cubeTextureCanvas.getArea(hit.uv);
124213
124952
  if (areaId >= 0) {
124214
- document.body.style.cursor = "pointer";
124953
+ self._navCubeCanvas.style.cursor = "pointer";
124215
124954
  if (lastAreaId >= 0) {
124216
124955
  self._cubeTextureCanvas.setAreaHighlighted(lastAreaId, false);
124217
124956
  self._repaint();
@@ -124237,7 +124976,7 @@ class NavCubePlugin extends Plugin {
124237
124976
  self._repaint();
124238
124977
  lastAreaId = -1;
124239
124978
  }
124240
- document.body.style.cursor = "pointer";
124979
+ self._navCubeCanvas.style.cursor = "pointer";
124241
124980
  if (lastAreaId >= 0) {
124242
124981
  self._cubeTextureCanvas.setAreaHighlighted(lastAreaId, false);
124243
124982
  self._repaint();
@@ -124256,7 +124995,7 @@ class NavCubePlugin extends Plugin {
124256
124995
  }
124257
124996
  });
124258
124997
 
124259
- document.addEventListener("mousemove", self._onMouseMove = function (e) {
124998
+ self._navCubeCanvas.addEventListener("mousemove", self._onMouseMove = function (e) {
124260
124999
  if (lastAreaId >= 0) {
124261
125000
  self._cubeTextureCanvas.setAreaHighlighted(lastAreaId, false);
124262
125001
  self._repaint();
@@ -124268,7 +125007,7 @@ class NavCubePlugin extends Plugin {
124268
125007
  if (down) {
124269
125008
  var posX = e.clientX;
124270
125009
  var posY = e.clientY;
124271
- document.body.style.cursor = "move";
125010
+ self._navCubeCanvas.style.cursor = "move";
124272
125011
  actionMove(posX, posY);
124273
125012
  return;
124274
125013
  }
@@ -124282,7 +125021,7 @@ class NavCubePlugin extends Plugin {
124282
125021
  });
124283
125022
  if (hit) {
124284
125023
  if (hit.uv) {
124285
- document.body.style.cursor = "pointer";
125024
+ self._navCubeCanvas.style.cursor = "pointer";
124286
125025
  var areaId = self._cubeTextureCanvas.getArea(hit.uv);
124287
125026
  if (areaId === lastAreaId) {
124288
125027
  return;
@@ -124297,7 +125036,7 @@ class NavCubePlugin extends Plugin {
124297
125036
  }
124298
125037
  }
124299
125038
  } else {
124300
- document.body.style.cursor = "default";
125039
+ self._navCubeCanvas.style.cursor = "default";
124301
125040
  if (lastAreaId >= 0) {
124302
125041
  self._cubeTextureCanvas.setAreaHighlighted(lastAreaId, false);
124303
125042
  self._repaint();
@@ -124560,8 +125299,8 @@ class NavCubePlugin extends Plugin {
124560
125299
  this._navCubeCanvas.removeEventListener("mouseleave", this._onMouseLeave);
124561
125300
  this._navCubeCanvas.removeEventListener("mousedown", this._onMouseDown);
124562
125301
 
124563
- document.removeEventListener("mousemove", this._onMouseMove);
124564
- document.removeEventListener("mouseup", this._onMouseUp);
125302
+ this._navCubeCanvas.removeEventListener("mousemove", this._onMouseMove);
125303
+ this._navCubeCanvas.removeEventListener("mouseup", this._onMouseUp);
124565
125304
 
124566
125305
  this._navCubeCanvas = null;
124567
125306
  this._cubeTextureCanvas.destroy();
@@ -125955,8 +126694,8 @@ class Control {
125955
126694
  const canvasPos = math.vec2();
125956
126695
 
125957
126696
  const copyCanvasPos = (event, vec2) => {
125958
- vec2[0] = event.clientX;
125959
- vec2[1] = event.clientY;
126697
+ vec2[0] = event.pageX;
126698
+ vec2[1] = event.pageY;
125960
126699
  transformToNode(canvas.ownerDocument.documentElement, canvas, vec2);
125961
126700
  };
125962
126701
 
@@ -129454,46 +130193,27 @@ class FaceAlignedSectionPlanesPlugin extends Plugin {
129454
130193
  * });
129455
130194
  *
129456
130195
  * // Add a GLTFModelsPlugin
129457
- * var gltfLoaderPlugin = new GLTFModelsPlugin(viewer, {
129458
- * id: "GLTFModels" // Default value
129459
- * });
130196
+ * var xktLoaderPlugin = new XKTLoaderPlugin(viewer);
129460
130197
  *
129461
130198
  * // Add a SkyboxesPlugin
129462
- * var skyboxesPlugin = new SkyboxesPlugin(viewer, {
129463
- * id: "Skyboxes" // Default value
129464
- * });
130199
+ * var skyboxesPlugin = new SkyboxesPlugin(viewer);
130200
+ *
130201
+ * // Create a skybox
130202
+ * skyBoxesPlugin.createSkybox({
130203
+ * src: [
130204
+ * "./assets/images/posx.jpg",
130205
+ * "./assets/images/negx.jpg",
130206
+ * "./assets/images/posy.jpg",
130207
+ * "./assets/images/negy.jpg",
130208
+ * "./assets/images/posz.jpg",
130209
+ * "./assets/images/negz.jpg"
130210
+ * ]
130211
+ * })
129465
130212
  *
129466
- * // Load a glTF model
129467
- * const model = gltfLoaderPlugin.load({
130213
+ * // Load an XKT model
130214
+ * const model = xktLoaderPlugin.load({
129468
130215
  * id: "myModel",
129469
- * src: "./models/gltf/mygltfmodel.gltf"
129470
- * });
129471
- *
129472
- * // Create three directional World-space lights. "World" means that they will appear as if part
129473
- * // of the world, instead of "View", where they move with the user's head.
129474
- *
129475
- * skyboxesPlugin.createLight({
129476
- * id: "keyLight",
129477
- * dir: [0.8, -0.6, -0.8],
129478
- * color: [1.0, 0.3, 0.3],
129479
- * intensity: 1.0,
129480
- * space: "world"
129481
- * });
129482
- *
129483
- * skyboxesPlugin.createLight({
129484
- * id: "fillLight",
129485
- * dir: [-0.8, -0.4, -0.4],
129486
- * color: [0.3, 1.0, 0.3],
129487
- * intensity: 1.0,
129488
- * space: "world"
129489
- * });
129490
- *
129491
- * skyboxesPlugin.createDirLight({
129492
- * id: "rimLight",
129493
- * dir: [0.2, -0.8, 0.8],
129494
- * color: [0.6, 0.6, 0.6],
129495
- * intensity: 1.0,
129496
- * space: "world"
130216
+ * src: "./models/xkt/myModel.xkt"
129497
130217
  * });
129498
130218
  *
129499
130219
  * @class SkyboxesPlugin
@@ -129519,23 +130239,17 @@ class SkyboxesPlugin extends Plugin {
129519
130239
  /**
129520
130240
  Creates a skybox.
129521
130241
 
129522
- @param {String} id Unique ID to assign to the skybox.
129523
130242
  @param {Object} params Skybox configuration.
130243
+ @param {String} [params.id] Optional ID, unique among all components in the parent {Scene}, generated automatically when omitted.
130244
+ @param {String | String[]} [params.src=null] Path to skybox texture
130245
+ @param {Number} [params.encoding=LinearEncoding] Texture encoding format. See the {@link Texture#encoding} property for more info.
130246
+ @param {Number} [params.size=1000] Size of this Skybox, given as the distance from the center at ````[0,0,0]```` to each face.
129524
130247
  @param {Boolean} [params.active=true] Whether the skybox plane is initially active. Only skyboxes while this is true.
129525
130248
  @returns {Skybox} The new skybox.
129526
130249
  */
129527
- createSkybox(id, params) {
129528
- if (this.viewer.scene.components[id]) {
129529
- this.error("Component with this ID already exists: " + id);
129530
- return this;
129531
- }
129532
- var skybox = new Skybox(this.viewer.scene, {
129533
- id: id,
129534
- pos: params.pos,
129535
- dir: params.dir,
129536
- active: true
129537
- });
129538
- this.skyboxes[id] = skybox;
130250
+ createSkybox(params) {
130251
+ const skybox = new Skybox(this.viewer.scene, params);
130252
+ this.skyboxes[skybox.id] = skybox;
129539
130253
  return skybox;
129540
130254
  }
129541
130255
 
@@ -130198,6 +130912,15 @@ class STLLoaderPlugin extends Plugin {
130198
130912
  */
130199
130913
  class RenderService {
130200
130914
 
130915
+ /**
130916
+ * @constructor
130917
+ *
130918
+ * @param {HTMLElement} treeViewContainer Container element for the tree which uses this render service.
130919
+ */
130920
+ constructor(treeViewContainer) {
130921
+ this._treeViewContainer = treeViewContainer;
130922
+ }
130923
+
130201
130924
  /*
130202
130925
  * Creates the root node of the tree.
130203
130926
  * @return {HTMLElement} The root node of the tree.
@@ -130322,7 +131045,7 @@ class RenderService {
130322
131045
  }
130323
131046
 
130324
131047
  getSwitchElement(nodeId) {
130325
- return document.getElementById(`switch-${nodeId}`);
131048
+ return this._treeViewContainer.querySelector(`[id="switch-${nodeId}"]`);
130326
131049
  }
130327
131050
 
130328
131051
  isChecked(element) {
@@ -130330,7 +131053,7 @@ class RenderService {
130330
131053
  }
130331
131054
 
130332
131055
  setCheckbox(nodeId, checked, indeterminate = false) {
130333
- const checkbox = document.getElementById(`checkbox-${nodeId}`);
131056
+ const checkbox = this._treeViewContainer.querySelector(`[id="checkbox-${nodeId}"]`);
130334
131057
  if (checkbox) {
130335
131058
  if (checked !== checkbox.checked) {
130336
131059
  checkbox.checked = checked;
@@ -130345,7 +131068,7 @@ class RenderService {
130345
131068
  }
130346
131069
 
130347
131070
  setXRayed(nodeId, xrayed) {
130348
- const treeNode = document.getElementById(nodeId);
131071
+ const treeNode = this._treeViewContainer.querySelector(`[id="${nodeId}"]`);
130349
131072
  if (treeNode) {
130350
131073
  if (xrayed) {
130351
131074
  treeNode.classList.add('xrayed-node');
@@ -130356,7 +131079,7 @@ class RenderService {
130356
131079
  }
130357
131080
 
130358
131081
  setHighlighted(nodeId, highlighted) {
130359
- const treeNode = document.getElementById(nodeId);
131082
+ const treeNode = this._treeViewContainer.querySelector(`[id="${nodeId}"]`);
130360
131083
  if (treeNode) {
130361
131084
  if (highlighted) {
130362
131085
  treeNode.scrollIntoView({block: "center"});
@@ -130782,7 +131505,7 @@ class TreeViewPlugin extends Plugin {
130782
131505
  this._sortNodes = cfg.sortNodes;
130783
131506
  this._pruneEmptyNodes = cfg.pruneEmptyNodes;
130784
131507
  this._showListItemElementId = null;
130785
- this._renderService = cfg.renderService || new RenderService();
131508
+ this._renderService = cfg.renderService || new RenderService(this._containerElement);
130786
131509
  this._showIndeterminate = cfg.showIndeterminate ?? false;
130787
131510
  this._showProjectNode = cfg.showProjectNode ?? false;
130788
131511
  this._elevationSortFunction = cfg.elevationSortFunction ?? undefined;
@@ -168436,151 +169159,6 @@ const hex2rgb = function(color) {
168436
169159
  return [ rgb(0), rgb(2), rgb(4) ];
168437
169160
  };
168438
169161
 
168439
- const triangulateEarClipping = function(planeCoords) {
168440
-
168441
- const polygonVertices = [ ];
168442
- for (let i = 0; i < planeCoords.length; ++i)
168443
- polygonVertices.push(i);
168444
-
168445
- const isCCW = (function() {
168446
- const ba = math.vec2();
168447
- const bc = math.vec2();
168448
-
168449
- let anglesSum = 0;
168450
-
168451
- for (let i = 0; i < polygonVertices.length; ++i)
168452
- {
168453
- const a = planeCoords[polygonVertices[i]];
168454
- const b = planeCoords[polygonVertices[(i + 1) % polygonVertices.length]];
168455
- const c = planeCoords[polygonVertices[(i + 2) % polygonVertices.length]];
168456
-
168457
- math.subVec2(a, b, ba);
168458
- math.subVec2(c, b, bc);
168459
-
168460
- const theta = math.dotVec2(ba, bc) / Math.sqrt(math.sqLenVec2(ba) * math.sqLenVec2(bc));
168461
- const angle = Math.acos(Math.max(-1, Math.min(theta, 1)));
168462
- const convex = (ba[0] * bc[1] - ba[1] * bc[0]) >= 0;
168463
- anglesSum += convex ? angle : (2 * Math.PI - angle);
168464
- }
168465
-
168466
- return anglesSum < (polygonVertices.length * Math.PI);
168467
- })();
168468
-
168469
- const pointInTriangle = (function() {
168470
- const sign = (p1, p2, p3) => {
168471
- return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]);
168472
- };
168473
-
168474
- return (pt, v1, v2, v3) => {
168475
- const d1 = sign(pt, v1, v2);
168476
- const d2 = sign(pt, v2, v3);
168477
- const d3 = sign(pt, v3, v1);
168478
-
168479
- const has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
168480
- const has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
168481
-
168482
- return !(has_neg && has_pos);
168483
- };
168484
- })();
168485
-
168486
- const baseTriangles = [ ];
168487
-
168488
- const vertices = (isCCW ? polygonVertices : polygonVertices.slice(0).reverse()).map(i => ({ idx: i }));
168489
- vertices.forEach((v, i) => {
168490
- v.prev = vertices[(i - 1 + vertices.length) % vertices.length];
168491
- v.next = vertices[(i + 1) % vertices.length];
168492
- });
168493
-
168494
- const ba = math.vec2();
168495
- const bc = math.vec2();
168496
-
168497
- while (vertices.length > 2) {
168498
- let earIdx = 0;
168499
- while (true) {
168500
- if (earIdx >= vertices.length)
168501
- {
168502
- throw `isCCW = ${isCCW}; earIdx = ${earIdx}; len = ${vertices.length}`;
168503
- }
168504
- const v = vertices[earIdx];
168505
-
168506
- const a = planeCoords[v.prev.idx];
168507
- const b = planeCoords[v.idx];
168508
- const c = planeCoords[v.next.idx];
168509
-
168510
- math.subVec2(a, b, ba);
168511
- math.subVec2(c, b, bc);
168512
-
168513
- if (((ba[0] * bc[1] - ba[1] * bc[0]) >= 0) // a convex vertex
168514
- &&
168515
- vertices.every( // no other vertices inside
168516
- vv => ((vv === v)
168517
- ||
168518
- (vv === v.prev)
168519
- ||
168520
- (vv === v.next)
168521
- ||
168522
- !pointInTriangle(planeCoords[vv.idx], a, b, c))))
168523
- break;
168524
- ++earIdx;
168525
- }
168526
-
168527
- const ear = vertices[earIdx];
168528
- vertices.splice(earIdx, 1);
168529
-
168530
- baseTriangles.push([ ear.idx, ear.next.idx, ear.prev.idx ]);
168531
-
168532
- const prev = ear.prev;
168533
- prev.next = ear.next;
168534
- const next = ear.next;
168535
- next.prev = ear.prev;
168536
- }
168537
-
168538
- return [ planeCoords, baseTriangles, isCCW ];
168539
- };
168540
-
168541
- const marker3D = function(scene, color) {
168542
- const marker = new Dot3D(scene, {}, scene.canvas.canvas.parentNode, {
168543
- borderColor: "white",
168544
- fillColor: color,
168545
- zIndex: 100
168546
- });
168547
-
168548
- return {
168549
- update: function(worldPos) {
168550
- if (worldPos)
168551
- {
168552
- marker.worldPos = worldPos;
168553
- }
168554
- marker.setVisible(!!worldPos);
168555
- },
168556
-
168557
- setHighlighted: h => marker.setHighlighted(h),
168558
- getCanvasPos: () => marker.canvasPos,
168559
- getWorldPos: () => marker.worldPos,
168560
- destroy: () => marker.destroy()
168561
- };
168562
- };
168563
-
168564
- const wire3D = function(scene, color, startWorldPos) {
168565
- const wire = new Wire3D(scene, scene.canvas.canvas.ownerDocument.body, {
168566
- color: color,
168567
- thickness: 1,
168568
- thicknessClickable: 6
168569
- });
168570
- wire.setVisible(false);
168571
- return {
168572
- update: endWorldPos => {
168573
- if (endWorldPos)
168574
- {
168575
- wire.setEnds(startWorldPos, endWorldPos);
168576
- }
168577
- wire.setVisible(!!endWorldPos);
168578
- },
168579
-
168580
- destroy: () => wire.destroy()
168581
- };
168582
- };
168583
-
168584
169162
  const basePolygon3D = function(scene, color, alpha) {
168585
169163
  let mesh = null;
168586
169164
 
@@ -169560,7 +170138,8 @@ const startPolysurfaceZoneCreateUI = function(scene, zoneAltitude, zoneHeight, z
169560
170138
 
169561
170139
  (function selectNextPoint(markers) {
169562
170140
  const marker = marker3D(scene, zoneColor);
169563
- const wire = (markers.length > 0) && wire3D(scene, zoneColor, markers[markers.length - 1].getWorldPos());
170141
+ const wire = (markers.length > 0) && wire3D(scene, zoneColor);
170142
+ const wireStart = wire && markers[markers.length - 1].getWorldPos();
169564
170143
 
169565
170144
  cleanups.push(() => {
169566
170145
  marker.destroy();
@@ -169620,7 +170199,7 @@ const startPolysurfaceZoneCreateUI = function(scene, zoneAltitude, zoneHeight, z
169620
170199
  () => {
169621
170200
  updatePointerLens(null);
169622
170201
  marker.update(null);
169623
- wire && wire.update(null);
170202
+ wire && wire.update(wireStart, null);
169624
170203
  basePolygon.updateBase((markers.length > 2) ? markers.map(m => m.getWorldPos()) : null);
169625
170204
  },
169626
170205
  (canvasPos, worldPos) => {
@@ -169628,7 +170207,7 @@ const startPolysurfaceZoneCreateUI = function(scene, zoneAltitude, zoneHeight, z
169628
170207
  firstMarker && firstMarker.setHighlighted(!! snappedFirst);
169629
170208
  updatePointerLens(snappedFirst ? snappedFirst.canvasPos : canvasPos);
169630
170209
  marker.update((! snappedFirst) && worldPos);
169631
- wire && wire.update(snappedFirst ? snappedFirst.worldPos : worldPos);
170210
+ wire && wire.update(wireStart, snappedFirst ? snappedFirst.worldPos : worldPos);
169632
170211
  if ((markers.length >= 2))
169633
170212
  {
169634
170213
  const pos = markers.map(m => m.getWorldPos()).concat(snappedFirst ? [] : [worldPos]);
@@ -169672,7 +170251,7 @@ const startPolysurfaceZoneCreateUI = function(scene, zoneAltitude, zoneHeight, z
169672
170251
  else
169673
170252
  {
169674
170253
  marker.update(worldPos);
169675
- wire && wire.update(worldPos);
170254
+ wire && wire.update(wireStart, worldPos);
169676
170255
  selectNextPoint(markers.concat(marker));
169677
170256
  }
169678
170257
  });
@@ -170290,6 +170869,8 @@ exports.ZonesPolysurfaceTouchControl = ZonesPolysurfaceTouchControl;
170290
170869
  exports.ZonesTouchControl = ZonesTouchControl;
170291
170870
  exports.activateDraggableDot = activateDraggableDot;
170292
170871
  exports.activateDraggableDots = activateDraggableDots;
170872
+ exports.addMousePressListener = addMousePressListener;
170873
+ exports.addTouchPressListener = addTouchPressListener;
170293
170874
  exports.buildBoxGeometry = buildBoxGeometry;
170294
170875
  exports.buildBoxLinesGeometry = buildBoxLinesGeometry;
170295
170876
  exports.buildBoxLinesGeometryFromAABB = buildBoxLinesGeometryFromAABB;
@@ -170309,6 +170890,7 @@ exports.getPlaneRTCPos = getPlaneRTCPos;
170309
170890
  exports.isTriangleMeshSolid = isTriangleMeshSolid;
170310
170891
  exports.load3DSGeometry = load3DSGeometry;
170311
170892
  exports.loadOBJGeometry = loadOBJGeometry;
170893
+ exports.marker3D = marker3D;
170312
170894
  exports.math = math;
170313
170895
  exports.meshSurfaceArea = meshSurfaceArea;
170314
170896
  exports.meshVolume = meshVolume;
@@ -170316,9 +170898,12 @@ exports.os = os;
170316
170898
  exports.rtcToWorldPos = rtcToWorldPos;
170317
170899
  exports.sRGBEncoding = sRGBEncoding;
170318
170900
  exports.setFrustum = setFrustum;
170901
+ exports.startPolygonCreate = startPolygonCreate;
170319
170902
  exports.stats = stats;
170320
170903
  exports.touchPointSelector = touchPointSelector;
170321
170904
  exports.transformToNode = transformToNode;
170905
+ exports.triangulateEarClipping = triangulateEarClipping;
170322
170906
  exports.utils = utils;
170907
+ exports.wire3D = wire3D;
170323
170908
  exports.worldToRTCPos = worldToRTCPos;
170324
170909
  exports.worldToRTCPositions = worldToRTCPositions;