PageSourceSearch

https://brilliant-village.com/wp-content/plugins/wpvr/public/lib/pannellum/src/js/libpannellum.js?ver=1

js brilliant-village.com collected 2026-10-04 08:27:07 UTC 60,505 bytes, 1,453 lines download raw bytes

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.