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 */ 33function 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;
vendor: 4,172 bytes, lines 196-267
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;
267 // 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 444 // Upload first half of image to the texture 445 var cropImage = cropContext.getImageData(0, 0, image.width / 2, image.height); 446 gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, cropImage); 447 448 // Create and bind texture for second half of image 449 program.texture2 = gl.createTexture(); 450 gl.activeTexture(gl.TEXTURE1); 451 gl.bindTexture(glBindType, program.texture2); 452 gl.uniform1i(gl.getUniformLocation(program, 'u_image1'), 1); 453 454 // Upload second half of image to the texture
455 cropContext.drawImage(image, -image.width / 2, 0); 456 cropImage = cropContext.getImageData(0, 0, image.width / 2, image.height); 457 gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, cropImage); 458 459 // Set parameters for rendering any size 460 gl.texParameteri(glBindType, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 461 gl.texParameteri(glBindType, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 462 gl.texParameteri(glBindType, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 463 gl.texParameteri(glBindType, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 464 465 // Reactivate first texture unit 466 gl.activeTexture(gl.TEXTURE0); 467 } 468 } 469 470 // Set parameters for rendering any size 471 gl.texParameteri(glBindType, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 472 gl.texParameteri(glBindType, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 473 gl.texParameteri(glBindType, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 474 gl.texParameteri(glBindType, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 475 476 } else { 477 // Look up vertex coordinates location 478 program.vertPosLocation = gl.getAttribLocation(program, 'a_vertCoord'); 479 gl.enableVertexAttribArray(program.vertPosLocation); 480 481 // Create buffers 482 if (!cubeVertBuf) 483 cubeVertBuf = gl.createBuffer(); 484 if (!cubeVertTexCoordBuf) 485 cubeVertTexCoordBuf = gl.createBuffer(); 486 if (!cubeVertIndBuf) 487 cubeVertIndBuf = gl.createBuffer(); 488 489 // Bind texture coordinate buffer and pass coordinates to WebGL 490 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertTexCoordBuf); 491 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0,1,0,1,1,0,1]), gl.STATIC_DRAW); 492 493 // Bind square index buffer and pass indicies to WebGL 494 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertIndBuf); 495 gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0,1,2,0,2,3]), gl.STATIC_DRAW); 496 497 // Find uniforms 498 program.perspUniform = gl.getUniformLocation(program, 'u_perspMatrix'); 499 program.cubeUniform = gl.getUniformLocation(program, 'u_cubeMatrix'); 500 //program.colorUniform = gl.getUniformLocation(program, 'u_color'); 501 502 program.level = -1; 503 504 program.currentNodes = []; 505 program.nodeCache = []; 506 program.nodeCacheTimestamp = 0; 507 } 508 509 // Check if there was an error 510 var err = gl.getError(); 511 if (err !== 0) { 512 console.log('Error: Something went wrong with WebGL!', err); 513 throw {type: 'webgl error'}; 514 } 515 516 callback(); 517 }; 518 519 /** 520 * Destroy renderer. 521 * @memberof Renderer 522 * @instance 523 */ 524 this.destroy = function() { 525 if (container !== undefined) { 526 if (canvas !== undefined && container.contains(canvas)) { 527 container.removeChild(canvas); 528 } 529 if (world !== undefined && container.contains(world)) { 530 container.removeChild(world); 531 } 532 } 533 if (gl) { 534 // The spec says this is only supposed to simulate losing the WebGL 535 // context, but in practice it tends to actually free the memory. 536 var extension = gl.getExtension('WEBGL_lose_context'); 537 if (extension) 538 extension.loseContext(); 539 } 540 }; 541 542 /** 543 * Resize renderer (call after resizing container). 544 * @memberof Renderer 545 * @instance 546 */ 547 this.resize = function() { 548 var pixelRatio = window.devicePixelRatio || 1; 549 canvas.width = canvas.clientWidth * pixelRatio; 550 canvas.height = canvas.clientHeight * pixelRatio; 551 552 if (gl) { 553 if (gl.getError() == 1286) 554 handleWebGLError1286(); 555 gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 556 if (imageType != 'multires') { 557 gl.uniform1f(program.aspectRatio, canvas.clientWidth / canvas.clientHeight); 558 } 559 } 560 }; 561 // Initialize canvas size 562 this.resize(); 563 564 /** 565 * Set renderer horizon pitch and roll. 566 * @memberof Renderer 567 * @instance 568 */ 569 this.setPose = function(horizonPitch, horizonRoll) { 570 pose = [horizonPitch, horizonRoll]; 571 }; 572 573 /**
vendor: 22,238 bytes, lines 574-1093
574 * Render new view of panorama. 575 * @memberof Renderer 576 * @instance 577 * @param {number} pitch - Pitch to render at (in radians). 578 * @param {number} yaw - Yaw to render at (in radians). 579 * @param {number} hfov - Horizontal field of view to render with (in radians). 580 * @param {Object} [params] - Extra configuration parameters. 581 * @param {number} [params.roll] - Camera roll (in radians). 582 * @param {boolean} [params.returnImage] - Return rendered image? 583 */ 584 this.render = function(pitch, yaw, hfov, params) { 585 var focal, i, s, roll = 0; 586 if (params === undefined) 587 params = {}; 588 if (params.roll) 589 roll = params.roll; 590 591 // Apply pitch and roll transformation if applicable 592 if (pose !== undefined) { 593 var horizonPitch = pose[0], 594 horizonRoll = pose[1]; 595 596 // Calculate new pitch and yaw 597 var orig_pitch = pitch, 598 orig_yaw = yaw, 599 x = Math.cos(horizonRoll) * Math.sin(pitch) * Math.sin(horizonPitch) + 600 Math.cos(pitch) * (Math.cos(horizonPitch) * Math.cos(yaw) + 601 Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.sin(yaw)), 602 y = -Math.sin(pitch) * Math.sin(horizonRoll) + 603 Math.cos(pitch) * Math.cos(horizonRoll) * Math.sin(yaw), 604 z = Math.cos(horizonRoll) * Math.cos(horizonPitch) * Math.sin(pitch) + 605 Math.cos(pitch) * (-Math.cos(yaw) * Math.sin(horizonPitch) + 606 Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.sin(yaw)); 607 pitch = Math.asin(Math.max(Math.min(z, 1), -1)); 608 yaw = Math.atan2(y, x); 609 610 // Calculate roll 611 var v = [Math.cos(orig_pitch) * (Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.cos(orig_yaw) - 612 Math.cos(horizonPitch) * Math.sin(orig_yaw)), 613 Math.cos(orig_pitch) * Math.cos(horizonRoll) * Math.cos(orig_yaw), 614 Math.cos(orig_pitch) * (Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.cos(orig_yaw) + 615 Math.sin(orig_yaw) * Math.sin(horizonPitch))], 616 w = [-Math.cos(pitch) * Math.sin(yaw), Math.cos(pitch) * Math.cos(yaw)]; 617 var roll_adj = Math.acos(Math.max(Math.min((v[0]*w[0] + v[1]*w[1]) / 618 (Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) * 619 Math.sqrt(w[0]*w[0]+w[1]*w[1])), 1), -1)); 620 if (v[2] < 0) 621 roll_adj = 2 * Math.PI - roll_adj; 622 roll += roll_adj; 623 } 624 625 // If no WebGL 626 if (!gl && (imageType == 'multires' || imageType == 'cubemap')) { 627 // Determine face transforms 628 s = fallbackImgSize / 2; 629 630 var transforms = { 631 f: 'translate3d(-' + (s + 2) + 'px, -' + (s + 2) + 'px, -' + s + 'px)', 632 b: 'translate3d(' + (s + 2) + 'px, -' + (s + 2) + 'px, ' + s + 'px) rotateX(180deg) rotateZ(180deg)', 633 u: 'translate3d(-' + (s + 2) + 'px, -' + s + 'px, ' + (s + 2) + 'px) rotateX(270deg)', 634 d: 'translate3d(-' + (s + 2) + 'px, ' + s + 'px, -' + (s + 2) + 'px) rotateX(90deg)', 635 l: 'translate3d(-' + s + 'px, -' + (s + 2) + 'px, ' + (s + 2) + 'px) rotateX(180deg) rotateY(90deg) rotateZ(180deg)', 636 r: 'translate3d(' + s + 'px, -' + (s + 2) + 'px, -' + (s + 2) + 'px) rotateY(270deg)' 637 }; 638 focal = 1 / Math.tan(hfov / 2); 639 var zoom = focal * canvas.clientWidth / 2 + 'px'; 640 var transform = 'perspective(' + zoom + ') translateZ(' + zoom + ') rotateX(' + pitch + 'rad) rotateY(' + yaw + 'rad) '; 641 642 // Apply face transforms 643 var faces = Object.keys(transforms); 644 for (i = 0; i < 6; i++) { 645 var face = world.querySelector('.pnlm-' + faces[i] + 'face'); 646 if (!face) 647 continue; // ignore missing face to support partial cubemap/fallback image 648 face.style.webkitTransform = transform + transforms[faces[i]]; 649 face.style.transform = transform + transforms[faces[i]]; 650 } 651 return; 652 } 653 654 if (imageType != 'multires') { 655 // Calculate focal length from vertical field of view 656 var vfov = 2 * Math.atan(Math.tan(hfov * 0.5) / (gl.drawingBufferWidth / gl.drawingBufferHeight)); 657 focal = 1 / Math.tan(vfov * 0.5); 658 659 // Pass psi, theta, roll, and focal length 660 gl.uniform1f(program.psi, yaw); 661 gl.uniform1f(program.theta, pitch); 662 gl.uniform1f(program.rot, roll); 663 gl.uniform1f(program.f, focal); 664 665 if (dynamic === true) { 666 // Update texture if dynamic 667 if (imageType == 'equirectangular') { 668 gl.bindTexture(gl.TEXTURE_2D, program.texture); 669 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); 670 } 671 } 672 673 // Draw using current buffer 674 gl.drawArrays(gl.TRIANGLES, 0, 6); 675 676 } else { 677 // Create perspective matrix 678 var perspMatrix = makePersp(hfov, gl.drawingBufferWidth / gl.drawingBufferHeight, 0.1, 100.0); 679 680 // Find correct zoom level 681 checkZoom(hfov); 682 683 // Create rotation matrix 684 var matrix = identityMatrix3(); 685 matrix = rotateMatrix(matrix, -roll, 'z'); 686 matrix = rotateMatrix(matrix, -pitch, 'x'); 687 matrix = rotateMatrix(matrix, yaw, 'y'); 688 matrix = makeMatrix4(matrix); 689 690 // Set matrix uniforms 691 gl.uniformMatrix4fv(program.perspUniform, false, new Float32Array(transposeMatrix4(perspMatrix))); 692 gl.uniformMatrix4fv(program.cubeUniform, false, new Float32Array(transposeMatrix4(matrix))); 693 694 // Find current nodes 695 var rotPersp = rotatePersp(perspMatrix, matrix); 696 program.nodeCache.sort(multiresNodeSort); 697 if (program.nodeCache.length > 200 && 698 program.nodeCache.length > program.currentNodes.length + 50) { 699 // Remove older nodes from cache 700 var removed = program.nodeCache.splice(200, program.nodeCache.length - 200); 701 for (var j = 0; j < removed.length; j++) { 702 // Explicitly delete textures 703 gl.deleteTexture(removed[j].texture); 704 } 705 } 706 program.currentNodes = []; 707 708 var sides = ['f', 'b', 'u', 'd', 'l', 'r']; 709 for (s = 0; s < 6; s++) { 710 var ntmp = new MultiresNode(vtmps[s], sides[s], 1, 0, 0, image.fullpath); 711 testMultiresNode(rotPersp, ntmp, pitch, yaw, hfov); 712 } 713 714 program.currentNodes.sort(multiresNodeRenderSort); 715 716 // Unqueue any pending requests for nodes that are no longer visible 717 for (i = pendingTextureRequests.length - 1; i >= 0; i--) { 718 if (program.currentNodes.indexOf(pendingTextureRequests[i].node) === -1) { 719 pendingTextureRequests[i].node.textureLoad = false; 720 pendingTextureRequests.splice(i, 1); 721 } 722 } 723 724 // Allow one request to be pending, so that we can create a texture buffer for that in advance of loading actually beginning 725 if (pendingTextureRequests.length === 0) { 726 for (i = 0; i < program.currentNodes.length; i++) { 727 var node = program.currentNodes[i]; 728 if (!node.texture && !node.textureLoad) { 729 node.textureLoad = true; 730 731 setTimeout(processNextTile, 0, node); 732 733 // Only process one tile per frame to improve responsiveness 734 break; 735 } 736 } 737 } 738 739 // Draw tiles 740 multiresDraw(); 741 } 742 743 if (params.returnImage !== undefined) { 744 return canvas.toDataURL('image/png'); 745 } 746 }; 747 748 /** 749 * Check if images are loading. 750 * @memberof Renderer 751 * @instance 752 * @returns {boolean} Whether or not images are loading. 753 */ 754 this.isLoading = function() { 755 if (gl && imageType == 'multires') { 756 for ( var i = 0; i < program.currentNodes.length; i++ ) { 757 if (!program.currentNodes[i].textureLoaded) { 758 return true; 759 } 760 } 761 } 762 return false; 763 }; 764 765 /** 766 * Retrieve renderer's canvas. 767 * @memberof Renderer 768 * @instance 769 * @returns {HTMLElement} Renderer's canvas. 770 */ 771 this.getCanvas = function() { 772 return canvas; 773 }; 774 775 /** 776 * Sorting method for multires nodes. 777 * @private 778 * @param {MultiresNode} a - First node. 779 * @param {MultiresNode} b - Second node. 780 * @returns {number} Base tiles first, then higher timestamp first. 781 */ 782 function multiresNodeSort(a, b) { 783 // Base tiles are always first 784 if (a.level == 1 && b.level != 1) { 785 return -1; 786 } 787 if (b. level == 1 && a.level != 1) { 788 return 1; 789 } 790 791 // Higher timestamp first 792 return b.timestamp - a.timestamp; 793 } 794 795 /** 796 * Sorting method for multires node rendering. 797 * @private 798 * @param {MultiresNode} a - First node. 799 * @param {MultiresNode} b - Second node. 800 * @returns {number} Lower zoom levels first, then closest to center first. 801 */ 802 function multiresNodeRenderSort(a, b) { 803 // Lower zoom levels first 804 if (a.level != b.level) { 805 return a.level - b.level; 806 } 807 808 // Lower distance from center first 809 return a.diff - b.diff; 810 } 811 812 /** 813 * Draws multires nodes. 814 * @private 815 */ 816 function multiresDraw() { 817 if (!program.drawInProgress) { 818 program.drawInProgress = true; 819 gl.clear(gl.COLOR_BUFFER_BIT); 820 for ( var i = 0; i < program.currentNodes.length; i++ ) { 821 if (program.currentNodes[i].textureLoaded > 1) { 822 //var color = program.currentNodes[i].color; 823 //gl.uniform4f(program.colorUniform, color[0], color[1], color[2], 1.0); 824 825 // Bind vertex buffer and pass vertices to WebGL 826 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertBuf); 827 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(program.currentNodes[i].vertices), gl.STATIC_DRAW); 828 gl.vertexAttribPointer(program.vertPosLocation, 3, gl.FLOAT, false, 0, 0); 829 830 // Prep for texture 831 gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertTexCoordBuf); 832 gl.vertexAttribPointer(program.texCoordLocation, 2, gl.FLOAT, false, 0, 0); 833 834 // Bind texture and draw tile 835 gl.bindTexture(gl.TEXTURE_2D, program.currentNodes[i].texture); // Bind program.currentNodes[i].texture to TEXTURE0 836 gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); 837 } 838 } 839 program.drawInProgress = false; 840 } 841 } 842 843 /** 844 * Creates new multires node. 845 * @constructor 846 * @private 847 * @param {number[]} vertices - Node's verticies. 848 * @param {string} side - Node's cube face. 849 * @param {number} level - Node's zoom level. 850 * @param {number} x - Node's x position. 851 * @param {number} y - Node's y position. 852 * @param {string} path - Node's path. 853 */ 854 function MultiresNode(vertices, side, level, x, y, path) { 855 this.vertices = vertices; 856 this.side = side; 857 this.level = level; 858 this.x = x; 859 this.y = y; 860 this.path = path.replace('%s',side).replace('%l',level).replace('%x',x).replace('%y',y); 861 } 862 863 /** 864 * Test if multires node is visible. If it is, add it to current nodes, 865 * load its texture, and load appropriate child nodes. 866 * @private 867 * @param {number[]} rotPersp - Rotated perspective matrix. 868 * @param {MultiresNode} node - Multires node to check. 869 * @param {number} pitch - Pitch to check at. 870 * @param {number} yaw - Yaw to check at. 871 * @param {number} hfov - Horizontal field of view to check at. 872 */ 873 function testMultiresNode(rotPersp, node, pitch, yaw, hfov) { 874 if (checkSquareInView(rotPersp, node.vertices)) { 875 // Calculate central angle between center of view and center of tile 876 var v = node.vertices; 877 var x = v[0] + v[3] + v[6] + v[ 9]; 878 var y = v[1] + v[4] + v[7] + v[10]; 879 var z = v[2] + v[5] + v[8] + v[11]; 880 var r = Math.sqrt(x*x + y*y + z*z); 881 var theta = Math.asin(z / r); 882 var phi = Math.atan2(y, x); 883 var ydiff = phi - yaw; 884 ydiff += (ydiff > Math.PI) ? -2 * Math.PI : (ydiff < -Math.PI) ? 2 * Math.PI : 0; 885 ydiff = Math.abs(ydiff); 886 node.diff = Math.acos(Math.sin(pitch) * Math.sin(theta) + Math.cos(pitch) * Math.cos(theta) * Math.cos(ydiff)); 887 888 // Add node to current nodes and load texture if needed 889 var inCurrent = false; 890 for (var k = 0; k < program.nodeCache.length; k++) { 891 if (program.nodeCache[k].path == node.path) { 892 inCurrent = true; 893 program.nodeCache[k].timestamp = program.nodeCacheTimestamp++; 894 program.nodeCache[k].diff = node.diff; 895 program.currentNodes.push(program.nodeCache[k]); 896 break; 897 } 898 } 899 if (!inCurrent) { 900 //node.color = [Math.random(), Math.random(), Math.random()]; 901 node.timestamp = program.nodeCacheTimestamp++; 902 program.currentNodes.push(node); 903 program.nodeCache.push(node); 904 } 905 906 // TODO: Test error 907 // Create child nodes 908 if (node.level < program.level) { 909 var cubeSize = image.cubeResolution * Math.pow(2, node.level - image.maxLevel); 910 var numTiles = Math.ceil(cubeSize * image.invTileResolution) - 1; 911 var doubleTileSize = cubeSize % image.tileResolution * 2; 912 var lastTileSize = (cubeSize * 2) % image.tileResolution; 913 if (lastTileSize === 0) { 914 lastTileSize = image.tileResolution; 915 } 916 if (doubleTileSize === 0) { 917 doubleTileSize = image.tileResolution * 2; 918 } 919 var f = 0.5; 920 if (node.x == numTiles || node.y == numTiles) { 921 f = 1.0 - image.tileResolution / (image.tileResolution + lastTileSize); 922 } 923 var i = 1.0 - f; 924 var children = []; 925 var vtmp, ntmp; 926 var f1 = f, f2 = f, f3 = f, i1 = i, i2 = i, i3 = i; 927 // Handle non-symmetric tiles 928 if (lastTileSize < image.tileResolution) { 929 if (node.x == numTiles && node.y != numTiles) { 930 f2 = 0.5; 931 i2 = 0.5; 932 if (node.side == 'd' || node.side == 'u') { 933 f3 = 0.5; 934 i3 = 0.5; 935 } 936 } else if (node.x != numTiles && node.y == numTiles) { 937 f1 = 0.5; 938 i1 = 0.5; 939 if (node.side == 'l' || node.side == 'r') { 940 f3 = 0.5; 941 i3 = 0.5; 942 } 943 } 944 } 945 // Handle small tiles that have fewer than four children 946 if (doubleTileSize <= image.tileResolution) { 947 if (node.x == numTiles) { 948 f1 = 0; 949 i1 = 1; 950 if (node.side == 'l' || node.side == 'r') { 951 f3 = 0; 952 i3 = 1; 953 } 954 } 955 if (node.y == numTiles) { 956 f2 = 0; 957 i2 = 1; 958 if (node.side == 'd' || node.side == 'u') { 959 f3 = 0; 960 i3 = 1; 961 } 962 } 963 } 964 965 vtmp = [ v[0], v[1], v[2], 966 v[0]*f1+v[3]*i1, v[1]*f+v[4]*i, v[2]*f3+v[5]*i3, 967 v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, 968 v[0]*f+v[9]*i, v[1]*f2+v[10]*i2, v[2]*f3+v[11]*i3 969 ]; 970 ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2, node.y*2, image.fullpath); 971 children.push(ntmp); 972 if (!(node.x == numTiles && doubleTileSize <= image.tileResolution)) { 973 vtmp = [v[0]*f1+v[3]*i1, v[1]*f+v[4]*i, v[2]*f3+v[5]*i3, 974 v[3], v[4], v[5], 975 v[3]*f+v[6]*i, v[4]*f2+v[7]*i2, v[5]*f3+v[8]*i3, 976 v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3 977 ]; 978 ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2+1, node.y*2, image.fullpath); 979 children.push(ntmp); 980 } 981 if (!(node.x == numTiles && doubleTileSize <= image.tileResolution) && 982 !(node.y == numTiles && doubleTileSize <= image.tileResolution)) { 983 vtmp = [v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, 984 v[3]*f+v[6]*i, v[4]*f2+v[7]*i2, v[5]*f3+v[8]*i3, 985 v[6], v[7], v[8], 986 v[9]*f1+v[6]*i1, v[10]*f+v[7]*i, v[11]*f3+v[8]*i3 987 ]; 988 ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2+1, node.y*2+1, image.fullpath); 989 children.push(ntmp); 990 } 991 if (!(node.y == numTiles && doubleTileSize <= image.tileResolution)) { 992 vtmp = [ v[0]*f+v[9]*i, v[1]*f2+v[10]*i2, v[2]*f3+v[11]*i3, 993 v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, 994 v[9]*f1+v[6]*i1, v[10]*f+v[7]*i, v[11]*f3+v[8]*i3, 995 v[9], v[10], v[11] 996 ]; 997 ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2, node.y*2+1, image.fullpath); 998 children.push(ntmp); 999 } 1000 for (var j = 0; j < children.length; j++) { 1001 testMultiresNode(rotPersp, children[j], pitch, yaw, hfov); 1002 } 1003 } 1004 } 1005 } 1006 1007 /** 1008 * Creates cube vertex array. 1009 * @private 1010 * @returns {number[]} Cube vertex array. 1011 */ 1012 function createCube() { 1013 return [-1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, // Front face 1014 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // Back face 1015 -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, // Up face 1016 -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, // Down face 1017 -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // Left face 1018 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1 // Right face 1019 ]; 1020 } 1021 1022 /** 1023 * Creates 3x3 identity matrix. 1024 * @private 1025 * @returns {number[]} Identity matrix. 1026 */ 1027 function identityMatrix3() { 1028 return [ 1029 1, 0, 0, 1030 0, 1, 0, 1031 0, 0, 1 1032 ]; 1033 } 1034 1035 /** 1036 * Rotates a 3x3 matrix. 1037 * @private 1038 * @param {number[]} m - Matrix to rotate. 1039 * @param {number[]} angle - Angle to rotate by in radians. 1040 * @param {string} axis - Axis to rotate about (`x`, `y`, or `z`). 1041 * @returns {number[]} Rotated matrix. 1042 */ 1043 function rotateMatrix(m, angle, axis) { 1044 var s = Math.sin(angle); 1045 var c = Math.cos(angle); 1046 if (axis == 'x') { 1047 return [ 1048 m[0], c*m[1] + s*m[2], c*m[2] - s*m[1], 1049 m[3], c*m[4] + s*m[5], c*m[5] - s*m[4], 1050 m[6], c*m[7] + s*m[8], c*m[8] - s*m[7] 1051 ]; 1052 } 1053 if (axis == 'y') { 1054 return [ 1055 c*m[0] - s*m[2], m[1], c*m[2] + s*m[0], 1056 c*m[3] - s*m[5], m[4], c*m[5] + s*m[3], 1057 c*m[6] - s*m[8], m[7], c*m[8] + s*m[6] 1058 ]; 1059 } 1060 if (axis == 'z') { 1061 return [ 1062 c*m[0] + s*m[1], c*m[1] - s*m[0], m[2], 1063 c*m[3] + s*m[4], c*m[4] - s*m[3], m[5], 1064 c*m[6] + s*m[7], c*m[7] - s*m[6], m[8] 1065 ]; 1066 } 1067 } 1068 1069 /** 1070 * Turns a 3x3 matrix into a 4x4 matrix. 1071 * @private 1072 * @param {number[]} m - Input matrix. 1073 * @returns {number[]} Expanded matrix. 1074 */ 1075 function makeMatrix4(m) { 1076 return [ 1077 m[0], m[1], m[2], 0, 1078 m[3], m[4], m[5], 0, 1079 m[6], m[7], m[8], 0, 1080 0, 0, 0, 1 1081 ]; 1082 } 1083 1084 /** 1085 * Transposes a 4x4 matrix. 1086 * @private 1087 * @param {number[]} m - Input matrix. 1088 * @returns {number[]} Transposed matrix. 1089 */ 1090 function transposeMatrix4(m) { 1091 return [ 1092 m[ 0], m[ 4], m[ 8], m[12], 1093 m[ 1], m[ 5], m[ 9], m[13],
1094 m[ 2], m[ 6], m[10], m[14], 1095 m[ 3], m[ 7], m[11], m[15] 1096 ]; 1097 } 1098 1099 /** 1100 * Creates a perspective matrix. 1101 * @private 1102 * @param {number} hfov - Desired horizontal field of view. 1103 * @param {number} aspect - Desired aspect ratio. 1104 * @param {number} znear - Near distance. 1105 * @param {number} zfar - Far distance. 1106 * @returns {number[]} Generated perspective matrix. 1107 */ 1108 function makePersp(hfov, aspect, znear, zfar) { 1109 var fovy = 2 * Math.atan(Math.tan(hfov/2) * gl.drawingBufferHeight / gl.drawingBufferWidth); 1110 var f = 1 / Math.tan(fovy/2); 1111 return [ 1112 f/aspect, 0, 0, 0, 1113 0, f, 0, 0, 1114 0, 0, (zfar+znear)/(znear-zfar), (2*zfar*znear)/(znear-zfar), 1115 0, 0, -1, 0 1116 ]; 1117 } 1118 1119 /** 1120 * Processes a loaded texture image into a WebGL texture. 1121 * @private 1122 * @param {Image} img - Input image. 1123 * @param {WebGLTexture} tex - Texture to bind image to. 1124 */ 1125 function processLoadedTexture(img, tex) { 1126 gl.bindTexture(gl.TEXTURE_2D, tex); 1127 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img); 1128 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 1129 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 1130 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 1131 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 1132 gl.bindTexture(gl.TEXTURE_2D, null); 1133 } 1134 1135 var pendingTextureRequests = []; 1136 1137 // Based on http://blog.tojicode.com/2012/03/javascript-memory-optimization-and.html 1138 var loadTexture = (function() { 1139 var cacheTop = 4; // Maximum number of concurrents loads 1140 var textureImageCache = {}; 1141 var crossOrigin; 1142 1143 function TextureImageLoader() { 1144 var self = this; 1145 this.texture = this.callback = null; 1146 this.image = new Image(); 1147 this.image.crossOrigin = crossOrigin ? crossOrigin : 'anonymous'; 1148 var loadFn = (function() { 1149 if (self.image.width > 0 && self.image.height > 0) { // ignore missing tile to supporting partial image 1150 processLoadedTexture(self.image, self.texture); 1151 self.callback(self.texture, true); 1152 } else { 1153 self.callback(self.texture, false); 1154 } 1155 releaseTextureImageLoader(self); 1156 }); 1157 this.image.addEventListener('load', loadFn); 1158 this.image.addEventListener('error', loadFn); // ignore missing tile file to support partial image, otherwise retry loop causes high CPU load 1159 } 1160 1161 TextureImageLoader.prototype.loadTexture = function(src, texture, callback) { 1162 this.texture = texture; 1163 this.callback = callback; 1164 this.image.src = src; 1165 }; 1166 1167 function PendingTextureRequest(node, src, texture, callback) { 1168 this.node = node; 1169 this.src = src; 1170 this.texture = texture; 1171 this.callback = callback; 1172 } 1173 1174 function releaseTextureImageLoader(til) { 1175 if (pendingTextureRequests.length) { 1176 var req = pendingTextureRequests.shift(); 1177 til.loadTexture(req.src, req.texture, req.callback); 1178 } else 1179 textureImageCache[cacheTop++] = til; 1180 } 1181 1182 for (var i = 0; i < cacheTop; i++) 1183 textureImageCache[i] = new TextureImageLoader(); 1184 1185 return function(node, src, callback, _crossOrigin) { 1186 crossOrigin = _crossOrigin; 1187 var texture = gl.createTexture(); 1188 if (cacheTop) 1189 textureImageCache[--cacheTop].loadTexture(src, texture, callback); 1190 else 1191 pendingTextureRequests.push(new PendingTextureRequest(node, src, texture, callback)); 1192 return texture; 1193 }; 1194 })(); 1195 1196 /** 1197 * Loads image and creates texture for a multires node / tile. 1198 * @private 1199 * @param {MultiresNode} node - Input node. 1200 */ 1201 function processNextTile(node) { 1202 loadTexture(node, node.path + '.' + image.extension, function(texture, loaded) { 1203 node.texture = texture; 1204 node.textureLoaded = loaded ? 2 : 1; 1205 }, globalParams.crossOrigin); 1206 } 1207 1208 /** 1209 * Finds and applies optimal multires zoom level. 1210 * @private 1211 * @param {number} hfov - Horizontal field of view to check at. 1212 */ 1213 function checkZoom(hfov) { 1214 // Find optimal level 1215 var newLevel = 1; 1216 while ( newLevel < image.maxLevel && 1217 gl.drawingBufferWidth >
1217 image.tileResolution * 1218 Math.pow(2, newLevel - 1) * Math.tan(hfov / 2) * 0.707 ) { 1219 newLevel++; 1220 } 1221 1222 // Apply change 1223 program.level = newLevel; 1224 } 1225 1226 /** 1227 * Rotates perspective matrix. 1228 * @private 1229 * @param {number[]} p - Perspective matrix. 1230 * @param {number[]} r - Rotation matrix. 1231 * @returns {number[]} Rotated matrix. 1232 */ 1233 function rotatePersp(p, r) { 1234 return [ 1235 p[ 0]*r[0], p[ 0]*r[1], p[ 0]*r[ 2], 0, 1236 p[ 5]*r[4], p[ 5]*r[5], p[ 5]*r[ 6], 0, 1237 p[10]*r[8], p[10]*r[9], p[10]*r[10], p[11], 1238 -r[8], -r[9], -r[10], 0 1239 ]; 1240 } 1241 1242 /** 1243 * Applies rotated perspective matrix to a 3-vector 1244 * (last element is inverted). 1245 * @private 1246 * @param {number[]} m - Rotated perspective matrix. 1247 * @param {number[]} v - Input 3-vector. 1248 * @returns {number[]} Resulting 4-vector. 1249 */ 1250 function applyRotPerspToVec(m, v) { 1251 return [ 1252 m[ 0]*v[0] + m[ 1]*v[1] + m[ 2]*v[2], 1253 m[ 4]*v[0] + m[ 5]*v[1] + m[ 6]*v[2], 1254 m[11] + m[ 8]*v[0] + m[ 9]*v[1] + m[10]*v[2], 1255 1/(m[12]*v[0] + m[13]*v[1] + m[14]*v[2]) 1256 ]; 1257 } 1258 1259 /** 1260 * Checks if a vertex is visible. 1261 * @private 1262 * @param {number[]} m - Rotated perspective matrix. 1263 * @param {number[]} v - Input vertex. 1264 * @returns {number} 1 or -1 if the vertex is or is not visible, 1265 * respectively. 1266 */ 1267 function checkInView(m, v) { 1268 var vpp = applyRotPerspToVec(m, v); 1269 var winX = vpp[0]*vpp[3]; 1270 var winY = vpp[1]*vpp[3]; 1271 var winZ = vpp[2]*vpp[3]; 1272 var ret = [0, 0, 0]; 1273 1274 if ( winX < -1 ) 1275 ret[0] = -1; 1276 if ( winX > 1 ) 1277 ret[0] = 1; 1278 if ( winY < -1 ) 1279 ret[1] = -1; 1280 if ( winY > 1 ) 1281 ret[1] = 1; 1282 if ( winZ < -1 || winZ > 1 ) 1283 ret[2] = 1; 1284 return ret; 1285 } 1286 1287 /** 1288 * Checks if a square (tile) is visible. 1289 * @private 1290 * @param {number[]} m - Rotated perspective matrix. 1291 * @param {number[]} v - Square's vertex array. 1292 * @returns {boolean} Whether or not the square is visible. 1293 */ 1294 function checkSquareInView(m, v) { 1295 var check1 = checkInView(m, v.slice(0, 3)); 1296 var check2 = checkInView(m, v.slice(3, 6)); 1297 var check3 = checkInView(m, v.slice(6, 9)); 1298 var check4 = checkInView(m, v.slice(9, 12)); 1299 var testX = check1[0] + check2[0] + check3[0] + check4[0]; 1300 if ( testX == -4 || testX == 4 ) 1301 return false; 1302 var testY = check1[1] + check2[1] + check3[1] + check4[1]; 1303 if ( testY == -4 || testY == 4 ) 1304 return false; 1305 var testZ = check1[2] + check2[2] + check3[2] + check4[2]; 1306 return testZ != 4; 1307 1308 1309 } 1310 1311 /** 1312 * On iOS (iPhone 5c, iOS 10.3), this WebGL error occurs when the canvas is 1313 * too big. Unfortuately, there's no way to test for this beforehand, so we 1314 * reduce the canvas size if this error is thrown. 1315 * @private 1316 */ 1317 function handleWebGLError1286() { 1318 console.log('Reducing canvas size due to error 1286!'); 1319 canvas.width = Math.round(canvas.width / 2); 1320 canvas.height = Math.round(canvas.height / 2); 1321 } 1322} 1323 1324// Vertex shader for equirectangular and cube 1325var v = [ 1326'attribute vec2 a_texCoord;', 1327'varying vec2 v_texCoord;', 1328 1329'void main() {', 1330 // Set position 1331 'gl_Position = vec4(a_texCoord, 0.0, 1.0);', 1332 1333 // Pass the coordinates to the fragment shader 1334 'v_texCoord = a_texCoord;', 1335'}' 1336].join(''); 1337 1338// Vertex shader for multires 1339var vMulti = [ 1340'attribute vec3 a_vertCoord;', 1341'attribute vec2 a_texCoord;', 1342 1343'uniform mat4 u_cubeMatrix;', 1344'uniform mat4 u_perspMatrix;', 1345 1346'varying mediump vec2 v_texCoord;', 1347 1348'void main(void) {', 1349 // Set position 1350 'gl_Position = u_perspMatrix * u_cubeMatrix * vec4(a_vertCoord, 1.0);', 1351 1352 // Pass the coordinates to the fragment shader 1353 'v_texCoord = a_texCoord;', 1354'}' 1355].join(''); 1356 1357// Fragment shader 1358var fragEquiCubeBase = [ 1359'precision highp float;', // mediump looks bad on some mobile devices 1360 1361'uniform float u_aspectRatio;', 1362'uniform float u_psi;', 1363'uniform float u_theta;', 1364'uniform float u_f;', 1365'uniform float u_h;', 1366'uniform float u_v;', 1367'uniform float u_vo;', 1368'uniform float u_rot;', 1369
1370'const float PI = 3.14159265358979323846264;', 1371 1372// Texture 1373'uniform sampler2D u_image0;', 1374'uniform sampler2D u_image1;', 1375'uniform bool u_splitImage;', 1376'uniform samplerCube u_imageCube;', 1377 1378// Coordinates passed in from vertex shader 1379'varying vec2 v_texCoord;', 1380 1381// Background color (display for partial panoramas) 1382'uniform vec4 u_backgroundColor;', 1383 1384'void main() {', 1385 // Map canvas/camera to sphere 1386 'float x = v_texCoord.x * u_aspectRatio;', 1387 'float y = v_texCoord.y;', 1388 'float sinrot = sin(u_rot);', 1389 'float cosrot = cos(u_rot);', 1390 'float rot_x = x * cosrot - y * sinrot;', 1391 'float rot_y = x * sinrot + y * cosrot;', 1392 'float sintheta = sin(u_theta);', 1393 'float costheta = cos(u_theta);', 1394 'float a = u_f * costheta - rot_y * sintheta;', 1395 'float root = sqrt(rot_x * rot_x + a * a);', 1396 'float lambda = atan(rot_x / root, a / root) + u_psi;', 1397 'float phi = atan((rot_y * costheta + u_f * sintheta) / root);', 1398].join('\n'); 1399 1400// Fragment shader 1401var fragCube = fragEquiCubeBase + [ 1402 // Look up color from texture 1403 'float cosphi = cos(phi);', 1404 'gl_FragColor = textureCube(u_imageCube, vec3(cosphi*sin(lambda), sin(phi), cosphi*cos(lambda)));', 1405'}' 1406].join('\n'); 1407 1408// Fragment shader 1409var fragEquirectangular = fragEquiCubeBase + [ 1410 // Wrap image 1411 'lambda = mod(lambda + PI, PI * 2.0) - PI;', 1412 1413 // Map texture to sphere 1414 'vec2 coord = vec2(lambda / PI, phi / (PI / 2.0));', 1415 1416 // Look up color from texture 1417 // Map from [-1,1] to [0,1] and flip y-axis 1418 'if(coord.x < -u_h || coord.x > u_h || coord.y < -u_v + u_vo || coord.y > u_v + u_vo)', 1419 'gl_FragColor = u_backgroundColor;', 1420 'else {', 1421 'if(u_splitImage) {', 1422 // Image was split into two textures to work around texture size limits 1423 'if(coord.x < 0.0)', 1424 'gl_FragColor = texture2D(u_image0, vec2((coord.x + u_h) / u_h, (-coord.y + u_v + u_vo) / (u_v * 2.0)));', 1425 'else', 1426 'gl_FragColor = texture2D(u_image1, vec2((coord.x + u_h) / u_h - 1.0, (-coord.y + u_v + u_vo) / (u_v * 2.0)));', 1427 '} else {', 1428 'gl_FragColor = texture2D(u_image0, vec2((coord.x + u_h) / (u_h * 2.0), (-coord.y + u_v + u_vo) / (u_v * 2.0)));', 1429 '}', 1430 '}', 1431'}' 1432].join('\n'); 1433 1434// Fragment shader 1435var fragMulti = [ 1436'varying mediump vec2 v_texCoord;', 1437'uniform sampler2D u_sampler;', 1438//'uniform mediump vec4 u_color;', 1439 1440'void main(void) {', 1441 // Look up color from texture 1442 'gl_FragColor = texture2D(u_sampler, v_texCoord);', 1443// 'gl_FragColor = u_color;', 1444'}' 1445].join(''); 1446 1447return { 1448 renderer: function(container, image, imagetype, dynamic) { 1449 return new Renderer(container, image, imagetype, dynamic); 1450 } 1451}; 1452 1453})(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.