@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeokit/xeokit-sdk",
3
- "version": "2.6.72",
3
+ "version": "2.6.75",
4
4
  "description": "3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision",
5
5
  "module": "./dist/xeokit-sdk.es.js",
6
6
  "main": "./dist/xeokit-sdk.cjs.js",
@@ -298,7 +298,8 @@ class ContextMenu {
298
298
  this._shown = false; // True when the ContextMenu is visible
299
299
  this._nextId = 0;
300
300
  this._parentNode = cfg.parentNode || document.body;
301
-
301
+ this._offsetParent = (this._parentNode instanceof ShadowRoot) ? this._parentNode.host : this._parentNode;
302
+
302
303
  /**
303
304
  * Subscriptions to events fired at this ContextMenu.
304
305
  * @private
@@ -510,7 +511,7 @@ class ContextMenu {
510
511
  for (let i = 0, len = this._menuList.length; i < len; i++) {
511
512
  const menu = this._menuList[i];
512
513
  const menuElement = menu.menuElement;
513
- menuElement.parentElement.removeChild(menuElement);
514
+ menuElement.remove();
514
515
  }
515
516
  this._itemsCfg = [];
516
517
  this._rootMenu = null;
@@ -738,21 +739,22 @@ class ContextMenu {
738
739
 
739
740
  const itemRect = itemElement.getBoundingClientRect();
740
741
  const menuRect = subMenuElement.getBoundingClientRect();
742
+ const offsetRect = self._offsetParent.getBoundingClientRect();
741
743
 
742
744
  const subMenuWidth = 200; // TODO
743
- const showOnRight = (itemRect.right + subMenuWidth) < window.innerWidth;
744
- const showOnLeft = (itemRect.left - subMenuWidth) > 0;
745
-
745
+ const showOnRight = (itemRect.right + subMenuWidth) < offsetRect.right;
746
+ const showOnLeft = (itemRect.left - subMenuWidth) > offsetRect.left;
747
+
746
748
  if(showOnRight)
747
- self._showMenu(subMenu.id, itemRect.right - 5, itemRect.top - 1);
749
+ self._showMenu(subMenu.id, itemRect.right + window.scrollX - 5, itemRect.top + window.scrollY - 1);
748
750
  else if (showOnLeft)
749
- self._showMenu(subMenu.id, itemRect.left - subMenuWidth, itemRect.top - 1);
751
+ self._showMenu(subMenu.id, itemRect.left - subMenuWidth + window.scrollX, itemRect.top + window.scrollY - 1);
750
752
  else {
751
- const spaceOnLeft = itemRect.left, spaceOnRight = window.innerWidth - itemRect.right;
753
+ const spaceOnLeft = itemRect.left - offsetRect.left, spaceOnRight = offsetRect.right - itemRect.right;
752
754
  if(spaceOnRight > spaceOnLeft)
753
- self._showMenu(subMenu.id, itemRect.right - 5 - (subMenuWidth - spaceOnRight), itemRect.top - 1);
755
+ self._showMenu(subMenu.id, itemRect.right - 5 - (subMenuWidth - spaceOnRight), itemRect.top + window.scrollY - 1);
754
756
  else
755
- self._showMenu(subMenu.id, itemRect.left - spaceOnLeft, itemRect.top - 1);
757
+ self._showMenu(subMenu.id, itemRect.left - spaceOnLeft, itemRect.top + window.scrollY - 1);
756
758
  }
757
759
 
758
760
  lastSubMenu = subMenu;
@@ -945,14 +947,21 @@ class ContextMenu {
945
947
  menuElement.style.display = 'block';
946
948
  const menuHeight = menuElement.offsetHeight;
947
949
  const menuWidth = menuElement.offsetWidth;
948
- if ((pageY + menuHeight) > window.innerHeight) {
949
- pageY = window.innerHeight - menuHeight;
950
+
951
+ const offsetRect = this._offsetParent.getBoundingClientRect();
952
+
953
+ const bottomContainerBorder = offsetRect.bottom + window.scrollY;
954
+ const rightContainerBorder = offsetRect.right + window.scrollX;
955
+ if ((pageY + menuHeight) > bottomContainerBorder) {
956
+ pageY = bottomContainerBorder- menuHeight;
950
957
  }
951
- if ((pageX + menuWidth) > window.innerWidth) {
952
- pageX = window.innerWidth - menuWidth;
958
+ if ((pageX + menuWidth) > rightContainerBorder) {
959
+ pageX = rightContainerBorder - menuWidth;
953
960
  }
954
- menuElement.style.left = pageX + 'px';
955
- menuElement.style.top = pageY + 'px';
961
+
962
+
963
+ menuElement.style.left = pageX - offsetRect.left - window.scrollX + 'px';
964
+ menuElement.style.top = pageY - offsetRect.top - window.scrollY + 'px';
956
965
  }
957
966
 
958
967
  _hideMenuElement(menuElement) {
@@ -108,6 +108,7 @@ class FastNavPlugin extends Plugin {
108
108
  * @param {Number} [cfg.scaleCanvasResolutionFactor=0.6] The factor by which we downscale the canvas resolution whenever we interact with the Viewer.
109
109
  * @param {Boolean} [cfg.delayBeforeRestore=true] Whether to temporarily have a delay before restoring normal rendering after we stop interacting with the Viewer.
110
110
  * @param {Number} [cfg.delayBeforeRestoreSeconds=0.5] Delay in seconds before restoring normal rendering after we stop interacting with the Viewer.
111
+ * @param {Function} [cfg.onMoved] Optional callback function fired during moving mode, should return the callback function that will be fired when the interaction stops.
111
112
  */
112
113
  constructor(viewer, cfg = {}) {
113
114
 
@@ -123,6 +124,8 @@ class FastNavPlugin extends Plugin {
123
124
  this._scaleCanvasResolutionFactor = cfg.scaleCanvasResolutionFactor || 0.6;
124
125
  this._delayBeforeRestore = (cfg.delayBeforeRestore !== false);
125
126
  this._delayBeforeRestoreSeconds = cfg.delayBeforeRestoreSeconds || 0.5;
127
+ this._onMoved = cfg.onMoved;
128
+ this._onStopped = null;
126
129
 
127
130
  let timer = this._delayBeforeRestoreSeconds * 1000;
128
131
  let fastMode = false;
@@ -141,6 +144,10 @@ class FastNavPlugin extends Plugin {
141
144
  } else {
142
145
  viewer.scene.canvas.resolutionScale = this._defaultScaleCanvasResolutionFactor;
143
146
  }
147
+ if (this._onMoved) {
148
+ this._onStopped = this._onMoved();
149
+ }
150
+
144
151
  fastMode = true;
145
152
  }
146
153
  };
@@ -152,6 +159,9 @@ class FastNavPlugin extends Plugin {
152
159
  viewer.scene._renderer.setPBREnabled(true);
153
160
  viewer.scene._renderer.setSAOEnabled(true);
154
161
  viewer.scene._renderer.setTransparentEnabled(true);
162
+ if (this._onStopped) {
163
+ this._onStopped();
164
+ }
155
165
  fastMode = false;
156
166
  };
157
167
 
@@ -12,46 +12,27 @@ import {Skybox} from "../../viewer/scene/skybox/Skybox.js"
12
12
  * });
13
13
  *
14
14
  * // Add a GLTFModelsPlugin
15
- * var gltfLoaderPlugin = new GLTFModelsPlugin(viewer, {
16
- * id: "GLTFModels" // Default value
17
- * });
15
+ * var xktLoaderPlugin = new XKTLoaderPlugin(viewer);
18
16
  *
19
17
  * // Add a SkyboxesPlugin
20
- * var skyboxesPlugin = new SkyboxesPlugin(viewer, {
21
- * id: "Skyboxes" // Default value
22
- * });
18
+ * var skyboxesPlugin = new SkyboxesPlugin(viewer);
19
+ *
20
+ * // Create a skybox
21
+ * skyBoxesPlugin.createSkybox({
22
+ * src: [
23
+ * "./assets/images/posx.jpg",
24
+ * "./assets/images/negx.jpg",
25
+ * "./assets/images/posy.jpg",
26
+ * "./assets/images/negy.jpg",
27
+ * "./assets/images/posz.jpg",
28
+ * "./assets/images/negz.jpg"
29
+ * ]
30
+ * })
23
31
  *
24
- * // Load a glTF model
25
- * const model = gltfLoaderPlugin.load({
32
+ * // Load an XKT model
33
+ * const model = xktLoaderPlugin.load({
26
34
  * id: "myModel",
27
- * src: "./models/gltf/mygltfmodel.gltf"
28
- * });
29
- *
30
- * // Create three directional World-space lights. "World" means that they will appear as if part
31
- * // of the world, instead of "View", where they move with the user's head.
32
- *
33
- * skyboxesPlugin.createLight({
34
- * id: "keyLight",
35
- * dir: [0.8, -0.6, -0.8],
36
- * color: [1.0, 0.3, 0.3],
37
- * intensity: 1.0,
38
- * space: "world"
39
- * });
40
- *
41
- * skyboxesPlugin.createLight({
42
- * id: "fillLight",
43
- * dir: [-0.8, -0.4, -0.4],
44
- * color: [0.3, 1.0, 0.3],
45
- * intensity: 1.0,
46
- * space: "world"
47
- * });
48
- *
49
- * skyboxesPlugin.createDirLight({
50
- * id: "rimLight",
51
- * dir: [0.2, -0.8, 0.8],
52
- * color: [0.6, 0.6, 0.6],
53
- * intensity: 1.0,
54
- * space: "world"
35
+ * src: "./models/xkt/myModel.xkt"
55
36
  * });
56
37
  *
57
38
  * @class SkyboxesPlugin
@@ -77,23 +58,17 @@ class SkyboxesPlugin extends Plugin {
77
58
  /**
78
59
  Creates a skybox.
79
60
 
80
- @param {String} id Unique ID to assign to the skybox.
81
61
  @param {Object} params Skybox configuration.
62
+ @param {String} [params.id] Optional ID, unique among all components in the parent {Scene}, generated automatically when omitted.
63
+ @param {String | String[]} [params.src=null] Path to skybox texture
64
+ @param {Number} [params.encoding=LinearEncoding] Texture encoding format. See the {@link Texture#encoding} property for more info.
65
+ @param {Number} [params.size=1000] Size of this Skybox, given as the distance from the center at ````[0,0,0]```` to each face.
82
66
  @param {Boolean} [params.active=true] Whether the skybox plane is initially active. Only skyboxes while this is true.
83
67
  @returns {Skybox} The new skybox.
84
68
  */
85
- createSkybox(id, params) {
86
- if (this.viewer.scene.components[id]) {
87
- this.error("Component with this ID already exists: " + id);
88
- return this;
89
- }
90
- var skybox = new Skybox(this.viewer.scene, {
91
- id: id,
92
- pos: params.pos,
93
- dir: params.dir,
94
- active: true || params.active
95
- });
96
- this.skyboxes[id] = skybox;
69
+ createSkybox(params) {
70
+ const skybox = new Skybox(this.viewer.scene, params);
71
+ this.skyboxes[skybox.id] = skybox;
97
72
  return skybox;
98
73
  }
99
74
 
@@ -5,6 +5,15 @@ import { addContextMenuListener } from "../lib/html/MenuEvent.js";
5
5
  */
6
6
  export class RenderService {
7
7
 
8
+ /**
9
+ * @constructor
10
+ *
11
+ * @param {HTMLElement} treeViewContainer Container element for the tree which uses this render service.
12
+ */
13
+ constructor(treeViewContainer) {
14
+ this._treeViewContainer = treeViewContainer;
15
+ }
16
+
8
17
  /*
9
18
  * Creates the root node of the tree.
10
19
  * @return {HTMLElement} The root node of the tree.
@@ -129,7 +138,7 @@ export class RenderService {
129
138
  }
130
139
 
131
140
  getSwitchElement(nodeId) {
132
- return document.getElementById(`switch-${nodeId}`);
141
+ return this._treeViewContainer.querySelector(`[id="switch-${nodeId}"]`);
133
142
  }
134
143
 
135
144
  isChecked(element) {
@@ -137,7 +146,7 @@ export class RenderService {
137
146
  }
138
147
 
139
148
  setCheckbox(nodeId, checked, indeterminate = false) {
140
- const checkbox = document.getElementById(`checkbox-${nodeId}`);
149
+ const checkbox = this._treeViewContainer.querySelector(`[id="checkbox-${nodeId}"]`);
141
150
  if (checkbox) {
142
151
  if (checked !== checkbox.checked) {
143
152
  checkbox.checked = checked;
@@ -152,7 +161,7 @@ export class RenderService {
152
161
  }
153
162
 
154
163
  setXRayed(nodeId, xrayed) {
155
- const treeNode = document.getElementById(nodeId);
164
+ const treeNode = this._treeViewContainer.querySelector(`[id="${nodeId}"]`);
156
165
  if (treeNode) {
157
166
  if (xrayed) {
158
167
  treeNode.classList.add('xrayed-node');
@@ -163,7 +172,7 @@ export class RenderService {
163
172
  }
164
173
 
165
174
  setHighlighted(nodeId, highlighted) {
166
- const treeNode = document.getElementById(nodeId);
175
+ const treeNode = this._treeViewContainer.querySelector(`[id="${nodeId}"]`);
167
176
  if (treeNode) {
168
177
  if (highlighted) {
169
178
  treeNode.scrollIntoView({block: "center"});
@@ -415,7 +415,7 @@ export class TreeViewPlugin extends Plugin {
415
415
  this._sortNodes = cfg.sortNodes;
416
416
  this._pruneEmptyNodes = cfg.pruneEmptyNodes;
417
417
  this._showListItemElementId = null;
418
- this._renderService = cfg.renderService || new RenderService();
418
+ this._renderService = cfg.renderService || new RenderService(this._containerElement);
419
419
  this._showIndeterminate = cfg.showIndeterminate ?? false;
420
420
  this._showProjectNode = cfg.showProjectNode ?? false;
421
421
  this._elevationSortFunction = cfg.elevationSortFunction ?? undefined;
@@ -363,22 +363,6 @@ class Viewer {
363
363
  this.sendToPlugins("snapshotStarting"); // Tells plugins to hide things that shouldn't be in snapshot
364
364
  }
365
365
 
366
- const captured = {};
367
- for (let i = 0, len = this._plugins.length; i < len; i++) {
368
- const plugin = this._plugins[i];
369
- if (plugin.getContainerElement) {
370
- const container = plugin.getContainerElement();
371
- if (container !== document.body) {
372
- if (!captured[container.id]) {
373
- captured[container.id] = true;
374
- html2canvas(container).then(function (canvas) {
375
- document.body.appendChild(canvas);
376
- });
377
- }
378
- }
379
- }
380
- }
381
-
382
366
  // firing "rendering" is necessary to trigger DTX{Lines,Triangles}Layer::_uploadDeferredFlags
383
367
  this.scene.fire("rendering", { }, true);
384
368
 
@@ -13,6 +13,9 @@ const tempVec4a = math.vec4();
13
13
  const tempVec4b = math.vec4();
14
14
  const tempVec4c = math.vec4();
15
15
 
16
+ const TOP_LIMIT = 0.001;
17
+ const BOTTOM_LIMIT = Math.PI - 0.001;
18
+
16
19
 
17
20
  /** @private */
18
21
  class PivotController {
@@ -188,14 +191,9 @@ class PivotController {
188
191
  */
189
192
  startPivot() {
190
193
 
191
- if (this._cameraLookingDownwards()) {
192
- this._pivoting = false;
193
- return false;
194
- }
195
-
196
194
  const camera = this._scene.camera;
197
195
 
198
- let lookat = math.lookAtMat4v(camera.eye, camera.look, camera.worldUp);
196
+ let lookat = math.lookAtMat4v(camera.eye, camera.look, camera.up);
199
197
  math.transformPoint3(lookat, this.getPivotPos(), this._cameraOffset);
200
198
 
201
199
  const pivotPos = this.getPivotPos();
@@ -298,8 +296,31 @@ class PivotController {
298
296
  dx = -dx;
299
297
  }
300
298
  this._azimuth += -dx * .01;
301
- this._polar += dy * .01;
302
- this._polar = math.clamp(this._polar, .001, Math.PI - .001);
299
+
300
+ const isMovingUp = dy < 0;
301
+ const isMovingDown = dy > 0;
302
+
303
+ // Track if we're at limits - only check if we're very close to the limit
304
+ const atTopLimit = Math.abs(this._polar - TOP_LIMIT) < 0.005;
305
+ const atBottomLimit = Math.abs(this._polar - BOTTOM_LIMIT) < 0.005;
306
+
307
+ let newPolar = this._polar + dy * .01;
308
+
309
+ // Case 1: At top limit and trying to go beyond
310
+ if (atTopLimit && isMovingUp) {
311
+ newPolar = TOP_LIMIT;
312
+ }
313
+ // Case 2: At bottom limit and trying to go beyond
314
+ else if (atBottomLimit && isMovingDown) {
315
+ newPolar = BOTTOM_LIMIT;
316
+ }
317
+ // Case 3: Normal rotation or moving away from a limit
318
+ else {
319
+ newPolar = math.clamp(newPolar, TOP_LIMIT, BOTTOM_LIMIT);
320
+ }
321
+
322
+ this._polar = newPolar;
323
+
303
324
  const pos = [
304
325
  this._radius * Math.sin(this._polar) * Math.sin(this._azimuth),
305
326
  this._radius * Math.cos(this._polar),
@@ -66,7 +66,7 @@ class LineSet extends Component {
66
66
  * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.
67
67
  * @param {Number[]} cfg.positions World-space 3D vertex positions.
68
68
  * @param {Number[]} [cfg.indices] Indices to connect ````positions```` into line segments. Note that these are separate line segments, not a polyline.
69
- * @param {Number[]} [cfg.color=[0,0,0]] The color of this ````LineSet````. This is both emissive and diffuse.
69
+ * @param {Number[]} [cfg.color=[1,1,1]] The color of this ````LineSet````. This is both emissive and diffuse.
70
70
  * @param {Boolean} [cfg.visible=true] Indicates whether or not this ````LineSet```` is visible.
71
71
  * @param {Number} [cfg.opacity=1.0] ````LineSet````'s initial opacity factor.
72
72
  */
@@ -86,6 +86,9 @@ class LineSet extends Component {
86
86
  }
87
87
  }
88
88
 
89
+ this._color = cfg.color || [1, 1, 1];
90
+ this._opacity = cfg.opacity || 1.0;
91
+
89
92
  this._sceneModel = new SceneModel(this, {
90
93
  isModel: false // Don't register in Scene.models
91
94
  });
@@ -94,7 +97,9 @@ class LineSet extends Component {
94
97
  id: "linesMesh",
95
98
  primitive: "lines",
96
99
  positions: this._positions,
97
- indices: this._indices
100
+ indices: this._indices,
101
+ color: this._color,
102
+ opacity: this._opacity,
98
103
  })
99
104
 
100
105
  this._sceneModel.createEntity({
@@ -3,6 +3,7 @@ import {Mesh} from "../mesh/Mesh.js";
3
3
  import {ReadableGeometry} from "../geometry/ReadableGeometry.js";
4
4
  import {PhongMaterial} from "../materials/PhongMaterial.js";
5
5
  import {Texture} from "../materials/Texture.js";
6
+ import { ClampToEdgeWrapping, LinearEncoding } from "../constants/constants.js";
6
7
 
7
8
  /**
8
9
  * @desc A Skybox.
@@ -14,8 +15,8 @@ class Skybox extends Component {
14
15
  * @param {Component} owner Owner component. When destroyed, the owner will destroy this PointLight as well.
15
16
  * @param {*} [cfg] Skybox configuration
16
17
  * @param {String} [cfg.id] Optional ID, unique among all components in the parent {Scene}, generated automatically when omitted.
17
- * @param {String} [cfg.src=null] Path to skybox texture
18
- * @param {String} [cfg.encoding="linear"] Texture encoding format. See the {@link Texture#encoding} property for more info.
18
+ * @param {String | String[]} [cfg.src=null] Path to skybox texture
19
+ * @param {Number} [cfg.encoding=LinearEncoding] Texture encoding format. See the {@link Texture#encoding} property for more info.
19
20
  * @param {Number} [cfg.size=1000] Size of this Skybox, given as the distance from the center at ````[0,0,0]```` to each face.
20
21
  * @param {Boolean} [cfg.active=true] True when this Skybox is visible.
21
22
  */
@@ -23,6 +24,32 @@ class Skybox extends Component {
23
24
 
24
25
  super(owner, cfg);
25
26
 
27
+ const useMultipleTexture = typeof cfg.src === "object" && cfg.src !== null;
28
+
29
+ if (useMultipleTexture) {
30
+ this._createCombinedTexture(cfg).then(combinedTexture => {
31
+ this._createSkyboxMesh(cfg, combinedTexture);
32
+ }).catch(e => this.error(e));
33
+ } else {
34
+ this._createSkyboxMesh(cfg);
35
+ }
36
+ }
37
+
38
+ /**
39
+ *
40
+ * @private
41
+ * @param {*} cfg
42
+ * @param {Texture} combinedTexture
43
+ */
44
+ _createSkyboxMesh(cfg, combinedTexture = null) {
45
+ const texture = combinedTexture || new Texture(this, {
46
+ src: cfg.src,
47
+ flipY: true,
48
+ wrapS: ClampToEdgeWrapping,
49
+ wrapT: ClampToEdgeWrapping,
50
+ encoding: cfg.encoding || LinearEncoding
51
+ });
52
+
26
53
  this._skyboxMesh = new Mesh(this, {
27
54
 
28
55
  geometry: new ReadableGeometry(this, { // Box-shaped geometry
@@ -53,13 +80,7 @@ class Skybox extends Component {
53
80
  diffuse: [0, 0, 0],
54
81
  specular: [0, 0, 0],
55
82
  emissive: [1, 1, 1],
56
- emissiveMap: new Texture(this, {
57
- src: cfg.src,
58
- flipY: true,
59
- wrapS: "clampToEdge",
60
- wrapT: "clampToEdge",
61
- encoding: cfg.encoding || "sRGB"
62
- }),
83
+ emissiveMap: texture,
63
84
  backfaces: true // Show interior faces of our skybox geometry
64
85
  }),
65
86
  // stationary: true,
@@ -73,6 +94,86 @@ class Skybox extends Component {
73
94
  this.active = cfg.active;
74
95
  }
75
96
 
97
+ /**
98
+ *
99
+ * @private
100
+ * @param {*} cfg
101
+ * @returns {Texture}
102
+ */
103
+ async _createCombinedTexture(cfg) {
104
+ const [
105
+ posX,
106
+ negX,
107
+ posY,
108
+ negY,
109
+ posZ,
110
+ negZ
111
+ ] = cfg.src;
112
+
113
+ if (!posX || !negX || !posY || !negY || !posZ || !negZ) {
114
+ throw new Error("All six skybox textures must be provided");
115
+ }
116
+
117
+ const canvas = document.createElement('canvas');
118
+ const ctx = canvas.getContext('2d');
119
+
120
+ const loadImage = src => {
121
+ return new Promise((resolve, reject) => {
122
+ const img = new Image();
123
+ img.crossOrigin = "anonymous";
124
+ img.onload = () => resolve(img);
125
+ img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
126
+ img.src = src;
127
+ });
128
+ };
129
+
130
+ try {
131
+ const [imgPosX, imgNegX, imgPosY, imgNegY, imgPosZ, imgNegZ] = await Promise.all([
132
+ loadImage(posX),
133
+ loadImage(negX),
134
+ loadImage(posY),
135
+ loadImage(negY),
136
+ loadImage(posZ),
137
+ loadImage(negZ)
138
+ ]);
139
+
140
+ const imageSize = imgPosX.width;
141
+ if (
142
+ imgNegX.width !== imageSize || imgPosY.width !== imageSize ||
143
+ imgNegY.width !== imageSize || imgPosZ.width !== imageSize ||
144
+ imgNegZ.width !== imageSize
145
+ ) {
146
+ throw new Error("All skybox textures must have the same dimensions");
147
+ }
148
+
149
+ // Set canvas size for the Christ cross layout (3×4 grid)
150
+ canvas.width = imageSize * 4;
151
+ canvas.height = imageSize * 3;
152
+
153
+ ctx.fillStyle = 'black';
154
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
155
+
156
+ ctx.drawImage(imgNegX, imageSize * 0, imageSize * 1, imageSize, imageSize); // -X (left)
157
+ ctx.drawImage(imgPosX, imageSize * 2, imageSize * 1, imageSize, imageSize); // +X (right)
158
+ ctx.drawImage(imgPosY, imageSize * 1, imageSize * 0, imageSize, imageSize); // +Y (top)
159
+ ctx.drawImage(imgNegY, imageSize * 1, imageSize * 2, imageSize, imageSize); // -Y (bottom)
160
+ ctx.drawImage(imgPosZ, imageSize * 1, imageSize * 1, imageSize, imageSize); // +Z (front)
161
+ ctx.drawImage(imgNegZ, imageSize * 3, imageSize * 1, imageSize, imageSize); // -Z (back)
162
+
163
+ const combinedTexture = new Texture(this, {
164
+ image: canvas,
165
+ flipY: true,
166
+ wrapS: ClampToEdgeWrapping,
167
+ wrapT: ClampToEdgeWrapping,
168
+ encoding: cfg.encoding || LinearEncoding
169
+ });
170
+
171
+ return combinedTexture;
172
+ } catch (error) {
173
+ console.error("Error creating combined skybox texture:", error);
174
+ throw error;
175
+ }
176
+ }
76
177
 
77
178
  /**
78
179
  * Sets the size of this Skybox, given as the distance from the center at [0,0,0] to each face.
@@ -1,4 +1,4 @@
1
- import { Plugin, Viewer } from "../../viewer";
1
+ import { Plugin, Viewer, Skybox } from "../../viewer";
2
2
 
3
3
  /**
4
4
  * {@link Viewer} plugin that manages skyboxes
@@ -12,14 +12,21 @@ export declare class SkyboxesPlugin extends Plugin {
12
12
 
13
13
  /**
14
14
  * Creates a skybox.
15
- * @param {String} id Unique ID to assign to the skybox.
16
15
  * @param {Object} params Skybox configuration.
16
+ * @param {String} [params.id] Optional ID, unique among all components in the parent {Scene}, generated automatically when omitted.
17
+ * @param {String | String[]} [params.src=null] Path to skybox texture
18
+ * @param {Number} [params.encoding=LinearEncoding] Texture encoding format. See the {@link Texture#encoding} property for more info.
19
+ * @param {Number} [params.size=1000] Size of this Skybox, given as the distance from the center at ````[0,0,0]```` to each face.
17
20
  * @param {Boolean} [params.active=true] Whether the skybox plane is initially active. Only skyboxes while this is true.
18
21
  * @returns {Skybox} The new skybox.
19
22
  */
20
- createSkybox(id: string, params: {
23
+ createSkybox(params: {
24
+ id?: string,
25
+ src: string | string[],
26
+ encoding?: number,
27
+ size?: number,
21
28
  active?: boolean;
22
- }): any; //Skybox;
29
+ }): Skybox; //Skybox;
23
30
 
24
31
  /**
25
32
  * Destroys a skybox.
@@ -31,4 +38,11 @@ export declare class SkyboxesPlugin extends Plugin {
31
38
  Destroys all skyboxes.
32
39
  */
33
40
  clear(): void;
41
+
42
+ /**
43
+ * Destroys this plugin.
44
+ *
45
+ * Clears skyboxes from the Viewer first.
46
+ */
47
+ destroy(): void;
34
48
  }