PageSourceSearch

https://auto.today/assets/b2199640/libpannellum.js

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.