PageSourceSearch

https://kramers-gut.de/wp-content/uploads/sites/228/ac_assets/360_view/libpannellum.js?ver=2.0.20

js kramers-gut.de collected 2026-10-08 12:52:43 UTC 60,915 bytes, 1,461 lines download raw bytes

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