vendor: 9,100 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, 65, 68, 83, 87, 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 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: 7,954 bytes, lines 1436-1613
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 //Sakib - customized hotspot dom for vr mode 1756 var title = document.createElement('span'); 1757 title.className = 'vr-mode-title'; 1758 if (hs.sceneId) 1759 title.innerHTML = escapeHTML(hs.sceneId); 1760 //Sakib - customized hotspot dom for vr mode end 1761 1762 var span = document.createElement('span'); 1763 if (hs.text) 1764 span.innerHTML = escapeHTML(hs.text); 1765 1766 var a; 1767 if (hs.video) { 1768 var video = document.createElement('video'), 1769 vidp = hs.video; 1770 if (config.basePath && !absoluteURL(vidp)) 1771 vidp = config.basePath + vidp; 1772 video.src = sanitizeURL(vidp); 1773 video.controls = true; 1774 video.style.width = hs.width + 'px'; 1775 renderContainer.appendChild(div); 1776 span.appendChild(video); 1777 } else if (hs.image) { 1778 var imgp = hs.image; 1779 if (config.basePath && !absoluteURL(imgp)) 1780 imgp = config.basePath + imgp; 1781 a = document.createElement('a'); 1782 a.href = sanitizeURL(hs.URL ? hs.URL : imgp, true); 1783 a.target = '_blank'; 1784 span.appendChild(a); 1785 var image = document.createElement('img'); 1786 image.src = sanitizeURL(imgp); 1787 image.style.width = hs.width + 'px'; 1788 image.style.paddingTop = '5px'; 1789 renderContainer.appendChild(div); 1790 a.appendChild(image); 1791 span.style.maxWidth = 'initial'; 1792 } else if (hs.URL) { 1793 a = document.createElement('a'); 1794 a.href = sanitizeURL(hs.URL, true); 1795 if (hs.attributes) { 1796 for (var key in hs.attributes) { 1797 a.setAttribute(key, hs.attributes[key]); 1798 } 1799 } else { 1800 //wpvr edit by sakib for in tab url open// -- handling from here 1801 if (hs.wpvr_url_open == 'on') { 1802 a.target = '_self'; 1803 } 1804 else { 1805 a.target = '_blank'; 1806 } 1807 } 1808 renderContainer.appendChild(a); 1809 div.className += ' pnlm-pointer'; 1810 span.className += ' pnlm-pointer'; 1811 a.appendChild(div); 1812 } else { 1813 if (hs.sceneId) { 1814 div.onclick = div.ontouchend = function() { 1815 if (!div.clicked) { 1816 div.clicked = true; 1817 loadScene(hs.sceneId, hs.targetPitch, hs.targetYaw, hs.targetHfov); 1818 } 1819 return false;
1820 }; 1821 div.className += ' pnlm-pointer'; 1822 span.className += ' pnlm-pointer'; 1823 } 1824 1825 if (hs.sceneId) 1826 div.appendChild(title); 1827 1828 renderContainer.appendChild(div); 1829 } 1830 1831 if (hs.createTooltipFunc) { 1832 hs.createTooltipFunc(div, hs.createTooltipArgs); 1833 } else if (hs.text || hs.video || hs.image) { 1834 div.classList.add('pnlm-tooltip'); 1835 div.appendChild(span); 1836 span.style.width = span.scrollWidth - 20 + 'px'; 1837 span.style.marginLeft = -(span.scrollWidth - div.offsetWidth) / 2 + 'px'; 1838 span.style.marginTop = -span.scrollHeight - 12 + 'px'; 1839 } 1840 if (hs.clickHandlerFunc) { 1841 div.addEventListener('click', function(e) { 1842 hs.clickHandlerFunc(e, hs.clickHandlerArgs); 1843 }, 'false'); 1844 div.className += ' pnlm-pointer'; 1845 span.className += ' pnlm-pointer'; 1846 } 1847 hs.div = div; 1848 } 1849 1850 /** 1851 * Creates hot spot elements for the current scene. 1852 * @private 1853 */ 1854 function createHotSpots() { 1855 if (hotspotsCreated) return; 1856 1857 if (!config.hotSpots) { 1858 config.hotSpots = []; 1859 } else { 1860 // Sort by pitch so tooltip is never obscured by another hot spot 1861 config.hotSpots = config.hotSpots.sort(function(a, b) { 1862 return a.pitch < b.pitch; 1863 }); 1864 config.hotSpots.forEach(createHotSpot); 1865 } 1866 hotspotsCreated = true; 1867 renderHotSpots(); 1868 } 1869 1870 /** 1871 * Destroys currently created hot spot elements. 1872 * @private 1873 */ 1874 function destroyHotSpots() { 1875 var hs = config.hotSpots; 1876 hotspotsCreated = false; 1877 delete config.hotSpots; 1878 if (hs) { 1879 for (var i = 0; i < hs.length; i++) { 1880 var current = hs[i].div; 1881 if (current) { 1882 while (current.parentNode && current.parentNode != renderContainer) { 1883 current = current.parentNode; 1884 } 1885 renderContainer.removeChild(current); 1886 } 1887 delete hs[i].div; 1888 } 1889 } 1890 } 1891 1892 /** 1893 * Renders hot spot, updating its position and visibility. 1894 * @private 1895 */ 1896 function renderHotSpot(hs) { 1897 var hsPitchSin = Math.sin(hs.pitch * Math.PI / 180), 1898 hsPitchCos = Math.cos(hs.pitch * Math.PI / 180), 1899 configPitchSin = Math.sin(config.pitch * Math.PI / 180), 1900 configPitchCos = Math.cos(config.pitch * Math.PI / 180), 1901 yawCos = Math.cos((-hs.yaw + config.yaw) * Math.PI / 180); 1902 var z = hsPitchSin * configPitchSin + hsPitchCos * yawCos * configPitchCos; 1903 if ((hs.yaw <= 90 && hs.yaw > -90 && z <= 0) || 1904 ((hs.yaw > 90 || hs.yaw <= -90) && z <= 0)) { 1905 hs.div.style.visibility = 'hidden'; 1906 } else { 1907 var yawSin = Math.sin((-hs.yaw + config.yaw) * Math.PI / 180), 1908 hfovTan = Math.tan(config.hfov * Math.PI / 360); 1909 hs.div.style.visibility = 'visible'; 1910 // Subpixel rendering doesn't work in Firefox 1911 // https://bugzilla.mozilla.org/show_bug.cgi?id=739176 1912 var canvas = renderer.getCanvas(), 1913 canvasWidth = canvas.clientWidth, 1914 canvasHeight = canvas.clientHeight; 1915 var coord = [-canvasWidth / hfovTan * yawSin * hsPitchCos / z / 2, 1916 -canvasWidth / hfovTan * (hsPitchSin * configPitchCos - 1917 hsPitchCos * yawCos * configPitchSin) / z / 2]; 1918 // Apply roll 1919 var rollSin = Math.sin(config.roll * Math.PI / 180), 1920 rollCos = Math.cos(config.roll * Math.PI / 180); 1921 coord = [coord[0] * rollCos - coord[1] * rollSin, 1922 coord[0] * rollSin + coord[1] * rollCos]; 1923 // Apply transform 1924 coord[0] += (canvasWidth - hs.div.offsetWidth) / 2; 1925 coord[1] += (canvasHeight - hs.div.offsetHeight) / 2; 1926 var transform = 'translate(' + coord[0] + 'px, ' + coord[1] + 1927 'px) translateZ(9999px) rotate(' + config.roll + 'deg)'; 1928 if (hs.scale) { 1929 transform += ' scale(' + (origHfov/config.hfov) / z + ')'; 1930 } 1931 hs.div.style.webkitTransform = transform; 1932 hs.div.style.MozTransform = transform; 1933 hs.div.style.transform = transform; 1934 } 1935 } 1936 1937 /**
1938 * Renders hot spots, updating their positions and visibility. 1939 * @private 1940 */ 1941 function renderHotSpots() { 1942 config.hotSpots.forEach(renderHotSpot); 1943 } 1944 1945 /** 1946 * Merges a scene configuration into the current configuration. 1947 * @private 1948 * @param {string} sceneId - Identifier of scene configuration to merge in. 1949 */ 1950 function mergeConfig(sceneId) { 1951 config = {}; 1952 var k, s; 1953 var photoSphereExcludes = ['haov', 'vaov', 'vOffset', 'northOffset', 'horizonPitch', 'horizonRoll']; 1954 specifiedPhotoSphereExcludes = []; 1955 1956 // Merge default config 1957 for (k in defaultConfig) { 1958 if (defaultConfig.hasOwnProperty(k)) { 1959 config[k] = defaultConfig[k]; 1960 } 1961 } 1962 1963 // Merge default scene config 1964 for (k in initialConfig.default) { 1965 if (initialConfig.default.hasOwnProperty(k)) { 1966 if (k == 'strings') { 1967 for (s in initialConfig.default.strings) { 1968 if (initialConfig.default.strings.hasOwnProperty(s)) { 1969 config.strings[s] = escapeHTML(initialConfig.default.strings[s]); 1970 } 1971 } 1972 } else { 1973 config[k] = initialConfig.default[k]; 1974 if (photoSphereExcludes.indexOf(k) >= 0) { 1975 specifiedPhotoSphereExcludes.push(k); 1976 } 1977 } 1978 } 1979 } 1980 1981 // Merge current scene config 1982 if ((sceneId !== null) && (sceneId !== '') && (initialConfig.scenes) && (initialConfig.scenes[sceneId])) { 1983 var scene = initialConfig.scenes[sceneId]; 1984 for (k in scene) { 1985 if (scene.hasOwnProperty(k)) { 1986 if (k == 'strings') { 1987 for (s in scene.strings) { 1988 if (scene.strings.hasOwnProperty(s)) { 1989 config.strings[s] = escapeHTML(scene.strings[s]); 1990 } 1991 } 1992 } else { 1993 config[k] = scene[k]; 1994 if (photoSphereExcludes.indexOf(k) >= 0) { 1995 specifiedPhotoSphereExcludes.push(k); 1996 } 1997 } 1998 } 1999 } 2000 config.scene = sceneId; 2001 } 2002 2003 // Merge initial config 2004 for (k in initialConfig) { 2005 if (initialConfig.hasOwnProperty(k)) { 2006 if (k == 'strings') { 2007 for (s in initialConfig.strings) { 2008 if (initialConfig.strings.hasOwnProperty(s)) { 2009 config.strings[s] = escapeHTML(initialConfig.strings[s]); 2010 } 2011 } 2012 } else { 2013 config[k] = initialConfig[k]; 2014 if (photoSphereExcludes.indexOf(k) >= 0) { 2015 specifiedPhotoSphereExcludes.push(k); 2016 } 2017 } 2018 } 2019 } 2020 } 2021 2022 /** 2023 * Processes configuration options. 2024 * @param {boolean} [isPreview] - Whether or not the preview is being displayed 2025 * @private 2026 */ 2027 function processOptions(isPreview) { 2028 isPreview = isPreview ? isPreview : false; 2029 2030 // Process preview first so it always loads before the browser hits its 2031 // maximum number of connections to a server as can happen with cubic 2032 // panoramas 2033 if (isPreview && 'preview' in config) { 2034 var p = config.preview; 2035 if (config.basePath && !absoluteURL(p)) 2036 p = config.basePath + p; 2037 preview = document.createElement('div'); 2038 preview.className = 'pnlm-preview-img'; 2039 preview.style.backgroundImage = "url('" + sanitizeURLForCss(p) + "')"; 2040 renderContainer.appendChild(preview); 2041 } 2042 2043 // Handle different preview values 2044 var title = config.title, 2045 author = config.author; 2046 if (isPreview) { 2047 if ('previewTitle' in config) 2048 config.title = config.previewTitle; 2049 if ('previewAuthor' in config) 2050 config.author = config.previewAuthor; 2051 } 2052 2053 // Reset title / author display 2054 if (!config.hasOwnProperty('title')) 2055 infoDisplay.title.innerHTML = ''; 2056 if (!config.hasOwnProperty('author')) 2057 infoDisplay.author.innerHTML = ''; 2058 if (!config.hasOwnProperty('title') && !config.hasOwnProperty('author')) 2059 infoDisplay.container.style.display = 'none'; 2060 2061 // Fill in load button label and loading box text 2062 controls.load.innerHTML = '<p>' + config.strings.loadButtonLabel + '</p>'; 2063 infoDisplay.load.boxp.innerHTML = config.strings.loadingLabel; 2064 2065 // Process other options 2066 for (var key in config) { 2067 if (config.hasOwnProperty(key)) { 2068 switch(key) { 2069 case 'title': 2070 infoDisplay.title.innerHTML = escapeHTML(config[key]); 2071 infoDisplay.container.style.display = 'inline'; 2072 break; 2073 2074 case 'author': 2075 var authorText = escapeHTML(config[key]); 2076 if (config.authorURL) { 2077 var authorLink = document.createElement('a'); 2078 authorLink.href = sanitizeURL(config['authorURL'], true); 2079 authorLink.target = '_blank'; 2080 authorLink.innerHTML = escapeHTML(config[key]); 2081 authorText = authorLink.outerHTML; 2082 } 2083 infoDisplay.author.innerHTML = config.strings.bylineLabel.replace('%s', authorText); 2084 infoDisplay.container.style.display = 'inline'; 2085 break; 2086 2087 case 'fallback': 2088 var link = document.createElement('a'); 2089 link.href = sanitizeURL(config[key], true); 2090 link.target = '_blank'; 2091 link.textContent = 'Click here to view this panorama in an alternative viewer.'; 2092 var message = document.createElement('p'); 2093 message.textContent = 'Your browser does not support WebGL.'; 2094 message.appendChild(document.createElement('br')); 2095 message.appendChild(link); 2096 infoDisplay.errorMsg.innerHTML = ''; // Removes all children nodes 2097 infoDisplay.errorMsg.appendChild(message); 2098 break; 2099 2100 case 'hfov': 2101 setHfov(Number(config[key])); 2102 break; 2103 2104 case 'autoLoad': 2105 if (config[key] === true && renderer === undefined) { 2106 // Show loading box 2107 infoDisplay.load.box.style.display = 'inline'; 2108 // Hide load button 2109 controls.load.style.display = 'none'; 2110 // Initialize 2111 init(); 2112 } 2113 break; 2114 2115 case 'showZoomCtrl': 2116 if (config[key] && config.showControls != false) { 2117 // Show zoom controls 2118 controls.zoom.style.display = 'block'; 2119 } else { 2120 // Hide zoom controls 2121 controls.zoom.style.display = 'none'; 2122 } 2123 break; 2124 2125 case 'showFullscreenCtrl': 2126 if (config[key] && config.showControls != false && ('fullscreen' in document || 'mozFullScreen' in document || 2127 'webkitIsFullScreen' in document || 'msFullscreenElement' in document)) { 2128 2129 // Show fullscreen control 2130 controls.fullscreen.style.display = 'block'; 2131 } else { 2132 // Hide fullscreen control 2133 controls.fullscreen.style.display = 'none'; 2134 } 2135 break; 2136 2137 case 'hotSpotDebug': 2138 if (config[key]) 2139 hotSpotDebugIndicator.style.display = 'block'; 2140 else 2141 hotSpotDebugIndicator.style.display = 'none'; 2142 break; 2143 2144 case 'showControls': 2145 if (!config[key]) { 2146 controls.orientation.style.display = 'none'; 2147 controls.zoom.style.display = 'none'; 2148 controls.fullscreen.style.display = 'none'; 2149 } 2150 break; 2151 2152 case 'orientationOnByDefault': 2153 if (config[key]) 2154 startOrientation(); 2155 break; 2156 } 2157 } 2158 } 2159 2160 if (isPreview) { 2161 // Restore original values if changed for preview 2162 if (title) 2163 config.title = title; 2164 else 2165 delete config.title; 2166 if (author) 2167 config.author = author; 2168 else 2169 delete config.author; 2170 } 2171 } 2172 2173 /** 2174 * Toggles fullscreen mode. 2175 * @private 2176 */ 2177 function toggleFullscreen() { 2178 if (loaded && !error) { 2179 if (!fullscreenActive) { 2180 try { 2181 if (container.requestFullscreen) { 2182 container.requestFullscreen(); 2183 } else if (container.mozRequestFullScreen) { 2184 container.mozRequestFullScreen(); 2185 } else if (container.msRequestFullscreen) { 2186 container.msRequestFullscreen(); 2187 } else { 2188 container.webkitRequestFullScreen(); 2189 } 2190 } catch(event) { 2191 // Fullscreen doesn't work 2192 } 2193 } else { 2194 if (document.exitFullscreen) { 2195 document.exitFullscreen(); 2196 } else if (document.mozCancelFullScreen) { 2197 document.mozCancelFullScreen(); 2198 } else if (document.webkitCancelFullScreen) { 2199 document.webkitCancelFullScreen(); 2200 } else if (document.msExitFullscreen) { 2201 document.msExitFullscreen(); 2202 } 2203 } 2204 } 2205 } 2206 2207 /** 2208 * Event handler for fullscreen changes. 2209 * @private 2210 */ 2211 function onFullScreenChange(resize) { 2212 if (document.fullscreenElement || document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement) { 2213 controls.fullscreen.classList.add('pnlm-fullscreen-toggle-button-active'); 2214 fullscreenActive = true; 2215 } else { 2216 controls.fullscreen.classList.remove('pnlm-fullscreen-toggle-button-active'); 2217 fullscreenActive = false;
vendor: 4,274 bytes, lines 2218-2332
2218 } 2219 if (resize !== 'resize') 2220 fireEvent('fullscreenchange', fullscreenActive); 2221 // Resize renderer (deal with browser quirks and fixes #155) 2222 renderer.resize(); 2223 setHfov(config.hfov); 2224 animateInit(); 2225 } 2226 2227 /** 2228 * Increases panorama zoom. For use with zoom button. 2229 * @private 2230 */ 2231 function zoomIn() { 2232 if (loaded) { 2233 setHfov(config.hfov - 5); 2234 animateInit(); 2235 } 2236 } 2237 2238 /** 2239 * Decreases panorama zoom. For use with zoom button. 2240 * @private 2241 */ 2242 function zoomOut() { 2243 if (loaded) { 2244 setHfov(config.hfov + 5); 2245 animateInit(); 2246 } 2247 } 2248 2249 /** 2250 * Clamps horzontal field of view to viewer's limits. 2251 * @private 2252 * @param {number} hfov - Input horizontal field of view (in degrees) 2253 * @return {number} - Clamped horizontal field of view (in degrees) 2254 */ 2255 function constrainHfov(hfov) { 2256 // Keep field of view within bounds 2257 var minHfov = config.minHfov; 2258 if (config.type == 'multires' && renderer && !config.multiResMinHfov) { 2259 minHfov = Math.min(minHfov, renderer.getCanvas().width / (config.multiRes.cubeResolution / 90 * 0.9)); 2260 } 2261 if (minHfov > config.maxHfov) { 2262 // Don't change view if bounds don't make sense 2263 console.log('HFOV bounds do not make sense (minHfov > maxHfov).'); 2264 return config.hfov; 2265 } 2266 var newHfov = config.hfov; 2267 if (hfov < minHfov) { 2268 newHfov = minHfov; 2269 } else if (hfov > config.maxHfov) { 2270 newHfov = config.maxHfov; 2271 } else { 2272 newHfov = hfov; 2273 } 2274 // Optionally avoid showing background (empty space) on top or bottom by adapting newHfov 2275 if (config.avoidShowingBackground && renderer) { 2276 var canvas = renderer.getCanvas(); 2277 newHfov = Math.min(newHfov, 2278 Math.atan(Math.tan((config.maxPitch - config.minPitch) / 360 * Math.PI) / 2279 canvas.height * canvas.width) * 360 / Math.PI); 2280 } 2281 return newHfov; 2282 } 2283 2284 /** 2285 * Sets viewer's horizontal field of view. 2286 * @private 2287 * @param {number} hfov - Desired horizontal field of view in degrees. 2288 */ 2289 function setHfov(hfov) { 2290 config.hfov = constrainHfov(hfov); 2291 fireEvent('zoomchange', config.hfov); 2292 } 2293 2294 /** 2295 * Stops auto rotation and animated moves. 2296 * @private 2297 */ 2298 function stopAnimation() { 2299 animatedMove = {}; 2300 autoRotateSpeed = config.autoRotate ? config.autoRotate : autoRotateSpeed; 2301 config.autoRotate = false; 2302 } 2303 2304 /** 2305 * Loads panorama. 2306 * @private 2307 */ 2308 function load() { 2309 // Since WebGL error handling is very general, first we clear any error box 2310 // since it is a new scene and the error from previous maybe because of lacking 2311 // memory etc and not because of a lack of WebGL support etc 2312 clearError(); 2313 loaded = false; 2314 2315 controls.load.style.display = 'none'; 2316 infoDisplay.load.box.style.display = 'inline'; 2317 init(); 2318 } 2319 2320 /** 2321 * Loads scene. 2322 * @private 2323 * @param {string} sceneId - Identifier of scene configuration to merge in. 2324 * @param {number} targetPitch - Pitch viewer should be centered on once scene loads. 2325 * @param {number} targetYaw - Yaw viewer should be centered on once scene loads. 2326 * @param {number} targetHfov - HFOV viewer should use once scene loads. 2327 * @param {boolean} [fadeDone] - If `true`, fade setup is skipped. 2328 */ 2329 function loadScene(sceneId, targetPitch, targetYaw, targetHfov, fadeDone) { 2330 if (!loaded) 2331 fadeDone = true; // Don't try to fade when there isn't a scene loaded 2332 loaded = false;
2333 animatedMove = {}; 2334 2335 // Set up fade if specified 2336 var fadeImg, workingPitch, workingYaw, workingHfov; 2337 if (config.sceneFadeDuration && !fadeDone) { 2338 var data = renderer.render(config.pitch * Math.PI / 180, config.yaw * Math.PI / 180, config.hfov * Math.PI / 180, {returnImage: true}); 2339 if (data !== undefined) { 2340 fadeImg = new Image(); 2341 fadeImg.className = 'pnlm-fade-img'; 2342 fadeImg.style.transition = 'opacity ' + (config.sceneFadeDuration / 1000) + 's'; 2343 fadeImg.style.width = '100%'; 2344 fadeImg.style.height = '100%'; 2345 fadeImg.onload = function() { 2346 loadScene(sceneId, targetPitch, targetYaw, targetHfov, true); 2347 }; 2348 fadeImg.src = data; 2349 renderContainer.appendChild(fadeImg); 2350 renderer.fadeImg = fadeImg; 2351 return; 2352 } 2353 } 2354 2355 // Set new pointing 2356 if (targetPitch === 'same') { 2357 workingPitch = config.pitch; 2358 } else { 2359 workingPitch = targetPitch; 2360 } 2361 if (targetYaw === 'same') { 2362 workingYaw = config.yaw; 2363 } else if (targetYaw === 'sameAzimuth') { 2364 workingYaw = config.yaw + (config.northOffset || 0) - (initialConfig.scenes[sceneId].northOffset || 0); 2365 } else { 2366 workingYaw = targetYaw; 2367 } 2368 if (targetHfov === 'same') { 2369 workingHfov = config.hfov; 2370 } else { 2371 workingHfov = targetHfov; 2372 } 2373 2374 // Destroy hot spots from previous scene 2375 destroyHotSpots(); 2376 2377 // Create the new config for the scene 2378 mergeConfig(sceneId); 2379 2380 // Stop motion 2381 speed.yaw = speed.pitch = speed.hfov = 0; 2382 2383 // Reload scene 2384 processOptions(); 2385 if (workingPitch !== undefined) { 2386 config.pitch = workingPitch; 2387 } 2388 if (workingYaw !== undefined) { 2389 config.yaw = workingYaw; 2390 } 2391 if (workingHfov !== undefined) { 2392 config.hfov = workingHfov; 2393 } 2394 fireEvent('scenechange', sceneId); 2395 load(); 2396 } 2397 2398 /** 2399 * Stop using device orientation. 2400 * @private 2401 */ 2402 function stopOrientation() { 2403 window.removeEventListener('deviceorientation', orientationListener); 2404 controls.orientation.classList.remove('pnlm-orientation-button-active'); 2405 orientation = false; 2406 } 2407 2408 /** 2409 * Start using device orientation. 2410 * @private 2411 */ 2412 function startOrientation() { 2413 if (typeof DeviceMotionEvent.requestPermission === 'function') { 2414 DeviceOrientationEvent.requestPermission().then(function(response) { 2415 if (response == 'granted') { 2416 orientation = 1; 2417 window.addEventListener('deviceorientation', orientationListener); 2418 controls.orientation.classList.add('pnlm-orientation-button-active'); 2419 } 2420 }); 2421 } else { 2422 orientation = 1; 2423 window.addEventListener('deviceorientation', orientationListener); 2424 controls.orientation.classList.add('pnlm-orientation-button-active'); 2425 } 2426 } 2427 2428 /** 2429 * Escapes HTML string (to mitigate possible DOM XSS attacks). 2430 * @private 2431 * @param {string} s - String to escape 2432 * @returns {string} Escaped string 2433 */ 2434 function escapeHTML(s) { 2435 if (!initialConfig.escapeHTML) 2436 return String(s).split('\n').join('<br>'); 2437 return String(s).split(/&/g).join('&') 2438 .split('"').join('"') 2439 .split("'").join(''') 2440 .split('<').join('<') 2441 .split('>').join('>') 2442 .split('/').join('/') 2443 .split('\n').join('<br>'); // Allow line breaks 2444 } 2445 2446 /** 2447 * Removes possibility of XSS attacks with URLs.
2448 * The URL cannot be of protocol 'javascript'. 2449 * @private 2450 * @param {string} url - URL to sanitize 2451 * @param {boolean} href - True if URL is for link (blocks data URIs) 2452 * @returns {string} Sanitized URL 2453 */ 2454 function sanitizeURL(url, href) { 2455 try { 2456 var decoded_url = decodeURIComponent(unescape(url)).replace(/[^\w:]/g, '').toLowerCase(); 2457 } catch (e) { 2458 return 'about:blank'; 2459 } 2460 if (decoded_url.indexOf('javascript:') === 0 || 2461 decoded_url.indexOf('vbscript:') === 0) { 2462 console.log('Script URL removed.'); 2463 return 'about:blank'; 2464 } 2465 if (href && decoded_url.indexOf('data:') === 0) { 2466 console.log('Data URI removed from link.'); 2467 return 'about:blank'; 2468 } 2469 return url; 2470 } 2471 2472 /** 2473 * Unescapes HTML entities. 2474 * Copied from Marked.js 0.7.0. 2475 * @private 2476 * @param {string} url - URL to sanitize 2477 * @param {boolean} href - True if URL is for link (blocks data URIs) 2478 * @returns {string} Sanitized URL 2479 */ 2480 function unescape(html) { 2481 // Explicitly match decimal, hex, and named HTML entities 2482 return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { 2483 n = n.toLowerCase(); 2484 if (n === 'colon') return ':'; 2485 if (n.charAt(0) === '#') { 2486 return n.charAt(1) === 'x' 2487 ? String.fromCharCode(parseInt(n.substring(2), 16)) 2488 : String.fromCharCode(+n.substring(1)); 2489 } 2490 return ''; 2491 }); 2492 } 2493 2494 /** 2495 * Removes possibility of XSS atacks with URLs for CSS. 2496 * The URL will be sanitized with `sanitizeURL()` and single quotes 2497 * and double quotes escaped. 2498 * @private 2499 * @param {string} url - URL to sanitize 2500 * @returns {string} Sanitized URL 2501 */ 2502 function sanitizeURLForCss(url) { 2503 return sanitizeURL(url) 2504 .replace(/"/g, '%22') 2505 .replace(/'/g, '%27'); 2506 } 2507 2508 /** 2509 * Checks whether or not a panorama is loaded. 2510 * @memberof Viewer 2511 * @instance 2512 * @returns {boolean} `true` if a panorama is loaded, else `false` 2513 */ 2514 this.isLoaded = function() { 2515 return Boolean(loaded); 2516 }; 2517 2518 /** 2519 * Returns the pitch of the center of the view. 2520 * @memberof Viewer 2521 * @instance 2522 * @returns {number} Pitch in degrees 2523 */ 2524 this.getPitch = function() { 2525 return config.pitch; 2526 }; 2527 2528 /** 2529 * Sets the pitch of the center of the view. 2530 * @memberof Viewer 2531 * @instance 2532 * @param {number} pitch - Pitch in degrees 2533 * @param {boolean|number} [animated=1000] - Animation duration in milliseconds or false for no animation 2534 * @param {function} [callback] - Function to call when animation finishes 2535 * @param {object} [callbackArgs] - Arguments to pass to callback function 2536 * @returns {Viewer} `this` 2537 */ 2538 this.setPitch = function(pitch, animated, callback, callbackArgs) { 2539 latestInteraction = Date.now(); 2540 if (Math.abs(pitch - config.pitch) <= eps) { 2541 if (typeof callback == 'function') 2542 callback(callbackArgs); 2543 return this; 2544 } 2545 animated = animated == undefined ? 1000: Number(animated); 2546 if (animated) { 2547 animatedMove.pitch = { 2548 'startTime': Date.now(), 2549 'startPosition': config.pitch, 2550 'endPosition': pitch, 2551 'duration': animated 2552 }; 2553 if (typeof callback == 'function') 2554 setTimeout(function(){callback(callbackArgs);}, animated); 2555 } else { 2556 config.pitch = pitch; 2557 } 2558 animateInit(); 2559 return this; 2560 }; 2561 2562 /** 2563 * Returns the minimum and maximum allowed pitches (in degrees). 2564 * @memberof Viewer 2565 * @instance 2566 * @returns {number[]} [minimum pitch, maximum pitch] 2567 */ 2568 this.getPitchBounds = function() { 2569 return [config.minPitch, config.maxPitch]; 2570 }; 2571 2572 /** 2573 * Set the minimum and maximum allowed pitches (in degrees). 2574 * @memberof Viewer 2575 * @instance 2576 * @param {number[]} bounds - [minimum pitch, maximum pitch] 2577 * @returns {Viewer} `this` 2578 */
2579 this.setPitchBounds = function(bounds) { 2580 config.minPitch = Math.max(-90, Math.min(bounds[0], 90)); 2581 config.maxPitch = Math.max(-90, Math.min(bounds[1], 90)); 2582 return this; 2583 }; 2584 2585 /** 2586 * Returns the yaw of the center of the view. 2587 * @memberof Viewer 2588 * @instance 2589 * @returns {number} Yaw in degrees 2590 */ 2591 this.getYaw = function() { 2592 return (config.yaw + 540) % 360 - 180; 2593 }; 2594 2595 /** 2596 * Sets the yaw of the center of the view. 2597 * @memberof Viewer 2598 * @instance 2599 * @param {number} yaw - Yaw in degrees [-180, 180] 2600 * @param {boolean|number} [animated=1000] - Animation duration in milliseconds or false for no animation 2601 * @param {function} [callback] - Function to call when animation finishes 2602 * @param {object} [callbackArgs] - Arguments to pass to callback function 2603 * @returns {Viewer} `this` 2604 */ 2605 this.setYaw = function(yaw, animated, callback, callbackArgs) { 2606 latestInteraction = Date.now(); 2607 if (Math.abs(yaw - config.yaw) <= eps) { 2608 if (typeof callback == 'function') 2609 callback(callbackArgs); 2610 return this; 2611 } 2612 animated = animated == undefined ? 1000: Number(animated); 2613 yaw = ((yaw + 180) % 360) - 180; // Keep in bounds 2614 if (animated) { 2615 // Animate in shortest direction 2616 if (config.yaw - yaw > 180) 2617 yaw += 360; 2618 else if (yaw - config.yaw > 180) 2619 yaw -= 360; 2620 2621 animatedMove.yaw = { 2622 'startTime': Date.now(), 2623 'startPosition': config.yaw, 2624 'endPosition': yaw, 2625 'duration': animated 2626 }; 2627 if (typeof callback == 'function') 2628 setTimeout(function(){callback(callbackArgs);}, animated); 2629 } else { 2630 config.yaw = yaw; 2631 } 2632 animateInit(); 2633 return this; 2634 }; 2635 2636 /** 2637 * Returns the minimum and maximum allowed pitches (in degrees). 2638 * @memberof Viewer 2639 * @instance 2640 * @returns {number[]} [yaw pitch, maximum yaw] 2641 */ 2642 this.getYawBounds = function() { 2643 return [config.minYaw, config.maxYaw]; 2644 }; 2645 2646 /** 2647 * Set the minimum and maximum allowed yaws (in degrees [-360, 360]). 2648 * @memberof Viewer 2649 * @instance 2650 * @param {number[]} bounds - [minimum yaw, maximum yaw] 2651 * @returns {Viewer} `this` 2652 */ 2653 this.setYawBounds = function(bounds) { 2654 config.minYaw = Math.max(-360, Math.min(bounds[0], 360)); 2655 config.maxYaw = Math.max(-360, Math.min(bounds[1], 360)); 2656 return this; 2657 }; 2658 2659 /** 2660 * Returns the horizontal field of view. 2661 * @memberof Viewer 2662 * @instance 2663 * @returns {number} Horizontal field of view in degrees 2664 */ 2665 this.getHfov = function() { 2666 return config.hfov; 2667 }; 2668 2669 /** 2670 * Sets the horizontal field of view. 2671 * @memberof Viewer 2672 * @instance 2673 * @param {number} hfov - Horizontal field of view in degrees 2674 * @param {boolean|number} [animated=1000] - Animation duration in milliseconds or false for no animation 2675 * @param {function} [callback] - Function to call when animation finishes 2676 * @param {object} [callbackArgs] - Arguments to pass to callback function 2677 * @returns {Viewer} `this` 2678 */ 2679 this.setHfov = function(hfov, animated, callback, callbackArgs) { 2680 latestInteraction = Date.now(); 2681 if (Math.abs(hfov - config.hfov) <= eps) { 2682 if (typeof callback == 'function') 2683 callback(callbackArgs); 2684 return this; 2685 } 2686 animated = animated == undefined ? 1000: Number(animated); 2687 if (animated) { 2688 animatedMove.hfov = { 2689 'startTime': Date.now(), 2690 'startPosition': config.hfov, 2691 'endPosition': constrainHfov(hfov), 2692 'duration': animated 2693 }; 2694 if (typeof callback == 'function') 2695 setTimeout(function(){callback(callbackArgs);}, animated); 2696 } else { 2697 setHfov(hfov); 2698 } 2699 animateInit(); 2700 return this; 2701 }; 2702 2703 /** 2704 * Returns the minimum and maximum allowed horizontal fields of view 2705 * (in degrees). 2706 * @memberof Viewer 2707 * @instance 2708 * @returns {number[]} [minimum hfov, maximum hfov] 2709 */ 2710 this.getHfovBounds = function() { 2711 return [config.minHfov, config.maxHfov]; 2712 }; 2713 2714 /** 2715 * Set the minimum and maximum allowed horizontal fields of view (in degrees). 2716 * @memberof Viewer 2717 * @instance 2718 * @param {number[]} bounds - [minimum hfov, maximum hfov] 2719 * @returns {Viewer} `this` 2720 */
2721 this.setHfovBounds = function(bounds) { 2722 config.minHfov = Math.max(0, bounds[0]); 2723 config.maxHfov = Math.max(0, bounds[1]); 2724 return this; 2725 }; 2726 2727 /** 2728 * Set a new view. Any parameters not specified remain the same. 2729 * @memberof Viewer 2730 * @instance 2731 * @param {number} [pitch] - Target pitch 2732 * @param {number} [yaw] - Target yaw 2733 * @param {number} [hfov] - Target hfov 2734 * @param {boolean|number} [animated=1000] - Animation duration in milliseconds or false for no animation 2735 * @param {function} [callback] - Function to call when animation finishes 2736 * @param {object} [callbackArgs] - Arguments to pass to callback function 2737 * @returns {Viewer} `this` 2738 */ 2739 this.lookAt = function(pitch, yaw, hfov, animated, callback, callbackArgs) { 2740 animated = animated == undefined ? 1000: Number(animated); 2741 if (pitch !== undefined && Math.abs(pitch - config.pitch) > eps) { 2742 this.setPitch(pitch, animated, callback, callbackArgs); 2743 callback = undefined; 2744 } 2745 if (yaw !== undefined && Math.abs(yaw - config.yaw) > eps) { 2746 this.setYaw(yaw, animated, callback, callbackArgs); 2747 callback = undefined; 2748 } 2749 if (hfov !== undefined && Math.abs(hfov - config.hfov) > eps) { 2750 this.setHfov(hfov, animated, callback, callbackArgs); 2751 callback = undefined; 2752 } 2753 if (typeof callback == 'function') 2754 callback(callbackArgs); 2755 return this; 2756 }; 2757 2758 /** 2759 * Returns the panorama's north offset. 2760 * @memberof Viewer 2761 * @instance 2762 * @returns {number} North offset in degrees 2763 */ 2764 this.getNorthOffset = function() { 2765 return config.northOffset; 2766 }; 2767 2768 /** 2769 * Sets the panorama's north offset. 2770 * @memberof Viewer 2771 * @instance 2772 * @param {number} heading - North offset in degrees 2773 * @returns {Viewer} `this` 2774 */ 2775 this.setNorthOffset = function(heading) { 2776 config.northOffset = Math.min(360, Math.max(0, heading)); 2777 animateInit(); 2778 return this; 2779 }; 2780 2781 /** 2782 * Returns the panorama's horizon roll. 2783 * @memberof Viewer 2784 * @instance 2785 * @returns {number} Horizon roll in degrees 2786 */ 2787 this.getHorizonRoll = function() { 2788 return config.horizonRoll; 2789 }; 2790 2791 /** 2792 * Sets the panorama's horizon roll. 2793 * @memberof Viewer 2794 * @instance 2795 * @param {number} roll - Horizon roll in degrees [-90, 90] 2796 * @returns {Viewer} `this` 2797 */ 2798 this.setHorizonRoll = function(roll) { 2799 config.horizonRoll = Math.min(90, Math.max(-90, roll)); 2800 renderer.setPose(config.horizonPitch * Math.PI / 180, config.horizonRoll * Math.PI / 180); 2801 animateInit(); 2802 return this; 2803 }; 2804 2805 /** 2806 * Returns the panorama's horizon pitch. 2807 * @memberof Viewer 2808 * @instance 2809 * @returns {number} Horizon pitch in degrees 2810 */ 2811 this.getHorizonPitch = function() { 2812 return config.horizonPitch; 2813 }; 2814 2815 /** 2816 * Sets the panorama's horizon pitch. 2817 * @memberof Viewer 2818 * @instance 2819 * @param {number} pitch - Horizon pitch in degrees [-90, 90] 2820 * @returns {Viewer} `this` 2821 */ 2822 this.setHorizonPitch = function(pitch) { 2823 config.horizonPitch = Math.min(90, Math.max(-90, pitch)); 2824 renderer.setPose(config.horizonPitch * Math.PI / 180, config.horizonRoll * Math.PI / 180); 2825 animateInit(); 2826 return this; 2827 }; 2828 2829 /** 2830 * Start auto rotation. 2831 * 2832 * Before starting rotation, the viewer is panned to `pitch`. 2833 * @memberof Viewer 2834 * @instance 2835 * @param {number}
2835 [speed] - Auto rotation speed / direction. If not specified, previous value is used. 2836 * @param {number} [pitch] - The pitch to rotate at. If not specified, inital pitch is used. 2837 * @returns {Viewer} `this` 2838 */ 2839 this.startAutoRotate = function(speed, pitch) { 2840 speed = speed || autoRotateSpeed || 1; 2841 pitch = pitch === undefined ? origPitch : pitch; 2842 config.autoRotate = speed; 2843 _this.lookAt(pitch, undefined, origHfov, 3000); 2844 animateInit(); 2845 return this; 2846 }; 2847 2848 /** 2849 * Stop auto rotation. 2850 * @memberof Viewer 2851 * @instance 2852 * @returns {Viewer} `this` 2853 */ 2854 this.stopAutoRotate = function() { 2855 autoRotateSpeed = config.autoRotate ? config.autoRotate : autoRotateSpeed; 2856 config.autoRotate = false; 2857 config.autoRotateInactivityDelay = -1; 2858 return this; 2859 }; 2860 2861 /** 2862 * Stops all movement. 2863 * @memberof Viewer 2864 * @instance 2865 */ 2866 this.stopMovement = function() { 2867 stopAnimation(); 2868 speed = {'yaw': 0, 'pitch': 0, 'hfov': 0}; 2869 }; 2870 2871 /** 2872 * Returns the panorama renderer. 2873 * @memberof Viewer 2874 * @instance 2875 * @returns {Renderer} 2876 */ 2877 this.getRenderer = function() { 2878 return renderer; 2879 }; 2880 2881 /** 2882 * Sets update flag for dynamic content. 2883 * @memberof Viewer 2884 * @instance 2885 * @param {boolean} bool - Whether or not viewer should update even when still 2886 * @returns {Viewer} `this` 2887 */ 2888 this.setUpdate = function(bool) { 2889 update = bool === true; 2890 if (renderer === undefined) 2891 onImageLoad(); 2892 else 2893 animateInit(); 2894 return this; 2895 }; 2896 2897 /** 2898 * Calculate panorama pitch and yaw from location of mouse event. 2899 * @memberof Viewer 2900 * @instance 2901 * @param {MouseEvent} event - Document mouse down event. 2902 * @returns {number[]} [pitch, yaw] 2903 */ 2904 this.mouseEventToCoords = function(event) { 2905 return mouseEventToCoords(event); 2906 }; 2907 2908 /** 2909 * Change scene being viewed. 2910 * @memberof Viewer 2911 * @instance 2912 * @param {string} sceneId - Identifier of scene to switch to. 2913 * @param {number} [pitch] - Pitch to use with new scene 2914 * @param {number} [yaw] - Yaw to use with new scene 2915 * @param {number} [hfov] - HFOV to use with new scene 2916 * @returns {Viewer} `this` 2917 */ 2918 this.loadScene = function(sceneId, pitch, yaw, hfov) { 2919 if (loaded !== false) 2920 loadScene(sceneId, pitch, yaw, hfov); 2921 return this; 2922 }; 2923 2924 /** 2925 * Get ID of current scene. 2926 * @memberof Viewer 2927 * @instance 2928 * @returns {string} ID of current scene 2929 */
2930 this.getScene = function() { 2931 return config.scene; 2932 }; 2933 2934 /** 2935 * Add a new scene. 2936 * @memberof Viewer 2937 * @instance 2938 * @param {string} sceneId - The ID of the new scene 2939 * @param {string} config - The configuration of the new scene 2940 * @returns {Viewer} `this` 2941 */ 2942 this.addScene = function(sceneId, config) { 2943 initialConfig.scenes[sceneId] = config; 2944 return this; 2945 }; 2946 2947 /** 2948 * Remove a scene. 2949 * @memberof Viewer 2950 * @instance 2951 * @param {string} sceneId - The ID of the scene 2952 * @returns {boolean} False if the scene is the current scene or if the scene doesn't exists, else true 2953 */ 2954 this.removeScene = function(sceneId) { 2955 if (config.scene === sceneId || !initialConfig.scenes.hasOwnProperty(sceneId)) 2956 return false; 2957 delete initialConfig.scenes[sceneId]; 2958 return true; 2959 }; 2960 2961 /** 2962 * Toggle fullscreen. 2963 * @memberof Viewer 2964 * @instance 2965 * @returns {Viewer} `this` 2966 */ 2967 this.toggleFullscreen = function() { 2968 toggleFullscreen(); 2969 return this; 2970 }; 2971 2972 /** 2973 * Get configuration of current scene. 2974 * @memberof Viewer 2975 * @instance 2976 * @returns {Object} Configuration of current scene 2977 */ 2978 this.getConfig = function() { 2979 return config; 2980 }; 2981 2982 /** 2983 * Get viewer's container element. 2984 * @memberof Viewer 2985 * @instance 2986 * @returns {HTMLElement} Container `div` element 2987 */ 2988 this.getContainer = function() { 2989 return container; 2990 }; 2991 2992 /** 2993 * Add a new hot spot. 2994 * @memberof Viewer 2995 * @instance 2996 * @param {Object} hs - The configuration for the hot spot 2997 * @param {string} [sceneId] - Adds hot spot to specified scene if provided, else to current scene 2998 * @returns {Viewer} `this` 2999 * @throws Throws an error if the scene ID is provided but invalid 3000 */ 3001 this.addHotSpot = function(hs, sceneId) { 3002 if (sceneId === undefined && config.scene === undefined) { 3003 // Not a tour 3004 config.hotSpots.push(hs); 3005 } else { 3006 // Tour 3007 var id = sceneId !== undefined ? sceneId : config.scene; 3008 if (initialConfig.scenes.hasOwnProperty(id)) { 3009 if (!initialConfig.scenes[id].hasOwnProperty('hotSpots')) { 3010 initialConfig.scenes[id].hotSpots = []; // Create hot spots array if needed 3011 if (id == config.scene) 3012 config.hotSpots = initialConfig.scenes[id].hotSpots; // Link to current config 3013 } 3014 initialConfig.scenes[id].hotSpots.push(hs); // Add hot spot to config 3015 } else { 3016 throw 'Invalid scene ID!'; 3017 } 3018 } 3019 if (sceneId === undefined || config.scene == sceneId) { 3020 // Add to current scene 3021 createHotSpot(hs); 3022 if (loaded) 3023 renderHotSpot(hs); 3024 } 3025 return this; 3026 }; 3027 3028 /** 3029 * Remove a hot spot. 3030 * @memberof Viewer 3031 * @instance 3032 * @param {string} hotSpotId - The ID of the hot spot 3033 * @param {string} [sceneId] - Removes hot spot from specified scene if provided, else from current scene 3034 * @returns {boolean} True if deletion is successful, else false 3035 */ 3036 this.removeHotSpot = function(hotSpotId, sceneId) { 3037 if (sceneId === undefined || config.scene == sceneId) { 3038 if (!config.hotSpots) 3039 return false; 3040 for (var i = 0; i < config.hotSpots.length; i++) { 3041 if (config.hotSpots[i].hasOwnProperty('id') && 3042 config.hotSpots[i].id === hotSpotId) { 3043 // Delete hot spot DOM elements 3044 var current = config.hotSpots[i].div; 3045 while (current.parentNode != renderContainer) 3046 current = current.parentNode; 3047 renderContainer.removeChild(current); 3048 delete config.hotSpots[i].div; 3049 // Remove hot spot from configuration 3050 config.hotSpots.splice(i, 1); 3051 return true; 3052 } 3053 } 3054 } else { 3055 if (initialConfig.scenes.hasOwnProperty(sceneId)) { 3056 if (!initialConfig.scenes[sceneId].hasOwnProperty('hotSpots')) 3057 return false;
vendor: 6,349 bytes, lines 3058-3227
3058 for (var j = 0; j < initialConfig.scenes[sceneId].hotSpots.length; j++) { 3059 if (initialConfig.scenes[sceneId].hotSpots[j].hasOwnProperty('id') && 3060 initialConfig.scenes[sceneId].hotSpots[j].id === hotSpotId) { 3061 // Remove hot spot from configuration 3062 initialConfig.scenes[sceneId].hotSpots.splice(j, 1); 3063 return true; 3064 } 3065 } 3066 } else { 3067 return false; 3068 } 3069 } 3070 }; 3071 3072 /** 3073 * This method should be called if the viewer's container is resized. 3074 * @memberof Viewer 3075 * @instance 3076 */ 3077 this.resize = function() { 3078 if (renderer) 3079 onDocumentResize(); 3080 }; 3081 3082 /** 3083 * Check if a panorama is loaded. 3084 * @memberof Viewer 3085 * @instance 3086 * @returns {boolean} True if a panorama is loaded, else false 3087 */ 3088 this.isLoaded = function() { 3089 return loaded; 3090 }; 3091 3092 /** 3093 * Check if device orientation control is supported. 3094 * @memberof Viewer 3095 * @instance 3096 * @returns {boolean} True if supported, else false 3097 */ 3098 this.isOrientationSupported = function() { 3099 return orientationSupport || false; 3100 }; 3101 3102 /** 3103 * Stop using device orientation. 3104 * @memberof Viewer 3105 * @instance 3106 */ 3107 this.stopOrientation = function() { 3108 stopOrientation(); 3109 }; 3110 3111 /** 3112 * Start using device orientation (does nothing if not supported). 3113 * @memberof Viewer 3114 * @instance 3115 */ 3116 this.startOrientation = function() { 3117 if (orientationSupport) 3118 startOrientation(); 3119 }; 3120 3121 /** 3122 * Check if device orientation control is currently activated. 3123 * @memberof Viewer 3124 * @instance 3125 * @returns {boolean} True if active, else false 3126 */ 3127 this.isOrientationActive = function() { 3128 return Boolean(orientation); 3129 }; 3130 3131 /** 3132 * Subscribe listener to specified event. 3133 * @memberof Viewer 3134 * @instance 3135 * @param {string} type - Type of event to subscribe to. 3136 * @param {Function} listener - Listener function to subscribe to event. 3137 * @returns {Viewer} `this` 3138 */ 3139 this.on = function(type, listener) { 3140 externalEventListeners[type] = externalEventListeners[type] || []; 3141 externalEventListeners[type].push(listener); 3142 return this; 3143 }; 3144 3145 /** 3146 * Remove an event listener (or listeners). 3147 * @memberof Viewer 3148 * @param {string} [type] - Type of event to remove listeners from. If not specified, all listeners are removed. 3149 * @param {Function} [listener] - Listener function to remove. If not specified, all listeners of specified type are removed. 3150 * @returns {Viewer} `this` 3151 */ 3152 this.off = function(type, listener) { 3153 if (!type) { 3154 // Remove all listeners if type isn't specified 3155 externalEventListeners = {}; 3156 return this; 3157 } 3158 if (listener) { 3159 var i = externalEventListeners[type].indexOf(listener); 3160 if (i >= 0) { 3161 // Remove listener if found 3162 externalEventListeners[type].splice(i, 1); 3163 } 3164 if (externalEventListeners[type].length == 0) { 3165 // Remove category if empty 3166 delete externalEventListeners[type]; 3167 } 3168 } else { 3169 // Remove category of listeners if listener isn't specified 3170 delete externalEventListeners[type]; 3171 } 3172 return this; 3173 }; 3174 3175 /** 3176 * Fire listeners attached to specified event. 3177 * @private 3178 * @param {string} [type] - Type of event to fire listeners for. 3179 */ 3180 function fireEvent(type) { 3181 if (type in externalEventListeners) { 3182 // Reverse iteration is useful, if event listener is removed inside its definition 3183 for (var i = externalEventListeners[type].length; i > 0; i--) { 3184 externalEventListeners[type][externalEventListeners[type].length - i].apply(null, [].slice.call(arguments, 1)); 3185 } 3186 } 3187 } 3188 3189 /** 3190 * Destructor. 3191 * @instance 3192 * @memberof Viewer 3193 */ 3194 this.destroy = function() { 3195 destroyed = true; 3196 clearTimeout(autoRotateStart); 3197 3198 if (renderer) 3199 renderer.destroy(); 3200 if (listenersAdded) { 3201 document.removeEventListener('mousemove', onDocumentMouseMove, false); 3202 document.removeEventListener('compasschange', onDocumentCompassChange, false); 3203 document.removeEventListener('mouseup', onDocumentMouseUp, false); 3204 container.removeEventListener('mozfullscreenchange', onFullScreenChange, false); 3205 container.removeEventListener('webkitfullscreenchange', onFullScreenChange, false); 3206 container.removeEventListener('msfullscreenchange', onFullScreenChange, false); 3207 container.removeEventListener('fullscreenchange', onFullScreenChange, false); 3208 window.removeEventListener('resize', onDocumentResize, false); 3209 window.removeEventListener('orientationchange', onDocumentResize, false); 3210 container.removeEventListener('keydown', onDocumentKeyPress, false); 3211 container.removeEventListener('keyup', onDocumentKeyUp, false); 3212 container.removeEventListener('blur', clearKeys, false); 3213 document.removeEventListener('mouseleave', onDocumentMouseUp, false); 3214 } 3215 container.innerHTML = ''; 3216 container.classList.remove('pnlm-container'); 3217 }; 3218 3219 } 3220 3221 return { 3222 viewer: function(container, config) { 3223 return new Viewer(container, config); 3224 } 3225 }; 3226 3227})(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.