WP VR – 360 Panorama and virtual tour creator for WordPress - Version 3.4.4

Version Description

  • Rotation fix on default scene face
  • Rotation fix on hotspot to target scene face
  • Plugin library update

=

Download this release

Release Info

Developer rextheme
Plugin Icon 128x128 WP VR – 360 Panorama and virtual tour creator for WordPress
Version 3.4.4
Comparing to
See all releases

Code changes from version 3.4.3 to 3.4.4

README.txt CHANGED
@@ -4,7 +4,7 @@ Donate link: https://rextheme.com/wp-vr-360-panorama-and-virtual-tour-creator-fo
4
  Tags: virtual tour, real estate tour, panorama, panorama viewer, virtual tour, 360 panorama, interactive tour
5
  Requires at least: 4.0
6
  Tested up to: 5.2
7
- Stable tag: 3.4.3
8
  Requires PHP: 5.6
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -275,5 +275,10 @@ Simply add "/plugins/wpvr" to exclusion field (or use the location where you sto
275
  * Autorotation error fix
276
  * Ziparchive class error fix
277
 
 
 
 
 
 
278
  == Upgrade Notice ==
279
  Please do update the WP VR to the latest version. Each update makes it sure your plugin is supporting all tour features.  
4
  Tags: virtual tour, real estate tour, panorama, panorama viewer, virtual tour, 360 panorama, interactive tour
5
  Requires at least: 4.0
6
  Tested up to: 5.2
7
+ Stable tag: 3.4.4
8
  Requires PHP: 5.6
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
275
  * Autorotation error fix
276
  * Ziparchive class error fix
277
 
278
+ = 3.4.4 =
279
+ * Rotation fix on default scene face
280
+ * Rotation fix on hotspot to target scene face
281
+ * Plugin library update
282
+
283
  == Upgrade Notice ==
284
  Please do update the WP VR to the latest version. Each update makes it sure your plugin is supporting all tour features.  
admin/class-wpvr-ajax.php CHANGED
@@ -320,11 +320,11 @@ class Wpvr_Ajax {
320
  $scene_author = '';
321
  $scene_author = sanitize_text_field($panoscenes["scene-author"]);
322
 
323
- $default_scene_pitch = '';
324
- $default_scene_pitch = $panoscenes["scene-pitch"];
325
 
326
- $default_scene_yaw = '';
327
- $default_scene_yaw = $panoscenes["scene-yaw"];
328
 
329
  $scene_max_pitch = '';
330
  $scene_max_pitch = (float)$panoscenes["scene-maxpitch"];
320
  $scene_author = '';
321
  $scene_author = sanitize_text_field($panoscenes["scene-author"]);
322
 
323
+ $default_scene_pitch = null;
324
+ $default_scene_pitch = (float)$panoscenes["scene-pitch"];
325
 
326
+ $default_scene_yaw = null;
327
+ $default_scene_yaw = (float)$panoscenes["scene-yaw"];
328
 
329
  $scene_max_pitch = '';
330
  $scene_max_pitch = (float)$panoscenes["scene-maxpitch"];
admin/js/wpvr-admin.js CHANGED
@@ -36,7 +36,7 @@
36
  margin:10,
37
  autoWidth:true,
38
  });
39
- });
40
 
41
  jQuery(document).ready(function($){
42
 
@@ -110,6 +110,14 @@
110
  });
111
  }
112
  var panoshow = pannellum.viewer(response.data[0]["panoid"], scenes);
 
 
 
 
 
 
 
 
113
  var touchtime = 0;
114
  if (scenes) {
115
  $.each(scenes.scenes, function (key, val) {
36
  margin:10,
37
  autoWidth:true,
38
  });
39
+ });
40
 
41
  jQuery(document).ready(function($){
42
 
110
  });
111
  }
112
  var panoshow = pannellum.viewer(response.data[0]["panoid"], scenes);
113
+ if (scenes.autoRotate) {
114
+ panoshow.on('load', function (){
115
+ setTimeout(function(){ panoshow.startAutoRotate(scenes.autoRotate, 0); }, 3000);
116
+ });
117
+ panoshow.on('scenechange', function (){
118
+ setTimeout(function(){ panoshow.startAutoRotate(scenes.autoRotate, 0); }, 3000);
119
+ });
120
+ }
121
  var touchtime = 0;
122
  if (scenes) {
123
  $.each(scenes.scenes, function (key, val) {
admin/lib/pannellum/src/css/img/background.svg CHANGED
File without changes
admin/lib/pannellum/src/css/img/compass.svg CHANGED
File without changes
admin/lib/pannellum/src/css/img/grab.svg CHANGED
File without changes
admin/lib/pannellum/src/css/img/grabbing.svg CHANGED
File without changes
admin/lib/pannellum/src/css/img/sprites.svg CHANGED
File without changes
admin/lib/pannellum/src/css/pannellum.css CHANGED
@@ -286,7 +286,7 @@
286
  table-layout: fixed;
287
  }
288
 
289
- .pnlm-info-box a {
290
  color: #fff;
291
  word-wrap: break-word;
292
  overflow-wrap: break-word;
@@ -437,3 +437,7 @@ div.pnlm-tooltip:hover span:after {
437
  top: 0;
438
  left: 0;
439
  }
 
 
 
 
286
  table-layout: fixed;
287
  }
288
 
289
+ .pnlm-info-box a, .pnlm-author-box a {
290
  color: #fff;
291
  word-wrap: break-word;
292
  overflow-wrap: break-word;
437
  top: 0;
438
  left: 0;
439
  }
440
+
441
+ .pnlm-pointer {
442
+ cursor: pointer;
443
+ }
admin/lib/pannellum/src/js/libpannellum.js CHANGED
@@ -1,6 +1,6 @@
1
  /*
2
  * libpannellum - A WebGL and CSS 3D transform based Panorama Renderer
3
- * Copyright (c) 2012-2018 Matthew Petroff
4
  *
5
  * Permission is hereby granted, free of charge, to any person obtaining a copy
6
  * of this software and associated documentation files (the "Software"), to deal
@@ -101,6 +101,40 @@ function Renderer(container) {
101
  pose = undefined;
102
 
103
  var s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
  // This awful browser specific test exists because iOS 8/9 and IE 11
106
  // don't display non-power-of-two cubemap textures but also don't
@@ -110,7 +144,7 @@ function Renderer(container) {
110
  // NPOT cubemaps, and the CSS 3D transform fallback renderer is used
111
  // instead.
112
  if (!(imageType == 'cubemap' &&
113
- (image[0].width & (image[0].width - 1)) !== 0 &&
114
  (navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 8_/) ||
115
  navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 9_/) ||
116
  navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 10_/) ||
@@ -123,6 +157,7 @@ function Renderer(container) {
123
  }
124
 
125
  // If there is no WebGL, fall back to CSS 3D transform renderer.
 
126
  // While browser specific tests are usually frowned upon, the
127
  // fallback viewer only really works with WebKit/Blink and IE 10/11
128
  // (it doesn't work properly in Firefox).
@@ -206,6 +241,16 @@ function Renderer(container) {
206
  // Draw image width duplicated edge pixels on canvas
207
  faceContext.putImageData(imgData, 0, 0);
208
 
 
 
 
 
 
 
 
 
 
 
209
  loaded++;
210
  if (loaded == 6) {
211
  fallbackImgSize = this.width;
@@ -213,23 +258,27 @@ function Renderer(container) {
213
  callback();
214
  }
215
  };
 
216
  for (s = 0; s < 6; s++) {
217
  var faceImg = new Image();
218
  faceImg.crossOrigin = globalParams.crossOrigin ? globalParams.crossOrigin : 'anonymous';
219
  faceImg.side = s;
220
  faceImg.onload = onLoad;
 
221
  if (imageType == 'multires') {
222
- faceImg.src = encodeURI(path.replace('%s', sides[s]) + '.' + image.extension);
223
  } else {
224
- faceImg.src = encodeURI(image[s].src);
225
  }
226
  }
227
-
228
  return;
229
  } else if (!gl) {
230
  console.log('Error: no WebGL support detected!');
231
  throw {type: 'no webgl'};
232
  }
 
 
233
  if (image.basePath) {
234
  image.fullpath = image.basePath + image.path;
235
  } else {
@@ -245,20 +294,19 @@ function Renderer(container) {
245
  }
246
 
247
  // Make sure image isn't too big
248
- var width, maxWidth;
249
  if (imageType == 'equirectangular') {
250
- width = Math.max(image.width, image.height);
251
  maxWidth = gl.getParameter(gl.MAX_TEXTURE_SIZE);
252
- if (width > maxWidth) {
253
- console.log('Error: The image is too big; it\'s ' + width + 'px wide, but this device\'s maximum supported width is ' + maxWidth + 'px.');
254
- throw {type: 'webgl size error', width: width, maxWidth: maxWidth};
 
255
  }
256
  } else if (imageType == 'cubemap') {
257
- width = image[0].width;
258
- maxWidth = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
259
- if (width > maxWidth) {
260
- console.log('Error: The cube face image is too big; it\'s ' + width + 'px wide, but this device\'s maximum supported width is ' + maxWidth + 'px.');
261
- throw {type: 'webgl size error', width: width, maxWidth: maxWidth};
262
  }
263
  }
264
 
@@ -273,6 +321,15 @@ function Renderer(container) {
273
  // Create viewport for entire canvas
274
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
275
 
 
 
 
 
 
 
 
 
 
276
  // Create vertex shader
277
  vs = gl.createShader(gl.VERTEX_SHADER);
278
  var vertexSrc = v;
@@ -313,6 +370,11 @@ function Renderer(container) {
313
 
314
  program.drawInProgress = false;
315
 
 
 
 
 
 
316
  // Look up texture coordinates location
317
  program.texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
318
  gl.enableVertexAttribArray(program.texCoordLocation);
@@ -346,7 +408,6 @@ function Renderer(container) {
346
  // Set background color
347
  if (imageType == 'equirectangular') {
348
  program.backgroundColor = gl.getUniformLocation(program, 'u_backgroundColor');
349
- var color = params.backgroundColor ? params.backgroundColor : [0, 0, 0];
350
  gl.uniform4fv(program.backgroundColor, color.concat([1]));
351
  }
352
 
@@ -364,8 +425,45 @@ function Renderer(container) {
364
  gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[0]);
365
  gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[2]);
366
  } else {
367
- // Upload image to the texture
368
- gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  }
370
 
371
  // Set parameters for rendering any size
@@ -536,15 +634,17 @@ function Renderer(container) {
536
  r: 'translate3d(' + s + 'px, -' + (s + 2) + 'px, -' + (s + 2) + 'px) rotateY(270deg)'
537
  };
538
  focal = 1 / Math.tan(hfov / 2);
539
- var zoom = focal * gl.drawingBufferWidth / 2 + 'px';
540
  var transform = 'perspective(' + zoom + ') translateZ(' + zoom + ') rotateX(' + pitch + 'rad) rotateY(' + yaw + 'rad) ';
541
 
542
  // Apply face transforms
543
  var faces = Object.keys(transforms);
544
  for (i = 0; i < 6; i++) {
545
- var face = world.querySelector('.pnlm-' + faces[i] + 'face').style;
546
- face.webkitTransform = transform + transforms[faces[i]];
547
- face.transform = transform + transforms[faces[i]];
 
 
548
  }
549
  return;
550
  }
@@ -596,9 +696,9 @@ function Renderer(container) {
596
  program.nodeCache.length > program.currentNodes.length + 50) {
597
  // Remove older nodes from cache
598
  var removed = program.nodeCache.splice(200, program.nodeCache.length - 200);
599
- for (var i = 0; i < removed.length; i++) {
600
  // Explicitly delete textures
601
- gl.deleteTexture(removed[i].texture);
602
  }
603
  }
604
  program.currentNodes = [];
@@ -608,12 +708,29 @@ function Renderer(container) {
608
  var ntmp = new MultiresNode(vtmps[s], sides[s], 1, 0, 0, image.fullpath);
609
  testMultiresNode(rotPersp, ntmp, pitch, yaw, hfov);
610
  }
 
611
  program.currentNodes.sort(multiresNodeRenderSort);
612
- // Only process one tile per frame to improve responsiveness
613
- for (i = 0; i < program.currentNodes.length; i++) {
614
- if (!program.currentNodes[i].texture) {
615
- setTimeout(processNextTile, 0, program.currentNodes[i]);
616
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
  }
618
  }
619
 
@@ -697,8 +814,9 @@ function Renderer(container) {
697
  function multiresDraw() {
698
  if (!program.drawInProgress) {
699
  program.drawInProgress = true;
 
700
  for ( var i = 0; i < program.currentNodes.length; i++ ) {
701
- if (program.currentNodes[i].textureLoaded) {
702
  //var color = program.currentNodes[i].color;
703
  //gl.uniform4f(program.colorUniform, color[0], color[1], color[2], 1.0);
704
 
@@ -1011,12 +1129,13 @@ function Renderer(container) {
1011
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1012
  gl.bindTexture(gl.TEXTURE_2D, null);
1013
  }
 
 
1014
 
1015
  // Based on http://blog.tojicode.com/2012/03/javascript-memory-optimization-and.html
1016
  var loadTexture = (function() {
1017
  var cacheTop = 4; // Maximum number of concurrents loads
1018
  var textureImageCache = {};
1019
- var pendingTextureRequests = [];
1020
  var crossOrigin;
1021
 
1022
  function TextureImageLoader() {
@@ -1024,12 +1143,18 @@ function Renderer(container) {
1024
  this.texture = this.callback = null;
1025
  this.image = new Image();
1026
  this.image.crossOrigin = crossOrigin ? crossOrigin : 'anonymous';
1027
- this.image.addEventListener('load', function() {
1028
- processLoadedTexture(self.image, self.texture);
1029
- self.callback(self.texture);
 
 
 
 
1030
  releaseTextureImageLoader(self);
1031
  });
1032
- };
 
 
1033
 
1034
  TextureImageLoader.prototype.loadTexture = function(src, texture, callback) {
1035
  this.texture = texture;
@@ -1037,11 +1162,12 @@ function Renderer(container) {
1037
  this.image.src = src;
1038
  };
1039
 
1040
- function PendingTextureRequest(src, texture, callback) {
 
1041
  this.src = src;
1042
  this.texture = texture;
1043
  this.callback = callback;
1044
- };
1045
 
1046
  function releaseTextureImageLoader(til) {
1047
  if (pendingTextureRequests.length) {
@@ -1054,13 +1180,13 @@ function Renderer(container) {
1054
  for (var i = 0; i < cacheTop; i++)
1055
  textureImageCache[i] = new TextureImageLoader();
1056
 
1057
- return function(src, callback, _crossOrigin) {
1058
  crossOrigin = _crossOrigin;
1059
  var texture = gl.createTexture();
1060
  if (cacheTop)
1061
  textureImageCache[--cacheTop].loadTexture(src, texture, callback);
1062
  else
1063
- pendingTextureRequests.push(new PendingTextureRequest(src, texture, callback));
1064
  return texture;
1065
  };
1066
  })();
@@ -1071,13 +1197,10 @@ function Renderer(container) {
1071
  * @param {MultiresNode} node - Input node.
1072
  */
1073
  function processNextTile(node) {
1074
- if (!node.textureLoad) {
1075
- node.textureLoad = true;
1076
- loadTexture(encodeURI(node.path + '.' + image.extension), function(texture) {
1077
- node.texture = texture;
1078
- node.textureLoaded = true;
1079
- }, globalParams.crossOrigin);
1080
- }
1081
  }
1082
 
1083
  /**
@@ -1231,7 +1354,7 @@ var vMulti = [
1231
 
1232
  // Fragment shader
1233
  var fragEquiCubeBase = [
1234
- 'precision mediump float;',
1235
 
1236
  'uniform float u_aspectRatio;',
1237
  'uniform float u_psi;',
@@ -1245,7 +1368,9 @@ var fragEquiCubeBase = [
1245
  'const float PI = 3.14159265358979323846264;',
1246
 
1247
  // Texture
1248
- 'uniform sampler2D u_image;',
 
 
1249
  'uniform samplerCube u_imageCube;',
1250
 
1251
  // Coordinates passed in from vertex shader
@@ -1290,8 +1415,17 @@ var fragEquirectangular = fragEquiCubeBase + [
1290
  // Map from [-1,1] to [0,1] and flip y-axis
1291
  'if(coord.x < -u_h || coord.x > u_h || coord.y < -u_v + u_vo || coord.y > u_v + u_vo)',
1292
  'gl_FragColor = u_backgroundColor;',
1293
- 'else',
1294
- 'gl_FragColor = texture2D(u_image, vec2((coord.x + u_h) / (u_h * 2.0), (-coord.y + u_v + u_vo) / (u_v * 2.0)));',
 
 
 
 
 
 
 
 
 
1295
  '}'
1296
  ].join('\n');
1297
 
1
  /*
2
  * libpannellum - A WebGL and CSS 3D transform based Panorama Renderer
3
+ * Copyright (c) 2012-2019 Matthew Petroff
4
  *
5
  * Permission is hereby granted, free of charge, to any person obtaining a copy
6
  * of this software and associated documentation files (the "Software"), to deal
101
  pose = undefined;
102
 
103
  var s;
104
+ var faceMissing = false;
105
+ var cubeImgWidth;
106
+ if (imageType == 'cubemap') {
107
+ for (s = 0; s < 6; s++) {
108
+ if (image[s].width > 0) {
109
+ if (cubeImgWidth === undefined)
110
+ cubeImgWidth = image[s].width;
111
+ if (cubeImgWidth != image[s].width)
112
+ console.log('Cube faces have inconsistent widths: ' + cubeImgWidth + ' vs. ' + image[s].width);
113
+ } else
114
+ faceMissing = true;
115
+ }
116
+ }
117
+ function fillMissingFaces(imgSize) {
118
+ if (faceMissing) { // Fill any missing fallback/cubemap faces with background
119
+ var nbytes = imgSize * imgSize * 4; // RGB, plus non-functional alpha
120
+ var imageArray = new Uint8ClampedArray(nbytes);
121
+ var rgb = params.backgroundColor ? params.backgroundColor : [0, 0, 0];
122
+ rgb[0] *= 255;
123
+ rgb[1] *= 255;
124
+ rgb[2] *= 255;
125
+ // Maybe filling could be done faster, see e.g. https://stackoverflow.com/questions/1295584/most-efficient-way-to-create-a-zero-filled-javascript-array
126
+ for (var i = 0; i < nbytes; i++) {
127
+ imageArray[i++] = rgb[0];
128
+ imageArray[i++] = rgb[1];
129
+ imageArray[i++] = rgb[2];
130
+ }
131
+ var backgroundSquare = new ImageData(imageArray, imgSize, imgSize);
132
+ for (s = 0; s < 6; s++) {
133
+ if (image[s].width == 0)
134
+ image[s] = backgroundSquare;
135
+ }
136
+ }
137
+ }
138
 
139
  // This awful browser specific test exists because iOS 8/9 and IE 11
140
  // don't display non-power-of-two cubemap textures but also don't
144
  // NPOT cubemaps, and the CSS 3D transform fallback renderer is used
145
  // instead.
146
  if (!(imageType == 'cubemap' &&
147
+ (cubeImgWidth & (cubeImgWidth - 1)) !== 0 &&
148
  (navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 8_/) ||
149
  navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 9_/) ||
150
  navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 10_/) ||
157
  }
158
 
159
  // If there is no WebGL, fall back to CSS 3D transform renderer.
160
+ // This will discard the image loaded so far and load the fallback image.
161
  // While browser specific tests are usually frowned upon, the
162
  // fallback viewer only really works with WebKit/Blink and IE 10/11
163
  // (it doesn't work properly in Firefox).
241
  // Draw image width duplicated edge pixels on canvas
242
  faceContext.putImageData(imgData, 0, 0);
243
 
244
+ incLoaded.call(this);
245
+ };
246
+ var incLoaded = function() {
247
+ if (this.width > 0) {
248
+ if (fallbackImgSize === undefined)
249
+ fallbackImgSize = this.width;
250
+ if (fallbackImgSize != this.width)
251
+ console.log('Fallback faces have inconsistent widths: ' + fallbackImgSize + ' vs. ' + this.width);
252
+ } else
253
+ faceMissing = true;
254
  loaded++;
255
  if (loaded == 6) {
256
  fallbackImgSize = this.width;
258
  callback();
259
  }
260
  };
261
+ faceMissing = false;
262
  for (s = 0; s < 6; s++) {
263
  var faceImg = new Image();
264
  faceImg.crossOrigin = globalParams.crossOrigin ? globalParams.crossOrigin : 'anonymous';
265
  faceImg.side = s;
266
  faceImg.onload = onLoad;
267
+ faceImg.onerror = incLoaded; // ignore missing face to support partial fallback image
268
  if (imageType == 'multires') {
269
+ faceImg.src = path.replace('%s', sides[s]) + '.' + image.extension;
270
  } else {
271
+ faceImg.src = image[s].src;
272
  }
273
  }
274
+ fillMissingFaces(fallbackImgSize);
275
  return;
276
  } else if (!gl) {
277
  console.log('Error: no WebGL support detected!');
278
  throw {type: 'no webgl'};
279
  }
280
+ if (imageType == 'cubemap')
281
+ fillMissingFaces(cubeImgWidth);
282
  if (image.basePath) {
283
  image.fullpath = image.basePath + image.path;
284
  } else {
294
  }
295
 
296
  // Make sure image isn't too big
297
+ var maxWidth = 0;
298
  if (imageType == 'equirectangular') {
 
299
  maxWidth = gl.getParameter(gl.MAX_TEXTURE_SIZE);
300
+ if (Math.max(image.width / 2, image.height) > maxWidth) {
301
+ console.log('Error: The image is too big; it\'s ' + image.width + 'px wide, '+
302
+ 'but this device\'s maximum supported size is ' + (maxWidth * 2) + 'px.');
303
+ throw {type: 'webgl size error', width: image.width, maxWidth: maxWidth * 2};
304
  }
305
  } else if (imageType == 'cubemap') {
306
+ if (cubeImgWidth > gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE)) {
307
+ console.log('Error: The image is too big; it\'s ' + cubeImgWidth + 'px wide, ' +
308
+ 'but this device\'s maximum supported size is ' + maxWidth + 'px.');
309
+ throw {type: 'webgl size error', width: cubeImgWidth, maxWidth: maxWidth};
 
310
  }
311
  }
312
 
321
  // Create viewport for entire canvas
322
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
323
 
324
+ // Check precision support
325
+ if (gl.getShaderPrecisionFormat) {
326
+ var precision = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
327
+ if (precision && precision.precision < 1) {
328
+ // `highp` precision not supported; https://stackoverflow.com/a/33308927
329
+ fragEquiCubeBase = fragEquiCubeBase.replace('highp', 'mediump');
330
+ }
331
+ }
332
+
333
  // Create vertex shader
334
  vs = gl.createShader(gl.VERTEX_SHADER);
335
  var vertexSrc = v;
370
 
371
  program.drawInProgress = false;
372
 
373
+ // Set background clear color (does not apply to cubemap/fallback image)
374
+ var color = params.backgroundColor ? params.backgroundColor : [0, 0, 0];
375
+ gl.clearColor(color[0], color[1], color[2], 1.0);
376
+ gl.clear(gl.COLOR_BUFFER_BIT);
377
+
378
  // Look up texture coordinates location
379
  program.texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
380
  gl.enableVertexAttribArray(program.texCoordLocation);
408
  // Set background color
409
  if (imageType == 'equirectangular') {
410
  program.backgroundColor = gl.getUniformLocation(program, 'u_backgroundColor');
 
411
  gl.uniform4fv(program.backgroundColor, color.concat([1]));
412
  }
413
 
425
  gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[0]);
426
  gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[2]);
427
  } else {
428
+ if (image.width <= maxWidth) {
429
+ gl.uniform1i(gl.getUniformLocation(program, 'u_splitImage'), 0);
430
+ // Upload image to the texture
431
+ gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
432
+ } else {
433
+ // Image needs to be split into two parts due to texture size limits
434
+ gl.uniform1i(gl.getUniformLocation(program, 'u_splitImage'), 1);
435
+
436
+ // Draw image on canvas
437
+ var cropCanvas = document.createElement('canvas');
438
+ cropCanvas.width = image.width / 2;
439
+ cropCanvas.height = image.height;
440
+ var cropContext = cropCanvas.getContext('2d');
441
+ cropContext.drawImage(image, 0, 0);
442
+
443
+ // Upload first half of image to the texture
444
+ var cropImage = cropContext.getImageData(0, 0, image.width / 2, image.height);
445
+ gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, cropImage);
446
+
447
+ // Create and bind texture for second half of image
448
+ program.texture2 = gl.createTexture();
449
+ gl.activeTexture(gl.TEXTURE1);
450
+ gl.bindTexture(glBindType, program.texture2);
451
+ gl.uniform1i(gl.getUniformLocation(program, 'u_image1'), 1);
452
+
453
+ // Upload second half of image to the texture
454
+ cropContext.drawImage(image, -image.width / 2, 0);
455
+ cropImage = cropContext.getImageData(0, 0, image.width / 2, image.height);
456
+ gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, cropImage);
457
+
458
+ // Set parameters for rendering any size
459
+ gl.texParameteri(glBindType, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
460
+ gl.texParameteri(glBindType, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
461
+ gl.texParameteri(glBindType, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
462
+ gl.texParameteri(glBindType, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
463
+
464
+ // Reactivate first texture unit
465
+ gl.activeTexture(gl.TEXTURE0);
466
+ }
467
  }
468
 
469
  // Set parameters for rendering any size
634
  r: 'translate3d(' + s + 'px, -' + (s + 2) + 'px, -' + (s + 2) + 'px) rotateY(270deg)'
635
  };
636
  focal = 1 / Math.tan(hfov / 2);
637
+ var zoom = focal * canvas.clientWidth / 2 + 'px';
638
  var transform = 'perspective(' + zoom + ') translateZ(' + zoom + ') rotateX(' + pitch + 'rad) rotateY(' + yaw + 'rad) ';
639
 
640
  // Apply face transforms
641
  var faces = Object.keys(transforms);
642
  for (i = 0; i < 6; i++) {
643
+ var face = world.querySelector('.pnlm-' + faces[i] + 'face');
644
+ if (!face)
645
+ continue; // ignore missing face to support partial cubemap/fallback image
646
+ face.style.webkitTransform = transform + transforms[faces[i]];
647
+ face.style.transform = transform + transforms[faces[i]];
648
  }
649
  return;
650
  }
696
  program.nodeCache.length > program.currentNodes.length + 50) {
697
  // Remove older nodes from cache
698
  var removed = program.nodeCache.splice(200, program.nodeCache.length - 200);
699
+ for (var j = 0; j < removed.length; j++) {
700
  // Explicitly delete textures
701
+ gl.deleteTexture(removed[j].texture);
702
  }
703
  }
704
  program.currentNodes = [];
708
  var ntmp = new MultiresNode(vtmps[s], sides[s], 1, 0, 0, image.fullpath);
709
  testMultiresNode(rotPersp, ntmp, pitch, yaw, hfov);
710
  }
711
+
712
  program.currentNodes.sort(multiresNodeRenderSort);
713
+
714
+ // Unqueue any pending requests for nodes that are no longer visible
715
+ for (i = pendingTextureRequests.length - 1; i >= 0; i--) {
716
+ if (program.currentNodes.indexOf(pendingTextureRequests[i].node) === -1) {
717
+ pendingTextureRequests[i].node.textureLoad = false;
718
+ pendingTextureRequests.splice(i, 1);
719
+ }
720
+ }
721
+
722
+ // Allow one request to be pending, so that we can create a texture buffer for that in advance of loading actually beginning
723
+ if (pendingTextureRequests.length === 0) {
724
+ for (i = 0; i < program.currentNodes.length; i++) {
725
+ var node = program.currentNodes[i];
726
+ if (!node.texture && !node.textureLoad) {
727
+ node.textureLoad = true;
728
+
729
+ setTimeout(processNextTile, 0, node);
730
+
731
+ // Only process one tile per frame to improve responsiveness
732
+ break;
733
+ }
734
  }
735
  }
736
 
814
  function multiresDraw() {
815
  if (!program.drawInProgress) {
816
  program.drawInProgress = true;
817
+ gl.clear(gl.COLOR_BUFFER_BIT);
818
  for ( var i = 0; i < program.currentNodes.length; i++ ) {
819
+ if (program.currentNodes[i].textureLoaded > 1) {
820
  //var color = program.currentNodes[i].color;
821
  //gl.uniform4f(program.colorUniform, color[0], color[1], color[2], 1.0);
822
 
1129
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1130
  gl.bindTexture(gl.TEXTURE_2D, null);
1131
  }
1132
+
1133
+ var pendingTextureRequests = [];
1134
 
1135
  // Based on http://blog.tojicode.com/2012/03/javascript-memory-optimization-and.html
1136
  var loadTexture = (function() {
1137
  var cacheTop = 4; // Maximum number of concurrents loads
1138
  var textureImageCache = {};
 
1139
  var crossOrigin;
1140
 
1141
  function TextureImageLoader() {
1143
  this.texture = this.callback = null;
1144
  this.image = new Image();
1145
  this.image.crossOrigin = crossOrigin ? crossOrigin : 'anonymous';
1146
+ var loadFn = (function() {
1147
+ if (self.image.width > 0 && self.image.height > 0) { // ignore missing tile to supporting partial image
1148
+ processLoadedTexture(self.image, self.texture);
1149
+ self.callback(self.texture, true);
1150
+ } else {
1151
+ self.callback(self.texture, false);
1152
+ }
1153
  releaseTextureImageLoader(self);
1154
  });
1155
+ this.image.addEventListener('load', loadFn);
1156
+ this.image.addEventListener('error', loadFn); // ignore missing tile file to support partial image, otherwise retry loop causes high CPU load
1157
+ }
1158
 
1159
  TextureImageLoader.prototype.loadTexture = function(src, texture, callback) {
1160
  this.texture = texture;
1162
  this.image.src = src;
1163
  };
1164
 
1165
+ function PendingTextureRequest(node, src, texture, callback) {
1166
+ this.node = node;
1167
  this.src = src;
1168
  this.texture = texture;
1169
  this.callback = callback;
1170
+ }
1171
 
1172
  function releaseTextureImageLoader(til) {
1173
  if (pendingTextureRequests.length) {
1180
  for (var i = 0; i < cacheTop; i++)
1181
  textureImageCache[i] = new TextureImageLoader();
1182
 
1183
+ return function(node, src, callback, _crossOrigin) {
1184
  crossOrigin = _crossOrigin;
1185
  var texture = gl.createTexture();
1186
  if (cacheTop)
1187
  textureImageCache[--cacheTop].loadTexture(src, texture, callback);
1188
  else
1189
+ pendingTextureRequests.push(new PendingTextureRequest(node, src, texture, callback));
1190
  return texture;
1191
  };
1192
  })();
1197
  * @param {MultiresNode} node - Input node.
1198
  */
1199
  function processNextTile(node) {
1200
+ loadTexture(node, node.path + '.' + image.extension, function(texture, loaded) {
1201
+ node.texture = texture;
1202
+ node.textureLoaded = loaded ? 2 : 1;
1203
+ }, globalParams.crossOrigin);
 
 
 
1204
  }
1205
 
1206
  /**
1354
 
1355
  // Fragment shader
1356
  var fragEquiCubeBase = [
1357
+ 'precision highp float;', // mediump looks bad on some mobile devices
1358
 
1359
  'uniform float u_aspectRatio;',
1360
  'uniform float u_psi;',
1368
  'const float PI = 3.14159265358979323846264;',
1369
 
1370
  // Texture
1371
+ 'uniform sampler2D u_image0;',
1372
+ 'uniform sampler2D u_image1;',
1373
+ 'uniform bool u_splitImage;',
1374
  'uniform samplerCube u_imageCube;',
1375
 
1376
  // Coordinates passed in from vertex shader
1415
  // Map from [-1,1] to [0,1] and flip y-axis
1416
  'if(coord.x < -u_h || coord.x > u_h || coord.y < -u_v + u_vo || coord.y > u_v + u_vo)',
1417
  'gl_FragColor = u_backgroundColor;',
1418
+ 'else {',
1419
+ 'if(u_splitImage) {',
1420
+ // Image was split into two textures to work around texture size limits
1421
+ 'if(coord.x < 0.0)',
1422
+ 'gl_FragColor = texture2D(u_image0, vec2((coord.x + u_h) / u_h, (-coord.y + u_v + u_vo) / (u_v * 2.0)));',
1423
+ 'else',
1424
+ 'gl_FragColor = texture2D(u_image1, vec2((coord.x + u_h) / u_h - 1.0, (-coord.y + u_v + u_vo) / (u_v * 2.0)));',
1425
+ '} else {',
1426
+ 'gl_FragColor = texture2D(u_image0, vec2((coord.x + u_h) / (u_h * 2.0), (-coord.y + u_v + u_vo) / (u_v * 2.0)));',
1427
+ '}',
1428
+ '}',
1429
  '}'
1430
  ].join('\n');
1431
 
admin/lib/pannellum/src/js/pannellum.js CHANGED
@@ -1,6 +1,6 @@
1
  /*
2
  * Pannellum - An HTML5 based Panorama Viewer
3
- * Copyright (c) 2011-2018 Matthew Petroff
4
  *
5
  * Permission is hereby granted, free of charge, to any person obtaining a copy
6
  * of this software and associated documentation files (the "Software"), to deal
@@ -49,7 +49,7 @@ var config,
49
  onPointerDownPitch = 0,
50
  keysDown = new Array(10),
51
  fullscreenActive = false,
52
- loaded = false,
53
  error = false,
54
  isTimedOut = false,
55
  listenersAdded = false,
@@ -67,11 +67,14 @@ var config,
67
  externalEventListeners = {},
68
  specifiedPhotoSphereExcludes = [],
69
  update = false, // Should we update when still to render dynamic content
70
- hotspotsCreated = false;
 
 
71
 
72
  var defaultConfig = {
73
  hfov: 100,
74
  minHfov: 50,
 
75
  maxHfov: 120,
76
  pitch: 0,
77
  minPitch: undefined,
@@ -90,6 +93,7 @@ var defaultConfig = {
90
  northOffset: 0,
91
  showFullscreenCtrl: true,
92
  dynamic: false,
 
93
  doubleClickZoom: true,
94
  keyboardZoom: true,
95
  mouseZoom: true,
@@ -99,10 +103,14 @@ var defaultConfig = {
99
  orientationOnByDefault: false,
100
  hotSpotDebug: false,
101
  backgroundColor: [0, 0, 0],
 
102
  animationTimingFunction: timingFunction,
103
  draggable: true,
104
  disableKeyboardCtrl: false,
105
  crossOrigin: 'anonymous',
 
 
 
106
  };
107
 
108
  // Translatable / configurable strings
@@ -112,7 +120,7 @@ defaultConfig.strings = {
112
  // Labels
113
  loadButtonLabel: 'Click to<br>Load<br>Panorama',
114
  loadingLabel: 'Loading...',
115
- bylineLabel: '%s', // One substitution: author
116
 
117
  // Errors
118
  noPanoramaError: 'No panorama image was specified.',
@@ -127,13 +135,10 @@ defaultConfig.strings = {
127
  '%spx wide. Try another device.' +
128
  ' (If you\'re the author, try scaling down the image.)', // Two substitutions: image width, max image width
129
  unknownError: 'Unknown error. Check developer console.',
130
- }
131
-
132
- var usedKeyNumbers = [16, 17, 27, 37, 38, 39, 40, 61, 65, 68, 83, 87, 107, 109, 173, 187, 189];
133
 
134
  // Initialize container
135
  container = typeof container === 'string' ? document.getElementById(container) : container;
136
- // console.log(container);
137
  container.classList.add('pnlm-container');
138
  container.tabIndex = 0;
139
 
@@ -154,7 +159,10 @@ uiContainer.appendChild(dragFix);
154
  var aboutMsg = document.createElement('span');
155
  aboutMsg.className = 'pnlm-about-msg';
156
 
 
157
  aboutMsg.innerHTML = '<a href="https://rextheme.com/docs/wpvr-360-panorama-and-virtual-tour-creator-for-wordpress/" target="_blank">Rextheme</a>';
 
 
158
  uiContainer.appendChild(aboutMsg);
159
  dragFix.addEventListener('contextmenu', aboutMessage);
160
 
@@ -353,18 +361,23 @@ function init() {
353
  var onError = function(e) {
354
  var a = document.createElement('a');
355
  a.href = e.target.src;
356
- a.innerHTML = a.href;
357
  anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
358
  };
359
 
360
  for (i = 0; i < panoImage.length; i++) {
361
- panoImage[i].onload = onLoad;
362
- panoImage[i].onerror = onError;
363
  p = config.cubeMap[i];
364
- if (config.basePath && !absoluteURL(p)) {
365
- p = config.basePath + p;
 
 
 
 
 
 
 
 
366
  }
367
- panoImage[i].src = encodeURI(p);
368
  }
369
  } else if (config.type == 'multires') {
370
  onImageLoad();
@@ -388,8 +401,8 @@ function init() {
388
  if (xhr.status != 200) {
389
  // Display error if image can't be loaded
390
  var a = document.createElement('a');
391
- a.href = encodeURI(p);
392
- a.innerHTML = a.href;
393
  anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
394
  }
395
  var img = this.response;
@@ -438,6 +451,13 @@ function init() {
438
  if (config.draggable)
439
  uiContainer.classList.add('pnlm-grab');
440
  uiContainer.classList.remove('pnlm-grabbing');
 
 
 
 
 
 
 
441
  }
442
 
443
  /**
@@ -449,7 +469,7 @@ function init() {
449
  function absoluteURL(url) {
450
  // From http://stackoverflow.com/a/19709846
451
  return new RegExp('^(?:[a-z]+:)?//', 'i').test(url) || url[0] == '/' || url.slice(0, 5) == 'blob:';
452
- };
453
 
454
  /**
455
  * Create renderer and initialize event listeners once image is loaded.
@@ -472,10 +492,10 @@ function onImageLoad() {
472
  if (config.doubleClickZoom) {
473
  dragFix.addEventListener('dblclick', onDocumentDoubleClick, false);
474
  }
475
- uiContainer.addEventListener('mozfullscreenchange', onFullScreenChange, false);
476
- uiContainer.addEventListener('webkitfullscreenchange', onFullScreenChange, false);
477
- uiContainer.addEventListener('msfullscreenchange', onFullScreenChange, false);
478
- uiContainer.addEventListener('fullscreenchange', onFullScreenChange, false);
479
  window.addEventListener('resize', onDocumentResize, false);
480
  window.addEventListener('orientationchange', onDocumentResize, false);
481
  if (!config.disableKeyboardCtrl) {
@@ -484,13 +504,17 @@ function onImageLoad() {
484
  container.addEventListener('blur', clearKeys, false);
485
  }
486
  document.addEventListener('mouseleave', onDocumentMouseUp, false);
487
- dragFix.addEventListener('touchstart', onDocumentTouchStart, false);
488
- dragFix.addEventListener('touchmove', onDocumentTouchMove, false);
489
- dragFix.addEventListener('touchend', onDocumentTouchEnd, false);
490
- dragFix.addEventListener('pointerdown', onDocumentPointerDown, false);
491
- dragFix.addEventListener('pointermove', onDocumentPointerMove, false);
492
- dragFix.addEventListener('pointerup', onDocumentPointerUp, false);
493
- dragFix.addEventListener('pointerleave', onDocumentPointerUp, false);
 
 
 
 
494
 
495
  // Deal with MS pointer events
496
  if (window.navigator.pointerEnabled)
@@ -498,6 +522,7 @@ function onImageLoad() {
498
  }
499
 
500
  renderInit();
 
501
  setTimeout(function(){isTimedOut = true;}, 500);
502
  }
503
 
@@ -604,6 +629,7 @@ function anError(errorMsg) {
604
  infoDisplay.load.box.style.display = 'none';
605
  infoDisplay.errorMsg.style.display = 'table';
606
  error = true;
 
607
  renderContainer.style.display = 'none';
608
  fireEvent('error', errorMsg);
609
  }
@@ -617,6 +643,7 @@ function clearError() {
617
  infoDisplay.load.box.style.display = 'none';
618
  infoDisplay.errorMsg.style.display = 'none';
619
  error = false;
 
620
  fireEvent('errorcleared');
621
  }
622
  }
@@ -648,8 +675,9 @@ function aboutMessage(event) {
648
  function mousePosition(event) {
649
  var bounds = container.getBoundingClientRect();
650
  var pos = {};
651
- pos.x = event.clientX - bounds.left;
652
- pos.y = event.clientY - bounds.top;
 
653
  return pos;
654
  }
655
 
@@ -678,11 +706,14 @@ function onDocumentMouseDown(event) {
678
  var coords = mouseEventToCoords(event);
679
  console.log('Pitch: ' + coords[0] + ', Yaw: ' + coords[1] + ', Center Pitch: ' +
680
  config.pitch + ', Center Yaw: ' + config.yaw + ', HFOV: ' + config.hfov);
681
-
682
  }
683
- var coords = mouseEventToCoords(event);
684
- jQuery("#panodata").html('Pitch: ' + coords[0] + ', Yaw: ' + coords[1]);
685
- jQuery( ".rex-hide-coordinates" ).removeClass( 'rex-hide-coordinates' );
 
 
 
 
686
  // Turn off auto-rotation if enabled
687
  stopAnimation();
688
 
@@ -876,7 +907,7 @@ function onDocumentTouchMove(event) {
876
  //
877
  // Currently this seems to *roughly* keep initial drag/pan start position close to
878
  // the user's finger while panning regardless of zoom level / config.hfov value.
879
- var touchmovePanSpeedCoeff = config.hfov / 360;
880
 
881
  var yaw = (onPointerDownPointerX - clientX) * touchmovePanSpeedCoeff + onPointerDownYaw;
882
  speed.yaw = (yaw - config.yaw) % 360 * 0.2;
@@ -912,6 +943,9 @@ var pointerIDs = [],
912
  */
913
  function onDocumentPointerDown(event) {
914
  if (event.pointerType == 'touch') {
 
 
 
915
  pointerIDs.push(event.pointerId);
916
  pointerCoordinates.push({clientX: event.clientX, clientY: event.clientY});
917
  event.targetTouches = pointerCoordinates;
@@ -927,13 +961,15 @@ function onDocumentPointerDown(event) {
927
  */
928
  function onDocumentPointerMove(event) {
929
  if (event.pointerType == 'touch') {
 
 
930
  for (var i = 0; i < pointerIDs.length; i++) {
931
  if (event.pointerId == pointerIDs[i]) {
932
  pointerCoordinates[i].clientX = event.clientX;
933
  pointerCoordinates[i].clientY = event.clientY;
934
  event.targetTouches = pointerCoordinates;
935
  onDocumentTouchMove(event);
936
- //event.preventDefault();
937
  return;
938
  }
939
  }
@@ -993,7 +1029,6 @@ function onDocumentMouseWheel(event) {
993
  setHfov(config.hfov + event.detail * 1.5);
994
  speed.hfov = event.detail > 0 ? 1 : -1;
995
  }
996
-
997
  animateInit();
998
  }
999
 
@@ -1014,8 +1049,8 @@ function onDocumentKeyPress(event) {
1014
  var keynumber = event.which || event.keycode;
1015
 
1016
  // Override default action for keys that are used
1017
- if (usedKeyNumbers.indexOf(keynumber) < 0)
1018
- return
1019
  event.preventDefault();
1020
 
1021
  // If escape key is pressed
@@ -1050,8 +1085,8 @@ function onDocumentKeyUp(event) {
1050
  var keynumber = event.which || event.keycode;
1051
 
1052
  // Override default action for keys that are used
1053
- if (usedKeyNumbers.indexOf(keynumber) < 0)
1054
- return
1055
  event.preventDefault();
1056
 
1057
  // Change key
@@ -1201,12 +1236,11 @@ function keyRepeat() {
1201
  latestInteraction = Date.now();
1202
 
1203
  // If auto-rotate
1204
- var inactivityInterval = Date.now() - latestInteraction;
1205
  if (config.autoRotate) {
1206
  // Pan
1207
  if (newTime - prevTime > 0.001) {
1208
  var timeDiff = (newTime - prevTime) / 1000;
1209
- var yawDiff = (speed.yaw / timeDiff * diff - config.autoRotate * 0.2) * timeDiff
1210
  yawDiff = (-config.autoRotate > 0 ? 1 : -1) * Math.min(Math.abs(config.autoRotate * timeDiff), Math.abs(yawDiff));
1211
  config.yaw += yawDiff;
1212
  }
@@ -1239,19 +1273,19 @@ function keyRepeat() {
1239
  // "Inertia"
1240
  if (diff > 0 && !config.autoRotate) {
1241
  // "Friction"
1242
- var friction = 0.85;
1243
 
1244
  // Yaw
1245
  if (!keysDown[4] && !keysDown[5] && !keysDown[8] && !keysDown[9] && !animatedMove.yaw) {
1246
- config.yaw += speed.yaw * diff * friction;
1247
  }
1248
  // Pitch
1249
  if (!keysDown[2] && !keysDown[3] && !keysDown[6] && !keysDown[7] && !animatedMove.pitch) {
1250
- config.pitch += speed.pitch * diff * friction;
1251
  }
1252
  // Zoom
1253
  if (!keysDown[0] && !keysDown[1] && !animatedMove.hfov) {
1254
- setHfov(config.hfov + speed.hfov * diff * friction);
1255
  }
1256
  }
1257
 
@@ -1269,7 +1303,7 @@ function keyRepeat() {
1269
  }
1270
 
1271
  // Stop movement if opposite controls are pressed
1272
- if (keysDown[0] && keysDown[0]) {
1273
  speed.hfov = 0;
1274
  }
1275
  if ((keysDown[2] || keysDown[6]) && (keysDown[3] || keysDown[7])) {
@@ -1294,11 +1328,7 @@ function animateMove(axis) {
1294
  t.endPosition === t.startPosition) {
1295
  result = t.endPosition;
1296
  speed[axis] = 0;
1297
- var callback = animatedMove[axis].callback,
1298
- callbackArgs = animatedMove[axis].callbackArgs;
1299
  delete animatedMove[axis];
1300
- if (typeof callback == 'function')
1301
- callback(callbackArgs);
1302
  }
1303
  config[axis] = result;
1304
  }
@@ -1323,7 +1353,7 @@ function onDocumentResize() {
1323
  //animateInit();
1324
 
1325
  // Kludge to deal with WebKit regression: https://bugs.webkit.org/show_bug.cgi?id=93525
1326
- onFullScreenChange();
1327
  }
1328
 
1329
  /**
@@ -1343,6 +1373,10 @@ function animateInit() {
1343
  * @private
1344
  */
1345
  function animate() {
 
 
 
 
1346
  render();
1347
  if (autoRotateStart)
1348
  clearTimeout(autoRotateStart);
@@ -1366,6 +1400,7 @@ function animate() {
1366
  } else if (renderer && (renderer.isLoading() || (config.dynamic === true && update))) {
1367
  requestAnimationFrame(animate);
1368
  } else {
 
1369
  animating = false;
1370
  prevTime = undefined;
1371
  var autoRotateStartTime = config.autoRotateInactivityDelay -
@@ -1373,7 +1408,10 @@ function animate() {
1373
  if (autoRotateStartTime > 0) {
1374
  autoRotateStart = setTimeout(function() {
1375
  config.autoRotate = autoRotateSpeed;
1376
- _this.lookAt(origPitch, undefined, origHfov, 3000);
 
 
 
1377
  animateInit();
1378
  }, autoRotateStartTime);
1379
  } else if (config.autoRotateInactivityDelay >= 0 && autoRotateSpeed) {
@@ -1392,37 +1430,68 @@ function render() {
1392
  var tmpyaw;
1393
 
1394
  if (loaded) {
1395
- if (config.yaw > 180) {
1396
- config.yaw -= 360;
1397
- } else if (config.yaw < -180) {
1398
- config.yaw += 360;
 
 
 
 
 
1399
  }
1400
 
1401
  // Keep a tmp value of yaw for autoRotate comparison later
1402
  tmpyaw = config.yaw;
1403
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1404
  // Ensure the yaw is within min and max allowed
1405
  var yawRange = config.maxYaw - config.minYaw,
1406
  minYaw = -180,
1407
  maxYaw = 180;
1408
  if (yawRange < 360) {
1409
- minYaw = config.minYaw + config.hfov / 2;
1410
- maxYaw = config.maxYaw - config.hfov / 2;
1411
  if (yawRange < config.hfov) {
1412
  // Lock yaw to average of min and max yaw when both can be seen at once
1413
  minYaw = maxYaw = (minYaw + maxYaw) / 2;
1414
  }
 
 
 
 
 
 
 
 
 
 
 
1415
  }
1416
- config.yaw = Math.max(minYaw, Math.min(maxYaw, config.yaw));
1417
 
1418
  // Check if we autoRotate in a limited by min and max yaw
1419
  // If so reverse direction
1420
- if (config.autoRotate !== false && tmpyaw != config.yaw) {
 
1421
  config.autoRotate *= -1;
1422
  }
1423
 
1424
  // Ensure the calculated pitch is within min and max allowed
1425
- var canvas = renderer.getCanvas();
1426
  var vfov = 2 * Math.atan(Math.tan(config.hfov / 180 * Math.PI * 0.5) /
1427
  (canvas.width / canvas.height)) / Math.PI * 180;
1428
  var minPitch = config.minPitch + vfov / 2,
@@ -1477,7 +1546,7 @@ Quaternion.prototype.multiply = function(q) {
1477
  this.x*q.w + this.w*q.x + this.y*q.z - this.z*q.y,
1478
  this.y*q.w + this.w*q.y + this.z*q.x - this.x*q.z,
1479
  this.z*q.w + this.w*q.z + this.x*q.y - this.y*q.x);
1480
- }
1481
 
1482
  /**
1483
  * Converts quaternion to Euler angles.
@@ -1491,7 +1560,7 @@ Quaternion.prototype.toEulerAngles = function() {
1491
  psi = Math.atan2(2 * (this.w * this.z + this.x * this.y),
1492
  1 - 2 * (this.y * this.y + this.z * this.z));
1493
  return [phi, theta, psi];
1494
- }
1495
 
1496
  /**
1497
  * Converts device orientation API Tait-Bryan angles to a quaternion.
@@ -1628,9 +1697,9 @@ function renderInitCallback() {
1628
  }
1629
  loaded = true;
1630
 
1631
- fireEvent('load');
1632
-
1633
  animateInit();
 
 
1634
  }
1635
 
1636
  /**
@@ -1644,7 +1713,7 @@ function createHotSpot(hs) {
1644
  hs.yaw = Number(hs.yaw) || 0;
1645
 
1646
  var div = document.createElement('div');
1647
- div.className = 'pnlm-hotspot-base'
1648
  if (hs.cssClass)
1649
  div.className += ' ' + hs.cssClass;
1650
  else
@@ -1657,24 +1726,24 @@ function createHotSpot(hs) {
1657
  var a;
1658
  if (hs.video) {
1659
  var video = document.createElement('video'),
1660
- p = hs.video;
1661
- if (config.basePath && !absoluteURL(p))
1662
- p = config.basePath + p;
1663
- video.src = encodeURI(p);
1664
  video.controls = true;
1665
  video.style.width = hs.width + 'px';
1666
  renderContainer.appendChild(div);
1667
  span.appendChild(video);
1668
  } else if (hs.image) {
1669
- var p = hs.image;
1670
- if (config.basePath && !absoluteURL(p))
1671
- p = config.basePath + p;
1672
  a = document.createElement('a');
1673
- a.href = encodeURI(hs.URL ? hs.URL : p);
1674
  a.target = '_blank';
1675
  span.appendChild(a);
1676
  var image = document.createElement('img');
1677
- image.src = encodeURI(p);
1678
  image.style.width = hs.width + 'px';
1679
  image.style.paddingTop = '5px';
1680
  renderContainer.appendChild(div);
@@ -1682,11 +1751,17 @@ function createHotSpot(hs) {
1682
  span.style.maxWidth = 'initial';
1683
  } else if (hs.URL) {
1684
  a = document.createElement('a');
1685
- a.href = encodeURI(hs.URL);
1686
- a.target = '_blank';
 
 
 
 
 
 
1687
  renderContainer.appendChild(a);
1688
- div.style.cursor = 'pointer';
1689
- span.style.cursor = 'pointer';
1690
  a.appendChild(div);
1691
  } else {
1692
  if (hs.sceneId) {
@@ -1697,8 +1772,8 @@ function createHotSpot(hs) {
1697
  }
1698
  return false;
1699
  };
1700
- div.style.cursor = 'pointer';
1701
- span.style.cursor = 'pointer';
1702
  }
1703
  renderContainer.appendChild(div);
1704
  }
@@ -1716,11 +1791,11 @@ function createHotSpot(hs) {
1716
  div.addEventListener('click', function(e) {
1717
  hs.clickHandlerFunc(e, hs.clickHandlerArgs);
1718
  }, 'false');
1719
- div.style.cursor = 'pointer';
1720
- span.style.cursor = 'pointer';
1721
  }
1722
  hs.div = div;
1723
- };
1724
 
1725
  /**
1726
  * Creates hot spot elements for the current scene.
@@ -1753,10 +1828,12 @@ function destroyHotSpots() {
1753
  if (hs) {
1754
  for (var i = 0; i < hs.length; i++) {
1755
  var current = hs[i].div;
1756
- while(current.parentNode != renderContainer) {
1757
- current = current.parentNode;
 
 
 
1758
  }
1759
- renderContainer.removeChild(current);
1760
  delete hs[i].div;
1761
  }
1762
  }
@@ -1798,6 +1875,9 @@ function renderHotSpot(hs) {
1798
  coord[1] += (canvasHeight - hs.div.offsetHeight) / 2;
1799
  var transform = 'translate(' + coord[0] + 'px, ' + coord[1] +
1800
  'px) translateZ(9999px) rotate(' + config.roll + 'deg)';
 
 
 
1801
  hs.div.style.webkitTransform = transform;
1802
  hs.div.style.MozTransform = transform;
1803
  hs.div.style.transform = transform;
@@ -1906,7 +1986,7 @@ function processOptions(isPreview) {
1906
  p = config.basePath + p;
1907
  preview = document.createElement('div');
1908
  preview.className = 'pnlm-preview-img';
1909
- preview.style.backgroundImage = "url('" + encodeURI(p) + "')";
1910
  renderContainer.appendChild(preview);
1911
  }
1912
 
@@ -1942,12 +2022,29 @@ function processOptions(isPreview) {
1942
  break;
1943
 
1944
  case 'author':
1945
- infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', escapeHTML(config[key]));
 
 
 
 
 
 
 
 
1946
  infoDisplay.container.style.display = 'inline';
1947
  break;
1948
 
1949
  case 'fallback':
1950
- infoDisplay.errorMsg.innerHTML = '<p>Your browser does not support WebGL.<br><a href="' + encodeURI(config[key]) + '" target="_blank">Click here to view this panorama in an alternative viewer.</a></p>';
 
 
 
 
 
 
 
 
 
1951
  break;
1952
 
1953
  case 'hfov':
@@ -2065,15 +2162,16 @@ function toggleFullscreen() {
2065
  * Event handler for fullscreen changes.
2066
  * @private
2067
  */
2068
- function onFullScreenChange() {
2069
- if (document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement) {
2070
  controls.fullscreen.classList.add('pnlm-fullscreen-toggle-button-active');
2071
  fullscreenActive = true;
2072
  } else {
2073
  controls.fullscreen.classList.remove('pnlm-fullscreen-toggle-button-active');
2074
  fullscreenActive = false;
2075
  }
2076
-
 
2077
  // Resize renderer (deal with browser quirks and fixes #155)
2078
  renderer.resize();
2079
  setHfov(config.hfov);
@@ -2111,20 +2209,30 @@ function zoomOut() {
2111
  function constrainHfov(hfov) {
2112
  // Keep field of view within bounds
2113
  var minHfov = config.minHfov;
2114
- if (config.type == 'multires' && renderer) {
2115
  minHfov = Math.min(minHfov, renderer.getCanvas().width / (config.multiRes.cubeResolution / 90 * 0.9));
2116
  }
2117
  if (minHfov > config.maxHfov) {
2118
  // Don't change view if bounds don't make sense
2119
- console.log('HFOV bounds do not make sense (minHfov > maxHfov).')
2120
  return config.hfov;
2121
- } if (hfov < minHfov) {
2122
- return minHfov;
 
 
2123
  } else if (hfov > config.maxHfov) {
2124
- return config.maxHfov;
2125
  } else {
2126
- return hfov;
 
 
 
 
 
 
 
2127
  }
 
2128
  }
2129
 
2130
  /**
@@ -2134,6 +2242,7 @@ function constrainHfov(hfov) {
2134
  */
2135
  function setHfov(hfov) {
2136
  config.hfov = constrainHfov(hfov);
 
2137
  }
2138
 
2139
  /**
@@ -2155,6 +2264,7 @@ function load() {
2155
  // since it is a new scene and the error from previous maybe because of lacking
2156
  // memory etc and not because of a lack of WebGL support etc
2157
  clearError();
 
2158
 
2159
  controls.load.style.display = 'none';
2160
  infoDisplay.load.box.style.display = 'inline';
@@ -2171,6 +2281,8 @@ function load() {
2171
  * @param {boolean} [fadeDone] - If `true`, fade setup is skipped.
2172
  */
2173
  function loadScene(sceneId, targetPitch, targetYaw, targetHfov, fadeDone) {
 
 
2174
  loaded = false;
2175
  animatedMove = {};
2176
 
@@ -2252,9 +2364,19 @@ function stopOrientation() {
2252
  * @private
2253
  */
2254
  function startOrientation() {
2255
- orientation = 1;
2256
- window.addEventListener('deviceorientation', orientationListener);
2257
- controls.orientation.classList.add('pnlm-orientation-button-active');
 
 
 
 
 
 
 
 
 
 
2258
  }
2259
 
2260
  /**
@@ -2275,6 +2397,34 @@ function escapeHTML(s) {
2275
  .split('\n').join('<br>'); // Allow line breaks
2276
  }
2277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2278
  /**
2279
  * Checks whether or not a panorama is loaded.
2280
  * @memberof Viewer
@@ -2282,7 +2432,7 @@ function escapeHTML(s) {
2282
  * @returns {boolean} `true` if a panorama is loaded, else `false`
2283
  */
2284
  this.isLoaded = function() {
2285
- return loaded;
2286
  };
2287
 
2288
  /**
@@ -2306,16 +2456,22 @@ this.getPitch = function() {
2306
  * @returns {Viewer} `this`
2307
  */
2308
  this.setPitch = function(pitch, animated, callback, callbackArgs) {
 
 
 
 
 
 
2309
  animated = animated == undefined ? 1000: Number(animated);
2310
  if (animated) {
2311
  animatedMove.pitch = {
2312
  'startTime': Date.now(),
2313
  'startPosition': config.pitch,
2314
  'endPosition': pitch,
2315
- 'duration': animated,
2316
- 'callback': callback,
2317
- 'callbackArgs': callbackArgs
2318
- }
2319
  } else {
2320
  config.pitch = pitch;
2321
  }
@@ -2367,23 +2523,29 @@ this.getYaw = function() {
2367
  * @returns {Viewer} `this`
2368
  */
2369
  this.setYaw = function(yaw, animated, callback, callbackArgs) {
 
 
 
 
 
 
2370
  animated = animated == undefined ? 1000: Number(animated);
2371
- yaw = ((yaw + 180) % 360) - 180 // Keep in bounds
2372
  if (animated) {
2373
  // Animate in shortest direction
2374
  if (config.yaw - yaw > 180)
2375
- yaw += 360
2376
  else if (yaw - config.yaw > 180)
2377
- yaw -= 360
2378
 
2379
  animatedMove.yaw = {
2380
  'startTime': Date.now(),
2381
  'startPosition': config.yaw,
2382
  'endPosition': yaw,
2383
- 'duration': animated,
2384
- 'callback': callback,
2385
- 'callbackArgs': callbackArgs
2386
- }
2387
  } else {
2388
  config.yaw = yaw;
2389
  }
@@ -2435,16 +2597,22 @@ this.getHfov = function() {
2435
  * @returns {Viewer} `this`
2436
  */
2437
  this.setHfov = function(hfov, animated, callback, callbackArgs) {
 
 
 
 
 
 
2438
  animated = animated == undefined ? 1000: Number(animated);
2439
  if (animated) {
2440
  animatedMove.hfov = {
2441
  'startTime': Date.now(),
2442
  'startPosition': config.hfov,
2443
  'endPosition': constrainHfov(hfov),
2444
- 'duration': animated,
2445
- 'callback': callback,
2446
- 'callbackArgs': callbackArgs
2447
- }
2448
  } else {
2449
  setHfov(hfov);
2450
  }
@@ -2490,18 +2658,22 @@ this.setHfovBounds = function(bounds) {
2490
  */
2491
  this.lookAt = function(pitch, yaw, hfov, animated, callback, callbackArgs) {
2492
  animated = animated == undefined ? 1000: Number(animated);
2493
- if (pitch !== undefined) {
2494
  this.setPitch(pitch, animated, callback, callbackArgs);
2495
  callback = undefined;
2496
  }
2497
- if (yaw !== undefined) {
2498
  this.setYaw(yaw, animated, callback, callbackArgs);
2499
  callback = undefined;
2500
  }
2501
- if (hfov !== undefined)
2502
  this.setHfov(hfov, animated, callback, callbackArgs);
 
 
 
 
2503
  return this;
2504
- }
2505
 
2506
  /**
2507
  * Returns the panorama's north offset.
@@ -2576,15 +2748,19 @@ this.setHorizonPitch = function(pitch) {
2576
 
2577
  /**
2578
  * Start auto rotation.
 
 
2579
  * @memberof Viewer
2580
  * @instance
2581
  * @param {number} [speed] - Auto rotation speed / direction. If not specified, previous value is used.
 
2582
  * @returns {Viewer} `this`
2583
  */
2584
- this.startAutoRotate = function(speed) {
2585
  speed = speed || autoRotateSpeed || 1;
 
2586
  config.autoRotate = speed;
2587
- _this.lookAt(origPitch, undefined, origHfov, 3000);
2588
  animateInit();
2589
  return this;
2590
  };
@@ -2602,6 +2778,16 @@ this.stopAutoRotate = function() {
2602
  return this;
2603
  };
2604
 
 
 
 
 
 
 
 
 
 
 
2605
  /**
2606
  * Returns the panorama renderer.
2607
  * @memberof Viewer
@@ -2626,7 +2812,7 @@ this.setUpdate = function(bool) {
2626
  else
2627
  animateInit();
2628
  return this;
2629
- }
2630
 
2631
  /**
2632
  * Calculate panorama pitch and yaw from location of mouse event.
@@ -2637,7 +2823,7 @@ this.setUpdate = function(bool) {
2637
  */
2638
  this.mouseEventToCoords = function(event) {
2639
  return mouseEventToCoords(event);
2640
- }
2641
 
2642
  /**
2643
  * Change scene being viewed.
@@ -2650,10 +2836,10 @@ this.mouseEventToCoords = function(event) {
2650
  * @returns {Viewer} `this`
2651
  */
2652
  this.loadScene = function(sceneId, pitch, yaw, hfov) {
2653
- if (loaded)
2654
  loadScene(sceneId, pitch, yaw, hfov);
2655
  return this;
2656
- }
2657
 
2658
  /**
2659
  * Get ID of current scene.
@@ -2663,7 +2849,7 @@ this.loadScene = function(sceneId, pitch, yaw, hfov) {
2663
  */
2664
  this.getScene = function() {
2665
  return config.scene;
2666
- }
2667
 
2668
  /**
2669
  * Add a new scene.
@@ -2701,7 +2887,7 @@ this.removeScene = function(sceneId) {
2701
  this.toggleFullscreen = function() {
2702
  toggleFullscreen();
2703
  return this;
2704
- }
2705
 
2706
  /**
2707
  * Get configuration of current scene.
@@ -2711,7 +2897,7 @@ this.toggleFullscreen = function() {
2711
  */
2712
  this.getConfig = function() {
2713
  return config;
2714
- }
2715
 
2716
  /**
2717
  * Get viewer's container element.
@@ -2721,7 +2907,7 @@ this.getConfig = function() {
2721
  */
2722
  this.getContainer = function() {
2723
  return container;
2724
- }
2725
 
2726
  /**
2727
  * Add a new hot spot.
@@ -2747,7 +2933,7 @@ this.addHotSpot = function(hs, sceneId) {
2747
  }
2748
  initialConfig.scenes[id].hotSpots.push(hs); // Add hot spot to config
2749
  } else {
2750
- throw 'Invalid scene ID!'
2751
  }
2752
  }
2753
  if (sceneId === undefined || config.scene == sceneId) {
@@ -2757,34 +2943,51 @@ this.addHotSpot = function(hs, sceneId) {
2757
  renderHotSpot(hs);
2758
  }
2759
  return this;
2760
- }
2761
 
2762
  /**
2763
  * Remove a hot spot.
2764
  * @memberof Viewer
2765
  * @instance
2766
  * @param {string} hotSpotId - The ID of the hot spot
 
2767
  * @returns {boolean} True if deletion is successful, else false
2768
  */
2769
- this.removeHotSpot = function(hotSpotId) {
2770
- if (!config.hotSpots)
2771
- return false;
2772
- for (var i = 0; i < config.hotSpots.length; i++) {
2773
- if (config.hotSpots[i].hasOwnProperty('id') &&
2774
- config.hotSpots[i].id === hotSpotId) {
2775
- // Delete hot spot DOM elements
2776
- var current = config.hotSpots[i].div;
2777
- while (current.parentNode != renderContainer)
2778
- current = current.parentNode;
2779
- renderContainer.removeChild(current);
2780
- delete config.hotSpots[i].div;
2781
- // Remove hot spot from configuration
2782
- config.hotSpots.splice(i, 1);
2783
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2784
  }
2785
  }
2786
- return false;
2787
- }
2788
 
2789
  /**
2790
  * This method should be called if the viewer's container is resized.
@@ -2792,8 +2995,9 @@ this.removeHotSpot = function(hotSpotId) {
2792
  * @instance
2793
  */
2794
  this.resize = function() {
2795
- onDocumentResize();
2796
- }
 
2797
 
2798
  /**
2799
  * Check if a panorama is loaded.
@@ -2803,7 +3007,7 @@ this.resize = function() {
2803
  */
2804
  this.isLoaded = function() {
2805
  return loaded;
2806
- }
2807
 
2808
  /**
2809
  * Check if device orientation control is supported.
@@ -2813,7 +3017,7 @@ this.isLoaded = function() {
2813
  */
2814
  this.isOrientationSupported = function() {
2815
  return orientationSupport || false;
2816
- }
2817
 
2818
  /**
2819
  * Stop using device orientation.
@@ -2822,7 +3026,7 @@ this.isOrientationSupported = function() {
2822
  */
2823
  this.stopOrientation = function() {
2824
  stopOrientation();
2825
- }
2826
 
2827
  /**
2828
  * Start using device orientation (does nothing if not supported).
@@ -2832,7 +3036,7 @@ this.stopOrientation = function() {
2832
  this.startOrientation = function() {
2833
  if (orientationSupport)
2834
  startOrientation();
2835
- }
2836
 
2837
  /**
2838
  * Check if device orientation control is currently activated.
@@ -2842,7 +3046,7 @@ this.startOrientation = function() {
2842
  */
2843
  this.isOrientationActive = function() {
2844
  return Boolean(orientation);
2845
- }
2846
 
2847
  /**
2848
  * Subscribe listener to specified event.
@@ -2856,7 +3060,7 @@ this.on = function(type, listener) {
2856
  externalEventListeners[type] = externalEventListeners[type] || [];
2857
  externalEventListeners[type].push(listener);
2858
  return this;
2859
- }
2860
 
2861
  /**
2862
  * Remove an event listener (or listeners).
@@ -2886,7 +3090,7 @@ this.off = function(type, listener) {
2886
  delete externalEventListeners[type];
2887
  }
2888
  return this;
2889
- }
2890
 
2891
  /**
2892
  * Fire listeners attached to specified event.
@@ -2908,15 +3112,14 @@ function fireEvent(type) {
2908
  * @memberof Viewer
2909
  */
2910
  this.destroy = function() {
 
 
 
2911
  if (renderer)
2912
- renderer.destroy()
2913
  if (listenersAdded) {
2914
- dragFix.removeEventListener('mousedown', onDocumentMouseDown, false);
2915
- dragFix.removeEventListener('dblclick', onDocumentDoubleClick, false);
2916
  document.removeEventListener('mousemove', onDocumentMouseMove, false);
2917
  document.removeEventListener('mouseup', onDocumentMouseUp, false);
2918
- container.removeEventListener('mousewheel', onDocumentMouseWheel, false);
2919
- container.removeEventListener('DOMMouseScroll', onDocumentMouseWheel, false);
2920
  container.removeEventListener('mozfullscreenchange', onFullScreenChange, false);
2921
  container.removeEventListener('webkitfullscreenchange', onFullScreenChange, false);
2922
  container.removeEventListener('msfullscreenchange', onFullScreenChange, false);
@@ -2927,19 +3130,10 @@ this.destroy = function() {
2927
  container.removeEventListener('keyup', onDocumentKeyUp, false);
2928
  container.removeEventListener('blur', clearKeys, false);
2929
  document.removeEventListener('mouseleave', onDocumentMouseUp, false);
2930
- dragFix.removeEventListener('touchstart', onDocumentTouchStart, false);
2931
- dragFix.removeEventListener('touchmove', onDocumentTouchMove, false);
2932
- dragFix.removeEventListener('touchend', onDocumentTouchEnd, false);
2933
- dragFix.removeEventListener('pointerdown', onDocumentPointerDown, false);
2934
- dragFix.removeEventListener('pointermove', onDocumentPointerMove, false);
2935
- dragFix.removeEventListener('pointerup', onDocumentPointerUp, false);
2936
- dragFix.removeEventListener('pointerleave', onDocumentPointerUp, false);
2937
  }
2938
  container.innerHTML = '';
2939
  container.classList.remove('pnlm-container');
2940
- uiContainer.classList.remove('pnlm-grab');
2941
- uiContainer.classList.remove('pnlm-grabbing');
2942
- }
2943
 
2944
  }
2945
 
1
  /*
2
  * Pannellum - An HTML5 based Panorama Viewer
3
+ * Copyright (c) 2011-2019 Matthew Petroff
4
  *
5
  * Permission is hereby granted, free of charge, to any person obtaining a copy
6
  * of this software and associated documentation files (the "Software"), to deal
49
  onPointerDownPitch = 0,
50
  keysDown = new Array(10),
51
  fullscreenActive = false,
52
+ loaded,
53
  error = false,
54
  isTimedOut = false,
55
  listenersAdded = false,
67
  externalEventListeners = {},
68
  specifiedPhotoSphereExcludes = [],
69
  update = false, // Should we update when still to render dynamic content
70
+ eps = 1e-6,
71
+ hotspotsCreated = false,
72
+ destroyed = false;
73
 
74
  var defaultConfig = {
75
  hfov: 100,
76
  minHfov: 50,
77
+ multiResMinHfov: false,
78
  maxHfov: 120,
79
  pitch: 0,
80
  minPitch: undefined,
93
  northOffset: 0,
94
  showFullscreenCtrl: true,
95
  dynamic: false,
96
+ dynamicUpdate: false,
97
  doubleClickZoom: true,
98
  keyboardZoom: true,
99
  mouseZoom: true,
103
  orientationOnByDefault: false,
104
  hotSpotDebug: false,
105
  backgroundColor: [0, 0, 0],
106
+ avoidShowingBackground: false,
107
  animationTimingFunction: timingFunction,
108
  draggable: true,
109
  disableKeyboardCtrl: false,
110
  crossOrigin: 'anonymous',
111
+ touchPanSpeedCoeffFactor: 1,
112
+ capturedKeyNumbers: [16, 17, 27, 37, 38, 39, 40, 61, 65, 68, 83, 87, 107, 109, 173, 187, 189],
113
+ friction: 0.15
114
  };
115
 
116
  // Translatable / configurable strings
120
  // Labels
121
  loadButtonLabel: 'Click to<br>Load<br>Panorama',
122
  loadingLabel: 'Loading...',
123
+ bylineLabel: 'by %s', // One substitution: author
124
 
125
  // Errors
126
  noPanoramaError: 'No panorama image was specified.',
135
  '%spx wide. Try another device.' +
136
  ' (If you\'re the author, try scaling down the image.)', // Two substitutions: image width, max image width
137
  unknownError: 'Unknown error. Check developer console.',
138
+ };
 
 
139
 
140
  // Initialize container
141
  container = typeof container === 'string' ? document.getElementById(container) : container;
 
142
  container.classList.add('pnlm-container');
143
  container.tabIndex = 0;
144
 
159
  var aboutMsg = document.createElement('span');
160
  aboutMsg.className = 'pnlm-about-msg';
161
 
162
+ //==wpvr custom rextheme link==//
163
  aboutMsg.innerHTML = '<a href="https://rextheme.com/docs/wpvr-360-panorama-and-virtual-tour-creator-for-wordpress/" target="_blank">Rextheme</a>';
164
+ //==wpvr custom rextheme link end==//
165
+
166
  uiContainer.appendChild(aboutMsg);
167
  dragFix.addEventListener('contextmenu', aboutMessage);
168
 
361
  var onError = function(e) {
362
  var a = document.createElement('a');
363
  a.href = e.target.src;
364
+ a.textContent = a.href;
365
  anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
366
  };
367
 
368
  for (i = 0; i < panoImage.length; i++) {
 
 
369
  p = config.cubeMap[i];
370
+ if (p == "null") { // support partial cubemap image with explicitly empty faces
371
+ console.log('Will use background instead of missing cubemap face ' + i);
372
+ onLoad();
373
+ } else {
374
+ if (config.basePath && !absoluteURL(p)) {
375
+ p = config.basePath + p;
376
+ }
377
+ panoImage[i].onload = onLoad;
378
+ panoImage[i].onerror = onError;
379
+ panoImage[i].src = sanitizeURL(p);
380
  }
 
381
  }
382
  } else if (config.type == 'multires') {
383
  onImageLoad();
401
  if (xhr.status != 200) {
402
  // Display error if image can't be loaded
403
  var a = document.createElement('a');
404
+ a.href = p;
405
+ a.textContent = a.href;
406
  anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
407
  }
408
  var img = this.response;
451
  if (config.draggable)
452
  uiContainer.classList.add('pnlm-grab');
453
  uiContainer.classList.remove('pnlm-grabbing');
454
+
455
+ // Properly handle switching to dynamic scenes
456
+ update = config.dynamicUpdate === true;
457
+ if (config.dynamic && update) {
458
+ panoImage = config.panorama;
459
+ onImageLoad();
460
+ }
461
  }
462
 
463
  /**
469
  function absoluteURL(url) {
470
  // From http://stackoverflow.com/a/19709846
471
  return new RegExp('^(?:[a-z]+:)?//', 'i').test(url) || url[0] == '/' || url.slice(0, 5) == 'blob:';
472
+ }
473
 
474
  /**
475
  * Create renderer and initialize event listeners once image is loaded.
492
  if (config.doubleClickZoom) {
493
  dragFix.addEventListener('dblclick', onDocumentDoubleClick, false);
494
  }
495
+ container.addEventListener('mozfullscreenchange', onFullScreenChange, false);
496
+ container.addEventListener('webkitfullscreenchange', onFullScreenChange, false);
497
+ container.addEventListener('msfullscreenchange', onFullScreenChange, false);
498
+ container.addEventListener('fullscreenchange', onFullScreenChange, false);
499
  window.addEventListener('resize', onDocumentResize, false);
500
  window.addEventListener('orientationchange', onDocumentResize, false);
501
  if (!config.disableKeyboardCtrl) {
504
  container.addEventListener('blur', clearKeys, false);
505
  }
506
  document.addEventListener('mouseleave', onDocumentMouseUp, false);
507
+ if (document.documentElement.style.pointerAction === '' &&
508
+ document.documentElement.style.touchAction === '') {
509
+ dragFix.addEventListener('pointerdown', onDocumentPointerDown, false);
510
+ dragFix.addEventListener('pointermove', onDocumentPointerMove, false);
511
+ dragFix.addEventListener('pointerup', onDocumentPointerUp, false);
512
+ dragFix.addEventListener('pointerleave', onDocumentPointerUp, false);
513
+ } else {
514
+ dragFix.addEventListener('touchstart', onDocumentTouchStart, false);
515
+ dragFix.addEventListener('touchmove', onDocumentTouchMove, false);
516
+ dragFix.addEventListener('touchend', onDocumentTouchEnd, false);
517
+ }
518
 
519
  // Deal with MS pointer events
520
  if (window.navigator.pointerEnabled)
522
  }
523
 
524
  renderInit();
525
+ setHfov(config.hfov); // possibly adapt hfov after configuration and canvas is complete; prevents empty space on top or bottom by zomming out too much
526
  setTimeout(function(){isTimedOut = true;}, 500);
527
  }
528
 
629
  infoDisplay.load.box.style.display = 'none';
630
  infoDisplay.errorMsg.style.display = 'table';
631
  error = true;
632
+ loaded = undefined;
633
  renderContainer.style.display = 'none';
634
  fireEvent('error', errorMsg);
635
  }
643
  infoDisplay.load.box.style.display = 'none';
644
  infoDisplay.errorMsg.style.display = 'none';
645
  error = false;
646
+ renderContainer.style.display = 'block';
647
  fireEvent('errorcleared');
648
  }
649
  }
675
  function mousePosition(event) {
676
  var bounds = container.getBoundingClientRect();
677
  var pos = {};
678
+ // pageX / pageY needed for iOS
679
+ pos.x = (event.clientX || event.pageX) - bounds.left;
680
+ pos.y = (event.clientY || event.pageY) - bounds.top;
681
  return pos;
682
  }
683
 
706
  var coords = mouseEventToCoords(event);
707
  console.log('Pitch: ' + coords[0] + ', Yaw: ' + coords[1] + ', Center Pitch: ' +
708
  config.pitch + ', Center Yaw: ' + config.yaw + ', HFOV: ' + config.hfov);
 
709
  }
710
+
711
+ //==Custom wpvr code to set coordinate on admin==//
712
+ var coords = mouseEventToCoords(event);
713
+ jQuery("#panodata").html('Pitch: ' + coords[0] + ', Yaw: ' + coords[1]);
714
+ jQuery( ".rex-hide-coordinates" ).removeClass( 'rex-hide-coordinates' );
715
+ //==Custom wpvr code end==//
716
+
717
  // Turn off auto-rotation if enabled
718
  stopAnimation();
719
 
907
  //
908
  // Currently this seems to *roughly* keep initial drag/pan start position close to
909
  // the user's finger while panning regardless of zoom level / config.hfov value.
910
+ var touchmovePanSpeedCoeff = (config.hfov / 360) * config.touchPanSpeedCoeffFactor;
911
 
912
  var yaw = (onPointerDownPointerX - clientX) * touchmovePanSpeedCoeff + onPointerDownYaw;
913
  speed.yaw = (yaw - config.yaw) % 360 * 0.2;
943
  */
944
  function onDocumentPointerDown(event) {
945
  if (event.pointerType == 'touch') {
946
+ // Only do something if the panorama is loaded
947
+ if (!loaded || !config.draggable)
948
+ return;
949
  pointerIDs.push(event.pointerId);
950
  pointerCoordinates.push({clientX: event.clientX, clientY: event.clientY});
951
  event.targetTouches = pointerCoordinates;
961
  */
962
  function onDocumentPointerMove(event) {
963
  if (event.pointerType == 'touch') {
964
+ if (!config.draggable)
965
+ return;
966
  for (var i = 0; i < pointerIDs.length; i++) {
967
  if (event.pointerId == pointerIDs[i]) {
968
  pointerCoordinates[i].clientX = event.clientX;
969
  pointerCoordinates[i].clientY = event.clientY;
970
  event.targetTouches = pointerCoordinates;
971
  onDocumentTouchMove(event);
972
+ event.preventDefault();
973
  return;
974
  }
975
  }
1029
  setHfov(config.hfov + event.detail * 1.5);
1030
  speed.hfov = event.detail > 0 ? 1 : -1;
1031
  }
 
1032
  animateInit();
1033
  }
1034
 
1049
  var keynumber = event.which || event.keycode;
1050
 
1051
  // Override default action for keys that are used
1052
+ if (config.capturedKeyNumbers.indexOf(keynumber) < 0)
1053
+ return;
1054
  event.preventDefault();
1055
 
1056
  // If escape key is pressed
1085
  var keynumber = event.which || event.keycode;
1086
 
1087
  // Override default action for keys that are used
1088
+ if (config.capturedKeyNumbers.indexOf(keynumber) < 0)
1089
+ return;
1090
  event.preventDefault();
1091
 
1092
  // Change key
1236
  latestInteraction = Date.now();
1237
 
1238
  // If auto-rotate
 
1239
  if (config.autoRotate) {
1240
  // Pan
1241
  if (newTime - prevTime > 0.001) {
1242
  var timeDiff = (newTime - prevTime) / 1000;
1243
+ var yawDiff = (speed.yaw / timeDiff * diff - config.autoRotate * 0.2) * timeDiff;
1244
  yawDiff = (-config.autoRotate > 0 ? 1 : -1) * Math.min(Math.abs(config.autoRotate * timeDiff), Math.abs(yawDiff));
1245
  config.yaw += yawDiff;
1246
  }
1273
  // "Inertia"
1274
  if (diff > 0 && !config.autoRotate) {
1275
  // "Friction"
1276
+ var slowDownFactor = 1 - config.friction;
1277
 
1278
  // Yaw
1279
  if (!keysDown[4] && !keysDown[5] && !keysDown[8] && !keysDown[9] && !animatedMove.yaw) {
1280
+ config.yaw += speed.yaw * diff * slowDownFactor;
1281
  }
1282
  // Pitch
1283
  if (!keysDown[2] && !keysDown[3] && !keysDown[6] && !keysDown[7] && !animatedMove.pitch) {
1284
+ config.pitch += speed.pitch * diff * slowDownFactor;
1285
  }
1286
  // Zoom
1287
  if (!keysDown[0] && !keysDown[1] && !animatedMove.hfov) {
1288
+ setHfov(config.hfov + speed.hfov * diff * slowDownFactor);
1289
  }
1290
  }
1291
 
1303
  }
1304
 
1305
  // Stop movement if opposite controls are pressed
1306
+ if (keysDown[0] && keysDown[1]) {
1307
  speed.hfov = 0;
1308
  }
1309
  if ((keysDown[2] || keysDown[6]) && (keysDown[3] || keysDown[7])) {
1328
  t.endPosition === t.startPosition) {
1329
  result = t.endPosition;
1330
  speed[axis] = 0;
 
 
1331
  delete animatedMove[axis];
 
 
1332
  }
1333
  config[axis] = result;
1334
  }
1353
  //animateInit();
1354
 
1355
  // Kludge to deal with WebKit regression: https://bugs.webkit.org/show_bug.cgi?id=93525
1356
+ onFullScreenChange('resize');
1357
  }
1358
 
1359
  /**
1373
  * @private
1374
  */
1375
  function animate() {
1376
+ if (destroyed) {
1377
+ return;
1378
+ }
1379
+
1380
  render();
1381
  if (autoRotateStart)
1382
  clearTimeout(autoRotateStart);
1400
  } else if (renderer && (renderer.isLoading() || (config.dynamic === true && update))) {
1401
  requestAnimationFrame(animate);
1402
  } else {
1403
+ fireEvent('animatefinished', {pitch: _this.getPitch(), yaw: _this.getYaw(), hfov: _this.getHfov()});
1404
  animating = false;
1405
  prevTime = undefined;
1406
  var autoRotateStartTime = config.autoRotateInactivityDelay -
1408
  if (autoRotateStartTime > 0) {
1409
  autoRotateStart = setTimeout(function() {
1410
  config.autoRotate = autoRotateSpeed;
1411
+ //==wpvr custom rotation fix==//
1412
+ //_this.lookAt(origPitch, undefined, origHfov, 3000); //main file from library
1413
+ _this.lookAt(0, undefined, origHfov, 3000); // fixed change from wpvr
1414
+ //==wpvr custom rotation fix end==//
1415
  animateInit();
1416
  }, autoRotateStartTime);
1417
  } else if (config.autoRotateInactivityDelay >= 0 && autoRotateSpeed) {
1430
  var tmpyaw;
1431
 
1432
  if (loaded) {
1433
+ var canvas = renderer.getCanvas();
1434
+
1435
+ if (config.autoRotate !== false) {
1436
+ // When auto-rotating this check needs to happen first (see issue #764)
1437
+ if (config.yaw > 180) {
1438
+ config.yaw -= 360;
1439
+ } else if (config.yaw < -180) {
1440
+ config.yaw += 360;
1441
+ }
1442
  }
1443
 
1444
  // Keep a tmp value of yaw for autoRotate comparison later
1445
  tmpyaw = config.yaw;
1446
 
1447
+ // Optionally avoid showing background (empty space) on left or right by adapting min/max yaw
1448
+ var hoffcut = 0,
1449
+ voffcut = 0;
1450
+ if (config.avoidShowingBackground) {
1451
+ var hfov2 = config.hfov / 2,
1452
+ vfov2 = Math.atan2(Math.tan(hfov2 / 180 * Math.PI), (canvas.width / canvas.height)) * 180 / Math.PI,
1453
+ transposed = config.vaov > config.haov;
1454
+ if (transposed) {
1455
+ voffcut = vfov2 * (1 - Math.min(Math.cos((config.pitch - hfov2) / 180 * Math.PI),
1456
+ Math.cos((config.pitch + hfov2) / 180 * Math.PI)));
1457
+ } else {
1458
+ hoffcut = hfov2 * (1 - Math.min(Math.cos((config.pitch - vfov2) / 180 * Math.PI),
1459
+ Math.cos((config.pitch + vfov2) / 180 * Math.PI)));
1460
+ }
1461
+ }
1462
+
1463
  // Ensure the yaw is within min and max allowed
1464
  var yawRange = config.maxYaw - config.minYaw,
1465
  minYaw = -180,
1466
  maxYaw = 180;
1467
  if (yawRange < 360) {
1468
+ minYaw = config.minYaw + config.hfov / 2 + hoffcut;
1469
+ maxYaw = config.maxYaw - config.hfov / 2 - hoffcut;
1470
  if (yawRange < config.hfov) {
1471
  // Lock yaw to average of min and max yaw when both can be seen at once
1472
  minYaw = maxYaw = (minYaw + maxYaw) / 2;
1473
  }
1474
+ config.yaw = Math.max(minYaw, Math.min(maxYaw, config.yaw));
1475
+ }
1476
+
1477
+ if (!(config.autoRotate !== false)) {
1478
+ // When not auto-rotating, this check needs to happen after the
1479
+ // previous check (see issue #698)
1480
+ if (config.yaw > 180) {
1481
+ config.yaw -= 360;
1482
+ } else if (config.yaw < -180) {
1483
+ config.yaw += 360;
1484
+ }
1485
  }
 
1486
 
1487
  // Check if we autoRotate in a limited by min and max yaw
1488
  // If so reverse direction
1489
+ if (config.autoRotate !== false && tmpyaw != config.yaw &&
1490
+ prevTime !== undefined) { // this condition prevents changing the direction initially
1491
  config.autoRotate *= -1;
1492
  }
1493
 
1494
  // Ensure the calculated pitch is within min and max allowed
 
1495
  var vfov = 2 * Math.atan(Math.tan(config.hfov / 180 * Math.PI * 0.5) /
1496
  (canvas.width / canvas.height)) / Math.PI * 180;
1497
  var minPitch = config.minPitch + vfov / 2,
1546
  this.x*q.w + this.w*q.x + this.y*q.z - this.z*q.y,
1547
  this.y*q.w + this.w*q.y + this.z*q.x - this.x*q.z,
1548
  this.z*q.w + this.w*q.z + this.x*q.y - this.y*q.x);
1549
+ };
1550
 
1551
  /**
1552
  * Converts quaternion to Euler angles.
1560
  psi = Math.atan2(2 * (this.w * this.z + this.x * this.y),
1561
  1 - 2 * (this.y * this.y + this.z * this.z));
1562
  return [phi, theta, psi];
1563
+ };
1564
 
1565
  /**
1566
  * Converts device orientation API Tait-Bryan angles to a quaternion.
1697
  }
1698
  loaded = true;
1699
 
 
 
1700
  animateInit();
1701
+
1702
+ fireEvent('load');
1703
  }
1704
 
1705
  /**
1713
  hs.yaw = Number(hs.yaw) || 0;
1714
 
1715
  var div = document.createElement('div');
1716
+ div.className = 'pnlm-hotspot-base';
1717
  if (hs.cssClass)
1718
  div.className += ' ' + hs.cssClass;
1719
  else
1726
  var a;
1727
  if (hs.video) {
1728
  var video = document.createElement('video'),
1729
+ vidp = hs.video;
1730
+ if (config.basePath && !absoluteURL(vidp))
1731
+ vidp = config.basePath + vidp;
1732
+ video.src = sanitizeURL(vidp);
1733
  video.controls = true;
1734
  video.style.width = hs.width + 'px';
1735
  renderContainer.appendChild(div);
1736
  span.appendChild(video);
1737
  } else if (hs.image) {
1738
+ var imgp = hs.image;
1739
+ if (config.basePath && !absoluteURL(imgp))
1740
+ imgp = config.basePath + imgp;
1741
  a = document.createElement('a');
1742
+ a.href = sanitizeURL(hs.URL ? hs.URL : imgp);
1743
  a.target = '_blank';
1744
  span.appendChild(a);
1745
  var image = document.createElement('img');
1746
+ image.src = sanitizeURL(imgp);
1747
  image.style.width = hs.width + 'px';
1748
  image.style.paddingTop = '5px';
1749
  renderContainer.appendChild(div);
1751
  span.style.maxWidth = 'initial';
1752
  } else if (hs.URL) {
1753
  a = document.createElement('a');
1754
+ a.href = sanitizeURL(hs.URL);
1755
+ if (hs.attributes) {
1756
+ for (var key in hs.attributes) {
1757
+ a.setAttribute(key, hs.attributes[key]);
1758
+ }
1759
+ } else {
1760
+ a.target = '_blank';
1761
+ }
1762
  renderContainer.appendChild(a);
1763
+ div.className += ' pnlm-pointer';
1764
+ span.className += ' pnlm-pointer';
1765
  a.appendChild(div);
1766
  } else {
1767
  if (hs.sceneId) {
1772
  }
1773
  return false;
1774
  };
1775
+ div.className += ' pnlm-pointer';
1776
+ span.className += ' pnlm-pointer';
1777
  }
1778
  renderContainer.appendChild(div);
1779
  }
1791
  div.addEventListener('click', function(e) {
1792
  hs.clickHandlerFunc(e, hs.clickHandlerArgs);
1793
  }, 'false');
1794
+ div.className += ' pnlm-pointer';
1795
+ span.className += ' pnlm-pointer';
1796
  }
1797
  hs.div = div;
1798
+ }
1799
 
1800
  /**
1801
  * Creates hot spot elements for the current scene.
1828
  if (hs) {
1829
  for (var i = 0; i < hs.length; i++) {
1830
  var current = hs[i].div;
1831
+ if (current) {
1832
+ while (current.parentNode && current.parentNode != renderContainer) {
1833
+ current = current.parentNode;
1834
+ }
1835
+ renderContainer.removeChild(current);
1836
  }
 
1837
  delete hs[i].div;
1838
  }
1839
  }
1875
  coord[1] += (canvasHeight - hs.div.offsetHeight) / 2;
1876
  var transform = 'translate(' + coord[0] + 'px, ' + coord[1] +
1877
  'px) translateZ(9999px) rotate(' + config.roll + 'deg)';
1878
+ if (hs.scale) {
1879
+ transform += ' scale(' + (origHfov/config.hfov) / z + ')';
1880
+ }
1881
  hs.div.style.webkitTransform = transform;
1882
  hs.div.style.MozTransform = transform;
1883
  hs.div.style.transform = transform;
1986
  p = config.basePath + p;
1987
  preview = document.createElement('div');
1988
  preview.className = 'pnlm-preview-img';
1989
+ preview.style.backgroundImage = "url('" + sanitizeURLForCss(p) + "')";
1990
  renderContainer.appendChild(preview);
1991
  }
1992
 
2022
  break;
2023
 
2024
  case 'author':
2025
+ var authorText = escapeHTML(config[key]);
2026
+ if (config.authorURL) {
2027
+ var authorLink = document.createElement('a');
2028
+ authorLink.href = sanitizeURL(config['authorURL']);
2029
+ authorLink.target = '_blank';
2030
+ authorLink.innerHTML = escapeHTML(config[key]);
2031
+ authorText = authorLink.outerHTML;
2032
+ }
2033
+ infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', authorText);
2034
  infoDisplay.container.style.display = 'inline';
2035
  break;
2036
 
2037
  case 'fallback':
2038
+ var link = document.createElement('a');
2039
+ link.href = sanitizeURL(config[key]);
2040
+ link.target = '_blank';
2041
+ link.textContent = 'Click here to view this panorama in an alternative viewer.';
2042
+ var message = document.createElement('p');
2043
+ message.textContent = 'Your browser does not support WebGL.';
2044
+ message.appendChild(document.createElement('br'));
2045
+ message.appendChild(link);
2046
+ infoDisplay.errorMsg.innerHTML = ''; // Removes all children nodes
2047
+ infoDisplay.errorMsg.appendChild(message);
2048
  break;
2049
 
2050
  case 'hfov':
2162
  * Event handler for fullscreen changes.
2163
  * @private
2164
  */
2165
+ function onFullScreenChange(resize) {
2166
+ if (document.fullscreenElement || document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement) {
2167
  controls.fullscreen.classList.add('pnlm-fullscreen-toggle-button-active');
2168
  fullscreenActive = true;
2169
  } else {
2170
  controls.fullscreen.classList.remove('pnlm-fullscreen-toggle-button-active');
2171
  fullscreenActive = false;
2172
  }
2173
+ if (resize !== 'resize')
2174
+ fireEvent('fullscreenchange', fullscreenActive);
2175
  // Resize renderer (deal with browser quirks and fixes #155)
2176
  renderer.resize();
2177
  setHfov(config.hfov);
2209
  function constrainHfov(hfov) {
2210
  // Keep field of view within bounds
2211
  var minHfov = config.minHfov;
2212
+ if (config.type == 'multires' && renderer && !config.multiResMinHfov) {
2213
  minHfov = Math.min(minHfov, renderer.getCanvas().width / (config.multiRes.cubeResolution / 90 * 0.9));
2214
  }
2215
  if (minHfov > config.maxHfov) {
2216
  // Don't change view if bounds don't make sense
2217
+ console.log('HFOV bounds do not make sense (minHfov > maxHfov).');
2218
  return config.hfov;
2219
+ }
2220
+ var newHfov = config.hfov;
2221
+ if (hfov < minHfov) {
2222
+ newHfov = minHfov;
2223
  } else if (hfov > config.maxHfov) {
2224
+ newHfov = config.maxHfov;
2225
  } else {
2226
+ newHfov = hfov;
2227
+ }
2228
+ // Optionally avoid showing background (empty space) on top or bottom by adapting newHfov
2229
+ if (config.avoidShowingBackground && renderer) {
2230
+ var canvas = renderer.getCanvas();
2231
+ newHfov = Math.min(newHfov,
2232
+ Math.atan(Math.tan((config.maxPitch - config.minPitch) / 360 * Math.PI) /
2233
+ canvas.height * canvas.width) * 360 / Math.PI);
2234
  }
2235
+ return newHfov;
2236
  }
2237
 
2238
  /**
2242
  */
2243
  function setHfov(hfov) {
2244
  config.hfov = constrainHfov(hfov);
2245
+ fireEvent('zoomchange', config.hfov);
2246
  }
2247
 
2248
  /**
2264
  // since it is a new scene and the error from previous maybe because of lacking
2265
  // memory etc and not because of a lack of WebGL support etc
2266
  clearError();
2267
+ loaded = false;
2268
 
2269
  controls.load.style.display = 'none';
2270
  infoDisplay.load.box.style.display = 'inline';
2281
  * @param {boolean} [fadeDone] - If `true`, fade setup is skipped.
2282
  */
2283
  function loadScene(sceneId, targetPitch, targetYaw, targetHfov, fadeDone) {
2284
+ if (!loaded)
2285
+ fadeDone = true; // Don't try to fade when there isn't a scene loaded
2286
  loaded = false;
2287
  animatedMove = {};
2288
 
2364
  * @private
2365
  */
2366
  function startOrientation() {
2367
+ if (typeof DeviceMotionEvent.requestPermission === 'function') {
2368
+ DeviceOrientationEvent.requestPermission().then(response => {
2369
+ if (response == 'granted') {
2370
+ orientation = 1;
2371
+ window.addEventListener('deviceorientation', orientationListener);
2372
+ controls.orientation.classList.add('pnlm-orientation-button-active');
2373
+ }
2374
+ });
2375
+ } else {
2376
+ orientation = 1;
2377
+ window.addEventListener('deviceorientation', orientationListener);
2378
+ controls.orientation.classList.add('pnlm-orientation-button-active');
2379
+ }
2380
  }
2381
 
2382
  /**
2397
  .split('\n').join('<br>'); // Allow line breaks
2398
  }
2399
 
2400
+ /**
2401
+ * Removes possibility of XSS attacks with URLs.
2402
+ * The URL cannot be of protocol 'javascript'.
2403
+ * @private
2404
+ * @param {string} url - URL to sanitize
2405
+ * @returns {string} Sanitized URL
2406
+ */
2407
+ function sanitizeURL(url) {
2408
+ if (url.trim().toLowerCase().indexOf('javascript:') === 0) {
2409
+ return 'about:blank';
2410
+ }
2411
+ return url;
2412
+ }
2413
+
2414
+ /**
2415
+ * Removes possibility of XSS atacks with URLs for CSS.
2416
+ * The URL will be sanitized with `sanitizeURL()` and single quotes
2417
+ * and double quotes escaped.
2418
+ * @private
2419
+ * @param {string} url - URL to sanitize
2420
+ * @returns {string} Sanitized URL
2421
+ */
2422
+ function sanitizeURLForCss(url) {
2423
+ return sanitizeURL(url)
2424
+ .replace(/"/g, '%22')
2425
+ .replace(/'/g, '%27');
2426
+ }
2427
+
2428
  /**
2429
  * Checks whether or not a panorama is loaded.
2430
  * @memberof Viewer
2432
  * @returns {boolean} `true` if a panorama is loaded, else `false`
2433
  */
2434
  this.isLoaded = function() {
2435
+ return Boolean(loaded);
2436
  };
2437
 
2438
  /**
2456
  * @returns {Viewer} `this`
2457
  */
2458
  this.setPitch = function(pitch, animated, callback, callbackArgs) {
2459
+ latestInteraction = Date.now();
2460
+ if (Math.abs(pitch - config.pitch) <= eps) {
2461
+ if (typeof callback == 'function')
2462
+ callback(callbackArgs);
2463
+ return this;
2464
+ }
2465
  animated = animated == undefined ? 1000: Number(animated);
2466
  if (animated) {
2467
  animatedMove.pitch = {
2468
  'startTime': Date.now(),
2469
  'startPosition': config.pitch,
2470
  'endPosition': pitch,
2471
+ 'duration': animated
2472
+ };
2473
+ if (typeof callback == 'function')
2474
+ setTimeout(function(){callback(callbackArgs);}, animated);
2475
  } else {
2476
  config.pitch = pitch;
2477
  }
2523
  * @returns {Viewer} `this`
2524
  */
2525
  this.setYaw = function(yaw, animated, callback, callbackArgs) {
2526
+ latestInteraction = Date.now();
2527
+ if (Math.abs(yaw - config.yaw) <= eps) {
2528
+ if (typeof callback == 'function')
2529
+ callback(callbackArgs);
2530
+ return this;
2531
+ }
2532
  animated = animated == undefined ? 1000: Number(animated);
2533
+ yaw = ((yaw + 180) % 360) - 180; // Keep in bounds
2534
  if (animated) {
2535
  // Animate in shortest direction
2536
  if (config.yaw - yaw > 180)
2537
+ yaw += 360;
2538
  else if (yaw - config.yaw > 180)
2539
+ yaw -= 360;
2540
 
2541
  animatedMove.yaw = {
2542
  'startTime': Date.now(),
2543
  'startPosition': config.yaw,
2544
  'endPosition': yaw,
2545
+ 'duration': animated
2546
+ };
2547
+ if (typeof callback == 'function')
2548
+ setTimeout(function(){callback(callbackArgs);}, animated);
2549
  } else {
2550
  config.yaw = yaw;
2551
  }
2597
  * @returns {Viewer} `this`
2598
  */
2599
  this.setHfov = function(hfov, animated, callback, callbackArgs) {
2600
+ latestInteraction = Date.now();
2601
+ if (Math.abs(hfov - config.hfov) <= eps) {
2602
+ if (typeof callback == 'function')
2603
+ callback(callbackArgs);
2604
+ return this;
2605
+ }
2606
  animated = animated == undefined ? 1000: Number(animated);
2607
  if (animated) {
2608
  animatedMove.hfov = {
2609
  'startTime': Date.now(),
2610
  'startPosition': config.hfov,
2611
  'endPosition': constrainHfov(hfov),
2612
+ 'duration': animated
2613
+ };
2614
+ if (typeof callback == 'function')
2615
+ setTimeout(function(){callback(callbackArgs);}, animated);
2616
  } else {
2617
  setHfov(hfov);
2618
  }
2658
  */
2659
  this.lookAt = function(pitch, yaw, hfov, animated, callback, callbackArgs) {
2660
  animated = animated == undefined ? 1000: Number(animated);
2661
+ if (pitch !== undefined && Math.abs(pitch - config.pitch) > eps) {
2662
  this.setPitch(pitch, animated, callback, callbackArgs);
2663
  callback = undefined;
2664
  }
2665
+ if (yaw !== undefined && Math.abs(yaw - config.yaw) > eps) {
2666
  this.setYaw(yaw, animated, callback, callbackArgs);
2667
  callback = undefined;
2668
  }
2669
+ if (hfov !== undefined && Math.abs(hfov - config.hfov) > eps) {
2670
  this.setHfov(hfov, animated, callback, callbackArgs);
2671
+ callback = undefined;
2672
+ }
2673
+ if (typeof callback == 'function')
2674
+ callback(callbackArgs);
2675
  return this;
2676
+ };
2677
 
2678
  /**
2679
  * Returns the panorama's north offset.
2748
 
2749
  /**
2750
  * Start auto rotation.
2751
+ *
2752
+ * Before starting rotation, the viewer is panned to `pitch`.
2753
  * @memberof Viewer
2754
  * @instance
2755
  * @param {number} [speed] - Auto rotation speed / direction. If not specified, previous value is used.
2756
+ * @param {number} [pitch] - The pitch to rotate at. If not specified, inital pitch is used.
2757
  * @returns {Viewer} `this`
2758
  */
2759
+ this.startAutoRotate = function(speed, pitch) {
2760
  speed = speed || autoRotateSpeed || 1;
2761
+ pitch = pitch === undefined ? origPitch : pitch;
2762
  config.autoRotate = speed;
2763
+ _this.lookAt(pitch, undefined, origHfov, 3000);
2764
  animateInit();
2765
  return this;
2766
  };
2778
  return this;
2779
  };
2780
 
2781
+ /**
2782
+ * Stops all movement.
2783
+ * @memberof Viewer
2784
+ * @instance
2785
+ */
2786
+ this.stopMovement = function() {
2787
+ stopAnimation();
2788
+ speed = {'yaw': 0, 'pitch': 0, 'hfov': 0};
2789
+ };
2790
+
2791
  /**
2792
  * Returns the panorama renderer.
2793
  * @memberof Viewer
2812
  else
2813
  animateInit();
2814
  return this;
2815
+ };
2816
 
2817
  /**
2818
  * Calculate panorama pitch and yaw from location of mouse event.
2823
  */
2824
  this.mouseEventToCoords = function(event) {
2825
  return mouseEventToCoords(event);
2826
+ };
2827
 
2828
  /**
2829
  * Change scene being viewed.
2836
  * @returns {Viewer} `this`
2837
  */
2838
  this.loadScene = function(sceneId, pitch, yaw, hfov) {
2839
+ if (loaded !== false)
2840
  loadScene(sceneId, pitch, yaw, hfov);
2841
  return this;
2842
+ };
2843
 
2844
  /**
2845
  * Get ID of current scene.
2849
  */
2850
  this.getScene = function() {
2851
  return config.scene;
2852
+ };
2853
 
2854
  /**
2855
  * Add a new scene.
2887
  this.toggleFullscreen = function() {
2888