@xeokit/xeokit-sdk 2.0.15 → 2.0.16

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.
@@ -7020,7 +7020,7 @@ function Core() {
7020
7020
  /**
7021
7021
  * @private
7022
7022
  */
7023
- this.setRAFEnabled = function(value) {
7023
+ this.setRAFEnabled = function (value) {
7024
7024
  if (value === rafEnabled) {
7025
7025
  return;
7026
7026
  }
@@ -7030,7 +7030,7 @@ function Core() {
7030
7030
  clearInterval(interval);
7031
7031
  interval = null;
7032
7032
  }
7033
- window.requestAnimationFrame(frame);
7033
+ (window.requestPostAnimationFrame !== undefined) ? window.requestPostAnimationFrame(frame) : requestAnimationFrame(frame);
7034
7034
  } else {
7035
7035
  interval = setInterval(frame, 16);
7036
7036
  }
@@ -7039,7 +7039,7 @@ function Core() {
7039
7039
  /**
7040
7040
  * @private
7041
7041
  */
7042
- this.getRAFEnabled = function() {
7042
+ this.getRAFEnabled = function () {
7043
7043
  return rafEnabled;
7044
7044
  };
7045
7045
 
@@ -7126,7 +7126,7 @@ const frame = function () {
7126
7126
  renderScenes();
7127
7127
  lastTime = time;
7128
7128
  if (rafEnabled) {
7129
- window.requestAnimationFrame(frame);
7129
+ (window.requestPostAnimationFrame !== undefined) ? window.requestPostAnimationFrame(frame) : requestAnimationFrame(frame);
7130
7130
  }
7131
7131
  };
7132
7132
 
@@ -7204,7 +7204,7 @@ function renderScenes() {
7204
7204
  }
7205
7205
 
7206
7206
  if (rafEnabled) {
7207
- window.requestAnimationFrame(frame);
7207
+ (window.requestPostAnimationFrame !== undefined) ? window.requestPostAnimationFrame(frame) : requestAnimationFrame(frame);
7208
7208
  } else {
7209
7209
  interval = setInterval(frame, 16);
7210
7210
  }
@@ -11986,13 +11986,6 @@ const WEBGL_CONTEXT_NAMES = [
11986
11986
  */
11987
11987
  class Canvas extends Component {
11988
11988
 
11989
- /**
11990
- @private
11991
- */
11992
- get type() {
11993
- return "Canvas";
11994
- }
11995
-
11996
11989
  /**
11997
11990
  * @constructor
11998
11991
  * @private
@@ -12060,8 +12053,10 @@ class Canvas extends Component {
12060
12053
  // If the canvas uses css styles to specify the sizes make sure the basic
12061
12054
  // width and height attributes match or the WebGL context will use 300 x 150
12062
12055
 
12063
- this.canvas.width = this.canvas.clientWidth;
12064
- this.canvas.height = this.canvas.clientHeight;
12056
+ this.resolutionScale = cfg.resolutionScale;
12057
+
12058
+ this.canvas.width = Math.round(this.canvas.clientWidth * this._resolutionScale);
12059
+ this.canvas.height = Math.round(this.canvas.clientHeight * this._resolutionScale);
12065
12060
 
12066
12061
  /**
12067
12062
  * Boundary of the Canvas in absolute browser window coordinates.
@@ -12124,6 +12119,8 @@ class Canvas extends Component {
12124
12119
 
12125
12120
  // Publish canvas size and position changes on each scene tick
12126
12121
 
12122
+ let lastResolutionScale = null;
12123
+
12127
12124
  let lastWindowWidth = null;
12128
12125
  let lastWindowHeight = null;
12129
12126
 
@@ -12135,10 +12132,11 @@ class Canvas extends Component {
12135
12132
 
12136
12133
  let lastParent = null;
12137
12134
 
12138
- this._tick = this.scene.on("tick", function () {
12135
+ this._tick = this.scene.on("tick", () => {
12139
12136
 
12140
- const canvas = self.canvas;
12137
+ const canvas = this.canvas;
12141
12138
 
12139
+ const newResolutionScale = (this._resolutionScale !== lastResolutionScale);
12142
12140
  const newWindowSize = (window.innerWidth !== lastWindowWidth || window.innerHeight !== lastWindowHeight);
12143
12141
  const newCanvasSize = (canvas.clientWidth !== lastCanvasWidth || canvas.clientHeight !== lastCanvasHeight);
12144
12142
  const newCanvasPos = (canvas.offsetLeft !== lastCanvasOffsetLeft || canvas.offsetTop !== lastCanvasOffsetTop);
@@ -12146,32 +12144,32 @@ class Canvas extends Component {
12146
12144
  const parent = canvas.parentElement;
12147
12145
  const newParent = (parent !== lastParent);
12148
12146
 
12149
- if (newWindowSize || newCanvasSize || newCanvasPos || newParent) {
12147
+ if (newResolutionScale || newWindowSize || newCanvasSize || newCanvasPos || newParent) {
12150
12148
 
12151
- self._spinner._adjustPosition();
12149
+ this._spinner._adjustPosition();
12152
12150
 
12153
- if (newCanvasSize || newCanvasPos) {
12151
+ if (newResolutionScale || newCanvasSize || newCanvasPos) {
12154
12152
 
12155
12153
  const newWidth = canvas.clientWidth;
12156
12154
  const newHeight = canvas.clientHeight;
12157
12155
 
12158
12156
  // TODO: Wasteful to re-count pixel size of each canvas on each canvas' resize
12159
- if (newCanvasSize) {
12157
+ if (newResolutionScale || newCanvasSize) {
12160
12158
  let countPixels = 0;
12161
12159
  let scene;
12162
12160
  for (const sceneId in core.scenes) {
12163
12161
  if (core.scenes.hasOwnProperty(sceneId)) {
12164
12162
  scene = core.scenes[sceneId];
12165
- countPixels += scene.canvas.canvas.clientWidth * scene.canvas.canvas.clientHeight;
12163
+ countPixels += Math.round((scene.canvas.canvas.clientWidth * this._resolutionScale) * (scene.canvas.canvas.clientHeight * this._resolutionScale));
12166
12164
  }
12167
12165
  }
12168
12166
  stats.memory.pixels = countPixels;
12169
12167
 
12170
- canvas.width = canvas.clientWidth;
12171
- canvas.height = canvas.clientHeight;
12168
+ canvas.width = Math.round(canvas.clientWidth * this._resolutionScale);
12169
+ canvas.height = Math.round(canvas.clientHeight * this._resolutionScale);
12172
12170
  }
12173
12171
 
12174
- const boundary = self.boundary;
12172
+ const boundary = this.boundary;
12175
12173
 
12176
12174
  boundary[0] = canvas.offsetLeft;
12177
12175
  boundary[1] = canvas.offsetTop;
@@ -12184,12 +12182,18 @@ class Canvas extends Component {
12184
12182
  * @event boundary
12185
12183
  * @param value The property's new value
12186
12184
  */
12187
- self.fire("boundary", boundary);
12185
+ if (!newResolutionScale) {
12186
+ this.fire("boundary", boundary);
12187
+ }
12188
12188
 
12189
12189
  lastCanvasWidth = newWidth;
12190
12190
  lastCanvasHeight = newHeight;
12191
12191
  }
12192
12192
 
12193
+ if (newResolutionScale) {
12194
+ lastResolutionScale = this._resolutionScale;
12195
+ }
12196
+
12193
12197
  if (newWindowSize) {
12194
12198
  lastWindowWidth = window.innerWidth;
12195
12199
  lastWindowHeight = window.innerHeight;
@@ -12210,6 +12214,122 @@ class Canvas extends Component {
12210
12214
  });
12211
12215
  }
12212
12216
 
12217
+ /**
12218
+ @private
12219
+ */
12220
+ get type() {
12221
+ return "Canvas";
12222
+ }
12223
+
12224
+ /**
12225
+ * Gets whether the canvas clear color will be derived from {@link AmbientLight} or {@link Canvas#backgroundColor}
12226
+ * when {@link Canvas#transparent} is ```true```.
12227
+ *
12228
+ * When {@link Canvas#transparent} is ```true``` and this is ````true````, then the canvas clear color will
12229
+ * be taken from the {@link Scene}'s ambient light color.
12230
+ *
12231
+ * When {@link Canvas#transparent} is ```true``` and this is ````false````, then the canvas clear color will
12232
+ * be taken from {@link Canvas#backgroundColor}.
12233
+ *
12234
+ * Default value is ````true````.
12235
+ *
12236
+ * @type {Boolean}
12237
+ */
12238
+ get backgroundColorFromAmbientLight() {
12239
+ return this._backgroundColorFromAmbientLight;
12240
+ }
12241
+
12242
+ /**
12243
+ * Sets if the canvas background color is derived from an {@link AmbientLight}.
12244
+ *
12245
+ * This only has effect when the canvas is not transparent. When not enabled, the background color
12246
+ * will be the canvas element's HTML/CSS background color.
12247
+ *
12248
+ * Default value is ````true````.
12249
+ *
12250
+ * @type {Boolean}
12251
+ */
12252
+ set backgroundColorFromAmbientLight(backgroundColorFromAmbientLight) {
12253
+ this._backgroundColorFromAmbientLight = (backgroundColorFromAmbientLight !== false);
12254
+ this.glRedraw();
12255
+ }
12256
+
12257
+ /**
12258
+ * Gets the canvas clear color.
12259
+ *
12260
+ * Default value is ````[1, 1, 1]````.
12261
+ *
12262
+ * @type {Number[]}
12263
+ */
12264
+ get backgroundColor() {
12265
+ return this._backgroundColor;
12266
+ }
12267
+
12268
+ /**
12269
+ * Sets the canvas clear color.
12270
+ *
12271
+ * Default value is ````[1, 1, 1]````.
12272
+ *
12273
+ * @type {Number[]}
12274
+ */
12275
+ set backgroundColor(value) {
12276
+ if (value) {
12277
+ this._backgroundColor[0] = value[0];
12278
+ this._backgroundColor[1] = value[1];
12279
+ this._backgroundColor[2] = value[2];
12280
+ } else {
12281
+ this._backgroundColor[0] = 1.0;
12282
+ this._backgroundColor[1] = 1.0;
12283
+ this._backgroundColor[2] = 1.0;
12284
+ }
12285
+ this.glRedraw();
12286
+ }
12287
+
12288
+ /**
12289
+ * Gets the scale of the canvas back buffer relative to the CSS-defined size of the canvas.
12290
+ *
12291
+ * This is a common way to trade off rendering quality for speed. If the canvas size is defined in CSS, then
12292
+ * setting this to a value between ````[0..1]```` (eg ````0.5````) will render into a smaller back buffer, giving
12293
+ * a performance boost.
12294
+ *
12295
+ * @returns {*|number} The resolution scale.
12296
+ */
12297
+ get resolutionScale() {
12298
+ return this._resolutionScale;
12299
+ }
12300
+
12301
+ /**
12302
+ * Sets the scale of the canvas back buffer relative to the CSS-defined size of the canvas.
12303
+ *
12304
+ * This is a common way to trade off rendering quality for speed. If the canvas size is defined in CSS, then
12305
+ * setting this to a value between ````[0..1]```` (eg ````0.5````) will render into a smaller back buffer, giving
12306
+ * a performance boost.
12307
+ *
12308
+ * @param {*|number} resolutionScale The resolution scale.
12309
+ */
12310
+ set resolutionScale(resolutionScale) {
12311
+ resolutionScale = resolutionScale || 1.0;
12312
+ if (resolutionScale === this._resolutionScale) {
12313
+ return;
12314
+ }
12315
+ this._resolutionScale = resolutionScale;
12316
+ const canvas = this.canvas;
12317
+ canvas.width = Math.round(canvas.clientWidth * this._resolutionScale);
12318
+ canvas.height = Math.round(canvas.clientHeight * this._resolutionScale);
12319
+ this.glRedraw();
12320
+ }
12321
+
12322
+ /**
12323
+ * The busy {@link Spinner} for this Canvas.
12324
+ *
12325
+ * @property spinner
12326
+ * @type Spinner
12327
+ * @final
12328
+ */
12329
+ get spinner() {
12330
+ return this._spinner;
12331
+ }
12332
+
12213
12333
  /**
12214
12334
  * Creates a default canvas in the DOM.
12215
12335
  * @private
@@ -12298,69 +12418,6 @@ class Canvas extends Component {
12298
12418
  }
12299
12419
  }
12300
12420
 
12301
- /**
12302
- * Sets if the canvas background color is derived from an {@link AmbientLight}.
12303
- *
12304
- * This only has effect when the canvas is not transparent. When not enabled, the background color
12305
- * will be the canvas element's HTML/CSS background color.
12306
- *
12307
- * Default value is ````true````.
12308
- *
12309
- * @type {Boolean}
12310
- */
12311
- set backgroundColorFromAmbientLight(backgroundColorFromAmbientLight) {
12312
- this._backgroundColorFromAmbientLight = (backgroundColorFromAmbientLight !== false);
12313
- }
12314
-
12315
- /**
12316
- * Gets whether the canvas clear color will be derived from {@link AmbientLight} or {@link Canvas#backgroundColor}
12317
- * when {@link Canvas#transparent} is ```true```.
12318
- *
12319
- * When {@link Canvas#transparent} is ```true``` and this is ````true````, then the canvas clear color will
12320
- * be taken from the {@link Scene}'s ambient light color.
12321
- *
12322
- * When {@link Canvas#transparent} is ```true``` and this is ````false````, then the canvas clear color will
12323
- * be taken from {@link Canvas#backgroundColor}.
12324
- *
12325
- * Default value is ````true````.
12326
- *
12327
- * @type {Boolean}
12328
- */
12329
- get backgroundColorFromAmbientLight() {
12330
- return this._backgroundColorFromAmbientLight;
12331
- }
12332
-
12333
- /**
12334
- * Sets the canvas clear color.
12335
- *
12336
- * Default value is ````[1, 1, 1]````.
12337
- *
12338
- * @type {Number[]}
12339
- */
12340
- set backgroundColor(value) {
12341
- if (value) {
12342
- this._backgroundColor[0] = value[0];
12343
- this._backgroundColor[1] = value[1];
12344
- this._backgroundColor[2] = value[2];
12345
- } else {
12346
- this._backgroundColor[0] = 1.0;
12347
- this._backgroundColor[1] = 1.0;
12348
- this._backgroundColor[2] = 1.0;
12349
- }
12350
- this.glRedraw();
12351
- }
12352
-
12353
- /**
12354
- * Gets the canvas clear color.
12355
- *
12356
- * Default value is ````[1, 1, 1]````.
12357
- *
12358
- * @type {Number[]}
12359
- */
12360
- get backgroundColor() {
12361
- return this._backgroundColor;
12362
- }
12363
-
12364
12421
  /**
12365
12422
  * @private
12366
12423
  * @deprecated
@@ -12404,17 +12461,6 @@ class Canvas extends Component {
12404
12461
  }
12405
12462
  }
12406
12463
 
12407
- /**
12408
- * The busy {@link Spinner} for this Canvas.
12409
- *
12410
- * @property spinner
12411
- * @type Spinner
12412
- * @final
12413
- */
12414
- get spinner() {
12415
- return this._spinner;
12416
- }
12417
-
12418
12464
  destroy() {
12419
12465
  this.scene.off(this._tick);
12420
12466
  this._spinner._destroy();
@@ -12681,1115 +12727,1115 @@ class FrameContext {
12681
12727
  }
12682
12728
  }
12683
12729
 
12684
- /*
12685
- * Canvas2Image v0.1
12686
- * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
12687
- * MIT License [http://www.opensource.org/licenses/mit-license.php]
12730
+ /**
12731
+ * @desc Pick result returned by {@link Scene#pick}.
12688
12732
  *
12689
- * Modified by @xeolabs to permit vertical flipping, so that snapshot can be taken from WebGL frame buffers,
12690
- * which vertically flip image data as part of the way that WebGL renders textures.
12691
12733
  */
12734
+ class PickResult {
12692
12735
 
12693
- /**
12694
- * @private
12695
- */
12696
- const Canvas2Image = (function () {
12697
- // check if we have canvas support
12698
- const oCanvas = document.createElement("canvas"), sc = String.fromCharCode;
12736
+ /**
12737
+ * @private
12738
+ * @param value
12739
+ */
12740
+ constructor() {
12699
12741
 
12700
- // no canvas, bail out.
12701
- if (!oCanvas.getContext) {
12702
- return {
12703
- saveAsBMP: function () {
12704
- },
12705
- saveAsPNG: function () {
12706
- },
12707
- saveAsJPEG: function () {
12708
- }
12709
- }
12710
- }
12742
+ /**
12743
+ * Picked entity.
12744
+ * Null when no entity was picked.
12745
+ * @property entity
12746
+ * @type {Entity|*}
12747
+ */
12748
+ this.entity = null;
12711
12749
 
12712
- const bHasImageData = !!(oCanvas.getContext("2d").getImageData), bHasDataURL = !!(oCanvas.toDataURL),
12713
- bHasBase64 = !!(window.btoa);
12750
+ /**
12751
+ * Type of primitive that was picked - usually "triangle".
12752
+ * Null when no primitive was picked.
12753
+ * @property primitive
12754
+ * @type {String}
12755
+ */
12756
+ this.primitive = null;
12714
12757
 
12715
- // ok, we're good
12716
- const readCanvasData = function (oCanvas) {
12717
- const iWidth = parseInt(oCanvas.width), iHeight = parseInt(oCanvas.height);
12718
- return oCanvas.getContext("2d").getImageData(0, 0, iWidth, iHeight);
12719
- };
12758
+ /**
12759
+ * Index of primitive that was picked.
12760
+ * -1 when no entity was picked.
12761
+ * @property primIndex
12762
+ * @type {number}
12763
+ */
12764
+ this.primIndex = -1;
12720
12765
 
12721
- // base64 encodes either a string or an array of charcodes
12722
- const encodeData = function (data) {
12723
- let i, aData, strData = "";
12766
+ /**
12767
+ * True when the picked surface position is full precision.
12768
+ * When false, the picked surface position should be regarded as approximate.
12769
+ * Full-precision surface picking is performed with the {@link Scene#pick} method's ````pickSurfacePrecision```` option.
12770
+ * @property pickSurfacePrecision
12771
+ * @type {Boolean}
12772
+ */
12773
+ this.pickSurfacePrecision = false;
12724
12774
 
12725
- if (typeof data == "string") {
12726
- strData = data;
12727
- } else {
12728
- aData = data;
12729
- for (i = 0; i < aData.length; i++) {
12730
- strData += sc(aData[i]);
12731
- }
12732
- }
12733
- return btoa(strData);
12734
- };
12735
-
12736
- // creates a base64 encoded string containing BMP data takes an imagedata object as argument
12737
- const createBMP = function (oData) {
12738
- let strHeader = '';
12739
- const iWidth = oData.width;
12740
- const iHeight = oData.height;
12741
-
12742
- strHeader += 'BM';
12743
-
12744
- let iFileSize = iWidth * iHeight * 4 + 54; // total header size = 54 bytes
12745
- strHeader += sc(iFileSize % 256);
12746
- iFileSize = Math.floor(iFileSize / 256);
12747
- strHeader += sc(iFileSize % 256);
12748
- iFileSize = Math.floor(iFileSize / 256);
12749
- strHeader += sc(iFileSize % 256);
12750
- iFileSize = Math.floor(iFileSize / 256);
12751
- strHeader += sc(iFileSize % 256);
12752
-
12753
- strHeader += sc(0, 0, 0, 0, 54, 0, 0, 0); // data offset
12754
- strHeader += sc(40, 0, 0, 0); // info header size
12755
-
12756
- let iImageWidth = iWidth;
12757
- strHeader += sc(iImageWidth % 256);
12758
- iImageWidth = Math.floor(iImageWidth / 256);
12759
- strHeader += sc(iImageWidth % 256);
12760
- iImageWidth = Math.floor(iImageWidth / 256);
12761
- strHeader += sc(iImageWidth % 256);
12762
- iImageWidth = Math.floor(iImageWidth / 256);
12763
- strHeader += sc(iImageWidth % 256);
12764
-
12765
- let iImageHeight = iHeight;
12766
- strHeader += sc(iImageHeight % 256);
12767
- iImageHeight = Math.floor(iImageHeight / 256);
12768
- strHeader += sc(iImageHeight % 256);
12769
- iImageHeight = Math.floor(iImageHeight / 256);
12770
- strHeader += sc(iImageHeight % 256);
12771
- iImageHeight = Math.floor(iImageHeight / 256);
12772
- strHeader += sc(iImageHeight % 256);
12773
-
12774
- strHeader += sc(1, 0, 32, 0); // num of planes & num of bits per pixel
12775
- strHeader += sc(0, 0, 0, 0); // compression = none
12776
-
12777
- let iDataSize = iWidth * iHeight * 4;
12778
- strHeader += sc(iDataSize % 256);
12779
- iDataSize = Math.floor(iDataSize / 256);
12780
- strHeader += sc(iDataSize % 256);
12781
- iDataSize = Math.floor(iDataSize / 256);
12782
- strHeader += sc(iDataSize % 256);
12783
- iDataSize = Math.floor(iDataSize / 256);
12784
- strHeader += sc(iDataSize % 256);
12785
-
12786
- strHeader += sc(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); // these bytes are not used
12787
-
12788
- const aImgData = oData.data;
12789
- let strPixelData = "";
12790
- let x;
12791
- let y = iHeight;
12792
- let iOffsetX;
12793
- let iOffsetY;
12794
- let strPixelRow;
12775
+ this._canvasPos = new Int16Array([0, 0]);
12776
+ this._origin = new Float64Array([0, 0, 0]);
12777
+ this._direction = new Float64Array([0, 0, 0]);
12778
+ this._indices = new Int32Array(3);
12779
+ this._localPos = new Float64Array([0, 0, 0]);
12780
+ this._worldPos = new Float64Array([0, 0, 0]);
12781
+ this._viewPos = new Float64Array([0, 0, 0]);
12782
+ this._bary = new Float64Array([0, 0, 0]);
12783
+ this._worldNormal = new Float64Array([0, 0, 0]);
12784
+ this._uv = new Float64Array([0, 0]);
12795
12785
 
12796
- do {
12797
- iOffsetY = iWidth * (y - 1) * 4;
12798
- strPixelRow = "";
12799
- for (x = 0; x < iWidth; x++) {
12800
- iOffsetX = 4 * x;
12801
- strPixelRow += sc(
12802
- aImgData[iOffsetY + iOffsetX + 2], // B
12803
- aImgData[iOffsetY + iOffsetX + 1], // G
12804
- aImgData[iOffsetY + iOffsetX], // R
12805
- aImgData[iOffsetY + iOffsetX + 3] // A
12806
- );
12807
- }
12808
- strPixelData += strPixelRow;
12809
- } while (--y);
12786
+ this.reset();
12787
+ }
12810
12788
 
12811
- return encodeData(strHeader + strPixelData);
12812
- };
12789
+ /**
12790
+ * Canvas coordinates when picking with a 2D pointer.
12791
+ * @property canvasPos
12792
+ * @type {Number[]}
12793
+ */
12794
+ get canvasPos() {
12795
+ return this._gotCanvasPos ? this._canvasPos : null;
12796
+ }
12813
12797
 
12814
- // sends the generated file to the client
12815
- const saveFile = function (strData) {
12816
- if (!window.open(strData)) {
12817
- document.location.href = strData;
12798
+ /**
12799
+ * @private
12800
+ * @param value
12801
+ */
12802
+ set canvasPos(value) {
12803
+ if (value) {
12804
+ this._canvasPos[0] = value[0];
12805
+ this._canvasPos[1] = value[1];
12806
+ this._gotCanvasPos = true;
12807
+ } else {
12808
+ this._gotCanvasPos = false;
12818
12809
  }
12819
- };
12820
-
12821
- const makeDataURI = function (strData, strMime) {
12822
- return "data:" + strMime + ";base64," + strData;
12823
- };
12810
+ }
12824
12811
 
12825
- // generates a <img> object containing the imagedata
12826
- const makeImageObject = function (strSource) {
12827
- const oImgElement = document.createElement("img");
12828
- oImgElement.src = strSource;
12829
- return oImgElement;
12830
- };
12812
+ /**
12813
+ * World-space 3D ray origin when raypicked.
12814
+ * @property origin
12815
+ * @type {Number[]}
12816
+ */
12817
+ get origin() {
12818
+ return this._gotOrigin ? this._origin : null;
12819
+ }
12831
12820
 
12832
- const scaleCanvas = function (oCanvas, iWidth, iHeight, flipy) {
12833
- if (iWidth && iHeight) {
12834
- const oSaveCanvas = document.createElement("canvas");
12835
- oSaveCanvas.width = iWidth;
12836
- oSaveCanvas.height = iHeight;
12837
- oSaveCanvas.style.width = iWidth + "px";
12838
- oSaveCanvas.style.height = iHeight + "px";
12839
- const oSaveCtx = oSaveCanvas.getContext("2d");
12840
- if (flipy) {
12841
- oSaveCtx.save();
12842
- oSaveCtx.scale(1.0, -1.0);
12843
- oSaveCtx.imageSmoothingEnabled = true;
12844
- oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, -iHeight);
12845
- oSaveCtx.restore();
12846
- } else {
12847
- oSaveCtx.imageSmoothingEnabled = true;
12848
- oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iHeight);
12849
- }
12850
- return oSaveCanvas;
12821
+ /**
12822
+ * @private
12823
+ * @param value
12824
+ */
12825
+ set origin(value) {
12826
+ if (value) {
12827
+ this._origin[0] = value[0];
12828
+ this._origin[1] = value[1];
12829
+ this._origin[2] = value[2];
12830
+ this._gotOrigin = true;
12831
+ } else {
12832
+ this._gotOrigin = false;
12851
12833
  }
12852
- return oCanvas;
12853
- };
12854
-
12855
- return {
12856
- saveAsPNG: function (oCanvas, bReturnImg, iWidth, iHeight, flipy) {
12857
- if (!bHasDataURL) return false;
12858
- const oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight, flipy);
12859
- const strMime = "image/png";
12860
- const strData = oScaledCanvas.toDataURL(strMime);
12861
- if (bReturnImg) {
12862
- return makeImageObject(strData);
12863
- } else {
12864
- saveFile(strData);
12865
- }
12866
- return true;
12867
- },
12834
+ }
12868
12835
 
12869
- saveAsJPEG: function (oCanvas, bReturnImg, iWidth, iHeight, flipy) {
12870
- if (!bHasDataURL) return false;
12871
- const oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight, flipy);
12872
- const strMime = "image/jpeg";
12873
- const strData = oScaledCanvas.toDataURL(strMime);
12874
- // check if browser actually supports jpeg by looking for the mime type in the data uri. if not, return false
12875
- if (strData.indexOf(strMime) != 5) return false;
12876
- if (bReturnImg) {
12877
- return makeImageObject(strData);
12878
- } else {
12879
- saveFile(strData);
12880
- }
12881
- return true;
12882
- },
12836
+ /**
12837
+ * World-space 3D ray direction when raypicked.
12838
+ * @property direction
12839
+ * @type {Number[]}
12840
+ */
12841
+ get direction() {
12842
+ return this._gotDirection ? this._direction : null;
12843
+ }
12883
12844
 
12884
- saveAsBMP: function (oCanvas, bReturnImg, iWidth, iHeight, flipy) {
12885
- if (!(bHasDataURL && bHasImageData && bHasBase64)) return false;
12886
- const oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight, flipy);
12887
- const strMime = "image/bmp";
12888
- const oData = readCanvasData(oScaledCanvas), strImgData = createBMP(oData);
12889
- if (bReturnImg) {
12890
- return makeImageObject(makeDataURI(strImgData, strMime));
12891
- } else {
12892
- saveFile(makeDataURI(strImgData, strMime));
12893
- }
12894
- return true;
12845
+ /**
12846
+ * @private
12847
+ * @param value
12848
+ */
12849
+ set direction(value) {
12850
+ if (value) {
12851
+ this._direction[0] = value[0];
12852
+ this._direction[1] = value[1];
12853
+ this._direction[2] = value[2];
12854
+ this._gotDirection = true;
12855
+ } else {
12856
+ this._gotDirection = false;
12895
12857
  }
12896
- };
12897
- })();
12898
-
12899
- /**
12900
- * @desc Represents a WebGL render buffer.
12901
- * @private
12902
- */
12903
- class RenderBuffer {
12904
-
12905
- constructor(canvas, gl, options) {
12906
- options = options || {};
12907
- this.gl = gl;
12908
- this.allocated = false;
12909
- this.canvas = canvas;
12910
- this.buffer = null;
12911
- this.bound = false;
12912
- this.size = options.size;
12913
- this._hasDepthTexture = !!options.depthTexture;
12858
+ }
12859
+
12860
+ /**
12861
+ * Picked triangle's vertex indices.
12862
+ * Only defined when an entity and triangle was picked.
12863
+ * @property indices
12864
+ * @type {Int32Array}
12865
+ */
12866
+ get indices() {
12867
+ return this.entity && this._gotIndices ? this._indices : null;
12914
12868
  }
12915
12869
 
12916
- setSize(size) {
12917
- this.size = size;
12870
+ /**
12871
+ * @private
12872
+ * @param value
12873
+ */
12874
+ set indices(value) {
12875
+ if (value) {
12876
+ this._indices[0] = value[0];
12877
+ this._indices[1] = value[1];
12878
+ this._indices[2] = value[2];
12879
+ this._gotIndices = true;
12880
+ } else {
12881
+ this._gotIndices = false;
12882
+ }
12918
12883
  }
12919
12884
 
12920
- webglContextRestored(gl) {
12921
- this.gl = gl;
12922
- this.buffer = null;
12923
- this.allocated = false;
12924
- this.bound = false;
12885
+ /**
12886
+ * Picked Local-space point on surface.
12887
+ * Only defined when an entity and a point on its surface was picked.
12888
+ * @property localPos
12889
+ * @type {Number[]}
12890
+ */
12891
+ get localPos() {
12892
+ return this.entity && this._gotLocalPos ? this._localPos : null;
12925
12893
  }
12926
12894
 
12927
- bind() {
12928
- this._touch();
12929
- if (this.bound) {
12930
- return;
12895
+ /**
12896
+ * @private
12897
+ * @param value
12898
+ */
12899
+ set localPos(value) {
12900
+ if (value) {
12901
+ this._localPos[0] = value[0];
12902
+ this._localPos[1] = value[1];
12903
+ this._localPos[2] = value[2];
12904
+ this._gotLocalPos = true;
12905
+ } else {
12906
+ this._gotLocalPos = false;
12931
12907
  }
12932
- const gl = this.gl;
12933
- gl.bindFramebuffer(gl.FRAMEBUFFER, this.buffer.framebuf);
12934
- this.bound = true;
12935
12908
  }
12936
12909
 
12937
- _touch() {
12910
+ /**
12911
+ * Picked World-space point on surface.
12912
+ * Only defined when an entity and a point on its surface was picked.
12913
+ * @property worldPos
12914
+ * @type {Number[]}
12915
+ */
12916
+ get worldPos() {
12917
+ return this.entity && this._gotWorldPos ? this._worldPos : null;
12918
+ }
12938
12919
 
12939
- let width;
12940
- let height;
12941
- const gl = this.gl;
12920
+ /**
12921
+ * @private
12922
+ * @param value
12923
+ */
12924
+ set worldPos(value) {
12925
+ if (value) {
12926
+ this._worldPos[0] = value[0];
12927
+ this._worldPos[1] = value[1];
12928
+ this._worldPos[2] = value[2];
12929
+ this._gotWorldPos = true;
12930
+ } else {
12931
+ this._gotWorldPos = false;
12932
+ }
12933
+ }
12942
12934
 
12943
- if (this.size) {
12944
- width = this.size[0];
12945
- height = this.size[1];
12935
+ /**
12936
+ * Picked View-space point on surface.
12937
+ * Only defined when an entity and a point on its surface was picked.
12938
+ * @property viewPos
12939
+ * @type {Number[]}
12940
+ */
12941
+ get viewPos() {
12942
+ return this.entity && this._gotViewPos ? this._viewPos : null;
12943
+ }
12946
12944
 
12945
+ /**
12946
+ * @private
12947
+ * @param value
12948
+ */
12949
+ set viewPos(value) {
12950
+ if (value) {
12951
+ this._viewPos[0] = value[0];
12952
+ this._viewPos[1] = value[1];
12953
+ this._viewPos[2] = value[2];
12954
+ this._gotViewPos = true;
12947
12955
  } else {
12948
- width = gl.drawingBufferWidth;
12949
- height = gl.drawingBufferHeight;
12956
+ this._gotViewPos = false;
12950
12957
  }
12958
+ }
12951
12959
 
12952
- if (this.buffer) {
12953
-
12954
- if (this.buffer.width === width && this.buffer.height === height) {
12955
- return;
12960
+ /**
12961
+ * Barycentric coordinate within picked triangle.
12962
+ * Only defined when an entity and a point on its surface was picked.
12963
+ * @property bary
12964
+ * @type {Number[]}
12965
+ */
12966
+ get bary() {
12967
+ return this.entity && this._gotBary ? this._bary : null;
12968
+ }
12956
12969
 
12957
- } else {
12958
- gl.deleteTexture(this.buffer.texture);
12959
- gl.deleteFramebuffer(this.buffer.framebuf);
12960
- gl.deleteRenderbuffer(this.buffer.renderbuf);
12961
- }
12970
+ /**
12971
+ * @private
12972
+ * @param value
12973
+ */
12974
+ set bary(value) {
12975
+ if (value) {
12976
+ this._bary[0] = value[0];
12977
+ this._bary[1] = value[1];
12978
+ this._bary[2] = value[2];
12979
+ this._gotBary = true;
12980
+ } else {
12981
+ this._gotBary = false;
12962
12982
  }
12983
+ }
12963
12984
 
12964
- const colorTexture = gl.createTexture();
12965
- gl.bindTexture(gl.TEXTURE_2D, colorTexture);
12966
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
12967
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
12968
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
12969
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
12970
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
12971
-
12972
- let depthTexture;
12985
+ /**
12986
+ * Normal vector at picked position on surface.
12987
+ * Only defined when an entity and a point on its surface was picked.
12988
+ * @property worldNormal
12989
+ * @type {Number[]}
12990
+ */
12991
+ get worldNormal() {
12992
+ return this.entity && this._gotWorldNormal ? this._worldNormal : null;
12993
+ }
12973
12994
 
12974
- if (this._hasDepthTexture) {
12975
- depthTexture = gl.createTexture();
12976
- gl.bindTexture(gl.TEXTURE_2D, depthTexture);
12977
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
12978
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
12979
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
12980
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
12981
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
12995
+ /**
12996
+ * @private
12997
+ * @param value
12998
+ */
12999
+ set worldNormal(value) {
13000
+ if (value) {
13001
+ this._worldNormal[0] = value[0];
13002
+ this._worldNormal[1] = value[1];
13003
+ this._worldNormal[2] = value[2];
13004
+ this._gotWorldNormal = true;
13005
+ } else {
13006
+ this._gotWorldNormal = false;
12982
13007
  }
13008
+ }
12983
13009
 
12984
- const renderbuf = gl.createRenderbuffer();
12985
- gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuf);
12986
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
12987
-
12988
- const framebuf = gl.createFramebuffer();
12989
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);
12990
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture, 0);
13010
+ /**
13011
+ * UV coordinates at picked position on surface.
13012
+ * Only defined when an entity and a point on its surface was picked.
13013
+ * @property uv
13014
+ * @type {Number[]}
13015
+ */
13016
+ get uv() {
13017
+ return this.entity && this._gotUV ? this._uv : null;
13018
+ }
12991
13019
 
12992
- if (this._hasDepthTexture) {
12993
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
13020
+ /**
13021
+ * @private
13022
+ * @param value
13023
+ */
13024
+ set uv(value) {
13025
+ if (value) {
13026
+ this._uv[0] = value[0];
13027
+ this._uv[1] = value[1];
13028
+ this._gotUV = true;
12994
13029
  } else {
12995
- gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuf);
13030
+ this._gotUV = false;
12996
13031
  }
13032
+ }
12997
13033
 
12998
- gl.bindTexture(gl.TEXTURE_2D, null);
12999
- gl.bindRenderbuffer(gl.RENDERBUFFER, null);
13000
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
13034
+ /**
13035
+ * @private
13036
+ */
13037
+ reset() {
13038
+ this.entity = null;
13039
+ this.primIndex = -1;
13040
+ this.primitive = null;
13041
+ this.pickSurfacePrecision = false;
13042
+ this._gotCanvasPos = false;
13043
+ this._gotOrigin = false;
13044
+ this._gotDirection = false;
13045
+ this._gotIndices = false;
13046
+ this._gotLocalPos = false;
13047
+ this._gotWorldPos = false;
13048
+ this._gotViewPos = false;
13049
+ this._gotBary = false;
13050
+ this._gotWorldNormal = false;
13051
+ this._gotUV = false;
13052
+ }
13053
+ }
13001
13054
 
13002
- // Verify framebuffer is OK
13055
+ /**
13056
+ * @desc Represents a vertex or fragment stage within a {@link Program}.
13057
+ * @private
13058
+ */
13059
+ class Shader {
13003
13060
 
13004
- gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);
13005
- if (!gl.isFramebuffer(framebuf)) {
13006
- throw "Invalid framebuffer";
13007
- }
13008
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
13061
+ constructor(gl, type, source) {
13009
13062
 
13010
- const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
13063
+ this.allocated = false;
13064
+ this.compiled = false;
13065
+ this.handle = gl.createShader(type);
13011
13066
 
13012
- switch (status) {
13067
+ if (!this.handle) {
13068
+ this.errors = [
13069
+ "Failed to allocate"
13070
+ ];
13071
+ return;
13072
+ }
13013
13073
 
13014
- case gl.FRAMEBUFFER_COMPLETE:
13015
- break;
13074
+ this.allocated = true;
13016
13075
 
13017
- case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
13018
- throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
13076
+ gl.shaderSource(this.handle, source);
13077
+ gl.compileShader(this.handle);
13019
13078
 
13020
- case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
13021
- throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
13079
+ this.compiled = gl.getShaderParameter(this.handle, gl.COMPILE_STATUS);
13022
13080
 
13023
- case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
13024
- throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
13081
+ if (!this.compiled) {
13025
13082
 
13026
- case gl.FRAMEBUFFER_UNSUPPORTED:
13027
- throw "Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED";
13083
+ if (!gl.isContextLost()) { // Handled explicitly elsewhere, so won't re-handle here
13028
13084
 
13029
- default:
13030
- throw "Incomplete framebuffer: " + status;
13085
+ const lines = source.split("\n");
13086
+ const numberedLines = [];
13087
+ for (let i = 0; i < lines.length; i++) {
13088
+ numberedLines.push((i + 1) + ": " + lines[i] + "\n");
13089
+ }
13090
+ this.errors = [];
13091
+ this.errors.push("");
13092
+ this.errors.push(gl.getShaderInfoLog(this.handle));
13093
+ this.errors = this.errors.concat(numberedLines.join(""));
13094
+ }
13031
13095
  }
13096
+ }
13032
13097
 
13033
- this.buffer = {
13034
- framebuf: framebuf,
13035
- renderbuf: renderbuf,
13036
- texture: colorTexture,
13037
- depthTexture: depthTexture,
13038
- width: width,
13039
- height: height
13040
- };
13098
+ destroy() {
13041
13099
 
13042
- this.bound = false;
13043
13100
  }
13101
+ }
13044
13102
 
13045
- clear() {
13046
- if (!this.bound) {
13047
- throw "Render buffer not bound";
13048
- }
13049
- const gl = this.gl;
13050
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
13103
+ /**
13104
+ * @desc A low-level component that represents a WebGL Sampler.
13105
+ * @private
13106
+ */
13107
+ class Sampler {
13108
+
13109
+ constructor(gl, location) {
13110
+ this.bindTexture = function (texture, unit) {
13111
+ if (texture.bind(unit)) {
13112
+ gl.uniform1i(location, unit);
13113
+ return true;
13114
+ }
13115
+ return false;
13116
+ };
13051
13117
  }
13118
+ }
13052
13119
 
13053
- read(pickX, pickY) {
13054
- const x = pickX;
13055
- const y = this.gl.drawingBufferHeight - pickY;
13056
- const pix = new Uint8Array(4);
13057
- const gl = this.gl;
13058
- gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pix);
13059
- return pix;
13120
+ /**
13121
+ * @desc Represents a WebGL vertex attribute buffer (VBO).
13122
+ * @private
13123
+ * @param gl {WebGLRenderingContext} The WebGL rendering context.
13124
+ */
13125
+ class Attribute {
13126
+
13127
+ constructor(gl, location) {
13128
+ this._gl = gl;
13129
+ this.location = location;
13060
13130
  }
13061
13131
 
13062
- readImage(params) {
13132
+ bindArrayBuffer(arrayBuf) {
13133
+ if (!arrayBuf) {
13134
+ return;
13135
+ }
13136
+ arrayBuf.bind();
13137
+ this._gl.enableVertexAttribArray(this.location);
13138
+ this._gl.vertexAttribPointer(this.location, arrayBuf.itemSize, arrayBuf.itemType, arrayBuf.normalized, arrayBuf.stride, arrayBuf.offset);
13139
+ }
13140
+ }
13063
13141
 
13064
- const gl = this.gl;
13065
- const imageDataCache = this._getImageDataCache();
13066
- const pixelData = imageDataCache.pixelData;
13067
- const canvas = imageDataCache.canvas;
13068
- const imageData = imageDataCache.imageData;
13069
- const context = imageDataCache.context;
13142
+ const ids$4 = new Map$1({});
13070
13143
 
13071
- gl.readPixels(0, 0, this.buffer.width, this.buffer.height, gl.RGBA, gl.UNSIGNED_BYTE, pixelData);
13144
+ function joinSansComments(srcLines) {
13145
+ const src = [];
13146
+ let line;
13147
+ let n;
13148
+ for (let i = 0, len = srcLines.length; i < len; i++) {
13149
+ line = srcLines[i];
13150
+ n = line.indexOf("/");
13151
+ if (n > 0) {
13152
+ if (line.charAt(n + 1) === "/") {
13153
+ line = line.substring(0, n);
13154
+ }
13155
+ }
13156
+ src.push(line);
13157
+ }
13158
+ return src.join("\n");
13159
+ }
13072
13160
 
13073
- imageData.data.set(pixelData);
13074
- context.putImageData(imageData, 0, 0);
13161
+ function logErrors(errors) {
13162
+ console.error(errors.join("\n"));
13163
+ }
13075
13164
 
13076
- const imageWidth = params.width || canvas.width;
13077
- const imageHeight = params.height || canvas.height;
13078
- const format = params.format || "jpeg";
13079
- const flipy = true; // Account for WebGL texture flipping
13165
+ /**
13166
+ * @desc Represents a WebGL program.
13167
+ * @private
13168
+ */
13169
+ class Program {
13080
13170
 
13081
- let image;
13171
+ constructor(gl, shaderSource) {
13172
+ this.id = ids$4.addItem({});
13173
+ this.source = shaderSource;
13174
+ this.init(gl);
13175
+ }
13082
13176
 
13083
- switch (format) {
13084
- case "jpeg":
13085
- image = Canvas2Image.saveAsJPEG(canvas, true, imageWidth, imageHeight, flipy);
13086
- break;
13087
- case "png":
13088
- image = Canvas2Image.saveAsPNG(canvas, true, imageWidth, imageHeight, flipy);
13089
- break;
13090
- case "bmp":
13091
- image = Canvas2Image.saveAsBMP(canvas, true, imageWidth, imageHeight, flipy);
13092
- break;
13093
- default:
13094
- console.error("Unsupported image format: '" + format + "' - supported types are 'jpeg', 'bmp' and 'png' - defaulting to 'jpeg'");
13095
- image = Canvas2Image.saveAsJPEG(canvas, true, imageWidth, imageHeight, flipy);
13177
+ init(gl) {
13178
+ this.gl = gl;
13179
+ this.allocated = false;
13180
+ this.compiled = false;
13181
+ this.linked = false;
13182
+ this.validated = false;
13183
+ this.errors = null;
13184
+ this.uniforms = {};
13185
+ this.samplers = {};
13186
+ this.attributes = {};
13187
+ this._vertexShader = new Shader(gl, gl.VERTEX_SHADER, joinSansComments(this.source.vertex));
13188
+ this._fragmentShader = new Shader(gl, gl.FRAGMENT_SHADER, joinSansComments(this.source.fragment));
13189
+ if (!this._vertexShader.allocated) {
13190
+ this.errors = ["Vertex shader failed to allocate"].concat(this._vertexShader.errors);
13191
+ logErrors(this.errors);
13192
+ return;
13193
+ }
13194
+ if (!this._fragmentShader.allocated) {
13195
+ this.errors = ["Fragment shader failed to allocate"].concat(this._fragmentShader.errors);
13196
+ logErrors(this.errors);
13197
+ return;
13198
+ }
13199
+ this.allocated = true;
13200
+ if (!this._vertexShader.compiled) {
13201
+ this.errors = ["Vertex shader failed to compile"].concat(this._vertexShader.errors);
13202
+ logErrors(this.errors);
13203
+ return;
13204
+ }
13205
+ if (!this._fragmentShader.compiled) {
13206
+ this.errors = ["Fragment shader failed to compile"].concat(this._fragmentShader.errors);
13207
+ logErrors(this.errors);
13208
+ return;
13209
+ }
13210
+ this.compiled = true;
13211
+ let a;
13212
+ let i;
13213
+ let u;
13214
+ let uName;
13215
+ let location;
13216
+ this.handle = gl.createProgram();
13217
+ if (!this.handle) {
13218
+ this.errors = ["Failed to allocate program"];
13219
+ return;
13220
+ }
13221
+ gl.attachShader(this.handle, this._vertexShader.handle);
13222
+ gl.attachShader(this.handle, this._fragmentShader.handle);
13223
+ gl.linkProgram(this.handle);
13224
+ this.linked = gl.getProgramParameter(this.handle, gl.LINK_STATUS);
13225
+ // HACK: Disable validation temporarily: https://github.com/xeolabs/xeokit/issues/5
13226
+ // Perhaps we should defer validation until render-time, when the program has values set for all inputs?
13227
+ this.validated = true;
13228
+ if (!this.linked || !this.validated) {
13229
+ this.errors = [];
13230
+ this.errors.push("");
13231
+ this.errors.push(gl.getProgramInfoLog(this.handle));
13232
+ this.errors.push("\nVertex shader:\n");
13233
+ this.errors = this.errors.concat(this.source.vertex);
13234
+ this.errors.push("\nFragment shader:\n");
13235
+ this.errors = this.errors.concat(this.source.fragment);
13236
+ logErrors(this.errors);
13237
+ return;
13096
13238
  }
13097
-
13098
- return image.src;
13099
- }
13100
-
13101
- _getImageDataCache() {
13102
-
13103
- const bufferWidth = this.buffer.width;
13104
- const bufferHeight = this.buffer.height;
13105
-
13106
- let imageDataCache = this._imageDataCache;
13107
-
13108
- if (imageDataCache) {
13109
- if (imageDataCache.width !== bufferWidth || imageDataCache.height !== bufferHeight) {
13110
- this._imageDataCache = null;
13111
- imageDataCache = null;
13239
+ const numUniforms = gl.getProgramParameter(this.handle, gl.ACTIVE_UNIFORMS);
13240
+ for (i = 0; i < numUniforms; ++i) {
13241
+ u = gl.getActiveUniform(this.handle, i);
13242
+ if (u) {
13243
+ uName = u.name;
13244
+ if (uName[uName.length - 1] === "\u0000") {
13245
+ uName = uName.substr(0, uName.length - 1);
13246
+ }
13247
+ location = gl.getUniformLocation(this.handle, uName);
13248
+ if ((u.type === gl.SAMPLER_2D) || (u.type === gl.SAMPLER_CUBE) || (u.type === 35682)) {
13249
+ this.samplers[uName] = new Sampler(gl, location);
13250
+ } else {
13251
+ this.uniforms[uName] = location;
13252
+ }
13112
13253
  }
13113
13254
  }
13114
-
13115
- if (!imageDataCache) {
13116
-
13117
- const canvas = document.createElement('canvas');
13118
- canvas.width = bufferWidth;
13119
- canvas.height = bufferHeight;
13120
-
13121
- const context = canvas.getContext('2d');
13122
- const imageData = context.createImageData(bufferWidth, bufferHeight);
13123
-
13124
- imageDataCache = {
13125
- pixelData: new Uint8Array(bufferWidth * bufferHeight * 4),
13126
- canvas: canvas,
13127
- context: context,
13128
- imageData: imageData,
13129
- width: bufferWidth,
13130
- height: bufferHeight
13131
- };
13132
-
13133
- this._imageDataCache = imageDataCache;
13255
+ const numAttribs = gl.getProgramParameter(this.handle, gl.ACTIVE_ATTRIBUTES);
13256
+ for (i = 0; i < numAttribs; i++) {
13257
+ a = gl.getActiveAttrib(this.handle, i);
13258
+ if (a) {
13259
+ location = gl.getAttribLocation(this.handle, a.name);
13260
+ this.attributes[a.name] = new Attribute(gl, location);
13261
+ }
13134
13262
  }
13135
-
13136
- return imageDataCache;
13263
+ this.allocated = true;
13137
13264
  }
13138
13265
 
13139
- unbind() {
13140
- const gl = this.gl;
13141
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
13142
- this.bound = false;
13266
+ bind() {
13267
+ if (!this.allocated) {
13268
+ return;
13269
+ }
13270
+ this.gl.useProgram(this.handle);
13143
13271
  }
13144
13272
 
13145
- getTexture() {
13146
- const self = this;
13147
- return this._texture || (this._texture = {
13148
- renderBuffer: this,
13149
- bind: function (unit) {
13150
- if (self.buffer && self.buffer.texture) {
13151
- self.gl.activeTexture(self.gl["TEXTURE" + unit]);
13152
- self.gl.bindTexture(self.gl.TEXTURE_2D, self.buffer.texture);
13153
- return true;
13154
- }
13155
- return false;
13156
- },
13157
- unbind: function (unit) {
13158
- if (self.buffer && self.buffer.texture) {
13159
- self.gl.activeTexture(self.gl["TEXTURE" + unit]);
13160
- self.gl.bindTexture(self.gl.TEXTURE_2D, null);
13161
- }
13162
- }
13163
- });
13273
+ getLocation(name) {
13274
+ if (!this.allocated) {
13275
+ return;
13276
+ }
13277
+ return this.uniforms[name];
13164
13278
  }
13165
13279
 
13166
- hasDepthTexture() {
13167
- return this._hasDepthTexture;
13280
+ getAttribute(name) {
13281
+ if (!this.allocated) {
13282
+ return;
13283
+ }
13284
+ return this.attributes[name];
13168
13285
  }
13169
13286
 
13170
- getDepthTexture() {
13171
- if (!this._hasDepthTexture) {
13172
- return null;
13287
+ bindTexture(name, texture, unit) {
13288
+ if (!this.allocated) {
13289
+ return false;
13290
+ }
13291
+ const sampler = this.samplers[name];
13292
+ if (sampler) {
13293
+ return sampler.bindTexture(texture, unit);
13294
+ } else {
13295
+ return false;
13173
13296
  }
13174
- const self = this;
13175
- return this._depthTexture || (this._dethTexture = {
13176
- renderBuffer: this,
13177
- bind: function (unit) {
13178
- if (self.buffer && self.buffer.depthTexture) {
13179
- self.gl.activeTexture(self.gl["TEXTURE" + unit]);
13180
- self.gl.bindTexture(self.gl.TEXTURE_2D, self.buffer.depthTexture);
13181
- return true;
13182
- }
13183
- return false;
13184
- },
13185
- unbind: function (unit) {
13186
- if (self.buffer && self.buffer.depthTexture) {
13187
- self.gl.activeTexture(self.gl["TEXTURE" + unit]);
13188
- self.gl.bindTexture(self.gl.TEXTURE_2D, null);
13189
- }
13190
- }
13191
- });
13192
13297
  }
13193
13298
 
13194
13299
  destroy() {
13195
- if (this.allocated) {
13196
- const gl = this.gl;
13197
- gl.deleteTexture(this.buffer.texture);
13198
- gl.deleteTexture(this.buffer.depthTexture);
13199
- gl.deleteFramebuffer(this.buffer.framebuf);
13200
- gl.deleteRenderbuffer(this.buffer.renderbuf);
13201
- this.allocated = false;
13202
- this.buffer = null;
13203
- this.bound = false;
13300
+ if (!this.allocated) {
13301
+ return;
13204
13302
  }
13205
- this._imageDataCache = null;
13206
- this._texture = null;
13207
- this._depthTexture = null;
13303
+ ids$4.removeItem(this.id);
13304
+ this.gl.deleteProgram(this.handle);
13305
+ this.gl.deleteShader(this._vertexShader.handle);
13306
+ this.gl.deleteShader(this._fragmentShader.handle);
13307
+ this.handle = null;
13308
+ this.attributes = null;
13309
+ this.uniforms = null;
13310
+ this.samplers = null;
13311
+ this.allocated = false;
13208
13312
  }
13209
13313
  }
13210
13314
 
13211
- /**
13212
- * @desc Pick result returned by {@link Scene#pick}.
13315
+ /*
13316
+ * Canvas2Image v0.1
13317
+ * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
13318
+ * MIT License [http://www.opensource.org/licenses/mit-license.php]
13213
13319
  *
13320
+ * Modified by @xeolabs to permit vertical flipping, so that snapshot can be taken from WebGL frame buffers,
13321
+ * which vertically flip image data as part of the way that WebGL renders textures.
13214
13322
  */
13215
- class PickResult {
13216
13323
 
13217
- /**
13218
- * @private
13219
- * @param value
13220
- */
13221
- constructor() {
13324
+ /**
13325
+ * @private
13326
+ */
13327
+ const Canvas2Image = (function () {
13328
+ // check if we have canvas support
13329
+ const oCanvas = document.createElement("canvas"), sc = String.fromCharCode;
13222
13330
 
13223
- /**
13224
- * Picked entity.
13225
- * Null when no entity was picked.
13226
- * @property entity
13227
- * @type {Entity|*}
13228
- */
13229
- this.entity = null;
13331
+ // no canvas, bail out.
13332
+ if (!oCanvas.getContext) {
13333
+ return {
13334
+ saveAsBMP: function () {
13335
+ },
13336
+ saveAsPNG: function () {
13337
+ },
13338
+ saveAsJPEG: function () {
13339
+ }
13340
+ }
13341
+ }
13342
+
13343
+ const bHasImageData = !!(oCanvas.getContext("2d").getImageData), bHasDataURL = !!(oCanvas.toDataURL),
13344
+ bHasBase64 = !!(window.btoa);
13345
+
13346
+ // ok, we're good
13347
+ const readCanvasData = function (oCanvas) {
13348
+ const iWidth = parseInt(oCanvas.width), iHeight = parseInt(oCanvas.height);
13349
+ return oCanvas.getContext("2d").getImageData(0, 0, iWidth, iHeight);
13350
+ };
13351
+
13352
+ // base64 encodes either a string or an array of charcodes
13353
+ const encodeData = function (data) {
13354
+ let i, aData, strData = "";
13355
+
13356
+ if (typeof data == "string") {
13357
+ strData = data;
13358
+ } else {
13359
+ aData = data;
13360
+ for (i = 0; i < aData.length; i++) {
13361
+ strData += sc(aData[i]);
13362
+ }
13363
+ }
13364
+ return btoa(strData);
13365
+ };
13366
+
13367
+ // creates a base64 encoded string containing BMP data takes an imagedata object as argument
13368
+ const createBMP = function (oData) {
13369
+ let strHeader = '';
13370
+ const iWidth = oData.width;
13371
+ const iHeight = oData.height;
13372
+
13373
+ strHeader += 'BM';
13374
+
13375
+ let iFileSize = iWidth * iHeight * 4 + 54; // total header size = 54 bytes
13376
+ strHeader += sc(iFileSize % 256);
13377
+ iFileSize = Math.floor(iFileSize / 256);
13378
+ strHeader += sc(iFileSize % 256);
13379
+ iFileSize = Math.floor(iFileSize / 256);
13380
+ strHeader += sc(iFileSize % 256);
13381
+ iFileSize = Math.floor(iFileSize / 256);
13382
+ strHeader += sc(iFileSize % 256);
13383
+
13384
+ strHeader += sc(0, 0, 0, 0, 54, 0, 0, 0); // data offset
13385
+ strHeader += sc(40, 0, 0, 0); // info header size
13386
+
13387
+ let iImageWidth = iWidth;
13388
+ strHeader += sc(iImageWidth % 256);
13389
+ iImageWidth = Math.floor(iImageWidth / 256);
13390
+ strHeader += sc(iImageWidth % 256);
13391
+ iImageWidth = Math.floor(iImageWidth / 256);
13392
+ strHeader += sc(iImageWidth % 256);
13393
+ iImageWidth = Math.floor(iImageWidth / 256);
13394
+ strHeader += sc(iImageWidth % 256);
13395
+
13396
+ let iImageHeight = iHeight;
13397
+ strHeader += sc(iImageHeight % 256);
13398
+ iImageHeight = Math.floor(iImageHeight / 256);
13399
+ strHeader += sc(iImageHeight % 256);
13400
+ iImageHeight = Math.floor(iImageHeight / 256);
13401
+ strHeader += sc(iImageHeight % 256);
13402
+ iImageHeight = Math.floor(iImageHeight / 256);
13403
+ strHeader += sc(iImageHeight % 256);
13230
13404
 
13231
- /**
13232
- * Type of primitive that was picked - usually "triangle".
13233
- * Null when no primitive was picked.
13234
- * @property primitive
13235
- * @type {String}
13236
- */
13237
- this.primitive = null;
13405
+ strHeader += sc(1, 0, 32, 0); // num of planes & num of bits per pixel
13406
+ strHeader += sc(0, 0, 0, 0); // compression = none
13238
13407
 
13239
- /**
13240
- * Index of primitive that was picked.
13241
- * -1 when no entity was picked.
13242
- * @property primIndex
13243
- * @type {number}
13244
- */
13245
- this.primIndex = -1;
13408
+ let iDataSize = iWidth * iHeight * 4;
13409
+ strHeader += sc(iDataSize % 256);
13410
+ iDataSize = Math.floor(iDataSize / 256);
13411
+ strHeader += sc(iDataSize % 256);
13412
+ iDataSize = Math.floor(iDataSize / 256);
13413
+ strHeader += sc(iDataSize % 256);
13414
+ iDataSize = Math.floor(iDataSize / 256);
13415
+ strHeader += sc(iDataSize % 256);
13246
13416
 
13247
- /**
13248
- * True when the picked surface position is full precision.
13249
- * When false, the picked surface position should be regarded as approximate.
13250
- * Full-precision surface picking is performed with the {@link Scene#pick} method's ````pickSurfacePrecision```` option.
13251
- * @property pickSurfacePrecision
13252
- * @type {Boolean}
13253
- */
13254
- this.pickSurfacePrecision = false;
13417
+ strHeader += sc(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); // these bytes are not used
13255
13418
 
13256
- this._canvasPos = new Int16Array([0, 0]);
13257
- this._origin = new Float64Array([0, 0, 0]);
13258
- this._direction = new Float64Array([0, 0, 0]);
13259
- this._indices = new Int32Array(3);
13260
- this._localPos = new Float64Array([0, 0, 0]);
13261
- this._worldPos = new Float64Array([0, 0, 0]);
13262
- this._viewPos = new Float64Array([0, 0, 0]);
13263
- this._bary = new Float64Array([0, 0, 0]);
13264
- this._worldNormal = new Float64Array([0, 0, 0]);
13265
- this._uv = new Float64Array([0, 0]);
13419
+ const aImgData = oData.data;
13420
+ let strPixelData = "";
13421
+ let x;
13422
+ let y = iHeight;
13423
+ let iOffsetX;
13424
+ let iOffsetY;
13425
+ let strPixelRow;
13266
13426
 
13267
- this.reset();
13268
- }
13427
+ do {
13428
+ iOffsetY = iWidth * (y - 1) * 4;
13429
+ strPixelRow = "";
13430
+ for (x = 0; x < iWidth; x++) {
13431
+ iOffsetX = 4 * x;
13432
+ strPixelRow += sc(
13433
+ aImgData[iOffsetY + iOffsetX + 2], // B
13434
+ aImgData[iOffsetY + iOffsetX + 1], // G
13435
+ aImgData[iOffsetY + iOffsetX], // R
13436
+ aImgData[iOffsetY + iOffsetX + 3] // A
13437
+ );
13438
+ }
13439
+ strPixelData += strPixelRow;
13440
+ } while (--y);
13269
13441
 
13270
- /**
13271
- * Canvas coordinates when picking with a 2D pointer.
13272
- * @property canvasPos
13273
- * @type {Number[]}
13274
- */
13275
- get canvasPos() {
13276
- return this._gotCanvasPos ? this._canvasPos : null;
13277
- }
13442
+ return encodeData(strHeader + strPixelData);
13443
+ };
13278
13444
 
13279
- /**
13280
- * @private
13281
- * @param value
13282
- */
13283
- set canvasPos(value) {
13284
- if (value) {
13285
- this._canvasPos[0] = value[0];
13286
- this._canvasPos[1] = value[1];
13287
- this._gotCanvasPos = true;
13288
- } else {
13289
- this._gotCanvasPos = false;
13445
+ // sends the generated file to the client
13446
+ const saveFile = function (strData) {
13447
+ if (!window.open(strData)) {
13448
+ document.location.href = strData;
13290
13449
  }
13291
- }
13450
+ };
13292
13451
 
13293
- /**
13294
- * World-space 3D ray origin when raypicked.
13295
- * @property origin
13296
- * @type {Number[]}
13297
- */
13298
- get origin() {
13299
- return this._gotOrigin ? this._origin : null;
13300
- }
13452
+ const makeDataURI = function (strData, strMime) {
13453
+ return "data:" + strMime + ";base64," + strData;
13454
+ };
13301
13455
 
13302
- /**
13303
- * @private
13304
- * @param value
13305
- */
13306
- set origin(value) {
13307
- if (value) {
13308
- this._origin[0] = value[0];
13309
- this._origin[1] = value[1];
13310
- this._origin[2] = value[2];
13311
- this._gotOrigin = true;
13312
- } else {
13313
- this._gotOrigin = false;
13456
+ // generates a <img> object containing the imagedata
13457
+ const makeImageObject = function (strSource) {
13458
+ const oImgElement = document.createElement("img");
13459
+ oImgElement.src = strSource;
13460
+ return oImgElement;
13461
+ };
13462
+
13463
+ const scaleCanvas = function (oCanvas, iWidth, iHeight, flipy) {
13464
+ if (iWidth && iHeight) {
13465
+ const oSaveCanvas = document.createElement("canvas");
13466
+ oSaveCanvas.width = iWidth;
13467
+ oSaveCanvas.height = iHeight;
13468
+ oSaveCanvas.style.width = iWidth + "px";
13469
+ oSaveCanvas.style.height = iHeight + "px";
13470
+ const oSaveCtx = oSaveCanvas.getContext("2d");
13471
+ if (flipy) {
13472
+ oSaveCtx.save();
13473
+ oSaveCtx.scale(1.0, -1.0);
13474
+ oSaveCtx.imageSmoothingEnabled = true;
13475
+ oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, -iHeight);
13476
+ oSaveCtx.restore();
13477
+ } else {
13478
+ oSaveCtx.imageSmoothingEnabled = true;
13479
+ oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iHeight);
13480
+ }
13481
+ return oSaveCanvas;
13314
13482
  }
13315
- }
13483
+ return oCanvas;
13484
+ };
13316
13485
 
13317
- /**
13318
- * World-space 3D ray direction when raypicked.
13319
- * @property direction
13320
- * @type {Number[]}
13321
- */
13322
- get direction() {
13323
- return this._gotDirection ? this._direction : null;
13324
- }
13486
+ return {
13487
+ saveAsPNG: function (oCanvas, bReturnImg, iWidth, iHeight, flipy) {
13488
+ if (!bHasDataURL) return false;
13489
+ const oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight, flipy);
13490
+ const strMime = "image/png";
13491
+ const strData = oScaledCanvas.toDataURL(strMime);
13492
+ if (bReturnImg) {
13493
+ return makeImageObject(strData);
13494
+ } else {
13495
+ saveFile(strData);
13496
+ }
13497
+ return true;
13498
+ },
13325
13499
 
13326
- /**
13327
- * @private
13328
- * @param value
13329
- */
13330
- set direction(value) {
13331
- if (value) {
13332
- this._direction[0] = value[0];
13333
- this._direction[1] = value[1];
13334
- this._direction[2] = value[2];
13335
- this._gotDirection = true;
13336
- } else {
13337
- this._gotDirection = false;
13338
- }
13339
- }
13340
-
13341
- /**
13342
- * Picked triangle's vertex indices.
13343
- * Only defined when an entity and triangle was picked.
13344
- * @property indices
13345
- * @type {Int32Array}
13346
- */
13347
- get indices() {
13348
- return this.entity && this._gotIndices ? this._indices : null;
13349
- }
13500
+ saveAsJPEG: function (oCanvas, bReturnImg, iWidth, iHeight, flipy) {
13501
+ if (!bHasDataURL) return false;
13502
+ const oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight, flipy);
13503
+ const strMime = "image/jpeg";
13504
+ const strData = oScaledCanvas.toDataURL(strMime);
13505
+ // check if browser actually supports jpeg by looking for the mime type in the data uri. if not, return false
13506
+ if (strData.indexOf(strMime) != 5) return false;
13507
+ if (bReturnImg) {
13508
+ return makeImageObject(strData);
13509
+ } else {
13510
+ saveFile(strData);
13511
+ }
13512
+ return true;
13513
+ },
13350
13514
 
13351
- /**
13352
- * @private
13353
- * @param value
13354
- */
13355
- set indices(value) {
13356
- if (value) {
13357
- this._indices[0] = value[0];
13358
- this._indices[1] = value[1];
13359
- this._indices[2] = value[2];
13360
- this._gotIndices = true;
13361
- } else {
13362
- this._gotIndices = false;
13515
+ saveAsBMP: function (oCanvas, bReturnImg, iWidth, iHeight, flipy) {
13516
+ if (!(bHasDataURL && bHasImageData && bHasBase64)) return false;
13517
+ const oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight, flipy);
13518
+ const strMime = "image/bmp";
13519
+ const oData = readCanvasData(oScaledCanvas), strImgData = createBMP(oData);
13520
+ if (bReturnImg) {
13521
+ return makeImageObject(makeDataURI(strImgData, strMime));
13522
+ } else {
13523
+ saveFile(makeDataURI(strImgData, strMime));
13524
+ }
13525
+ return true;
13363
13526
  }
13364
- }
13527
+ };
13528
+ })();
13365
13529
 
13366
- /**
13367
- * Picked Local-space point on surface.
13368
- * Only defined when an entity and a point on its surface was picked.
13369
- * @property localPos
13370
- * @type {Number[]}
13371
- */
13372
- get localPos() {
13373
- return this.entity && this._gotLocalPos ? this._localPos : null;
13530
+ /**
13531
+ * @desc Represents a WebGL render buffer.
13532
+ * @private
13533
+ */
13534
+ class RenderBuffer {
13535
+
13536
+ constructor(canvas, gl, options) {
13537
+ options = options || {};
13538
+ this.gl = gl;
13539
+ this.allocated = false;
13540
+ this.canvas = canvas;
13541
+ this.buffer = null;
13542
+ this.bound = false;
13543
+ this.size = options.size;
13544
+ this._hasDepthTexture = !!options.depthTexture;
13374
13545
  }
13375
13546
 
13376
- /**
13377
- * @private
13378
- * @param value
13379
- */
13380
- set localPos(value) {
13381
- if (value) {
13382
- this._localPos[0] = value[0];
13383
- this._localPos[1] = value[1];
13384
- this._localPos[2] = value[2];
13385
- this._gotLocalPos = true;
13386
- } else {
13387
- this._gotLocalPos = false;
13388
- }
13547
+ setSize(size) {
13548
+ this.size = size;
13389
13549
  }
13390
13550
 
13391
- /**
13392
- * Picked World-space point on surface.
13393
- * Only defined when an entity and a point on its surface was picked.
13394
- * @property worldPos
13395
- * @type {Number[]}
13396
- */
13397
- get worldPos() {
13398
- return this.entity && this._gotWorldPos ? this._worldPos : null;
13551
+ webglContextRestored(gl) {
13552
+ this.gl = gl;
13553
+ this.buffer = null;
13554
+ this.allocated = false;
13555
+ this.bound = false;
13399
13556
  }
13400
13557
 
13401
- /**
13402
- * @private
13403
- * @param value
13404
- */
13405
- set worldPos(value) {
13406
- if (value) {
13407
- this._worldPos[0] = value[0];
13408
- this._worldPos[1] = value[1];
13409
- this._worldPos[2] = value[2];
13410
- this._gotWorldPos = true;
13411
- } else {
13412
- this._gotWorldPos = false;
13558
+ bind() {
13559
+ this._touch();
13560
+ if (this.bound) {
13561
+ return;
13413
13562
  }
13563
+ const gl = this.gl;
13564
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.buffer.framebuf);
13565
+ this.bound = true;
13414
13566
  }
13415
13567
 
13416
- /**
13417
- * Picked View-space point on surface.
13418
- * Only defined when an entity and a point on its surface was picked.
13419
- * @property viewPos
13420
- * @type {Number[]}
13421
- */
13422
- get viewPos() {
13423
- return this.entity && this._gotViewPos ? this._viewPos : null;
13424
- }
13568
+ _touch() {
13569
+
13570
+ let width;
13571
+ let height;
13572
+ const gl = this.gl;
13573
+
13574
+ if (this.size) {
13575
+ width = this.size[0];
13576
+ height = this.size[1];
13425
13577
 
13426
- /**
13427
- * @private
13428
- * @param value
13429
- */
13430
- set viewPos(value) {
13431
- if (value) {
13432
- this._viewPos[0] = value[0];
13433
- this._viewPos[1] = value[1];
13434
- this._viewPos[2] = value[2];
13435
- this._gotViewPos = true;
13436
13578
  } else {
13437
- this._gotViewPos = false;
13579
+ width = gl.drawingBufferWidth;
13580
+ height = gl.drawingBufferHeight;
13438
13581
  }
13439
- }
13440
13582
 
13441
- /**
13442
- * Barycentric coordinate within picked triangle.
13443
- * Only defined when an entity and a point on its surface was picked.
13444
- * @property bary
13445
- * @type {Number[]}
13446
- */
13447
- get bary() {
13448
- return this.entity && this._gotBary ? this._bary : null;
13449
- }
13583
+ if (this.buffer) {
13450
13584
 
13451
- /**
13452
- * @private
13453
- * @param value
13454
- */
13455
- set bary(value) {
13456
- if (value) {
13457
- this._bary[0] = value[0];
13458
- this._bary[1] = value[1];
13459
- this._bary[2] = value[2];
13460
- this._gotBary = true;
13461
- } else {
13462
- this._gotBary = false;
13585
+ if (this.buffer.width === width && this.buffer.height === height) {
13586
+ return;
13587
+
13588
+ } else {
13589
+ gl.deleteTexture(this.buffer.texture);
13590
+ gl.deleteFramebuffer(this.buffer.framebuf);
13591
+ gl.deleteRenderbuffer(this.buffer.renderbuf);
13592
+ }
13463
13593
  }
13464
- }
13465
13594
 
13466
- /**
13467
- * Normal vector at picked position on surface.
13468
- * Only defined when an entity and a point on its surface was picked.
13469
- * @property worldNormal
13470
- * @type {Number[]}
13471
- */
13472
- get worldNormal() {
13473
- return this.entity && this._gotWorldNormal ? this._worldNormal : null;
13474
- }
13595
+ const colorTexture = gl.createTexture();
13596
+ gl.bindTexture(gl.TEXTURE_2D, colorTexture);
13597
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
13598
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
13599
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
13600
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
13601
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
13475
13602
 
13476
- /**
13477
- * @private
13478
- * @param value
13479
- */
13480
- set worldNormal(value) {
13481
- if (value) {
13482
- this._worldNormal[0] = value[0];
13483
- this._worldNormal[1] = value[1];
13484
- this._worldNormal[2] = value[2];
13485
- this._gotWorldNormal = true;
13486
- } else {
13487
- this._gotWorldNormal = false;
13603
+ let depthTexture;
13604
+
13605
+ if (this._hasDepthTexture) {
13606
+ depthTexture = gl.createTexture();
13607
+ gl.bindTexture(gl.TEXTURE_2D, depthTexture);
13608
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
13609
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
13610
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
13611
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
13612
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
13488
13613
  }
13489
- }
13490
13614
 
13491
- /**
13492
- * UV coordinates at picked position on surface.
13493
- * Only defined when an entity and a point on its surface was picked.
13494
- * @property uv
13495
- * @type {Number[]}
13496
- */
13497
- get uv() {
13498
- return this.entity && this._gotUV ? this._uv : null;
13499
- }
13615
+ const renderbuf = gl.createRenderbuffer();
13616
+ gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuf);
13617
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
13500
13618
 
13501
- /**
13502
- * @private
13503
- * @param value
13504
- */
13505
- set uv(value) {
13506
- if (value) {
13507
- this._uv[0] = value[0];
13508
- this._uv[1] = value[1];
13509
- this._gotUV = true;
13619
+ const framebuf = gl.createFramebuffer();
13620
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);
13621
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTexture, 0);
13622
+
13623
+ if (this._hasDepthTexture) {
13624
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, 0);
13510
13625
  } else {
13511
- this._gotUV = false;
13626
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuf);
13512
13627
  }
13513
- }
13514
13628
 
13515
- /**
13516
- * @private
13517
- */
13518
- reset() {
13519
- this.entity = null;
13520
- this.primIndex = -1;
13521
- this.primitive = null;
13522
- this.pickSurfacePrecision = false;
13523
- this._gotCanvasPos = false;
13524
- this._gotOrigin = false;
13525
- this._gotDirection = false;
13526
- this._gotIndices = false;
13527
- this._gotLocalPos = false;
13528
- this._gotWorldPos = false;
13529
- this._gotViewPos = false;
13530
- this._gotBary = false;
13531
- this._gotWorldNormal = false;
13532
- this._gotUV = false;
13533
- }
13534
- }
13629
+ gl.bindTexture(gl.TEXTURE_2D, null);
13630
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
13631
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
13535
13632
 
13536
- /**
13537
- * @desc Represents a vertex or fragment stage within a {@link Program}.
13538
- * @private
13539
- */
13540
- class Shader {
13633
+ // Verify framebuffer is OK
13541
13634
 
13542
- constructor(gl, type, source) {
13635
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuf);
13636
+ if (!gl.isFramebuffer(framebuf)) {
13637
+ throw "Invalid framebuffer";
13638
+ }
13639
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
13543
13640
 
13544
- this.allocated = false;
13545
- this.compiled = false;
13546
- this.handle = gl.createShader(type);
13641
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
13547
13642
 
13548
- if (!this.handle) {
13549
- this.errors = [
13550
- "Failed to allocate"
13551
- ];
13552
- return;
13553
- }
13643
+ switch (status) {
13554
13644
 
13555
- this.allocated = true;
13645
+ case gl.FRAMEBUFFER_COMPLETE:
13646
+ break;
13556
13647
 
13557
- gl.shaderSource(this.handle, source);
13558
- gl.compileShader(this.handle);
13648
+ case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
13649
+ throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
13559
13650
 
13560
- this.compiled = gl.getShaderParameter(this.handle, gl.COMPILE_STATUS);
13651
+ case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
13652
+ throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
13561
13653
 
13562
- if (!this.compiled) {
13654
+ case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
13655
+ throw "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
13563
13656
 
13564
- if (!gl.isContextLost()) { // Handled explicitly elsewhere, so won't re-handle here
13657
+ case gl.FRAMEBUFFER_UNSUPPORTED:
13658
+ throw "Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED";
13565
13659
 
13566
- const lines = source.split("\n");
13567
- const numberedLines = [];
13568
- for (let i = 0; i < lines.length; i++) {
13569
- numberedLines.push((i + 1) + ": " + lines[i] + "\n");
13570
- }
13571
- this.errors = [];
13572
- this.errors.push("");
13573
- this.errors.push(gl.getShaderInfoLog(this.handle));
13574
- this.errors = this.errors.concat(numberedLines.join(""));
13575
- }
13660
+ default:
13661
+ throw "Incomplete framebuffer: " + status;
13576
13662
  }
13663
+
13664
+ this.buffer = {
13665
+ framebuf: framebuf,
13666
+ renderbuf: renderbuf,
13667
+ texture: colorTexture,
13668
+ depthTexture: depthTexture,
13669
+ width: width,
13670
+ height: height
13671
+ };
13672
+
13673
+ this.bound = false;
13577
13674
  }
13578
13675
 
13579
- destroy() {
13676
+ clear() {
13677
+ if (!this.bound) {
13678
+ throw "Render buffer not bound";
13679
+ }
13680
+ const gl = this.gl;
13681
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
13682
+ }
13580
13683
 
13684
+ read(pickX, pickY) {
13685
+ const x = pickX;
13686
+ const y = this.gl.drawingBufferHeight - pickY;
13687
+ const pix = new Uint8Array(4);
13688
+ const gl = this.gl;
13689
+ gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pix);
13690
+ return pix;
13581
13691
  }
13582
- }
13583
13692
 
13584
- /**
13585
- * @desc A low-level component that represents a WebGL Sampler.
13586
- * @private
13587
- */
13588
- class Sampler {
13693
+ readImage(params) {
13589
13694
 
13590
- constructor(gl, location) {
13591
- this.bindTexture = function (texture, unit) {
13592
- if (texture.bind(unit)) {
13593
- gl.uniform1i(location, unit);
13594
- return true;
13595
- }
13596
- return false;
13597
- };
13598
- }
13599
- }
13695
+ const gl = this.gl;
13696
+ const imageDataCache = this._getImageDataCache();
13697
+ const pixelData = imageDataCache.pixelData;
13698
+ const canvas = imageDataCache.canvas;
13699
+ const imageData = imageDataCache.imageData;
13700
+ const context = imageDataCache.context;
13600
13701
 
13601
- /**
13602
- * @desc Represents a WebGL vertex attribute buffer (VBO).
13603
- * @private
13604
- * @param gl {WebGLRenderingContext} The WebGL rendering context.
13605
- */
13606
- class Attribute {
13702
+ gl.readPixels(0, 0, this.buffer.width, this.buffer.height, gl.RGBA, gl.UNSIGNED_BYTE, pixelData);
13607
13703
 
13608
- constructor(gl, location) {
13609
- this._gl = gl;
13610
- this.location = location;
13611
- }
13704
+ imageData.data.set(pixelData);
13705
+ context.putImageData(imageData, 0, 0);
13612
13706
 
13613
- bindArrayBuffer(arrayBuf) {
13614
- if (!arrayBuf) {
13615
- return;
13616
- }
13617
- arrayBuf.bind();
13618
- this._gl.enableVertexAttribArray(this.location);
13619
- this._gl.vertexAttribPointer(this.location, arrayBuf.itemSize, arrayBuf.itemType, arrayBuf.normalized, arrayBuf.stride, arrayBuf.offset);
13620
- }
13621
- }
13707
+ const imageWidth = params.width || canvas.width;
13708
+ const imageHeight = params.height || canvas.height;
13709
+ const format = params.format || "jpeg";
13710
+ const flipy = true; // Account for WebGL texture flipping
13622
13711
 
13623
- const ids$4 = new Map$1({});
13712
+ let image;
13624
13713
 
13625
- function joinSansComments(srcLines) {
13626
- const src = [];
13627
- let line;
13628
- let n;
13629
- for (let i = 0, len = srcLines.length; i < len; i++) {
13630
- line = srcLines[i];
13631
- n = line.indexOf("/");
13632
- if (n > 0) {
13633
- if (line.charAt(n + 1) === "/") {
13634
- line = line.substring(0, n);
13635
- }
13714
+ switch (format) {
13715
+ case "jpeg":
13716
+ image = Canvas2Image.saveAsJPEG(canvas, true, imageWidth, imageHeight, flipy);
13717
+ break;
13718
+ case "png":
13719
+ image = Canvas2Image.saveAsPNG(canvas, true, imageWidth, imageHeight, flipy);
13720
+ break;
13721
+ case "bmp":
13722
+ image = Canvas2Image.saveAsBMP(canvas, true, imageWidth, imageHeight, flipy);
13723
+ break;
13724
+ default:
13725
+ console.error("Unsupported image format: '" + format + "' - supported types are 'jpeg', 'bmp' and 'png' - defaulting to 'jpeg'");
13726
+ image = Canvas2Image.saveAsJPEG(canvas, true, imageWidth, imageHeight, flipy);
13636
13727
  }
13637
- src.push(line);
13728
+
13729
+ return image.src;
13638
13730
  }
13639
- return src.join("\n");
13640
- }
13641
13731
 
13642
- function logErrors(errors) {
13643
- console.error(errors.join("\n"));
13644
- }
13732
+ _getImageDataCache() {
13645
13733
 
13646
- /**
13647
- * @desc Represents a WebGL program.
13648
- * @private
13649
- */
13650
- class Program {
13734
+ const bufferWidth = this.buffer.width;
13735
+ const bufferHeight = this.buffer.height;
13651
13736
 
13652
- constructor(gl, shaderSource) {
13653
- this.id = ids$4.addItem({});
13654
- this.source = shaderSource;
13655
- this.init(gl);
13656
- }
13737
+ let imageDataCache = this._imageDataCache;
13657
13738
 
13658
- init(gl) {
13659
- this.gl = gl;
13660
- this.allocated = false;
13661
- this.compiled = false;
13662
- this.linked = false;
13663
- this.validated = false;
13664
- this.errors = null;
13665
- this.uniforms = {};
13666
- this.samplers = {};
13667
- this.attributes = {};
13668
- this._vertexShader = new Shader(gl, gl.VERTEX_SHADER, joinSansComments(this.source.vertex));
13669
- this._fragmentShader = new Shader(gl, gl.FRAGMENT_SHADER, joinSansComments(this.source.fragment));
13670
- if (!this._vertexShader.allocated) {
13671
- this.errors = ["Vertex shader failed to allocate"].concat(this._vertexShader.errors);
13672
- logErrors(this.errors);
13673
- return;
13674
- }
13675
- if (!this._fragmentShader.allocated) {
13676
- this.errors = ["Fragment shader failed to allocate"].concat(this._fragmentShader.errors);
13677
- logErrors(this.errors);
13678
- return;
13679
- }
13680
- this.allocated = true;
13681
- if (!this._vertexShader.compiled) {
13682
- this.errors = ["Vertex shader failed to compile"].concat(this._vertexShader.errors);
13683
- logErrors(this.errors);
13684
- return;
13685
- }
13686
- if (!this._fragmentShader.compiled) {
13687
- this.errors = ["Fragment shader failed to compile"].concat(this._fragmentShader.errors);
13688
- logErrors(this.errors);
13689
- return;
13690
- }
13691
- this.compiled = true;
13692
- let a;
13693
- let i;
13694
- let u;
13695
- let uName;
13696
- let location;
13697
- this.handle = gl.createProgram();
13698
- if (!this.handle) {
13699
- this.errors = ["Failed to allocate program"];
13700
- return;
13701
- }
13702
- gl.attachShader(this.handle, this._vertexShader.handle);
13703
- gl.attachShader(this.handle, this._fragmentShader.handle);
13704
- gl.linkProgram(this.handle);
13705
- this.linked = gl.getProgramParameter(this.handle, gl.LINK_STATUS);
13706
- // HACK: Disable validation temporarily: https://github.com/xeolabs/xeokit/issues/5
13707
- // Perhaps we should defer validation until render-time, when the program has values set for all inputs?
13708
- this.validated = true;
13709
- if (!this.linked || !this.validated) {
13710
- this.errors = [];
13711
- this.errors.push("");
13712
- this.errors.push(gl.getProgramInfoLog(this.handle));
13713
- this.errors.push("\nVertex shader:\n");
13714
- this.errors = this.errors.concat(this.source.vertex);
13715
- this.errors.push("\nFragment shader:\n");
13716
- this.errors = this.errors.concat(this.source.fragment);
13717
- logErrors(this.errors);
13718
- return;
13719
- }
13720
- const numUniforms = gl.getProgramParameter(this.handle, gl.ACTIVE_UNIFORMS);
13721
- for (i = 0; i < numUniforms; ++i) {
13722
- u = gl.getActiveUniform(this.handle, i);
13723
- if (u) {
13724
- uName = u.name;
13725
- if (uName[uName.length - 1] === "\u0000") {
13726
- uName = uName.substr(0, uName.length - 1);
13727
- }
13728
- location = gl.getUniformLocation(this.handle, uName);
13729
- if ((u.type === gl.SAMPLER_2D) || (u.type === gl.SAMPLER_CUBE) || (u.type === 35682)) {
13730
- this.samplers[uName] = new Sampler(gl, location);
13731
- } else {
13732
- this.uniforms[uName] = location;
13733
- }
13739
+ if (imageDataCache) {
13740
+ if (imageDataCache.width !== bufferWidth || imageDataCache.height !== bufferHeight) {
13741
+ this._imageDataCache = null;
13742
+ imageDataCache = null;
13734
13743
  }
13735
13744
  }
13736
- const numAttribs = gl.getProgramParameter(this.handle, gl.ACTIVE_ATTRIBUTES);
13737
- for (i = 0; i < numAttribs; i++) {
13738
- a = gl.getActiveAttrib(this.handle, i);
13739
- if (a) {
13740
- location = gl.getAttribLocation(this.handle, a.name);
13741
- this.attributes[a.name] = new Attribute(gl, location);
13742
- }
13745
+
13746
+ if (!imageDataCache) {
13747
+
13748
+ const canvas = document.createElement('canvas');
13749
+ canvas.width = bufferWidth;
13750
+ canvas.height = bufferHeight;
13751
+
13752
+ const context = canvas.getContext('2d');
13753
+ const imageData = context.createImageData(bufferWidth, bufferHeight);
13754
+
13755
+ imageDataCache = {
13756
+ pixelData: new Uint8Array(bufferWidth * bufferHeight * 4),
13757
+ canvas: canvas,
13758
+ context: context,
13759
+ imageData: imageData,
13760
+ width: bufferWidth,
13761
+ height: bufferHeight
13762
+ };
13763
+
13764
+ this._imageDataCache = imageDataCache;
13743
13765
  }
13744
- this.allocated = true;
13766
+
13767
+ return imageDataCache;
13745
13768
  }
13746
13769
 
13747
- bind() {
13748
- if (!this.allocated) {
13749
- return;
13750
- }
13751
- this.gl.useProgram(this.handle);
13770
+ unbind() {
13771
+ const gl = this.gl;
13772
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
13773
+ this.bound = false;
13752
13774
  }
13753
13775
 
13754
- getLocation(name) {
13755
- if (!this.allocated) {
13756
- return;
13757
- }
13758
- return this.uniforms[name];
13776
+ getTexture() {
13777
+ const self = this;
13778
+ return this._texture || (this._texture = {
13779
+ renderBuffer: this,
13780
+ bind: function (unit) {
13781
+ if (self.buffer && self.buffer.texture) {
13782
+ self.gl.activeTexture(self.gl["TEXTURE" + unit]);
13783
+ self.gl.bindTexture(self.gl.TEXTURE_2D, self.buffer.texture);
13784
+ return true;
13785
+ }
13786
+ return false;
13787
+ },
13788
+ unbind: function (unit) {
13789
+ if (self.buffer && self.buffer.texture) {
13790
+ self.gl.activeTexture(self.gl["TEXTURE" + unit]);
13791
+ self.gl.bindTexture(self.gl.TEXTURE_2D, null);
13792
+ }
13793
+ }
13794
+ });
13759
13795
  }
13760
13796
 
13761
- getAttribute(name) {
13762
- if (!this.allocated) {
13763
- return;
13764
- }
13765
- return this.attributes[name];
13797
+ hasDepthTexture() {
13798
+ return this._hasDepthTexture;
13766
13799
  }
13767
13800
 
13768
- bindTexture(name, texture, unit) {
13769
- if (!this.allocated) {
13770
- return false;
13771
- }
13772
- const sampler = this.samplers[name];
13773
- if (sampler) {
13774
- return sampler.bindTexture(texture, unit);
13775
- } else {
13776
- return false;
13801
+ getDepthTexture() {
13802
+ if (!this._hasDepthTexture) {
13803
+ return null;
13777
13804
  }
13805
+ const self = this;
13806
+ return this._depthTexture || (this._dethTexture = {
13807
+ renderBuffer: this,
13808
+ bind: function (unit) {
13809
+ if (self.buffer && self.buffer.depthTexture) {
13810
+ self.gl.activeTexture(self.gl["TEXTURE" + unit]);
13811
+ self.gl.bindTexture(self.gl.TEXTURE_2D, self.buffer.depthTexture);
13812
+ return true;
13813
+ }
13814
+ return false;
13815
+ },
13816
+ unbind: function (unit) {
13817
+ if (self.buffer && self.buffer.depthTexture) {
13818
+ self.gl.activeTexture(self.gl["TEXTURE" + unit]);
13819
+ self.gl.bindTexture(self.gl.TEXTURE_2D, null);
13820
+ }
13821
+ }
13822
+ });
13778
13823
  }
13779
13824
 
13780
13825
  destroy() {
13781
- if (!this.allocated) {
13782
- return;
13826
+ if (this.allocated) {
13827
+ const gl = this.gl;
13828
+ gl.deleteTexture(this.buffer.texture);
13829
+ gl.deleteTexture(this.buffer.depthTexture);
13830
+ gl.deleteFramebuffer(this.buffer.framebuf);
13831
+ gl.deleteRenderbuffer(this.buffer.renderbuf);
13832
+ this.allocated = false;
13833
+ this.buffer = null;
13834
+ this.bound = false;
13783
13835
  }
13784
- ids$4.removeItem(this.id);
13785
- this.gl.deleteProgram(this.handle);
13786
- this.gl.deleteShader(this._vertexShader.handle);
13787
- this.gl.deleteShader(this._fragmentShader.handle);
13788
- this.handle = null;
13789
- this.attributes = null;
13790
- this.uniforms = null;
13791
- this.samplers = null;
13792
- this.allocated = false;
13836
+ this._imageDataCache = null;
13837
+ this._texture = null;
13838
+ this._depthTexture = null;
13793
13839
  }
13794
13840
  }
13795
13841
 
@@ -14132,10 +14178,12 @@ const tempVec3a$_ = math.vec3();
14132
14178
  */
14133
14179
  class OcclusionTester {
14134
14180
 
14135
- constructor(scene) {
14181
+ constructor(scene, renderBufferManager) {
14136
14182
 
14137
14183
  this._scene = scene;
14138
14184
 
14185
+ this._renderBufferManager = renderBufferManager;
14186
+
14139
14187
  this._occlusionLayers = {};
14140
14188
  this._occlusionLayersList = [];
14141
14189
  this._occlusionLayersListDirty = false;
@@ -14280,7 +14328,7 @@ class OcclusionTester {
14280
14328
  }
14281
14329
 
14282
14330
  {
14283
- this._readPixelBuf = this._readPixelBuf || (this._readPixelBuf = new RenderBuffer(this._scene.canvas.canvas, this._scene.canvas.gl));
14331
+ this._readPixelBuf = this._renderBufferManager.getRenderBuffer("occlusionReadPix");
14284
14332
  this._readPixelBuf.bind();
14285
14333
  this._readPixelBuf.clear();
14286
14334
  }
@@ -14492,6 +14540,8 @@ class OcclusionTester {
14492
14540
 
14493
14541
  {
14494
14542
 
14543
+ const resolutionScale = this._scene.canvas.resolutionScale;
14544
+
14495
14545
  const markerR = MARKER_COLOR[0] * 255;
14496
14546
  const markerG = MARKER_COLOR[1] * 255;
14497
14547
  const markerB = MARKER_COLOR[2] * 255;
@@ -14504,7 +14554,7 @@ class OcclusionTester {
14504
14554
 
14505
14555
  const marker = occlusionLayer.occlusionTestList[i];
14506
14556
  const j = i * 2;
14507
- const color = this._readPixelBuf.read(occlusionLayer.pixels[j], occlusionLayer.pixels[j + 1]);
14557
+ const color = this._readPixelBuf.read(Math.round(occlusionLayer.pixels[j] * resolutionScale), Math.round(occlusionLayer.pixels[j + 1] * resolutionScale));
14508
14558
  const visible = (color[0] === markerR) && (color[1] === markerG) && (color[2] === markerB);
14509
14559
 
14510
14560
  marker._setVisible(visible);
@@ -15234,6 +15284,37 @@ function createSampleOffsets(kernelRadius, uvIncrement) {
15234
15284
  return offsets;
15235
15285
  }
15236
15286
 
15287
+ /**
15288
+ * @private
15289
+ */
15290
+ class RenderBufferManager {
15291
+
15292
+ constructor(scene) {
15293
+ this.scene = scene;
15294
+ this._renderBuffersBasic = {};
15295
+ this._renderBuffersScaled = {};
15296
+ }
15297
+
15298
+ getRenderBuffer(id, options) {
15299
+ const renderBuffers = (this.scene.canvas.resolutionScale === 1.0) ? this._renderBuffersBasic : this._renderBuffersScaled;
15300
+ let renderBuffer = renderBuffers[id];
15301
+ if (!renderBuffer) {
15302
+ renderBuffer = new RenderBuffer(this.scene.canvas.canvas, this.scene.canvas.gl, options);
15303
+ renderBuffers[id] = renderBuffer;
15304
+ }
15305
+ return renderBuffer;
15306
+ }
15307
+
15308
+ destroy() {
15309
+ for (let id in this._renderBuffersBasic) {
15310
+ this._renderBuffersBasic[id].destroy();
15311
+ }
15312
+ for (let id in this._renderBuffersScaled) {
15313
+ this._renderBuffersScaled[id].destroy();
15314
+ }
15315
+ }
15316
+ }
15317
+
15237
15318
  /**
15238
15319
  * @private
15239
15320
  */
@@ -15256,14 +15337,12 @@ const Renderer = function (scene, options) {
15256
15337
  let stateSortDirty = true;
15257
15338
  let imageDirty = true;
15258
15339
 
15259
- const saoDepthRenderBuffer = new RenderBuffer(canvas, gl, {
15260
- depthTexture: WEBGL_INFO$1.SUPPORTED_EXTENSIONS["WEBGL_depth_texture"]
15261
- });
15262
- const occlusionRenderBuffer1 = new RenderBuffer(canvas, gl);
15263
- const occlusionRenderBuffer2 = new RenderBuffer(canvas, gl);
15340
+ let transparentEnabled = true;
15341
+ let edgesEnabled = true;
15342
+ let saoEnabled = true;
15343
+ let pbrEnabled = true;
15264
15344
 
15265
- const pickBuffer = new RenderBuffer(canvas, gl);
15266
- const snapshotBuffer = new RenderBuffer(canvas, gl);
15345
+ const renderBufferManager = new RenderBufferManager(scene);
15267
15346
 
15268
15347
  let snapshotBound = false;
15269
15348
 
@@ -15272,6 +15351,26 @@ const Renderer = function (scene, options) {
15272
15351
 
15273
15352
  this._occlusionTester = null; // Lazy-created in #addMarker()
15274
15353
 
15354
+ this.setTransparentEnabled = function (enabled) {
15355
+ transparentEnabled = enabled;
15356
+ imageDirty = true;
15357
+ };
15358
+
15359
+ this.setEdgesEnabled = function (enabled) {
15360
+ edgesEnabled = enabled;
15361
+ imageDirty = true;
15362
+ };
15363
+
15364
+ this.setSAOEnabled = function (enabled) {
15365
+ saoEnabled = enabled;
15366
+ imageDirty = true;
15367
+ };
15368
+
15369
+ this.setPBREnabled = function (enabled) {
15370
+ pbrEnabled = enabled;
15371
+ imageDirty = true;
15372
+ };
15373
+
15275
15374
  this.needStateSort = function () {
15276
15375
  stateSortDirty = true;
15277
15376
  };
@@ -15288,11 +15387,7 @@ const Renderer = function (scene, options) {
15288
15387
 
15289
15388
  this.webglContextRestored = function (gl) {
15290
15389
 
15291
- pickBuffer.webglContextRestored(gl);
15292
- snapshotBuffer.webglContextRestored(gl);
15293
- saoDepthRenderBuffer.webglContextRestored(gl);
15294
- occlusionRenderBuffer1.webglContextRestored(gl);
15295
- occlusionRenderBuffer2.webglContextRestored(gl);
15390
+ // renderBufferManager.webglContextRestored(gl);
15296
15391
 
15297
15392
  saoOcclusionRenderer.init();
15298
15393
  saoDepthLimitedBlurRenderer.init();
@@ -15477,7 +15572,7 @@ const Renderer = function (scene, options) {
15477
15572
 
15478
15573
  const sao = scene.sao;
15479
15574
 
15480
- if (sao.possible) {
15575
+ if (saoEnabled && sao.possible) {
15481
15576
  drawSAOBuffers(params);
15482
15577
  }
15483
15578
 
@@ -15492,6 +15587,10 @@ const Renderer = function (scene, options) {
15492
15587
 
15493
15588
  // Render depth buffer
15494
15589
 
15590
+ const saoDepthRenderBuffer = renderBufferManager.getRenderBuffer("saoDepth", {
15591
+ depthTexture: WEBGL_INFO$1.SUPPORTED_EXTENSIONS["WEBGL_depth_texture"]
15592
+ });
15593
+
15495
15594
  saoDepthRenderBuffer.bind();
15496
15595
  saoDepthRenderBuffer.clear();
15497
15596
  drawDepth(params);
@@ -15499,6 +15598,8 @@ const Renderer = function (scene, options) {
15499
15598
 
15500
15599
  // Render occlusion buffer
15501
15600
 
15601
+ const occlusionRenderBuffer1 = renderBufferManager.getRenderBuffer("saoOcclusion");
15602
+
15502
15603
  occlusionRenderBuffer1.bind();
15503
15604
  occlusionRenderBuffer1.clear();
15504
15605
  saoOcclusionRenderer.render(saoDepthRenderBuffer);
@@ -15508,6 +15609,8 @@ const Renderer = function (scene, options) {
15508
15609
 
15509
15610
  // Horizontally blur occlusion buffer 1 into occlusion buffer 2
15510
15611
 
15612
+ const occlusionRenderBuffer2 = renderBufferManager.getRenderBuffer("saoOcclusion2");
15613
+
15511
15614
  occlusionRenderBuffer2.bind();
15512
15615
  occlusionRenderBuffer2.clear();
15513
15616
  saoDepthLimitedBlurRenderer.render(saoDepthRenderBuffer, occlusionRenderBuffer1, 0);
@@ -15665,7 +15768,7 @@ const Renderer = function (scene, options) {
15665
15768
  frameCtx.reset();
15666
15769
  frameCtx.pass = params.pass;
15667
15770
  frameCtx.withSAO = false;
15668
- frameCtx.pbrEnabled = !!scene.pbrEnabled;
15771
+ frameCtx.pbrEnabled = pbrEnabled && !!scene.pbrEnabled;
15669
15772
 
15670
15773
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
15671
15774
 
@@ -15685,7 +15788,14 @@ const Renderer = function (scene, options) {
15685
15788
  frameCtx.lineWidth = 1;
15686
15789
 
15687
15790
  const saoPossible = scene.sao.possible;
15688
- frameCtx.occlusionTexture = saoPossible ? occlusionRenderBuffer1.getTexture() : null;
15791
+
15792
+ if (saoEnabled && saoPossible) {
15793
+ const occlusionRenderBuffer1 = renderBufferManager.getRenderBuffer("saoOcclusion");
15794
+ frameCtx.occlusionTexture = occlusionRenderBuffer1 ? occlusionRenderBuffer1.getTexture() : null;
15795
+ } else {
15796
+ frameCtx.occlusionTexture = null;
15797
+
15798
+ }
15689
15799
 
15690
15800
  let i;
15691
15801
  let len;
@@ -15738,15 +15848,17 @@ const Renderer = function (scene, options) {
15738
15848
  const renderFlags = drawable.renderFlags;
15739
15849
 
15740
15850
  if (renderFlags.colorOpaque) {
15741
- if (saoPossible && drawable.saoEnabled) {
15851
+ if (saoEnabled && saoPossible && drawable.saoEnabled) {
15742
15852
  normalDrawSAOBin[normalDrawSAOBinLen++] = drawable;
15743
15853
  } else {
15744
15854
  drawable.drawColorOpaque(frameCtx);
15745
15855
  }
15746
15856
  }
15747
15857
 
15748
- if (renderFlags.colorTransparent) {
15749
- normalFillTransparentBin[normalFillTransparentBinLen++] = drawable;
15858
+ if (transparentEnabled) {
15859
+ if (renderFlags.colorTransparent) {
15860
+ normalFillTransparentBin[normalFillTransparentBinLen++] = drawable;
15861
+ }
15750
15862
  }
15751
15863
 
15752
15864
  if (renderFlags.xrayedSilhouetteTransparent) {
@@ -15773,36 +15885,38 @@ const Renderer = function (scene, options) {
15773
15885
  selectedFillOpaqueBin[selectedFillOpaqueBinLen++] = drawable;
15774
15886
  }
15775
15887
 
15776
- if (renderFlags.edgesOpaque) {
15777
- normalEdgesOpaqueBin[normalEdgesOpaqueBinLen++] = drawable;
15778
- }
15888
+ if (edgesEnabled) {
15889
+ if (renderFlags.edgesOpaque) {
15890
+ normalEdgesOpaqueBin[normalEdgesOpaqueBinLen++] = drawable;
15891
+ }
15779
15892
 
15780
- if (renderFlags.edgesTransparent) {
15781
- normalEdgesTransparentBin[normalEdgesTransparentBinLen++] = drawable;
15782
- }
15893
+ if (renderFlags.edgesTransparent) {
15894
+ normalEdgesTransparentBin[normalEdgesTransparentBinLen++] = drawable;
15895
+ }
15783
15896
 
15784
- if (renderFlags.selectedEdgesTransparent) {
15785
- selectedEdgesTransparentBin[selectedEdgesTransparentBinLen++] = drawable;
15786
- }
15897
+ if (renderFlags.selectedEdgesTransparent) {
15898
+ selectedEdgesTransparentBin[selectedEdgesTransparentBinLen++] = drawable;
15899
+ }
15787
15900
 
15788
- if (renderFlags.selectedEdgesOpaque) {
15789
- selectedEdgesOpaqueBin[selectedEdgesOpaqueBinLen++] = drawable;
15790
- }
15901
+ if (renderFlags.selectedEdgesOpaque) {
15902
+ selectedEdgesOpaqueBin[selectedEdgesOpaqueBinLen++] = drawable;
15903
+ }
15791
15904
 
15792
- if (renderFlags.xrayedEdgesTransparent) {
15793
- xrayEdgesTransparentBin[xrayEdgesTransparentBinLen++] = drawable;
15794
- }
15905
+ if (renderFlags.xrayedEdgesTransparent) {
15906
+ xrayEdgesTransparentBin[xrayEdgesTransparentBinLen++] = drawable;
15907
+ }
15795
15908
 
15796
- if (renderFlags.xrayedEdgesOpaque) {
15797
- xrayEdgesOpaqueBin[xrayEdgesOpaqueBinLen++] = drawable;
15798
- }
15909
+ if (renderFlags.xrayedEdgesOpaque) {
15910
+ xrayEdgesOpaqueBin[xrayEdgesOpaqueBinLen++] = drawable;
15911
+ }
15799
15912
 
15800
- if (renderFlags.highlightedEdgesTransparent) {
15801
- highlightedEdgesTransparentBin[highlightedEdgesTransparentBinLen++] = drawable;
15802
- }
15913
+ if (renderFlags.highlightedEdgesTransparent) {
15914
+ highlightedEdgesTransparentBin[highlightedEdgesTransparentBinLen++] = drawable;
15915
+ }
15803
15916
 
15804
- if (renderFlags.highlightedEdgesOpaque) {
15805
- highlightedEdgesOpaqueBin[highlightedEdgesOpaqueBinLen++] = drawable;
15917
+ if (renderFlags.highlightedEdgesOpaque) {
15918
+ highlightedEdgesOpaqueBin[highlightedEdgesOpaqueBinLen++] = drawable;
15919
+ }
15806
15920
  }
15807
15921
  }
15808
15922
  }
@@ -16083,9 +16197,11 @@ const Renderer = function (scene, options) {
16083
16197
  canvasPos[1] = canvas.clientHeight * 0.5;
16084
16198
  }
16085
16199
 
16200
+ const pickBuffer = renderBufferManager.getRenderBuffer("pick");
16201
+
16086
16202
  pickBuffer.bind();
16087
16203
 
16088
- const pickable = gpuPickPickable(canvasPos, pickViewMatrix, pickProjMatrix, params);
16204
+ const pickable = gpuPickPickable(pickBuffer, canvasPos, pickViewMatrix, pickProjMatrix, params);
16089
16205
 
16090
16206
  if (!pickable) {
16091
16207
  pickBuffer.unbind();
@@ -16118,7 +16234,7 @@ const Renderer = function (scene, options) {
16118
16234
  }
16119
16235
 
16120
16236
  // if (params.pickSurfaceNormal !== false) {
16121
- // gpuPickWorldNormal(pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult);
16237
+ // gpuPickWorldNormal(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult);
16122
16238
  // }
16123
16239
 
16124
16240
  pickResult.pickSurfacePrecision = true;
@@ -16130,7 +16246,7 @@ const Renderer = function (scene, options) {
16130
16246
 
16131
16247
  if (pickable.canPickTriangle && pickable.canPickTriangle()) {
16132
16248
 
16133
- gpuPickTriangle(pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult);
16249
+ gpuPickTriangle(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult);
16134
16250
 
16135
16251
  pickable.pickTriangleSurface(pickViewMatrix, pickProjMatrix, pickResult);
16136
16252
 
@@ -16143,10 +16259,10 @@ const Renderer = function (scene, options) {
16143
16259
  nearAndFar[0] = scene.camera.project.near;
16144
16260
  nearAndFar[1] = scene.camera.project.far;
16145
16261
 
16146
- gpuPickWorldPos(pickable, canvasPos, pickViewMatrix, pickProjMatrix, nearAndFar, pickResult);
16262
+ gpuPickWorldPos(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, nearAndFar, pickResult);
16147
16263
 
16148
16264
  if (params.pickSurfaceNormal !== false) {
16149
- gpuPickWorldNormal(pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult);
16265
+ gpuPickWorldNormal(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult);
16150
16266
  }
16151
16267
 
16152
16268
  pickResult.pickSurfacePrecision = false;
@@ -16163,7 +16279,7 @@ const Renderer = function (scene, options) {
16163
16279
  };
16164
16280
  })();
16165
16281
 
16166
- function gpuPickPickable(canvasPos, pickViewMatrix, pickProjMatrix, params) {
16282
+ function gpuPickPickable(pickBuffer, canvasPos, pickViewMatrix, pickProjMatrix, params) {
16167
16283
 
16168
16284
  frameCtx.reset();
16169
16285
  frameCtx.backfaces = true;
@@ -16207,8 +16323,8 @@ const Renderer = function (scene, options) {
16207
16323
  }
16208
16324
  }
16209
16325
  }
16210
-
16211
- const pix = pickBuffer.read(Math.round(canvasPos[0]), Math.round(canvasPos[1]));
16326
+ const resolutionScale = scene.canvas.resolutionScale;
16327
+ const pix = pickBuffer.read(Math.round(canvasPos[0] * resolutionScale), Math.round(canvasPos[1] * resolutionScale));
16212
16328
  let pickID = pix[0] + (pix[1] * 256) + (pix[2] * 256 * 256) + (pix[3] * 256 * 256 * 256);
16213
16329
 
16214
16330
  if (pickID < 0) {
@@ -16220,7 +16336,7 @@ const Renderer = function (scene, options) {
16220
16336
  return pickable;
16221
16337
  }
16222
16338
 
16223
- function gpuPickTriangle(pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult) {
16339
+ function gpuPickTriangle(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult) {
16224
16340
 
16225
16341
  if (!pickable.drawPickTriangles) {
16226
16342
  return;
@@ -16243,7 +16359,8 @@ const Renderer = function (scene, options) {
16243
16359
 
16244
16360
  pickable.drawPickTriangles(frameCtx);
16245
16361
 
16246
- const pix = pickBuffer.read(canvasPos[0], canvasPos[1]);
16362
+ const resolutionScale = scene.canvas.resolutionScale;
16363
+ const pix = pickBuffer.read(Math.round(canvasPos[0] * resolutionScale), Math.round(canvasPos[1] * resolutionScale));
16247
16364
 
16248
16365
  let primIndex = pix[0] + (pix[1] * 256) + (pix[2] * 256 * 256) + (pix[3] * 256 * 256 * 256);
16249
16366
 
@@ -16263,7 +16380,7 @@ const Renderer = function (scene, options) {
16263
16380
  const tempMat4b = math.mat4();
16264
16381
  const tempMat4c = math.mat4();
16265
16382
 
16266
- return function (pickable, canvasPos, pickViewMatrix, pickProjMatrix, nearAndFar, pickResult) {
16383
+ return function (pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, nearAndFar, pickResult) {
16267
16384
 
16268
16385
  frameCtx.reset();
16269
16386
  frameCtx.backfaces = true;
@@ -16283,13 +16400,14 @@ const Renderer = function (scene, options) {
16283
16400
 
16284
16401
  pickable.drawPickDepths(frameCtx); // Draw color-encoded fragment screen-space depths
16285
16402
 
16286
- const pix = pickBuffer.read(Math.round(canvasPos[0]), Math.round(canvasPos[1]));
16403
+ const resolutionScale = scene.canvas.resolutionScale;
16404
+ const pix = pickBuffer.read(Math.round(canvasPos[0] * resolutionScale), Math.round(canvasPos[1] * resolutionScale));
16287
16405
 
16288
16406
  const screenZ = unpackDepth(pix); // Get screen-space Z at the given canvas coords
16289
16407
 
16290
16408
  // Calculate clip space coordinates, which will be in range of x=[-1..1] and y=[-1..1], with y=(+1) at top
16291
- const x = (canvasPos[0] - canvas.width / 2) / (canvas.width / 2);
16292
- const y = -(canvasPos[1] - canvas.height / 2) / (canvas.height / 2);
16409
+ const x = (canvasPos[0] - canvas.clientWidth / 2) / (canvas.clientWidth / 2);
16410
+ const y = -(canvasPos[1] - canvas.clientHeight / 2) / (canvas.clientHeight / 2);
16293
16411
 
16294
16412
  const origin = pickable.origin;
16295
16413
  let pvMat;
@@ -16337,7 +16455,7 @@ const Renderer = function (scene, options) {
16337
16455
  return math.dotVec4(vec, bitShift);
16338
16456
  }
16339
16457
 
16340
- function gpuPickWorldNormal(pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult) {
16458
+ function gpuPickWorldNormal(pickBuffer, pickable, canvasPos, pickViewMatrix, pickProjMatrix, pickResult) {
16341
16459
 
16342
16460
  frameCtx.reset();
16343
16461
  frameCtx.backfaces = true;
@@ -16355,7 +16473,8 @@ const Renderer = function (scene, options) {
16355
16473
 
16356
16474
  pickable.drawPickNormals(frameCtx); // Draw color-encoded fragment World-space normals
16357
16475
 
16358
- const pix = pickBuffer.read(Math.round(canvasPos[0]), Math.round(canvasPos[1]));
16476
+ const resolutionScale = scene.canvas.resolutionScale;
16477
+ const pix = pickBuffer.read(Math.round(canvasPos[0] * resolutionScale), Math.round(canvasPos[1] * resolutionScale));
16359
16478
 
16360
16479
  const worldNormal = [(pix[0] / 256.0) - 0.5, (pix[1] / 256.0) - 0.5, (pix[2] / 256.0) - 0.5];
16361
16480
 
@@ -16369,7 +16488,7 @@ const Renderer = function (scene, options) {
16369
16488
  * @param marker
16370
16489
  */
16371
16490
  this.addMarker = function (marker) {
16372
- this._occlusionTester = this._occlusionTester || new OcclusionTester(scene);
16491
+ this._occlusionTester = this._occlusionTester || new OcclusionTester(scene, renderBufferManager);
16373
16492
  this._occlusionTester.addMarker(marker);
16374
16493
  scene.occlusionTestCountdown = 0;
16375
16494
  };
@@ -16443,6 +16562,7 @@ const Renderer = function (scene, options) {
16443
16562
  * @private
16444
16563
  */
16445
16564
  this.readPixels = function (pixels, colors, len, opaqueOnly) {
16565
+ const snapshotBuffer = renderBufferManager.getRenderBuffer("snapshot");
16446
16566
  snapshotBuffer.bind();
16447
16567
  snapshotBuffer.clear();
16448
16568
  this.render({force: true, opaqueOnly: opaqueOnly});
@@ -16471,6 +16591,7 @@ const Renderer = function (scene, options) {
16471
16591
  * Exit snapshot mode using endSnapshot().
16472
16592
  */
16473
16593
  this.beginSnapshot = function () {
16594
+ const snapshotBuffer = renderBufferManager.getRenderBuffer("snapshot");
16474
16595
  snapshotBuffer.bind();
16475
16596
  snapshotBuffer.clear();
16476
16597
  snapshotBound = true;
@@ -16483,6 +16604,7 @@ const Renderer = function (scene, options) {
16483
16604
  if (!snapshotBound) {
16484
16605
  return;
16485
16606
  }
16607
+ const snapshotBuffer = renderBufferManager.getRenderBuffer("snapshot");
16486
16608
  snapshotBuffer.clear();
16487
16609
  this.render({force: true, opaqueOnly: false});
16488
16610
  imageDirty = true;
@@ -16495,6 +16617,7 @@ const Renderer = function (scene, options) {
16495
16617
  * @returns {String} The image data URI.
16496
16618
  */
16497
16619
  this.readSnapshot = function (params) {
16620
+ const snapshotBuffer = renderBufferManager.getRenderBuffer("snapshot");
16498
16621
  const imageDataURI = snapshotBuffer.readImage(params);
16499
16622
  return imageDataURI;
16500
16623
  };
@@ -16508,6 +16631,7 @@ const Renderer = function (scene, options) {
16508
16631
  if (!snapshotBound) {
16509
16632
  return;
16510
16633
  }
16634
+ const snapshotBuffer = renderBufferManager.getRenderBuffer("snapshot");
16511
16635
  snapshotBuffer.unbind();
16512
16636
  snapshotBound = false;
16513
16637
  };
@@ -16521,11 +16645,7 @@ const Renderer = function (scene, options) {
16521
16645
  drawableTypeInfo = {};
16522
16646
  drawables = {};
16523
16647
 
16524
- pickBuffer.destroy();
16525
- snapshotBuffer.destroy();
16526
- saoDepthRenderBuffer.destroy();
16527
- occlusionRenderBuffer1.destroy();
16528
- occlusionRenderBuffer2.destroy();
16648
+ renderBufferManager.destroy();
16529
16649
 
16530
16650
  saoOcclusionRenderer.destroy();
16531
16651
  saoDepthLimitedBlurRenderer.destroy();
@@ -39283,47 +39403,75 @@ class DistanceMeasurementsPlugin extends Plugin {
39283
39403
  }
39284
39404
 
39285
39405
  /**
39286
- * {@link Viewer} plugin that improves interactivity by disabling expensive rendering effects while the {@link Camera} is moving.
39406
+ * {@link Viewer} plugin that improves interactivity by temporarily switching to fast and simple rendering while the
39407
+ * {@link Camera} is moving or the {@link Canvas} is resizing.
39408
+ *
39409
+ * FastNavPlugin works by disabling specified rendering features, and optionally down-scaling the canvas, whenever we
39410
+ * move the Camera or resize the Canvas. Then, once the Camera or Canvas has been at rest after a certain time, FastNavPlugin
39411
+ * restores those rendering features and original canvas scale again.
39412
+ *
39413
+ * The effect we experience is a low-quality view while moving, then a high-quality view
39414
+ * after we stop, following an optional delay.
39415
+ *
39416
+ * Down-scaling the canvas resolution gives particularly good results. For example, scaling by ````0.5````
39417
+ * means that we're rendering a quarter of the pixels while moving, which makes the Viewer noticeably smoother
39418
+ * with big models.
39287
39419
  *
39288
39420
  * # Usage
39289
39421
  *
39290
39422
  * In the example below, we'll create a {@link Viewer}, add a {@link FastNavPlugin}, then use an {@link XKTLoaderPlugin} to load a model.
39291
39423
  *
39292
- * This viewer will only render the model with enhanced edges, physically-based rendering (PBR) and scalable
39293
- * ambient obscurance (SAO) when the camera is not moving.
39424
+ * Whenever our Camera moves, the FastNavPlugin will:
39425
+ *
39426
+ * * disable edges,
39427
+ * * disable ambient shadows,
39428
+ * * disable physically-based materials (switching to non-PBR),
39429
+ * * hide transparent objects, and
39430
+ * * down-scale the canvas by 0.5, causing 75% less pixels to render.
39294
39431
  *
39295
- * Note how we enable SAO and PBR on the ````Scene```` and the model.
39432
+ * We'll also configure a 0.5 second delay before we transition back to high-quality each time we stop moving, so that we're
39433
+ * not continually flipping between low and high quality as we interact.
39296
39434
  *
39297
39435
  * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/#performance_FastNavPlugin)]
39298
39436
  *
39299
39437
  * ````javascript
39300
39438
  * import {Viewer, XKTLoaderPlugin, FastNavPlugin} from "xeokit-sdk.es.js";
39301
39439
  *
39440
+ * // Create a Viewer with PBR and SAO enabled
39441
+ *
39302
39442
  * const viewer = new Viewer({
39303
39443
  * canvasId: "myCanvas",
39304
39444
  * transparent: true,
39305
- * pbrEnabled: true,
39306
- * saoEnabled: true
39445
+ * pbr: true, // Enable physically-based rendering for Viewer
39446
+ * sao: true // Enable ambient shadows for Viewer
39307
39447
  * });
39308
39448
  *
39309
39449
  * viewer.scene.camera.eye = [-66.26, 105.84, -281.92];
39310
39450
  * viewer.scene.camera.look = [42.45, 49.62, -43.59];
39311
39451
  * viewer.scene.camera.up = [0.05, 0.95, 0.15];
39312
39452
  *
39453
+ * // Install a FastNavPlugin
39454
+ *
39313
39455
  * new FastNavPlugin(viewer, {
39314
- * pbrEnabled: true,
39315
- * saoEnabled: true,
39316
- * edgesEnabled: true
39456
+ * dynamicEdges: false, // Don't show edges while moving (default is false)
39457
+ * dynamicSAO: false, // Don't show ambient shadows while moving (default is false)
39458
+ * dynamicPBR: false, // Non-physically-based rendering while moving (default is false)
39459
+ * dynamicTransparent: false, // Hide transparent objects while moving (default is false)
39460
+ * dynamicCanvasResolution: true, // Reduce canvas resolution while moving (default is false)
39461
+ * dynamicCanvasResolutionScale: 0.5, // Factor by which we reduce canvas resolution when moving (default is 0.6)
39462
+ * delayBeforeStatic: true, // When we stop, delay before returning to quality static render (default is true)
39463
+ * delayBeforeStaticDuration: 0.5 // The delay duration, in seconds (default is 0.5)
39317
39464
  * });
39318
39465
  *
39466
+ * // Load a BIM model from XKT
39467
+ *
39319
39468
  * const xktLoader = new XKTLoaderPlugin(viewer);
39320
39469
  *
39321
39470
  * const model = xktLoader.load({
39322
39471
  * id: "myModel",
39323
39472
  * src: "./models/xkt/HolterTower.xkt",
39324
- * edges: true,
39325
- * saoEnabled: true,
39326
- * pbrEnabled: true
39473
+ * sao: true, // Enable ambient shadows for this model
39474
+ * pbr: true // Enable physically-based rendering for this model
39327
39475
  * });
39328
39476
  * ````
39329
39477
  *
@@ -39336,63 +39484,66 @@ class FastNavPlugin extends Plugin {
39336
39484
  * @param {Viewer} viewer The Viewer.
39337
39485
  * @param {Object} cfg FastNavPlugin configuration.
39338
39486
  * @param {String} [cfg.id="FastNav"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}.
39339
- * @param {Boolean} [cfg.pbrEnabled] Whether to enable physically-based rendering (PBR) when the camera stops moving. When not specified, PBR will be enabled if its currently enabled for the Viewer (see {@link Viewer#pbrEnabled}).
39340
- * @param {Boolean} [cfg.saoEnabled] Whether to enable scalable ambient occlusion (SAO) when the camera stops moving. When not specified, SAO will be enabled if its currently enabled for the Viewer (see {@link Scene#pbrEnabled}).
39341
- * @param {Boolean} [cfg.edgesEnabled] Whether to show enhanced edges when the camera stops moving. When not specified, edges will be enabled if they're currently enabled for the Viewer (see {@link EdgeMaterial#edges}).
39487
+ * @param {Boolean} [cfg.dynamicPBR=false] Whether to enable physically-based rendering (PBR) while the camera is moving.
39488
+ * @param {Boolean} [cfg.dynamicSAO=false] Whether to enable scalable ambient occlusion (SAO) while the camera is moving.
39489
+ * @param {Boolean} [cfg.dynamicEdges=false] Whether to enable enhanced edges while the camera is moving.
39490
+ * @param {Boolean} [cfg.dynamicTransparent=true] Whether to show transparent objects when the camera is moving.
39491
+ * @param {Number} [cfg.dynamicCanvasResolution=false] Whether to down-scale the canvas resolution while the camera is moving.
39492
+ * @param {Number} [cfg.dynamicCanvasResolutionScale=0.6] The factor by which we downscale the canvas resolution while the camera is moving.
39493
+ * @param {Boolean} [cfg.delayBeforeStatic=true] Once the camera stops moving, whether to have a delay before transitioning back to normal rendering.
39494
+ * @param {Number} [cfg.delayBeforeStaticDuration=0.5] Delay in seconds before transitioning back to normal rendering when camera stops moving. Only works when ````delayBeforeStatic```` is ````true````.
39342
39495
  */
39343
39496
  constructor(viewer, cfg = {}) {
39344
39497
 
39345
39498
  super("FastNav", viewer);
39346
39499
 
39347
- this._pbrEnabled = (cfg.pbrEnabled !== undefined && cfg.pbrEnabled !== null) ? cfg.pbrEnabled : viewer.scene.pbrEnabled;
39348
- this._saoEnabled = (cfg.saoEnabled !== undefined && cfg.saoEnabled !== null) ? cfg.saoEnabled : viewer.scene.sao.enabled;
39349
- this._edgesEnabled = (cfg.edgesEnabled !== undefined && cfg.edgesEnabled !== null) ? cfg.edgesEnabled : viewer.scene.edgeMaterial.edges;
39350
-
39351
- this._pInterval = null;
39352
- this._fadeMillisecs = 500;
39500
+ this._dynamicPBR = !!cfg.dynamicPBR;
39501
+ this._dynamicSAO = !!cfg.dynamicSAO;
39502
+ this._dynamicEdges = !!cfg.dynamicEdges;
39503
+ this._dynamicTransparent = (cfg.dynamicTransparent !== false);
39504
+ this._dynamicCanvasResolution = !!cfg.dynamicCanvasResolution;
39505
+ this._dynamicCanvasResolutionScale = cfg.dynamicCanvasResolutionScale || 0.6;
39506
+ this._delayBeforeStatic = (cfg.delayBeforeStatic !== false);
39507
+ this._delayBeforeStaticDuration = cfg.delayBeforeStaticDuration || 0.5;
39353
39508
 
39354
- let timeoutDuration = 600; // Milliseconds
39355
- let timer = timeoutDuration;
39509
+ let timer = this._delayBeforeStaticDuration * 1000;
39356
39510
  let fastMode = false;
39357
39511
 
39358
- this._onCanvasBoundary = viewer.scene.canvas.on("boundary", () => {
39359
- timer = timeoutDuration;
39512
+ const switchToLowQuality = () => {
39513
+ timer = (this._delayBeforeStaticDuration * 1000);
39360
39514
  if (!fastMode) {
39361
- this._cancelFade();
39362
- viewer.scene.pbrEnabled = false;
39363
- viewer.scene.sao.enabled = false;
39364
- viewer.scene.edgeMaterial.edges = false;
39515
+ viewer.scene._renderer.setPBREnabled(this._dynamicPBR);
39516
+ viewer.scene._renderer.setSAOEnabled(this._dynamicSAO);
39517
+ viewer.scene._renderer.setTransparentEnabled(this._dynamicTransparent);
39518
+ viewer.scene._renderer.setEdgesEnabled(this._dynamicEdges);
39519
+ if (this._dynamicCanvasResolution) {
39520
+ viewer.scene.canvas.resolutionScale = this._dynamicCanvasResolutionScale;
39521
+ } else {
39522
+ viewer.scene.canvas.resolutionScale = 1;
39523
+ }
39365
39524
  fastMode = true;
39366
39525
  }
39367
- });
39526
+ };
39368
39527
 
39369
- this._onCameraMatrix = viewer.scene.camera.on("matrix", () => {
39370
- timer = timeoutDuration;
39371
- if (!fastMode) {
39372
- this._cancelFade();
39373
- viewer.scene.pbrEnabled = false;
39374
- viewer.scene.sao.enabled = false;
39375
- viewer.scene.edgeMaterial.edges = false;
39376
- fastMode = true;
39377
- }
39378
- });
39528
+ const switchToHighQuality = () => {
39529
+ viewer.scene.canvas.resolutionScale = 1;
39530
+ viewer.scene._renderer.setEdgesEnabled(true);
39531
+ viewer.scene._renderer.setPBREnabled(true);
39532
+ viewer.scene._renderer.setSAOEnabled(true);
39533
+ viewer.scene._renderer.setTransparentEnabled(true);
39534
+ fastMode = false;
39535
+ };
39536
+
39537
+ this._onCanvasBoundary = viewer.scene.canvas.on("boundary", switchToLowQuality);
39538
+ this._onCameraMatrix = viewer.scene.camera.on("matrix", switchToLowQuality);
39379
39539
 
39380
- this._onSceneTick = viewer.scene.on("tick", (tickEvent) => { // Milliseconds
39540
+ this._onSceneTick = viewer.scene.on("tick", (tickEvent) => {
39381
39541
  if (!fastMode) {
39382
39542
  return;
39383
39543
  }
39384
39544
  timer -= tickEvent.deltaTime;
39385
- if (timer <= 0) {
39386
- if (fastMode) {
39387
- this._startFade();
39388
- this._pInterval2 = setTimeout(() => { // Needed by Firefox - https://github.com/xeokit/xeokit-sdk/issues/624
39389
- viewer.scene.pbrEnabled = this._pbrEnabled;
39390
- viewer.scene.sao.enabled = this._saoEnabled;
39391
- viewer.scene.edgeMaterial.edges = this._edgesEnabled;
39392
- }, 100);
39393
-
39394
- fastMode = false;
39395
- }
39545
+ if ((!this._delayBeforeStatic) || timer <= 0) {
39546
+ switchToHighQuality();
39396
39547
  }
39397
39548
  });
39398
39549
 
@@ -39410,184 +39561,216 @@ class FastNavPlugin extends Plugin {
39410
39561
  if (!down) {
39411
39562
  return;
39412
39563
  }
39413
- timer = timeoutDuration;
39414
- if (!fastMode) {
39415
- this._cancelFade();
39416
- viewer.scene.pbrEnabled = false;
39417
- viewer.scene.sao.enabled = false;
39418
- viewer.scene.edgeMaterial.edges = false;
39419
- fastMode = true;
39420
- }
39564
+ switchToLowQuality();
39421
39565
  });
39422
39566
  }
39423
39567
 
39424
- _startFade() {
39568
+ /**
39569
+ * Gets whether to enable physically-based rendering (PBR) while the camera is moving.
39570
+ *
39571
+ * Default is ````false````.
39572
+ *
39573
+ * @return {Boolean} Whether PBR will be enabled while the camera is moving.
39574
+ */
39575
+ get dynamicPBR() {
39576
+ return this._dynamicPBR;
39577
+ }
39425
39578
 
39426
- if (!this._img) {
39427
- this._initFade();
39428
- }
39579
+ /**
39580
+ * Sets whether to enable physically-based rendering (PBR) while the camera is moving.
39581
+ *
39582
+ * Default is ````false````.
39583
+ *
39584
+ * @param {Boolean} dynamicPBR Whether PBR will be enabled while the camera is moving.
39585
+ */
39586
+ set dynamicPBR(dynamicPBR) {
39587
+ this._dynamicPBR = dynamicPBR;
39588
+ }
39429
39589
 
39430
- const interval = 50;
39431
- const inc = 1 / (this._fadeMillisecs / interval);
39590
+ /**
39591
+ * Gets whether to enable Scalable Ambient Obscurrance (SAO) while the camera is moving.
39592
+ *
39593
+ * Default is ````false````.
39594
+ *
39595
+ * @return {Boolean} Whether SAO will be enabled.
39596
+ */
39597
+ get dynamicSAO() {
39598
+ return this._dynamicSAO;
39599
+ }
39432
39600
 
39433
- if (this._pInterval) {
39434
- clearInterval(this._pInterval);
39435
- this._pInterval = null;
39436
- }
39601
+ /**
39602
+ * Sets whether to enable Scalable Ambient Obscurrance (SAO) while the camera is moving.
39603
+ *
39604
+ * Default is ````false````.
39605
+ *
39606
+ * @param {Boolean} dynamicSAO Whether SAO will be enabled while the camera is moving.
39607
+ */
39608
+ set dynamicSAO(dynamicSAO) {
39609
+ this._dynamicSAO = dynamicSAO;
39610
+ }
39437
39611
 
39438
- const viewer = this.viewer;
39612
+ /**
39613
+ * Gets whether to enable enhanced edges while the camera is moving.
39614
+ *
39615
+ * Default is ````false````.
39616
+ *
39617
+ * @return {Boolean} Whether edges will be enabled while the camera is moving.
39618
+ */
39619
+ get dynamicEdges() {
39620
+ return this._dynamicEdges;
39621
+ }
39439
39622
 
39440
- const canvas = viewer.scene.canvas.canvas;
39441
- const canvasOffset = cumulativeOffset(canvas);
39442
- const zIndex = (parseInt(canvas.style["z-index"]) || 0) + 1;
39443
- this._img.style.position = "fixed";
39444
- this._img.style["margin"] = 0 + "px";
39445
- this._img.style["z-index"] = zIndex;
39446
- this._img.style["background"] = canvas.style.background;
39447
- this._img.style.left = canvasOffset.left + "px";
39448
- this._img.style.top = canvasOffset.top + "px";
39449
- this._img.style.width = canvas.width + "px";
39450
- this._img.style.height = canvas.height + "px";
39451
- this._img.width = canvas.width;
39452
- this._img.height = canvas.height;
39453
- this._img.src = ""; // Needed by Firefox - https://github.com/xeokit/xeokit-sdk/issues/624
39454
- this._img.src = viewer.getSnapshot({
39455
- format: "png",
39456
- includeGizmos: true
39457
- });
39458
- this._img.style.visibility = "visible";
39459
- this._img.style.opacity = 1;
39460
-
39461
- let opacity = 1;
39462
- this._pInterval = setInterval(() => {
39463
- opacity -= inc;
39464
- if (opacity > 0) {
39465
- this._img.style.opacity = opacity;
39466
- const canvasOffset = cumulativeOffset(canvas);
39467
- this._img.style.left = canvasOffset.left + "px";
39468
- this._img.style.top = canvasOffset.top + "px";
39469
- this._img.style.width = canvas.width + "px";
39470
- this._img.style.height = canvas.height + "px";
39471
- this._img.style.opacity = opacity;
39472
- this._img.width = canvas.width;
39473
- this._img.height = canvas.height;
39623
+ /**
39624
+ * Sets whether to enable enhanced edges while the camera is moving.
39625
+ *
39626
+ * Default is ````false````.
39627
+ *
39628
+ * @param {Boolean} dynamicEdges Whether edge enhancement will be enabled while the camera is moving.
39629
+ */
39630
+ set dynamicEdges(dynamicEdges) {
39631
+ this._dynamicEdges = dynamicEdges;
39632
+ }
39474
39633
 
39475
- } else {
39476
- this._img.style.opacity = 0;
39477
- this._img.style.visibility = "hidden";
39478
- clearInterval(this._pInterval);
39479
- this._pInterval = null;
39480
- }
39481
- }, interval);
39482
- }
39483
-
39484
- _initFade() {
39485
- this._img = document.createElement('img');
39486
- const canvas = this.viewer.scene.canvas.canvas;
39487
- const canvasOffset = cumulativeOffset(canvas);
39488
- (parseInt(canvas.style["z-index"]) || 0) + 1;
39489
- this._img.style.position = "absolute";
39490
- this._img.style.visibility = "hidden";
39491
- this._img.style["pointer-events"] = "none";
39492
- this._img.style["z-index"] = 5;
39493
- this._img.style.left = canvasOffset.left + "px";
39494
- this._img.style.top = canvasOffset.top + "px";
39495
- this._img.style.width = canvas.width + "px";
39496
- this._img.style.height = canvas.height + "px";
39497
- this._img.style.opacity = 1;
39498
- this._img.width = canvas.width;
39499
- this._img.height = canvas.height;
39500
- this._img.left = canvasOffset.left;
39501
- this._img.top = canvasOffset.top;
39502
- canvas.parentNode.insertBefore(this._img, canvas.nextSibling);
39503
- }
39504
-
39505
- _cancelFade() {
39506
- if (!this._img) {
39507
- return;
39508
- }
39509
- if (this._pInterval) {
39510
- clearInterval(this._pInterval);
39511
- this._pInterval = null;
39512
- }
39513
- if (this._pInterval2) {
39514
- clearInterval(this._pInterval2);
39515
- this._pInterval2 = null;
39516
- }
39517
- this._img.style.opacity = 0;
39518
- this._img.style.visibility = "hidden";
39634
+ /**
39635
+ * Gets whether to show transparent objects while the camera is moving.
39636
+ *
39637
+ * Default is ````true````.
39638
+ *
39639
+ * @return {Boolean} Whether to show transparent objects while the camera is moving.
39640
+ */
39641
+ get dynamicTransparent() {
39642
+ return this._dynamicTransparent
39519
39643
  }
39520
39644
 
39521
39645
  /**
39522
- * Sets whether to enable physically-based rendering (PBR) when the camera stops moving.
39646
+ * Sets whether to show transparent objects while the camera is moving.
39647
+ *
39648
+ * Default is ````true````.
39523
39649
  *
39524
- * @return {Boolean} Whether PBR will be enabled.
39650
+ * @param {Boolean} dynamicTransparent Whether to show transparent objects while the camera is moving.
39525
39651
  */
39526
- set pbrEnabled(pbrEnabled) {
39527
- this._pbrEnabled = pbrEnabled;
39652
+ set dynamicTransparent(dynamicTransparent) {
39653
+ this._dynamicTransparent = (dynamicTransparent !== false);
39528
39654
  }
39529
39655
 
39530
39656
  /**
39531
- * Gets whether to enable physically-based rendering (PBR) when the camera stops moving.
39657
+ * Gets whether to down-scale the canvas resolution while the camera is moving.
39658
+ *
39659
+ * Default is ````true````.
39660
+ *
39661
+ * The down-scaling factor is configured via {@link FastNavPlugin#dynamicCanvasResolutionScale}.
39532
39662
  *
39533
- * @return {Boolean} Whether PBR will be enabled.
39663
+ * @return {Boolean} Whether to down-scale the canvas resolution while the camera is moving.
39534
39664
  */
39535
- get pbrEnabled() {
39536
- return this._pbrEnabled
39665
+ get dynamicCanvasResolution() {
39666
+ return this._dynamicCanvasResolution;
39537
39667
  }
39538
39668
 
39539
39669
  /**
39540
- * Sets whether to enable scalable ambient occlusion (SAO) when the camera stops moving.
39670
+ * Sets whether to down-scale the canvas resolution while the camera is moving.
39541
39671
  *
39542
- * @return {Boolean} Whether SAO will be enabled.
39672
+ * Default is ````true````.
39673
+ *
39674
+ * The down-scaling factor is configured via {@link FastNavPlugin#dynamicCanvasResolutionScale}.
39675
+ *
39676
+ * @param {Boolean} dynamicCanvasResolution Whether to down-scale the canvas resolution while the camera is moving.
39543
39677
  */
39544
- set saoEnabled(saoEnabled) {
39545
- this._saoEnabled = saoEnabled;
39678
+ set dynamicCanvasResolution(dynamicCanvasResolution) {
39679
+ this._dynamicCanvasResolution = dynamicCanvasResolution;
39546
39680
  }
39547
39681
 
39548
39682
  /**
39549
- * Gets whether the FastNavPlugin enables SAO when switching to quality rendering.
39683
+ * Gets the factor by which we downscale the canvas resolution while the camera is moving.
39550
39684
  *
39551
- * @return {Boolean} Whether SAO will be enabled.
39685
+ * This allows us to render a low-resolution image to the canvas while we're moving the camera.
39686
+ *
39687
+ * Accepted range is ````[0.0 .. 1.0]````.
39688
+ *
39689
+ * Default is ````0.6````.
39690
+ *
39691
+ * @return {Number} Factor by which we downscale the canvas resolution while the camera is moving.
39552
39692
  */
39553
- get saoEnabled() {
39554
- return this._saoEnabled
39693
+ get dynamicCanvasResolutionScale() {
39694
+ return this._dynamicCanvasResolutionScale;
39695
+ }
39696
+
39697
+ /**
39698
+ * Sets the factor by which we downscale the canvas resolution while the camera is moving.
39699
+ *
39700
+ * This allows us to render a low-resolution image to the canvas while we're moving the camera.
39701
+ *
39702
+ * Accepted range is ````[0.0 .. 1.0]````.
39703
+ *
39704
+ * Default is ````0.6````.
39705
+ *
39706
+ * @param {Number} dynamicCanvasResolutionScale Factor by which we downscale the canvas resolution while the camera is moving.
39707
+ */
39708
+ set dynamicCanvasResolutionScale(dynamicCanvasResolutionScale) {
39709
+ this._dynamicCanvasResolutionScale = dynamicCanvasResolutionScale || 0.6;
39710
+ }
39711
+
39712
+ /**
39713
+ * Gets whether to have a delay before transitioning back to normal static rendering after the camera stops moving.
39714
+ *
39715
+ * The delay duration is configured via {@link FastNavPlugin#delayBeforeStaticDuration}.
39716
+ *
39717
+ * Default is ````true````.
39718
+ *
39719
+ * @return {Boolean} Whether to have a delay.
39720
+ */
39721
+ get delayBeforeStatic() {
39722
+ return this._delayBeforeStatic;
39723
+ }
39724
+
39725
+ /**
39726
+ * Sets whether to have a delay before transitioning back to normal static rendering after the camera stops moving.
39727
+ *
39728
+ * The delay duration is configured via {@link FastNavPlugin#delayBeforeStaticDuration}.
39729
+ *
39730
+ * Default is ````true````.
39731
+ *
39732
+ * @param {Boolean} delayBeforeStatic Whether to have a delay.
39733
+ */
39734
+ set delayBeforeStatic(delayBeforeStatic) {
39735
+ this._delayBeforeStatic = delayBeforeStatic;
39555
39736
  }
39556
39737
 
39557
39738
  /**
39558
- * Sets whether to show enhanced edges when the camera stops moving.
39739
+ * Gets the delay in seconds before transitioning back to normal static rendering when camera stops moving.
39740
+ *
39741
+ * The delay is enabled when {@link FastNavPlugin#delayBeforeStatic} is ````true````.
39742
+ *
39743
+ * Default is ````0.5```` seconds.
39559
39744
  *
39560
- * @return {Boolean} Whether edge enhancement will be enabled.
39745
+ * @return {Number} Timeout duration in seconds.
39561
39746
  */
39562
- set edgesEnabled(edgesEnabled) {
39563
- this._edgesEnabled = edgesEnabled;
39747
+ get delayBeforeStaticDuration() {
39748
+ return this._delayBeforeStaticDuration;
39564
39749
  }
39565
39750
 
39566
39751
  /**
39567
- * Gets whether to show enhanced edges when the camera stops moving.
39752
+ * Sets the delay in seconds before transitioning back to normal static rendering when camera stops moving.
39568
39753
  *
39569
- * @return {Boolean} Whether edge enhancement will be enabled.
39754
+ * The delay is enabled when {@link FastNavPlugin#delayBeforeStatic} is ````true````.
39755
+ *
39756
+ * Default is ````0.5```` seconds.
39757
+ *
39758
+ * @param {Number} delayBeforeStaticDuration Timeout duration in seconds.
39570
39759
  */
39571
- get edgesEnabled() {
39572
- return this._edgesEnabled
39760
+ set delayBeforeStaticDuration(delayBeforeStaticDuration) {
39761
+ this._delayBeforeStaticDuration = delayBeforeStaticDuration !== null && delayBeforeStaticDuration !== undefined ? delayBeforeStaticDuration : 0.5;
39573
39762
  }
39574
39763
 
39575
39764
  /**
39576
39765
  * @private
39577
39766
  */
39578
39767
  send(name, value) {
39579
- switch (name) {
39580
- case "clear":
39581
- this._cancelFade();
39582
- break;
39583
- }
39584
39768
  }
39585
39769
 
39586
39770
  /**
39587
39771
  * Destroys this plugin.
39588
39772
  */
39589
39773
  destroy() {
39590
- this._cancelFade();
39591
39774
  this.viewer.scene.camera.off(this._onCameraMatrix);
39592
39775
  this.viewer.scene.canvas.off(this._onCanvasBoundary);
39593
39776
  this.viewer.scene.input.off(this._onSceneMouseDown);
@@ -39595,27 +39778,9 @@ class FastNavPlugin extends Plugin {
39595
39778
  this.viewer.scene.input.off(this._onSceneMouseMove);
39596
39779
  this.viewer.scene.off(this._onSceneTick);
39597
39780
  super.destroy();
39598
- if (this._img) {
39599
- this._img.parentNode.removeChild(this._img);
39600
- this._img = null;
39601
- }
39602
39781
  }
39603
39782
  }
39604
39783
 
39605
- function cumulativeOffset(element) {
39606
- let top = 0, left = 0;
39607
- do {
39608
- top += element.offsetTop || 0;
39609
- left += element.offsetLeft || 0;
39610
- element = element.offsetParent;
39611
- } while (element);
39612
-
39613
- return {
39614
- top: top,
39615
- left: left
39616
- };
39617
- }
39618
-
39619
39784
  /**
39620
39785
  * Default data access strategy for {@link GLTFLoaderPlugin}.
39621
39786
  *