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
  toggleFullscreen();
2889
  return this;
2890
+ };
2891
 
2892
  /**
2893
  * Get configuration of current scene.
2897
  */
2898
  this.getConfig = function() {
2899
  return config;
2900
+ };
2901
 
2902
  /**
2903
  * Get viewer's container element.
2907
  */
2908
  this.getContainer = function() {
2909
  return container;
2910
+ };
2911
 
2912
  /**
2913
  * Add a new hot spot.
2933
  }
2934
  initialConfig.scenes[id].hotSpots.push(hs); // Add hot spot to config
2935
  } else {
2936
+ throw 'Invalid scene ID!';
2937
  }
2938
  }
2939
  if (sceneId === undefined || config.scene == sceneId) {
2943
  renderHotSpot(hs);
2944
  }
2945
  return this;
2946
+ };
2947
 
2948
  /**
2949
  * Remove a hot spot.
2950
  * @memberof Viewer
2951
  * @instance
2952
  * @param {string} hotSpotId - The ID of the hot spot
2953
+ * @param {string} [sceneId] - Removes hot spot from specified scene if provided, else from current scene
2954
  * @returns {boolean} True if deletion is successful, else false
2955
  */
2956
+ this.removeHotSpot = function(hotSpotId, sceneId) {
2957
+ if (sceneId === undefined || config.scene == sceneId) {
2958
+ if (!config.hotSpots)
2959
+ return false;
2960
+ for (var i = 0; i < config.hotSpots.length; i++) {
2961
+ if (config.hotSpots[i].hasOwnProperty('id') &&
2962
+ config.hotSpots[i].id === hotSpotId) {
2963
+ // Delete hot spot DOM elements
2964
+ var current = config.hotSpots[i].div;
2965
+ while (current.parentNode != renderContainer)
2966
+ current = current.parentNode;
2967
+ renderContainer.removeChild(current);
2968
+ delete config.hotSpots[i].div;
2969
+ // Remove hot spot from configuration
2970
+ config.hotSpots.splice(i, 1);
2971
+ return true;
2972
+ }
2973
+ }
2974
+ } else {
2975
+ if (initialConfig.scenes.hasOwnProperty(sceneId)) {
2976
+ if (!initialConfig.scenes[sceneId].hasOwnProperty('hotSpots'))
2977
+ return false;
2978
+ for (var j = 0; j < initialConfig.scenes[sceneId].hotSpots.length; j++) {
2979
+ if (initialConfig.scenes[sceneId].hotSpots[j].hasOwnProperty('id') &&
2980
+ initialConfig.scenes[sceneId].hotSpots[j].id === hotSpotId) {
2981
+ // Remove hot spot from configuration
2982
+ initialConfig.scenes[sceneId].hotSpots.splice(j, 1);
2983
+ return true;
2984
+ }
2985
+ }
2986
+ } else {
2987
+ return false;
2988
  }
2989
  }
2990
+ };
 
2991
 
2992
  /**
2993
  * This method should be called if the viewer's container is resized.
2995
  * @instance
2996
  */
2997
  this.resize = function() {
2998
+ if (renderer)
2999
+ onDocumentResize();
3000
+ };
3001
 
3002
  /**
3003
  * Check if a panorama is loaded.
3007
  */
3008
  this.isLoaded = function() {
3009
  return loaded;
3010
+ };
3011
 
3012
  /**
3013
  * Check if device orientation control is supported.
3017
  */
3018
  this.isOrientationSupported = function() {
3019
  return orientationSupport || false;
3020
+ };
3021
 
3022
  /**
3023
  * Stop using device orientation.
3026
  */
3027
  this.stopOrientation = function() {
3028
  stopOrientation();
3029
+ };
3030
 
3031
  /**
3032
  * Start using device orientation (does nothing if not supported).
3036
  this.startOrientation = function() {
3037
  if (orientationSupport)
3038
  startOrientation();
3039
+ };
3040
 
3041
  /**
3042
  * Check if device orientation control is currently activated.
3046
  */
3047
  this.isOrientationActive = function() {
3048
  return Boolean(orientation);
3049
+ };
3050
 
3051
  /**
3052
  * Subscribe listener to specified event.
3060
  externalEventListeners[type] = externalEventListeners[type] || [];
3061
  externalEventListeners[type].push(listener);
3062
  return this;
3063
+ };
3064
 
3065
  /**
3066
  * Remove an event listener (or listeners).
3090
  delete externalEventListeners[type];
3091
  }
3092
  return this;
3093
+ };
3094
 
3095
  /**
3096
  * Fire listeners attached to specified event.
3112
  * @memberof Viewer
3113
  */
3114
  this.destroy = function() {
3115
+ destroyed = true;
3116
+ clearTimeout(autoRotateStart);
3117
+
3118
  if (renderer)
3119
+ renderer.destroy();
3120
  if (listenersAdded) {
 
 
3121
  document.removeEventListener('mousemove', onDocumentMouseMove, false);
3122
  document.removeEventListener('mouseup', onDocumentMouseUp, false);
 
 
3123
  container.removeEventListener('mozfullscreenchange', onFullScreenChange, false);
3124
  container.removeEventListener('webkitfullscreenchange', onFullScreenChange, false);
3125
  container.removeEventListener('msfullscreenchange', onFullScreenChange, false);
3130
  container.removeEventListener('keyup', onDocumentKeyUp, false);
3131
  container.removeEventListener('blur', clearKeys, false);
3132
  document.removeEventListener('mouseleave', onDocumentMouseUp, false);
 
 
 
 
 
 
 
3133
  }
3134
  container.innerHTML = '';
3135
  container.classList.remove('pnlm-container');
3136
+ };
 
 
3137
 
3138
  }
3139
 
admin/lib/pannellum/src/js/videojs-pannellum-plugin.js CHANGED
@@ -52,4 +52,4 @@ registerPlugin('pannellum', function(config) {
52
  });
53
  });
54
 
55
- })(document, videojs, pannellum);
52
  });
53
  });
54
 
55
+ })(document, videojs, pannellum);
admin/partials/wpvr-meta-box-builder-display.php CHANGED
@@ -129,14 +129,14 @@ if (isset($postdata['panodata'])) {
129
  $scene_author = sanitize_text_field($panoscenes["scene-author"]);
130
  }
131
 
132
- $default_scene_pitch = '';
133
  if (isset($panoscenes["scene-pitch"])) {
134
- $default_scene_pitch = $panoscenes["scene-pitch"];
135
  }
136
 
137
- $default_scene_yaw = '';
138
  if (isset($panoscenes["scene-yaw"])) {
139
- $default_scene_yaw = $panoscenes["scene-yaw"];
140
  }
141
 
142
  $scene_max_pitch = '';
@@ -344,13 +344,14 @@ if (isset($postdata['panodata'])) {
344
  </ul>
345
 
346
  <div class="scene-gallery vrowl-carousel">
347
-
348
  </div>
349
  </div>
350
 
351
 
352
 
353
  <script>
 
354
  var response = <?php echo $response; ?>;
355
  var scenes = response[1];
356
 
@@ -375,6 +376,15 @@ if (isset($postdata['panodata'])) {
375
 
376
  if (response[1]['scenes'] != "") {
377
  var panoshow = pannellum.viewer(response[0]["panoid"], scenes);
 
 
 
 
 
 
 
 
 
378
  var touchtime = 0;
379
  if (scenes) {
380
  $.each(scenes.scenes, function (key, val) {
129
  $scene_author = sanitize_text_field($panoscenes["scene-author"]);
130
  }
131
 
132
+ $default_scene_pitch = null;
133
  if (isset($panoscenes["scene-pitch"])) {
134
+ $default_scene_pitch = (float)$panoscenes["scene-pitch"];
135
  }
136
 
137
+ $default_scene_yaw = null;
138
  if (isset($panoscenes["scene-yaw"])) {
139
+ $default_scene_yaw = (float)$panoscenes["scene-yaw"];
140
  }
141
 
142
  $scene_max_pitch = '';
344
  </ul>
345
 
346
  <div class="scene-gallery vrowl-carousel">
347
+
348
  </div>
349
  </div>
350
 
351
 
352
 
353
  <script>
354
+
355
  var response = <?php echo $response; ?>;
356
  var scenes = response[1];
357
 
376
 
377
  if (response[1]['scenes'] != "") {
378
  var panoshow = pannellum.viewer(response[0]["panoid"], scenes);
379
+ if (scenes.autoRotate) {
380
+ panoshow.on('load', function (){
381
+ setTimeout(function(){ panoshow.startAutoRotate(scenes.autoRotate, 0); }, 3000);
382
+ });
383
+ panoshow.on('scenechange', function (){
384
+ setTimeout(function(){ panoshow.startAutoRotate(scenes.autoRotate, 0); }, 3000);
385
+ });
386
+ }
387
+
388
  var touchtime = 0;
389
  if (scenes) {
390
  $.each(scenes.scenes, function (key, val) {
public/class-wpvr-public.php CHANGED
@@ -122,7 +122,7 @@ class Wpvr_Public {
122
  * @since 1.0.0
123
  */
124
  public function wpvr_shortcode( $atts ) {
125
-
126
  extract(
127
  shortcode_atts(
128
  array(
@@ -165,7 +165,7 @@ class Wpvr_Public {
165
  $html .= '<div style="text-align: center; max-width:100%; height:auto; margin: 0 auto;">';
166
  $html .= '<iframe width="'.trim($width,'px').'" height="'.trim($height,'px').'" src="https://www.youtube.com/embed/'.$foundid.'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
167
  $html .= '</div>';
168
-
169
  } elseif (strpos($videourl, 'vimeo') > 0) {
170
 
171
  $explodeid = '';
@@ -176,7 +176,7 @@ class Wpvr_Public {
176
  $html .= '<div style="text-align: center; max-width:100%; height:auto; margin: 0 auto;">';
177
  $html .= '<iframe src="https://player.vimeo.com/video/'.$foundid.'" width="'.trim($width,'px').'" height="'.trim($height,'px').'" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
178
  $html .= '</div>';
179
-
180
  } else {
181
  $html = '';
182
  $html .= '<div id="pano'.$id.'" class="pano-wrap" style="width: '.$width.'; height: '.$height.'; margin: 0 auto;">';
@@ -190,7 +190,7 @@ class Wpvr_Public {
190
  $html .= '</script>';
191
  $html .= '</div>';
192
  }
193
-
194
  return $html;
195
  }
196
 
@@ -213,13 +213,13 @@ class Wpvr_Public {
213
  if (isset($postdata['compass'])) {
214
  $compass = $postdata['compass'];
215
  }
216
-
217
  $autoload = false;
218
 
219
  if (isset($postdata['autoLoad'])) {
220
  $autoload = $postdata['autoLoad'];
221
  }
222
-
223
  $default_scene = '';
224
  if (isset($postdata['defaultscene'])) {
225
  $default_scene = $postdata['defaultscene'];
@@ -242,12 +242,12 @@ class Wpvr_Public {
242
  if (isset($postdata["autoRotateStopDelay"])) {
243
  $autorotationstopdelay = $postdata["autoRotateStopDelay"];
244
  }
245
-
246
  $scene_fade_duration = '';
247
  if (isset($postdata['scenefadeduration'])) {
248
  $scene_fade_duration = $postdata['scenefadeduration'];
249
  }
250
-
251
  $panodata = '';
252
  if (isset($postdata['panodata'])) {
253
  $panodata = $postdata['panodata'];
@@ -265,50 +265,50 @@ class Wpvr_Public {
265
  if (isset($panoscenes["scene-ititle"])) {
266
  $scene_ititle = sanitize_text_field($panoscenes["scene-ititle"]);
267
  }
268
-
269
  $scene_author = '';
270
  if (isset($panoscenes["scene-author"])) {
271
  $scene_author = sanitize_text_field($panoscenes["scene-author"]);
272
  }
273
 
274
- $default_scene_pitch = '';
275
  if (isset($panoscenes["scene-pitch"])) {
276
- $default_scene_pitch = $panoscenes["scene-pitch"];
277
  }
278
-
279
- $default_scene_yaw = '';
280
  if (isset($panoscenes["scene-yaw"])) {
281
- $default_scene_yaw = $panoscenes["scene-yaw"];
282
  }
283
 
284
  $scene_max_pitch = '';
285
  if (isset($panoscenes["scene-maxpitch"])) {
286
  $scene_max_pitch = (float)$panoscenes["scene-maxpitch"];
287
  }
288
-
289
 
290
  $scene_min_pitch = '';
291
  if (isset($panoscenes["scene-minpitch"])) {
292
  $scene_min_pitch = (float)$panoscenes["scene-minpitch"];
293
  }
294
-
295
 
296
  $scene_max_yaw = '';
297
  if (isset($panoscenes["scene-maxyaw"])) {
298
  $scene_max_yaw = (float)$panoscenes["scene-maxyaw"];
299
  }
300
-
301
 
302
  $scene_min_yaw = '';
303
  if (isset($panoscenes["scene-minyaw"])) {
304
  $scene_min_yaw = (float)$panoscenes["scene-minyaw"];
305
  }
306
-
307
  $default_zoom = 100;
308
  if (isset($panoscenes["scene-zoom"])) {
309
  $default_zoom = $panoscenes["scene-zoom"];
310
  }
311
-
312
  if (!empty($default_zoom)) {
313
  $default_zoom = (int)$default_zoom;
314
  }
@@ -320,7 +320,7 @@ class Wpvr_Public {
320
  if (isset($panoscenes["scene-maxzoom"])) {
321
  $max_zoom = $panoscenes["scene-maxzoom"];
322
  }
323
-
324
  if (!empty($max_zoom)) {
325
  $max_zoom = (int)$max_zoom;
326
  }
@@ -332,18 +332,18 @@ class Wpvr_Public {
332
  if (isset($panoscenes["scene-minzoom"])) {
333
  $min_zoom = $panoscenes["scene-minzoom"];
334
  }
335
-
336
  if (!empty($min_zoom)) {
337
  $min_zoom = (int)$min_zoom;
338
  }
339
  else {
340
  $min_zoom = 50;
341
- }
342
  $hotspot_datas = array();
343
  if (isset($panoscenes["hotspot-list"])) {
344
  $hotspot_datas = $panoscenes["hotspot-list"];
345
  }
346
-
347
  $hotspots = array();
348
  foreach ($hotspot_datas as $hotspot_data) {
349
 
@@ -351,7 +351,7 @@ class Wpvr_Public {
351
  if( $status !== false && $status == 'valid' ) {
352
  if (isset($hotspot_data["hotspot-customclass-pro"]) && $hotspot_data["hotspot-customclass-pro"] != 'none') {
353
  $hotspot_data["hotspot-customclass"] = $hotspot_data["hotspot-customclass-pro"];
354
- $hotspoticoncolor = $hotspot_data["hotspot-customclass-color-icon-value"];
355
  }
356
  if (isset($hotspot_data['hotspot-blink'])) {
357
  $hotspotblink = $hotspot_data['hotspot-blink'];
@@ -364,8 +364,8 @@ class Wpvr_Public {
364
  $hotspot_scene_yaw = '';
365
  if (isset($hotspot_data["hotspot-scene-yaw"])) {
366
  $hotspot_scene_yaw = $hotspot_data["hotspot-scene-yaw"];
367
- }
368
-
369
  $hotspot_info = array(
370
  "text"=>$hotspot_data["hotspot-title"],
371
  "pitch"=>$hotspot_data["hotspot-pitch"],
@@ -440,13 +440,13 @@ class Wpvr_Public {
440
  unset($scene_info['minHfov']);
441
  }
442
  }
443
-
444
  $scene_array = array();
445
  $scene_array = array(
446
  $panoscenes["scene-id"]=>$scene_info
447
  );
448
  $scene_data[$panoscenes["scene-id"]] = $scene_info;
449
- }
450
  }
451
 
452
  $pano_id_array = array();
@@ -491,10 +491,10 @@ class Wpvr_Public {
491
  $html .= '#'.$panoid.' div.pnlm-hotspot-base.fas,
492
  #'.$panoid.' div.pnlm-hotspot-base.fab,
493
  #'.$panoid.' div.pnlm-hotspot-base.far {
494
- display: block !important;
495
  background-color: '.$hotspoticoncolor.';
496
  color: '.$foreground_color.';
497
- border-radius: 100%;
498
  width: 30px;
499
  height: 30px;
500
  animation: icon-pulse'.$panoid.' 1.5s infinite cubic-bezier(.25, 0, 0, 1);
@@ -515,23 +515,23 @@ class Wpvr_Public {
515
  100% {
516
  box-shadow: 0 0 0 10px rgba('.$pulse_color[0].', 0);
517
  }
518
- }';
519
- }
520
-
521
  $status = get_option( 'wpvr_edd_license_status' );
522
  if( $status !== false && $status == 'valid' ) {
523
  if (!$gyro) {
524
  $html .= '#'.$panoid.' div.pnlm-orientation-button {
525
  display: none;
526
- }';
527
- }
528
  }
529
  else {
530
  $html .= '#'.$panoid.' div.pnlm-orientation-button {
531
  display: none;
532
  }';
533
- }
534
-
535
  $html .= '</style>';
536
  if ($width == 'fullwidth') {
537
  if (wpvr_isMobileDevice()) {
@@ -541,7 +541,7 @@ class Wpvr_Public {
541
  else {
542
  $html .= '<div id="pano'.$id.'" class="pano-wrap" style="text-align:center;">';
543
  }
544
-
545
  }
546
  else {
547
  if ($radius) {
@@ -550,7 +550,7 @@ class Wpvr_Public {
550
  else {
551
  $html .= '<div id="pano'.$id.'" class="pano-wrap vrfullwidth" style=" text-align:center; height: '.$height.';" >';
552
  }
553
-
554
  }
555
  }
556
  else {
@@ -560,7 +560,7 @@ class Wpvr_Public {
560
  else {
561
  $html .= '<div id="pano'.$id.'" class="pano-wrap" style=" text-align:center; width: '.$width.'; height: '.$height.'; margin: 0 auto;">';
562
  }
563
-
564
  }
565
 
566
  if ($vrgallery) {
@@ -580,8 +580,8 @@ class Wpvr_Public {
580
  }
581
  }
582
  $html .= '</div>';
583
-
584
- //===Carousal setup end===//
585
  }
586
 
587
 
@@ -600,15 +600,25 @@ class Wpvr_Public {
600
  $html .= 'for(var i = 0; i < scenehotspot.length; i++) {';
601
  $html .= 'if(scenehotspot[i]["clickHandlerArgs"] != "") {';
602
  $html .= 'scenehotspot[i]["clickHandlerFunc"] = wpvrhotspot;';
603
- $html .= '}';
604
  $html .= 'if(scenehotspot[i]["createTooltipArgs"] != "") {';
605
  $html .= 'scenehotspot[i]["createTooltipFunc"] = wpvrtooltip;';
606
- $html .= '}';
607
- $html .= '}';
608
  $html .= '}';
609
- $html .= '}';
610
 
611
  $html .= 'var panoshow'.$id.' = pannellum.viewer(response[0]["panoid"], scenes);';
 
 
 
 
 
 
 
 
 
 
612
  $html .= 'var touchtime = 0;';
613
  if ($vrgallery) {
614
  if (isset($panodata["scene-list"])) {
@@ -628,12 +638,12 @@ class Wpvr_Public {
628
  $html .= '}';
629
 
630
  $html .= '}';
631
-
632
  $html .= '});';
633
  }
634
- }
635
  }
636
-
637
  $html .= '</script>';
638
  //script end
639
 
122
  * @since 1.0.0
123
  */
124
  public function wpvr_shortcode( $atts ) {
125
+
126
  extract(
127
  shortcode_atts(
128
  array(
165
  $html .= '<div style="text-align: center; max-width:100%; height:auto; margin: 0 auto;">';
166
  $html .= '<iframe width="'.trim($width,'px').'" height="'.trim($height,'px').'" src="https://www.youtube.com/embed/'.$foundid.'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
167
  $html .= '</div>';
168
+
169
  } elseif (strpos($videourl, 'vimeo') > 0) {
170
 
171
  $explodeid = '';
176
  $html .= '<div style="text-align: center; max-width:100%; height:auto; margin: 0 auto;">';
177
  $html .= '<iframe src="https://player.vimeo.com/video/'.$foundid.'" width="'.trim($width,'px').'" height="'.trim($height,'px').'" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
178
  $html .= '</div>';
179
+
180
  } else {
181
  $html = '';
182
  $html .= '<div id="pano'.$id.'" class="pano-wrap" style="width: '.$width.'; height: '.$height.'; margin: 0 auto;">';
190
  $html .= '</script>';
191
  $html .= '</div>';
192
  }
193
+
194
  return $html;
195
  }
196
 
213
  if (isset($postdata['compass'])) {
214
  $compass = $postdata['compass'];
215
  }
216
+
217
  $autoload = false;
218
 
219
  if (isset($postdata['autoLoad'])) {
220
  $autoload = $postdata['autoLoad'];
221
  }
222
+
223
  $default_scene = '';
224
  if (isset($postdata['defaultscene'])) {
225
  $default_scene = $postdata['defaultscene'];
242
  if (isset($postdata["autoRotateStopDelay"])) {
243
  $autorotationstopdelay = $postdata["autoRotateStopDelay"];
244
  }
245
+
246
  $scene_fade_duration = '';
247
  if (isset($postdata['scenefadeduration'])) {
248
  $scene_fade_duration = $postdata['scenefadeduration'];
249
  }
250
+
251
  $panodata = '';
252
  if (isset($postdata['panodata'])) {
253
  $panodata = $postdata['panodata'];
265
  if (isset($panoscenes["scene-ititle"])) {
266
  $scene_ititle = sanitize_text_field($panoscenes["scene-ititle"]);
267
  }
268
+
269
  $scene_author = '';
270
  if (isset($panoscenes["scene-author"])) {
271
  $scene_author = sanitize_text_field($panoscenes["scene-author"]);
272
  }
273
 
274
+ $default_scene_pitch = null;
275
  if (isset($panoscenes["scene-pitch"])) {
276
+ $default_scene_pitch = (float)$panoscenes["scene-pitch"];
277
  }
278
+
279
+ $default_scene_yaw = null;
280
  if (isset($panoscenes["scene-yaw"])) {
281
+ $default_scene_yaw = (float)$panoscenes["scene-yaw"];
282
  }
283
 
284
  $scene_max_pitch = '';
285
  if (isset($panoscenes["scene-maxpitch"])) {
286
  $scene_max_pitch = (float)$panoscenes["scene-maxpitch"];
287
  }
288
+
289
 
290
  $scene_min_pitch = '';
291
  if (isset($panoscenes["scene-minpitch"])) {
292
  $scene_min_pitch = (float)$panoscenes["scene-minpitch"];
293
  }
294
+
295
 
296
  $scene_max_yaw = '';
297
  if (isset($panoscenes["scene-maxyaw"])) {
298
  $scene_max_yaw = (float)$panoscenes["scene-maxyaw"];
299
  }
300
+
301
 
302
  $scene_min_yaw = '';
303
  if (isset($panoscenes["scene-minyaw"])) {
304
  $scene_min_yaw = (float)$panoscenes["scene-minyaw"];
305
  }
306
+
307
  $default_zoom = 100;
308
  if (isset($panoscenes["scene-zoom"])) {
309
  $default_zoom = $panoscenes["scene-zoom"];
310
  }
311
+
312
  if (!empty($default_zoom)) {
313
  $default_zoom = (int)$default_zoom;
314
  }
320
  if (isset($panoscenes["scene-maxzoom"])) {
321
  $max_zoom = $panoscenes["scene-maxzoom"];
322
  }
323
+
324
  if (!empty($max_zoom)) {
325
  $max_zoom = (int)$max_zoom;
326
  }
332
  if (isset($panoscenes["scene-minzoom"])) {
333
  $min_zoom = $panoscenes["scene-minzoom"];
334
  }
335
+
336
  if (!empty($min_zoom)) {
337
  $min_zoom = (int)$min_zoom;
338
  }
339
  else {
340
  $min_zoom = 50;
341
+ }
342
  $hotspot_datas = array();
343
  if (isset($panoscenes["hotspot-list"])) {
344
  $hotspot_datas = $panoscenes["hotspot-list"];
345
  }
346
+
347
  $hotspots = array();
348
  foreach ($hotspot_datas as $hotspot_data) {
349
 
351
  if( $status !== false && $status == 'valid' ) {
352
  if (isset($hotspot_data["hotspot-customclass-pro"]) && $hotspot_data["hotspot-customclass-pro"] != 'none') {
353
  $hotspot_data["hotspot-customclass"] = $hotspot_data["hotspot-customclass-pro"];
354
+ $hotspoticoncolor = $hotspot_data["hotspot-customclass-color-icon-value"];
355
  }
356
  if (isset($hotspot_data['hotspot-blink'])) {
357
  $hotspotblink = $hotspot_data['hotspot-blink'];
364
  $hotspot_scene_yaw = '';
365
  if (isset($hotspot_data["hotspot-scene-yaw"])) {
366
  $hotspot_scene_yaw = $hotspot_data["hotspot-scene-yaw"];
367
+ }
368
+
369
  $hotspot_info = array(
370
  "text"=>$hotspot_data["hotspot-title"],
371
  "pitch"=>$hotspot_data["hotspot-pitch"],
440
  unset($scene_info['minHfov']);
441
  }
442
  }
443
+
444
  $scene_array = array();
445
  $scene_array = array(
446
  $panoscenes["scene-id"]=>$scene_info
447
  );
448
  $scene_data[$panoscenes["scene-id"]] = $scene_info;
449
+ }
450
  }
451
 
452
  $pano_id_array = array();
491
  $html .= '#'.$panoid.' div.pnlm-hotspot-base.fas,
492
  #'.$panoid.' div.pnlm-hotspot-base.fab,
493
  #'.$panoid.' div.pnlm-hotspot-base.far {
494
+ display: block !important;
495
  background-color: '.$hotspoticoncolor.';
496
  color: '.$foreground_color.';
497
+ border-radius: 100%;
498
  width: 30px;
499
  height: 30px;
500
  animation: icon-pulse'.$panoid.' 1.5s infinite cubic-bezier(.25, 0, 0, 1);
515
  100% {
516
  box-shadow: 0 0 0 10px rgba('.$pulse_color[0].', 0);
517
  }
518
+ }';
519
+ }
520
+
521
  $status = get_option( 'wpvr_edd_license_status' );
522
  if( $status !== false && $status == 'valid' ) {
523
  if (!$gyro) {
524
  $html .= '#'.$panoid.' div.pnlm-orientation-button {
525
  display: none;
526
+ }';
527
+ }
528
  }
529
  else {
530
  $html .= '#'.$panoid.' div.pnlm-orientation-button {
531
  display: none;
532
  }';
533
+ }
534
+
535
  $html .= '</style>';
536
  if ($width == 'fullwidth') {
537
  if (wpvr_isMobileDevice()) {
541
  else {
542
  $html .= '<div id="pano'.$id.'" class="pano-wrap" style="text-align:center;">';
543
  }
544
+
545
  }
546
  else {
547
  if ($radius) {
550
  else {
551
  $html .= '<div id="pano'.$id.'" class="pano-wrap vrfullwidth" style=" text-align:center; height: '.$height.';" >';
552
  }
553
+
554
  }
555
  }
556
  else {
560
  else {
561
  $html .= '<div id="pano'.$id.'" class="pano-wrap" style=" text-align:center; width: '.$width.'; height: '.$height.'; margin: 0 auto;">';
562
  }
563
+
564
  }
565
 
566
  if ($vrgallery) {
580
  }
581
  }
582
  $html .= '</div>';
583
+
584
+ //===Carousal setup end===//
585
  }
586
 
587
 
600
  $html .= 'for(var i = 0; i < scenehotspot.length; i++) {';
601
  $html .= 'if(scenehotspot[i]["clickHandlerArgs"] != "") {';
602
  $html .= 'scenehotspot[i]["clickHandlerFunc"] = wpvrhotspot;';
603
+ $html .= '}';
604
  $html .= 'if(scenehotspot[i]["createTooltipArgs"] != "") {';
605
  $html .= 'scenehotspot[i]["createTooltipFunc"] = wpvrtooltip;';
606
+ $html .= '}';
607
+ $html .= '}';
608
  $html .= '}';
609
+ $html .= '}';
610
 
611
  $html .= 'var panoshow'.$id.' = pannellum.viewer(response[0]["panoid"], scenes);';
612
+ $html .= '
613
+ if (scenes.autoRotate) {
614
+ panoshow'.$id.'.on("load", function (){
615
+ setTimeout(function(){ panoshow'.$id.'.startAutoRotate(scenes.autoRotate, 0); }, 3000);
616
+ });
617
+ panoshow'.$id.'.on("scenechange", function (){
618
+ setTimeout(function(){ panoshow'.$id.'.startAutoRotate(scenes.autoRotate, 0); }, 3000);
619
+ });
620
+ }
621
+ ';
622
  $html .= 'var touchtime = 0;';
623
  if ($vrgallery) {
624
  if (isset($panodata["scene-list"])) {
638
  $html .= '}';
639
 
640
  $html .= '}';
641
+
642
  $html .= '});';
643
  }
644
+ }
645
  }
646
+
647
  $html .= '</script>';
648
  //script end
649
 
public/lib/pannellum/src/css/img/background.svg CHANGED
File without changes
public/lib/pannellum/src/css/img/compass.svg CHANGED
File without changes
public/lib/pannellum/src/css/img/grab.svg CHANGED
File without changes
public/lib/pannellum/src/css/img/grabbing.svg CHANGED
File without changes
public/lib/pannellum/src/css/img/sprites.svg CHANGED
File without changes
public/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
+ }
public/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
 
public/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,9 +135,7 @@ 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;
@@ -152,7 +158,11 @@ uiContainer.appendChild(dragFix);
152
  // Display about information on right click
153
  var aboutMsg = document.createElement('span');
154
  aboutMsg.className = 'pnlm-about-msg';
 
 
155
  aboutMsg.innerHTML = '<a href="https://rextheme.com/docs/wpvr-360-panorama-and-virtual-tour-creator-for-wordpress/" target="_blank">Rextheme</a>';
 
 
156
  uiContainer.appendChild(aboutMsg);
157
  dragFix.addEventListener('contextmenu', aboutMessage);
158
 
@@ -243,7 +253,6 @@ controls.orientation.addEventListener('click', function(e) {
243
  stopOrientation();
244
  else
245
  startOrientation();
246
- // stopOrientation();
247
  });
248
  controls.orientation.addEventListener('mousedown', function(e) {e.stopPropagation();});
249
  controls.orientation.addEventListener('touchstart', function(e) {e.stopPropagation();});
@@ -352,18 +361,23 @@ function init() {
352
  var onError = function(e) {
353
  var a = document.createElement('a');
354
  a.href = e.target.src;
355
- a.innerHTML = a.href;
356
  anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
357
  };
358
 
359
  for (i = 0; i < panoImage.length; i++) {
360
- panoImage[i].onload = onLoad;
361
- panoImage[i].onerror = onError;
362
  p = config.cubeMap[i];
363
- if (config.basePath && !absoluteURL(p)) {
364
- p = config.basePath + p;
 
 
 
 
 
 
 
 
365
  }
366
- panoImage[i].src = encodeURI(p);
367
  }
368
  } else if (config.type == 'multires') {
369
  onImageLoad();
@@ -387,8 +401,8 @@ function init() {
387
  if (xhr.status != 200) {
388
  // Display error if image can't be loaded
389
  var a = document.createElement('a');
390
- a.href = encodeURI(p);
391
- a.innerHTML = a.href;
392
  anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
393
  }
394
  var img = this.response;
@@ -437,6 +451,13 @@ function init() {
437
  if (config.draggable)
438
  uiContainer.classList.add('pnlm-grab');
439
  uiContainer.classList.remove('pnlm-grabbing');
 
 
 
 
 
 
 
440
  }
441
 
442
  /**
@@ -448,7 +469,7 @@ function init() {
448
  function absoluteURL(url) {
449
  // From http://stackoverflow.com/a/19709846
450
  return new RegExp('^(?:[a-z]+:)?//', 'i').test(url) || url[0] == '/' || url.slice(0, 5) == 'blob:';
451
- };
452
 
453
  /**
454
  * Create renderer and initialize event listeners once image is loaded.
@@ -471,10 +492,10 @@ function onImageLoad() {
471
  if (config.doubleClickZoom) {
472
  dragFix.addEventListener('dblclick', onDocumentDoubleClick, false);
473
  }
474
- uiContainer.addEventListener('mozfullscreenchange', onFullScreenChange, false);
475
- uiContainer.addEventListener('webkitfullscreenchange', onFullScreenChange, false);
476
- uiContainer.addEventListener('msfullscreenchange', onFullScreenChange, false);
477
- uiContainer.addEventListener('fullscreenchange', onFullScreenChange, false);
478
  window.addEventListener('resize', onDocumentResize, false);
479
  window.addEventListener('orientationchange', onDocumentResize, false);
480
  if (!config.disableKeyboardCtrl) {
@@ -483,13 +504,17 @@ function onImageLoad() {
483
  container.addEventListener('blur', clearKeys, false);
484
  }
485
  document.addEventListener('mouseleave', onDocumentMouseUp, false);
486
- dragFix.addEventListener('touchstart', onDocumentTouchStart, false);
487
- dragFix.addEventListener('touchmove', onDocumentTouchMove, false);
488
- dragFix.addEventListener('touchend', onDocumentTouchEnd, false);
489
- dragFix.addEventListener('pointerdown', onDocumentPointerDown, false);
490
- dragFix.addEventListener('pointermove', onDocumentPointerMove, false);
491
- dragFix.addEventListener('pointerup', onDocumentPointerUp, false);
492
- dragFix.addEventListener('pointerleave', onDocumentPointerUp, false);
 
 
 
 
493
 
494
  // Deal with MS pointer events
495
  if (window.navigator.pointerEnabled)
@@ -497,6 +522,7 @@ function onImageLoad() {
497
  }
498
 
499
  renderInit();
 
500
  setTimeout(function(){isTimedOut = true;}, 500);
501
  }
502
 
@@ -603,6 +629,7 @@ function anError(errorMsg) {
603
  infoDisplay.load.box.style.display = 'none';
604
  infoDisplay.errorMsg.style.display = 'table';
605
  error = true;
 
606
  renderContainer.style.display = 'none';
607
  fireEvent('error', errorMsg);
608
  }
@@ -616,6 +643,7 @@ function clearError() {
616
  infoDisplay.load.box.style.display = 'none';
617
  infoDisplay.errorMsg.style.display = 'none';
618
  error = false;
 
619
  fireEvent('errorcleared');
620
  }
621
  }
@@ -647,8 +675,9 @@ function aboutMessage(event) {
647
  function mousePosition(event) {
648
  var bounds = container.getBoundingClientRect();
649
  var pos = {};
650
- pos.x = event.clientX - bounds.left;
651
- pos.y = event.clientY - bounds.top;
 
652
  return pos;
653
  }
654
 
@@ -677,10 +706,8 @@ function onDocumentMouseDown(event) {
677
  var coords = mouseEventToCoords(event);
678
  console.log('Pitch: ' + coords[0] + ', Yaw: ' + coords[1] + ', Center Pitch: ' +
679
  config.pitch + ', Center Yaw: ' + config.yaw + ', HFOV: ' + config.hfov);
680
-
681
  }
682
- var coords = mouseEventToCoords(event);
683
- jQuery("#panodata").html('Pitch: ' + coords[0] + ', Yaw: ' + coords[1]);
684
  // Turn off auto-rotation if enabled
685
  stopAnimation();
686
 
@@ -874,7 +901,7 @@ function onDocumentTouchMove(event) {
874
  //
875
  // Currently this seems to *roughly* keep initial drag/pan start position close to
876
  // the user's finger while panning regardless of zoom level / config.hfov value.
877
- var touchmovePanSpeedCoeff = config.hfov / 360;
878
 
879
  var yaw = (onPointerDownPointerX - clientX) * touchmovePanSpeedCoeff + onPointerDownYaw;
880
  speed.yaw = (yaw - config.yaw) % 360 * 0.2;
@@ -910,6 +937,9 @@ var pointerIDs = [],
910
  */
911
  function onDocumentPointerDown(event) {
912
  if (event.pointerType == 'touch') {
 
 
 
913
  pointerIDs.push(event.pointerId);
914
  pointerCoordinates.push({clientX: event.clientX, clientY: event.clientY});
915
  event.targetTouches = pointerCoordinates;
@@ -925,13 +955,15 @@ function onDocumentPointerDown(event) {
925
  */
926
  function onDocumentPointerMove(event) {
927
  if (event.pointerType == 'touch') {
 
 
928
  for (var i = 0; i < pointerIDs.length; i++) {
929
  if (event.pointerId == pointerIDs[i]) {
930
  pointerCoordinates[i].clientX = event.clientX;
931
  pointerCoordinates[i].clientY = event.clientY;
932
  event.targetTouches = pointerCoordinates;
933
  onDocumentTouchMove(event);
934
- //event.preventDefault();
935
  return;
936
  }
937
  }
@@ -991,7 +1023,6 @@ function onDocumentMouseWheel(event) {
991
  setHfov(config.hfov + event.detail * 1.5);
992
  speed.hfov = event.detail > 0 ? 1 : -1;
993
  }
994
-
995
  animateInit();
996
  }
997
 
@@ -1012,8 +1043,8 @@ function onDocumentKeyPress(event) {
1012
  var keynumber = event.which || event.keycode;
1013
 
1014
  // Override default action for keys that are used
1015
- if (usedKeyNumbers.indexOf(keynumber) < 0)
1016
- return
1017
  event.preventDefault();
1018
 
1019
  // If escape key is pressed
@@ -1048,8 +1079,8 @@ function onDocumentKeyUp(event) {
1048
  var keynumber = event.which || event.keycode;
1049
 
1050
  // Override default action for keys that are used
1051
- if (usedKeyNumbers.indexOf(keynumber) < 0)
1052
- return
1053
  event.preventDefault();
1054
 
1055
  // Change key
@@ -1199,12 +1230,11 @@ function keyRepeat() {
1199
  latestInteraction = Date.now();
1200
 
1201
  // If auto-rotate
1202
- var inactivityInterval = Date.now() - latestInteraction;
1203
  if (config.autoRotate) {
1204
  // Pan
1205
  if (newTime - prevTime > 0.001) {
1206
  var timeDiff = (newTime - prevTime) / 1000;
1207
- var yawDiff = (speed.yaw / timeDiff * diff - config.autoRotate * 0.2) * timeDiff
1208
  yawDiff = (-config.autoRotate > 0 ? 1 : -1) * Math.min(Math.abs(config.autoRotate * timeDiff), Math.abs(yawDiff));
1209
  config.yaw += yawDiff;
1210
  }
@@ -1237,19 +1267,19 @@ function keyRepeat() {
1237
  // "Inertia"
1238
  if (diff > 0 && !config.autoRotate) {
1239
  // "Friction"
1240
- var friction = 0.85;
1241
 
1242
  // Yaw
1243
  if (!keysDown[4] && !keysDown[5] && !keysDown[8] && !keysDown[9] && !animatedMove.yaw) {
1244
- config.yaw += speed.yaw * diff * friction;
1245
  }
1246
  // Pitch
1247
  if (!keysDown[2] && !keysDown[3] && !keysDown[6] && !keysDown[7] && !animatedMove.pitch) {
1248
- config.pitch += speed.pitch * diff * friction;
1249
  }
1250
  // Zoom
1251
  if (!keysDown[0] && !keysDown[1] && !animatedMove.hfov) {
1252
- setHfov(config.hfov + speed.hfov * diff * friction);
1253
  }
1254
  }
1255
 
@@ -1267,7 +1297,7 @@ function keyRepeat() {
1267
  }
1268
 
1269
  // Stop movement if opposite controls are pressed
1270
- if (keysDown[0] && keysDown[0]) {
1271
  speed.hfov = 0;
1272
  }
1273
  if ((keysDown[2] || keysDown[6]) && (keysDown[3] || keysDown[7])) {
@@ -1292,11 +1322,7 @@ function animateMove(axis) {
1292
  t.endPosition === t.startPosition) {
1293
  result = t.endPosition;
1294
  speed[axis] = 0;
1295
- var callback = animatedMove[axis].callback,
1296
- callbackArgs = animatedMove[axis].callbackArgs;
1297
  delete animatedMove[axis];
1298
- if (typeof callback == 'function')
1299
- callback(callbackArgs);
1300
  }
1301
  config[axis] = result;
1302
  }
@@ -1321,7 +1347,7 @@ function onDocumentResize() {
1321
  //animateInit();
1322
 
1323
  // Kludge to deal with WebKit regression: https://bugs.webkit.org/show_bug.cgi?id=93525
1324
- onFullScreenChange();
1325
  }
1326
 
1327
  /**
@@ -1341,6 +1367,10 @@ function animateInit() {
1341
  * @private
1342
  */
1343
  function animate() {
 
 
 
 
1344
  render();
1345
  if (autoRotateStart)
1346
  clearTimeout(autoRotateStart);
@@ -1364,6 +1394,7 @@ function animate() {
1364
  } else if (renderer && (renderer.isLoading() || (config.dynamic === true && update))) {
1365
  requestAnimationFrame(animate);
1366
  } else {
 
1367
  animating = false;
1368
  prevTime = undefined;
1369
  var autoRotateStartTime = config.autoRotateInactivityDelay -
@@ -1371,7 +1402,7 @@ function animate() {
1371
  if (autoRotateStartTime > 0) {
1372
  autoRotateStart = setTimeout(function() {
1373
  config.autoRotate = autoRotateSpeed;
1374
- _this.lookAt(0, undefined, origHfov, 3000);
1375
  animateInit();
1376
  }, autoRotateStartTime);
1377
  } else if (config.autoRotateInactivityDelay >= 0 && autoRotateSpeed) {
@@ -1390,37 +1421,68 @@ function render() {
1390
  var tmpyaw;
1391
 
1392
  if (loaded) {
1393
- if (config.yaw > 180) {
1394
- config.yaw -= 360;
1395
- } else if (config.yaw < -180) {
1396
- config.yaw += 360;
 
 
 
 
 
1397
  }
1398
 
1399
  // Keep a tmp value of yaw for autoRotate comparison later
1400
  tmpyaw = config.yaw;
1401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1402
  // Ensure the yaw is within min and max allowed
1403
  var yawRange = config.maxYaw - config.minYaw,
1404
  minYaw = -180,
1405
  maxYaw = 180;
1406
  if (yawRange < 360) {
1407
- minYaw = config.minYaw + config.hfov / 2;
1408
- maxYaw = config.maxYaw - config.hfov / 2;
1409
  if (yawRange < config.hfov) {
1410
  // Lock yaw to average of min and max yaw when both can be seen at once
1411
  minYaw = maxYaw = (minYaw + maxYaw) / 2;
1412
  }
 
 
 
 
 
 
 
 
 
 
 
1413
  }
1414
- config.yaw = Math.max(minYaw, Math.min(maxYaw, config.yaw));
1415
 
1416
  // Check if we autoRotate in a limited by min and max yaw
1417
  // If so reverse direction
1418
- if (config.autoRotate !== false && tmpyaw != config.yaw) {
 
1419
  config.autoRotate *= -1;
1420
  }
1421
 
1422
  // Ensure the calculated pitch is within min and max allowed
1423
- var canvas = renderer.getCanvas();
1424
  var vfov = 2 * Math.atan(Math.tan(config.hfov / 180 * Math.PI * 0.5) /
1425
  (canvas.width / canvas.height)) / Math.PI * 180;
1426
  var minPitch = config.minPitch + vfov / 2,
@@ -1475,7 +1537,7 @@ Quaternion.prototype.multiply = function(q) {
1475
  this.x*q.w + this.w*q.x + this.y*q.z - this.z*q.y,
1476
  this.y*q.w + this.w*q.y + this.z*q.x - this.x*q.z,
1477
  this.z*q.w + this.w*q.z + this.x*q.y - this.y*q.x);
1478
- }
1479
 
1480
  /**
1481
  * Converts quaternion to Euler angles.
@@ -1489,7 +1551,7 @@ Quaternion.prototype.toEulerAngles = function() {
1489
  psi = Math.atan2(2 * (this.w * this.z + this.x * this.y),
1490
  1 - 2 * (this.y * this.y + this.z * this.z));
1491
  return [phi, theta, psi];
1492
- }
1493
 
1494
  /**
1495
  * Converts device orientation API Tait-Bryan angles to a quaternion.
@@ -1537,6 +1599,8 @@ function computeQuaternion(alpha, beta, gamma) {
1537
  * @param {DeviceOrientationEvent} event - Device orientation event.
1538
  */
1539
  function orientationListener(e) {
 
 
1540
  var q = computeQuaternion(e.alpha, e.beta, e.gamma).toEulerAngles();
1541
  if (typeof(orientation) == 'number' && orientation < 10) {
1542
  // This kludge is necessary because iOS sometimes provides a few stale
@@ -1642,7 +1706,7 @@ function createHotSpot(hs) {
1642
  hs.yaw = Number(hs.yaw) || 0;
1643
 
1644
  var div = document.createElement('div');
1645
- div.className = 'pnlm-hotspot-base'
1646
  if (hs.cssClass)
1647
  div.className += ' ' + hs.cssClass;
1648
  else
@@ -1655,24 +1719,24 @@ function createHotSpot(hs) {
1655
  var a;
1656
  if (hs.video) {
1657
  var video = document.createElement('video'),
1658
- p = hs.video;
1659
- if (config.basePath && !absoluteURL(p))
1660
- p = config.basePath + p;
1661
- video.src = encodeURI(p);
1662
  video.controls = true;
1663
  video.style.width = hs.width + 'px';
1664
  renderContainer.appendChild(div);
1665
  span.appendChild(video);
1666
  } else if (hs.image) {
1667
- var p = hs.image;
1668
- if (config.basePath && !absoluteURL(p))
1669
- p = config.basePath + p;
1670
  a = document.createElement('a');
1671
- a.href = encodeURI(hs.URL ? hs.URL : p);
1672
  a.target = '_blank';
1673
  span.appendChild(a);
1674
  var image = document.createElement('img');
1675
- image.src = encodeURI(p);
1676
  image.style.width = hs.width + 'px';
1677
  image.style.paddingTop = '5px';
1678
  renderContainer.appendChild(div);
@@ -1680,11 +1744,17 @@ function createHotSpot(hs) {
1680
  span.style.maxWidth = 'initial';
1681
  } else if (hs.URL) {
1682
  a = document.createElement('a');
1683
- a.href = encodeURI(hs.URL);
1684
- a.target = '_blank';
 
 
 
 
 
 
1685
  renderContainer.appendChild(a);
1686
- div.style.cursor = 'pointer';
1687
- span.style.cursor = 'pointer';
1688
  a.appendChild(div);
1689
  } else {
1690
  if (hs.sceneId) {
@@ -1695,8 +1765,8 @@ function createHotSpot(hs) {
1695
  }
1696
  return false;
1697
  };
1698
- div.style.cursor = 'pointer';
1699
- span.style.cursor = 'pointer';
1700
  }
1701
  renderContainer.appendChild(div);
1702
  }
@@ -1714,11 +1784,11 @@ function createHotSpot(hs) {
1714
  div.addEventListener('click', function(e) {
1715
  hs.clickHandlerFunc(e, hs.clickHandlerArgs);
1716
  }, 'false');
1717
- div.style.cursor = 'pointer';
1718
- span.style.cursor = 'pointer';
1719
  }
1720
  hs.div = div;
1721
- };
1722
 
1723
  /**
1724
  * Creates hot spot elements for the current scene.
@@ -1751,10 +1821,12 @@ function destroyHotSpots() {
1751
  if (hs) {
1752
  for (var i = 0; i < hs.length; i++) {
1753
  var current = hs[i].div;
1754
- while(current.parentNode != renderContainer) {
1755
- current = current.parentNode;
 
 
 
1756
  }
1757
- renderContainer.removeChild(current);
1758
  delete hs[i].div;
1759
  }
1760
  }
@@ -1796,6 +1868,9 @@ function renderHotSpot(hs) {
1796
  coord[1] += (canvasHeight - hs.div.offsetHeight) / 2;
1797
  var transform = 'translate(' + coord[0] + 'px, ' + coord[1] +
1798
  'px) translateZ(9999px) rotate(' + config.roll + 'deg)';
 
 
 
1799
  hs.div.style.webkitTransform = transform;
1800
  hs.div.style.MozTransform = transform;
1801
  hs.div.style.transform = transform;
@@ -1904,7 +1979,7 @@ function processOptions(isPreview) {
1904
  p = config.basePath + p;
1905
  preview = document.createElement('div');
1906
  preview.className = 'pnlm-preview-img';
1907
- preview.style.backgroundImage = "url('" + encodeURI(p) + "')";
1908
  renderContainer.appendChild(preview);
1909
  }
1910
 
@@ -1940,12 +2015,29 @@ function processOptions(isPreview) {
1940
  break;
1941
 
1942
  case 'author':
1943
- infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', escapeHTML(config[key]));
 
 
 
 
 
 
 
 
1944
  infoDisplay.container.style.display = 'inline';
1945
  break;
1946
 
1947
  case 'fallback':
1948
- 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>';
 
 
 
 
 
 
 
 
 
1949
  break;
1950
 
1951
  case 'hfov':
@@ -2063,15 +2155,16 @@ function toggleFullscreen() {
2063
  * Event handler for fullscreen changes.
2064
  * @private
2065
  */
2066
- function onFullScreenChange() {
2067
- if (document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement) {
2068
  controls.fullscreen.classList.add('pnlm-fullscreen-toggle-button-active');
2069
  fullscreenActive = true;
2070
  } else {
2071
  controls.fullscreen.classList.remove('pnlm-fullscreen-toggle-button-active');
2072
  fullscreenActive = false;
2073
  }
2074
-
 
2075
  // Resize renderer (deal with browser quirks and fixes #155)
2076
  renderer.resize();
2077
  setHfov(config.hfov);
@@ -2109,20 +2202,30 @@ function zoomOut() {
2109
  function constrainHfov(hfov) {
2110
  // Keep field of view within bounds
2111
  var minHfov = config.minHfov;
2112
- if (config.type == 'multires' && renderer) {
2113
  minHfov = Math.min(minHfov, renderer.getCanvas().width / (config.multiRes.cubeResolution / 90 * 0.9));
2114
  }
2115
  if (minHfov > config.maxHfov) {
2116
  // Don't change view if bounds don't make sense
2117
- console.log('HFOV bounds do not make sense (minHfov > maxHfov).')
2118
  return config.hfov;
2119
- } if (hfov < minHfov) {
2120
- return minHfov;
 
 
2121
  } else if (hfov > config.maxHfov) {
2122
- return config.maxHfov;
2123
  } else {
2124
- return hfov;
2125
  }
 
 
 
 
 
 
 
 
2126
  }
2127
 
2128
  /**
@@ -2132,6 +2235,7 @@ function constrainHfov(hfov) {
2132
  */
2133
  function setHfov(hfov) {
2134
  config.hfov = constrainHfov(hfov);
 
2135
  }
2136
 
2137
  /**
@@ -2153,6 +2257,7 @@ function load() {
2153
  // since it is a new scene and the error from previous maybe because of lacking
2154
  // memory etc and not because of a lack of WebGL support etc
2155
  clearError();
 
2156
 
2157
  controls.load.style.display = 'none';
2158
  infoDisplay.load.box.style.display = 'inline';
@@ -2169,6 +2274,8 @@ function load() {
2169
  * @param {boolean} [fadeDone] - If `true`, fade setup is skipped.
2170
  */
2171
  function loadScene(sceneId, targetPitch, targetYaw, targetHfov, fadeDone) {
 
 
2172
  loaded = false;
2173
  animatedMove = {};
2174
 
@@ -2273,6 +2380,34 @@ function escapeHTML(s) {
2273
  .split('\n').join('<br>'); // Allow line breaks
2274
  }
2275
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2276
  /**
2277
  * Checks whether or not a panorama is loaded.
2278
  * @memberof Viewer
@@ -2280,7 +2415,7 @@ function escapeHTML(s) {
2280
  * @returns {boolean} `true` if a panorama is loaded, else `false`
2281
  */
2282
  this.isLoaded = function() {
2283
- return loaded;
2284
  };
2285
 
2286
  /**
@@ -2304,16 +2439,22 @@ this.getPitch = function() {
2304
  * @returns {Viewer} `this`
2305
  */
2306
  this.setPitch = function(pitch, animated, callback, callbackArgs) {
 
 
 
 
 
 
2307
  animated = animated == undefined ? 1000: Number(animated);
2308
  if (animated) {
2309
  animatedMove.pitch = {
2310
  'startTime': Date.now(),
2311
  'startPosition': config.pitch,
2312
  'endPosition': pitch,
2313
- 'duration': animated,
2314
- 'callback': callback,
2315
- 'callbackArgs': callbackArgs
2316
- }
2317
  } else {
2318
  config.pitch = pitch;
2319
  }
@@ -2365,23 +2506,29 @@ this.getYaw = function() {
2365
  * @returns {Viewer} `this`
2366
  */
2367
  this.setYaw = function(yaw, animated, callback, callbackArgs) {
 
 
 
 
 
 
2368
  animated = animated == undefined ? 1000: Number(animated);
2369
- yaw = ((yaw + 180) % 360) - 180 // Keep in bounds
2370
  if (animated) {
2371
  // Animate in shortest direction
2372
  if (config.yaw - yaw > 180)
2373
- yaw += 360
2374
  else if (yaw - config.yaw > 180)
2375
- yaw -= 360
2376
 
2377
  animatedMove.yaw = {
2378
  'startTime': Date.now(),
2379
  'startPosition': config.yaw,
2380
  'endPosition': yaw,
2381
- 'duration': animated,
2382
- 'callback': callback,
2383
- 'callbackArgs': callbackArgs
2384
- }
2385
  } else {
2386
  config.yaw = yaw;
2387
  }
@@ -2433,16 +2580,22 @@ this.getHfov = function() {
2433
  * @returns {Viewer} `this`
2434
  */
2435
  this.setHfov = function(hfov, animated, callback, callbackArgs) {
 
 
 
 
 
 
2436
  animated = animated == undefined ? 1000: Number(animated);
2437
  if (animated) {
2438
  animatedMove.hfov = {
2439
  'startTime': Date.now(),
2440
  'startPosition': config.hfov,
2441
  'endPosition': constrainHfov(hfov),
2442
- 'duration': animated,
2443
- 'callback': callback,
2444
- 'callbackArgs': callbackArgs
2445
- }
2446
  } else {
2447
  setHfov(hfov);
2448
  }
@@ -2488,18 +2641,22 @@ this.setHfovBounds = function(bounds) {
2488
  */
2489
  this.lookAt = function(pitch, yaw, hfov, animated, callback, callbackArgs) {
2490
  animated = animated == undefined ? 1000: Number(animated);
2491
- if (pitch !== undefined) {
2492
  this.setPitch(pitch, animated, callback, callbackArgs);
2493
  callback = undefined;
2494
  }
2495
- if (yaw !== undefined) {
2496
  this.setYaw(yaw, animated, callback, callbackArgs);
2497
  callback = undefined;
2498
  }
2499
- if (hfov !== undefined)
2500
  this.setHfov(hfov, animated, callback, callbackArgs);
 
 
 
 
2501
  return this;
2502
- }
2503
 
2504
  /**
2505
  * Returns the panorama's north offset.
@@ -2574,15 +2731,19 @@ this.setHorizonPitch = function(pitch) {
2574
 
2575
  /**
2576
  * Start auto rotation.
 
 
2577
  * @memberof Viewer
2578
  * @instance
2579
  * @param {number} [speed] - Auto rotation speed / direction. If not specified, previous value is used.
 
2580
  * @returns {Viewer} `this`
2581
  */
2582
- this.startAutoRotate = function(speed) {
2583
  speed = speed || autoRotateSpeed || 1;
 
2584
  config.autoRotate = speed;
2585
- _this.lookAt(origPitch, undefined, origHfov, 3000);
2586
  animateInit();
2587
  return this;
2588
  };
@@ -2600,6 +2761,16 @@ this.stopAutoRotate = function() {
2600
  return this;
2601
  };
2602
 
 
 
 
 
 
 
 
 
 
 
2603
  /**
2604
  * Returns the panorama renderer.
2605
  * @memberof Viewer
@@ -2624,7 +2795,7 @@ this.setUpdate = function(bool) {
2624
  else
2625
  animateInit();
2626
  return this;
2627
- }
2628
 
2629
  /**
2630
  * Calculate panorama pitch and yaw from location of mouse event.
@@ -2635,7 +2806,7 @@ this.setUpdate = function(bool) {
2635
  */
2636
  this.mouseEventToCoords = function(event) {
2637
  return mouseEventToCoords(event);
2638
- }
2639
 
2640
  /**
2641
  * Change scene being viewed.
@@ -2648,10 +2819,10 @@ this.mouseEventToCoords = function(event) {
2648
  * @returns {Viewer} `this`
2649
  */
2650
  this.loadScene = function(sceneId, pitch, yaw, hfov) {
2651
- if (loaded)
2652
  loadScene(sceneId, pitch, yaw, hfov);
2653
  return this;
2654
- }
2655
 
2656
  /**
2657
  * Get ID of current scene.
@@ -2661,7 +2832,7 @@ this.loadScene = function(sceneId, pitch, yaw, hfov) {
2661
  */
2662
  this.getScene = function() {
2663
  return config.scene;
2664
- }
2665
 
2666
  /**
2667
  * Add a new scene.
@@ -2699,7 +2870,7 @@ this.removeScene = function(sceneId) {
2699
  this.toggleFullscreen = function() {
2700
  toggleFullscreen();
2701
  return this;
2702
- }
2703
 
2704
  /**
2705
  * Get configuration of current scene.
@@ -2709,7 +2880,7 @@ this.toggleFullscreen = function() {
2709
  */
2710
  this.getConfig = function() {
2711
  return config;
2712
- }
2713
 
2714
  /**
2715
  * Get viewer's container element.
@@ -2719,7 +2890,7 @@ this.getConfig = function() {
2719
  */
2720
  this.getContainer = function() {
2721
  return container;
2722
- }
2723
 
2724
  /**
2725
  * Add a new hot spot.
@@ -2745,7 +2916,7 @@ this.addHotSpot = function(hs, sceneId) {
2745
  }
2746
  initialConfig.scenes[id].hotSpots.push(hs); // Add hot spot to config
2747
  } else {
2748
- throw 'Invalid scene ID!'
2749
  }
2750
  }
2751
  if (sceneId === undefined || config.scene == sceneId) {
@@ -2755,34 +2926,51 @@ this.addHotSpot = function(hs, sceneId) {
2755
  renderHotSpot(hs);
2756
  }
2757
  return this;
2758
- }
2759
 
2760
  /**
2761
  * Remove a hot spot.
2762
  * @memberof Viewer
2763
  * @instance
2764
  * @param {string} hotSpotId - The ID of the hot spot
 
2765
  * @returns {boolean} True if deletion is successful, else false
2766
  */
2767
- this.removeHotSpot = function(hotSpotId) {
2768
- if (!config.hotSpots)
2769
- return false;
2770
- for (var i = 0; i < config.hotSpots.length; i++) {
2771
- if (config.hotSpots[i].hasOwnProperty('id') &&
2772
- config.hotSpots[i].id === hotSpotId) {
2773
- // Delete hot spot DOM elements
2774
- var current = config.hotSpots[i].div;
2775
- while (current.parentNode != renderContainer)
2776
- current = current.parentNode;
2777
- renderContainer.removeChild(current);
2778
- delete config.hotSpots[i].div;
2779
- // Remove hot spot from configuration
2780
- config.hotSpots.splice(i, 1);
2781
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2782
  }
2783
  }
2784
- return false;
2785
- }
2786
 
2787
  /**
2788
  * This method should be called if the viewer's container is resized.
@@ -2790,8 +2978,9 @@ this.removeHotSpot = function(hotSpotId) {
2790
  * @instance
2791
  */
2792
  this.resize = function() {
2793
- onDocumentResize();
2794
- }
 
2795
 
2796
  /**
2797
  * Check if a panorama is loaded.
@@ -2801,7 +2990,7 @@ this.resize = function() {
2801
  */
2802
  this.isLoaded = function() {
2803
  return loaded;
2804
- }
2805
 
2806
  /**
2807
  * Check if device orientation control is supported.
@@ -2811,7 +3000,7 @@ this.isLoaded = function() {
2811
  */
2812
  this.isOrientationSupported = function() {
2813
  return orientationSupport || false;
2814
- }
2815
 
2816
  /**
2817
  * Stop using device orientation.
@@ -2820,7 +3009,7 @@ this.isOrientationSupported = function() {
2820
  */
2821
  this.stopOrientation = function() {
2822
  stopOrientation();
2823
- }
2824
 
2825
  /**
2826
  * Start using device orientation (does nothing if not supported).
@@ -2830,7 +3019,7 @@ this.stopOrientation = function() {
2830
  this.startOrientation = function() {
2831
  if (orientationSupport)
2832
  startOrientation();
2833
- }
2834
 
2835
  /**
2836
  * Check if device orientation control is currently activated.
@@ -2840,7 +3029,7 @@ this.startOrientation = function() {
2840
  */
2841
  this.isOrientationActive = function() {
2842
  return Boolean(orientation);
2843
- }
2844
 
2845
  /**
2846
  * Subscribe listener to specified event.
@@ -2854,7 +3043,7 @@ this.on = function(type, listener) {
2854
  externalEventListeners[type] = externalEventListeners[type] || [];
2855
  externalEventListeners[type].push(listener);
2856
  return this;
2857
- }
2858
 
2859
  /**
2860
  * Remove an event listener (or listeners).
@@ -2884,7 +3073,7 @@ this.off = function(type, listener) {
2884
  delete externalEventListeners[type];
2885
  }
2886
  return this;
2887
- }
2888
 
2889
  /**
2890
  * Fire listeners attached to specified event.
@@ -2906,15 +3095,14 @@ function fireEvent(type) {
2906
  * @memberof Viewer
2907
  */
2908
  this.destroy = function() {
 
 
 
2909
  if (renderer)
2910
- renderer.destroy()
2911
  if (listenersAdded) {
2912
- dragFix.removeEventListener('mousedown', onDocumentMouseDown, false);
2913
- dragFix.removeEventListener('dblclick', onDocumentDoubleClick, false);
2914
  document.removeEventListener('mousemove', onDocumentMouseMove, false);
2915
  document.removeEventListener('mouseup', onDocumentMouseUp, false);
2916
- container.removeEventListener('mousewheel', onDocumentMouseWheel, false);
2917
- container.removeEventListener('DOMMouseScroll', onDocumentMouseWheel, false);
2918
  container.removeEventListener('mozfullscreenchange', onFullScreenChange, false);
2919
  container.removeEventListener('webkitfullscreenchange', onFullScreenChange, false);
2920
  container.removeEventListener('msfullscreenchange', onFullScreenChange, false);
@@ -2925,19 +3113,10 @@ this.destroy = function() {
2925
  container.removeEventListener('keyup', onDocumentKeyUp, false);
2926
  container.removeEventListener('blur', clearKeys, false);
2927
  document.removeEventListener('mouseleave', onDocumentMouseUp, false);
2928
- dragFix.removeEventListener('touchstart', onDocumentTouchStart, false);
2929
- dragFix.removeEventListener('touchmove', onDocumentTouchMove, false);
2930
- dragFix.removeEventListener('touchend', onDocumentTouchEnd, false);
2931
- dragFix.removeEventListener('pointerdown', onDocumentPointerDown, false);
2932
- dragFix.removeEventListener('pointermove', onDocumentPointerMove, false);
2933
- dragFix.removeEventListener('pointerup', onDocumentPointerUp, false);
2934
- dragFix.removeEventListener('pointerleave', onDocumentPointerUp, false);
2935
  }
2936
  container.innerHTML = '';
2937
  container.classList.remove('pnlm-container');
2938
- uiContainer.classList.remove('pnlm-grab');
2939
- uiContainer.classList.remove('pnlm-grabbing');
2940
- }
2941
 
2942
  }
2943
 
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;
158
  // Display about information on right click
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
 
253
  stopOrientation();
254
  else
255
  startOrientation();
 
256
  });
257
  controls.orientation.addEventListener('mousedown', function(e) {e.stopPropagation();});
258
  controls.orientation.addEventListener('touchstart', function(e) {e.stopPropagation();});
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
  // Turn off auto-rotation if enabled
712
  stopAnimation();
713
 
901
  //
902
  // Currently this seems to *roughly* keep initial drag/pan start position close to
903
  // the user's finger while panning regardless of zoom level / config.hfov value.
904
+ var touchmovePanSpeedCoeff = (config.hfov / 360) * config.touchPanSpeedCoeffFactor;
905
 
906
  var yaw = (onPointerDownPointerX - clientX) * touchmovePanSpeedCoeff + onPointerDownYaw;
907
  speed.yaw = (yaw - config.yaw) % 360 * 0.2;
937
  */
938
  function onDocumentPointerDown(event) {
939
  if (event.pointerType == 'touch') {
940
+ // Only do something if the panorama is loaded
941
+ if (!loaded || !config.draggable)
942
+ return;
943
  pointerIDs.push(event.pointerId);
944
  pointerCoordinates.push({clientX: event.clientX, clientY: event.clientY});
945
  event.targetTouches = pointerCoordinates;
955
  */
956
  function onDocumentPointerMove(event) {
957
  if (event.pointerType == 'touch') {
958
+ if (!config.draggable)
959
+ return;
960
  for (var i = 0; i < pointerIDs.length; i++) {
961
  if (event.pointerId == pointerIDs[i]) {
962
  pointerCoordinates[i].clientX = event.clientX;
963
  pointerCoordinates[i].clientY = event.clientY;
964
  event.targetTouches = pointerCoordinates;
965
  onDocumentTouchMove(event);
966
+ event.preventDefault();
967
  return;
968
  }
969
  }
1023
  setHfov(config.hfov + event.detail * 1.5);
1024
  speed.hfov = event.detail > 0 ? 1 : -1;
1025
  }
 
1026
  animateInit();
1027
  }
1028
 
1043
  var keynumber = event.which || event.keycode;
1044
 
1045
  // Override default action for keys that are used
1046
+ if (config.capturedKeyNumbers.indexOf(keynumber) < 0)
1047
+ return;
1048
  event.preventDefault();
1049
 
1050
  // If escape key is pressed
1079
  var keynumber = event.which || event.keycode;
1080
 
1081
  // Override default action for keys that are used
1082
+ if (config.capturedKeyNumbers.indexOf(keynumber) < 0)
1083
+ return;
1084
  event.preventDefault();
1085
 
1086
  // Change key
1230
  latestInteraction = Date.now();
1231
 
1232
  // If auto-rotate
 
1233
  if (config.autoRotate) {
1234
  // Pan
1235
  if (newTime - prevTime > 0.001) {
1236
  var timeDiff = (newTime - prevTime) / 1000;
1237
+ var yawDiff = (speed.yaw / timeDiff * diff - config.autoRotate * 0.2) * timeDiff;
1238
  yawDiff = (-config.autoRotate > 0 ? 1 : -1) * Math.min(Math.abs(config.autoRotate * timeDiff), Math.abs(yawDiff));
1239
  config.yaw += yawDiff;
1240
  }
1267
  // "Inertia"
1268
  if (diff > 0 && !config.autoRotate) {
1269
  // "Friction"
1270
+ var slowDownFactor = 1 - config.friction;
1271
 
1272
  // Yaw
1273
  if (!keysDown[4] && !keysDown[5] && !keysDown[8] && !keysDown[9] && !animatedMove.yaw) {
1274
+ config.yaw += speed.yaw * diff * slowDownFactor;
1275
  }
1276
  // Pitch
1277
  if (!keysDown[2] && !keysDown[3] && !keysDown[6] && !keysDown[7] && !animatedMove.pitch) {
1278
+ config.pitch += speed.pitch * diff * slowDownFactor;
1279
  }
1280
  // Zoom
1281
  if (!keysDown[0] && !keysDown[1] && !animatedMove.hfov) {
1282
+ setHfov(config.hfov + speed.hfov * diff * slowDownFactor);
1283
  }
1284
  }
1285
 
1297
  }
1298
 
1299
  // Stop movement if opposite controls are pressed
1300
+ if (keysDown[0] && keysDown[1]) {
1301
  speed.hfov = 0;
1302
  }
1303
  if ((keysDown[2] || keysDown[6]) && (keysDown[3] || keysDown[7])) {
1322
  t.endPosition === t.startPosition) {
1323
  result = t.endPosition;
1324
  speed[axis] = 0;
 
 
1325
  delete animatedMove[axis];
 
 
1326
  }
1327
  config[axis] = result;
1328
  }
1347
  //animateInit();
1348
 
1349
  // Kludge to deal with WebKit regression: https://bugs.webkit.org/show_bug.cgi?id=93525
1350
+ onFullScreenChange('resize');
1351
  }
1352
 
1353
  /**
1367
  * @private
1368
  */
1369
  function animate() {
1370
+ if (destroyed) {
1371
+ return;
1372
+ }
1373
+
1374
  render();
1375
  if (autoRotateStart)
1376
  clearTimeout(autoRotateStart);
1394
  } else if (renderer && (renderer.isLoading() || (config.dynamic === true && update))) {
1395
  requestAnimationFrame(animate);
1396
  } else {
1397
+ fireEvent('animatefinished', {pitch: _this.getPitch(), yaw: _this.getYaw(), hfov: _this.getHfov()});
1398
  animating = false;
1399
  prevTime = undefined;
1400
  var autoRotateStartTime = config.autoRotateInactivityDelay -
1402
  if (autoRotateStartTime > 0) {
1403
  autoRotateStart = setTimeout(function() {
1404
  config.autoRotate = autoRotateSpeed;
1405
+ _this.lookAt(origPitch, undefined, origHfov, 3000);
1406
  animateInit();
1407
  }, autoRotateStartTime);
1408
  } else if (config.autoRotateInactivityDelay >= 0 && autoRotateSpeed) {
1421
  var tmpyaw;
1422
 
1423
  if (loaded) {
1424
+ var canvas = renderer.getCanvas();
1425
+
1426
+ if (config.autoRotate !== false) {
1427
+ // When auto-rotating this check needs to happen first (see issue #764)
1428
+ if (config.yaw > 180) {
1429
+ config.yaw -= 360;
1430
+ } else if (config.yaw < -180) {
1431
+ config.yaw += 360;
1432
+ }
1433
  }
1434
 
1435
  // Keep a tmp value of yaw for autoRotate comparison later
1436
  tmpyaw = config.yaw;
1437
 
1438
+ // Optionally avoid showing background (empty space) on left or right by adapting min/max yaw
1439
+ var hoffcut = 0,
1440
+ voffcut = 0;
1441
+ if (config.avoidShowingBackground) {
1442
+ var hfov2 = config.hfov / 2,
1443
+ vfov2 = Math.atan2(Math.tan(hfov2 / 180 * Math.PI), (canvas.width / canvas.height)) * 180 / Math.PI,
1444
+ transposed = config.vaov > config.haov;
1445
+ if (transposed) {
1446
+ voffcut = vfov2 * (1 - Math.min(Math.cos((config.pitch - hfov2) / 180 * Math.PI),
1447
+ Math.cos((config.pitch + hfov2) / 180 * Math.PI)));
1448
+ } else {
1449
+ hoffcut = hfov2 * (1 - Math.min(Math.cos((config.pitch - vfov2) / 180 * Math.PI),
1450
+ Math.cos((config.pitch + vfov2) / 180 * Math.PI)));
1451
+ }
1452
+ }
1453
+
1454
  // Ensure the yaw is within min and max allowed
1455
  var yawRange = config.maxYaw - config.minYaw,
1456
  minYaw = -180,
1457
  maxYaw = 180;
1458
  if (yawRange < 360) {
1459
+ minYaw = config.minYaw + config.hfov / 2 + hoffcut;
1460
+ maxYaw = config.maxYaw - config.hfov / 2 - hoffcut;
1461
  if (yawRange < config.hfov) {
1462
  // Lock yaw to average of min and max yaw when both can be seen at once
1463
  minYaw = maxYaw = (minYaw + maxYaw) / 2;
1464
  }
1465
+ config.yaw = Math.max(minYaw, Math.min(maxYaw, config.yaw));
1466
+ }
1467
+
1468
+ if (!(config.autoRotate !== false)) {
1469
+ // When not auto-rotating, this check needs to happen after the
1470
+ // previous check (see issue #698)
1471
+ if (config.yaw > 180) {
1472
+ config.yaw -= 360;
1473
+ } else if (config.yaw < -180) {
1474
+ config.yaw += 360;
1475
+ }
1476
  }
 
1477
 
1478
  // Check if we autoRotate in a limited by min and max yaw
1479
  // If so reverse direction
1480
+ if (config.autoRotate !== false && tmpyaw != config.yaw &&
1481
+ prevTime !== undefined) { // this condition prevents changing the direction initially
1482
  config.autoRotate *= -1;
1483
  }
1484
 
1485
  // Ensure the calculated pitch is within min and max allowed
 
1486
  var vfov = 2 * Math.atan(Math.tan(config.hfov / 180 * Math.PI * 0.5) /
1487
  (canvas.width / canvas.height)) / Math.PI * 180;
1488
  var minPitch = config.minPitch + vfov / 2,
1537
  this.x*q.w + this.w*q.x + this.y*q.z - this.z*q.y,
1538
  this.y*q.w + this.w*q.y + this.z*q.x - this.x*q.z,
1539
  this.z*q.w + this.w*q.z + this.x*q.y - this.y*q.x);
1540
+ };
1541
 
1542
  /**
1543
  * Converts quaternion to Euler angles.
1551
  psi = Math.atan2(2 * (this.w * this.z + this.x * this.y),
1552
  1 - 2 * (this.y * this.y + this.z * this.z));
1553
  return [phi, theta, psi];
1554
+ };
1555
 
1556
  /**
1557
  * Converts device orientation API Tait-Bryan angles to a quaternion.
1599
  * @param {DeviceOrientationEvent} event - Device orientation event.
1600
  */
1601
  function orientationListener(e) {
1602
+ if (e.hasOwnProperty('requestPermission'))
1603
+ e.requestPermission()
1604
  var q = computeQuaternion(e.alpha, e.beta, e.gamma).toEulerAngles();
1605
  if (typeof(orientation) == 'number' && orientation < 10) {
1606
  // This kludge is necessary because iOS sometimes provides a few stale
1706
  hs.yaw = Number(hs.yaw) || 0;
1707
 
1708
  var div = document.createElement('div');
1709
+ div.className = 'pnlm-hotspot-base';
1710
  if (hs.cssClass)
1711
  div.className += ' ' + hs.cssClass;
1712
  else
1719
  var a;
1720
  if (hs.video) {
1721
  var video = document.createElement('video'),
1722
+ vidp = hs.video;
1723
+ if (config.basePath && !absoluteURL(vidp))
1724
+ vidp = config.basePath + vidp;
1725
+ video.src = sanitizeURL(vidp);
1726
  video.controls = true;
1727
  video.style.width = hs.width + 'px';
1728
  renderContainer.appendChild(div);
1729
  span.appendChild(video);
1730
  } else if (hs.image) {
1731
+ var imgp = hs.image;
1732
+ if (config.basePath && !absoluteURL(imgp))
1733
+ imgp = config.basePath + imgp;
1734
  a = document.createElement('a');
1735
+ a.href = sanitizeURL(hs.URL ? hs.URL : imgp);
1736
  a.target = '_blank';
1737
  span.appendChild(a);
1738
  var image = document.createElement('img');
1739
+ image.src = sanitizeURL(imgp);
1740
  image.style.width = hs.width + 'px';
1741
  image.style.paddingTop = '5px';
1742
  renderContainer.appendChild(div);
1744
  span.style.maxWidth = 'initial';
1745
  } else if (hs.URL) {
1746
  a = document.createElement('a');
1747
+ a.href = sanitizeURL(hs.URL);
1748
+ if (hs.attributes) {
1749
+ for (var key in hs.attributes) {
1750
+ a.setAttribute(key, hs.attributes[key]);
1751
+ }
1752
+ } else {
1753
+ a.target = '_blank';
1754
+ }
1755
  renderContainer.appendChild(a);
1756
+ div.className += ' pnlm-pointer';
1757
+ span.className += ' pnlm-pointer';
1758
  a.appendChild(div);
1759
  } else {
1760
  if (hs.sceneId) {
1765
  }
1766
  return false;
1767
  };
1768
+ div.className += ' pnlm-pointer';
1769
+ span.className += ' pnlm-pointer';
1770
  }
1771
  renderContainer.appendChild(div);
1772
  }
1784
  div.addEventListener('click', function(e) {
1785
  hs.clickHandlerFunc(e, hs.clickHandlerArgs);
1786
  }, 'false');
1787
+ div.className += ' pnlm-pointer';
1788
+ span.className += ' pnlm-pointer';
1789
  }
1790
  hs.div = div;
1791
+ }
1792
 
1793
  /**
1794
  * Creates hot spot elements for the current scene.
1821
  if (hs) {
1822
  for (var i = 0; i < hs.length; i++) {
1823
  var current = hs[i].div;
1824
+ if (current) {
1825
+ while (current.parentNode && current.parentNode != renderContainer) {
1826
+ current = current.parentNode;
1827
+ }
1828
+ renderContainer.removeChild(current);
1829
  }
 
1830
  delete hs[i].div;
1831
  }
1832
  }
1868
  coord[1] += (canvasHeight - hs.div.offsetHeight) / 2;
1869
  var transform = 'translate(' + coord[0] + 'px, ' + coord[1] +
1870
  'px) translateZ(9999px) rotate(' + config.roll + 'deg)';
1871
+ if (hs.scale) {
1872
+ transform += ' scale(' + (origHfov/config.hfov) / z + ')';
1873
+ }
1874
  hs.div.style.webkitTransform = transform;
1875
  hs.div.style.MozTransform = transform;
1876
  hs.div.style.transform = transform;
1979
  p = config.basePath + p;
1980
  preview = document.createElement('div');
1981
  preview.className = 'pnlm-preview-img';
1982
+ preview.style.backgroundImage = "url('" + sanitizeURLForCss(p) + "')";
1983
  renderContainer.appendChild(preview);
1984
  }
1985
 
2015
  break;
2016
 
2017
  case 'author':
2018
+ var authorText = escapeHTML(config[key]);
2019
+ if (config.authorURL) {
2020
+ var authorLink = document.createElement('a');
2021
+ authorLink.href = sanitizeURL(config['authorURL']);
2022
+ authorLink.target = '_blank';
2023
+ authorLink.innerHTML = escapeHTML(config[key]);
2024
+ authorText = authorLink.outerHTML;
2025
+ }
2026
+ infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', authorText);
2027
  infoDisplay.container.style.display = 'inline';
2028
  break;
2029
 
2030
  case 'fallback':
2031
+ var link = document.createElement('a');
2032
+ link.href = sanitizeURL(config[key]);
2033
+ link.target = '_blank';
2034
+ link.textContent = 'Click here to view this panorama in an alternative viewer.';
2035
+ var message = document.createElement('p');
2036
+ message.textContent = 'Your browser does not support WebGL.';
2037
+ message.appendChild(document.createElement('br'));
2038
+ message.appendChild(link);
2039
+ infoDisplay.errorMsg.innerHTML = ''; // Removes all children nodes
2040
+ infoDisplay.errorMsg.appendChild(message);
2041
  break;
2042
 
2043
  case 'hfov':
2155
  * Event handler for fullscreen changes.
2156
  * @private
2157
  */
2158
+ function onFullScreenChange(resize) {
2159
+ if (document.fullscreenElement || document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement) {
2160
  controls.fullscreen.classList.add('pnlm-fullscreen-toggle-button-active');
2161
  fullscreenActive = true;
2162
  } else {
2163
  controls.fullscreen.classList.remove('pnlm-fullscreen-toggle-button-active');
2164
  fullscreenActive = false;
2165
  }
2166
+ if (resize !== 'resize')
2167
+ fireEvent('fullscreenchange', fullscreenActive);
2168
  // Resize renderer (deal with browser quirks and fixes #155)
2169
  renderer.resize();
2170
  setHfov(config.hfov);
2202
  function constrainHfov(hfov) {
2203
  // Keep field of view within bounds
2204
  var minHfov = config.minHfov;
2205
+ if (config.type == 'multires' && renderer && !config.multiResMinHfov) {
2206
  minHfov = Math.min(minHfov, renderer.getCanvas().width / (config.multiRes.cubeResolution / 90 * 0.9));
2207
  }
2208
  if (minHfov > config.maxHfov) {
2209
  // Don't change view if bounds don't make sense
2210
+ console.log('HFOV bounds do not make sense (minHfov > maxHfov).');
2211
  return config.hfov;
2212
+ }
2213
+ var newHfov = config.hfov;
2214
+ if (hfov < minHfov) {
2215
+ newHfov = minHfov;
2216
  } else if (hfov > config.maxHfov) {
2217
+ newHfov = config.maxHfov;
2218
  } else {
2219
+ newHfov = hfov;
2220
  }
2221
+ // Optionally avoid showing background (empty space) on top or bottom by adapting newHfov
2222
+ if (config.avoidShowingBackground && renderer) {
2223
+ var canvas = renderer.getCanvas();
2224
+ newHfov = Math.min(newHfov,
2225
+ Math.atan(Math.tan((config.maxPitch - config.minPitch) / 360 * Math.PI) /
2226
+ canvas.height * canvas.width) * 360 / Math.PI);
2227
+ }
2228
+ return newHfov;
2229
  }
2230
 
2231
  /**
2235
  */
2236
  function setHfov(hfov) {
2237
  config.hfov = constrainHfov(hfov);
2238
+ fireEvent('zoomchange', config.hfov);
2239
  }
2240
 
2241
  /**
2257
  // since it is a new scene and the error from previous maybe because of lacking
2258
  // memory etc and not because of a lack of WebGL support etc
2259
  clearError();
2260
+ loaded = false;
2261
 
2262
  controls.load.style.display = 'none';
2263
  infoDisplay.load.box.style.display = 'inline';
2274
  * @param {boolean} [fadeDone] - If `true`, fade setup is skipped.
2275
  */
2276
  function loadScene(sceneId, targetPitch, targetYaw, targetHfov, fadeDone) {
2277
+ if (!loaded)
2278
+ fadeDone = true; // Don't try to fade when there isn't a scene loaded
2279
  loaded = false;
2280
  animatedMove = {};
2281
 
2380
  .split('\n').join('<br>'); // Allow line breaks
2381
  }
2382
 
2383
+ /**
2384
+ * Removes possibility of XSS attacks with URLs.
2385
+ * The URL cannot be of protocol 'javascript'.
2386
+ * @private
2387
+ * @param {string} url - URL to sanitize
2388
+ * @returns {string} Sanitized URL
2389
+ */
2390
+ function sanitizeURL(url) {
2391
+ if (url.trim().toLowerCase().indexOf('javascript:') === 0) {
2392
+ return 'about:blank';
2393
+ }
2394
+ return url;
2395
+ }
2396
+
2397
+ /**
2398
+ * Removes possibility of XSS atacks with URLs for CSS.
2399
+ * The URL will be sanitized with `sanitizeURL()` and single quotes
2400
+ * and double quotes escaped.
2401
+ * @private
2402
+ * @param {string} url - URL to sanitize
2403
+ * @returns {string} Sanitized URL
2404
+ */
2405
+ function sanitizeURLForCss(url) {
2406
+ return sanitizeURL(url)
2407
+ .replace(/"/g, '%22')
2408
+ .replace(/'/g, '%27');
2409
+ }
2410
+
2411
  /**
2412
  * Checks whether or not a panorama is loaded.
2413
  * @memberof Viewer
2415
  * @returns {boolean} `true` if a panorama is loaded, else `false`
2416
  */
2417
  this.isLoaded = function() {
2418
+ return Boolean(loaded);
2419
  };
2420
 
2421
  /**
2439
  * @returns {Viewer} `this`
2440
  */
2441
  this.setPitch = function(pitch, animated, callback, callbackArgs) {
2442
+ latestInteraction = Date.now();
2443
+ if (Math.abs(pitch - config.pitch) <= eps) {
2444
+ if (typeof callback == 'function')
2445
+ callback(callbackArgs);
2446
+ return this;
2447
+ }
2448
  animated = animated == undefined ? 1000: Number(animated);
2449
  if (animated) {
2450
  animatedMove.pitch = {
2451
  'startTime': Date.now(),
2452
  'startPosition': config.pitch,
2453
  'endPosition': pitch,
2454
+ 'duration': animated
2455
+ };
2456
+ if (typeof callback == 'function')
2457
+ setTimeout(function(){callback(callbackArgs);}, animated);
2458
  } else {
2459
  config.pitch = pitch;
2460
  }
2506
  * @returns {Viewer} `this`
2507
  */
2508
  this.setYaw = function(yaw, animated, callback, callbackArgs) {
2509
+ latestInteraction = Date.now();
2510
+ if (Math.abs(yaw - config.yaw) <= eps) {
2511
+ if (typeof callback == 'function')
2512
+ callback(callbackArgs);
2513
+ return this;
2514
+ }
2515
  animated = animated == undefined ? 1000: Number(animated);
2516
+ yaw = ((yaw + 180) % 360) - 180; // Keep in bounds
2517
  if (animated) {
2518
  // Animate in shortest direction
2519
  if (config.yaw - yaw > 180)
2520
+ yaw += 360;
2521
  else if (yaw - config.yaw > 180)
2522
+ yaw -= 360;
2523
 
2524
  animatedMove.yaw = {
2525
  'startTime': Date.now(),
2526
  'startPosition': config.yaw,
2527
  'endPosition': yaw,
2528
+ 'duration': animated
2529
+ };
2530
+ if (typeof callback == 'function')
2531
+ setTimeout(function(){callback(callbackArgs);}, animated);
2532
  } else {
2533
  config.yaw = yaw;
2534
  }
2580
  * @returns {Viewer} `this`
2581
  */
2582
  this.setHfov = function(hfov, animated, callback, callbackArgs) {
2583
+ latestInteraction = Date.now();
2584
+ if (Math.abs(hfov - config.hfov) <= eps) {
2585
+ if (typeof callback == 'function')
2586
+ callback(callbackArgs);
2587
+ return this;
2588
+ }
2589
  animated = animated == undefined ? 1000: Number(animated);
2590
  if (animated) {
2591
  animatedMove.hfov = {
2592
  'startTime': Date.now(),
2593
  'startPosition': config.hfov,
2594
  'endPosition': constrainHfov(hfov),
2595
+ 'duration': animated
2596
+ };
2597
+ if (typeof callback == 'function')
2598
+ setTimeout(function(){callback(callbackArgs);}, animated);
2599
  } else {
2600
  setHfov(hfov);
2601
  }
2641
  */
2642
  this.lookAt = function(pitch, yaw, hfov, animated, callback, callbackArgs) {
2643
  animated = animated == undefined ? 1000: Number(animated);
2644
+ if (pitch !== undefined && Math.abs(pitch - config.pitch) > eps) {
2645
  this.setPitch(pitch, animated, callback, callbackArgs);
2646
  callback = undefined;
2647
  }
2648
+ if (yaw !== undefined && Math.abs(yaw - config.yaw) > eps) {
2649
  this.setYaw(yaw, animated, callback, callbackArgs);
2650
  callback = undefined;
2651
  }
2652
+ if (hfov !== undefined && Math.abs(hfov - config.hfov) > eps) {
2653
  this.setHfov(hfov, animated, callback, callbackArgs);
2654
+ callback = undefined;
2655
+ }
2656
+ if (typeof callback == 'function')
2657
+ callback(callbackArgs);
2658
  return this;
2659
+ };
2660
 
2661
  /**
2662
  * Returns the panorama's north offset.
2731
 
2732
  /**
2733
  * Start auto rotation.
2734
+ *
2735
+ * Before starting rotation, the viewer is panned to `pitch`.
2736
  * @memberof Viewer
2737
  * @instance
2738
  * @param {number} [speed] - Auto rotation speed / direction. If not specified, previous value is used.
2739
+ * @param {number} [pitch] - The pitch to rotate at. If not specified, inital pitch is used.
2740
  * @returns {Viewer} `this`
2741
  */
2742
+ this.startAutoRotate = function(speed, pitch) {
2743
  speed = speed || autoRotateSpeed || 1;
2744
+ pitch = pitch === undefined ? origPitch : pitch;
2745
  config.autoRotate = speed;
2746
+ _this.lookAt(pitch, undefined, origHfov, 3000);
2747
  animateInit();
2748
  return this;
2749
  };
2761
  return this;
2762
  };
2763
 
2764
+ /**
2765
+ * Stops all movement.
2766
+ * @memberof Viewer
2767
+ * @instance
2768
+ */
2769
+ this.stopMovement = function() {
2770
+ stopAnimation();
2771
+ speed = {'yaw': 0, 'pitch': 0, 'hfov': 0};
2772
+ };
2773
+
2774
  /**
2775
  * Returns the panorama renderer.
2776
  * @memberof Viewer
2795
  else
2796
  animateInit();
2797
  return this;
2798
+ };
2799
 
2800
  /**
2801
  * Calculate panorama pitch and yaw from location of mouse event.
2806
  */
2807
  this.mouseEventToCoords = function(event) {
2808
  return mouseEventToCoords(event);
2809
+ };
2810
 
2811
  /**
2812
  * Change scene being viewed.
2819
  * @returns {Viewer} `this`
2820
  */
2821
  this.loadScene = function(sceneId, pitch, yaw, hfov) {
2822
+ if (loaded !== false)
2823
  loadScene(sceneId, pitch, yaw, hfov);
2824
  return this;
2825
+ };
2826
 
2827
  /**
2828
  * Get ID of current scene.
2832
  */
2833
  this.getScene = function() {
2834
  return config.scene;
2835
+ };
2836
 
2837
  /**
2838
  * Add a new scene.
2870
  this.toggleFullscreen = function() {
2871
  toggleFullscreen();
2872
  return this;
2873
+ };
2874
 
2875
  /**
2876
  * Get configuration of current scene.
2880
  */
2881
  this.getConfig = function() {
2882
  return config;
2883
+ };
2884
 
2885
  /**
2886
  * Get viewer's container element.
2890
  */
2891
  this.getContainer = function() {
2892
  return container;
2893
+ };
2894
 
2895
  /**
2896
  * Add a new hot spot.
2916
  }
2917
  initialConfig.scenes[id].hotSpots.push(hs); // Add hot spot to config
2918
  } else {
2919
+ throw 'Invalid scene ID!';
2920
  }
2921
  }
2922
  if (sceneId === undefined || config.scene == sceneId) {
2926
  renderHotSpot(hs);
2927
  }
2928
  return this;
2929
+ };
2930
 
2931
  /**
2932
  * Remove a hot spot.
2933
  * @memberof Viewer
2934
  * @instance
2935
  * @param {string} hotSpotId - The ID of the hot spot
2936
+ * @param {string} [sceneId] - Removes hot spot from specified scene if provided, else from current scene
2937
  * @returns {boolean} True if deletion is successful, else false
2938
  */
2939
+ this.removeHotSpot = function(hotSpotId, sceneId) {
2940
+ if (sceneId === undefined || config.scene == sceneId) {
2941
+ if (!config.hotSpots)
2942
+ return false;
2943
+ for (var i = 0; i < config.hotSpots.length; i++) {
2944
+ if (config.hotSpots[i].hasOwnProperty('id') &&
2945
+ config.hotSpots[i].id === hotSpotId) {
2946
+ // Delete hot spot DOM elements
2947
+ var current = config.hotSpots[i].div;
2948
+ while (current.parentNode != renderContainer)
2949
+ current = current.parentNode;
2950
+ renderContainer.removeChild(current);
2951
+ delete config.hotSpots[i].div;
2952
+ // Remove hot spot from configuration
2953
+ config.hotSpots.splice(i, 1);
2954
+ return true;
2955
+ }
2956
+ }
2957
+ } else {
2958
+ if (initialConfig.scenes.hasOwnProperty(sceneId)) {
2959
+ if (!initialConfig.scenes[sceneId].hasOwnProperty('hotSpots'))
2960
+ return false;
2961
+ for (var j = 0; j < initialConfig.scenes[sceneId].hotSpots.length; j++) {
2962
+ if (initialConfig.scenes[sceneId].hotSpots[j].hasOwnProperty('id') &&
2963
+ initialConfig.scenes[sceneId].hotSpots[j].id === hotSpotId) {
2964
+ // Remove hot spot from configuration
2965
+ initialConfig.scenes[sceneId].hotSpots.splice(j, 1);
2966
+ return true;
2967
+ }
2968
+ }
2969
+ } else {
2970
+ return false;
2971
  }
2972
  }
2973
+ };
 
2974
 
2975
  /**
2976
  * This method should be called if the viewer's container is resized.
2978
  * @instance
2979
  */
2980
  this.resize = function() {
2981
+ if (renderer)
2982
+ onDocumentResize();
2983
+ };
2984
 
2985
  /**
2986
  * Check if a panorama is loaded.
2990
  */
2991
  this.isLoaded = function() {
2992
  return loaded;
2993
+ };
2994
 
2995
  /**
2996
  * Check if device orientation control is supported.
3000
  */
3001
  this.isOrientationSupported = function() {
3002
  return orientationSupport || false;
3003
+ };
3004
 
3005
  /**
3006
  * Stop using device orientation.
3009
  */
3010
  this.stopOrientation = function() {
3011
  stopOrientation();
3012
+ };
3013
 
3014
  /**
3015
  * Start using device orientation (does nothing if not supported).
3019
  this.startOrientation = function() {
3020
  if (orientationSupport)
3021
  startOrientation();
3022
+ };
3023
 
3024
  /**
3025
  * Check if device orientation control is currently activated.
3029
  */
3030
  this.isOrientationActive = function() {
3031
  return Boolean(orientation);
3032
+ };
3033
 
3034
  /**
3035
  * Subscribe listener to specified event.
3043
  externalEventListeners[type] = externalEventListeners[type] || [];
3044
  externalEventListeners[type].push(listener);
3045
  return this;
3046
+ };
3047
 
3048
  /**
3049
  * Remove an event listener (or listeners).
3073
  delete externalEventListeners[type];
3074
  }
3075
  return this;
3076
+ };
3077
 
3078
  /**
3079
  * Fire listeners attached to specified event.
3095
  * @memberof Viewer
3096
  */
3097
  this.destroy = function() {
3098
+ destroyed = true;
3099
+ clearTimeout(autoRotateStart);
3100
+
3101
  if (renderer)
3102
+ renderer.destroy();
3103
  if (listenersAdded) {
 
 
3104
  document.removeEventListener('mousemove', onDocumentMouseMove, false);
3105
  document.removeEventListener('mouseup', onDocumentMouseUp, false);
 
 
3106
  container.removeEventListener('mozfullscreenchange', onFullScreenChange, false);
3107
  container.removeEventListener('webkitfullscreenchange', onFullScreenChange, false);
3108
  container.removeEventListener('msfullscreenchange', onFullScreenChange, false);
3113
  container.removeEventListener('keyup', onDocumentKeyUp, false);
3114
  container.removeEventListener('blur', clearKeys, false);
3115
  document.removeEventListener('mouseleave', onDocumentMouseUp, false);
 
 
 
 
 
 
 
3116
  }
3117
  container.innerHTML = '';
3118
  container.classList.remove('pnlm-container');
3119
+ };
 
 
3120
 
3121
  }
3122
 
wpvr.php CHANGED
@@ -16,7 +16,7 @@
16
  * Plugin Name: WP VR
17
  * Plugin URI: https://rextheme.com/wpvr/
18
  * Description: WP VR - 360 Panorama and virtual tour creator for WordPress is a customized panaroma & virtual builder tool for WordPress Website.
19
- * Version: 3.4.3
20
  * Author: Rextheme
21
  * Author URI: http://rextheme.com/
22
  * License: GPL-2.0+
@@ -310,14 +310,14 @@ function wpvr_block_render( $attributes ) {
310
  $scene_author = sanitize_text_field($panoscenes["scene-author"]);
311
  }
312
 
313
- $default_scene_pitch = '';
314
  if (isset($panoscenes["scene-pitch"])) {
315
- $default_scene_pitch = $panoscenes["scene-pitch"];
316
  }
317
 
318
- $default_scene_yaw = '';
319
  if (isset($panoscenes["scene-yaw"])) {
320
- $default_scene_yaw = $panoscenes["scene-yaw"];
321
  }
322
 
323
  $scene_max_pitch = '';
@@ -630,6 +630,16 @@ function wpvr_block_render( $attributes ) {
630
  $html .= '}';
631
  $html .= '}';
632
  $html .= 'var panoshow'.$id.' = pannellum.viewer(response[0]["panoid"], scenes);';
 
 
 
 
 
 
 
 
 
 
633
  $html .= 'var touchtime = 0;';
634
  if ($vrgallery) {
635
  if (isset($panodata["scene-list"])) {
16
  * Plugin Name: WP VR
17
  * Plugin URI: https://rextheme.com/wpvr/
18
  * Description: WP VR - 360 Panorama and virtual tour creator for WordPress is a customized panaroma & virtual builder tool for WordPress Website.
19
+ * Version: 3.4.4
20
  * Author: Rextheme
21
  * Author URI: http://rextheme.com/
22
  * License: GPL-2.0+
310
  $scene_author = sanitize_text_field($panoscenes["scene-author"]);
311
  }
312
 
313
+ $default_scene_pitch = null;
314
  if (isset($panoscenes["scene-pitch"])) {
315
+ $default_scene_pitch = (float)$panoscenes["scene-pitch"];
316
  }
317
 
318
+ $default_scene_yaw = null;
319
  if (isset($panoscenes["scene-yaw"])) {
320
+ $default_scene_yaw = (float)$panoscenes["scene-yaw"];
321
  }
322
 
323
  $scene_max_pitch = '';
630
  $html .= '}';
631
  $html .= '}';
632
  $html .= 'var panoshow'.$id.' = pannellum.viewer(response[0]["panoid"], scenes);';
633
+ $html .= '
634
+ if (scenes.autoRotate) {
635
+ panoshow'.$id.'.on("load", function (){
636
+ setTimeout(function(){ panoshow'.$id.'.startAutoRotate(scenes.autoRotate, 0); }, 3000);
637
+ });
638
+ panoshow'.$id.'.on("scenechange", function (){
639
+ setTimeout(function(){ panoshow'.$id.'.startAutoRotate(scenes.autoRotate, 0); }, 3000);
640
+ });
641
+ }
642
+ ';
643
  $html .= 'var touchtime = 0;';
644
  if ($vrgallery) {
645
  if (isset($panodata["scene-list"])) {