1/* 2 * libpannellum - A WebGL and CSS 3D transform based Panorama Renderer 3 * Copyright (c) 2012-2019 Matthew Petroff 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to deal 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.libpannellum = (function(window, document, undefined) { 25 26 'use strict'; 27 28 /** 29 * Creates a new panorama renderer. 30 * @constructor 31 * @param {HTMLElement} container - The container element for the renderer. 32 */
33 function Renderer(container) { 34 var canvas = document.createElement('canvas'); 35 canvas.style.width = canvas.style.height = '100%'; 36 container.appendChild(canvas); 37 38 var program, gl, vs, fs; 39 var fallbackImgSize; 40 var world; 41 var vtmps; 42 var pose; 43 var image, imageType, dynamic; 44 var texCoordBuffer, cubeVertBuf, cubeVertTexCoordBuf, cubeVertIndBuf; 45 var globalParams; 46 47 /** 48 * Initialize renderer. 49 * @memberof Renderer 50 * @instance 51 * @param {Image|Array|Object} image - Input image; format varies based on 52 * `imageType`. For `equirectangular`, this is an image; for 53 * `cubemap`, this is an array of images for the cube faces in the 54 * order [+z, +x, -z, -x, +y, -y]; for `multires`, this is a 55 * configuration object. 56 * @param {string} imageType - The type of the image: `equirectangular`, 57 * `cubemap`, or `multires`. 58 * @param {boolean} dynamic - Whether or not the image is dynamic (e.g. video). 59 * @param {number} haov - Initial horizontal angle of view. 60 * @param {number} vaov - Initial vertical angle of view. 61 * @param {number} voffset - Initial vertical offset angle. 62 * @param {function} callback - Load callback function. 63 * @param {Object} [params] - Other configuration parameters (`horizonPitch`, `horizonRoll`, `backgroundColor`). 64 */ 65 this.init = function(_image, _imageType, _dynamic, haov, vaov, voffset, callback, params) { 66 // Default argument for image type 67 if (_imageType === undefined) 68 _imageType = 'equirectangular'; 69 70 if (_imageType != 'equirectangular' && _imageType != 'cubemap' && 71 _imageType != 'multires') { 72 console.log('Error: invalid image type specified!'); 73 throw {type: 'config error'}; 74 } 75 76 imageType = _imageType; 77 image = _image; 78 dynamic = _dynamic; 79 globalParams = params || {}; 80 81 // Clear old data 82 if (program) { 83 if (vs) { 84 gl.detachShader(program, vs); 85 gl.deleteShader(vs); 86 } 87 if (fs) { 88 gl.detachShader(program, fs); 89 gl.deleteShader(fs); 90 } 91 gl.bindBuffer(gl.ARRAY_BUFFER, null); 92 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 93 if (program.texture) 94 gl.deleteTexture(program.texture); 95 if (program.nodeCache) 96 for (var i = 0; i < program.nodeCache.length; i++) 97 gl.deleteTexture(program.nodeCache[i].texture); 98 gl.deleteProgram(program); 99 program = undefined; 100 } 101 pose = undefined; 102 103 var s; 104 var faceMissing = false; 105 var cubeImgWidth; 106 if (imageType == 'cubemap') { 107 for (s = 0; s < 6; s++) { 108 if (image[s].width > 0) { 109 if (cubeImgWidth === undefined) 110 cubeImgWidth = image[s].width; 111 if (cubeImgWidth != image[s].width) 112 console.log('Cube faces have inconsistent widths: ' + cubeImgWidth + ' vs. ' + image[s].width); 113 } else 114 faceMissing = true; 115 } 116 } 117 function fillMissingFaces(imgSize) { 118 if (faceMissing) { // Fill any missing fallback/cubemap faces with background 119 var nbytes = imgSize * imgSize * 4; // RGB, plus non-functional alpha 120 var imageArray = new Uint8ClampedArray(nbytes); 121 var rgb = params.backgroundColor ? params.backgroundColor : [0, 0, 0]; 122 rgb[0] *= 255; 123 rgb[1] *= 255; 124 rgb[2] *= 255; 125 // Maybe filling could be done faster, see e.g. https://stackoverflow.com/questions/1295584/most-efficient-way-to-create-a-zero-filled-javascript-array 126 for (var i = 0; i < nbytes; i++) { 127 imageArray[i++] = rgb[0]; 128 imageArray[i++] = rgb[1]; 129 imageArray[i++] = rgb[2]; 130 } 131 var backgroundSquare = new ImageData(imageArray, imgSize, imgSize); 132 for (s = 0; s < 6; s++) { 133 if (image[s].width == 0) 134 image[s] = backgroundSquare; 135 } 136 } 137 } 138 139 // This awful browser specific test exists because iOS 8/9 and IE 11 140 // don't display non-power-of-two cubemap textures but also don't 141 // throw an error (tested on an iPhone 5c / iOS 8.1.3 / iOS 9.2 / 142 // iOS 10.3.1). 143 // Therefore, the WebGL context is never created for these browsers for 144 // NPOT cubemaps, and the CSS 3D transform fallback renderer is used 145 // instead. 146 if (!(imageType == 'cubemap' && 147 (cubeImgWidth & (cubeImgWidth - 1)) !== 0 &&
148 (navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 8_/) || 149 navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 9_/) || 150 navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 10_/) || 151 navigator.userAgent.match(/Trident.*rv[ :]*11\./)))) { 152 // Enable WebGL on canvas 153 if (!gl) 154 gl = canvas.getContext('experimental-webgl', {alpha: false, depth: false}); 155 if (gl && gl.getError() == 1286) 156 handleWebGLError1286(); 157 } 158 159 // If there is no WebGL, fall back to CSS 3D transform renderer. 160 // This will discard the image loaded so far and load the fallback image. 161 // While browser specific tests are usually frowned upon, the 162 // fallback viewer only really works with WebKit/Blink and IE 10/11 163 // (it doesn't work properly in Firefox). 164 if (!gl && ((imageType == 'multires' && image.hasOwnProperty('fallbackPath')) || 165 imageType == 'cubemap') && 166 ('WebkitAppearance' in document.documentElement.style || 167 navigator.userAgent.match(/Trident.*rv[ :]*11\./) || 168 navigator.appVersion.indexOf('MSIE 10') !== -1)) { 169 // Remove old world if it exists 170 if (world) { 171 container.removeChild(world); 172 } 173 174 // Initialize renderer 175 world = document.createElement('div'); 176 world.className = 'pnlm-world'; 177 178 // Add images 179 var path; 180 if (image.basePath) { 181 path = image.basePath + image.fallbackPath; 182 } else { 183 path = image.fallbackPath; 184 } 185 var sides = ['f', 'r', 'b', 'l', 'u', 'd']; 186 var loaded = 0; 187 var onLoad = function() { 188 // Draw image on canvas 189 var faceCanvas = document.createElement('canvas'); 190 faceCanvas.className = 'pnlm-face pnlm-' + sides[this.side] + 'face'; 191 world.appendChild(faceCanvas); 192 var faceContext = faceCanvas.getContext('2d'); 193 faceCanvas.style.width = this.width + 4 + 'px'; 194 faceCanvas.style.height = this.height + 4 + 'px'; 195 faceCanvas.width = this.width + 4; 196 faceCanvas.height = this.height + 4; 197 faceContext.drawImage(this, 2, 2); 198 var imgData = faceContext.getImageData(0, 0, faceCanvas.width, faceCanvas.height); 199 var data = imgData.data; 200 201 // Duplicate edge pixels 202 var i; 203 var j; 204 for (i = 2; i < faceCanvas.width - 2; i++) { 205 for (j = 0; j < 4; j++) { 206 data[(i + faceCanvas.width) * 4 + j] = data[(i + faceCanvas.width * 2) * 4 + j]; 207 data[(i + faceCanvas.width * (faceCanvas.height - 2)) * 4 + j] = data[(i + faceCanvas.width * (faceCanvas.height - 3)) * 4 + j]; 208 } 209 } 210 for (i = 2; i < faceCanvas.height - 2; i++) { 211 for (j = 0; j < 4; j++) { 212 data[(i * faceCanvas.width + 1) * 4 + j] = data[(i * faceCanvas.width + 2) * 4 + j]; 213 data[((i + 1) * faceCanvas.width - 2) * 4 + j] = data[((i + 1) * faceCanvas.width - 3) * 4 + j]; 214 } 215 } 216 for (j = 0; j < 4; j++) { 217 data[(faceCanvas.width + 1) * 4 + j] = data[(faceCanvas.width * 2 + 2) * 4 + j]; 218 data[(faceCanvas.width * 2 - 2) * 4 + j] = data[(faceCanvas.width * 3 - 3) * 4 + j]; 219 data[(faceCanvas.width * (faceCanvas.height - 2) + 1) * 4 + j] = data[(faceCanvas.width * (faceCanvas.height - 3) + 2) * 4 + j]; 220 data[(faceCanvas.width * (faceCanvas.height - 1) - 2) * 4 + j] = data[(faceCanvas.width * (faceCanvas.height - 2) - 3) * 4 + j]; 221 } 222 for (i = 1; i < faceCanvas.width - 1; i++) { 223 for (j = 0; j < 4; j++) { 224 data[i * 4 + j] = data[(i + faceCanvas.width) * 4 + j]; 225 data[(i + faceCanvas.width * (faceCanvas.height - 1)) * 4 + j] = data[(i + faceCanvas.width * (faceCanvas.height - 2)) * 4 + j]; 226 } 227 } 228 for (i = 1; i < faceCanvas.height - 1; i++) { 229 for (j = 0; j < 4; j++) { 230 data[(i * faceCanvas.width) * 4 + j] = data[(i * faceCanvas.width + 1) * 4 + j]; 231 data[((i + 1) * faceCanvas.width - 1) * 4 + j] = data[((i + 1) * faceCanvas.width - 2) * 4 + j]; 232 } 233 } 234 for (j = 0; j < 4; j++) { 235 data[j] = data[(faceCanvas.width + 1) * 4 + j]; 236 data[(faceCanvas.width - 1) * 4 + j] = data[(faceCanvas.width * 2 - 2) * 4 + j]; 237 data[(faceCanvas.width * (faceCanvas.height - 1)) * 4 + j] = data[(faceCanvas.width * (faceCanvas.height - 2) + 1) * 4 + j]; 238 data[(faceCanvas.width * faceCanvas.height - 1) * 4 + j] = data[(faceCanvas.width * (faceCanvas.height - 1) - 2) * 4 + j]; 239 } 240 241 // Draw image width duplicated edge pixels on canvas 242 faceContext.putImageData(imgData, 0, 0); 243 244 incLoaded.call(this); 245 }; 246 var incLoaded = function() { 247 if (this.width > 0) { 248 if (fallbackImgSize === undefined) 249 fallbackImgSize = this.width; 250 if (fallbackImgSize != this.width) 251 console.log('Fallback faces have inconsistent widths: ' + fallbackImgSize + ' vs. ' + this.width); 252 } else 253 faceMissing = true; 254 loaded++; 255 if (loaded == 6) { 256 fallbackImgSize = this.width; 257 container.appendChild(world); 258 callback(); 259 } 260 }; 261 faceMissing = false;
262 for (s = 0; s < 6; s++) { 263 var faceImg = new Image(); 264 faceImg.crossOrigin = globalParams.crossOrigin ? globalParams.crossOrigin : 'anonymous'; 265 faceImg.side = s; 266 faceImg.onload = onLoad; 267 faceImg.onerror = incLoaded; // ignore missing face to support partial fallback image 268 if (imageType == 'multires') { 269 faceImg.src = path.replace('%s', sides[s]) + '.' + image.extension; 270 } else { 271 faceImg.src = image[s].src; 272 } 273 } 274 fillMissingFaces(fallbackImgSize); 275 return; 276 } else if (!gl) { 277 console.log('Error: no WebGL support detected!'); 278 throw {type: 'no webgl'}; 279 } 280 if (imageType == 'cubemap') 281 fillMissingFaces(cubeImgWidth); 282 if (image.basePath) { 283 image.fullpath = image.basePath + image.path; 284 } else { 285 image.fullpath = image.path; 286 } 287 image.invTileResolution = 1 / image.tileResolution; 288 289 var vertices = createCube(); 290 vtmps = []; 291 for (s = 0; s < 6; s++) { 292 vtmps[s] = vertices.slice(s * 12, s * 12 + 12); 293 vertices = createCube(); 294 } 295 296 // Make sure image isn't too big 297 var maxWidth = 0; 298 if (imageType == 'equirectangular') { 299 maxWidth = gl.getParameter(gl.MAX_TEXTURE_SIZE); 300 if (Math.max(image.width / 2, image.height) > maxWidth) { 301 console.log('Error: The image is too big; it\'s ' + image.width + 'px wide, '+ 302 'but this device\'s maximum supported size is ' + (maxWidth * 2) + 'px.'); 303 throw {type: 'webgl size error', width: image.width, maxWidth: maxWidth * 2}; 304 } 305 } else if (imageType == 'cubemap') { 306 if (cubeImgWidth > gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE)) { 307 console.log('Error: The image is too big; it\'s ' + cubeImgWidth + 'px wide, ' + 308 'but this device\'s maximum supported size is ' + maxWidth + 'px.'); 309 throw {type: 'webgl size error', width: cubeImgWidth, maxWidth: maxWidth}; 310 } 311 } 312 313 // Store horizon pitch and roll if applicable 314 if (params !== undefined && (params.horizonPitch !== undefined || params.horizonRoll !== undefined)) 315 pose = [params.horizonPitch == undefined ? 0 : params.horizonPitch, 316 params.horizonRoll == undefined ? 0 : params.horizonRoll]; 317 318 // Set 2d texture binding 319 var glBindType = gl.TEXTURE_2D; 320 321 // Create viewport for entire canvas 322 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 323 324 // Check precision support 325 if (gl.getShaderPrecisionFormat) { 326 var precision = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT); 327 if (precision && precision.precision < 1) { 328 // `highp` precision not supported; https://stackoverflow.com/a/33308927 329 fragEquiCubeBase = fragEquiCubeBase.replace('highp', 'mediump'); 330 } 331 } 332 333 // Create vertex shader 334 vs = gl.createShader(gl.VERTEX_SHADER); 335 var vertexSrc = v; 336 if (imageType == 'multires') { 337 vertexSrc = vMulti; 338 } 339 gl.shaderSource(vs, vertexSrc); 340 gl.compileShader(vs); 341 342 // Create fragment shader 343 fs = gl.createShader(gl.FRAGMENT_SHADER); 344 var fragmentSrc = fragEquirectangular; 345 if (imageType == 'cubemap') { 346 glBindType = gl.TEXTURE_CUBE_MAP; 347 fragmentSrc = fragCube; 348 } else if (imageType == 'multires') { 349 fragmentSrc = fragMulti; 350 } 351 gl.shaderSource(fs, fragmentSrc); 352 gl.compileShader(fs); 353 354 // Link WebGL program 355 program = gl.createProgram(); 356 gl.attachShader(program, vs); 357 gl.attachShader(program, fs); 358 gl.linkProgram(program); 359 360 // Log errors 361 if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) 362 console.log(gl.getShaderInfoLog(vs)); 363 if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) 364 console.log(gl.getShaderInfoLog(fs)); 365 if (!gl.getProgramParameter(program, gl.LINK_STATUS)) 366 console.log(gl.getProgramInfoLog(program)); 367 368 // Use WebGL program 369 gl.useProgram(program); 370 371 program.drawInProgress = false;
372 373 // Set background clear color (does not apply to cubemap/fallback image) 374 var color = params.backgroundColor ? params.backgroundColor : [0, 0, 0]; 375 gl.clearColor(color[0], color[1], color[2], 1.0); 376 gl.clear(gl.COLOR_BUFFER_BIT); 377 378 // Look up texture coordinates location 379 program.texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); 380 gl.enableVertexAttribArray(program.texCoordLocation); 381 382 if (imageType != 'multires') { 383 // Provide texture coordinates for rectangle 384 if (!texCoordBuffer) 385 texCoordBuffer = gl.createBuffer(); 386 gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); 387 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,1,1,1,1,-1,-1,1,1,-1,-1,-1]), gl.STATIC_DRAW); 388 gl.vertexAttribPointer(program.texCoordLocation, 2, gl.FLOAT, false, 0, 0); 389 390 // Pass aspect ratio 391 program.aspectRatio = gl.getUniformLocation(program, 'u_aspectRatio'); 392 gl.uniform1f(program.aspectRatio, gl.drawingBufferWidth / gl.drawingBufferHeight); 393 394 // Locate psi, theta, focal length, horizontal extent, vertical extent, and vertical offset 395 program.psi = gl.getUniformLocation(program, 'u_psi'); 396 program.theta = gl.getUniformLocation(program, 'u_theta'); 397 program.f = gl.getUniformLocation(program, 'u_f'); 398 program.h = gl.getUniformLocation(program, 'u_h'); 399 program.v = gl.getUniformLocation(program, 'u_v'); 400 program.vo = gl.getUniformLocation(program, 'u_vo'); 401 program.rot = gl.getUniformLocation(program, 'u_rot'); 402 403 // Pass horizontal extent, vertical extent, and vertical offset 404 gl.uniform1f(program.h, haov / (Math.PI * 2.0)); 405 gl.uniform1f(program.v, vaov / Math.PI); 406 gl.uniform1f(program.vo, voffset / Math.PI * 2); 407 408 // Set background color 409 if (imageType == 'equirectangular') { 410 program.backgroundColor = gl.getUniformLocation(program, 'u_backgroundColor'); 411 gl.uniform4fv(program.backgroundColor, color.concat([1])); 412 } 413 414 // Create texture 415 program.texture = gl.createTexture(); 416 gl.bindTexture(glBindType, program.texture); 417 418 // Upload images to texture depending on type 419 if (imageType == 'cubemap') { 420 // Load all six sides of the cube map 421 gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[1]); 422 gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[3]); 423 gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[4]); 424 gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[5]); 425 gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[0]); 426 gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[2]); 427 } else { 428 if (image.width <= maxWidth) { 429 gl.uniform1i(gl.getUniformLocation(program, 'u_splitImage'), 0); 430 // Upload image to the texture 431 gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); 432 } else { 433 // Image needs to be split into two parts due to texture size limits 434 gl.uniform1i(gl.getUniformLocation(program, 'u_splitImage'), 1); 435 436 // Draw image on canvas 437 var cropCanvas = document.createElement('canvas'); 438 cropCanvas.width = image.width / 2; 439 cropCanvas.height = image.height; 440 var cropContext = cropCanvas.getContext('2d');
441 cropContext.drawImage(image, 0, 0); 442 443 // Upload first half of image to the texture 444 var cropImage = cropContext.getImageData(0, 0, image.width / 2, image.height); 445 gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, cropImage); 446 447 // Create and bind texture for second half of image 448 program.texture2 = gl.createTexture(); 449 gl.activeTexture(gl.TEXTURE1); 450 gl.bindTexture(glBindType, program.texture2); 451 gl.uniform1i(gl.getUniformLocation(program, 'u_image1'), 1); 452 453 // Upload second half of image to the texture 454 cropContext.drawImage(image, -image.width / 2, 0); 455 cropImage = cropContext.getImageData(0, 0, image.width / 2, image.height); 456 gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, cropImage); 457 458 // Set parameters for rendering any size 459 gl.texParameteri(glBindType, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 460 gl.texParameteri(glBindType, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 461 gl.texParameteri(glBindType, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 462 gl.texParameteri(glBindType, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 463 464 // Reactivate first texture unit 465 gl.activeTexture(gl.TEXTURE0); 466 } 467 } 468 469 // Set parameters for rendering any size 470 gl.texParameteri(glBindType, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 471 gl.texParameteri(glBindType, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 472 gl.texParameteri(glBindType, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 473 gl.texParameteri(glBindType, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 474 475 } else { 476 // Look up vertex coordinates location 477 program.vertPosLocation = gl.getAttribLocation(program, 'a_vertCoord'); 478 gl.enableVertexAttribArray(program.vertPosLocation); 479 480 // Create buffers 481 if (!cubeVertBuf) 482 cubeVertBuf = gl.createBuffer(); 483 if (!cubeVertTexCoordBuf) 484 cubeVertTexCoordBuf = gl.createBuffer(); 485 if (!cubeVertIndBuf) 486 cubeVertIndBuf = gl.createBuffer(); 487 488 // Bind texture coordinate buffer and pass coordinates to WebGL 489 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertTexCoordBuf); 490 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0,1,0,1,1,0,1]), gl.STATIC_DRAW); 491 492 // Bind square index buffer and pass indicies to WebGL 493 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertIndBuf); 494 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0,1,2,0,2,3]), gl.STATIC_DRAW); 495 496 // Find uniforms 497 program.perspUniform = gl.getUniformLocation(program, 'u_perspMatrix'); 498 program.cubeUniform = gl.getUniformLocation(program, 'u_cubeMatrix'); 499 //program.colorUniform = gl.getUniformLocation(program, 'u_color'); 500 501 program.level = -1; 502 503 program.currentNodes = []; 504 program.nodeCache = []; 505 program.nodeCacheTimestamp = 0; 506 } 507 508 // Check if there was an error 509 var err = gl.getError(); 510 if (err !== 0) { 511 console.log('Error: Something went wrong with WebGL!', err); 512 throw {type: 'webgl error'}; 513 } 514 515 callback(); 516 }; 517 518 /** 519 * Destroy renderer. 520 * @memberof Renderer 521 * @instance 522 */ 523 this.destroy = function() { 524 if (container !== undefined) { 525 if (canvas !== undefined && container.contains(canvas)) { 526 container.removeChild(canvas); 527 } 528 if (world !== undefined && container.contains(world)) { 529 container.removeChild(world); 530 } 531 } 532 if (gl) { 533 // The spec says this is only supposed to simulate losing the WebGL 534 // context, but in practice it tends to actually free the memory. 535 var extension = gl.getExtension('WEBGL_lose_context'); 536 if (extension) 537 extension.loseContext(); 538 } 539 }; 540 541 /** 542 * Resize renderer (call after resizing container). 543 * @memberof Renderer 544 * @instance 545 */ 546 this.resize = function() { 547 var pixelRatio = window.devicePixelRatio || 1; 548 canvas.width = canvas.clientWidth * pixelRatio; 549 canvas.height = canvas.clientHeight * pixelRatio; 550 if (gl) { 551 if (gl.getError() == 1286) 552 handleWebGLError1286(); 553 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 554 if (imageType != 'multires') { 555 gl.uniform1f(program.aspectRatio, canvas.clientWidth / canvas.clientHeight); 556 } 557 } 558 }; 559 // Initialize canvas size 560 this.resize(); 561 562 /** 563 * Set renderer horizon pitch and roll. 564 * @memberof Renderer 565 * @instance 566 */ 567 this.setPose = function(horizonPitch, horizonRoll) { 568 pose = [horizonPitch, horizonRoll]; 569 }; 570 571 /**
572 * Render new view of panorama. 573 * @memberof Renderer 574 * @instance 575 * @param {number} pitch - Pitch to render at (in radians). 576 * @param {number} yaw - Yaw to render at (in radians). 577 * @param {number} hfov - Horizontal field of view to render with (in radians). 578 * @param {Object} [params] - Extra configuration parameters. 579 * @param {number} [params.roll] - Camera roll (in radians). 580 * @param {boolean} [params.returnImage] - Return rendered image? 581 */ 582 this.render = function(pitch, yaw, hfov, params) { 583 var focal, i, s, roll = 0; 584 if (params === undefined) 585 params = {}; 586 if (params.roll) 587 roll = params.roll; 588 589 // Apply pitch and roll transformation if applicable 590 if (pose !== undefined) { 591 var horizonPitch = pose[0], 592 horizonRoll = pose[1]; 593 594 // Calculate new pitch and yaw 595 var orig_pitch = pitch, 596 orig_yaw = yaw, 597 x = Math.cos(horizonRoll) * Math.sin(pitch) * Math.sin(horizonPitch) + 598 Math.cos(pitch) * (Math.cos(horizonPitch) * Math.cos(yaw) + 599 Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.sin(yaw)), 600 y = -Math.sin(pitch) * Math.sin(horizonRoll) + 601 Math.cos(pitch) * Math.cos(horizonRoll) * Math.sin(yaw), 602 z = Math.cos(horizonRoll) * Math.cos(horizonPitch) * Math.sin(pitch) + 603 Math.cos(pitch) * (-Math.cos(yaw) * Math.sin(horizonPitch) + 604 Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.sin(yaw)); 605 pitch = Math.asin(Math.max(Math.min(z, 1), -1)); 606 yaw = Math.atan2(y, x); 607 608 // Calculate roll 609 var v = [Math.cos(orig_pitch) * (Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.cos(orig_yaw) - 610 Math.cos(horizonPitch) * Math.sin(orig_yaw)), 611 Math.cos(orig_pitch) * Math.cos(horizonRoll) * Math.cos(orig_yaw), 612 Math.cos(orig_pitch) * (Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.cos(orig_yaw) + 613 Math.sin(orig_yaw) * Math.sin(horizonPitch))], 614 w = [-Math.cos(pitch) * Math.sin(yaw), Math.cos(pitch) * Math.cos(yaw)]; 615 var roll_adj = Math.acos(Math.max(Math.min((v[0]*w[0] + v[1]*w[1]) / 616 (Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) * 617 Math.sqrt(w[0]*w[0]+w[1]*w[1])), 1), -1)); 618 if (v[2] < 0) 619 roll_adj = 2 * Math.PI - roll_adj; 620 roll += roll_adj; 621 } 622 623 // If no WebGL 624 if (!gl && (imageType == 'multires' || imageType == 'cubemap')) { 625 // Determine face transforms 626 s = fallbackImgSize / 2; 627 628 var transforms = { 629 f: 'translate3d(-' + (s + 2) + 'px, -' + (s + 2) + 'px, -' + s + 'px)', 630 b: 'translate3d(' + (s + 2) + 'px, -' + (s + 2) + 'px, ' + s + 'px) rotateX(180deg) rotateZ(180deg)', 631 u: 'translate3d(-' + (s + 2) + 'px, -' + s + 'px, ' + (s + 2) + 'px) rotateX(270deg)', 632 d: 'translate3d(-' + (s + 2) + 'px, ' + s + 'px, -' + (s + 2) + 'px) rotateX(90deg)', 633 l: 'translate3d(-' + s + 'px, -' + (s + 2) + 'px, ' + (s + 2) + 'px) rotateX(180deg) rotateY(90deg) rotateZ(180deg)', 634 r: 'translate3d(' + s + 'px, -' + (s + 2) + 'px, -' + (s + 2) + 'px) rotateY(270deg)' 635 }; 636 focal = 1 / Math.tan(hfov / 2); 637 var zoom = focal * canvas.clientWidth / 2 + 'px'; 638 var transform = 'perspective(' + zoom + ') translateZ(' + zoom + ') rotateX(' + pitch + 'rad) rotateY(' + yaw + 'rad) '; 639 640 // Apply face transforms 641 var faces = Object.keys(transforms); 642 for (i = 0; i < 6; i++) { 643 var face = world.querySelector('.pnlm-' + faces[i] + 'face'); 644 if (!face) 645 continue;
vendor: 7,995 bytes, lines 645-837
645 // ignore missing face to support partial cubemap/fallback image 646 face.style.webkitTransform = transform + transforms[faces[i]]; 647 face.style.transform = transform + transforms[faces[i]]; 648 } 649 return; 650 } 651 652 if (imageType != 'multires') { 653 // Calculate focal length from vertical field of view 654 var vfov = 2 * Math.atan(Math.tan(hfov * 0.5) / (gl.drawingBufferWidth / gl.drawingBufferHeight)); 655 focal = 1 / Math.tan(vfov * 0.5); 656 657 // Pass psi, theta, roll, and focal length 658 gl.uniform1f(program.psi, yaw); 659 gl.uniform1f(program.theta, pitch); 660 gl.uniform1f(program.rot, roll); 661 gl.uniform1f(program.f, focal); 662 663 if (dynamic === true) { 664 // Update texture if dynamic 665 if (imageType == 'equirectangular') { 666 gl.bindTexture(gl.TEXTURE_2D, program.texture); 667 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); 668 } 669 } 670 671 // Draw using current buffer 672 gl.drawArrays(gl.TRIANGLES, 0, 6); 673 674 } else { 675 // Create perspective matrix 676 var perspMatrix = makePersp(hfov, gl.drawingBufferWidth / gl.drawingBufferHeight, 0.1, 100.0); 677 678 // Find correct zoom level 679 checkZoom(hfov); 680 681 // Create rotation matrix 682 var matrix = identityMatrix3(); 683 matrix = rotateMatrix(matrix, -roll, 'z'); 684 matrix = rotateMatrix(matrix, -pitch, 'x'); 685 matrix = rotateMatrix(matrix, yaw, 'y'); 686 matrix = makeMatrix4(matrix); 687 688 // Set matrix uniforms 689 gl.uniformMatrix4fv(program.perspUniform, false, new Float32Array(transposeMatrix4(perspMatrix))); 690 gl.uniformMatrix4fv(program.cubeUniform, false, new Float32Array(transposeMatrix4(matrix))); 691 692 // Find current nodes 693 var rotPersp = rotatePersp(perspMatrix, matrix); 694 program.nodeCache.sort(multiresNodeSort); 695 if (program.nodeCache.length > 200 && 696 program.nodeCache.length > program.currentNodes.length + 50) { 697 // Remove older nodes from cache 698 var removed = program.nodeCache.splice(200, program.nodeCache.length - 200); 699 for (var j = 0; j < removed.length; j++) { 700 // Explicitly delete textures 701 gl.deleteTexture(removed[j].texture); 702 } 703 } 704 program.currentNodes = []; 705 706 var sides = ['f', 'b', 'u', 'd', 'l', 'r']; 707 for (s = 0; s < 6; s++) { 708 var ntmp = new MultiresNode(vtmps[s], sides[s], 1, 0, 0, image.fullpath); 709 testMultiresNode(rotPersp, ntmp, pitch, yaw, hfov); 710 } 711 712 program.currentNodes.sort(multiresNodeRenderSort); 713 714 // Unqueue any pending requests for nodes that are no longer visible 715 for (i = pendingTextureRequests.length - 1; i >= 0; i--) { 716 if (program.currentNodes.indexOf(pendingTextureRequests[i].node) === -1) { 717 pendingTextureRequests[i].node.textureLoad = false; 718 pendingTextureRequests.splice(i, 1); 719 } 720 } 721 722 // Allow one request to be pending, so that we can create a texture buffer for that in advance of loading actually beginning 723 if (pendingTextureRequests.length === 0) { 724 for (i = 0; i < program.currentNodes.length; i++) { 725 var node = program.currentNodes[i]; 726 if (!node.texture && !node.textureLoad) { 727 node.textureLoad = true; 728 729 setTimeout(processNextTile, 0, node); 730 731 // Only process one tile per frame to improve responsiveness 732 break; 733 } 734 } 735 } 736 737 // Draw tiles 738 multiresDraw(); 739 } 740 741 if (params.returnImage !== undefined) { 742 return canvas.toDataURL('image/png'); 743 } 744 }; 745 746 /** 747 * Check if images are loading. 748 * @memberof Renderer 749 * @instance 750 * @returns {boolean} Whether or not images are loading. 751 */ 752 this.isLoading = function() { 753 if (gl && imageType == 'multires') { 754 for ( var i = 0; i < program.currentNodes.length; i++ ) { 755 if (!program.currentNodes[i].textureLoaded) { 756 return true; 757 } 758 } 759 } 760 return false; 761 }; 762 763 /** 764 * Retrieve renderer's canvas. 765 * @memberof Renderer 766 * @instance 767 * @returns {HTMLElement} Renderer's canvas. 768 */ 769 this.getCanvas = function() { 770 return canvas; 771 }; 772 773 /** 774 * Sorting method for multires nodes. 775 * @private 776 * @param {MultiresNode} a - First node. 777 * @param {MultiresNode} b - Second node. 778 * @returns {number} Base tiles first, then higher timestamp first. 779 */ 780 function multiresNodeSort(a, b) { 781 // Base tiles are always first 782 if (a.level == 1 && b.level != 1) { 783 return -1; 784 } 785 if (b. level == 1 && a.level != 1) { 786 return 1; 787 } 788 789 // Higher timestamp first 790 return b.timestamp - a.timestamp; 791 } 792 793 /** 794 * Sorting method for multires node rendering. 795 * @private 796 * @param {MultiresNode} a - First node. 797 * @param {MultiresNode} b - Second node. 798 * @returns {number} Lower zoom levels first, then closest to center first. 799 */ 800 function multiresNodeRenderSort(a, b) { 801 // Lower zoom levels first 802 if (a.level != b.level) { 803 return a.level - b.level; 804 } 805 806 // Lower distance from center first 807 return a.diff - b.diff; 808 } 809 810 /** 811 * Draws multires nodes. 812 * @private 813 */ 814 function multiresDraw() { 815 if (!program.drawInProgress) { 816 program.drawInProgress = true; 817 gl.clear(gl.COLOR_BUFFER_BIT); 818 for ( var i = 0; i < program.currentNodes.length; i++ ) { 819 if (program.currentNodes[i].textureLoaded > 1) { 820 //var color = program.currentNodes[i].color; 821 //gl.uniform4f(program.colorUniform, color[0], color[1], color[2], 1.0); 822 823 // Bind vertex buffer and pass vertices to WebGL 824 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertBuf); 825 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(program.currentNodes[i].vertices), gl.STATIC_DRAW); 826 gl.vertexAttribPointer(program.vertPosLocation, 3, gl.FLOAT, false, 0, 0); 827 828 // Prep for texture 829 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertTexCoordBuf); 830 gl.vertexAttribPointer(program.texCoordLocation, 2, gl.FLOAT, false, 0, 0); 831 832 // Bind texture and draw tile 833 gl.bindTexture(gl.TEXTURE_2D, program.currentNodes[i].texture); // Bind program.currentNodes[i].texture to TEXTURE0 834 gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); 835 } 836 } 837 program.drawInProgress = false;
838 } 839 } 840 841 /** 842 * Creates new multires node. 843 * @constructor 844 * @private 845 * @param {number[]} vertices - Node's verticies. 846 * @param {string} side - Node's cube face. 847 * @param {number} level - Node's zoom level. 848 * @param {number} x - Node's x position. 849 * @param {number} y - Node's y position. 850 * @param {string} path - Node's path. 851 */ 852 function MultiresNode(vertices, side, level, x, y, path) { 853 this.vertices = vertices; 854 this.side = side; 855 this.level = level; 856 this.x = x; 857 this.y = y; 858 this.path = path.replace('%s',side).replace('%l',level).replace('%x',x).replace('%y',y); 859 } 860 861 /** 862 * Test if multires node is visible. If it is, add it to current nodes, 863 * load its texture, and load appropriate child nodes. 864 * @private 865 * @param {number[]} rotPersp - Rotated perspective matrix. 866 * @param {MultiresNode} node - Multires node to check. 867 * @param {number} pitch - Pitch to check at. 868 * @param {number} yaw - Yaw to check at. 869 * @param {number} hfov - Horizontal field of view to check at. 870 */ 871 function testMultiresNode(rotPersp, node, pitch, yaw, hfov) { 872 if (checkSquareInView(rotPersp, node.vertices)) { 873 // Calculate central angle between center of view and center of tile 874 var v = node.vertices; 875 var x = v[0] + v[3] + v[6] + v[ 9]; 876 var y = v[1] + v[4] + v[7] + v[10]; 877 var z = v[2] + v[5] + v[8] + v[11]; 878 var r = Math.sqrt(x*x + y*y + z*z); 879 var theta = Math.asin(z / r); 880 var phi = Math.atan2(y, x); 881 var ydiff = phi - yaw; 882 ydiff += (ydiff > Math.PI) ? -2 * Math.PI : (ydiff < -Math.PI) ? 2 * Math.PI : 0; 883 ydiff = Math.abs(ydiff); 884 node.diff = Math.acos(Math.sin(pitch) * Math.sin(theta) + Math.cos(pitch) * Math.cos(theta) * Math.cos(ydiff)); 885 886 // Add node to current nodes and load texture if needed 887 var inCurrent = false; 888 for (var k = 0; k < program.nodeCache.length; k++) { 889 if (program.nodeCache[k].path == node.path) { 890 inCurrent = true; 891 program.nodeCache[k].timestamp = program.nodeCacheTimestamp++; 892 program.nodeCache[k].diff = node.diff; 893 program.currentNodes.push(program.nodeCache[k]); 894 break; 895 } 896 } 897 if (!inCurrent) { 898 //node.color = [Math.random(), Math.random(), Math.random()]; 899 node.timestamp = program.nodeCacheTimestamp++; 900 program.currentNodes.push(node); 901 program.nodeCache.push(node); 902 } 903 904 // TODO: Test error 905 // Create child nodes 906 if (node.level < program.level) { 907 var cubeSize = image.cubeResolution * Math.pow(2, node.level - image.maxLevel); 908 var numTiles = Math.ceil(cubeSize * image.invTileResolution) - 1; 909 var doubleTileSize = cubeSize % image.tileResolution * 2; 910 var lastTileSize = (cubeSize * 2) % image.tileResolution; 911 if (lastTileSize === 0) { 912 lastTileSize = image.tileResolution; 913 } 914 if (doubleTileSize === 0) { 915 doubleTileSize = image.tileResolution * 2; 916 } 917 var f = 0.5; 918 if (node.x == numTiles || node.y == numTiles) { 919 f = 1.0 - image.tileResolution / (image.tileResolution + lastTileSize); 920 } 921 var i = 1.0 - f; 922 var children = []; 923 var vtmp, ntmp; 924 var f1 = f, f2 = f, f3 = f, i1 = i, i2 = i, i3 = i; 925 // Handle non-symmetric tiles
vendor: 4,313 bytes, lines 926-1006
926 if (lastTileSize < image.tileResolution) { 927 if (node.x == numTiles && node.y != numTiles) { 928 f2 = 0.5; 929 i2 = 0.5; 930 if (node.side == 'd' || node.side == 'u') { 931 f3 = 0.5; 932 i3 = 0.5; 933 } 934 } else if (node.x != numTiles && node.y == numTiles) { 935 f1 = 0.5; 936 i1 = 0.5; 937 if (node.side == 'l' || node.side == 'r') { 938 f3 = 0.5; 939 i3 = 0.5; 940 } 941 } 942 } 943 // Handle small tiles that have fewer than four children 944 if (doubleTileSize <= image.tileResolution) { 945 if (node.x == numTiles) { 946 f1 = 0; 947 i1 = 1; 948 if (node.side == 'l' || node.side == 'r') { 949 f3 = 0; 950 i3 = 1; 951 } 952 } 953 if (node.y == numTiles) { 954 f2 = 0; 955 i2 = 1; 956 if (node.side == 'd' || node.side == 'u') { 957 f3 = 0; 958 i3 = 1; 959 } 960 } 961 } 962 963 vtmp = [ v[0], v[1], v[2], 964 v[0]*f1+v[3]*i1, v[1]*f+v[4]*i, v[2]*f3+v[5]*i3, 965 v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, 966 v[0]*f+v[9]*i, v[1]*f2+v[10]*i2, v[2]*f3+v[11]*i3 967 ]; 968 ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2, node.y*2, image.fullpath); 969 children.push(ntmp); 970 if (!(node.x == numTiles && doubleTileSize <= image.tileResolution)) { 971 vtmp = [v[0]*f1+v[3]*i1, v[1]*f+v[4]*i, v[2]*f3+v[5]*i3, 972 v[3], v[4], v[5], 973 v[3]*f+v[6]*i, v[4]*f2+v[7]*i2, v[5]*f3+v[8]*i3, 974 v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3 975 ]; 976 ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2+1, node.y*2, image.fullpath); 977 children.push(ntmp); 978 } 979 if (!(node.x == numTiles && doubleTileSize <= image.tileResolution) && 980 !(node.y == numTiles && doubleTileSize <= image.tileResolution)) { 981 vtmp = [v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, 982 v[3]*f+v[6]*i, v[4]*f2+v[7]*i2, v[5]*f3+v[8]*i3, 983 v[6], v[7], v[8], 984 v[9]*f1+v[6]*i1, v[10]*f+v[7]*i, v[11]*f3+v[8]*i3 985 ]; 986 ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2+1, node.y*2+1, image.fullpath); 987 children.push(ntmp); 988 } 989 if (!(node.y == numTiles && doubleTileSize <= image.tileResolution)) { 990 vtmp = [ v[0]*f+v[9]*i, v[1]*f2+v[10]*i2, v[2]*f3+v[11]*i3, 991 v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, 992 v[9]*f1+v[6]*i1, v[10]*f+v[7]*i, v[11]*f3+v[8]*i3, 993 v[9], v[10], v[11] 994 ]; 995 ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2, node.y*2+1, image.fullpath); 996 children.push(ntmp); 997 } 998 for (var j = 0; j < children.length; j++) { 999 testMultiresNode(rotPersp, children[j], pitch, yaw, hfov); 1000 } 1001 } 1002 } 1003 } 1004 1005 /** 1006 * Creates cube vertex array.
1007 * @private 1008 * @returns {number[]} Cube vertex array. 1009 */ 1010 function createCube() { 1011 return [-1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, // Front face 1012 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // Back face 1013 -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, // Up face 1014 -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, // Down face 1015 -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // Left face 1016 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1 // Right face 1017 ]; 1018 } 1019 1020 /** 1021 * Creates 3x3 identity matrix. 1022 * @private 1023 * @returns {number[]} Identity matrix. 1024 */ 1025 function identityMatrix3() { 1026 return [ 1027 1, 0, 0, 1028 0, 1, 0, 1029 0, 0, 1 1030 ]; 1031 } 1032 1033 /** 1034 * Rotates a 3x3 matrix. 1035 * @private 1036 * @param {number[]} m - Matrix to rotate. 1037 * @param {number[]} angle - Angle to rotate by in radians. 1038 * @param {string} axis - Axis to rotate about (`x`, `y`, or `z`). 1039 * @returns {number[]} Rotated matrix. 1040 */ 1041 function rotateMatrix(m, angle, axis) { 1042 var s = Math.sin(angle); 1043 var c = Math.cos(angle); 1044 if (axis == 'x') { 1045 return [ 1046 m[0], c*m[1] + s*m[2], c*m[2] - s*m[1], 1047 m[3], c*m[4] + s*m[5], c*m[5] - s*m[4], 1048 m[6], c*m[7] + s*m[8], c*m[8] - s*m[7] 1049 ]; 1050 } 1051 if (axis == 'y') { 1052 return [ 1053 c*m[0] - s*m[2], m[1], c*m[2] + s*m[0], 1054 c*m[3] - s*m[5], m[4], c*m[5] + s*m[3], 1055 c*m[6] - s*m[8], m[7], c*m[8] + s*m[6] 1056 ]; 1057 } 1058 if (axis == 'z') { 1059 return [ 1060 c*m[0] + s*m[1], c*m[1] - s*m[0], m[2], 1061 c*m[3] + s*m[4], c*m[4] - s*m[3], m[5], 1062 c*m[6] + s*m[7], c*m[7] - s*m[6], m[8] 1063 ]; 1064 } 1065 } 1066 1067 /** 1068 * Turns a 3x3 matrix into a 4x4 matrix. 1069 * @private 1070 * @param {number[]} m - Input matrix. 1071 * @returns {number[]} Expanded matrix. 1072 */ 1073 function makeMatrix4(m) { 1074 return [ 1075 m[0], m[1], m[2], 0, 1076 m[3], m[4], m[5], 0, 1077 m[6], m[7], m[8], 0, 1078 0, 0, 0, 1 1079 ]; 1080 } 1081 1082 /** 1083 * Transposes a 4x4 matrix. 1084 * @private 1085 * @param {number[]} m - Input matrix. 1086 * @returns {number[]} Transposed matrix. 1087 */ 1088 function transposeMatrix4(m) { 1089 return [ 1090 m[ 0], m[ 4], m[ 8], m[12], 1091 m[ 1], m[ 5], m[ 9], m[13], 1092 m[ 2], m[ 6], m[10], m[14], 1093 m[ 3], m[ 7], m[11], m[15] 1094 ]; 1095 } 1096 1097 /** 1098 * Creates a perspective matrix. 1099 * @private 1100 * @param {number} hfov - Desired horizontal field of view. 1101 * @param {number} aspect - Desired aspect ratio. 1102 * @param {number} znear - Near distance. 1103 * @param {number} zfar - Far distance. 1104 * @returns {number[]} Generated perspective matrix. 1105 */ 1106 function makePersp(hfov, aspect, znear, zfar) { 1107 var fovy = 2 * Math.atan(Math.tan(hfov/2) * gl.drawingBufferHeight / gl.drawingBufferWidth); 1108 var f = 1 / Math.tan(fovy/2); 1109 return [ 1110 f/aspect, 0, 0, 0, 1111 0, f, 0, 0, 1112 0, 0, (zfar+znear)/(znear-zfar), (2*zfar*znear)/(znear-zfar), 1113 0, 0, -1, 0 1114 ]; 1115 } 1116 1117 /** 1118 * Processes a loaded texture image into a WebGL texture. 1119 * @private 1120 * @param {Image} img - Input image. 1121 * @param {WebGLTexture} tex - Texture to bind image to. 1122 */ 1123 function processLoadedTexture(img, tex) { 1124 gl.bindTexture(gl.TEXTURE_2D, tex); 1125 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img); 1126 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 1127 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 1128 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 1129 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 1130 gl.bindTexture(gl.TEXTURE_2D, null); 1131 } 1132 1133 var pendingTextureRequests = []; 1134 1135 // Based on http://blog.tojicode.com/2012/03/javascript-memory-optimization-and.html 1136 var loadTexture = (function() { 1137 var cacheTop = 4; // Maximum number of concurrents loads 1138 var textureImageCache = {}; 1139 var crossOrigin; 1140 1141 function TextureImageLoader() { 1142 var self = this; 1143 this.texture = this.callback = null; 1144 this.image = new Image(); 1145 this.image.crossOrigin = crossOrigin ? crossOrigin : 'anonymous'; 1146 var loadFn = (function() { 1147 if (self.image.width > 0 && self.image.height >
1147 0) { // ignore missing tile to supporting partial image 1148 processLoadedTexture(self.image, self.texture); 1149 self.callback(self.texture, true); 1150 } else { 1151 self.callback(self.texture, false); 1152 } 1153 releaseTextureImageLoader(self); 1154 }); 1155 this.image.addEventListener('load', loadFn); 1156 this.image.addEventListener('error', loadFn); // ignore missing tile file to support partial image, otherwise retry loop causes high CPU load 1157 } 1158 1159 TextureImageLoader.prototype.loadTexture = function(src, texture, callback) { 1160 this.texture = texture; 1161 this.callback = callback; 1162 this.image.src = src; 1163 }; 1164 1165 function PendingTextureRequest(node, src, texture, callback) { 1166 this.node = node; 1167 this.src = src; 1168 this.texture = texture; 1169 this.callback = callback; 1170 } 1171 1172 function releaseTextureImageLoader(til) { 1173 if (pendingTextureRequests.length) { 1174 var req = pendingTextureRequests.shift(); 1175 til.loadTexture(req.src, req.texture, req.callback); 1176 } else 1177 textureImageCache[cacheTop++] = til; 1178 } 1179 1180 for (var i = 0; i < cacheTop; i++) 1181 textureImageCache[i] = new TextureImageLoader(); 1182 1183 return function(node, src, callback, _crossOrigin) { 1184 crossOrigin = _crossOrigin; 1185 var texture = gl.createTexture(); 1186 if (cacheTop) 1187 textureImageCache[--cacheTop].loadTexture(src, texture, callback); 1188 else 1189 pendingTextureRequests.push(new PendingTextureRequest(node, src, texture, callback)); 1190 return texture; 1191 }; 1192 })(); 1193 1194 /** 1195 * Loads image and creates texture for a multires node / tile. 1196 * @private 1197 * @param {MultiresNode} node - Input node. 1198 */ 1199 function processNextTile(node) { 1200 loadTexture(node, node.path + '.' + image.extension, function(texture, loaded) { 1201 node.texture = texture; 1202 node.textureLoaded = loaded ? 2 : 1; 1203 }, globalParams.crossOrigin); 1204 } 1205 1206 /** 1207 * Finds and applies optimal multires zoom level. 1208 * @private 1209 * @param {number} hfov - Horizontal field of view to check at. 1210 */ 1211 function checkZoom(hfov) { 1212 // Find optimal level 1213 var newLevel = 1; 1214 while ( newLevel < image.maxLevel && 1215 gl.drawingBufferWidth > image.tileResolution * 1216 Math.pow(2, newLevel - 1) * Math.tan(hfov / 2) * 0.707 ) { 1217 newLevel++; 1218 } 1219 1220 // Apply change 1221 program.level = newLevel; 1222 } 1223 1224 /** 1225 * Rotates perspective matrix. 1226 * @private 1227 * @param {number[]} p - Perspective matrix. 1228 * @param {number[]} r - Rotation matrix. 1229 * @returns {number[]} Rotated matrix. 1230 */ 1231 function rotatePersp(p, r) { 1232 return [ 1233 p[ 0]*r[0], p[ 0]*r[1], p[ 0]*r[ 2], 0, 1234 p[ 5]*r[4], p[ 5]*r[5], p[ 5]*r[ 6], 0, 1235 p[10]*r[8], p[10]*r[9], p[10]*r[10], p[11], 1236 -r[8], -r[9], -r[10], 0 1237 ]; 1238 } 1239 1240 /** 1241 * Applies rotated perspective matrix to a 3-vector 1242 * (last element is inverted). 1243 * @private 1244 * @param {number[]} m - Rotated perspective matrix. 1245 * @param {number[]} v - Input 3-vector. 1246 * @returns {number[]} Resulting 4-vector. 1247 */ 1248 function applyRotPerspToVec(m, v) { 1249 return [ 1250 m[ 0]*v[0] + m[ 1]*v[1] + m[ 2]*v[2], 1251 m[ 4]*v[0] + m[ 5]*v[1] + m[ 6]*v[2], 1252 m[11] + m[ 8]*v[0] + m[ 9]*v[1] + m[10]*v[2], 1253 1/(m[12]*v[0] + m[13]*v[1] + m[14]*v[2]) 1254 ]; 1255 } 1256 1257 /** 1258 * Checks if a vertex is visible. 1259 * @private 1260 * @param {number[]} m - Rotated perspective matrix. 1261 * @param {number[]} v - Input vertex. 1262 * @returns {number} 1 or -1 if the vertex is or is not visible, 1263 * respectively. 1264 */ 1265 function checkInView(m, v) { 1266 var vpp = applyRotPerspToVec(m, v); 1267 var winX = vpp[0]*vpp[3]; 1268 var winY = vpp[1]*vpp[3]; 1269 var winZ = vpp[2]*vpp[3]; 1270 var ret = [0, 0, 0]; 1271 1272 if ( winX < -1 ) 1273 ret[0] = -1; 1274 if ( winX > 1 ) 1275 ret[0] = 1; 1276 if ( winY < -1 ) 1277 ret[1] = -1; 1278 if ( winY > 1 ) 1279 ret[1] = 1; 1280 if ( winZ < -1 || winZ > 1 ) 1281 ret[2] = 1; 1282 return ret; 1283 } 1284 1285 /** 1286 * Checks if a square (tile) is visible. 1287 * @private 1288 * @param {number[]} m - Rotated perspective matrix. 1289 * @param {number[]} v - Square's vertex array.
1290 * @returns {boolean} Whether or not the square is visible. 1291 */ 1292 function checkSquareInView(m, v) { 1293 var check1 = checkInView(m, v.slice(0, 3)); 1294 var check2 = checkInView(m, v.slice(3, 6)); 1295 var check3 = checkInView(m, v.slice(6, 9)); 1296 var check4 = checkInView(m, v.slice(9, 12)); 1297 var testX = check1[0] + check2[0] + check3[0] + check4[0]; 1298 if ( testX == -4 || testX == 4 ) 1299 return false; 1300 var testY = check1[1] + check2[1] + check3[1] + check4[1]; 1301 if ( testY == -4 || testY == 4 ) 1302 return false; 1303 var testZ = check1[2] + check2[2] + check3[2] + check4[2]; 1304 return testZ != 4; 1305 1306 1307 } 1308 1309 /** 1310 * On iOS (iPhone 5c, iOS 10.3), this WebGL error occurs when the canvas is 1311 * too big. Unfortuately, there's no way to test for this beforehand, so we 1312 * reduce the canvas size if this error is thrown. 1313 * @private 1314 */ 1315 function handleWebGLError1286() { 1316 console.log('Reducing canvas size due to error 1286!'); 1317 canvas.width = Math.round(canvas.width / 2); 1318 canvas.height = Math.round(canvas.height / 2); 1319 } 1320 } 1321 1322 // Vertex shader for equirectangular and cube 1323 var v = [ 1324 'attribute vec2 a_texCoord;', 1325 'varying vec2 v_texCoord;', 1326 1327 'void main() {', 1328 // Set position 1329 'gl_Position = vec4(a_texCoord, 0.0, 1.0);', 1330 1331 // Pass the coordinates to the fragment shader 1332 'v_texCoord = a_texCoord;', 1333 '}' 1334 ].join(''); 1335 1336 // Vertex shader for multires 1337 var vMulti = [ 1338 'attribute vec3 a_vertCoord;', 1339 'attribute vec2 a_texCoord;', 1340 1341 'uniform mat4 u_cubeMatrix;', 1342 'uniform mat4 u_perspMatrix;', 1343 1344 'varying mediump vec2 v_texCoord;', 1345 1346 'void main(void) {', 1347 // Set position 1348 'gl_Position = u_perspMatrix * u_cubeMatrix * vec4(a_vertCoord, 1.0);', 1349 1350 // Pass the coordinates to the fragment shader 1351 'v_texCoord = a_texCoord;', 1352 '}' 1353 ].join(''); 1354 1355 // Fragment shader 1356 var fragEquiCubeBase = [ 1357 'precision highp float;', // mediump looks bad on some mobile devices 1358 1359 'uniform float u_aspectRatio;', 1360 'uniform float u_psi;', 1361 'uniform float u_theta;', 1362 'uniform float u_f;', 1363 'uniform float u_h;', 1364 'uniform float u_v;', 1365 'uniform float u_vo;', 1366 'uniform float u_rot;', 1367 1368 'const float PI = 3.14159265358979323846264;', 1369 1370 // Texture 1371 'uniform sampler2D u_image0;', 1372 'uniform sampler2D u_image1;', 1373 'uniform bool u_splitImage;', 1374 'uniform samplerCube u_imageCube;', 1375 1376 // Coordinates passed in from vertex shader 1377 'varying vec2 v_texCoord;', 1378 1379 // Background color (display for partial panoramas) 1380 'uniform vec4 u_backgroundColor;', 1381 1382 'void main() {', 1383 // Map canvas/camera to sphere 1384 'float x = v_texCoord.x * u_aspectRatio;', 1385 'float y = v_texCoord.y;', 1386 'float sinrot = sin(u_rot);', 1387 'float cosrot = cos(u_rot);', 1388 'float rot_x = x * cosrot - y * sinrot;', 1389 'float rot_y = x * sinrot + y * cosrot;', 1390 'float sintheta = sin(u_theta);', 1391 'float costheta = cos(u_theta);', 1392 'float a = u_f * costheta - rot_y * sintheta;', 1393 'float root = sqrt(rot_x * rot_x + a * a);', 1394 'float lambda = atan(rot_x / root, a / root) + u_psi;', 1395 'float phi = atan((rot_y * costheta + u_f * sintheta) / root);', 1396 ].join('\n'); 1397 1398 // Fragment shader 1399 var fragCube = fragEquiCubeBase + [ 1400 // Look up color from texture 1401 'float cosphi = cos(phi);', 1402 'gl_FragColor = textureCube(u_imageCube, vec3(cosphi*sin(lambda), sin(phi), cosphi*cos(lambda)));', 1403 '}' 1404 ].join('\n'); 1405 1406 // Fragment shader 1407 var fragEquirectangular = fragEquiCubeBase + [ 1408 // Wrap image 1409 'lambda = mod(lambda + PI, PI * 2.0) - PI;', 1410 1411 // Map texture to sphere 1412 'vec2 coord = vec2(lambda / PI, phi / (PI / 2.0));', 1413 1414 // Look up color from texture 1415 // Map from [-1,1] to [0,1] and flip y-axis 1416 'if(coord.x < -u_h || coord.x > u_h || coord.y < -u_v + u_vo || coord.y > u_v + u_vo)', 1417 'gl_FragColor = u_backgroundColor;', 1418 'else {', 1419 'if(u_splitImage) {', 1420 // Image was split into two textures to work around texture size limits 1421 'if(coord.x < 0.0)', 1422 'gl_FragColor = texture2D(u_image0, vec2((coord.x + u_h) / u_h, (-coord.y + u_v + u_vo) / (u_v * 2.0)));', 1423 'else', 1424 'gl_FragColor = texture2D(u_image1, vec2((coord.x + u_h) / u_h - 1.0, (-coord.y + u_v + u_vo) / (u_v * 2.0)));', 1425 '} else {', 1426 'gl_FragColor = texture2D(u_image0, vec2((coord.x + u_h) / (u_h * 2.0), (-coord.y + u_v + u_vo) / (u_v * 2.0)));', 1427 '}', 1428 '}', 1429 '}' 1430 ].join('\n'); 1431 1432 // Fragment shader 1433 var fragMulti = [ 1434 'varying mediump vec2 v_texCoord;', 1435 'uniform sampler2D u_sampler;', 1436 //'uniform mediump vec4 u_color;', 1437 1438 'void main(void) {', 1439 // Look up color from texture 1440 'gl_FragColor = texture2D(u_sampler, v_texCoord);', 1441 // 'gl_FragColor = u_color;', 1442 '}' 1443 ].join(''); 1444 1445 return { 1446 renderer: function(container, image, imagetype, dynamic) { 1447 return new Renderer(container, image, imagetype, dynamic); 1448 } 1449 }; 1450 1451})(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.