vendor: 9,103 bytes, lines 1-214
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 7 * in the Software without restriction, including without limitation the rights 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 * copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice shall be included in 13 * all copies or substantial portions of the Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 * THE SOFTWARE. 22 */ 23 24window.pannellum = (function(window, document, undefined) { 25 26 'use strict'; 27 28 /** 29 * Creates a new panorama viewer. 30 * @constructor 31 * @param {HTMLElement|string} container - The container (div) element for the 32 * viewer, or its ID. 33 * @param {Object} initialConfig - Inital configuration for viewer. 34 */ 35 function Viewer(container, initialConfig) { 36 37 var _this = this; 38 39// Declare variables 40 var config, 41 renderer, 42 preview, 43 isUserInteracting = false, 44 latestInteraction = Date.now(), 45 onPointerDownPointerX = 0, 46 onPointerDownPointerY = 0, 47 onPointerDownPointerDist = -1, 48 onPointerDownYaw = 0, 49 onPointerDownPitch = 0, 50 keysDown = new Array(10), 51 fullscreenActive = false, 52 loaded, 53 error = false, 54 isTimedOut = false, 55 listenersAdded = false, 56 panoImage, 57 prevTime, 58 speed = {'yaw': 0, 'pitch': 0, 'hfov': 0}, 59 animating = false, 60 orientation = false, 61 orientationYawOffset = 0, 62 autoRotateStart, 63 autoRotateSpeed = 0, 64 origHfov, 65 origPitch, 66 animatedMove = {}, 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, 81 maxPitch: undefined, 82 yaw: 0, 83 minYaw: -180, 84 maxYaw: 180, 85 roll: 0, 86 haov: 360, 87 vaov: 180, 88 vOffset: 0, 89 autoRotate: false, 90 autoRotateInactivityDelay: -1, 91 autoRotateStopDelay: undefined, 92 type: 'equirectangular', 93 northOffset: 0, 94 showFullscreenCtrl: true, 95 dynamic: false, 96 dynamicUpdate: false, 97 doubleClickZoom: true, 98 keyboardZoom: true, 99 mouseZoom: true, 100 showZoomCtrl: true, 101 autoLoad: false, 102 showControls: 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, 107, 109, 173, 187, 189], 113 friction: 0.15 114 }; 115 116// Translatable / configurable strings 117// Some strings contain '%s', which is a placeholder for inserted values 118// When setting strings in external configuration, `\n` should be used instead of `<br>` to insert line breaks 119 defaultConfig.strings = { 120 // Labels 121 loadButtonLabel: 'Click to<br>Load<br>Panorama', 122 loadingLabel: 'Loading...', 123 bylineLabel: ' %s', // One substitution: author 124 125 // Errors 126 noPanoramaError: 'No panorama image was specified.', 127 fileAccessError: 'The file %s could not be accessed.', // One substitution: file URL 128 malformedURLError: 'There is something wrong with the panorama URL.', 129 iOS8WebGLError: "Due to iOS 8's broken WebGL implementation, only " + 130 "progressive encoded JPEGs work for your device (this " + 131 "panorama uses standard encoding).", 132 genericWebGLError: 'Your browser does not have the necessary WebGL support to display this panorama.', 133 textureSizeError: 'This panorama is too big for your device! It\'s ' + 134 '%spx wide, but your device only supports images up to ' + 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 145// Create container for ui 146 var uiContainer = document.createElement('div'); 147 uiContainer.className = 'pnlm-ui'; 148 container.appendChild(uiContainer); 149 150// Create container for renderer 151 var renderContainer = document.createElement('div'); 152 renderContainer.className = 'pnlm-render-container'; 153 container.appendChild(renderContainer); 154 var dragFix = document.createElement('div'); 155 dragFix.className = 'pnlm-dragfix'; 156 uiContainer.appendChild(dragFix); 157 158// Display about information on right click off 159// var aboutMsg = document.createElement('span'); 160// aboutMsg.className = 'pnlm-about-msg'; 161//==wpvr custom rextheme link==// 162// aboutMsg.innerHTML = '<a href="https://rextheme.com/docs/wpvr-360-panorama-and-virtual-tour-creator-for-wordpress/" target="_blank">Rextheme</a>'; 163//==wpvr custom rextheme link end==// 164// uiContainer.appendChild(aboutMsg); 165// dragFix.addEventListener('contextmenu', aboutMessage); 166 167// Create info display 168 var infoDisplay = {}; 169 170// Hot spot debug indicator 171 var hotSpotDebugIndicator = document.createElement('div'); 172 hotSpotDebugIndicator.className = 'pnlm-sprite pnlm-hot-spot-debug-indicator'; 173 uiContainer.appendChild(hotSpotDebugIndicator); 174 175// Panorama info 176 infoDisplay.container = document.createElement('div'); 177 infoDisplay.container.className = 'pnlm-panorama-info'; 178 infoDisplay.title = document.createElement('div'); 179 infoDisplay.title.className = 'pnlm-title-box'; 180 infoDisplay.container.appendChild(infoDisplay.title); 181 infoDisplay.author = document.createElement('div'); 182 infoDisplay.author.className = 'pnlm-author-box'; 183 infoDisplay.container.appendChild(infoDisplay.author); 184 uiContainer.appendChild(infoDisplay.container); 185 186// Load box 187 infoDisplay.load = {}; 188 infoDisplay.load.box = document.createElement('div'); 189 infoDisplay.load.box.className = 'pnlm-load-box'; 190 infoDisplay.load.boxp = document.createElement('p'); 191 infoDisplay.load.box.appendChild(infoDisplay.load.boxp); 192 infoDisplay.load.lbox = document.createElement('div'); 193 infoDisplay.load.lbox.className = 'pnlm-lbox'; 194 infoDisplay.load.lbox.innerHTML = '<div class="pnlm-loading"></div>'; 195 infoDisplay.load.box.appendChild(infoDisplay.load.lbox); 196 infoDisplay.load.lbar = document.createElement('div'); 197 infoDisplay.load.lbar.className = 'pnlm-lbar'; 198 infoDisplay.load.lbarFill = document.createElement('div'); 199 infoDisplay.load.lbarFill.className = 'pnlm-lbar-fill'; 200 infoDisplay.load.lbar.appendChild(infoDisplay.load.lbarFill); 201 infoDisplay.load.box.appendChild(infoDisplay.load.lbar); 202 infoDisplay.load.msg = document.createElement('p'); 203 infoDisplay.load.msg.className = 'pnlm-lmsg'; 204 infoDisplay.load.box.appendChild(infoDisplay.load.msg); 205 uiContainer.appendChild(infoDisplay.load.box); 206 207// Error message 208 infoDisplay.errorMsg = document.createElement('div'); 209 infoDisplay.errorMsg.className = 'pnlm-error-msg pnlm-info-box'; 210 uiContainer.appendChild(infoDisplay.errorMsg); 211 212// Create controls 213 var controls = {}; 214 controls.container = document.createElement('div');
215 controls.container.className = 'pnlm-controls-container'; 216 uiContainer.appendChild(controls.container); 217 218// Load button 219 controls.load = document.createElement('div'); 220 controls.load.className = 'pnlm-load-button'; 221 controls.load.addEventListener('click', function() { 222 processOptions(); 223 load(); 224 }); 225 uiContainer.appendChild(controls.load); 226 227// Zoom controls 228 controls.zoom = document.createElement('div'); 229 controls.zoom.className = 'pnlm-zoom-controls pnlm-controls'; 230 controls.zoomIn = document.createElement('div'); 231 controls.zoomIn.className = 'pnlm-zoom-in pnlm-sprite pnlm-control'; 232 controls.zoomIn.addEventListener('click', zoomIn); 233 controls.zoom.appendChild(controls.zoomIn); 234 controls.zoomOut = document.createElement('div'); 235 controls.zoomOut.className = 'pnlm-zoom-out pnlm-sprite pnlm-control'; 236 controls.zoomOut.addEventListener('click', zoomOut); 237 controls.zoom.appendChild(controls.zoomOut); 238 controls.container.appendChild(controls.zoom); 239 240// Fullscreen toggle 241 controls.fullscreen = document.createElement('div'); 242 controls.fullscreen.addEventListener('click', toggleFullscreen); 243 controls.fullscreen.className = 'pnlm-fullscreen-toggle-button pnlm-sprite pnlm-fullscreen-toggle-button-inactive pnlm-controls pnlm-control'; 244 if (document.fullscreenEnabled || document.mozFullScreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled) 245 controls.container.appendChild(controls.fullscreen); 246 247// Device orientation toggle 248 controls.orientation = document.createElement('div'); 249 controls.orientation.addEventListener('click', function(e) { 250 if (orientation) 251 stopOrientation(); 252 else 253 startOrientation(); 254 }); 255 controls.orientation.addEventListener('mousedown', function(e) {e.stopPropagation();}); 256 controls.orientation.addEventListener('touchstart', function(e) {e.stopPropagation();}); 257 controls.orientation.addEventListener('pointerdown', function(e) {e.stopPropagation();}); 258 controls.orientation.className = 'pnlm-orientation-button pnlm-orientation-button-inactive pnlm-sprite pnlm-controls pnlm-control'; 259 var orientationSupport = false; 260 if (window.DeviceOrientationEvent && location.protocol == 'https:' && 261 navigator.userAgent.toLowerCase().indexOf('mobi') >= 0) { 262 // This user agent check is here because there's no way to check if a 263 // device has an inertia measurement unit. We used to be able to check if a 264 // DeviceOrientationEvent had non-null values, but with iOS 13 requiring a 265 // permission prompt to access such events, this is no longer possible. 266 controls.container.appendChild(controls.orientation); 267 orientationSupport = true; 268 } 269 270// Compass 271 var compass = document.createElement('div'); 272 compass.className = 'pnlm-compass pnlm-controls pnlm-control'; 273 uiContainer.appendChild(compass); 274 275// Load and process configuration 276 if (initialConfig.firstScene) { 277 // Activate first scene if specified in URL 278 mergeConfig(initialConfig.firstScene); 279 } else if (initialConfig.default && initialConfig.default.firstScene) { 280 // Activate first scene if specified in file 281 mergeConfig(initialConfig.default.firstScene); 282 } else { 283 mergeConfig(null); 284 } 285 processOptions(true); 286 287 /** 288 * Initializes viewer. 289 * @private 290 */ 291 function init() { 292 // Display an error for IE 9 as it doesn't work but also doesn't otherwise 293 // show an error (older versions don't work at all) 294 // Based on: http://stackoverflow.com/a/10965203 295 var div = document.createElement("div"); 296 div.innerHTML = "<!--[if lte IE 9]><i></i><![endif]-->"; 297 if (div.getElementsByTagName("i").length == 1) { 298 anError(); 299 return; 300 } 301 302 origHfov = config.hfov; 303 origPitch = config.pitch; 304 305 var i, p; 306 307 if (config.type == 'cubemap') { 308 panoImage = []; 309 for (i = 0; i < 6; i++) { 310 panoImage.push(new Image()); 311 panoImage[i].crossOrigin = config.crossOrigin; 312 } 313 infoDisplay.load.lbox.style.display = 'block'; 314 infoDisplay.load.lbar.style.display = 'none'; 315 } else if (config.type == 'multires') { 316 var c = JSON.parse(JSON.stringify(config.multiRes)); // Deep copy 317 // Avoid "undefined" in path, check (optional) multiRes.basePath, too 318 // Use only multiRes.basePath if it's an absolute URL 319 if (config.basePath && config.multiRes.basePath && 320 !(/^(?:[a-z]+:)?\/\//i.test(config.multiRes.basePath))) { 321 c.basePath = config.basePath + config.multiRes.basePath; 322 } else if (config.multiRes.basePath) { 323 c.basePath = config.multiRes.basePath; 324 } else if(config.basePath) { 325 c.basePath = config.basePath; 326 } 327 panoImage = c; 328 } else { 329 if (config.dynamic === true) { 330 panoImage = config.panorama; 331 } else { 332 if (config.panorama === undefined) { 333 anError(config.strings.noPanoramaError); 334 return; 335 } 336 panoImage = new Image(); 337 } 338 } 339 340 // Configure image loading 341 if (config.type == 'cubemap') { 342 // Quick loading counter for synchronous loading 343 var itemsToLoad = 6; 344 345 var onLoad = function() { 346 itemsToLoad--; 347 if (itemsToLoad === 0) { 348 onImageLoad(); 349 } 350 }; 351 352 var onError = function(e) { 353 var a = document.createElement('a'); 354 a.href = e.target.src; 355 a.textContent = a.href;
vendor: 7,842 bytes, lines 356-512
356 anError(config.strings.fileAccessError.replace('%s', a.outerHTML)); 357 }; 358 359 for (i = 0; i < panoImage.length; i++) { 360 p = config.cubeMap[i]; 361 if (p == "null") { // support partial cubemap image with explicitly empty faces 362 console.log('Will use background instead of missing cubemap face ' + i); 363 onLoad(); 364 } else { 365 if (config.basePath && !absoluteURL(p)) { 366 p = config.basePath + p; 367 } 368 panoImage[i].onload = onLoad; 369 panoImage[i].onerror = onError; 370 panoImage[i].src = sanitizeURL(p); 371 } 372 } 373 } else if (config.type == 'multires') { 374 onImageLoad(); 375 } else { 376 p = ''; 377 if (config.basePath) { 378 p = config.basePath; 379 } 380 381 if (config.dynamic !== true) { 382 // Still image 383 p = absoluteURL(config.panorama) ? config.panorama : p + config.panorama; 384 385 panoImage.onload = function() { 386 window.URL.revokeObjectURL(this.src); // Clean up 387 onImageLoad(); 388 }; 389 390 var xhr = new XMLHttpRequest(); 391 xhr.onloadend = function() { 392 if (xhr.status != 200) { 393 // Display error if image can't be loaded 394 var a = document.createElement('a'); 395 a.href = p; 396 a.textContent = a.href; 397 anError(config.strings.fileAccessError.replace('%s', a.outerHTML)); 398 } 399 var img = this.response; 400 parseGPanoXMP(img, p); 401 infoDisplay.load.msg.innerHTML = ''; 402 }; 403 xhr.onprogress = function(e) { 404 if (e.lengthComputable) { 405 // Display progress 406 var percent = e.loaded / e.total * 100; 407 infoDisplay.load.lbarFill.style.width = percent + '%'; 408 var unit, numerator, denominator; 409 if (e.total > 1e6) { 410 unit = 'MB'; 411 numerator = (e.loaded / 1e6).toFixed(2); 412 denominator = (e.total / 1e6).toFixed(2); 413 } else if (e.total > 1e3) { 414 unit = 'kB'; 415 numerator = (e.loaded / 1e3).toFixed(1); 416 denominator = (e.total / 1e3).toFixed(1); 417 } else { 418 unit = 'B'; 419 numerator = e.loaded; 420 denominator = e.total; 421 } 422 infoDisplay.load.msg.innerHTML = numerator + ' / ' + denominator + ' ' + unit; 423 } else { 424 // Display loading spinner 425 infoDisplay.load.lbox.style.display = 'block'; 426 infoDisplay.load.lbar.style.display = 'none'; 427 } 428 }; 429 try { 430 xhr.open('GET', p, true); 431 } catch (e) { 432 // Malformed URL 433 anError(config.strings.malformedURLError); 434 } 435 xhr.responseType = 'blob'; 436 xhr.setRequestHeader('Accept', 'image/*,*/*;q=0.9'); 437 xhr.withCredentials = config.crossOrigin === 'use-credentials'; 438 xhr.send(); 439 } 440 } 441 442 if (config.draggable) 443 uiContainer.classList.add('pnlm-grab'); 444 uiContainer.classList.remove('pnlm-grabbing'); 445 446 // Properly handle switching to dynamic scenes 447 update = config.dynamicUpdate === true; 448 if (config.dynamic && update) { 449 panoImage = config.panorama; 450 onImageLoad(); 451 } 452 } 453 454 /** 455 * Test if URL is absolute or relative. 456 * @private 457 * @param {string} url - URL to test 458 * @returns {boolean} True if absolute, else false 459 */ 460 function absoluteURL(url) { 461 // From http://stackoverflow.com/a/19709846 462 return new RegExp('^(?:[a-z]+:)?//', 'i').test(url) || url[0] == '/' || url.slice(0, 5) == 'blob:'; 463 } 464 465 /** 466 * Create renderer and initialize event listeners once image is loaded. 467 * @private 468 */ 469 function onImageLoad() { 470 if (!renderer) 471 renderer = new libpannellum.renderer(renderContainer); 472 473 // Only add event listeners once 474 if (!listenersAdded) { 475 listenersAdded = true; 476 dragFix.addEventListener('mousedown', onDocumentMouseDown, false); 477 document.addEventListener('mousemove', onDocumentMouseMove, false); 478 document.addEventListener('compasschange', onDocumentCompassChange, false); 479 document.addEventListener('mouseup', onDocumentMouseUp, false); 480 if (config.mouseZoom) { 481 uiContainer.addEventListener('mousewheel', onDocumentMouseWheel, false); 482 uiContainer.addEventListener('DOMMouseScroll', onDocumentMouseWheel, false); 483 } 484 if (config.doubleClickZoom) { 485 dragFix.addEventListener('dblclick', onDocumentDoubleClick, false); 486 } 487 container.addEventListener('mozfullscreenchange', onFullScreenChange, false); 488 container.addEventListener('webkitfullscreenchange', onFullScreenChange, false); 489 container.addEventListener('msfullscreenchange', onFullScreenChange, false); 490 container.addEventListener('fullscreenchange', onFullScreenChange, false); 491 window.addEventListener('resize', onDocumentResize, false); 492 window.addEventListener('orientationchange', onDocumentResize, false); 493 if (!config.disableKeyboardCtrl) { 494 container.addEventListener('keydown', onDocumentKeyPress, false); 495 container.addEventListener('keyup', onDocumentKeyUp, false); 496 container.addEventListener('blur', clearKeys, false); 497 } 498 document.addEventListener('mouseleave', onDocumentMouseUp, false); 499 if (document.documentElement.style.pointerAction === '' && 500 document.documentElement.style.touchAction === '') { 501 dragFix.addEventListener('pointerdown', onDocumentPointerDown, false); 502 dragFix.addEventListener('pointermove', onDocumentPointerMove, false); 503 dragFix.addEventListener('pointerup', onDocumentPointerUp, false); 504 dragFix.addEventListener('pointerleave', onDocumentPointerUp, false); 505 } else { 506 dragFix.addEventListener('touchstart', onDocumentTouchStart, false); 507 dragFix.addEventListener('touchmove', onDocumentTouchMove, false); 508 dragFix.addEventListener('touchend', onDocumentTouchEnd, false); 509 } 510 511 // Deal with MS pointer events 512 if (window.navigator.pointerEnabled)
513 container.style.touchAction = 'none'; 514 } 515 516 renderInit(); 517 setHfov(config.hfov); // possibly adapt hfov after configuration and canvas is complete; prevents empty space on top or bottom by zomming out too much 518 setTimeout(function(){isTimedOut = true;}, 500); 519 } 520 521 /** 522 * Parses Google Photo Sphere XMP Metadata. 523 * https://developers.google.com/photo-sphere/metadata/ 524 * @private 525 * @param {Image} image - Image to read XMP metadata from. 526 */ 527 function parseGPanoXMP(image, url) { 528 var reader = new FileReader(); 529 reader.addEventListener('loadend', function() { 530 var img = reader.result; 531 532 // This awful browser specific test exists because iOS 8 does not work 533 // with non-progressive encoded JPEGs. 534 if (navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 8_/)) { 535 var flagIndex = img.indexOf('\xff\xc2'); 536 if (flagIndex < 0 || flagIndex > 65536) 537 anError(config.strings.iOS8WebGLError); 538 } 539 540 var start = img.indexOf('<x:xmpmeta'); 541 if (start > -1 && config.ignoreGPanoXMP !== true) { 542 var xmpData = img.substring(start, img.indexOf('</x:xmpmeta>') + 12); 543 544 // Extract the requested tag from the XMP data 545 var getTag = function(tag) { 546 var result; 547 if (xmpData.indexOf(tag + '="') >= 0) { 548 result = xmpData.substring(xmpData.indexOf(tag + '="') + tag.length + 2); 549 result = result.substring(0, result.indexOf('"')); 550 } else if (xmpData.indexOf(tag + '>') >= 0) { 551 result = xmpData.substring(xmpData.indexOf(tag + '>') + tag.length + 1); 552 result = result.substring(0, result.indexOf('<')); 553 } 554 if (result !== undefined) { 555 return Number(result); 556 } 557 return null; 558 }; 559 560 // Relevant XMP data 561 var xmp = { 562 fullWidth: getTag('GPano:FullPanoWidthPixels'), 563 croppedWidth: getTag('GPano:CroppedAreaImageWidthPixels'), 564 fullHeight: getTag('GPano:FullPanoHeightPixels'), 565 croppedHeight: getTag('GPano:CroppedAreaImageHeightPixels'), 566 topPixels: getTag('GPano:CroppedAreaTopPixels'), 567 heading: getTag('GPano:PoseHeadingDegrees'), 568 horizonPitch: getTag('GPano:PosePitchDegrees'), 569 horizonRoll: getTag('GPano:PoseRollDegrees') 570 }; 571 572 if (xmp.fullWidth !== null && xmp.croppedWidth !== null && 573 xmp.fullHeight !== null && xmp.croppedHeight !== null && 574 xmp.topPixels !== null) { 575 576 // Set up viewer using GPano XMP data 577 if (specifiedPhotoSphereExcludes.indexOf('haov') < 0) 578 config.haov = xmp.croppedWidth / xmp.fullWidth * 360; 579 if (specifiedPhotoSphereExcludes.indexOf('vaov') < 0) 580 config.vaov = xmp.croppedHeight / xmp.fullHeight * 180; 581 if (specifiedPhotoSphereExcludes.indexOf('vOffset') < 0) 582 config.vOffset = ((xmp.topPixels + xmp.croppedHeight / 2) / xmp.fullHeight - 0.5) * -180; 583 if (xmp.heading !== null && specifiedPhotoSphereExcludes.indexOf('northOffset') < 0) { 584 // TODO: make sure this works correctly for partial panoramas 585 config.northOffset = xmp.heading; 586 if (config.compass !== false) { 587 config.compass = true; 588 } 589 } 590 if (xmp.horizonPitch !== null && xmp.horizonRoll !== null) { 591 if (specifiedPhotoSphereExcludes.indexOf('horizonPitch') < 0) 592 config.horizonPitch = xmp.horizonPitch; 593 if (specifiedPhotoSphereExcludes.indexOf('horizonRoll') < 0) 594 config.horizonRoll = xmp.horizonRoll; 595 } 596 597 // TODO: add support for initial view settings 598 } 599 } 600 601 // Load panorama 602 panoImage.src = window.URL.createObjectURL(image); 603 panoImage.onerror = function() { 604 // If the image fails to load, we check the Content Security Policy 605 // headers and see if they block loading images as blobs. If they 606 // do, we load the image directly from the URL. While this should 607 // allow the image to load, it does prevent parsing of XMP data. 608 function getCspHeaders() { 609 if (!window.fetch) 610 return null; 611 return window.fetch(document.location.href) 612 .then(function(resp){ 613 return resp.headers.get('Content-Security-Policy'); 614 }); 615 } 616 getCspHeaders().then(function(cspHeaders) { 617 if (cspHeaders) { 618 var invalidImgSource = cspHeaders.split(";").find(function(p) { 619 var matchstring = p.match(/img-src(.*)/); 620 if (matchstring) { 621 return !matchstring[1].includes("blob"); 622 } 623 }); 624 if (invalidImgSource) { 625 console.log('CSP blocks blobs; reverting to URL.'); 626 panoImage.crossOrigin = config.crossOrigin; 627 panoImage.src = url; 628 } 629 } 630 }); 631 } 632 }); 633 if (reader.readAsBinaryString !== undefined) 634 reader.readAsBinaryString(image); 635 else 636 reader.readAsText(image); 637 } 638 639 /**
640 * Displays an error message. 641 * @private 642 * @param {string} errorMsg - Error message to display. If not specified, a 643 * generic WebGL error is displayed. 644 */ 645 function anError(errorMsg) { 646 if (errorMsg === undefined) 647 errorMsg = config.strings.genericWebGLError; 648 infoDisplay.errorMsg.innerHTML = '<p>' + errorMsg + '</p>'; 649 controls.load.style.display = 'none'; 650 infoDisplay.load.box.style.display = 'none'; 651 infoDisplay.errorMsg.style.display = 'table'; 652 error = true; 653 loaded = undefined; 654 renderContainer.style.display = 'none'; 655 fireEvent('error', errorMsg); 656 } 657 658 /** 659 * Hides error message display. 660 * @private 661 */ 662 function clearError() { 663 if (error) { 664 infoDisplay.load.box.style.display = 'none'; 665 infoDisplay.errorMsg.style.display = 'none'; 666 error = false; 667 renderContainer.style.display = 'block'; 668 fireEvent('errorcleared'); 669 } 670 } 671 672 /** 673 * Displays about message. 674 * @private 675 * @param {MouseEvent} event - Right click location 676 */ 677 function aboutMessage(event) { 678 var pos = mousePosition(event); 679 aboutMsg.style.left = pos.x + 'px'; 680 aboutMsg.style.top = pos.y + 'px'; 681 clearTimeout(aboutMessage.t1); 682 clearTimeout(aboutMessage.t2); 683 aboutMsg.style.display = 'block'; 684 aboutMsg.style.opacity = 1; 685 aboutMessage.t1 = setTimeout(function() {aboutMsg.style.opacity = 0;}, 2000); 686 aboutMessage.t2 = setTimeout(function() {aboutMsg.style.display = 'none';}, 2500); 687 event.preventDefault(); 688 } 689 690 /** 691 * Calculate mouse position relative to top left of viewer container. 692 * @private 693 * @param {MouseEvent} event - Mouse event to use in calculation 694 * @returns {Object} Calculated X and Y coordinates 695 */ 696 function mousePosition(event) { 697 var bounds = container.getBoundingClientRect(); 698 var pos = {}; 699 // pageX / pageY needed for iOS 700 pos.x = (event.clientX || event.pageX) - bounds.left; 701 pos.y = (event.clientY || event.pageY) - bounds.top; 702 return pos; 703 } 704 705 /** 706 * Event handler for mouse clicks. Initializes panning. Prints center and click 707 * location coordinates when hot spot debugging is enabled. 708 * @private 709 * @param {MouseEvent} event - Document mouse down event. 710 */ 711 function onDocumentMouseDown(event) { 712 // Override default action 713 event.preventDefault(); 714 // But not all of it 715 container.focus(); 716 717 // Only do something if the panorama is loaded 718 if (!loaded || !config.draggable) { 719 return; 720 } 721 722 // Calculate mouse position relative to top left of viewer container 723 var pos = mousePosition(event); 724 725 // Log pitch / yaw of mouse click when debugging / placing hot spots 726 if (config.hotSpotDebug) { 727 var coords = mouseEventToCoords(event); 728 console.log('Pitch: ' + coords[0] + ', Yaw: ' + coords[1] + ', Center Pitch: ' + 729 config.pitch + ', Center Yaw: ' + config.yaw + ', HFOV: ' + config.hfov); 730 } 731 732 // Turn off auto-rotation if enabled 733 stopAnimation(); 734 735 stopOrientation(); 736 config.roll = 0; 737 738 speed.hfov = 0; 739 740 isUserInteracting = true; 741 latestInteraction = Date.now(); 742 743 onPointerDownPointerX = pos.x; 744 onPointerDownPointerY = pos.y; 745 746 onPointerDownYaw = config.yaw; 747 onPointerDownPitch = config.pitch; 748 749 uiContainer.classList.add('pnlm-grabbing'); 750 uiContainer.classList.remove('pnlm-grab'); 751 752 fireEvent('mousedown', event); 753 animateInit(); 754 } 755 756 /** 757 * Event handler for double clicks. Zooms in at clicked location 758 * @private 759 * @param {MouseEvent} event - Document mouse down event. 760 */ 761 function onDocumentDoubleClick(event) { 762 if (config.minHfov === config.hfov) { 763 _this.setHfov(origHfov, 1000); 764 } else { 765 var coords = mouseEventToCoords(event); 766 _this.lookAt(coords[0], coords[1], config.minHfov, 1000); 767 } 768 } 769 770 /** 771 * Calculate panorama pitch and yaw from location of mouse event. 772 * @private 773 * @param {MouseEvent} event - Document mouse down event. 774 * @returns {number[]} [pitch, yaw] 775 */ 776 function mouseEventToCoords(event) { 777 var pos = mousePosition(event); 778 var canvas = renderer.getCanvas(); 779 var canvasWidth = canvas.clientWidth, 780 canvasHeight = canvas.clientHeight; 781 var x = pos.x / canvasWidth * 2 - 1; 782 var y = (1 - pos.y / canvasHeight * 2) * canvasHeight / canvasWidth; 783 var focal = 1 / Math.tan(config.hfov * Math.PI / 360); 784 var s = Math.sin(config.pitch * Math.PI / 180); 785 var c = Math.cos(config.pitch * Math.PI / 180); 786 var a = focal * c - y * s; 787 var root = Math.sqrt(x*x + a*a); 788 var pitch = Math.atan((y * c + focal * s) / root) * 180 / Math.PI; 789 var yaw = Math.atan2(x / root, a / root) * 180 / Math.PI + config.yaw; 790 if (yaw < -180) 791 yaw += 360; 792 if (yaw > 180) 793 yaw -= 360; 794 return [pitch, yaw]; 795 } 796 797 function onDocumentCompassChange(event) { 798 799 } 800 801 /** 802 * Event handler for mouse moves. Pans center of view. 803 * @private 804 * @param {MouseEvent} event - Document mouse move event. 805 */ 806 function onDocumentMouseMove(event) { 807 if (isUserInteracting && loaded) { 808 latestInteraction = Date.now(); 809 var canvas = renderer.getCanvas(); 810 var canvasWidth = canvas.clientWidth, 811 canvasHeight = canvas.clientHeight; 812 var pos = mousePosition(event); 813 //TODO: This still isn't quite right 814 var yaw = ((Math.atan(onPointerDownPointerX / canvasWidth * 2 - 1) - Math.atan(pos.x / canvasWidth * 2 - 1)) * 180 / Math.PI * config.hfov / 90) + onPointerDownYaw; 815 speed.yaw = (yaw - config.yaw) % 360 * 0.2; 816 config.yaw = yaw; 817 818 var vfov = 2 * Math.atan(Math.tan(config.hfov/360*Math.PI) * canvasHeight / canvasWidth) * 180 / Math.PI; 819 820 var pitch = ((Math.atan(pos.y / canvasHeight * 2 - 1) - Math.atan(onPointerDownPointerY / canvasHeight * 2 - 1)) * 180 / Math.PI * vfov / 90) + onPointerDownPitch; 821 speed.pitch = (pitch - config.pitch) * 0.2; 822 config.pitch = pitch; 823 824 var mirrorData = { 825 pitch: pitch, 826 yaw: yaw 827 } 828 fireEvent('mousemove', mirrorData); 829 830 } 831 } 832 833 /** 834 * Event handler for mouse up events. Stops panning. 835 * @private 836 */ 837 function onDocumentMouseUp(event) { 838 if (!isUserInteracting) { 839 return; 840 } 841 isUserInteracting = false;
vendor: 4,780 bytes, lines 842-958
842 if (Date.now() - latestInteraction > 15) { 843 // Prevents jump when user rapidly moves mouse, stops, and then 844 // releases the mouse button 845 speed.pitch = speed.yaw = 0; 846 } 847 uiContainer.classList.add('pnlm-grab'); 848 uiContainer.classList.remove('pnlm-grabbing'); 849 latestInteraction = Date.now(); 850 851 fireEvent('mouseup', event); 852 } 853 854 /** 855 * Event handler for touches. Initializes panning if one touch or zooming if 856 * two touches. 857 * @private 858 * @param {TouchEvent} event - Document touch start event. 859 */ 860 function onDocumentTouchStart(event) { 861 // Only do something if the panorama is loaded 862 if (!loaded || !config.draggable) { 863 return; 864 } 865 866 // Turn off auto-rotation if enabled 867 stopAnimation(); 868 869 stopOrientation(); 870 config.roll = 0; 871 872 speed.hfov = 0; 873 874 // Calculate touch position relative to top left of viewer container 875 var pos0 = mousePosition(event.targetTouches[0]); 876 877 onPointerDownPointerX = pos0.x; 878 onPointerDownPointerY = pos0.y; 879 880 if (event.targetTouches.length == 2) { 881 // Down pointer is the center of the two fingers 882 var pos1 = mousePosition(event.targetTouches[1]); 883 onPointerDownPointerX += (pos1.x - pos0.x) * 0.5; 884 onPointerDownPointerY += (pos1.y - pos0.y) * 0.5; 885 onPointerDownPointerDist = Math.sqrt((pos0.x - pos1.x) * (pos0.x - pos1.x) + 886 (pos0.y - pos1.y) * (pos0.y - pos1.y)); 887 } 888 isUserInteracting = true; 889 latestInteraction = Date.now(); 890 891 onPointerDownYaw = config.yaw; 892 onPointerDownPitch = config.pitch; 893 894 fireEvent('touchstart', event); 895 animateInit(); 896 } 897 898 /** 899 * Event handler for touch movements. Pans center of view if one touch or 900 * adjusts zoom if two touches. 901 * @private 902 * @param {TouchEvent} event - Document touch move event. 903 */ 904 function onDocumentTouchMove(event) { 905 if (!config.draggable) { 906 return; 907 } 908 909 // Override default action 910 event.preventDefault(); 911 if (loaded) { 912 latestInteraction = Date.now(); 913 } 914 if (isUserInteracting && loaded) { 915 var pos0 = mousePosition(event.targetTouches[0]); 916 var clientX = pos0.x; 917 var clientY = pos0.y; 918 919 if (event.targetTouches.length == 2 && onPointerDownPointerDist != -1) { 920 var pos1 = mousePosition(event.targetTouches[1]); 921 clientX += (pos1.x - pos0.x) * 0.5; 922 clientY += (pos1.y - pos0.y) * 0.5; 923 var clientDist = Math.sqrt((pos0.x - pos1.x) * (pos0.x - pos1.x) + 924 (pos0.y - pos1.y) * (pos0.y - pos1.y)); 925 setHfov(config.hfov + (onPointerDownPointerDist - clientDist) * 0.1); 926 onPointerDownPointerDist = clientDist; 927 } 928 929 // The smaller the config.hfov value (the more zoomed-in the user is), the faster 930 // yaw/pitch are perceived to change on one-finger touchmove (panning) events and vice versa. 931 // To improve usability at both small and large zoom levels (config.hfov values) 932 // we introduce a dynamic pan speed coefficient. 933 // 934 // Currently this seems to *roughly* keep initial drag/pan start position close to 935 // the user's finger while panning regardless of zoom level / config.hfov value. 936 var touchmovePanSpeedCoeff = (config.hfov / 360) * config.touchPanSpeedCoeffFactor; 937 938 var yaw = (onPointerDownPointerX - clientX) * touchmovePanSpeedCoeff + onPointerDownYaw; 939 speed.yaw = (yaw - config.yaw) % 360 * 0.2; 940 config.yaw = yaw; 941 942 var pitch = (clientY - onPointerDownPointerY) * touchmovePanSpeedCoeff + onPointerDownPitch; 943 speed.pitch = (pitch - config.pitch) * 0.2; 944 config.pitch = pitch; 945 var mirrorData = { 946 pitch: pitch, 947 yaw: yaw 948 } 949 fireEvent('touchmove', mirrorData); 950 } 951 } 952 953 /** 954 * Event handler for end of touches. Stops panning and/or zooming. 955 * @private 956 */ 957 function onDocumentTouchEnd() { 958 isUserInteracting = false;
959 if (Date.now() - latestInteraction > 150) { 960 speed.pitch = speed.yaw = 0; 961 } 962 onPointerDownPointerDist = -1; 963 latestInteraction = Date.now(); 964 965 fireEvent('touchend', event); 966 } 967 968 var pointerIDs = [], 969 pointerCoordinates = []; 970 /** 971 * Event handler for touch starts in IE / Edge. 972 * @private 973 * @param {PointerEvent} event - Document pointer down event. 974 */ 975 function onDocumentPointerDown(event) { 976 if (event.pointerType == 'touch') { 977 // Only do something if the panorama is loaded 978 if (!loaded || !config.draggable) 979 return; 980 pointerIDs.push(event.pointerId); 981 pointerCoordinates.push({clientX: event.clientX, clientY: event.clientY}); 982 event.targetTouches = pointerCoordinates; 983 onDocumentTouchStart(event); 984 event.preventDefault(); 985 } 986 } 987 988 /** 989 * Event handler for touch moves in IE / Edge. 990 * @private 991 * @param {PointerEvent} event - Document pointer move event. 992 */ 993 function onDocumentPointerMove(event) { 994 if (event.pointerType == 'touch') { 995 if (!config.draggable) 996 return; 997 for (var i = 0; i < pointerIDs.length; i++) { 998 if (event.pointerId == pointerIDs[i]) { 999 pointerCoordinates[i].clientX = event.clientX; 1000 pointerCoordinates[i].clientY = event.clientY; 1001 event.targetTouches = pointerCoordinates; 1002 onDocumentTouchMove(event); 1003 event.preventDefault(); 1004 return; 1005 } 1006 } 1007 } 1008 } 1009 1010 /** 1011 * Event handler for touch ends in IE / Edge. 1012 * @private 1013 * @param {PointerEvent} event - Document pointer up event. 1014 */ 1015 function onDocumentPointerUp(event) { 1016 if (event.pointerType == 'touch') { 1017 var defined = false; 1018 for (var i = 0; i < pointerIDs.length; i++) { 1019 if (event.pointerId == pointerIDs[i]) 1020 pointerIDs[i] = undefined; 1021 if (pointerIDs[i]) 1022 defined = true; 1023 } 1024 if (!defined) { 1025 pointerIDs = []; 1026 pointerCoordinates = []; 1027 onDocumentTouchEnd(); 1028 } 1029 event.preventDefault(); 1030 } 1031 } 1032 1033 /** 1034 * Event handler for mouse wheel. Changes zoom. 1035 * @private 1036 * @param {WheelEvent} event - Document mouse wheel event. 1037 */ 1038 function onDocumentMouseWheel(event) { 1039 // Only do something if the panorama is loaded and mouse wheel zoom is enabled 1040 if (!loaded || (config.mouseZoom == 'fullscreenonly' && !fullscreenActive)) { 1041 return; 1042 } 1043 1044 event.preventDefault(); 1045 1046 // Turn off auto-rotation if enabled 1047 stopAnimation(); 1048 latestInteraction = Date.now(); 1049 1050 if (event.wheelDeltaY) { 1051 // WebKit 1052 setHfov(config.hfov - event.wheelDeltaY * 0.05); 1053 speed.hfov = event.wheelDelta < 0 ? 1 : -1; 1054 } else if (event.wheelDelta) { 1055 // Opera / Explorer 9 1056 setHfov(config.hfov - event.wheelDelta * 0.05); 1057 speed.hfov = event.wheelDelta < 0 ? 1 : -1; 1058 } else if (event.detail) { 1059 // Firefox 1060 setHfov(config.hfov + event.detail * 1.5); 1061 speed.hfov = event.detail > 0 ? 1 : -1; 1062 } 1063 animateInit(); 1064 } 1065 1066 /** 1067 * Event handler for key presses. Updates list of currently pressed keys. 1068 * @private 1069 * @param {KeyboardEvent} event - Document key press event. 1070 */ 1071 function onDocumentKeyPress(event) { 1072 // Turn off auto-rotation if enabled 1073 stopAnimation(); 1074 latestInteraction = Date.now(); 1075 1076 stopOrientation(); 1077 config.roll = 0; 1078 1079 // Record key pressed 1080 var keynumber = event.which || event.keycode; 1081 1082 // Override default action for keys that are used 1083 if (config.capturedKeyNumbers.indexOf(keynumber) < 0) 1084 return; 1085 event.preventDefault(); 1086 1087 // If escape key is pressed 1088 if (keynumber == 27) { 1089 // If in fullscreen mode 1090 if (fullscreenActive) { 1091 toggleFullscreen(); 1092 } 1093 } else { 1094 // Change key 1095 changeKey(keynumber, true); 1096 } 1097 } 1098 1099 /** 1100 * Clears list of currently pressed keys. 1101 * @private 1102 */ 1103 function clearKeys() { 1104 for (var i = 0; i < 10; i++) { 1105 keysDown[i] = false;
vendor: 6,327 bytes, lines 1106-1283
1106 } 1107 } 1108 1109 /** 1110 * Event handler for key releases. Updates list of currently pressed keys. 1111 * @private 1112 * @param {KeyboardEvent} event - Document key up event. 1113 */ 1114 function onDocumentKeyUp(event) { 1115 // Record key pressed 1116 var keynumber = event.which || event.keycode; 1117 1118 // Override default action for keys that are used 1119 if (config.capturedKeyNumbers.indexOf(keynumber) < 0) 1120 return; 1121 event.preventDefault(); 1122 1123 // Change key 1124 changeKey(keynumber, false); 1125 } 1126 1127 /** 1128 * Updates list of currently pressed keys. 1129 * @private 1130 * @param {number} keynumber - Key number. 1131 * @param {boolean} value - Whether or not key is pressed. 1132 */ 1133 function changeKey(keynumber, value) { 1134 var keyChanged = false; 1135 switch(keynumber) { 1136 // If minus key is released 1137 case 109: case 189: case 17: case 173: 1138 if (keysDown[0] != value) { keyChanged = true; } 1139 keysDown[0] = value; break; 1140 1141 // If plus key is released 1142 case 107: case 187: case 16: case 61: 1143 if (keysDown[1] != value) { keyChanged = true; } 1144 keysDown[1] = value; break; 1145 1146 // If up arrow is released 1147 case 38: 1148 if (keysDown[2] != value) { keyChanged = true; } 1149 keysDown[2] = value; break; 1150 1151 // If "w" is released 1152 case 87: 1153 if (keysDown[6] != value) { keyChanged = true; } 1154 keysDown[6] = value; break; 1155 1156 // If down arrow is released 1157 case 40: 1158 if (keysDown[3] != value) { keyChanged = true; } 1159 keysDown[3] = value; break; 1160 1161 // If "s" is released 1162 case 83: 1163 if (keysDown[7] != value) { keyChanged = true; } 1164 keysDown[7] = value; break; 1165 1166 // If left arrow is released 1167 case 37: 1168 if (keysDown[4] != value) { keyChanged = true; } 1169 keysDown[4] = value; break; 1170 1171 // If "a" is released 1172 case 65: 1173 if (keysDown[8] != value) { keyChanged = true; } 1174 keysDown[8] = value; break; 1175 1176 // If right arrow is released 1177 case 39: 1178 if (keysDown[5] != value) { keyChanged = true; } 1179 keysDown[5] = value; break; 1180 1181 // If "d" is released 1182 case 68: 1183 if (keysDown[9] != value) { keyChanged = true; } 1184 keysDown[9] = value; 1185 } 1186 1187 if (keyChanged && value) { 1188 if (typeof performance !== 'undefined' && performance.now()) { 1189 prevTime = performance.now(); 1190 } else { 1191 prevTime = Date.now(); 1192 } 1193 animateInit(); 1194 } 1195 } 1196 1197 /** 1198 * Pans and/or zooms panorama based on currently pressed keys. Also handles 1199 * panorama "inertia" and auto rotation. 1200 * @private 1201 */ 1202 function keyRepeat() { 1203 // Only do something if the panorama is loaded 1204 if (!loaded) { 1205 return; 1206 } 1207 1208 var isKeyDown = false; 1209 1210 var prevPitch = config.pitch; 1211 var prevYaw = config.yaw; 1212 var prevZoom = config.hfov; 1213 1214 var newTime; 1215 if (typeof performance !== 'undefined' && performance.now()) { 1216 newTime = performance.now(); 1217 } else { 1218 newTime = Date.now(); 1219 } 1220 if (prevTime === undefined) { 1221 prevTime = newTime; 1222 } 1223 var diff = (newTime - prevTime) * config.hfov / 1700; 1224 diff = Math.min(diff, 1.0); 1225 1226 // If minus key is down 1227 if (keysDown[0] && config.keyboardZoom === true) { 1228 setHfov(config.hfov + (speed.hfov * 0.8 + 0.5) * diff); 1229 isKeyDown = true; 1230 } 1231 1232 // If plus key is down 1233 if (keysDown[1] && config.keyboardZoom === true) { 1234 setHfov(config.hfov + (speed.hfov * 0.8 - 0.2) * diff); 1235 isKeyDown = true; 1236 } 1237 1238 // If up arrow or "w" is down 1239 if (keysDown[2] || keysDown[6]) { 1240 // Pan up 1241 config.pitch += (speed.pitch * 0.8 + 0.2) * diff; 1242 isKeyDown = true; 1243 } 1244 1245 // If down arrow or "s" is down 1246 if (keysDown[3] || keysDown[7]) { 1247 // Pan down 1248 config.pitch += (speed.pitch * 0.8 - 0.2) * diff; 1249 isKeyDown = true; 1250 } 1251 1252 // If left arrow or "a" is down 1253 if (keysDown[4] || keysDown[8]) { 1254 // Pan left 1255 config.yaw += (speed.yaw * 0.8 - 0.2) * diff; 1256 isKeyDown = true; 1257 } 1258 1259 // If right arrow or "d" is down 1260 if (keysDown[5] || keysDown[9]) { 1261 // Pan right 1262 config.yaw += (speed.yaw * 0.8 + 0.2) * diff; 1263 isKeyDown = true; 1264 } 1265 1266 if (isKeyDown) 1267 latestInteraction = Date.now(); 1268 1269 // If auto-rotate 1270 if (config.autoRotate) { 1271 // Pan 1272 if (newTime - prevTime > 0.001) { 1273 var timeDiff = (newTime - prevTime) / 1000; 1274 var yawDiff = (speed.yaw / timeDiff * diff - config.autoRotate * 0.2) * timeDiff; 1275 yawDiff = (-config.autoRotate > 0 ? 1 : -1) * Math.min(Math.abs(config.autoRotate * timeDiff), Math.abs(yawDiff)); 1276 config.yaw += yawDiff; 1277 } 1278 1279 // Deal with stopping auto rotation after a set delay 1280 if (config.autoRotateStopDelay) { 1281 config.autoRotateStopDelay -= newTime - prevTime; 1282 if (config.autoRotateStopDelay <= 0) { 1283 config.autoRotateStopDelay = false;
1284 autoRotateSpeed = config.autoRotate; 1285 config.autoRotate = 0; 1286 } 1287 } 1288 } 1289 1290 // Animated moves 1291 if (animatedMove.pitch) { 1292 animateMove('pitch'); 1293 prevPitch = config.pitch; 1294 } 1295 if (animatedMove.yaw) { 1296 animateMove('yaw'); 1297 prevYaw = config.yaw; 1298 } 1299 if (animatedMove.hfov) { 1300 animateMove('hfov'); 1301 prevZoom = config.hfov; 1302 } 1303 1304 // "Inertia" 1305 if (diff > 0 && !config.autoRotate) { 1306 // "Friction" 1307 var slowDownFactor = 1 - config.friction; 1308 1309 // Yaw 1310 if (!keysDown[4] && !keysDown[5] && !keysDown[8] && !keysDown[9] && !animatedMove.yaw) { 1311 config.yaw += speed.yaw * diff * slowDownFactor; 1312 } 1313 // Pitch 1314 if (!keysDown[2] && !keysDown[3] && !keysDown[6] && !keysDown[7] && !animatedMove.pitch) { 1315 config.pitch += speed.pitch * diff * slowDownFactor; 1316 } 1317 // Zoom 1318 if (!keysDown[0] && !keysDown[1] && !animatedMove.hfov) { 1319 setHfov(config.hfov + speed.hfov * diff * slowDownFactor); 1320 } 1321 } 1322 1323 prevTime = newTime; 1324 if (diff > 0) { 1325 speed.yaw = speed.yaw * 0.8 + (config.yaw - prevYaw) / diff * 0.2; 1326 speed.pitch = speed.pitch * 0.8 + (config.pitch - prevPitch) / diff * 0.2; 1327 speed.hfov = speed.hfov * 0.8 + (config.hfov - prevZoom) / diff * 0.2; 1328 1329 // Limit speed 1330 var maxSpeed = config.autoRotate ? Math.abs(config.autoRotate) : 5; 1331 speed.yaw = Math.min(maxSpeed, Math.max(speed.yaw, -maxSpeed)); 1332 speed.pitch = Math.min(maxSpeed, Math.max(speed.pitch, -maxSpeed)); 1333 speed.hfov = Math.min(maxSpeed, Math.max(speed.hfov, -maxSpeed)); 1334 } 1335 1336 // Stop movement if opposite controls are pressed 1337 if (keysDown[0] && keysDown[1]) { 1338 speed.hfov = 0; 1339 } 1340 if ((keysDown[2] || keysDown[6]) && (keysDown[3] || keysDown[7])) { 1341 speed.pitch = 0; 1342 } 1343 if ((keysDown[4] || keysDown[8]) && (keysDown[5] || keysDown[9])) { 1344 speed.yaw = 0; 1345 } 1346 } 1347 1348 /** 1349 * Animates moves. 1350 * @param {string} axis - Axis to animate 1351 * @private 1352 */ 1353 function animateMove(axis) { 1354 var t = animatedMove[axis]; 1355 var normTime = Math.min(1, Math.max((Date.now() - t.startTime) / 1000 / (t.duration / 1000), 0)); 1356 var result = t.startPosition + config.animationTimingFunction(normTime) * (t.endPosition - t.startPosition); 1357 if ((t.endPosition > t.startPosition && result >= t.endPosition) || 1358 (t.endPosition < t.startPosition && result <= t.endPosition) || 1359 t.endPosition === t.startPosition) { 1360 result = t.endPosition; 1361 speed[axis] = 0; 1362 delete animatedMove[axis]; 1363 } 1364 config[axis] = result; 1365 } 1366 1367 /** 1368 * @param {number} t - Normalized time in animation 1369 * @return {number} Position in animation 1370 * @private 1371 */ 1372 function timingFunction(t) { 1373 // easeInOutQuad from https://gist.github.com/gre/1650294 1374 return t < 0.5 ? 2*t*t : -1+(4-2*t)*t; 1375 } 1376 1377 /** 1378 * Event handler for document resizes. Updates viewer size and rerenders view. 1379 * @private 1380 */ 1381 function onDocumentResize() { 1382 // Resize panorama renderer (moved to onFullScreenChange) 1383 //renderer.resize(); 1384 //animateInit(); 1385 1386 // Kludge to deal with WebKit regression: https://bugs.webkit.org/show_bug.cgi?id=93525 1387 onFullScreenChange('resize'); 1388 } 1389 1390 /** 1391 * Initializes animation. 1392 * @private 1393 */ 1394 function animateInit() { 1395 if (animating) { 1396 return; 1397 } 1398 animating = true; 1399 animate(); 1400 } 1401 1402 /** 1403 * Animates view, using requestAnimationFrame to trigger rendering. 1404 * @private 1405 */ 1406 function animate() { 1407 if (destroyed) { 1408 return; 1409 } 1410 1411 render(); 1412 if (autoRotateStart) 1413 clearTimeout(autoRotateStart); 1414 if (isUserInteracting || orientation === true) { 1415 requestAnimationFrame(animate); 1416 } else if (keysDown[0] || keysDown[1] || keysDown[2] || keysDown[3] || 1417 keysDown[4] || keysDown[5] || keysDown[6] || keysDown[7] || 1418 keysDown[8] || keysDown[9] || config.autoRotate || 1419 animatedMove.pitch || animatedMove.yaw || animatedMove.hfov || 1420 Math.abs(speed.yaw) > 0.01 || Math.abs(speed.pitch) > 0.01 || 1421 Math.abs(speed.hfov) > 0.01) { 1422 1423 keyRepeat(); 1424 if (config.autoRotateInactivityDelay >= 0 && autoRotateSpeed && 1425 Date.now() - latestInteraction > config.autoRotateInactivityDelay && 1426 !config.autoRotate) { 1427 config.autoRotate = autoRotateSpeed; 1428 _this.lookAt(origPitch, undefined, origHfov, 3000); 1429 } 1430 requestAnimationFrame(animate); 1431 } else if (renderer && (renderer.isLoading() || (config.dynamic === true && update))) { 1432 requestAnimationFrame(animate); 1433 } else { 1434 fireEvent('animatefinished', {pitch: _this.getPitch(), yaw: _this.getYaw(), hfov: _this.getHfov()}); 1435 animating = false;
vendor: 14,108 bytes, lines 1436-1759
1436 prevTime = undefined; 1437 var autoRotateStartTime = config.autoRotateInactivityDelay - 1438 (Date.now() - latestInteraction); 1439 if (autoRotateStartTime > 0) { 1440 autoRotateStart = setTimeout(function() { 1441 config.autoRotate = autoRotateSpeed; 1442 _this.lookAt(origPitch, undefined, origHfov, 3000); 1443 animateInit(); 1444 }, autoRotateStartTime); 1445 } else if (config.autoRotateInactivityDelay >= 0 && autoRotateSpeed) { 1446 config.autoRotate = autoRotateSpeed; 1447 _this.lookAt(origPitch, undefined, origHfov, 3000); 1448 animateInit(); 1449 } 1450 } 1451 } 1452 1453 /** 1454 * Renders panorama view. 1455 * @private 1456 */ 1457 function render() { 1458 var tmpyaw; 1459 1460 if (loaded) { 1461 var canvas = renderer.getCanvas(); 1462 1463 if (config.autoRotate !== false) { 1464 // When auto-rotating this check needs to happen first (see issue #764) 1465 if (config.yaw > 360) { 1466 config.yaw -= 360; 1467 } else if (config.yaw < -360) { 1468 config.yaw += 360; 1469 } 1470 } 1471 1472 // Keep a tmp value of yaw for autoRotate comparison later 1473 tmpyaw = config.yaw; 1474 1475 // Optionally avoid showing background (empty space) on left or right by adapting min/max yaw 1476 var hoffcut = 0, 1477 voffcut = 0; 1478 if (config.avoidShowingBackground) { 1479 var hfov2 = config.hfov / 2, 1480 vfov2 = Math.atan2(Math.tan(hfov2 / 180 * Math.PI), (canvas.width / canvas.height)) * 180 / Math.PI, 1481 transposed = config.vaov > config.haov; 1482 if (transposed) { 1483 voffcut = vfov2 * (1 - Math.min(Math.cos((config.pitch - hfov2) / 180 * Math.PI), 1484 Math.cos((config.pitch + hfov2) / 180 * Math.PI))); 1485 } else { 1486 hoffcut = hfov2 * (1 - Math.min(Math.cos((config.pitch - vfov2) / 180 * Math.PI), 1487 Math.cos((config.pitch + vfov2) / 180 * Math.PI))); 1488 } 1489 } 1490 1491 // Ensure the yaw is within min and max allowed 1492 var yawRange = config.maxYaw - config.minYaw, 1493 minYaw = -180, 1494 maxYaw = 180; 1495 if (yawRange < 360) { 1496 minYaw = config.minYaw + config.hfov / 2 + hoffcut; 1497 maxYaw = config.maxYaw - config.hfov / 2 - hoffcut; 1498 if (yawRange < config.hfov) { 1499 // Lock yaw to average of min and max yaw when both can be seen at once 1500 minYaw = maxYaw = (minYaw + maxYaw) / 2; 1501 } 1502 config.yaw = Math.max(minYaw, Math.min(maxYaw, config.yaw)); 1503 } 1504 1505 if (!(config.autoRotate !== false)) { 1506 // When not auto-rotating, this check needs to happen after the 1507 // previous check (see issue #698) 1508 if (config.yaw > 360) { 1509 config.yaw -= 360; 1510 } else if (config.yaw < -360) { 1511 config.yaw += 360; 1512 } 1513 } 1514 1515 // Check if we autoRotate in a limited by min and max yaw 1516 // If so reverse direction 1517 if (config.autoRotate !== false && tmpyaw != config.yaw && 1518 prevTime !== undefined) { // this condition prevents changing the direction initially 1519 config.autoRotate *= -1; 1520 } 1521 1522 // Ensure the calculated pitch is within min and max allowed 1523 var vfov = 2 * Math.atan(Math.tan(config.hfov / 180 * Math.PI * 0.5) / 1524 (canvas.width / canvas.height)) / Math.PI * 180; 1525 var minPitch = config.minPitch + vfov / 2, 1526 maxPitch = config.maxPitch - vfov / 2; 1527 var pitchRange = config.maxPitch - config.minPitch; 1528 if (pitchRange < vfov) { 1529 // Lock pitch to average of min and max pitch when both can be seen at once 1530 minPitch = maxPitch = (minPitch + maxPitch) / 2; 1531 } 1532 if (isNaN(minPitch)) 1533 minPitch = -90; 1534 if (isNaN(maxPitch)) 1535 maxPitch = 90; 1536 config.pitch = Math.max(minPitch, Math.min(maxPitch, config.pitch)); 1537 1538 renderer.render(config.pitch * Math.PI / 180, config.yaw * Math.PI / 180, config.hfov * Math.PI / 180, {roll: config.roll * Math.PI / 180}); 1539 1540 renderHotSpots(); 1541 1542 // Update compass 1543 //compass data fired by sakib 1544 // const node = document.getElementsByClassName('add-pulse')[0]; 1545 // if(node) { 1546 // node.style.transform = 'rotate(' + (-config.yaw - config.northOffset) + 'deg)'; 1547 // } 1548 if (config.compass) { 1549 compass.style.transform = 'rotate(' + (-config.yaw - config.northOffset) + 'deg)'; 1550 compass.style.webkitTransform = 'rotate(' + (-config.yaw - config.northOffset) + 'deg)'; 1551 } 1552 } 1553 } 1554 1555 /** 1556 * Creates a new quaternion. 1557 * @private 1558 * @constructor 1559 * @param {Number} w - W value 1560 * @param {Number} x - X value 1561 * @param {Number} y - Y value 1562 * @param {Number} z - Z value 1563 */ 1564 function Quaternion(w, x, y, z) { 1565 this.w = w; 1566 this.x = x; 1567 this.y = y; 1568 this.z = z; 1569 } 1570 1571 /** 1572 * Multiplies quaternions. 1573 * @private 1574 * @param {Quaternion} q - Quaternion to multiply 1575 * @returns {Quaternion} Result of multiplication 1576 */ 1577 Quaternion.prototype.multiply = function(q) { 1578 return new Quaternion(this.w*q.w - this.x*q.x - this.y*q.y - this.z*q.z, 1579 this.x*q.w + this.w*q.x + this.y*q.z - this.z*q.y, 1580 this.y*q.w + this.w*q.y + this.z*q.x - this.x*q.z, 1581 this.z*q.w + this.w*q.z + this.x*q.y - this.y*q.x); 1582 }; 1583 1584 /** 1585 * Converts quaternion to Euler angles. 1586 * @private 1587 * @returns {Number[]} [phi angle, theta angle, psi angle] 1588 */ 1589 Quaternion.prototype.toEulerAngles = function() { 1590 var phi = Math.atan2(2 * (this.w * this.x + this.y * this.z), 1591 1 - 2 * (this.x * this.x + this.y * this.y)), 1592 theta = Math.asin(2 * (this.w * this.y - this.z * this.x)), 1593 psi = Math.atan2(2 * (this.w * this.z + this.x * this.y), 1594 1 - 2 * (this.y * this.y + this.z * this.z)); 1595 return [phi, theta, psi]; 1596 }; 1597 1598 /** 1599 * Converts device orientation API Tait-Bryan angles to a quaternion. 1600 * @private 1601 * @param {Number} alpha - Alpha angle (in degrees) 1602 * @param {Number} beta - Beta angle (in degrees) 1603 * @param {Number} gamma - Gamma angle (in degrees) 1604 * @returns {Quaternion} Orientation quaternion 1605 */ 1606 function taitBryanToQuaternion(alpha, beta, gamma) { 1607 var r = [beta ? beta * Math.PI / 180 / 2 : 0, 1608 gamma ? gamma * Math.PI / 180 / 2 : 0, 1609 alpha ? alpha * Math.PI / 180 / 2 : 0]; 1610 var c = [Math.cos(r[0]), Math.cos(r[1]), Math.cos(r[2])], 1611 s = [Math.sin(r[0]), Math.sin(r[1]), Math.sin(r[2])]; 1612 1613 return new Quaternion(c[0]*c[1]*c[2] - s[0]*s[1]*s[2], 1614 s[0]*c[1]*c[2] - c[0]*s[1]*s[2], 1615 c[0]*s[1]*c[2] + s[0]*c[1]*s[2], 1616 c[0]*c[1]*s[2] + s[0]*s[1]*c[2]); 1617 } 1618 1619 /** 1620 * Computes current device orientation quaternion from device orientation API 1621 * Tait-Bryan angles. 1622 * @private 1623 * @param {Number} alpha - Alpha angle (in degrees) 1624 * @param {Number} beta - Beta angle (in degrees) 1625 * @param {Number} gamma - Gamma angle (in degrees) 1626 * @returns {Quaternion} Orientation quaternion 1627 */ 1628 function computeQuaternion(alpha, beta, gamma) { 1629 // Convert Tait-Bryan angles to quaternion 1630 var quaternion = taitBryanToQuaternion(alpha, beta, gamma); 1631 // Apply world transform 1632 quaternion = quaternion.multiply(new Quaternion(Math.sqrt(0.5), -Math.sqrt(0.5), 0, 0)); 1633 // Apply screen transform 1634 var angle = window.orientation ? -window.orientation * Math.PI / 180 / 2 : 0; 1635 return quaternion.multiply(new Quaternion(Math.cos(angle), 0, -Math.sin(angle), 0)); 1636 } 1637 1638 /** 1639 * Event handler for device orientation API. Controls pointing. 1640 * @private 1641 * @param {DeviceOrientationEvent} event - Device orientation event. 1642 */ 1643 function orientationListener(e) { 1644 var q = computeQuaternion(e.alpha, e.beta, e.gamma).toEulerAngles(); 1645 if (typeof(orientation) == 'number' && orientation < 10) { 1646 // This kludge is necessary because iOS sometimes provides a few stale 1647 // device orientation events when the listener is removed and then 1648 // readded. Thus, we skip the first 10 events to prevent this from 1649 // causing problems. 1650 orientation += 1; 1651 } else if (orientation === 10) { 1652 // Record starting yaw to prevent jumping 1653 orientationYawOffset = q[2] / Math.PI * 180 + config.yaw; 1654 orientation = true; 1655 requestAnimationFrame(animate); 1656 } else { 1657 config.pitch = q[0] / Math.PI * 180; 1658 config.roll = -q[1] / Math.PI * 180; 1659 config.yaw = -q[2] / Math.PI * 180 + orientationYawOffset; 1660 } 1661 } 1662 1663 /** 1664 * Initializes renderer. 1665 * @private 1666 */ 1667 function renderInit() { 1668 try { 1669 var params = {}; 1670 if (config.horizonPitch !== undefined) 1671 params.horizonPitch = config.horizonPitch * Math.PI / 180; 1672 if (config.horizonRoll !== undefined) 1673 params.horizonRoll = config.horizonRoll * Math.PI / 180; 1674 if (config.backgroundColor !== undefined) 1675 params.backgroundColor = config.backgroundColor; 1676 renderer.init(panoImage, config.type, config.dynamic, config.haov * Math.PI / 180, config.vaov * Math.PI / 180, config.vOffset * Math.PI / 180, renderInitCallback, params); 1677 if (config.dynamic !== true) { 1678 // Allow image to be garbage collected 1679 panoImage = undefined; 1680 } 1681 } catch(event) { 1682 // Panorama not loaded 1683 1684 // Display error if there is a bad texture 1685 if (event.type == 'webgl error' || event.type == 'no webgl') { 1686 anError(); 1687 } else if (event.type == 'webgl size error') { 1688 anError(config.strings.textureSizeError.replace('%s', event.width).replace('%s', event.maxWidth)); 1689 } else { 1690 anError(config.strings.unknownError); 1691 throw event; 1692 } 1693 } 1694 } 1695 1696 /** 1697 * Triggered when render initialization finishes. Handles fading between 1698 * scenes as well as showing the compass and hotspots and hiding the loading 1699 * display. 1700 * @private 1701 */ 1702 function renderInitCallback() { 1703 // Fade if specified 1704 if (config.sceneFadeDuration && renderer.fadeImg !== undefined) { 1705 renderer.fadeImg.style.opacity = 0; 1706 // Remove image 1707 var fadeImg = renderer.fadeImg; 1708 delete renderer.fadeImg; 1709 setTimeout(function() { 1710 renderContainer.removeChild(fadeImg); 1711 fireEvent('scenechangefadedone'); 1712 }, config.sceneFadeDuration); 1713 } 1714 1715 // Show compass if applicable 1716 if (config.compass) { 1717 compass.style.display = 'inline'; 1718 } else { 1719 compass.style.display = 'none'; 1720 } 1721 1722 // Show hotspots 1723 createHotSpots(); 1724 1725 // Hide loading display 1726 infoDisplay.load.box.style.display = 'none'; 1727 if (preview !== undefined) { 1728 renderContainer.removeChild(preview); 1729 preview = undefined; 1730 } 1731 loaded = true; 1732 1733 animateInit(); 1734 1735 fireEvent('load'); 1736 } 1737 1738 /** 1739 * Creates hot spot element for the current scene. 1740 * @private 1741 * @param {Object} hs - The configuration for the hotspot 1742 */ 1743 function createHotSpot(hs) { 1744 // Make sure hot spot pitch and yaw are numbers 1745 hs.pitch = Number(hs.pitch) || 0; 1746 hs.yaw = Number(hs.yaw) || 0; 1747 1748 var div = document.createElement('div'); 1749 div.className = 'pnlm-hotspot-base'; 1750 if (hs.cssClass) 1751 div.className += ' ' + hs.cssClass; 1752 else 1753 div.className += ' pnlm-hotspot pnlm-sprite pnlm-' + escapeHTML(hs.type); 1754 1755 //Shahin - customized hotspot dom for Hexagon shape 1756 if(hs.hotspot_shape === 'hexagon'){ 1757 var hotspotShape = document.createElement('div'); 1758 hotspotShape.className = 'hexagon-wrapper'; 1759 const hexagonShapeIcon = '<svg fill="none" width="30" height="25" viewB
1759ox="0 0 40 35" xmlns="http://www.w3.org/2000/svg"><path fill="#00b4ff" d="M0 17.32L10 0h20l10 17.32-10 17.321H10L0 17.321z"/></svg>'; 1760 hotspotShape.innerHTML = hexagonShapeIcon; 1761 div.appendChild(hotspotShape); 1762 } 1763 //Sakib - customized hotspot dom for vr mode 1764 var title = document.createElement('span'); 1765 title.className = 'vr-mode-title'; 1766 if (hs.sceneId) 1767 title.innerHTML = escapeHTML(hs.sceneId); 1768 //Sakib - customized hotspot dom for vr mode end 1769 1770 var span = document.createElement('span'); 1771 if (hs.text) 1772 span.innerHTML = escapeHTML(hs.text); 1773 1774 var a; 1775 if (hs.video) { 1776 var video = document.createElement('video'), 1777 vidp = hs.video; 1778 if (config.basePath && !absoluteURL(vidp)) 1779 vidp = config.basePath + vidp; 1780 video.src = sanitizeURL(vidp); 1781 video.controls = true; 1782 video.style.width = hs.width + 'px'; 1783 renderContainer.appendChild(div); 1784 span.appendChild(video); 1785 } else if (hs.image) { 1786 var imgp = hs.image; 1787 if (config.basePath && !absoluteURL(imgp)) 1788 imgp = config.basePath + imgp; 1789 a = document.createElement('a'); 1790 a.href = sanitizeURL(hs.URL ? hs.URL : imgp, true); 1791 a.target = '_blank'; 1792 span.appendChild(a); 1793 var image = document.createElement('img'); 1794 image.src = sanitizeURL(imgp); 1795 image.style.width = hs.width + 'px'; 1796 image.style.paddingTop = '5px'; 1797 renderContainer.appendChild(div); 1798 a.appendChild(image); 1799 span.style.maxWidth = 'initial'; 1800 } else if (hs.URL) { 1801 a = document.createElement('a'); 1802 a.href = sanitizeURL(hs.URL, true); 1803 if (hs.attributes) { 1804 for (var key in hs.attributes) { 1805 a.setAttribute(key, hs.attributes[key]); 1806 } 1807 } else { 1808 //wpvr edit by sakib for in tab url open// -- handling from here 1809 if (hs.wpvr_url_open == 'on') { 1810 a.target = '_self'; 1811 } 1812 else { 1813 a.target = '_blank'; 1814 } 1815 } 1816 renderContainer.appendChild(a); 1817 div.className += ' pnlm-pointer'; 1818 span.className += ' pnlm-pointer'; 1819 a.appendChild(div); 1820 } else { 1821 if (hs.sceneId) { 1822 div.onclick = div.ontouchend = function() { 1823 if (!div.clicked) { 1824 div.clicked = true; 1825 loadScene(hs.sceneId, hs.targetPitch, hs.targetYaw, hs.targetHfov); 1826 } 1827 return false; 1828 }; 1829 div.className += ' pnlm-pointer'; 1830 span.className += ' pnlm-pointer'; 1831 } 1832 1833 if (hs.sceneId) 1834 div.appendChild(title); 1835 1836 renderContainer.appendChild(div); 1837 } 1838 1839 if (hs.createTooltipFunc) { 1840 hs.createTooltipFunc(div, hs.createTooltipArgs); 1841 } else if (hs.text || hs.video || hs.image) { 1842 div.classList.add('pnlm-tooltip'); 1843 div.appendChild(span); 1844 span.style.width = span.scrollWidth - 20 + 'px'; 1845 span.style.marginLeft = -(span.scrollWidth - div.offsetWidth) / 2 + 'px'; 1846 span.style.marginTop = -span.scrollHeight - 12 + 'px'; 1847 } 1848 if (hs.clickHandlerFunc) { 1849 div.addEventListener('click', function(e) { 1850 hs.clickHandlerFunc(e, hs.clickHandlerArgs); 1851 }, 'false'); 1852 div.className += ' pnlm-pointer'; 1853 span.className += ' pnlm-pointer'; 1854 } 1855 hs.div = div; 1856 } 1857 1858 /** 1859 * Creates hot spot elements for the current scene. 1860 * @private 1861 */ 1862 function createHotSpots() { 1863 if (hotspotsCreated) return; 1864 1865 if (!config.hotSpots) { 1866 config.hotSpots = []; 1867 } else { 1868 // Sort by pitch so tooltip is never obscured by another hot spot 1869 config.hotSpots = config.hotSpots.sort(function(a, b) { 1870 return a.pitch < b.pitch; 1871 });
vendor: 7,062 bytes, lines 1872-2036
1872 config.hotSpots.forEach(createHotSpot); 1873 } 1874 hotspotsCreated = true; 1875 renderHotSpots(); 1876 } 1877 1878 /** 1879 * Destroys currently created hot spot elements. 1880 * @private 1881 */ 1882 function destroyHotSpots() { 1883 var hs = config.hotSpots; 1884 hotspotsCreated = false; 1885 delete config.hotSpots; 1886 if (hs) { 1887 for (var i = 0; i < hs.length; i++) { 1888 var current = hs[i].div; 1889 if (current) { 1890 while (current.parentNode && current.parentNode != renderContainer) { 1891 current = current.parentNode; 1892 } 1893 renderContainer.removeChild(current); 1894 } 1895 delete hs[i].div; 1896 } 1897 } 1898 } 1899 1900 /** 1901 * Renders hot spot, updating its position and visibility. 1902 * @private 1903 */ 1904 function renderHotSpot(hs) { 1905 var hsPitchSin = Math.sin(hs.pitch * Math.PI / 180), 1906 hsPitchCos = Math.cos(hs.pitch * Math.PI / 180), 1907 configPitchSin = Math.sin(config.pitch * Math.PI / 180), 1908 configPitchCos = Math.cos(config.pitch * Math.PI / 180), 1909 yawCos = Math.cos((-hs.yaw + config.yaw) * Math.PI / 180); 1910 var z = hsPitchSin * configPitchSin + hsPitchCos * yawCos * configPitchCos; 1911 if ((hs.yaw <= 90 && hs.yaw > -90 && z <= 0) || 1912 ((hs.yaw > 90 || hs.yaw <= -90) && z <= 0)) { 1913 hs.div.style.visibility = 'hidden'; 1914 } else { 1915 var yawSin = Math.sin((-hs.yaw + config.yaw) * Math.PI / 180), 1916 hfovTan = Math.tan(config.hfov * Math.PI / 360); 1917 hs.div.style.visibility = 'visible'; 1918 // Subpixel rendering doesn't work in Firefox 1919 // https://bugzilla.mozilla.org/show_bug.cgi?id=739176 1920 var canvas = renderer.getCanvas(), 1921 canvasWidth = canvas.clientWidth, 1922 canvasHeight = canvas.clientHeight; 1923 var coord = [-canvasWidth / hfovTan * yawSin * hsPitchCos / z / 2, 1924 -canvasWidth / hfovTan * (hsPitchSin * configPitchCos - 1925 hsPitchCos * yawCos * configPitchSin) / z / 2]; 1926 // Apply roll 1927 var rollSin = Math.sin(config.roll * Math.PI / 180), 1928 rollCos = Math.cos(config.roll * Math.PI / 180); 1929 coord = [coord[0] * rollCos - coord[1] * rollSin, 1930 coord[0] * rollSin + coord[1] * rollCos]; 1931 // Apply transform 1932 coord[0] += (canvasWidth - hs.div.offsetWidth) / 2; 1933 coord[1] += (canvasHeight - hs.div.offsetHeight) / 2; 1934 var transform = 'translate(' + coord[0] + 'px, ' + coord[1] + 1935 'px) translateZ(9999px) rotate(' + config.roll + 'deg)'; 1936 if (hs.scale) { 1937 transform += ' scale(' + (origHfov/config.hfov) / z + ')'; 1938 } 1939 hs.div.style.webkitTransform = transform; 1940 hs.div.style.MozTransform = transform; 1941 hs.div.style.transform = transform; 1942 } 1943 } 1944 1945 /** 1946 * Renders hot spots, updating their positions and visibility. 1947 * @private 1948 */ 1949 function renderHotSpots() { 1950 config.hotSpots.forEach(renderHotSpot); 1951 } 1952 1953 /** 1954 * Merges a scene configuration into the current configuration. 1955 * @private 1956 * @param {string} sceneId - Identifier of scene configuration to merge in. 1957 */ 1958 function mergeConfig(sceneId) { 1959 config = {}; 1960 var k, s; 1961 var photoSphereExcludes = ['haov', 'vaov', 'vOffset', 'northOffset', 'horizonPitch', 'horizonRoll']; 1962 specifiedPhotoSphereExcludes = []; 1963 1964 // Merge default config 1965 for (k in defaultConfig) { 1966 if (defaultConfig.hasOwnProperty(k)) { 1967 config[k] = defaultConfig[k]; 1968 } 1969 } 1970 1971 // Merge default scene config 1972 for (k in initialConfig.default) { 1973 if (initialConfig.default.hasOwnProperty(k)) { 1974 if (k == 'strings') { 1975 for (s in initialConfig.default.strings) { 1976 if (initialConfig.default.strings.hasOwnProperty(s)) { 1977 config.strings[s] = escapeHTML(initialConfig.default.strings[s]); 1978 } 1979 } 1980 } else { 1981 config[k] = initialConfig.default[k]; 1982 if (photoSphereExcludes.indexOf(k) >= 0) { 1983 specifiedPhotoSphereExcludes.push(k); 1984 } 1985 } 1986 } 1987 } 1988 1989 // Merge current scene config 1990 if ((sceneId !== null) && (sceneId !== '') && (initialConfig.scenes) && (initialConfig.scenes[sceneId])) { 1991 var scene = initialConfig.scenes[sceneId]; 1992 for (k in scene) { 1993 if (scene.hasOwnProperty(k)) { 1994 if (k == 'strings') { 1995 for (s in scene.strings) { 1996 if (scene.strings.hasOwnProperty(s)) { 1997 config.strings[s] = escapeHTML(scene.strings[s]); 1998 } 1999 } 2000 } else { 2001 config[k] = scene[k]; 2002 if (photoSphereExcludes.indexOf(k) >= 0) { 2003 specifiedPhotoSphereExcludes.push(k); 2004 } 2005 } 2006 } 2007 } 2008 config.scene = sceneId; 2009 } 2010 2011 // Merge initial config 2012 for (k in initialConfig) { 2013 if (initialConfig.hasOwnProperty(k)) { 2014 if (k == 'strings') { 2015 for (s in initialConfig.strings) { 2016 if (initialConfig.strings.hasOwnProperty(s)) { 2017 config.strings[s] = escapeHTML(initialConfig.strings[s]); 2018 } 2019 } 2020 } else { 2021 config[k] = initialConfig[k]; 2022 if (photoSphereExcludes.indexOf(k) >= 0) { 2023 specifiedPhotoSphereExcludes.push(k); 2024 } 2025 } 2026 } 2027 } 2028 } 2029 2030 /** 2031 * Processes configuration options. 2032 * @param {boolean} [isPreview] - Whether or not the preview is being displayed 2033 * @private 2034 */ 2035 function processOptions(isPreview) { 2036 isPreview = isPreview ? isPreview : false;
2037 2038 // Process preview first so it always loads before the browser hits its 2039 // maximum number of connections to a server as can happen with cubic 2040 // panoramas 2041 if (isPreview && 'preview' in config) { 2042 var p = config.preview; 2043 if (config.basePath && !absoluteURL(p)) 2044 p = config.basePath + p; 2045 preview = document.createElement('div'); 2046 preview.className = 'pnlm-preview-img'; 2047 preview.style.backgroundImage = "url('" + sanitizeURLForCss(p) + "')"; 2048 renderContainer.appendChild(preview); 2049 } 2050 2051 // Handle different preview values 2052 var title = config.title, 2053 author = config.author; 2054 if (isPreview) { 2055 if ('previewTitle' in config) 2056 config.title = config.previewTitle; 2057 if ('previewAuthor' in config) 2058 config.author = config.previewAuthor; 2059 } 2060 2061 // Reset title / author display 2062 if (!config.hasOwnProperty('title')) 2063 infoDisplay.title.innerHTML = ''; 2064 if (!config.hasOwnProperty('author')) 2065 infoDisplay.author.innerHTML = ''; 2066 if (!config.hasOwnProperty('title') && !config.hasOwnProperty('author')) 2067 infoDisplay.container.style.display = 'none'; 2068 2069 // Fill in load button label and loading box text 2070 controls.load.innerHTML = '<p>' + config.strings.loadButtonLabel + '</p>'; 2071 infoDisplay.load.boxp.innerHTML = config.strings.loadingLabel; 2072 2073 // Process other options 2074 for (var key in config) { 2075 if (config.hasOwnProperty(key)) { 2076 switch(key) { 2077 case 'title': 2078 infoDisplay.title.innerHTML = escapeHTML(config[key]); 2079 infoDisplay.container.style.display = 'inline'; 2080 break; 2081 2082 case 'author': 2083 var authorText = escapeHTML(config[key]); 2084 if (config.authorURL) { 2085 var authorLink = document.createElement('a'); 2086 authorLink.href = sanitizeURL(config['authorURL'], true); 2087 authorLink.target = '_blank'; 2088 authorLink.innerHTML = escapeHTML(config[key]); 2089 authorText = authorLink.outerHTML; 2090 } 2091 infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', authorText); 2092 infoDisplay.container.style.display = 'inline'; 2093 break; 2094 2095 case 'fallback': 2096 var link = document.createElement('a'); 2097 link.href = sanitizeURL(config[key], true); 2098 link.target = '_blank'; 2099 link.textContent = 'Click here to view this panorama in an alternative viewer.'; 2100 var message = document.createElement('p'); 2101 message.textContent = 'Your browser does not support WebGL.'; 2102 message.appendChild(document.createElement('br')); 2103 message.appendChild(link); 2104 infoDisplay.errorMsg.innerHTML = ''; // Removes all children nodes 2105 infoDisplay.errorMsg.appendChild(message); 2106 break; 2107 2108 case 'hfov': 2109 setHfov(Number(config[key])); 2110 break; 2111 2112 case 'autoLoad': 2113 if (config[key] === true && renderer === undefined) { 2114 // Show loading box 2115 infoDisplay.load.box.style.display = 'inline'; 2116 // Hide load button 2117 controls.load.style.display = 'none'; 2118 // Initialize 2119 init(); 2120 } 2121 break; 2122 2123 case 'showZoomCtrl': 2124 if (config[key] && config.showControls != false) { 2125 // Show zoom controls 2126 controls.zoom.style.display = 'block'; 2127 } else { 2128 // Hide zoom controls 2129 controls.zoom.style.display = 'none'; 2130 } 2131 break; 2132 2133 case 'showFullscreenCtrl': 2134 if (config[key] && config.showControls != false && ('fullscreen' in document || 'mozFullScreen' in document || 2135 'webkitIsFullScreen' in document || 'msFullscreenElement' in document)) { 2136 2137 // Show fullscreen control 2138 controls.fullscreen.style.display = 'block'; 2139 } else { 2140 // Hide fullscreen control 2141 controls.fullscreen.style.display = 'none'; 2142 } 2143 break; 2144 2145 case 'hotSpotDebug': 2146 if (config[key]) 2147 hotSpotDebugIndicator.style.display = 'block'; 2148 else 2149 hotSpotDebugIndicator.style.display = 'none'; 2150 break; 2151 2152 case 'showControls': 2153 if (!config[key]) { 2154 controls.orientation.style.display = 'none'; 2155 controls.zoom.style.display = 'none'; 2156 controls.fullscreen.style.display = 'none'; 2157 } 2158 break; 2159 2160 case 'orientationOnByDefault': 2161 if (config[key]) 2162 startOrientation(); 2163 break; 2164 } 2165 } 2166 } 2167 2168 if (isPreview) { 2169 // Restore original values if changed for preview 2170 if (title) 2171 config.title = title; 2172 else 2173 delete config.title; 2174 if (author) 2175 config.author = author; 2176 else 2177 delete config.author; 2178 } 2179 } 2180 2181 /** 2182 * Toggles fullscreen mode. 2183 * @private 2184 */ 2185 function toggleFullscreen() { 2186 if (loaded && !error) { 2187 if (!fullscreenActive) { 2188 try { 2189 if (container.requestFullscreen) { 2190 container.requestFullscreen(); 2191 } else if (container.mozRequestFullScreen) { 2192 container.mozRequestFullScreen(); 2193 } else if (container.msRequestFullscreen) { 2194 container.msRequestFullscreen(); 2195 } else { 2196 container.webkitRequestFullScreen(); 2197 } 2198 } catch(event) { 2199 // Fullscreen doesn't work 2200 } 2201 } else { 2202 if (document.exitFullscreen) { 2203 document.exitFullscreen(); 2204 } else if (document.mozCancelFullScreen) { 2205 document.mozCancelFullScreen(); 2206 } else if (document.webkitCancelFullScreen) { 2207 document.webkitCancelFullScreen(); 2208 } else if (document.msExitFullscreen) { 2209 document.msExitFullscreen(); 2210 } 2211 } 2212 } 2213 } 2214 2215 /** 2216 * Event handler for fullscreen changes. 2217 * @private 2218 */ 2219 function onFullScreenChange(resize) { 2220 if (document.fullscreenElement || document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement) { 2221 controls.fullscreen.classList.add('pnlm-fullscreen-toggle-button-active'); 2222 fullscreenActive = true; 2223 } else { 2224 controls.fullscreen.classList.remove('pnlm-fullscreen-toggle-button-active'); 2225 fullscreenActive = false;
vendor: 4,274 bytes, lines 2226-2340
2226 } 2227 if (resize !== 'resize') 2228 fireEvent('fullscreenchange', fullscreenActive); 2229 // Resize renderer (deal with browser quirks and fixes #155) 2230 renderer.resize(); 2231 setHfov(config.hfov); 2232 animateInit(); 2233 } 2234 2235 /** 2236 * Increases panorama zoom. For use with zoom button. 2237 * @private 2238 */ 2239 function zoomIn() { 2240 if (loaded) { 2241 setHfov(config.hfov - 5); 2242 animateInit(); 2243 } 2244 } 2245 2246 /** 2247 * Decreases panorama zoom. For use with zoom button. 2248 * @private 2249 */ 2250 function zoomOut() { 2251 if (loaded) { 2252 setHfov(config.hfov + 5); 2253 animateInit(); 2254 } 2255 } 2256 2257 /** 2258 * Clamps horzontal field of view to viewer's limits. 2259 * @private 2260 * @param {number} hfov - Input horizontal field of view (in degrees) 2261 * @return {number} - Clamped horizontal field of view (in degrees) 2262 */ 2263 function constrainHfov(hfov) { 2264 // Keep field of view within bounds 2265 var minHfov = config.minHfov; 2266 if (config.type == 'multires' && renderer && !config.multiResMinHfov) { 2267 minHfov = Math.min(minHfov, renderer.getCanvas().width / (config.multiRes.cubeResolution / 90 * 0.9)); 2268 } 2269 if (minHfov > config.maxHfov) { 2270 // Don't change view if bounds don't make sense 2271 console.log('HFOV bounds do not make sense (minHfov > maxHfov).'); 2272 return config.hfov; 2273 } 2274 var newHfov = config.hfov; 2275 if (hfov < minHfov) { 2276 newHfov = minHfov; 2277 } else if (hfov > config.maxHfov) { 2278 newHfov = config.maxHfov; 2279 } else { 2280 newHfov = hfov; 2281 } 2282 // Optionally avoid showing background (empty space) on top or bottom by adapting newHfov 2283 if (config.avoidShowingBackground && renderer) { 2284 var canvas = renderer.getCanvas(); 2285 newHfov = Math.min(newHfov, 2286 Math.atan(Math.tan((config.maxPitch - config.minPitch) / 360 * Math.PI) / 2287 canvas.height * canvas.width) * 360 / Math.PI); 2288 } 2289 return newHfov; 2290 } 2291 2292 /** 2293 * Sets viewer's horizontal field of view. 2294 * @private 2295 * @param {number} hfov - Desired horizontal field of view in degrees. 2296 */ 2297 function setHfov(hfov) { 2298 config.hfov = constrainHfov(hfov); 2299 fireEvent('zoomchange', config.hfov); 2300 } 2301 2302 /** 2303 * Stops auto rotation and animated moves. 2304 * @private 2305 */ 2306 function stopAnimation() { 2307 animatedMove = {}; 2308 autoRotateSpeed = config.autoRotate ? config.autoRotate : autoRotateSpeed; 2309 config.autoRotate = false; 2310 } 2311 2312 /** 2313 * Loads panorama. 2314 * @private 2315 */ 2316 function load() { 2317 // Since WebGL error handling is very general, first we clear any error box 2318 // since it is a new scene and the error from previous maybe because of lacking 2319 // memory etc and not because of a lack of WebGL support etc 2320 clearError(); 2321 loaded = false; 2322 2323 controls.load.style.display = 'none'; 2324 infoDisplay.load.box.style.display = 'inline'; 2325 init(); 2326 } 2327 2328 /** 2329 * Loads scene. 2330 * @private 2331 * @param {string} sceneId - Identifier of scene configuration to merge in. 2332 * @param {number} targetPitch - Pitch viewer should be centered on once scene loads. 2333 * @param {number} targetYaw - Yaw viewer should be centered on once scene loads. 2334 * @param {number} targetHfov - HFOV viewer should use once scene loads. 2335 * @param {boolean} [fadeDone] - If `true`, fade setup is skipped. 2336 */ 2337 function loadScene(sceneId, targetPitch, targetYaw, targetHfov, fadeDone) { 2338 if (!loaded) 2339 fadeDone = true; // Don't try to fade when there isn't a scene loaded 2340 loaded = false;
2341 animatedMove = {}; 2342 2343 // Set up fade if specified 2344 var fadeImg, workingPitch, workingYaw, workingHfov; 2345 if (config.sceneFadeDuration && !fadeDone) { 2346 var data = renderer.render(config.pitch * Math.PI / 180, config.yaw * Math.PI / 180, config.hfov * Math.PI / 180, {returnImage: true}); 2347 if (data !== undefined) { 2348 fadeImg = new Image(); 2349 fadeImg.className = 'pnlm-fade-img'; 2350 fadeImg.style.transition = 'opacity ' + (config.sceneFadeDuration / 1000) + 's'; 2351 fadeImg.style.width = '100%'; 2352 fadeImg.style.height = '100%'; 2353 fadeImg.onload = function() { 2354 loadScene(sceneId, targetPitch, targetYaw, targetHfov, true); 2355 }; 2356 fadeImg.src = data; 2357 renderContainer.appendChild(fadeImg); 2358 renderer.fadeImg = fadeImg; 2359 return; 2360 } 2361 } 2362 2363 // Set new pointing 2364 if (targetPitch === 'same') { 2365 workingPitch = config.pitch; 2366 } else { 2367 workingPitch = targetPitch; 2368 } 2369 if (targetYaw === 'same') { 2370 workingYaw = config.yaw; 2371 } else if (targetYaw === 'sameAzimuth') { 2372 workingYaw = config.yaw + (config.northOffset || 0) - (initialConfig.scenes[sceneId].northOffset || 0); 2373 } else { 2374 workingYaw = targetYaw; 2375 } 2376 if (targetHfov === 'same') { 2377 workingHfov = config.hfov; 2378 } else { 2379 workingHfov = targetHfov; 2380 } 2381 2382 // Destroy hot spots from previous scene 2383 destroyHotSpots(); 2384 2385 // Create the new config for the scene 2386 mergeConfig(sceneId); 2387 2388 // Stop motion 2389 speed.yaw = speed.pitch = speed.hfov = 0; 2390 2391 // Reload scene 2392 processOptions(); 2393 if (workingPitch !== undefined) { 2394 config.pitch = workingPitch; 2395 } 2396 if (workingYaw !== undefined) { 2397 config.yaw = workingYaw; 2398 } 2399 if (workingHfov !== undefined) { 2400 config.hfov = workingHfov; 2401 } 2402 fireEvent('scenechange', sceneId); 2403 load(); 2404 } 2405 2406 /** 2407 * Stop using device orientation. 2408 * @private 2409 */ 2410 function stopOrientation() { 2411 window.removeEventListener('deviceorientation', orientationListener); 2412 controls.orientation.classList.remove('pnlm-orientation-button-active'); 2413 orientation = false; 2414 } 2415 2416 /** 2417 * Start using device orientation. 2418 * @private 2419 */ 2420 function startOrientation() { 2421 if (!orientationSupport) 2422 return; 2423 if (typeof DeviceMotionEvent !== 'undefined' && 2424 typeof DeviceMotionEvent.requestPermission === 'function') { 2425 DeviceOrientationEvent.requestPermission().then(function(response) { 2426 if (response == 'granted') { 2427 orientation = 1; 2428 window.addEventListener('deviceorientation', orientationListener); 2429 controls.orientation.classList.add('pnlm-orientation-button-active'); 2430 } 2431 }); 2432 } else { 2433 orientation = 1; 2434 window.addEventListener('deviceorientation', orientationListener); 2435 controls.orientation.classList.add('pnlm-orientation-button-active'); 2436 } 2437 } 2438 2439 /** 2440 * Escapes HTML string (to mitigate possible DOM XSS attacks). 2441 * @private 2442 * @param {string} s - String to escape 2443 * @returns {string} Escaped string 2444 */ 2445 function escapeHTML(s) { 2446 if (!initialConfig.escapeHTML) 2447 return String(s).split('\n').join('<br>'); 2448 return String(s).split(/&/g).join('&') 2449 .split('"').join('"') 2450 .split("'").join(''') 2451 .split('<').join('<') 2452 .split('>').join('>') 2453 .split('/').join('/') 2454 .split('\n').join('<br>'); // Allow line breaks 2455 } 2456 2457 /** 2458 * Removes possibility of XSS attacks with URLs.
2459 * The URL cannot be of protocol 'javascript'. 2460 * @private 2461 * @param {string} url - URL to sanitize 2462 * @param {boolean} href - True if URL is for link (blocks data URIs) 2463 * @returns {string} Sanitized URL 2464 */ 2465 function sanitizeURL(url, href) { 2466 try { 2467 var decoded_url = decodeURIComponent(unescape(url)).replace(/[^\w:]/g, '').toLowerCase(); 2468 } catch (e) { 2469 return 'about:blank'; 2470 } 2471 if (decoded_url.indexOf('javascript:') === 0 || 2472 decoded_url.indexOf('vbscript:') === 0) { 2473 console.log('Script URL removed.'); 2474 return 'about:blank'; 2475 } 2476 if (href && decoded_url.indexOf('data:') === 0) { 2477 console.log('Data URI removed from link.'); 2478 return 'about:blank'; 2479 } 2480 return url; 2481 } 2482 2483 /** 2484 * Unescapes HTML entities. 2485 * Copied from Marked.js 0.7.0. 2486 * @private 2487 * @param {string} url - URL to sanitize 2488 * @param {boolean} href - True if URL is for link (blocks data URIs) 2489 * @returns {string} Sanitized URL 2490 */ 2491 function unescape(html) { 2492 // Explicitly match decimal, hex, and named HTML entities 2493 return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { 2494 n = n.toLowerCase(); 2495 if (n === 'colon') return ':'; 2496 if (n.charAt(0) === '#') { 2497 return n.charAt(1) === 'x' 2498 ? String.fromCharCode(parseInt(n.substring(2), 16)) 2499 : String.fromCharCode(+n.substring(1)); 2500 } 2501 return ''; 2502 }); 2503 } 2504 2505 /** 2506 * Removes possibility of XSS atacks with URLs for CSS. 2507 * The URL will be sanitized with `sanitizeURL()` and single quotes 2508 * and double quotes escaped. 2509 * @private 2510 * @param {string} url - URL to sanitize 2511 * @returns {string} Sanitized URL 2512 */ 2513 function sanitizeURLForCss(url) { 2514 return sanitizeURL(url) 2515 .replace(/"/g, '%22') 2516 .replace(/'/g, '%27'); 2517 } 2518 2519 /** 2520 * Checks whether or not a panorama is loaded. 2521 * @memberof Viewer 2522 * @instance 2523 * @returns {boolean} `true` if a panorama is loaded, else `false` 2524 */ 2525 this.isLoaded = function() { 2526 return Boolean(loaded); 2527 }; 2528 2529 /** 2530 * Returns the pitch of the center of the view. 2531 * @memberof Viewer 2532 * @instance 2533 * @returns {number} Pitch in degrees 2534 */ 2535 this.getPitch = function() { 2536 return config.pitch; 2537 }; 2538 2539 /** 2540 * Sets the pitch of the center of the view. 2541 * @memberof Viewer 2542 * @instance 2543 * @param {number} pitch - Pitch in degrees 2544 * @param {boolean|number} [animated=1000] - Animation duration in milliseconds or false for no animation 2545 * @param {function} [callback] - Function to call when animation finishes 2546 * @param {object} [callbackArgs] - Arguments to pass to callback function 2547 * @returns {Viewer} `this` 2548 */ 2549 this.setPitch = function(pitch, animated, callback, callbackArgs) { 2550 latestInteraction = Date.now(); 2551 if (Math.abs(pitch - config.pitch) <= eps) { 2552 if (typeof callback == 'function') 2553 callback(callbackArgs); 2554 return this; 2555 } 2556 animated = animated == undefined ? 1000: Number(animated); 2557 if (animated) { 2558 animatedMove.pitch = { 2559 'startTime': Date.now(), 2560 'startPosition': config.pitch, 2561 'endPosition': pitch, 2562 'duration': animated 2563 }; 2564 if (typeof callback == 'function') 2565 setTimeout(function(){callback(callbackArgs);}, animated); 2566 } else { 2567 config.pitch = pitch; 2568 } 2569 animateInit(); 2570 return this; 2571 }; 2572 2573 /** 2574 * Returns the minimum and maximum allowed pitches (in degrees). 2575 * @memberof Viewer 2576 * @instance 2577 * @returns {number[]} [minimum pitch, maximum pitch] 2578 */ 2579 this.getPitchBounds = function() { 2580 return [config.minPitch, config.maxPitch]; 2581 }; 2582 2583 /** 2584 * Set the minimum and maximum allowed pitches (in degrees). 2585 * @memberof Viewer 2586 * @instance 2587 * @param {number[]} bounds - [minimum pitch, maximum pitch] 2588 * @returns {Viewer} `this` 2589 */
2590 this.setPitchBounds = function(bounds) { 2591 config.minPitch = Math.max(-90, Math.min(bounds[0], 90)); 2592 config.maxPitch = Math.max(-90, Math.min(bounds[1], 90)); 2593 return this; 2594 }; 2595 2596 /** 2597 * Returns the yaw of the center of the view. 2598 * @memberof Viewer 2599 * @instance 2600 * @returns {number} Yaw in degrees 2601 */ 2602 this.getYaw = function() { 2603 return (config.yaw + 540) % 360 - 180; 2604 }; 2605 2606 /** 2607 * Sets the yaw of the center of the view. 2608 * @memberof Viewer 2609 * @instance 2610 * @param {number} yaw - Yaw in degrees [-180, 180] 2611 * @param {boolean|number} [animated=1000] - Animation duration in milliseconds or false for no animation 2612 * @param {function} [callback] - Function to call when animation finishes 2613 * @param {object} [callbackArgs] - Arguments to pass to callback function 2614 * @returns {Viewer} `this` 2615 */ 2616 this.setYaw = function(yaw, animated, callback, callbackArgs) { 2617 latestInteraction = Date.now(); 2618 if (Math.abs(yaw - config.yaw) <= eps) { 2619 if (typeof callback == 'function') 2620 callback(callbackArgs); 2621 return this; 2622 } 2623 animated = animated == undefined ? 1000: Number(animated); 2624 yaw = ((yaw + 180) % 360) - 180; // Keep in bounds 2625 if (animated) { 2626 // Animate in shortest direction 2627 if (config.yaw - yaw > 180) 2628 yaw += 360; 2629 else if (yaw - config.yaw > 180) 2630 yaw -= 360; 2631 2632 animatedMove.yaw = { 2633 'startTime': Date.now(), 2634 'startPosition': config.yaw, 2635 'endPosition': yaw, 2636 'duration': animated 2637 }; 2638 if (typeof callback == 'function') 2639 setTimeout(function(){callback(callbackArgs);}, animated); 2640 } else { 2641 config.yaw = yaw; 2642 } 2643 animateInit(); 2644 return this; 2645 }; 2646 2647 /** 2648 * Returns the minimum and maximum allowed pitches (in degrees). 2649 * @memberof Viewer 2650 * @instance 2651 * @returns {number[]} [yaw pitch, maximum yaw] 2652 */ 2653 this.getYawBounds = function() { 2654 return [config.minYaw, config.maxYaw]; 2655 }; 2656 2657 /** 2658 * Set the minimum and maximum allowed yaws (in degrees [-360, 360]). 2659 * @memberof Viewer 2660 * @instance 2661 * @param {number[]} bounds - [minimum yaw, maximum yaw] 2662 * @returns {Viewer} `this` 2663 */ 2664 this.setYawBounds = function(bounds) { 2665 config.minYaw = Math.max(-360, Math.min(bounds[0], 360)); 2666 config.maxYaw = Math.max(-360, Math.min(bounds[1], 360)); 2667 return this; 2668 }; 2669 2670 /** 2671 * Returns the horizontal field of view. 2672 * @memberof Viewer 2673 * @instance 2674 * @returns {number} Horizontal field of view in degrees 2675 */ 2676 this.getHfov = function() { 2677 return config.hfov; 2678 }; 2679 2680 /** 2681 * Sets the horizontal field of view. 2682 * @memberof Viewer 2683 * @instance 2684 * @param {number} hfov - Horizontal field of view in degrees 2685 * @param {boolean|number} [animated=1000] - Animation duration in milliseconds or false for no animation 2686 * @param {function} [callback] - Function to call when animation finishes 2687 * @param {object} [callbackArgs] - Arguments to pass to callback function 2688 * @returns {Viewer} `this` 2689 */ 2690 this.setHfov = function(hfov, animated, callback, callbackArgs) { 2691 latestInteraction = Date.now(); 2692 if (Math.abs(hfov - config.hfov) <= eps) { 2693 if (typeof callback == 'function') 2694 callback(callbackArgs); 2695 return this; 2696 } 2697 animated = animated == undefined ? 1000: Number(animated); 2698 if (animated) { 2699 animatedMove.hfov = { 2700 'startTime': Date.now(), 2701 'startPosition': config.hfov, 2702 'endPosition': constrainHfov(hfov), 2703 'duration': animated 2704 }; 2705 if (typeof callback == 'function') 2706 setTimeout(function(){callback(callbackArgs);}, animated); 2707 } else { 2708 setHfov(hfov); 2709 } 2710 animateInit(); 2711 return this; 2712 }; 2713 2714 /** 2715 * Returns the minimum and maximum allowed horizontal fields of view 2716 * (in degrees). 2717 * @memberof Viewer 2718 * @instance 2719 * @returns {number[]} [minimum hfov, maximum hfov] 2720 */ 2721 this.getHfovBounds = function() { 2722 return [config.minHfov, config.maxHfov]; 2723 }; 2724 2725 /** 2726 * Set the minimum and maximum allowed horizontal fields of view (in degrees). 2727 * @memberof Viewer 2728 * @instance 2729 * @param {number[]} bounds - [minimum hfov, maximum hfov] 2730 * @returns {Viewer} `this` 2731 */
2732 this.setHfovBounds = function(bounds) { 2733 config.minHfov = Math.max(0, bounds[0]); 2734 config.maxHfov = Math.max(0, bounds[1]); 2735 return this; 2736 }; 2737 2738 /** 2739 * Set a new view. Any parameters not specified remain the same. 2740 * @memberof Viewer 2741 * @instance 2742 * @param {number} [pitch] - Target pitch 2743 * @param {number} [yaw] - Target yaw 2744 * @param {number} [hfov] - Target hfov 2745 * @param {boolean|number} [animated=1000] - Animation duration in milliseconds or false for no animation 2746 * @param {function} [callback] - Function to call when animation finishes 2747 * @param {object} [callbackArgs] - Arguments to pass to callback function 2748 * @returns {Viewer} `this` 2749 */ 2750 this.lookAt = function(pitch, yaw, hfov, animated, callback, callbackArgs) { 2751 animated = animated == undefined ? 1000: Number(animated); 2752 if (pitch !== undefined && Math.abs(pitch - config.pitch) > eps) { 2753 this.setPitch(pitch, animated, callback, callbackArgs); 2754 callback = undefined; 2755 } 2756 if (yaw !== undefined && Math.abs(yaw - config.yaw) > eps) { 2757 this.setYaw(yaw, animated, callback, callbackArgs); 2758 callback = undefined; 2759 } 2760 if (hfov !== undefined && Math.abs(hfov - config.hfov) > eps) { 2761 this.setHfov(hfov, animated, callback, callbackArgs); 2762 callback = undefined; 2763 } 2764 if (typeof callback == 'function') 2765 callback(callbackArgs); 2766 return this; 2767 }; 2768 2769 /** 2770 * Returns the panorama's north offset. 2771 * @memberof Viewer 2772 * @instance 2773 * @returns {number} North offset in degrees 2774 */ 2775 this.getNorthOffset = function() { 2776 return config.northOffset; 2777 }; 2778 2779 /** 2780 * Sets the panorama's north offset. 2781 * @memberof Viewer 2782 * @instance 2783 * @param {number} heading - North offset in degrees 2784 * @returns {Viewer} `this` 2785 */ 2786 this.setNorthOffset = function(heading) { 2787 config.northOffset = Math.min(360, Math.max(0, heading)); 2788 animateInit(); 2789 return this; 2790 }; 2791 2792 /** 2793 * Returns the panorama's horizon roll. 2794 * @memberof Viewer 2795 * @instance 2796 * @returns {number} Horizon roll in degrees 2797 */ 2798 this.getHorizonRoll = function() { 2799 return config.horizonRoll; 2800 }; 2801 2802 /** 2803 * Sets the panorama's horizon roll. 2804 * @memberof Viewer 2805 * @instance 2806 * @param {number} roll - Horizon roll in degrees [-90, 90] 2807 * @returns {Viewer} `this` 2808 */ 2809 this.setHorizonRoll = function(roll) { 2810 config.horizonRoll = Math.min(90, Math.max(-90, roll)); 2811 renderer.setPose(config.horizonPitch * Math.PI / 180, config.horizonRoll * Math.PI / 180); 2812 animateInit(); 2813 return this; 2814 }; 2815 2816 /** 2817 * Returns the panorama's horizon pitch. 2818 * @memberof Viewer 2819 * @instance 2820 * @returns {number} Horizon pitch in degrees 2821 */ 2822 this.getHorizonPitch = function() { 2823 return config.horizonPitch; 2824 }; 2825 2826 /** 2827 * Sets the panorama's horizon pitch. 2828 * @memberof Viewer 2829 * @instance 2830 * @param {number} pitch - Horizon pitch in degrees [-90, 90] 2831 * @returns {Viewer} `this` 2832 */ 2833 this.setHorizonPitch = function(pitch) { 2834 config.horizonPitch = Math.min(90, Math.max(-90, pitch)); 2835 renderer.setPose(config.horizonPitch * Math.PI / 180, config.horizonRoll * Math.PI / 180); 2836 animateInit(); 2837 return this; 2838 }; 2839 2840 /** 2841 * Start auto rotation. 2842 * 2843 * Before starting rotation, the viewer is panned to `pitch`. 2844 * @memberof Viewer 2845 * @instance 2846 * @param {number}
2846 [speed] - Auto rotation speed / direction. If not specified, previous value is used. 2847 * @param {number} [pitch] - The pitch to rotate at. If not specified, inital pitch is used. 2848 * @returns {Viewer} `this` 2849 */ 2850 this.startAutoRotate = function(speed, pitch) { 2851 speed = speed || autoRotateSpeed || 1; 2852 pitch = pitch === undefined ? origPitch : pitch; 2853 config.autoRotate = speed; 2854 _this.lookAt(pitch, undefined, origHfov, 3000); 2855 animateInit(); 2856 return this; 2857 }; 2858 2859 /** 2860 * Stop auto rotation. 2861 * @memberof Viewer 2862 * @instance 2863 * @returns {Viewer} `this` 2864 */ 2865 this.stopAutoRotate = function() { 2866 autoRotateSpeed = config.autoRotate ? config.autoRotate : autoRotateSpeed; 2867 config.autoRotate = false; 2868 config.autoRotateInactivityDelay = -1; 2869 return this; 2870 }; 2871 2872 /** 2873 * Stops all movement. 2874 * @memberof Viewer 2875 * @instance 2876 */ 2877 this.stopMovement = function() { 2878 stopAnimation(); 2879 speed = {'yaw': 0, 'pitch': 0, 'hfov': 0}; 2880 }; 2881 2882 /** 2883 * Returns the panorama renderer. 2884 * @memberof Viewer 2885 * @instance 2886 * @returns {Renderer} 2887 */ 2888 this.getRenderer = function() { 2889 return renderer; 2890 }; 2891 2892 /** 2893 * Sets update flag for dynamic content. 2894 * @memberof Viewer 2895 * @instance 2896 * @param {boolean} bool - Whether or not viewer should update even when still 2897 * @returns {Viewer} `this` 2898 */ 2899 this.setUpdate = function(bool) { 2900 update = bool === true; 2901 if (renderer === undefined) 2902 onImageLoad(); 2903 else 2904 animateInit(); 2905 return this; 2906 }; 2907 2908 /** 2909 * Calculate panorama pitch and yaw from location of mouse event. 2910 * @memberof Viewer 2911 * @instance 2912 * @param {MouseEvent} event - Document mouse down event. 2913 * @returns {number[]} [pitch, yaw] 2914 */ 2915 this.mouseEventToCoords = function(event) { 2916 return mouseEventToCoords(event); 2917 }; 2918 2919 /** 2920 * Change scene being viewed. 2921 * @memberof Viewer 2922 * @instance 2923 * @param {string} sceneId - Identifier of scene to switch to. 2924 * @param {number} [pitch] - Pitch to use with new scene 2925 * @param {number} [yaw] - Yaw to use with new scene 2926 * @param {number} [hfov] - HFOV to use with new scene 2927 * @returns {Viewer} `this` 2928 */ 2929 this.loadScene = function(sceneId, pitch, yaw, hfov) { 2930 if (loaded !== false) 2931 loadScene(sceneId, pitch, yaw, hfov); 2932 return this; 2933 }; 2934 2935 /** 2936 * Get ID of current scene. 2937 * @memberof Viewer 2938 * @instance 2939 * @returns {string} ID of current scene 2940 */
2941 this.getScene = function() { 2942 return config.scene; 2943 }; 2944 2945 /** 2946 * Add a new scene. 2947 * @memberof Viewer 2948 * @instance 2949 * @param {string} sceneId - The ID of the new scene 2950 * @param {string} config - The configuration of the new scene 2951 * @returns {Viewer} `this` 2952 */ 2953 this.addScene = function(sceneId, config) { 2954 initialConfig.scenes[sceneId] = config; 2955 return this; 2956 }; 2957 2958 /** 2959 * Remove a scene. 2960 * @memberof Viewer 2961 * @instance 2962 * @param {string} sceneId - The ID of the scene 2963 * @returns {boolean} False if the scene is the current scene or if the scene doesn't exists, else true 2964 */ 2965 this.removeScene = function(sceneId) { 2966 if (config.scene === sceneId || !initialConfig.scenes.hasOwnProperty(sceneId)) 2967 return false; 2968 delete initialConfig.scenes[sceneId]; 2969 return true; 2970 }; 2971 2972 /** 2973 * Toggle fullscreen. 2974 * @memberof Viewer 2975 * @instance 2976 * @returns {Viewer} `this` 2977 */ 2978 this.toggleFullscreen = function() { 2979 toggleFullscreen(); 2980 return this; 2981 }; 2982 2983 /** 2984 * Get configuration of current scene. 2985 * @memberof Viewer 2986 * @instance 2987 * @returns {Object} Configuration of current scene 2988 */ 2989 this.getConfig = function() { 2990 return config; 2991 }; 2992 2993 /** 2994 * Get viewer's container element. 2995 * @memberof Viewer 2996 * @instance 2997 * @returns {HTMLElement} Container `div` element 2998 */ 2999 this.getContainer = function() { 3000 return container; 3001 }; 3002 3003 /** 3004 * Add a new hot spot. 3005 * @memberof Viewer 3006 * @instance 3007 * @param {Object} hs - The configuration for the hot spot 3008 * @param {string} [sceneId] - Adds hot spot to specified scene if provided, else to current scene 3009 * @returns {Viewer} `this` 3010 * @throws Throws an error if the scene ID is provided but invalid 3011 */ 3012 this.addHotSpot = function(hs, sceneId) { 3013 if (sceneId === undefined && config.scene === undefined) { 3014 // Not a tour 3015 config.hotSpots.push(hs); 3016 } else { 3017 // Tour 3018 var id = sceneId !== undefined ? sceneId : config.scene; 3019 if (initialConfig.scenes.hasOwnProperty(id)) { 3020 if (!initialConfig.scenes[id].hasOwnProperty('hotSpots')) { 3021 initialConfig.scenes[id].hotSpots = []; // Create hot spots array if needed 3022 if (id == config.scene) 3023 config.hotSpots = initialConfig.scenes[id].hotSpots; // Link to current config 3024 } 3025 initialConfig.scenes[id].hotSpots.push(hs); // Add hot spot to config 3026 } else { 3027 throw 'Invalid scene ID!'; 3028 } 3029 } 3030 if (sceneId === undefined || config.scene == sceneId) { 3031 // Add to current scene 3032 createHotSpot(hs); 3033 if (loaded) 3034 renderHotSpot(hs); 3035 } 3036 return this; 3037 }; 3038 3039 /** 3040 * Remove a hot spot. 3041 * @memberof Viewer 3042 * @instance 3043 * @param {string} hotSpotId - The ID of the hot spot 3044 * @param {string} [sceneId] - Removes hot spot from specified scene if provided, else from current scene 3045 * @returns {boolean} True if deletion is successful, else false 3046 */ 3047 this.removeHotSpot = function(hotSpotId, sceneId) { 3048 if (sceneId === undefined || config.scene == sceneId) { 3049 if (!config.hotSpots) 3050 return false; 3051 for (var i = 0; i < config.hotSpots.length; i++) { 3052 if (config.hotSpots[i].hasOwnProperty('id') && 3053 config.hotSpots[i].id === hotSpotId) { 3054 // Delete hot spot DOM elements 3055 var current = config.hotSpots[i].div; 3056 while (current.parentNode != renderContainer) 3057 current = current.parentNode; 3058 renderContainer.removeChild(current); 3059 delete config.hotSpots[i].div; 3060 // Remove hot spot from configuration 3061 config.hotSpots.splice(i, 1); 3062 return true; 3063 } 3064 } 3065 } else { 3066 if (initialConfig.scenes.hasOwnProperty(sceneId)) { 3067 if (!initialConfig.scenes[sceneId].hasOwnProperty('hotSpots')) 3068 return false;
vendor: 6,349 bytes, lines 3069-3238
3069 for (var j = 0; j < initialConfig.scenes[sceneId].hotSpots.length; j++) { 3070 if (initialConfig.scenes[sceneId].hotSpots[j].hasOwnProperty('id') && 3071 initialConfig.scenes[sceneId].hotSpots[j].id === hotSpotId) { 3072 // Remove hot spot from configuration 3073 initialConfig.scenes[sceneId].hotSpots.splice(j, 1); 3074 return true; 3075 } 3076 } 3077 } else { 3078 return false; 3079 } 3080 } 3081 }; 3082 3083 /** 3084 * This method should be called if the viewer's container is resized. 3085 * @memberof Viewer 3086 * @instance 3087 */ 3088 this.resize = function() { 3089 if (renderer) 3090 onDocumentResize(); 3091 }; 3092 3093 /** 3094 * Check if a panorama is loaded. 3095 * @memberof Viewer 3096 * @instance 3097 * @returns {boolean} True if a panorama is loaded, else false 3098 */ 3099 this.isLoaded = function() { 3100 return loaded; 3101 }; 3102 3103 /** 3104 * Check if device orientation control is supported. 3105 * @memberof Viewer 3106 * @instance 3107 * @returns {boolean} True if supported, else false 3108 */ 3109 this.isOrientationSupported = function() { 3110 return orientationSupport || false; 3111 }; 3112 3113 /** 3114 * Stop using device orientation. 3115 * @memberof Viewer 3116 * @instance 3117 */ 3118 this.stopOrientation = function() { 3119 stopOrientation(); 3120 }; 3121 3122 /** 3123 * Start using device orientation (does nothing if not supported). 3124 * @memberof Viewer 3125 * @instance 3126 */ 3127 this.startOrientation = function() { 3128 if (orientationSupport) 3129 startOrientation(); 3130 }; 3131 3132 /** 3133 * Check if device orientation control is currently activated. 3134 * @memberof Viewer 3135 * @instance 3136 * @returns {boolean} True if active, else false 3137 */ 3138 this.isOrientationActive = function() { 3139 return Boolean(orientation); 3140 }; 3141 3142 /** 3143 * Subscribe listener to specified event. 3144 * @memberof Viewer 3145 * @instance 3146 * @param {string} type - Type of event to subscribe to. 3147 * @param {Function} listener - Listener function to subscribe to event. 3148 * @returns {Viewer} `this` 3149 */ 3150 this.on = function(type, listener) { 3151 externalEventListeners[type] = externalEventListeners[type] || []; 3152 externalEventListeners[type].push(listener); 3153 return this; 3154 }; 3155 3156 /** 3157 * Remove an event listener (or listeners). 3158 * @memberof Viewer 3159 * @param {string} [type] - Type of event to remove listeners from. If not specified, all listeners are removed. 3160 * @param {Function} [listener] - Listener function to remove. If not specified, all listeners of specified type are removed. 3161 * @returns {Viewer} `this` 3162 */ 3163 this.off = function(type, listener) { 3164 if (!type) { 3165 // Remove all listeners if type isn't specified 3166 externalEventListeners = {}; 3167 return this; 3168 } 3169 if (listener) { 3170 var i = externalEventListeners[type].indexOf(listener); 3171 if (i >= 0) { 3172 // Remove listener if found 3173 externalEventListeners[type].splice(i, 1); 3174 } 3175 if (externalEventListeners[type].length == 0) { 3176 // Remove category if empty 3177 delete externalEventListeners[type]; 3178 } 3179 } else { 3180 // Remove category of listeners if listener isn't specified 3181 delete externalEventListeners[type]; 3182 } 3183 return this; 3184 }; 3185 3186 /** 3187 * Fire listeners attached to specified event. 3188 * @private 3189 * @param {string} [type] - Type of event to fire listeners for. 3190 */ 3191 function fireEvent(type) { 3192 if (type in externalEventListeners) { 3193 // Reverse iteration is useful, if event listener is removed inside its definition 3194 for (var i = externalEventListeners[type].length; i > 0; i--) { 3195 externalEventListeners[type][externalEventListeners[type].length - i].apply(null, [].slice.call(arguments, 1)); 3196 } 3197 } 3198 } 3199 3200 /** 3201 * Destructor. 3202 * @instance 3203 * @memberof Viewer 3204 */ 3205 this.destroy = function() { 3206 destroyed = true; 3207 clearTimeout(autoRotateStart); 3208 3209 if (renderer) 3210 renderer.destroy(); 3211 if (listenersAdded) { 3212 document.removeEventListener('mousemove', onDocumentMouseMove, false); 3213 document.removeEventListener('compasschange', onDocumentCompassChange, false); 3214 document.removeEventListener('mouseup', onDocumentMouseUp, false); 3215 container.removeEventListener('mozfullscreenchange', onFullScreenChange, false); 3216 container.removeEventListener('webkitfullscreenchange', onFullScreenChange, false); 3217 container.removeEventListener('msfullscreenchange', onFullScreenChange, false); 3218 container.removeEventListener('fullscreenchange', onFullScreenChange, false); 3219 window.removeEventListener('resize', onDocumentResize, false); 3220 window.removeEventListener('orientationchange', onDocumentResize, false); 3221 container.removeEventListener('keydown', onDocumentKeyPress, false); 3222 container.removeEventListener('keyup', onDocumentKeyUp, false); 3223 container.removeEventListener('blur', clearKeys, false); 3224 document.removeEventListener('mouseleave', onDocumentMouseUp, false); 3225 } 3226 container.innerHTML = ''; 3227 container.classList.remove('pnlm-container'); 3228 }; 3229 3230 } 3231 3232 return { 3233 viewer: function(container, config) { 3234 return new Viewer(container, config); 3235 } 3236 }; 3237 3238})(window, document);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.