├── LICENSE.md ├── README.md ├── nouislider.min.css ├── view.js ├── webgl-utils.js ├── startup.js ├── interface.js ├── gauge.min.js ├── physics.js ├── index.html ├── misc.js ├── nouislider.min.js ├── airplane.js ├── terrain.js ├── webgl-debug.js └── gl-matrix-min.js /LICENSE.md: -------------------------------------------------------------------------------- 1 | *The license below applies to the listed files:* 2 | 3 | + airplane.js 4 | + Flight.html 5 | + interface.js 6 | + misc.js 7 | + physics.js 8 | + terrain.js 9 | + view.js 10 | 11 | The MIT License 12 | 13 | Copyright (c) 2016 Po-Han Huang phuang17@illinois.edu 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Po-Han Huang's Flight Simulator 2 | 3 | This is a machine problem for *[CS418: Computer Graphics](https://courses.engr.illinois.edu/cs418/)* at University of Illinois at Urbana-Champaign in the Fall 2016 semester. 4 | 5 | ## Instructions 6 | 7 | Open [*Flight.html*](https://phuang17.github.io/SideProjects/FlightSimulator/index.html) in web browser, and instructions should pop up automatically. 8 | 9 | ## Compatibility 10 | 11 | Tested on **Firefox 49** and **Google 53**. Run on my laptop (Intel Core i5 & NVIDIA Geforce GT720M) without any lagging issues. If you do have lagging issues, adjust the `TERRAIN_DETAIL_LEVEL` in *terrain.js* to a lower value. 12 | 13 | ## Contributions 14 | 15 | The following files are not created by me: 16 | 17 | + **gauge.min.js**: [gauge interfaces](http://bernii.github.io/gauge.js/) 18 | + **gl-matrix-min.js**: [glmatrix library](http://glmatrix.net/) 19 | + **nouislider.min.css** and **nouislider.min.js**: [slider interfaces](https://refreshless.com/nouislider/) 20 | + **webgl-utils.js**: [WebGL utility library](https://www.khronos.org/registry/webgl/sdk/demos/common/) 21 | + **webgl-debug.js**: [WebGL debugging library](https://www.khronos.org/webgl/wiki/Debugging) 22 | + Part of **startup.js** is given on [course website](https://courses.engr.illinois.edu/cs418/). 23 | 24 | For license of other files, see [LICENSE.md](LICENSE.md). -------------------------------------------------------------------------------- /nouislider.min.css: -------------------------------------------------------------------------------- 1 | /*! nouislider - 9.0.0 - 2016-09-29 21:44:03 */ 2 | 3 | 4 | .noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative;direction:ltr}.noUi-base{width:100%;height:100%;position:relative;z-index:1}.noUi-connect{position:absolute;right:0;top:0;left:0;bottom:0}.noUi-origin{position:absolute;height:0;width:0}.noUi-handle{position:relative;z-index:1}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{-webkit-transition:top .3s,right .3s,bottom .3s,left .3s;transition:top .3s,right .3s,bottom .3s,left .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-base,.noUi-handle{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}.noUi-target{background:#FAFAFA;border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-connect{background:#3FB8AF;box-shadow:inset 0 0 3px rgba(51,51,51,.45);-webkit-transition:background 450ms;transition:background 450ms}.noUi-draggable{cursor:w-resize}.noUi-vertical .noUi-draggable{cursor:n-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{-webkit-transform:translate3d(-50%,50%,0);transform:translate3d(-50%,50%,0)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0);padding-left:25px}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #D9D9D9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center}.noUi-horizontal .noUi-tooltip{-webkit-transform:translate(-50%,0);transform:translate(-50%,0);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{-webkit-transform:translate(0,-50%);transform:translate(0,-50%);top:50%;right:120%} -------------------------------------------------------------------------------- /view.js: -------------------------------------------------------------------------------- 1 | /** 2 | * view.js 3 | * 4 | * @fileoverview Manages all vision features, including viewing parameters 5 | * (eye coordinates, lookAt vector, up vector, view quaternion, pitch, roll, 6 | * yaw, viewing matrices) and lighting parameters. 7 | * @author Po-Han Huang 8 | */ 9 | 10 | /** Initial eye height */ 11 | var AIRPLANE_HEIGHT = 0.0006; 12 | /** Initial eye coordinates */ 13 | var viewOrigin = vec3.fromValues(0.0,-0.9,AIRPLANE_HEIGHT); 14 | /** Coordinates of the tip of airplane */ 15 | var airplaneTip = vec3.clone(viewOrigin); 16 | 17 | /** Up vector */ 18 | var viewUp = vec3.create(); 19 | /** LookAt vector */ 20 | var viewLookAt = vec3.create(); 21 | /** LookAt center */ 22 | var viewCenter = vec3.create(); 23 | /** Right vector */ 24 | var viewRight = vec3.create(); 25 | 26 | /** Viewing quaternion */ 27 | var viewQuat = quat.create(); 28 | /** Viewing matrix */ 29 | var mvMatrix = mat4.create(); 30 | /** Perspective projection matrix */ 31 | var pMatrix = mat4.create(); 32 | 33 | /** Unit vectors along each axis */ 34 | var X_AXIS = vec3.fromValues(1.0,0.0,0.0); 35 | var Y_AXIS = vec3.fromValues(0.0,1.0,0.0); 36 | var Z_AXIS = vec3.fromValues(0.0,0.0,1.0); 37 | 38 | /** Initial viewing directions */ 39 | var VIEW_UP_INIT = vec3.fromValues(0.0,0.0,1.0); 40 | var VIEW_LOOKAT_INIT = vec3.fromValues(0.0,-1.0,0.0); 41 | var VIEW_RIGHT_INIT = vec3.fromValues(-1.0,0.0,0.0); 42 | 43 | /** Field of view */ 44 | var VIEWPORT = 85; 45 | 46 | /** Lighting parameters */ 47 | /** Parallel light source */ 48 | var LIGHT_DIRECTION = vec3.fromValues(Math.sin(degToRad(30))*Math.cos(degToRad(30)), 49 | Math.sin(degToRad(30))*Math.sin(degToRad(30)), 50 | Math.cos(degToRad(30))); 51 | /** White lights for ambient and diffuse parts. */ 52 | var AMBIENT_LIGHT = vec3.fromValues(0.3, 0.3, 0.3); 53 | var DIFFUSE_LIGHT = vec3.fromValues(0.6, 0.6, 0.6); 54 | /** Specular light is soft warm sunlight (RGB = 253,184,19). */ 55 | var SPECULAR_LIGHT = vec3.fromValues(0.55*253/255, 0.55*184/255, 0.55*19/255); 56 | 57 | /** Initialization of view.js */ 58 | function viewInit(){ 59 | /** Generate perpective projection matrix. */ 60 | mat4.perspective(pMatrix, degToRad(VIEWPORT), gl.viewportWidth / gl.viewportHeight, 0.0001, 10.0); 61 | } 62 | 63 | /** Update viewing vectors and matrices. */ 64 | function viewUpdateMatrix(){ 65 | 66 | /** Update up, lookAt, and right vector based on current quat. */ 67 | vec3.transformQuat(viewUp, VIEW_UP_INIT, viewQuat); 68 | vec3.transformQuat(viewLookAt, VIEW_LOOKAT_INIT, viewQuat); 69 | vec3.transformQuat(viewRight, VIEW_RIGHT_INIT, viewQuat); 70 | 71 | /** Update viewing matrix */ 72 | vec3.add(viewCenter, viewOrigin, viewLookAt); 73 | mat4.lookAt(mvMatrix, viewOrigin, viewCenter, viewUp); 74 | 75 | /** Update airplane tip. */ 76 | var offsetFront = vec3.create(); 77 | var offsetUp = vec3.create(); 78 | 79 | vec3.scale(offsetFront, viewLookAt, AIRPLANE_TIP_FRONT); 80 | vec3.scale(offsetUp, viewUp, AIRPLANE_TIP_UP); 81 | 82 | vec3.add(airplaneTip, viewOrigin, offsetFront); 83 | vec3.add(airplaneTip, airplaneTip, offsetUp); 84 | 85 | } 86 | 87 | /** Calculate current roll. 88 | * @return {float} roll in rad 89 | */ 90 | function getRoll(){ 91 | return vec3.angle(viewRight, Z_AXIS) - Math.PI / 2; 92 | } 93 | 94 | /** Calculate current pitch. 95 | * @return {float} pitch in rad 96 | */ 97 | function getPitch(){ 98 | return Math.PI / 2 - vec3.angle(viewLookAt, Z_AXIS); 99 | } 100 | 101 | /** Calculate current yaw. 102 | * @return {float} yaw in rad 103 | */ 104 | function getYaw(){ 105 | return angleNormalize(getOrientation() - getVelocityOrientation()); 106 | } 107 | 108 | /** Calculate current lookAt orientation (with +y-axis being 0 and +x-axis being PI/2). 109 | * @return {float} looatAtOrientation in rad 110 | */ 111 | function getOrientation(){ 112 | var orientation = vec3.fromValues(viewLookAt[0],viewLookAt[1],0); 113 | return angleNormalize(viewLookAt[0] < 0 ? -vec3.angle(orientation, Y_AXIS) : vec3.angle(orientation, Y_AXIS)); 114 | } 115 | 116 | /** Calculate current velocity orientation (with +y-axis being 0 and +x-axis being PI/2). 117 | * @return {float} velocityOrientation in rad 118 | */ 119 | function getVelocityOrientation(){ 120 | var orientation = vec3.fromValues(velocity[0],velocity[1],0); 121 | return angleNormalize(velocity[0] < 0 ? -vec3.angle(orientation, Y_AXIS) : vec3.angle(orientation, Y_AXIS)); 122 | } 123 | 124 | /** Check if velocity is in opposite direction to lookAt. (Basically, whether 125 | * airplane is going backwards.) 126 | * @return {boolean} 127 | */ 128 | function getIfVelocityOppositeToLookat(){ 129 | var yaw = getYaw(); 130 | return yaw > degToRad(90) || yaw < degToRad(-90); 131 | } 132 | 133 | /** Calculate horizontal velocity. 134 | * @return {float} horizontalVelocity 135 | */ 136 | function getHorizontalVelocity(){ 137 | return Math.sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1]); 138 | } 139 | 140 | /** Helper function to make sure an angle falls within -PI to PI. 141 | * @param {float} angle in rad 142 | * @return {float} angle in rad 143 | */ 144 | function angleNormalize(angle){ 145 | if(angle > degToRad(180)){ 146 | angle -= Math.PI * 2; 147 | }else if(angle <= degToRad(-180)){ 148 | angle += Math.PI * 2; 149 | } 150 | return angle; 151 | } 152 | 153 | 154 | -------------------------------------------------------------------------------- /webgl-utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010, Google Inc. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above 12 | * copyright notice, this list of conditions and the following disclaimer 13 | * in the documentation and/or other materials provided with the 14 | * distribution. 15 | * * Neither the name of Google Inc. nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | */ 31 | 32 | 33 | /** 34 | * @fileoverview This file contains functions every webgl program will need 35 | * a version of one way or another. 36 | * 37 | * Instead of setting up a context manually it is recommended to 38 | * use. This will check for success or failure. On failure it 39 | * will attempt to present an approriate message to the user. 40 | * 41 | * gl = WebGLUtils.setupWebGL(canvas); 42 | * 43 | * For animated WebGL apps use of setTimeout or setInterval are 44 | * discouraged. It is recommended you structure your rendering 45 | * loop like this. 46 | * 47 | * function render() { 48 | * window.requestAnimFrame(render, canvas); 49 | * 50 | * // do rendering 51 | * ... 52 | * } 53 | * render(); 54 | * 55 | * This will call your rendering function up to the refresh rate 56 | * of your display but will stop rendering if your app is not 57 | * visible. 58 | */ 59 | 60 | WebGLUtils = function() { 61 | 62 | /** 63 | * Creates the HTLM for a failure message 64 | * @param {string} canvasContainerId id of container of th 65 | * canvas. 66 | * @return {string} The html. 67 | */ 68 | var makeFailHTML = function(msg) { 69 | return '' + 70 | '' + 71 | '
' + 72 | '
' + 73 | '
' + msg + '
' + 74 | '
' + 75 | '
'; 76 | }; 77 | 78 | /** 79 | * Mesasge for getting a webgl browser 80 | * @type {string} 81 | */ 82 | var GET_A_WEBGL_BROWSER = '' + 83 | 'This page requires a browser that supports WebGL.
' + 84 | 'Click here to upgrade your browser.'; 85 | 86 | /** 87 | * Mesasge for need better hardware 88 | * @type {string} 89 | */ 90 | var OTHER_PROBLEM = '' + 91 | "It doesn't appear your computer can support WebGL.
" + 92 | 'Click here for more information.'; 93 | 94 | /** 95 | * Creates a webgl context. If creation fails it will 96 | * change the contents of the container of the 97 | * tag to an error message with the correct links for WebGL. 98 | * @param {Element} canvas. The canvas element to create a 99 | * context from. 100 | * @param {WebGLContextCreationAttirbutes} opt_attribs Any 101 | * creation attributes you want to pass in. 102 | * @param {function:(msg)} opt_onError An function to call 103 | * if there is an error during creation. 104 | * @return {WebGLRenderingContext} The created context. 105 | */ 106 | var setupWebGL = function(canvas, opt_attribs, opt_onError) { 107 | function handleCreationError(msg) { 108 | var container = canvas.parentNode; 109 | if (container) { 110 | var str = window.WebGLRenderingContext ? 111 | OTHER_PROBLEM : 112 | GET_A_WEBGL_BROWSER; 113 | if (msg) { 114 | str += "

Status: " + msg; 115 | } 116 | container.innerHTML = makeFailHTML(str); 117 | } 118 | }; 119 | 120 | opt_onError = opt_onError || handleCreationError; 121 | 122 | if (canvas.addEventListener) { 123 | canvas.addEventListener("webglcontextcreationerror", function(event) { 124 | opt_onError(event.statusMessage); 125 | }, false); 126 | } 127 | var context = create3DContext(canvas, opt_attribs); 128 | if (!context) { 129 | if (!window.WebGLRenderingContext) { 130 | opt_onError(""); 131 | } 132 | } 133 | return context; 134 | }; 135 | 136 | /** 137 | * Creates a webgl context. 138 | * @param {!Canvas} canvas The canvas tag to get context 139 | * from. If one is not passed in one will be created. 140 | * @return {!WebGLContext} The created context. 141 | */ 142 | var create3DContext = function(canvas, opt_attribs) { 143 | var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"]; 144 | var context = null; 145 | for (var ii = 0; ii < names.length; ++ii) { 146 | try { 147 | context = canvas.getContext(names[ii], opt_attribs); 148 | } catch(e) {} 149 | if (context) { 150 | break; 151 | } 152 | } 153 | return context; 154 | } 155 | 156 | return { 157 | create3DContext: create3DContext, 158 | setupWebGL: setupWebGL 159 | }; 160 | }(); 161 | 162 | /** 163 | * Provides requestAnimationFrame in a cross browser way. 164 | */ 165 | window.requestAnimFrame = (function() { 166 | return window.requestAnimationFrame || 167 | window.webkitRequestAnimationFrame || 168 | window.mozRequestAnimationFrame || 169 | window.oRequestAnimationFrame || 170 | window.msRequestAnimationFrame || 171 | function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { 172 | window.setTimeout(callback, 1000/60); 173 | }; 174 | })(); 175 | 176 | -------------------------------------------------------------------------------- /startup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * startup.js 3 | * 4 | * @fileoverview Manages startup functions as well as restart and gameover. 5 | * Part of this file is given as course materials for CS418 at University of 6 | * Illinois at Urbana-Champaign. (Course website: 7 | * https://courses.engr.illinois.edu/cs418/ ) 8 | * @author Po-Han Huang 9 | */ 10 | 11 | /** Main GL entity */ 12 | var gl; 13 | 14 | /** Shader prefixes */ 15 | var shaderPrefix = []; 16 | /** Shader programs */ 17 | var shaderPrograms = {}; 18 | /** Shader initialization functions */ 19 | var shaderInit = {}; 20 | /** Buffer initialization functions */ 21 | var bufferInit = {}; 22 | /** Draw functions */ 23 | var drawFunctions = {}; 24 | /** Animate functions */ 25 | var animateFunctions = {}; 26 | 27 | /** Timers */ 28 | var startTime; 29 | var previousTime; 30 | 31 | /** Enter point of the scripts for initialization of everything. */ 32 | function startup() { 33 | 34 | /** Get message borad and display message. */ 35 | msgDOM = document.getElementById("msgDOM").firstChild; 36 | msg("Startup() called."); 37 | 38 | /** Create GL entity. */ 39 | var canvas = document.getElementById("myGLCanvas"); 40 | gl = createGLContext(canvas); 41 | gl.clearColor(135/255, 206/255, 250/255, 1.0); 42 | gl.enable(gl.DEPTH_TEST); 43 | gl.getExtension("OES_element_index_uint"); 44 | 45 | /** Set timers. */ 46 | startTime = previousTime = Date.now(); 47 | 48 | /** Initialize all scripts, shaders, and buffers. */ 49 | initAll(); 50 | msg("Iitializing shaders..."); 51 | setupShaders(); 52 | msg("Iitializing buffers..."); 53 | setupBuffers(); 54 | 55 | /** Start drawing! */ 56 | tick(); 57 | } 58 | 59 | /** Render a frame. */ 60 | function tick() { 61 | requestAnimFrame(tick); 62 | 63 | /** Only draw and animate if game is still playing. */ 64 | if(playing){ 65 | draw(); 66 | animate(); 67 | interfaceUpdate(); 68 | } 69 | 70 | /** Check if restart. */ 71 | if(currentlyPressedKeys[82]){ 72 | restart(); 73 | } 74 | } 75 | 76 | /** Create GL context. 77 | * @param {canvasElement} canvas 78 | * @return {glContext} 79 | */ 80 | function createGLContext(canvas) { 81 | var names = ["webgl", "experimental-webgl"]; 82 | var context = null; 83 | for (var i=0; i < names.length; i++) { 84 | try { 85 | context = canvas.getContext(names[i]); 86 | /** For debugging only */ 87 | // context = WebGLDebugUtils.makeDebugContext(canvas.getContext(names[i])); 88 | } catch(e) {} 89 | if (context) { 90 | break; 91 | } 92 | } 93 | if (context) { 94 | context.viewportWidth = canvas.width; 95 | context.viewportHeight = canvas.height; 96 | } else { 97 | alert("Failed to create WebGL context!"); 98 | } 99 | return context; 100 | } 101 | 102 | /** Setup all shaders. */ 103 | function setupShaders(){ 104 | for(var i = 0; i < shaderPrefix.length; i++){ 105 | setupOneShader(shaderPrefix[i]); 106 | shaderInit[shaderPrefix[i]](); 107 | } 108 | } 109 | 110 | /** Setup one shader with specified identification prefix. 111 | * @param {string} prefix 112 | */ 113 | function setupOneShader(prefix){ 114 | 115 | /** Create new program. */ 116 | var oneShaderProgram = gl.createProgram(); 117 | /** Get shader codes. */ 118 | var vertexShader = gl.createShader(gl.VERTEX_SHADER); 119 | var vertexShaderSource = document.getElementById(prefix + "VertexShaderDOM").innerHTML; 120 | var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 121 | var fragmentShaderSource = document.getElementById(prefix + "FragmentShaderDOM").innerHTML; 122 | 123 | /** Compile and link program. */ 124 | gl.shaderSource(vertexShader,vertexShaderSource); 125 | gl.compileShader(vertexShader); 126 | gl.attachShader(oneShaderProgram, vertexShader); 127 | gl.shaderSource(fragmentShader,fragmentShaderSource); 128 | gl.compileShader(fragmentShader); 129 | gl.attachShader(oneShaderProgram, fragmentShader); 130 | gl.linkProgram(oneShaderProgram); 131 | 132 | if (!gl.getProgramParameter(oneShaderProgram, gl.LINK_STATUS)) { 133 | console.log("Failed to setup shader:" + prefix); 134 | } 135 | 136 | shaderPrograms[prefix] = oneShaderProgram; 137 | } 138 | 139 | /** Setup all buffers. */ 140 | function setupBuffers(){ 141 | for(var i = 0; i < shaderPrefix.length; i++){ 142 | bufferInit[shaderPrefix[i]](); 143 | } 144 | } 145 | 146 | /** Draw a frame by calling all draw functions. */ 147 | function draw(){ 148 | gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); 149 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 150 | for(var i = 0; i < shaderPrefix.length; i++){ 151 | drawFunctions[shaderPrefix[i]](); 152 | } 153 | } 154 | 155 | /** Animate by calling all animate functions. */ 156 | function animate(){ 157 | /** Get timelapse since last frame in sec. */ 158 | var lapse = Date.now() - previousTime; 159 | previousTime = previousTime + lapse; 160 | lapse /= 1000.0; 161 | 162 | /** Update physics and views. */ 163 | physicsUpdate(lapse); 164 | viewUpdateMatrix(); 165 | 166 | /** Call animate functions. */ 167 | for(var i = 0; i < shaderPrefix.length; i++){ 168 | animateFunctions[shaderPrefix[i]](lapse); 169 | } 170 | } 171 | 172 | /** Convert degree to rad. 173 | * @param {float} d degree 174 | * @return {float} r rad 175 | */ 176 | function degToRad(d){ 177 | return d * Math.PI / 180 ; 178 | } 179 | 180 | /** Convert rad to degree. 181 | * @param {float} r rad 182 | * @return {float} d degree 183 | */ 184 | function radToDeg(r){ 185 | return r / Math.PI * 180 ; 186 | } 187 | 188 | /** Initialize all scripts. */ 189 | function initAll(){ 190 | msg("Iitializing interface..."); 191 | interfaceInit(); 192 | msg("Iitializing view..."); 193 | viewInit(); 194 | msg("Iitializing physics..."); 195 | physicsInit(); 196 | msg("Iitializing airplane..."); 197 | airplaneInit(); 198 | msg("Iitializing misc..."); 199 | miscInit(); 200 | msg("Iitializing terrain..."); 201 | terrainInit(); 202 | } 203 | 204 | /** Restart the game. */ 205 | function restart(){ 206 | 207 | playing = true; 208 | ground = true; 209 | reverse = false; 210 | 211 | startTime = previousTime = Date.now(); 212 | 213 | viewQuat = quat.create(); 214 | viewOrigin = vec3.fromValues(0.0,-0.9,AIRPLANE_HEIGHT); 215 | velocity = vec3.create(); 216 | 217 | interfaces.thrust.set(0.0); 218 | 219 | msg("Initialization done! Start having fun!"); 220 | 221 | document.getElementById("msgDOM").style.color = "black"; 222 | } 223 | 224 | /** End the game and display text on message board. 225 | * @param {string} text 226 | */ 227 | function gameover(text){ 228 | playing = false; 229 | msg(text); 230 | document.getElementById("msgDOM").style.color = "red"; 231 | } -------------------------------------------------------------------------------- /interface.js: -------------------------------------------------------------------------------- 1 | /** 2 | * interface.js 3 | * 4 | * @fileoverview Manages interfaces, including keyboard presses, sliders, 5 | * gauges, and location map. 6 | * @author Po-Han Huang 7 | */ 8 | 9 | /** Currently pressed keys. True if pressed, otherwise not pressed. */ 10 | var currentlyPressedKeys = {}; 11 | /** Hold interface elements. */ 12 | var interfaces = {}; 13 | /** Hold text nodes of interface elements. */ 14 | var values = {}; 15 | 16 | /** Hold elements of airplane icons. */ 17 | var airplaneIcons = []; 18 | 19 | /** Hold the text node of message board. */ 20 | var msgDOM; 21 | 22 | /** Time limit of each game in ms. */ 23 | var TIMEOUT = 900 * 1000; 24 | 25 | /** Initialize interfaces. */ 26 | function interfaceInit(){ 27 | 28 | /** Attach key event listeners. */ 29 | document.onkeydown = handleKeyDown; 30 | document.onkeyup = handleKeyUp; 31 | 32 | /* Initialize gauges. */ 33 | interfaces.velocity = new Gauge( document.getElementById("velocityGaugeCanvas") ).setOptions({ limitMax: 'true' }); 34 | interfaces.velocity.maxValue = 0.04; 35 | interfaces.velocity.animationSpeed = 1; 36 | interfaces.velocity.set(0); 37 | values.velocity = document.getElementById("velocityGaugeValue").firstChild; 38 | 39 | interfaces.fuel = new Gauge( document.getElementById("fuelGaugeCanvas") ).setOptions({ limitMax: 'true' }); 40 | interfaces.fuel.maxValue = 1; 41 | interfaces.fuel.animationSpeed = 1; 42 | interfaces.fuel.set(1); 43 | values.fuel = document.getElementById("fuelGaugeValue").firstChild; 44 | 45 | /** Initialize compass. */ 46 | interfaces.orientation = document.getElementById("orientationCompass") 47 | 48 | /** Initialize sliders. */ 49 | interfaces.roll = noUiSlider.create(document.getElementById("rollSlider"), 50 | { start: [0], 51 | range: 52 | { 'min': [ -Math.PI / 2 ], 53 | 'max': [ Math.PI / 2 ] 54 | }, 55 | connect: [true, false], 56 | animate: false 57 | }); 58 | values.roll = document.getElementById("rollSliderValue").firstChild; 59 | 60 | interfaces.pitch = noUiSlider.create(document.getElementById("pitchSlider"), 61 | { start: [0], 62 | range: 63 | { 'min': [ -Math.PI / 6 ], 64 | 'max': [ Math.PI / 6 ] 65 | }, 66 | connect: [true, false], 67 | orientation: "vertical", 68 | direction: 'rtl', 69 | animate: false 70 | }); 71 | values.pitch = document.getElementById("pitchSliderValue").firstChild; 72 | 73 | interfaces.height = noUiSlider.create(document.getElementById("heightSlider"), 74 | { start: [0], 75 | range: 76 | { 'min': [ 0.0 ], 77 | 'max': [ 1.0 ] 78 | }, 79 | connect: [true, false], 80 | orientation: "vertical", 81 | direction: 'rtl', 82 | animate: false 83 | }); 84 | values.height = document.getElementById("heightSliderValue").firstChild; 85 | 86 | interfaces.thrust = noUiSlider.create(document.getElementById("thrustSlider"), 87 | { start: [0], 88 | range: 89 | { 'min': [ 0 ], 90 | 'max': [ 1000 ] 91 | }, 92 | step: 1, 93 | connect: [true, false], 94 | orientation: "vertical", 95 | direction: 'rtl', 96 | animate: false 97 | }); 98 | values.thrust = document.getElementById("thrustSliderValue").firstChild; 99 | 100 | /** Create airplane icons for obstacles. */ 101 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 102 | var newDOM = document.createElement("img"); 103 | newDOM.src = "https://maxcdn.icons8.com/office/PNG/16/Transport/airport-16.png"; 104 | newDOM.width = 16; 105 | newDOM.style.position = "absolute"; 106 | newDOM.style.zIndex = 10; 107 | newDOM.style.visibility = "hidden"; 108 | document.getElementById("positionDOM").appendChild(newDOM); 109 | airplaneIcons.push(newDOM); 110 | } 111 | 112 | } 113 | 114 | /** 115 | * Handle key presses. 116 | * @param {event object} event 117 | */ 118 | function handleKeyDown(event) { 119 | currentlyPressedKeys[event.keyCode] = true; 120 | 121 | /** Toggle thrust if C or J is pressed. */ 122 | if(event.keyCode == 67 || event.keyCode == 74){ 123 | reverse = !reverse; 124 | var button = document.getElementById("reverseButton"); 125 | button.style.borderStyle = (button.style.borderStyle!=='inset' ? 'inset' : 'outset'); 126 | } 127 | 128 | } 129 | 130 | /** 131 | * Handle key releases. 132 | * @param {event object} event 133 | */ 134 | function handleKeyUp(event) { 135 | currentlyPressedKeys[event.keyCode] = false; 136 | } 137 | 138 | /** Update the interface. */ 139 | function interfaceUpdate() { 140 | 141 | /** Set values of interfaces. */ 142 | var velocity = getIfVelocityOppositeToLookat() ? -getHorizontalVelocity() : getHorizontalVelocity(); 143 | 144 | interfaces.velocity.set( velocity ); 145 | values.velocity.nodeValue = (velocity*10000).toFixed(2) + " knots"; 146 | 147 | interfaces.fuel.set( 1.0 - ( Date.now() - startTime ) / TIMEOUT ); 148 | values.fuel.nodeValue = ((1.0 - ( Date.now() - startTime ) / TIMEOUT)*100).toFixed(2) + "%"; 149 | 150 | interfaces.roll.set( getRoll() ); 151 | values.roll.nodeValue = (getRoll()>=0?"+":"") + radToDeg(getRoll()).toFixed(2) + " deg"; 152 | 153 | interfaces.pitch.set( getPitch() ); 154 | values.pitch.nodeValue = (getPitch()>=0?"+":"") + radToDeg(getPitch()).toFixed(2) + " deg"; 155 | 156 | interfaces.height.set( viewOrigin[2] ); 157 | values.height.nodeValue = (viewOrigin[2]*10000).toFixed(2) + " feet"; 158 | 159 | /** Increment/decrement thrust if necessary. */ 160 | if(currentlyPressedKeys[88] || currentlyPressedKeys[76]){ 161 | /** If X or L is pressed. */ 162 | interfaces.thrust.set( parseFloat(interfaces.thrust.get()) + 5 ); 163 | }else if(currentlyPressedKeys[90] || currentlyPressedKeys[75]){ 164 | /** If Z or K is pressed. */ 165 | interfaces.thrust.set( parseFloat(interfaces.thrust.get()) - 5 ); 166 | } 167 | 168 | /** Calculate and update thrust based on slider value. */ 169 | thrust = parseFloat(interfaces.thrust.get()) / 1000 * (reverse ? (getIfVelocityOppositeToLookat() ? -0.2 : -1.6) : 1.0); 170 | values.thrust.nodeValue = (thrust>=0?"+":"-") + (parseFloat(interfaces.thrust.get())/10.0).toFixed(1) + "%"; 171 | 172 | /** Update compass' location and direction. */ 173 | interfaces.orientation.style.transform = "rotate(" + radToDeg( getOrientation() ) + "deg)"; 174 | interfaces.orientation.style.left = ((viewOrigin[0]+1.0)/2.0*200-12) + "px"; 175 | interfaces.orientation.style.top = ((1.0-viewOrigin[1])/2.0*200-12) + "px"; 176 | 177 | /** Update obstacles' locations and directions. */ 178 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 179 | airplaneIcons[i].style.transform = "rotate(" + (-radToDeg( obstacleAngle[i] )) + "deg)"; 180 | airplaneIcons[i].style.left = ((obstacleOrigin[i][0]+1.0)/2.0*200-8) + "px"; 181 | airplaneIcons[i].style.top = ((1.0-obstacleOrigin[i][1])/2.0*200-8) + "px"; 182 | } 183 | 184 | } 185 | 186 | /** Show messgae on message board. 187 | * @param {string} text 188 | * @param {function|null} callback callback function after the text is shown. 189 | */ 190 | function msg(text, callback){ 191 | msgDOM.nodeValue = text; 192 | if(callback){ 193 | setTimeout(callback,0); 194 | } 195 | } 196 | 197 | /** Set obstacle level. 198 | * @param {int} level none=0, static=1, moving=2 199 | */ 200 | function setObstacleLevel(level){ 201 | obstacleLevel = level; 202 | /** Update visibility of airplane icons. */ 203 | if(level === 0){ 204 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 205 | airplaneIcons[i].style.visibility = "hidden"; 206 | } 207 | }else{ 208 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 209 | airplaneIcons[i].style.visibility = "visible"; 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /gauge.min.js: -------------------------------------------------------------------------------- 1 | (function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q={}.hasOwnProperty,r=function(a,b){function c(){this.constructor=a}for(var d in b)q.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};!function(){var a,b,c,d,e,f,g;for(g=["ms","moz","webkit","o"],c=0,e=g.length;e>c&&(f=g[c],!window.requestAnimationFrame);c++)window.requestAnimationFrame=window[f+"RequestAnimationFrame"],window.cancelAnimationFrame=window[f+"CancelAnimationFrame"]||window[f+"CancelRequestAnimationFrame"];return a=null,d=0,b={},requestAnimationFrame?window.cancelAnimationFrame?void 0:(a=window.requestAnimationFrame,window.requestAnimationFrame=function(c,e){var f;return f=++d,a(function(){return b[f]?void 0:c()},e),f},window.cancelAnimationFrame=function(a){return b[a]=!0}):(window.requestAnimationFrame=function(a,b){var c,d,e,f;return c=(new Date).getTime(),f=Math.max(0,16-(c-e)),d=window.setTimeout(function(){return a(c+f)},f),e=c+f,d},window.cancelAnimationFrame=function(a){return clearTimeout(a)})}(),String.prototype.hashCode=function(){var a,b,c,d,e;if(b=0,0===this.length)return b;for(c=d=0,e=this.length;e>=0?e>d:d>e;c=e>=0?++d:--d)a=this.charCodeAt(c),b=(b<<5)-b+a,b&=b;return b},o=function(a){var b,c;for(b=Math.floor(a/3600),c=Math.floor((a-3600*b)/60),a-=3600*b+60*c,a+="",c+="";c.length<2;)c="0"+c;for(;a.length<2;)a="0"+a;return b=b?b+":":"",b+c+":"+a},m=function(a){return k(a.toFixed(0))},p=function(a,b){var c,d;for(c in b)q.call(b,c)&&(d=b[c],a[c]=d);return a},n=function(a,b){var c,d,e;d={};for(c in a)q.call(a,c)&&(e=a[c],d[c]=e);for(c in b)q.call(b,c)&&(e=b[c],d[c]=e);return d},k=function(a){var b,c,d,e;for(a+="",c=a.split("."),d=c[0],e="",c.length>1&&(e="."+c[1]),b=/(\d+)(\d{3})/;b.test(d);)d=d.replace(b,"$1,$2");return d+e},l=function(a){return"#"===a.charAt(0)?a.substring(1,7):a},j=function(){function a(a,b){null==a&&(a=!0),this.clear=null!=b?b:!0,a&&AnimationUpdater.add(this)}return a.prototype.animationSpeed=32,a.prototype.update=function(a){var b;return null==a&&(a=!1),a||this.displayedValue!==this.value?(this.ctx&&this.clear&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),b=this.value-this.displayedValue,Math.abs(b/this.animationSpeed)<=.001?this.displayedValue=this.value:this.displayedValue=this.displayedValue+b/this.animationSpeed,this.render(),!0):!1},a}(),e=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}return r(b,a),b.prototype.displayScale=1,b.prototype.setTextField=function(a){return this.textField=a instanceof i?a:new i(a)},b.prototype.setMinValue=function(a,b){var c,d,e,f,g;if(this.minValue=a,null==b&&(b=!0),b){for(this.displayedValue=this.minValue,f=this.gp||[],g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.displayedValue=this.minValue);return g}},b.prototype.setOptions=function(a){return null==a&&(a=null),this.options=n(this.options,a),this.textField&&(this.textField.el.style.fontSize=a.fontSize+"px"),this.options.angle>.5&&(this.gauge.options.angle=.5),this.configDisplayScale(),this},b.prototype.configDisplayScale=function(){var a,b,c,d,e;return d=this.displayScale,this.options.highDpiSupport===!1?delete this.displayScale:(b=window.devicePixelRatio||1,a=this.ctx.webkitBackingStorePixelRatio||this.ctx.mozBackingStorePixelRatio||this.ctx.msBackingStorePixelRatio||this.ctx.oBackingStorePixelRatio||this.ctx.backingStorePixelRatio||1,this.displayScale=b/a),this.displayScale!==d&&(e=this.canvas.G__width||this.canvas.width,c=this.canvas.G__height||this.canvas.height,this.canvas.width=e*this.displayScale,this.canvas.height=c*this.displayScale,this.canvas.style.width=e+"px",this.canvas.style.height=c+"px",this.canvas.G__width=e,this.canvas.G__height=c),this},b}(j),i=function(){function a(a){this.el=a}return a.prototype.render=function(a){return this.el.innerHTML=m(a.displayedValue)},a}(),a=function(a){function b(a,b){this.elem=a,this.text=null!=b?b:!1,this.value=1*this.elem.innerHTML,this.text&&(this.value=0)}return r(b,a),b.prototype.displayedValue=0,b.prototype.value=0,b.prototype.setVal=function(a){return this.value=1*a},b.prototype.render=function(){var a;return a=this.text?o(this.displayedValue.toFixed(0)):k(m(this.displayedValue)),this.elem.innerHTML=a},b}(j),b={create:function(b){var c,d,e,f;for(f=[],d=0,e=b.length;e>d;d++)c=b[d],f.push(new a(c));return f}},h=function(a){function b(a){this.gauge=a,this.ctx=this.gauge.ctx,this.canvas=this.gauge.canvas,b.__super__.constructor.call(this,!1,!1),this.setOptions()}return r(b,a),b.prototype.displayedValue=0,b.prototype.value=0,b.prototype.options={strokeWidth:.035,length:.1,color:"#000000"},b.prototype.setOptions=function(a){return null==a&&(a=null),p(this.options,a),this.length=this.canvas.height*this.options.length,this.strokeWidth=this.canvas.height*this.options.strokeWidth,this.maxValue=this.gauge.maxValue,this.minValue=this.gauge.minValue,this.displayedValue=this.gauge.minValue,this.animationSpeed=this.gauge.animationSpeed,this.options.angle=this.gauge.options.angle},b.prototype.render=function(){var a,b,c,d,e,f,g,h,i;return a=this.gauge.getAngle.call(this,this.displayedValue),b=this.canvas.width/2,c=.9*this.canvas.height,h=Math.round(b+this.length*Math.cos(a)),i=Math.round(c+this.length*Math.sin(a)),f=Math.round(b+this.strokeWidth*Math.cos(a-Math.PI/2)),g=Math.round(c+this.strokeWidth*Math.sin(a-Math.PI/2)),d=Math.round(b+this.strokeWidth*Math.cos(a+Math.PI/2)),e=Math.round(c+this.strokeWidth*Math.sin(a+Math.PI/2)),this.ctx.fillStyle=this.options.color,this.ctx.beginPath(),this.ctx.arc(b,c,this.strokeWidth,0,2*Math.PI,!0),this.ctx.fill(),this.ctx.beginPath(),this.ctx.moveTo(f,g),this.ctx.lineTo(h,i),this.ctx.lineTo(d,e),this.ctx.fill()},b}(j),c=function(){function a(a){this.elem=a}return a.prototype.updateValues=function(a){return this.value=a[0],this.maxValue=a[1],this.avgValue=a[2],this.render()},a.prototype.render=function(){var a,b;return this.textField&&this.textField.text(m(this.value)),0===this.maxValue&&(this.maxValue=2*this.avgValue),b=this.value/this.maxValue*100,a=this.avgValue/this.maxValue*100,$(".bar-value",this.elem).css({width:b+"%"}),$(".typical-value",this.elem).css({width:a+"%"})},a}(),g=function(a){function b(a){this.canvas=a,b.__super__.constructor.call(this),this.percentColors=null,"undefined"!=typeof G_vmlCanvasManager&&(this.canvas=window.G_vmlCanvasManager.initElement(this.canvas)),this.ctx=this.canvas.getContext("2d"),this.gp=[new h(this)],this.setOptions(),this.render()}return r(b,a),b.prototype.elem=null,b.prototype.value=[20],b.prototype.maxValue=80,b.prototype.minValue=0,b.prototype.displayedAngle=0,b.prototype.displayedValue=0,b.prototype.lineWidth=40,b.prototype.paddingBottom=.1,b.prototype.percentColors=null,b.prototype.options={colorStart:"#6fadcf",colorStop:void 0,gradientType:0,strokeColor:"#e0e0e0",pointer:{length:.8,strokeWidth:.035},angle:.15,lineWidth:.44,fontSize:40,limitMax:!1},b.prototype.setOptions=function(a){var c,d,e,f;for(null==a&&(a=null),b.__super__.setOptions.call(this,a),this.configPercentColors(),this.lineWidth=this.canvas.height*(1-this.paddingBottom)*this.options.lineWidth,this.radius=this.canvas.height*(1-this.paddingBottom)-this.lineWidth,this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.render(),f=this.gp,d=0,e=f.length;e>d;d++)c=f[d],c.setOptions(this.options.pointer),c.render();return this},b.prototype.configPercentColors=function(){var a,b,c,d,e,f,g;if(this.percentColors=null,void 0!==this.options.percentColors){for(this.percentColors=new Array,f=[],c=d=0,e=this.options.percentColors.length-1;e>=0?e>=d:d>=e;c=e>=0?++d:--d)g=parseInt(l(this.options.percentColors[c][1]).substring(0,2),16),b=parseInt(l(this.options.percentColors[c][1]).substring(2,4),16),a=parseInt(l(this.options.percentColors[c][1]).substring(4,6),16),f.push(this.percentColors[c]={pct:this.options.percentColors[c][0],color:{r:g,g:b,b:a}});return f}},b.prototype.set=function(a){var b,c,d,e,f,g,i;if(this.displayedValue=this.minValue,a instanceof Array||(a=[a]),a.length>this.gp.length)for(b=c=0,g=a.length-this.gp.length;g>=0?g>c:c>g;b=g>=0?++c:--c)this.gp.push(new h(this));for(b=0,f=!1,d=0,e=a.length;e>d;d++)i=a[d],i>this.maxValue&&(this.maxValue=1.1*this.value,f=!0),this.gp[b].value=i,this.gp[b++].setOptions({maxValue:this.maxValue,angle:this.options.angle});return this.value=a[a.length-1],f&&this.options.limitMax?void 0:AnimationUpdater.run()},b.prototype.getAngle=function(a){return(1+this.options.angle)*Math.PI+(a-this.minValue)/(this.maxValue-this.minValue)*(1-2*this.options.angle)*Math.PI},b.prototype.getColorForPercentage=function(a,b){var c,d,e,f,g,h,i;if(0===a)c=this.percentColors[0].color;else for(c=this.percentColors[this.percentColors.length-1].color,e=f=0,h=this.percentColors.length-1;h>=0?h>=f:f>=h;e=h>=0?++f:--f)if(a<=this.percentColors[e].pct){b===!0?(i=this.percentColors[e-1],d=this.percentColors[e],g=(a-i.pct)/(d.pct-i.pct),c={r:Math.floor(i.color.r*(1-g)+d.color.r*g),g:Math.floor(i.color.g*(1-g)+d.color.g*g),b:Math.floor(i.color.b*(1-g)+d.color.b*g)}):c=this.percentColors[e].color;break}return"rgb("+[c.r,c.g,c.b].join(",")+")"},b.prototype.getColorForValue=function(a,b){var c;return c=(a-this.minValue)/(this.maxValue-this.minValue),this.getColorForPercentage(c,b)},b.prototype.render=function(){var a,b,c,d,e,f,g,h,i;for(i=this.canvas.width/2,d=this.canvas.height*(1-this.paddingBottom),a=this.getAngle(this.displayedValue),this.textField&&this.textField.render(this),this.ctx.lineCap="butt",void 0!==this.options.customFillStyle?b=this.options.customFillStyle(this):null!==this.percentColors?b=this.getColorForValue(this.displayedValue,!0):void 0!==this.options.colorStop?(b=0===this.options.gradientType?this.ctx.createRadialGradient(i,d,9,i,d,70):this.ctx.createLinearGradient(0,0,i,0),b.addColorStop(0,this.options.colorStart),b.addColorStop(1,this.options.colorStop)):b=this.options.colorStart,this.ctx.strokeStyle=b,this.ctx.beginPath(),this.ctx.arc(i,d,this.radius,(1+this.options.angle)*Math.PI,a,!1),this.ctx.lineWidth=this.lineWidth,this.ctx.stroke(),this.ctx.strokeStyle=this.options.strokeColor,this.ctx.beginPath(),this.ctx.arc(i,d,this.radius,a,(2-this.options.angle)*Math.PI,!1),this.ctx.stroke(),g=this.gp,h=[],e=0,f=g.length;f>e;e++)c=g[e],h.push(c.update(!0));return h},b}(e),d=function(a){function b(a){this.canvas=a,b.__super__.constructor.call(this),"undefined"!=typeof G_vmlCanvasManager&&(this.canvas=window.G_vmlCanvasManager.initElement(this.canvas)),this.ctx=this.canvas.getContext("2d"),this.setOptions(),this.render()}return r(b,a),b.prototype.lineWidth=15,b.prototype.displayedValue=0,b.prototype.value=33,b.prototype.maxValue=80,b.prototype.minValue=0,b.prototype.options={lineWidth:.1,colorStart:"#6f6ea0",colorStop:"#c0c0db",strokeColor:"#eeeeee",shadowColor:"#d5d5d5",angle:.35},b.prototype.getAngle=function(a){return(1-this.options.angle)*Math.PI+(a-this.minValue)/(this.maxValue-this.minValue)*(2+this.options.angle-(1-this.options.angle))*Math.PI},b.prototype.setOptions=function(a){return null==a&&(a=null),b.__super__.setOptions.call(this,a),this.lineWidth=this.canvas.height*this.options.lineWidth,this.radius=this.canvas.height/2-this.lineWidth/2,this},b.prototype.set=function(a){return this.value=a,this.value>this.maxValue&&(this.maxValue=1.1*this.value),AnimationUpdater.run()},b.prototype.render=function(){var a,b,c,d,e,f;return a=this.getAngle(this.displayedValue),f=this.canvas.width/2,c=this.canvas.height/2,this.textField&&this.textField.render(this),b=this.ctx.createRadialGradient(f,c,39,f,c,70),b.addColorStop(0,this.options.colorStart),b.addColorStop(1,this.options.colorStop),d=this.radius-this.lineWidth/2,e=this.radius+this.lineWidth/2,this.ctx.strokeStyle=this.options.strokeColor,this.ctx.beginPath(),this.ctx.arc(f,c,this.radius,(1-this.options.angle)*Math.PI,(2+this.options.angle)*Math.PI,!1),this.ctx.lineWidth=this.lineWidth,this.ctx.lineCap="round",this.ctx.stroke(),this.ctx.strokeStyle=b,this.ctx.beginPath(),this.ctx.arc(f,c,this.radius,(1-this.options.angle)*Math.PI,a,!1),this.ctx.stroke()},b}(e),f=function(a){function b(){return b.__super__.constructor.apply(this,arguments)}return r(b,a),b.prototype.strokeGradient=function(a,b,c,d){var e;return e=this.ctx.createRadialGradient(a,b,c,a,b,d),e.addColorStop(0,this.options.shadowColor),e.addColorStop(.12,this.options._orgStrokeColor),e.addColorStop(.88,this.options._orgStrokeColor),e.addColorStop(1,this.options.shadowColor),e},b.prototype.setOptions=function(a){var c,d,e,f;return null==a&&(a=null),b.__super__.setOptions.call(this,a),f=this.canvas.width/2,c=this.canvas.height/2,d=this.radius-this.lineWidth/2,e=this.radius+this.lineWidth/2,this.options._orgStrokeColor=this.options.strokeColor,this.options.strokeColor=this.strokeGradient(f,c,d,e),this},b}(d),window.AnimationUpdater={elements:[],animId:null,addAll:function(a){var b,c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(AnimationUpdater.elements.push(b));return e},add:function(a){return AnimationUpdater.elements.push(a)},run:function(){var a,b,c,d,e;for(a=!0,e=AnimationUpdater.elements,c=0,d=e.length;d>c;c++)b=e[c],b.update()&&(a=!1);return a?cancelAnimationFrame(AnimationUpdater.animId):AnimationUpdater.animId=requestAnimationFrame(AnimationUpdater.run)}},"function"==typeof window.define&&null!=window.define.amd?define(function(){return{Gauge:g,Donut:f,BaseDonut:d,TextRenderer:i}}):"undefined"!=typeof module&&null!=module.exports?module.exports={Gauge:g,Donut:f,BaseDonut:d,TextRenderer:i}:(window.Gauge=g,window.Donut=f,window.BaseDonut=d,window.TextRenderer=i)}).call(this); -------------------------------------------------------------------------------- /physics.js: -------------------------------------------------------------------------------- 1 | /** 2 | * physics.js 3 | * 4 | * @fileoverview Manages all physics related features, including forces, 5 | * displacements, and collision detection. 6 | * @author Po-Han Huang 7 | */ 8 | 9 | /** Velocity of airplane */ 10 | var velocity = vec3.create(); 11 | 12 | /** Current thrust */ 13 | var thrust = 0; 14 | /** Whether reverse thrust is turned on */ 15 | var reverse = false; 16 | /** Whether airplane is on the ground (as opposed to being in the air). */ 17 | var ground = true; 18 | /** Whether the game is stil playing (as opposed to gameover or winning). */ 19 | var playing = true; 20 | 21 | /** Gravity */ 22 | var GRAVITY = 0.002; 23 | /** Drag force coefficient (see getDragForce()) */ 24 | var DRAG_FORCE_COEFF = 0.0883883; 25 | /** Drag force power (see getDragForce()) */ 26 | var DRAG_FORCE_POWER = 1.5; 27 | /** Drag force threshold (see getDragForce()) */ 28 | var DRAG_FORCE_THRESHOLD = 0.000001; 29 | /** Lift force coefficient (see getLiftForce()) */ 30 | var LIFT_FORCE_COEFF = 0.1; 31 | /** Lift force multipier according to pitch keypresses. (see getLiftForce()) */ 32 | var LIFT_PITCH_COEFF = [0.4,1.0,1.6]; 33 | /** Acceleration coefficient (see getThrustForce()) */ 34 | var ACCELERATION_COEFF = 1.2e-3; 35 | /** Roll change rate in reponse to roll keypresses. */ 36 | var ROLL_CHANGE_RATE = 2.0e-3; 37 | /** Yaw change rate in reponse to roll keypresses. */ 38 | var YAW_CHANGE_RATE = 0.1; 39 | 40 | /** Velocity threshold to judge whether crashing on land. */ 41 | var CRASH_ON_LAND_VELOCITY_THRESHOLD = 0.025; 42 | /** Pitch threshold to judge whether crashing on land. */ 43 | var CRASH_ON_LAND_PITCH_THRESHOLD = degToRad(20); 44 | /** Roll threshold to judge whether crashing on land. */ 45 | var CRASH_ON_LAND_ROLL_THRESHOLD = degToRad(20); 46 | /** Threshold to judge whether crashing into the mountains */ 47 | var CRASH_ON_MOUNTAIN_THRESHOLD = 0.01; 48 | /** Threshold to judge whether crashing into the walls. */ 49 | var CRASH_ON_WALL_THRESHOLD = 0.1; 50 | 51 | /** Initialization of hysics.js */ 52 | function physicsInit(){ 53 | 54 | } 55 | 56 | /** Update physics data. 57 | * @param {float} lapse timelapse since last frame in sec 58 | */ 59 | function physicsUpdate(lapse){ 60 | 61 | physicsUpdatePosition(lapse); 62 | physicsUpdateVelocity(lapse); 63 | 64 | physicsCheckCrashes(); 65 | physicsCheckGround(); 66 | 67 | physicsUpdateRoll(lapse); 68 | physicsUpdateLookAt(); 69 | 70 | } 71 | 72 | /** Update airplane location based on velocity. 73 | * @param {float} lapse timelapse since last frame in sec 74 | */ 75 | function physicsUpdatePosition(lapse){ 76 | var viewOriginChange = vec3.clone(velocity); 77 | vec3.scale(viewOriginChange, viewOriginChange, lapse); 78 | vec3.add(viewOrigin, viewOrigin, viewOriginChange); 79 | } 80 | 81 | /** Update airplane velocity based on forces. 82 | * @param {float} lapse timelapse since last frame in sec 83 | */ 84 | function physicsUpdateVelocity(lapse){ 85 | 86 | /** Apply lift force (along z-axis and left-right direction). */ 87 | var liftVelocityChange = vec3.create(); 88 | var liftDirection = vec3.fromValues(viewLookAt[0],viewLookAt[1],0.0); 89 | vec3.normalize(liftDirection, liftDirection); 90 | vec3.scale(liftDirection, liftDirection, vec3.dot(liftDirection, viewUp)); 91 | vec3.subtract(liftDirection, viewUp, liftDirection); 92 | vec3.normalize(liftDirection, liftDirection); 93 | vec3.scale(liftVelocityChange, liftDirection, getLiftForce()*lapse); 94 | 95 | /** Apply thrust force (along lookAt direction). */ 96 | var thrustVelocityChange = vec3.create(); 97 | vec3.scale(thrustVelocityChange, viewLookAt, getThrustForce()*lapse); 98 | 99 | /** Apply drag force (along negative lookAt direction). */ 100 | var dragVelocityChange = vec3.create(); 101 | vec3.scale(dragVelocityChange, viewLookAt, -getDragForce()*lapse); 102 | 103 | /** Apply gravity (along z-axis). */ 104 | velocity[2] -= GRAVITY * lapse; 105 | 106 | vec3.add(velocity, velocity, thrustVelocityChange); 107 | vec3.add(velocity, velocity, liftVelocityChange); 108 | vec3.add(velocity, velocity, dragVelocityChange); 109 | 110 | } 111 | 112 | /** Update lookAt direction to match velocity direction. */ 113 | function physicsUpdateLookAt(){ 114 | 115 | /** Quaternion change to apply. */ 116 | var quatChange = quat.create(); 117 | /** Velocity direction. */ 118 | var normalizedVelocity = vec3.create(); 119 | 120 | /** Update only when velocity is not zero. */ 121 | if(vec3.length(velocity) >= 0.000001){ 122 | 123 | vec3.normalize(normalizedVelocity, velocity); 124 | 125 | /** If moving backwards, match lookAt to opposite of velocity direction. */ 126 | if(vec3.dot(viewLookAt, normalizedVelocity) < 0){ 127 | vec3.negate(normalizedVelocity, normalizedVelocity); 128 | } 129 | 130 | /** Apply quaternion change. */ 131 | quat.rotationTo(quatChange, viewLookAt, normalizedVelocity); 132 | quat.multiply(viewQuat, quatChange, viewQuat); 133 | quat.normalize(viewQuat, viewQuat); 134 | 135 | } 136 | 137 | /** If on the ground, eliminate roll. */ 138 | if(ground){ 139 | vec3.transformQuat(viewUp, VIEW_UP_INIT, viewQuat); 140 | vec3.transformQuat(viewLookAt, VIEW_LOOKAT_INIT, viewQuat); 141 | vec3.transformQuat(viewRight, VIEW_RIGHT_INIT, viewQuat); 142 | quat.setAxisAngle(quatChange, viewLookAt, -getRoll()); 143 | quat.multiply(viewQuat, quatChange, viewQuat); 144 | quat.normalize(viewQuat, viewQuat); 145 | } 146 | 147 | } 148 | 149 | /** Check if airplane is on the ground. */ 150 | function physicsCheckGround(){ 151 | 152 | /** If was on the ground... */ 153 | if(ground){ 154 | 155 | /** If vertical velocity is positive (i.e. gravity is overcome), fly! */ 156 | if(velocity[2] > 0.0){ 157 | ground = false; 158 | }else 159 | /** If airplane is still on the land, clear vertical velocity and set 160 | * airplane location right on the ground (not under the ground). 161 | */ 162 | if(physicsCheckAboveLand()){ 163 | velocity[2] = 0; 164 | viewOrigin[2] = AIRPLANE_HEIGHT; 165 | }else 166 | { 167 | /** If airplane is NOT on the land, fall into the ocean. */ 168 | ground = false; 169 | } 170 | 171 | }else{ 172 | 173 | /** If was in the air, check if airplane has landed. */ 174 | if(physicsCheckAboveLand() && viewOrigin[2] <= AIRPLANE_HEIGHT){ 175 | velocity[2] = 0; 176 | viewOrigin[2] = AIRPLANE_HEIGHT; 177 | ground = true; 178 | } 179 | 180 | } 181 | } 182 | 183 | /** Helper functino to check whether airplane is above the land or above the 184 | * ocean. 185 | */ 186 | function physicsCheckAboveLand(){ 187 | return viewOrigin[0] >= -1.0 && viewOrigin[0] < 1.0 && viewOrigin[1] >= -1.0 && viewOrigin[1] < 1.0; 188 | } 189 | 190 | /** Check if gameover or winning. */ 191 | function physicsCheckCrashes(){ 192 | physicsCheckCrashOcean(); 193 | physicsCheckCrashLand(); 194 | physicsCheckCrashMountain(); 195 | physicsCheckCrashWall(); 196 | physicsCheckCrashObstacle(); 197 | physicsCheckNoFuel(); 198 | physicsCheckWin(); 199 | } 200 | 201 | /** Check if airplane crashes into ocean. */ 202 | function physicsCheckCrashOcean(){ 203 | if(!physicsCheckAboveLand() && (viewOrigin[2] <= 0.0 || airplaneTip[2] <= 0.0)){ 204 | viewOrigin[2] = AIRPLANE_HEIGHT; 205 | gameover("GAMEOVER! You crashed into the ocean. Press R to restart."); 206 | } 207 | } 208 | 209 | /** Check if airplane crashes on land. */ 210 | function physicsCheckCrashLand(){ 211 | if(ground === false && viewOrigin[2] <= AIRPLANE_HEIGHT && 212 | (velocity[2] <= -CRASH_ON_LAND_VELOCITY_THRESHOLD || 213 | Math.abs(getRoll()) >= CRASH_ON_LAND_ROLL_THRESHOLD || 214 | Math.abs(getPitch()) >= CRASH_ON_LAND_PITCH_THRESHOLD ) ){ 215 | gameover("GAMEOVER! You crashed on land. Press R to restart."); 216 | } 217 | } 218 | 219 | /** Check if airplane crashes into mountains. */ 220 | function physicsCheckCrashMountain(){ 221 | if(terrainReady && physicsCheckAboveLand()){ 222 | 223 | /** Distance between adjacent terrain vertices */ 224 | var stride = 2.0 / (TERRAIN_SIZE - 1); 225 | /** Row and column index airplane is at */ 226 | var col = Math.floor((airplaneTip[0] + 1.0) / stride); 227 | var row = Math.floor((airplaneTip[1] + 1.0) / stride); 228 | /** X and Y distances to vertex */ 229 | var dx = (airplaneTip[0] + 1.0) / stride - col; 230 | var dy = (airplaneTip[1] + 1.0) / stride - row; 231 | 232 | /** Calculate the height of terrain where airplane is at. */ 233 | var gradientX; 234 | var gradientY; 235 | var z; 236 | 237 | /** Check lower or upper triangle. */ 238 | if(dx + dy <= 1.0){ 239 | gradientX = terrainPositionArray[terrainIndex(row, col+1) + 2] - terrainPositionArray[terrainIndex(row, col) + 2]; 240 | gradientY = terrainPositionArray[terrainIndex(row+1, col) + 2] - terrainPositionArray[terrainIndex(row, col) + 2]; 241 | z = terrainPositionArray[terrainIndex(row, col) + 2] + dx * gradientX + dy * gradientY; 242 | }else{ 243 | gradientX = terrainPositionArray[terrainIndex(row+1, col+1) + 2] - terrainPositionArray[terrainIndex(row+1, col) + 2]; 244 | gradientY = terrainPositionArray[terrainIndex(row+1, col+1) + 2] - terrainPositionArray[terrainIndex(row, col+1) + 2]; 245 | z = terrainPositionArray[terrainIndex(row+1, col+1) + 2] + (1.0 - dx) * gradientX + (1.0 - dy) * gradientY; 246 | } 247 | 248 | /** Check if airplane is below terrain. */ 249 | if(airplaneTip[2] < z - CRASH_ON_MOUNTAIN_THRESHOLD){ 250 | gameover("GAMEOVER! You crashed into the mountains. Press R to restart."); 251 | } 252 | } 253 | } 254 | 255 | /** Check if airplane tries to bypass mountains. */ 256 | function physicsCheckCrashWall(){ 257 | if(( viewOrigin[0] < -(1.0 + CRASH_ON_WALL_THRESHOLD) || 258 | viewOrigin[0] > 1.0 + CRASH_ON_WALL_THRESHOLD ) 259 | && viewOrigin[1] < 0.0 && airplaneTip[1] > 0.0){ 260 | gameover("GAMEOVER! You cannot fly by the mountains. Press R to restart."); 261 | } 262 | } 263 | 264 | /** Check if airplane crashes into obstacles. */ 265 | function physicsCheckCrashObstacle(){ 266 | 267 | /** Check only when obstacles are enabled. */ 268 | if(obstacleLevel !== 0){ 269 | 270 | var collision = false; 271 | 272 | /** Iterate through all obstacles. */ 273 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 274 | 275 | /** Assume obstacle is cylinder with radius r and height h. */ 276 | var r = 0.3 * OBSTACLE_SCALE[0]; 277 | var h = 1.1 * OBSTACLE_SCALE[1]; 278 | /** Location of obstacle */ 279 | var o = vec3.clone(obstacleOrigin[i]); 280 | /** Get the two center coordinates of the two bottoms of cylinder. */ 281 | var angle = obstacleAngle[i]; 282 | var a = vec3.fromValues(o[0] - h * Math.sin(angle), o[1] + h * Math.cos(angle), o[2]); 283 | var b = vec3.fromValues(o[0] + h * Math.sin(angle), o[1] - h * Math.cos(angle), o[2]); 284 | /** Get coordinates of airplane tip. */ 285 | var x = vec3.clone(airplaneTip); 286 | 287 | /** Check if x is in the cylinder. */ 288 | var ba = vec3.create(); 289 | vec3.subtract(ba, b, a); 290 | vec3.normalize(ba, ba); 291 | var xa = vec3.create(); 292 | vec3.subtract(xa, x, a); 293 | var d = vec3.create(); 294 | vec3.cross(d, xa, ba); 295 | 296 | if(vec3.dot(xa,ba) > 0 && vec3.dot(xa,ba) < h*2 && vec3.length(d) < r){ 297 | collision = true; 298 | } 299 | 300 | } 301 | 302 | /** If there is collision, gameover. */ 303 | if(collision){ 304 | gameover("GAMEOVER! You crashed into another aircraft. Press R to restart."); 305 | } 306 | } 307 | } 308 | 309 | /** Check if timeout. */ 310 | function physicsCheckNoFuel(){ 311 | if(Date.now() - startTime >= TIMEOUT){ 312 | gameover("GAMEOVER! You ran out of fuel. Press R to restart."); 313 | } 314 | } 315 | 316 | /** Check if wins. */ 317 | function physicsCheckWin(){ 318 | if(ground && viewOrigin[0] >= -0.003 && viewOrigin[0] <= 0.003 319 | && viewOrigin[1] >= 0.9 && viewOrigin[1] <= 1.0 320 | && getHorizontalVelocity() <= 0.002){ 321 | gameover("You WIN!!!!!!! Congratulations!"); 322 | } 323 | } 324 | 325 | /** Update roll or yaw in reaction to keypresses. */ 326 | function physicsUpdateRoll(lapse){ 327 | 328 | /** If in the air, modify roll. */ 329 | if(!ground){ 330 | var quatChange = quat.create(); 331 | if (currentlyPressedKeys[37] || currentlyPressedKeys[65]) { 332 | /** Roll leftwards when Left-arrow or A is pressed. */ 333 | quat.setAxisAngle(quatChange, viewLookAt, -ROLL_CHANGE_RATE); 334 | } else if (currentlyPressedKeys[39] || currentlyPressedKeys[68]) { 335 | /** Roll rightwards when Right-arrow or D is pressed. */ 336 | quat.setAxisAngle(quatChange, viewLookAt, ROLL_CHANGE_RATE); 337 | } 338 | quat.multiply(viewQuat, quatChange, viewQuat); 339 | quat.normalize(viewQuat, viewQuat); 340 | }else{ 341 | /** If on the ground, modify yaw. */ 342 | if (currentlyPressedKeys[37] || currentlyPressedKeys[65]) { 343 | /** Turn left when Left-arrow or A is pressed. */ 344 | vec3.rotateZ(velocity, velocity, vec3.create(), YAW_CHANGE_RATE*lapse); 345 | } else if (currentlyPressedKeys[39] || currentlyPressedKeys[68]) { 346 | /** Turn rigt when Right-arrow or D is pressed. */ 347 | vec3.rotateZ(velocity, velocity, vec3.create(), -YAW_CHANGE_RATE*lapse); 348 | } 349 | } 350 | 351 | } 352 | 353 | /** Calculate lift force. 354 | * @return {float} liftForce 355 | */ 356 | function getLiftForce(){ 357 | 358 | /** Lift force = KEY_COEFF * LIFT_FORCE_COEFF * horizontal velocity */ 359 | 360 | if (currentlyPressedKeys[38] || currentlyPressedKeys[87]) { 361 | /** More lift force when Up-arrow or W is pressed. */ 362 | return LIFT_PITCH_COEFF[2] * LIFT_FORCE_COEFF * getHorizontalVelocity(); 363 | }else if (currentlyPressedKeys[40] || currentlyPressedKeys[83]) { 364 | /** Less lift force when Down-arrow or S is pressed. */ 365 | return LIFT_PITCH_COEFF[0] * LIFT_FORCE_COEFF * getHorizontalVelocity(); 366 | }else{ 367 | /** Default lift force. */ 368 | return LIFT_PITCH_COEFF[1] * LIFT_FORCE_COEFF * getHorizontalVelocity(); 369 | } 370 | 371 | } 372 | 373 | /** Calculate thrust force. 374 | * @return {float} thrustForce 375 | */ 376 | function getThrustForce(){ 377 | 378 | /** Thrust force = thrust * ACCELERATION_COEFF */ 379 | 380 | return thrust * ACCELERATION_COEFF; 381 | } 382 | 383 | /** Calculate drag force. 384 | * @return {float} dragForce 385 | */ 386 | function getDragForce(){ 387 | 388 | /** Drag force = DRAG_FORCE_COEFF * (velocity)^(DRAG_FORCE_POWER) */ 389 | 390 | /** If velocity if too small, no drag force. */ 391 | if(vec3.length(velocity) > DRAG_FORCE_THRESHOLD){ 392 | return DRAG_FORCE_COEFF * Math.pow(vec3.length(velocity), DRAG_FORCE_POWER); 393 | }else{ 394 | return 0; 395 | } 396 | } 397 | 398 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Po-Han Huang's Flight Simulator 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 |

Instructions: (Click anywhere to close this page)

36 |
    37 |
  • Goal: Fly over the mountains and land on the runway on the other side.

  • 38 |
  • Operations: (The keys inside parenthesis are for the left-handed.) 39 |
      40 |
    • Use the mouse on the thrust slider to control thrust force (moving forward).
      41 | (Thrust can also be controlled with keys X (L) and Z (K))

    • 42 |
    • Press Up-arrow (W) and Down-arrow (S) to control Pitch. It only works when the speed is sufficiently fast to overcome gravity.

    • 43 |
    • Press Left-arrow (A) and Right-arrow (D) to control Roll. It only works in the air. When the plane is on the ground, Yaw (Direction) is changed instead.

    • 44 |
    • Press C (J) or click on the Toggle Reverse button to enable reverse thrust (used to decelerate after landing).

    • 45 |
    • Press R to restart.

    • 46 |
    47 |
  • 48 |
  • Useful hints: 49 |
      50 |
    • At takeoff:

      51 |
        52 |
      1. Use full thrust (100%). Hold "Up-arrow (W)" key until the plane finally leaves the ground.

      2. 53 |
      3. Control the Pitch to be about +20 deg. Climb to height at about 5000 ft.

      4. 54 |
      5. Make a big U-turn by adjusting Roll, and you'll see the beautiful mountains.

      6. 55 |
      56 |
    • 57 |
    • In the air:

      58 |
        59 |
      1. Reduce the thrust such that the velocity is around 200 knots. (This is the steady state.)

      2. 60 |
      3. Adjust Pitch to get to desired Height. Be aware not to hit the mountains.

      4. 61 |
      5. Right after you pass the mountains, start decreasing hieght and speed to prepare for landing.

      6. 62 |
      63 |
    • 64 |
    • Landing:

      65 |
        66 |
      1. Make a big U-turn again. Aim the runway from afar.

      2. 67 |
      3. Decrease height by keeping Pitch at about -10 deg.

      4. 68 |
      5. When the height is close to zero, control Pitch at around 0 deg carefully so that you don't crash into the ocean. If you find that you cannot lift the pitch, that means your airspeed is too slow. (140-160 knots are good values.)

      6. 69 |
      7. If you touch the gorund too hard or with large pitch or roll, you will crash.

      8. 70 |
      71 |
    • 72 |
    • After touchdown:

      73 |
        74 |
      1. Make the thrust -100% to decelerate.

      2. 75 |
      3. Control direction so that you don't crash into the mountains.

      4. 76 |
      5. It's fine if you don't land exactly on the runway. You can cruise to runway after deceleration.

      6. 77 |
      7. The winning condition is such that you stop on the runway.

      8. 78 |
      79 |
    • 80 |
    81 |
  • 82 |
83 |

Finally, practice makes perfect!

84 |
85 |
86 | 87 | 88 |
89 | 90 |
91 | 92 | 93 |
94 | 95 |

Welcome to Po-Han Huang's flight simulator!

96 | 97 | 98 |
99 | Obstacles: 100 |
101 | None 102 | Static 103 | Moving 104 |
105 | Click here for useful instructions! 106 |
107 | 108 | 109 |

Status:

110 | 111 | 112 |
113 |
114 |

Thrust

115 |
116 |

117 | 118 |
119 |
120 |

Height

121 |
122 |

123 |
124 |
125 |

Pitch

126 |
127 |

128 |
129 |
130 |
131 |

Roll

132 |
133 |

134 |
135 |
136 |

Horizontal Velocity

137 | 138 |

139 |
140 |
141 |
142 |

Position and Orientation

143 |
144 | 145 | 146 |
147 |
148 |
149 |
150 |
151 |

Fuel

152 | 153 |

154 |
155 |
156 |
157 | 158 |
159 | 160 | 161 | 327 | 328 | 342 | 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /misc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * misc.js 3 | * 4 | * @fileoverview Manages miscellaneous objects, including runways, oceans, and 5 | * the walls. 6 | * @author Po-Han Huang 7 | */ 8 | 9 | /** Identification prefix for misc shader. */ 10 | var MISC_PREFIX = "misc"; 11 | 12 | /** GL buffers for misc. */ 13 | var miscPositionBuffer; 14 | var miscColorBuffer; 15 | var miscNormalBuffer; 16 | 17 | /** Data array for misc objects. */ 18 | var miscPositionArray; 19 | var miscColorArray; 20 | var miscNormalArray; 21 | 22 | /** Shader program for misc. */ 23 | var miscShaderProgram; 24 | /** Variable locations in shader program. */ 25 | var miscLocations = {}; 26 | 27 | /** Runway colors */ 28 | var MISC_RUNWAY_BLACK = vec3.fromValues(0.2,0.2,0.2); 29 | var MISC_RUNWAY_YELLOW = vec3.fromValues(1.0,0.9,0.3); 30 | var MISC_RUNWAY_WHITE = vec3.fromValues(1.0,1.0,1.0); 31 | /** Runway height to avoid z-fighting */ 32 | var MISC_RUNWAY_Z = 0.00001; 33 | 34 | /** Ocean and wall colors */ 35 | var MISC_OCEAN_BLUE = vec3.fromValues(0.0,0.1,0.4); 36 | var MISC_WALL_COLOR = vec3.fromValues(0.0,0.3,0.0); 37 | 38 | /** Initialization of misc.js */ 39 | function miscInit(){ 40 | 41 | /** Register shaders, draw calls, animate calls. */ 42 | var prefix = "misc"; 43 | shaderPrefix.push(prefix); 44 | shaderInit[prefix] = miscShaderInit; 45 | bufferInit[prefix] = miscBufferInit; 46 | drawFunctions[prefix] = miscDraw; 47 | animateFunctions[prefix] = miscAnimate; 48 | 49 | /** Initialize arrays. */ 50 | miscPositionArray = []; 51 | miscColorArray = []; 52 | miscNormalArray = []; 53 | 54 | /** Initialize shapes. */ 55 | miscGenerateShape(); 56 | } 57 | 58 | /** Initialize misc's shader programs and variable locations. */ 59 | function miscShaderInit(){ 60 | miscShaderProgram = shaderPrograms[MISC_PREFIX]; 61 | 62 | /** Attributes */ 63 | miscLocations["aVertexPosition"] = gl.getAttribLocation(miscShaderProgram, "aVertexPosition"); 64 | gl.enableVertexAttribArray(miscLocations["aVertexPosition"]); 65 | 66 | miscLocations["aVertexColor"] = gl.getAttribLocation(miscShaderProgram, "aVertexColor"); 67 | gl.enableVertexAttribArray(miscLocations["aVertexColor"]); 68 | 69 | miscLocations["aVertexNormal"] = gl.getAttribLocation(miscShaderProgram, "aVertexNormal"); 70 | gl.enableVertexAttribArray(miscLocations["aVertexNormal"]); 71 | 72 | /** Uniforms */ 73 | miscLocations["uPMatrix"] = gl.getUniformLocation(miscShaderProgram, "uPMatrix"); 74 | miscLocations["uMVMatrix"] = gl.getUniformLocation(miscShaderProgram, "uMVMatrix"); 75 | 76 | miscLocations["uViewOrigin"] = gl.getUniformLocation(miscShaderProgram, "uViewOrigin"); 77 | miscLocations["uLightDirection"] = gl.getUniformLocation(miscShaderProgram, "uLightDirection"); 78 | miscLocations["uAmbientLight"] = gl.getUniformLocation(miscShaderProgram, "uAmbientLight"); 79 | miscLocations["uDiffuseLight"] = gl.getUniformLocation(miscShaderProgram, "uDiffuseLight"); 80 | miscLocations["uSpecularLight"] = gl.getUniformLocation(miscShaderProgram, "uSpecularLight"); 81 | } 82 | 83 | /** Initialize misc's buffer. */ 84 | function miscBufferInit(){ 85 | 86 | /** Create buffers. */ 87 | miscPositionBuffer = gl.createBuffer(); 88 | miscColorBuffer = gl.createBuffer(); 89 | miscNormalBuffer = gl.createBuffer(); 90 | 91 | /** Bind buffers. */ 92 | miscPositionBuffer.itemSize = 3; 93 | miscPositionBuffer.numOfItems = miscPositionArray.length / miscPositionBuffer.itemSize; 94 | gl.bindBuffer(gl.ARRAY_BUFFER, miscPositionBuffer); 95 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(miscPositionArray), gl.STATIC_DRAW); 96 | 97 | miscColorBuffer.itemSize = 3; 98 | miscColorBuffer.numOfItems = miscColorArray.length / miscColorBuffer.itemSize; 99 | gl.bindBuffer(gl.ARRAY_BUFFER, miscColorBuffer); 100 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(miscColorArray), gl.STATIC_DRAW); 101 | 102 | miscNormalBuffer.itemSize = 3; 103 | miscNormalBuffer.numOfItems = miscNormalArray.length / miscNormalBuffer.itemSize; 104 | gl.bindBuffer(gl.ARRAY_BUFFER, miscNormalBuffer); 105 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(miscNormalArray), gl.STATIC_DRAW); 106 | 107 | } 108 | 109 | /** Misc draw call */ 110 | function miscDraw(){ 111 | 112 | /** Setup variables. */ 113 | gl.useProgram(miscShaderProgram); 114 | 115 | gl.bindBuffer(gl.ARRAY_BUFFER, miscPositionBuffer); 116 | gl.vertexAttribPointer(miscLocations["aVertexPosition"], miscPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); 117 | 118 | gl.bindBuffer(gl.ARRAY_BUFFER, miscColorBuffer); 119 | gl.vertexAttribPointer(miscLocations["aVertexColor"], miscColorBuffer.itemSize, gl.FLOAT, false, 0, 0); 120 | 121 | gl.bindBuffer(gl.ARRAY_BUFFER, miscNormalBuffer); 122 | gl.vertexAttribPointer(miscLocations["aVertexNormal"], miscNormalBuffer.itemSize, gl.FLOAT, false, 0, 0); 123 | 124 | gl.uniformMatrix4fv(miscLocations["uPMatrix"], false, pMatrix); 125 | gl.uniformMatrix4fv(miscLocations["uMVMatrix"], false, mvMatrix); 126 | 127 | gl.uniform3fv(miscLocations["uViewOrigin"], viewOrigin); 128 | gl.uniform3fv(miscLocations["uLightDirection"], LIGHT_DIRECTION); 129 | gl.uniform3fv(miscLocations["uAmbientLight"], AMBIENT_LIGHT); 130 | gl.uniform3fv(miscLocations["uDiffuseLight"], DIFFUSE_LIGHT); 131 | gl.uniform3fv(miscLocations["uSpecularLight"], SPECULAR_LIGHT); 132 | 133 | /** Draw! */ 134 | gl.drawArrays(gl.TRIANGLES, 0, miscPositionBuffer.numOfItems); 135 | } 136 | 137 | /** Misc animate call 138 | * 139 | * @param {float} lapse timelapse since last frame in sec 140 | */ 141 | function miscAnimate(lapse){ 142 | 143 | } 144 | 145 | /** Generate misc shapes. */ 146 | function miscGenerateShape(){ 147 | miscGenerateRunway(); 148 | miscGenerateOcean(); 149 | miscGenerateWall(); 150 | } 151 | 152 | /** Generate runways. */ 153 | function miscGenerateRunway(){ 154 | 155 | /** Runway at departure */ 156 | 157 | /** Left black border */ 158 | miscPushVertex(-0.0020, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 159 | miscPushVertex(-0.0019, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 160 | miscPushVertex(-0.0020, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 161 | miscPushVertex(-0.0019, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 162 | miscPushVertex(-0.0019, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 163 | miscPushVertex(-0.0020, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 164 | 165 | /** Left white line */ 166 | miscPushVertex(-0.0019, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 167 | miscPushVertex(-0.0018, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 168 | miscPushVertex(-0.0019, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 169 | miscPushVertex(-0.0018, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 170 | miscPushVertex(-0.0018, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 171 | miscPushVertex(-0.0019, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 172 | 173 | /** Left black main runway */ 174 | miscPushVertex(-0.0018, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 175 | miscPushVertex(-0.0001, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 176 | miscPushVertex(-0.0018, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 177 | miscPushVertex(-0.0001, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 178 | miscPushVertex(-0.0001, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 179 | miscPushVertex(-0.0018, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 180 | 181 | /** Right black main runway */ 182 | miscPushVertex(0.0001, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 183 | miscPushVertex(0.0018, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 184 | miscPushVertex(0.0001, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 185 | miscPushVertex(0.0018, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 186 | miscPushVertex(0.0018, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 187 | miscPushVertex(0.0001, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 188 | 189 | /** Right white line */ 190 | miscPushVertex(0.0018, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 191 | miscPushVertex(0.0019, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 192 | miscPushVertex(0.0018, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 193 | miscPushVertex(0.0019, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 194 | miscPushVertex(0.0019, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 195 | miscPushVertex(0.0018, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 196 | 197 | /** Right black border */ 198 | miscPushVertex(0.0019, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 199 | miscPushVertex(0.0020, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 200 | miscPushVertex(0.0019, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 201 | miscPushVertex(0.0020, -1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 202 | miscPushVertex(0.0020, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 203 | miscPushVertex(0.0019, -0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 204 | 205 | /** Generate center dashed line. */ 206 | 207 | /** Number of yellow stripes */ 208 | var yellow_total = 100; 209 | var stride = 0.1 / yellow_total; 210 | 211 | for(var i = 0; i < yellow_total; i++){ 212 | var endpoints = [-1.0+i*stride,-1.0+i*stride+stride/2,-1.0+i*stride+stride]; 213 | 214 | /** Black part */ 215 | miscPushVertex(-0.0001, endpoints[0], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 216 | miscPushVertex( 0.0001, endpoints[0], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 217 | miscPushVertex(-0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 218 | miscPushVertex( 0.0001, endpoints[0], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 219 | miscPushVertex( 0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 220 | miscPushVertex(-0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 221 | 222 | /** Yellow part */ 223 | miscPushVertex(-0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 224 | miscPushVertex( 0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 225 | miscPushVertex(-0.0001, endpoints[2], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 226 | miscPushVertex( 0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 227 | miscPushVertex( 0.0001, endpoints[2], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 228 | miscPushVertex(-0.0001, endpoints[2], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 229 | } 230 | 231 | /** Runway at destination */ 232 | 233 | /** Left black border */ 234 | miscPushVertex(-0.0020, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 235 | miscPushVertex(-0.0019, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 236 | miscPushVertex(-0.0020, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 237 | miscPushVertex(-0.0019, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 238 | miscPushVertex(-0.0019, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 239 | miscPushVertex(-0.0020, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 240 | 241 | /** Left white line */ 242 | miscPushVertex(-0.0019, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 243 | miscPushVertex(-0.0018, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 244 | miscPushVertex(-0.0019, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 245 | miscPushVertex(-0.0018, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 246 | miscPushVertex(-0.0018, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 247 | miscPushVertex(-0.0019, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 248 | 249 | /** Left black main runway */ 250 | miscPushVertex(-0.0018, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 251 | miscPushVertex(-0.0001, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 252 | miscPushVertex(-0.0018, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 253 | miscPushVertex(-0.0001, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 254 | miscPushVertex(-0.0001, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 255 | miscPushVertex(-0.0018, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 256 | 257 | /** Right black main runway */ 258 | miscPushVertex(0.0001, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 259 | miscPushVertex(0.0018, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 260 | miscPushVertex(0.0001, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 261 | miscPushVertex(0.0018, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 262 | miscPushVertex(0.0018, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 263 | miscPushVertex(0.0001, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 264 | 265 | /** Right white line */ 266 | miscPushVertex(0.0018, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 267 | miscPushVertex(0.0019, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 268 | miscPushVertex(0.0018, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 269 | miscPushVertex(0.0019, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 270 | miscPushVertex(0.0019, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 271 | miscPushVertex(0.0018, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_WHITE); 272 | 273 | /** Right black border */ 274 | miscPushVertex(0.0019, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 275 | miscPushVertex(0.0020, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 276 | miscPushVertex(0.0019, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 277 | miscPushVertex(0.0020, 0.9, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 278 | miscPushVertex(0.0020, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 279 | miscPushVertex(0.0019, 1.0, MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 280 | 281 | /** Generate center dashed line. */ 282 | 283 | /** Number of yellow stripes */ 284 | var yellow_total = 100; 285 | var stride = 0.1 / yellow_total; 286 | 287 | for(var i = 0; i < yellow_total; i++){ 288 | var endpoints = [0.9+i*stride,0.9+i*stride+stride/2,0.9+i*stride+stride]; 289 | 290 | /** Black part */ 291 | miscPushVertex(-0.0001, endpoints[0], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 292 | miscPushVertex( 0.0001, endpoints[0], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 293 | miscPushVertex(-0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 294 | miscPushVertex( 0.0001, endpoints[0], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 295 | miscPushVertex( 0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 296 | miscPushVertex(-0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_BLACK); 297 | 298 | /** Yellow part */ 299 | miscPushVertex(-0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 300 | miscPushVertex( 0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 301 | miscPushVertex(-0.0001, endpoints[2], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 302 | miscPushVertex( 0.0001, endpoints[1], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 303 | miscPushVertex( 0.0001, endpoints[2], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 304 | miscPushVertex(-0.0001, endpoints[2], MISC_RUNWAY_Z, Z_AXIS, MISC_RUNWAY_YELLOW); 305 | } 306 | 307 | } 308 | 309 | /** Generate ocean. */ 310 | function miscGenerateOcean(){ 311 | 312 | /** South */ 313 | miscPushVertex(-1.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 314 | miscPushVertex( 1.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 315 | miscPushVertex(-1.0, -1.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 316 | miscPushVertex( 1.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 317 | miscPushVertex( 1.0, -1.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 318 | miscPushVertex(-1.0, -1.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 319 | 320 | /** North */ 321 | miscPushVertex(-1.0, 1.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 322 | miscPushVertex( 1.0, 1.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 323 | miscPushVertex(-1.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 324 | miscPushVertex( 1.0, 1.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 325 | miscPushVertex( 1.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 326 | miscPushVertex(-1.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 327 | 328 | /** West */ 329 | miscPushVertex( -100.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 330 | miscPushVertex( -1.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 331 | miscPushVertex( -100.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 332 | miscPushVertex( -1.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 333 | miscPushVertex( -1.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 334 | miscPushVertex( -100.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 335 | 336 | /** East */ 337 | miscPushVertex( 1.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 338 | miscPushVertex( 100.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 339 | miscPushVertex( 1.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 340 | miscPushVertex( 100.0, -100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 341 | miscPushVertex( 100.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 342 | miscPushVertex( 1.0, 100.0, 0.0, Z_AXIS, MISC_OCEAN_BLUE); 343 | } 344 | 345 | /** Generate wall. */ 346 | function miscGenerateWall(){ 347 | 348 | /** West */ 349 | miscPushVertex( -100.0, 0.0, 0.0, Z_AXIS, MISC_WALL_COLOR); 350 | miscPushVertex( -1.0, 0.0, 0.0, Z_AXIS, MISC_WALL_COLOR); 351 | miscPushVertex( -100.0, 0.0, 0.6, Z_AXIS, MISC_WALL_COLOR); 352 | miscPushVertex( -1.0, 0.0, 0.0, Z_AXIS, MISC_WALL_COLOR); 353 | miscPushVertex( -1.0, 0.0, 0.6, Z_AXIS, MISC_WALL_COLOR); 354 | miscPushVertex( -100.0, 0.0, 0.6, Z_AXIS, MISC_WALL_COLOR); 355 | 356 | /** East */ 357 | miscPushVertex( 1.0, 0.0, 0.0, Z_AXIS, MISC_WALL_COLOR); 358 | miscPushVertex( 100.0, 0.0, 0.0, Z_AXIS, MISC_WALL_COLOR); 359 | miscPushVertex( 1.0, 0.0, 0.6, Z_AXIS, MISC_WALL_COLOR); 360 | miscPushVertex( 100.0, 0.0, 0.0, Z_AXIS, MISC_WALL_COLOR); 361 | miscPushVertex( 100.0, 0.0, 0.6, Z_AXIS, MISC_WALL_COLOR); 362 | miscPushVertex( 1.0, 0.0, 0.6, Z_AXIS, MISC_WALL_COLOR); 363 | 364 | } 365 | 366 | /** 367 | * Helper function to push a vertex 368 | * 369 | * @param {float} x 370 | * @param {float} y 371 | * @param {float} z 372 | * @param {vec3|array} normal 373 | * @param {vec3|array} color 374 | */ 375 | function miscPushVertex(x,y,z,normal,color){ 376 | miscPositionArray.push(x); 377 | miscPositionArray.push(y); 378 | miscPositionArray.push(z); 379 | miscNormalArray.push(normal[0]); 380 | miscNormalArray.push(normal[1]); 381 | miscNormalArray.push(normal[2]); 382 | miscColorArray.push(color[0]); 383 | miscColorArray.push(color[1]); 384 | miscColorArray.push(color[2]); 385 | } 386 | 387 | -------------------------------------------------------------------------------- /nouislider.min.js: -------------------------------------------------------------------------------- 1 | /*! nouislider - 9.0.0 - 2016-09-29 21:44:02 */ 2 | 3 | !function(a){"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?module.exports=a():window.noUiSlider=a()}(function(){"use strict";function a(a,b){var c=document.createElement("div");return j(c,b),a.appendChild(c),c}function b(a){return a.filter(function(a){return!this[a]&&(this[a]=!0)},{})}function c(a,b){return Math.round(a/b)*b}function d(a,b){var c=a.getBoundingClientRect(),d=a.ownerDocument,e=d.documentElement,f=m();return/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)&&(f.x=0),b?c.top+f.y-e.clientTop:c.left+f.x-e.clientLeft}function e(a){return"number"==typeof a&&!isNaN(a)&&isFinite(a)}function f(a,b,c){c>0&&(j(a,b),setTimeout(function(){k(a,b)},c))}function g(a){return Math.max(Math.min(a,100),0)}function h(a){return Array.isArray(a)?a:[a]}function i(a){a=String(a);var b=a.split(".");return b.length>1?b[1].length:0}function j(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function k(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function l(a,b){return a.classList?a.classList.contains(b):new RegExp("\\b"+b+"\\b").test(a.className)}function m(){var a=void 0!==window.pageXOffset,b="CSS1Compat"===(document.compatMode||""),c=a?window.pageXOffset:b?document.documentElement.scrollLeft:document.body.scrollLeft,d=a?window.pageYOffset:b?document.documentElement.scrollTop:document.body.scrollTop;return{x:c,y:d}}function n(){return window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",end:"mouseup touchend"}}function o(a,b){return 100/(b-a)}function p(a,b){return 100*b/(a[1]-a[0])}function q(a,b){return p(a,a[0]<0?b+Math.abs(a[0]):b-a[0])}function r(a,b){return b*(a[1]-a[0])/100+a[0]}function s(a,b){for(var c=1;a>=b[c];)c+=1;return c}function t(a,b,c){if(c>=a.slice(-1)[0])return 100;var d,e,f,g,h=s(c,a);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],f+q([d,e],c)/o(f,g)}function u(a,b,c){if(c>=100)return a.slice(-1)[0];var d,e,f,g,h=s(c,b);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],r([d,e],(c-f)*o(f,g))}function v(a,b,d,e){if(100===e)return e;var f,g,h=s(e,a);return d?(f=a[h-1],g=a[h],e-f>(g-f)/2?g:f):b[h-1]?a[h-1]+c(e-a[h-1],b[h-1]):e}function w(a,b,c){var d;if("number"==typeof b&&(b=[b]),"[object Array]"!==Object.prototype.toString.call(b))throw new Error("noUiSlider: 'range' contains invalid value.");if(d="min"===a?0:"max"===a?100:parseFloat(a),!e(d)||!e(b[0]))throw new Error("noUiSlider: 'range' value isn't numeric.");c.xPct.push(d),c.xVal.push(b[0]),d?c.xSteps.push(!isNaN(b[1])&&b[1]):isNaN(b[1])||(c.xSteps[0]=b[1]),c.xHighestCompleteStep.push(0)}function x(a,b,c){if(!b)return!0;c.xSteps[a]=p([c.xVal[a],c.xVal[a+1]],b)/o(c.xPct[a],c.xPct[a+1]);var d=(c.xVal[a+1]-c.xVal[a])/c.xNumSteps[a],e=Math.ceil(Number(d.toFixed(3))-1),f=c.xVal[a]+c.xNumSteps[a]*e;c.xHighestCompleteStep[a]=f}function y(a,b,c,d){this.xPct=[],this.xVal=[],this.xSteps=[d||!1],this.xNumSteps=[!1],this.xHighestCompleteStep=[],this.snap=b,this.direction=c;var e,f=[];for(e in a)a.hasOwnProperty(e)&&f.push([a[e],e]);for(f.length&&"object"==typeof f[0][0]?f.sort(function(a,b){return a[0][0]-b[0][0]}):f.sort(function(a,b){return a[0]-b[0]}),e=0;e=0,d=b.indexOf("drag")>=0,e=b.indexOf("fixed")>=0,f=b.indexOf("snap")>=0,g=b.indexOf("hover")>=0;if(e){if(2!==a.handles)throw new Error("noUiSlider: 'fixed' behaviour must be used with 2 handles");H(a,a.start[1]-a.start[0])}a.events={tap:c||f,drag:d,fixed:e,snap:f,hover:g}}function L(a,b){if(b!==!1)if(b===!0){a.tooltips=[];for(var c=0;c-1?1:"steps"===c?2:0,!g&&i&&(q=0),l===v&&j||(f[n.toFixed(5)]=[l,q]),k=n}}),f}function w(a,b,c){function d(a,b){var c=b===e.cssClasses.value,d=c?m:n,f=c?k:l;return b+" "+d[e.ort]+" "+f[a]}function f(a,b,c){return'class="'+d(c[1],b)+'" style="'+e.style+": "+a+'%"'}function g(a,d){d[1]=d[1]&&b?b(d[0],d[1]):d[1],i+="
",d[1]&&(i+="
"+c.to(d[0])+"
")}var h=document.createElement("div"),i="",k=[e.cssClasses.valueNormal,e.cssClasses.valueLarge,e.cssClasses.valueSub],l=[e.cssClasses.markerNormal,e.cssClasses.markerLarge,e.cssClasses.markerSub],m=[e.cssClasses.valueHorizontal,e.cssClasses.valueVertical],n=[e.cssClasses.markerHorizontal,e.cssClasses.markerVertical];return j(h,e.cssClasses.pips),j(h,0===e.ort?e.cssClasses.pipsHorizontal:e.cssClasses.pipsVertical),Object.keys(a).forEach(function(b){g(b,a[b])}),h.innerHTML=i,h}function x(a){var b=a.mode,c=a.density||1,d=a.filter||!1,e=a.values||!1,f=a.stepped||!1,g=u(b,e,f),h=v(c,b,g),i=a.format||{to:Math.round};return fa.appendChild(w(h,d,i))}function y(){var a=aa.getBoundingClientRect(),b="offset"+["Width","Height"][e.ort];return 0===e.ort?a.width||aa[b]:a.height||aa[b]}function z(a,b,c,d){var f=function(b){return!fa.hasAttribute("disabled")&&(!l(fa,e.cssClasses.tap)&&(b=A(b,d.pageOffset),!(a===ea.start&&void 0!==b.buttons&&b.buttons>1)&&((!d.hover||!b.buttons)&&(b.calcPoint=b.points[e.ort],void c(b,d)))))},g=[];return a.split(" ").forEach(function(a){b.addEventListener(a,f,!1),g.push([a,f])}),g}function A(a,b){a.preventDefault();var c,d,e=0===a.type.indexOf("touch"),f=0===a.type.indexOf("mouse"),g=0===a.type.indexOf("pointer"),h=a;if(0===a.type.indexOf("MSPointer")&&(g=!0),e){if(h.touches.length>1)return!1;c=a.changedTouches[0].pageX,d=a.changedTouches[0].pageY}return b=b||m(),(f||g)&&(c=a.clientX+b.x,d=a.clientY+b.y),h.pageOffset=b,h.points=[c,d],h.cursor=f||g,h}function B(a){var b=a-d(aa,e.ort),c=100*b/y();return e.dir?100-c:c}function C(a){var b=100,c=!1;return ba.forEach(function(d,e){if(!d.hasAttribute("disabled")){var f=Math.abs(ga[e]-a);f1?d.forEach(function(a,c){var d=M(e,a,e[a]+b,f[c],g[c]);d===!1?b=0:(b=d-e[a],e[a]=d)}):f=g=[!0];var h=!1;d.forEach(function(a,d){h=R(a,c[a]+b,f[d],g[d])||h}),h&&d.forEach(function(a){E("update",a),E("slide",a)})}function E(a,b,c){Object.keys(ka).forEach(function(d){var f=d.split(".")[0];a===f&&ka[d].forEach(function(a){a.call(da,ja.map(e.format.to),b,ja.slice(),c||!1,ga.slice())})})}function F(a,b){"mouseout"===a.type&&"HTML"===a.target.nodeName&&null===a.relatedTarget&&H(a,b)}function G(a,b){if(navigator.appVersion.indexOf("MSIE 9")===-1&&0===a.buttons&&0!==b.buttonsProperty)return H(a,b);var c=(e.dir?-1:1)*(a.calcPoint-b.startCalcPoint),d=100*c/b.baseSize;D(c>0,d,b.locations,b.handleNumbers)}function H(a,b){var c=aa.querySelector("."+e.cssClasses.active);null!==c&&k(c,e.cssClasses.active),a.cursor&&(document.body.style.cursor="",document.body.removeEventListener("selectstart",document.body.noUiListener)),document.documentElement.noUiListeners.forEach(function(a){document.documentElement.removeEventListener(a[0],a[1])}),k(fa,e.cssClasses.drag),P(),b.handleNumbers.forEach(function(a){E("set",a),E("change",a),E("end",a)})}function I(a,b){if(1===b.handleNumbers.length){var c=ba[b.handleNumbers[0]];if(c.hasAttribute("disabled"))return!1;j(c.children[0],e.cssClasses.active)}a.preventDefault(),a.stopPropagation();var d=z(ea.move,document.documentElement,G,{startCalcPoint:a.calcPoint,baseSize:y(),pageOffset:a.pageOffset,handleNumbers:b.handleNumbers,buttonsProperty:a.buttons,locations:ga.slice()}),f=z(ea.end,document.documentElement,H,{handleNumbers:b.handleNumbers}),g=z("mouseout",document.documentElement,F,{handleNumbers:b.handleNumbers});if(document.documentElement.noUiListeners=d.concat(f,g),a.cursor){document.body.style.cursor=getComputedStyle(a.target).cursor,ba.length>1&&j(fa,e.cssClasses.drag);var h=function(){return!1};document.body.noUiListener=h,document.body.addEventListener("selectstart",h,!1)}b.handleNumbers.forEach(function(a){E("start",a)})}function J(a){a.stopPropagation();var b=B(a.calcPoint),c=C(b);return c!==!1&&(e.events.snap||f(fa,e.cssClasses.tap,e.animationDuration),R(c,b,!0,!0),P(),E("slide",c,!0),E("set",c,!0),E("change",c,!0),E("update",c,!0),void(e.events.snap&&I(a,{handleNumbers:[c]})))}function K(a){var b=B(a.calcPoint),c=ia.getStep(b),d=ia.fromStepping(c);Object.keys(ka).forEach(function(a){"hover"===a.split(".")[0]&&ka[a].forEach(function(a){a.call(da,d)})})}function L(a){a.fixed||ba.forEach(function(a,b){z(ea.start,a.children[0],I,{handleNumbers:[b]})}),a.tap&&z(ea.start,aa,J,{}),a.hover&&z(ea.move,aa,K,{hover:!0}),a.drag&&ca.forEach(function(b,c){if(b!==!1&&0!==c&&c!==ca.length-1){var d=ba[c-1],f=ba[c],g=[b];j(b,e.cssClasses.draggable),a.fixed&&(g.push(d.children[0]),g.push(f.children[0])),g.forEach(function(a){z(ea.start,a,I,{handles:[d,f],handleNumbers:[c-1,c]})})}})}function M(a,b,c,d,f){return ba.length>1&&(d&&b>0&&(c=Math.max(c,a[b-1]+e.margin)),f&&b1&&e.limit&&(d&&b>0&&(c=Math.min(c,a[b-1]+e.limit)),f&&b50?-1:1,c=3+(ba.length+b*a);ba[a].childNodes[0].style.zIndex=c})}function R(a,b,c,d){return b=M(ga,a,b,c,d),b!==!1&&(O(a,b),!0)}function S(a){if(ca[a]){var b=0,c=100;0!==a&&(b=ga[a-1]),a!==ca.length-1&&(c=ga[a]),ca[a].style[e.style]=N(b),ca[a].style[e.styleOposite]=N(100-c)}}function T(a,b){null!==a&&a!==!1&&("number"==typeof a&&(a=String(a)),a=e.format.from(a),a===!1||isNaN(a)||R(b,ia.toStepping(a),!1,!1))}function U(a,b){var c=h(a),d=void 0===ga[0];b=void 0===b||!!b,c.forEach(T),e.animate&&!d&&f(fa,e.cssClasses.tap,e.animationDuration),ha.forEach(function(a){R(a,ga[a],!0,!1)}),P(),ha.forEach(function(a){E("update",a),null!==c[a]&&b&&E("set",a)})}function V(a){U(e.start,a)}function W(){var a=ja.map(e.format.to);return 1===a.length?a[0]:a}function X(){for(var a in e.cssClasses)e.cssClasses.hasOwnProperty(a)&&k(fa,e.cssClasses[a]);for(;fa.firstChild;)fa.removeChild(fa.firstChild);delete fa.noUiSlider}function Y(){return ga.map(function(a,b){var c=ia.getNearbySteps(a),d=ja[b],e=c.thisStep.step,f=null;e!==!1&&d+e>c.stepAfter.startValue&&(e=c.stepAfter.startValue-d),f=d>c.thisStep.startValue?c.thisStep.step:c.stepBefore.step!==!1&&d-c.stepBefore.highestStep,100===a?e=null:0===a&&(f=null);var g=ia.countStepDecimals();return null!==e&&e!==!1&&(e=Number(e.toFixed(g))),null!==f&&f!==!1&&(f=Number(f.toFixed(g))),[f,e]})}function Z(a,b){ka[a]=ka[a]||[],ka[a].push(b),"update"===a.split(".")[0]&&ba.forEach(function(a,b){E("update",b)})}function $(a){var b=a&&a.split(".")[0],c=b&&a.substring(b.length);Object.keys(ka).forEach(function(a){var d=a.split(".")[0],e=a.substring(d.length);b&&b!==d||c&&c!==e||delete ka[a]})}function _(a,b){var c=W(),d=["margin","limit","range","animate","snap","step","format"];d.forEach(function(b){void 0!==a[b]&&(i[b]=a[b])});var f=Q(i);d.forEach(function(b){void 0!==a[b]&&(e[b]=f[b])}),f.spectrum.direction=ia.direction,ia=f.spectrum,e.margin=f.margin,e.limit=f.limit,ga=[],U(a.start||c,b)}var aa,ba,ca,da,ea=n(),fa=c,ga=[],ha=[],ia=e.spectrum,ja=[],ka={};if(fa.noUiSlider)throw new Error("Slider was already initialized.");return r(fa),q(e.connect,aa),da={destroy:X,steps:Y,on:Z,off:$,get:W,set:U,reset:V,__moveHandles:function(a,b,c){D(a,b,ga,c)},options:i,updateOptions:_,target:fa,pips:x},L(e.events),U(e.start),e.pips&&x(e.pips),e.tooltips&&t(),da}function S(a,b){if(!a.nodeName)throw new Error("noUiSlider.create requires a single element.");var c=Q(b,a),d=R(a,c,b);return a.noUiSlider=d,d}y.prototype.getMargin=function(a){var b=this.xNumSteps[0];if(b&&a%b)throw new Error("noUiSlider: 'limit' and 'margin' must be divisible by step.");return 2===this.xPct.length&&p(this.xVal,a)},y.prototype.toStepping=function(a){return a=t(this.xVal,this.xPct,a)},y.prototype.fromStepping=function(a){return u(this.xVal,this.xPct,a)},y.prototype.getStep=function(a){return a=v(this.xPct,this.xSteps,this.snap,a)},y.prototype.getNearbySteps=function(a){var b=s(a,this.xPct);return{stepBefore:{startValue:this.xVal[b-2],step:this.xNumSteps[b-2],highestStep:this.xHighestCompleteStep[b-2]},thisStep:{startValue:this.xVal[b-1],step:this.xNumSteps[b-1],highestStep:this.xHighestCompleteStep[b-1]},stepAfter:{startValue:this.xVal[b-0],step:this.xNumSteps[b-0],highestStep:this.xHighestCompleteStep[b-0]}}},y.prototype.countStepDecimals=function(){var a=this.xNumSteps.map(i);return Math.max.apply(null,a)},y.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var T={to:function(a){return void 0!==a&&a.toFixed(2)},from:Number};return{create:S}}); -------------------------------------------------------------------------------- /airplane.js: -------------------------------------------------------------------------------- 1 | /** 2 | * airplane.js 3 | * 4 | * @fileoverview Manages buffers, arrays, and other data related to airplane 5 | * models and obstacles. In this file, "airplane" refers to the airplane right 6 | * in front of the viewport, whereas "obstacles" refer to the airplanes flying 7 | * in the mid-air as obstacles. 8 | * @author Po-Han Huang 9 | */ 10 | 11 | /** Identification prefix for airplane shader. */ 12 | var AIRPLANE_PREFIX = "airplane"; 13 | 14 | /** GL buffers for airplanes. */ 15 | var airplanePositionBuffer; 16 | var airplaneIndexBuffer; 17 | var airplaneColorBuffer; 18 | var airplaneNormalBuffer; 19 | 20 | /** Data array for airplanes. */ 21 | var airplanePositionArray = []; 22 | var airplaneIndexArray = []; 23 | var airplaneColorArray = []; 24 | var airplaneNormalArray = []; 25 | 26 | /** Shader program for airplanes. */ 27 | var airplaneShaderProgram; 28 | /** Variable locations in shader program. */ 29 | var airplaneLocations = {}; 30 | 31 | /** Model-to-world matrix of the airplane. */ 32 | var airplaneModelMatrix = mat4.create(); 33 | 34 | /** Airplane body color */ 35 | var AIRPLANE_BODY_COLOR = [1.1,1.1,1.1]; 36 | 37 | /** Model-to-World scale of airplane */ 38 | var AIRPLANE_SCALE = vec3.fromValues(0.0004,0.0004,0.0004); 39 | /** Vertical offset of airplane from view origin */ 40 | var AIRPLANE_OFFSET_UP = -0.0004; 41 | /** Frontal offset of airplane from view origin */ 42 | var AIRPLANE_OFFSET_FRONT = 0.0008; 43 | /** Vertical offset of airplane tip (used for collision detection) from view 44 | * origin 45 | */ 46 | var AIRPLANE_TIP_UP = -0.0004; 47 | /** Frontal offset of airplane tip from view origin */ 48 | var AIRPLANE_TIP_FRONT = 0.001; 49 | 50 | /** No obstacles, static obstacles, or moving obstacles. (0,1,2, respectively) 51 | */ 52 | var obstacleLevel = 0; 53 | /** Array of obstacles' origin */ 54 | var obstacleOrigin = []; 55 | /** Array of obstacles' angle (ccw rotation from +y direction) */ 56 | var obstacleAngle = []; 57 | /** Array of obstacles' rotation quaternion {quat} */ 58 | var obstacleRotation = []; 59 | /** Array of obstacles' velocity vectors {vec3} */ 60 | var obstacleVelocity = []; 61 | /** Array of obstacles' model-to-world matrices {mat4} */ 62 | var obstacleModelMatrix = []; 63 | 64 | /** Number of obstacles */ 65 | var OBSTACLE_COUNT = 75; 66 | /** Model-to-World scale of obstacles */ 67 | var OBSTACLE_SCALE = vec3.fromValues(0.1,0.1,0.1); 68 | 69 | /** Initialization of airplane.js */ 70 | function airplaneInit(){ 71 | 72 | /** Register shaders, draw calls, animate calls. */ 73 | var prefix = "airplane"; 74 | shaderPrefix.push(prefix); 75 | shaderInit[prefix] = airplaneShaderInit; 76 | bufferInit[prefix] = airplaneBufferInit; 77 | drawFunctions[prefix] = airplaneDraw; 78 | animateFunctions[prefix] = airplaneAnimate; 79 | 80 | /** Initialize airplane and obstacles. */ 81 | airplaneGenerateShape(); 82 | airplaneInitObstacles(); 83 | } 84 | 85 | /** Initialize airplane's shader programs and variable locations. */ 86 | function airplaneShaderInit(){ 87 | airplaneShaderProgram = shaderPrograms[AIRPLANE_PREFIX]; 88 | 89 | /** Attributes */ 90 | airplaneLocations["aVertexPosition"] = gl.getAttribLocation(airplaneShaderProgram, "aVertexPosition"); 91 | gl.enableVertexAttribArray(airplaneLocations["aVertexPosition"]); 92 | 93 | airplaneLocations["aVertexColor"] = gl.getAttribLocation(airplaneShaderProgram, "aVertexColor"); 94 | gl.enableVertexAttribArray(airplaneLocations["aVertexColor"]); 95 | 96 | airplaneLocations["aVertexNormal"] = gl.getAttribLocation(airplaneShaderProgram, "aVertexNormal"); 97 | gl.enableVertexAttribArray(airplaneLocations["aVertexNormal"]); 98 | 99 | /** Uniforms */ 100 | airplaneLocations["uPMatrix"] = gl.getUniformLocation(airplaneShaderProgram, "uPMatrix"); 101 | airplaneLocations["uMVMatrix"] = gl.getUniformLocation(airplaneShaderProgram, "uMVMatrix"); 102 | airplaneLocations["uModelMatrix"] = gl.getUniformLocation(airplaneShaderProgram, "uModelMatrix"); 103 | 104 | airplaneLocations["uViewOrigin"] = gl.getUniformLocation(airplaneShaderProgram, "uViewOrigin"); 105 | airplaneLocations["uLightDirection"] = gl.getUniformLocation(airplaneShaderProgram, "uLightDirection"); 106 | airplaneLocations["uAmbientLight"] = gl.getUniformLocation(airplaneShaderProgram, "uAmbientLight"); 107 | airplaneLocations["uDiffuseLight"] = gl.getUniformLocation(airplaneShaderProgram, "uDiffuseLight"); 108 | airplaneLocations["uSpecularLight"] = gl.getUniformLocation(airplaneShaderProgram, "uSpecularLight"); 109 | } 110 | 111 | /** Initialize airplane's buffer. */ 112 | function airplaneBufferInit(){ 113 | 114 | /** Create buffers. */ 115 | airplanePositionBuffer = gl.createBuffer(); 116 | airplaneIndexBuffer = gl.createBuffer(); 117 | airplaneColorBuffer = gl.createBuffer(); 118 | airplaneNormalBuffer = gl.createBuffer(); 119 | 120 | /** Bind buffers. */ 121 | airplanePositionBuffer.itemSize = 3; 122 | airplanePositionBuffer.numOfItems = airplanePositionArray.length / airplanePositionBuffer.itemSize; 123 | gl.bindBuffer(gl.ARRAY_BUFFER, airplanePositionBuffer); 124 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(airplanePositionArray), gl.STATIC_DRAW); 125 | 126 | airplaneIndexBuffer.itemSize = 1; 127 | airplaneIndexBuffer.numOfItems = airplaneIndexArray.length / airplaneIndexBuffer.itemSize; 128 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, airplaneIndexBuffer); 129 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int32Array(airplaneIndexArray), gl.STATIC_DRAW); 130 | 131 | airplaneColorBuffer.itemSize = 3; 132 | airplaneColorBuffer.numOfItems = airplaneColorArray.length / airplaneColorBuffer.itemSize; 133 | gl.bindBuffer(gl.ARRAY_BUFFER, airplaneColorBuffer); 134 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(airplaneColorArray), gl.STATIC_DRAW); 135 | 136 | airplaneNormalBuffer.itemSize = 3; 137 | airplaneNormalBuffer.numOfItems = airplaneNormalArray.length / airplaneNormalBuffer.itemSize; 138 | gl.bindBuffer(gl.ARRAY_BUFFER, airplaneNormalBuffer); 139 | gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(airplaneNormalArray), gl.STATIC_DRAW); 140 | 141 | } 142 | 143 | /** Airplane draw call */ 144 | function airplaneDraw(){ 145 | 146 | /** Update matrices. */ 147 | airplanePrepareMatrix(); 148 | obstaclePrepareMatrix(); 149 | 150 | /** Setup variables. */ 151 | gl.useProgram(airplaneShaderProgram); 152 | 153 | gl.bindBuffer(gl.ARRAY_BUFFER, airplanePositionBuffer); 154 | gl.vertexAttribPointer(airplaneLocations["aVertexPosition"], airplanePositionBuffer.itemSize, gl.FLOAT, false, 0, 0); 155 | 156 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, airplaneIndexBuffer); 157 | 158 | gl.bindBuffer(gl.ARRAY_BUFFER, airplaneColorBuffer); 159 | gl.vertexAttribPointer(airplaneLocations["aVertexColor"], airplaneColorBuffer.itemSize, gl.FLOAT, false, 0, 0); 160 | 161 | gl.bindBuffer(gl.ARRAY_BUFFER, airplaneNormalBuffer); 162 | gl.vertexAttribPointer(airplaneLocations["aVertexNormal"], airplaneNormalBuffer.itemSize, gl.FLOAT, false, 0, 0); 163 | 164 | gl.uniformMatrix4fv(airplaneLocations["uPMatrix"], false, pMatrix); 165 | gl.uniformMatrix4fv(airplaneLocations["uMVMatrix"], false, mvMatrix); 166 | gl.uniform3fv(airplaneLocations["uViewOrigin"], viewOrigin); 167 | 168 | gl.uniform3fv(airplaneLocations["uLightDirection"], LIGHT_DIRECTION); 169 | gl.uniform3fv(airplaneLocations["uAmbientLight"], AMBIENT_LIGHT); 170 | gl.uniform3fv(airplaneLocations["uDiffuseLight"], DIFFUSE_LIGHT); 171 | gl.uniform3fv(airplaneLocations["uSpecularLight"], SPECULAR_LIGHT); 172 | 173 | /** Draw! */ 174 | airplaneDrawObstacle(); 175 | airplaneDrawAirplane(); 176 | 177 | } 178 | 179 | /** Draw airplane. */ 180 | function airplaneDrawAirplane(){ 181 | gl.uniformMatrix4fv(airplaneLocations["uModelMatrix"], false, airplaneModelMatrix); 182 | gl.drawElements(gl.TRIANGLES, airplaneIndexBuffer.numOfItems, gl.UNSIGNED_INT, 0); 183 | } 184 | 185 | /** Draw obstacles. */ 186 | function airplaneDrawObstacle(){ 187 | if(obstacleLevel !== 0){ 188 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 189 | gl.uniformMatrix4fv(airplaneLocations["uModelMatrix"], false, obstacleModelMatrix[i]); 190 | gl.drawElements(gl.TRIANGLES, airplaneIndexBuffer.numOfItems, gl.UNSIGNED_INT, 0); 191 | } 192 | } 193 | } 194 | 195 | /** Update airplane's model-to-world matrix. */ 196 | function airplanePrepareMatrix(){ 197 | 198 | /** Prepare required quats and vecs. */ 199 | var rotation = mat4.create(); 200 | mat4.fromQuat(rotation, viewQuat); 201 | 202 | var offsetFront = vec3.create(); 203 | vec3.scale(offsetFront, viewLookAt, AIRPLANE_OFFSET_FRONT); 204 | 205 | var offsetUp = vec3.create(); 206 | vec3.scale(offsetUp, viewUp, AIRPLANE_OFFSET_UP); 207 | 208 | /** Generate matrix. */ 209 | var m = mat4.create(); 210 | 211 | mat4.translate(m, m, offsetFront); 212 | mat4.translate(m, m, offsetUp); 213 | mat4.translate(m, m, viewOrigin); 214 | mat4.multiply(m, m, rotation); 215 | mat4.rotateZ(m, m, degToRad(180)); 216 | mat4.scale(m, m, AIRPLANE_SCALE); 217 | 218 | airplaneModelMatrix = m; 219 | } 220 | 221 | /** Update obstacles' model-to-world matrices. */ 222 | function obstaclePrepareMatrix(){ 223 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 224 | mat4.fromRotationTranslationScale(obstacleModelMatrix[i], obstacleRotation[i], obstacleOrigin[i], OBSTACLE_SCALE); 225 | } 226 | } 227 | 228 | /** 229 | * Airplane animate call 230 | * 231 | * @param {float} lapse timelapse since last frame in sec 232 | */ 233 | function airplaneAnimate(lapse){ 234 | 235 | /** Move the obstacles only when necessary. */ 236 | if(obstacleLevel === 2){ 237 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 238 | 239 | /** Update obstacles' locations. */ 240 | vec3.scaleAndAdd(obstacleOrigin[i], obstacleOrigin[i], obstacleVelocity[i], lapse); 241 | 242 | /** Check boundary. */ 243 | if(obstacleOrigin[i][0] < -1.2){ 244 | obstacleOrigin[i][0] += 2.4; 245 | }else if(obstacleOrigin[i][0] > 1.2){ 246 | obstacleOrigin[i][0] -= 2.4; 247 | } 248 | 249 | if(obstacleOrigin[i][1] < -1.2){ 250 | obstacleOrigin[i][1] += 2.4; 251 | }else if(obstacleOrigin[i][1] > 1.2){ 252 | obstacleOrigin[i][1] -= 2.4; 253 | } 254 | } 255 | } 256 | } 257 | 258 | /** Generate airplane model. 259 | * 260 | * The default model is an airplane centered at origin, with its front facing 261 | * +y direction, and its wings expand along x axis. 262 | */ 263 | function airplaneGenerateShape(){ 264 | 265 | /** Generate body part */ 266 | 267 | /** Number of layers along y axis */ 268 | var depth_total = 2; 269 | /** Y coordinates of each layer */ 270 | var depth_coords = [-1.0,1.0]; 271 | /** Number of points at each layer (radial resolution) */ 272 | var angle_total = 36; 273 | /** Helper function to get index with depth and angle 274 | * @param {int} d depth index 275 | * @param {int} a angle index 276 | * @return {int} index of specified point 277 | */ 278 | var body_index = function(d,a){ 279 | /** Smart modulo operation to handle negative "a" */ 280 | return d*angle_total+((a % angle_total) + angle_total) % angle_total; 281 | }; 282 | 283 | /** Fill array. */ 284 | for(var depth = 0; depth < depth_total; depth++){ 285 | for(var angle = 0; angle < angle_total; angle++){ 286 | /** Push vertex positions, normals, and colors. */ 287 | airplanePositionArray.push(0.2*Math.sin(degToRad(360.0/angle_total*angle))); 288 | airplanePositionArray.push(depth_coords[depth]); 289 | airplanePositionArray.push(0.2*Math.cos(degToRad(360.0/angle_total*angle))); 290 | airplaneNormalArray.push(Math.sin(degToRad(360.0/angle_total*angle))); 291 | airplaneNormalArray.push(0.0); 292 | airplaneNormalArray.push(Math.cos(degToRad(360.0/angle_total*angle))); 293 | airplanePushColor(); 294 | } 295 | 296 | /** Push indices to index array. */ 297 | if(depth !== 0){ 298 | for(var angle = 0; angle < angle_total; angle++){ 299 | airplaneIndexArray.push(body_index(depth,angle)); 300 | airplaneIndexArray.push(body_index(depth-1,angle)); 301 | airplaneIndexArray.push(body_index(depth-1,angle+1)); 302 | airplaneIndexArray.push(body_index(depth,angle+1)); 303 | airplaneIndexArray.push(body_index(depth,angle)); 304 | airplaneIndexArray.push(body_index(depth-1,angle+1)); 305 | } 306 | } 307 | } 308 | 309 | /** Keep track of how many vertices already in array. */ 310 | var positionOffset = depth_total * angle_total; 311 | 312 | /** Generate tail. */ 313 | airplanePositionArray.push(0); 314 | airplanePositionArray.push(-1.15); 315 | airplanePositionArray.push(0); 316 | airplaneNormalArray.push(0.0); 317 | airplaneNormalArray.push(-1.0); 318 | airplaneNormalArray.push(0.0); 319 | airplanePushColor(); 320 | for(var angle = 0; angle < angle_total; angle++){ 321 | airplaneIndexArray.push(positionOffset); 322 | airplaneIndexArray.push(body_index(0,angle+1)); 323 | airplaneIndexArray.push(body_index(0,angle)); 324 | } 325 | 326 | positionOffset += 1; 327 | 328 | /** Generate head. */ 329 | airplanePositionArray.push(0); 330 | airplanePositionArray.push(1.2); 331 | airplanePositionArray.push(0); 332 | airplaneNormalArray.push(0.0); 333 | airplaneNormalArray.push(1.0); 334 | airplaneNormalArray.push(0.0); 335 | airplanePushColor(); 336 | for(var angle = 0; angle < angle_total; angle++){ 337 | airplaneIndexArray.push(positionOffset); 338 | airplaneIndexArray.push(body_index(depth_total-1,angle)); 339 | airplaneIndexArray.push(body_index(depth_total-1,angle+1)); 340 | } 341 | 342 | positionOffset += 1; 343 | 344 | /** Generate left wing. */ 345 | airplanePositionArray.push(-0.2); 346 | airplanePositionArray.push(-0.2); 347 | airplanePositionArray.push(0); 348 | airplanePositionArray.push(-0.2); 349 | airplanePositionArray.push(0.5); 350 | airplanePositionArray.push(0.0); 351 | airplanePositionArray.push(-1.6); 352 | airplanePositionArray.push(-0.5); 353 | airplanePositionArray.push(0.0); 354 | airplaneNormalArray.push(0.0); 355 | airplaneNormalArray.push(0.0); 356 | airplaneNormalArray.push(1.0); 357 | airplaneNormalArray.push(0.0); 358 | airplaneNormalArray.push(0.0); 359 | airplaneNormalArray.push(1.0); 360 | airplaneNormalArray.push(0.0); 361 | airplaneNormalArray.push(0.0); 362 | airplaneNormalArray.push(1.0); 363 | airplanePushColor(); 364 | airplanePushColor(); 365 | airplanePushColor(); 366 | airplaneIndexArray.push(positionOffset+0); 367 | airplaneIndexArray.push(positionOffset+1); 368 | airplaneIndexArray.push(positionOffset+2); 369 | 370 | positionOffset += 3; 371 | 372 | /** Generate left tail wing. */ 373 | airplanePositionArray.push(-0.2); 374 | airplanePositionArray.push(-1.0); 375 | airplanePositionArray.push(0.0); 376 | airplanePositionArray.push(-0.2); 377 | airplanePositionArray.push(-0.8); 378 | airplanePositionArray.push(0.0); 379 | airplanePositionArray.push(-0.6); 380 | airplanePositionArray.push(-1.0); 381 | airplanePositionArray.push(0.0); 382 | airplaneNormalArray.push(0.0); 383 | airplaneNormalArray.push(0.0); 384 | airplaneNormalArray.push(1.0); 385 | airplaneNormalArray.push(0.0); 386 | airplaneNormalArray.push(0.0); 387 | airplaneNormalArray.push(1.0); 388 | airplaneNormalArray.push(0.0); 389 | airplaneNormalArray.push(0.0); 390 | airplaneNormalArray.push(1.0); 391 | airplanePushColor(); 392 | airplanePushColor(); 393 | airplanePushColor(); 394 | airplaneIndexArray.push(positionOffset+0); 395 | airplaneIndexArray.push(positionOffset+1); 396 | airplaneIndexArray.push(positionOffset+2); 397 | 398 | positionOffset += 3; 399 | 400 | /** Generate right wing. */ 401 | airplanePositionArray.push(0.2); 402 | airplanePositionArray.push(-0.2); 403 | airplanePositionArray.push(0); 404 | airplanePositionArray.push(0.2); 405 | airplanePositionArray.push(0.5); 406 | airplanePositionArray.push(0.0); 407 | airplanePositionArray.push(1.6); 408 | airplanePositionArray.push(-0.5); 409 | airplanePositionArray.push(0.0); 410 | airplaneNormalArray.push(0.0); 411 | airplaneNormalArray.push(0.0); 412 | airplaneNormalArray.push(1.0); 413 | airplaneNormalArray.push(0.0); 414 | airplaneNormalArray.push(0.0); 415 | airplaneNormalArray.push(1.0); 416 | airplaneNormalArray.push(0.0); 417 | airplaneNormalArray.push(0.0); 418 | airplaneNormalArray.push(1.0); 419 | airplanePushColor(); 420 | airplanePushColor(); 421 | airplanePushColor(); 422 | airplaneIndexArray.push(positionOffset+0); 423 | airplaneIndexArray.push(positionOffset+1); 424 | airplaneIndexArray.push(positionOffset+2); 425 | 426 | positionOffset += 3; 427 | 428 | /** Generate right tail wing. */ 429 | airplanePositionArray.push(0.2); 430 | airplanePositionArray.push(-1.0); 431 | airplanePositionArray.push(0.0); 432 | airplanePositionArray.push(0.2); 433 | airplanePositionArray.push(-0.8); 434 | airplanePositionArray.push(0.0); 435 | airplanePositionArray.push(0.6); 436 | airplanePositionArray.push(-1.0); 437 | airplanePositionArray.push(0.0); 438 | airplaneNormalArray.push(0.0); 439 | airplaneNormalArray.push(0.0); 440 | airplaneNormalArray.push(1.0); 441 | airplaneNormalArray.push(0.0); 442 | airplaneNormalArray.push(0.0); 443 | airplaneNormalArray.push(1.0); 444 | airplaneNormalArray.push(0.0); 445 | airplaneNormalArray.push(0.0); 446 | airplaneNormalArray.push(1.0); 447 | airplanePushColor(); 448 | airplanePushColor(); 449 | airplanePushColor(); 450 | airplaneIndexArray.push(positionOffset+0); 451 | airplaneIndexArray.push(positionOffset+1); 452 | airplaneIndexArray.push(positionOffset+2); 453 | 454 | positionOffset += 3; 455 | 456 | /** Generate center tail wing. */ 457 | airplanePositionArray.push(0.0); 458 | airplanePositionArray.push(-1.0); 459 | airplanePositionArray.push(0.2); 460 | airplanePositionArray.push(0.0); 461 | airplanePositionArray.push(-0.8); 462 | airplanePositionArray.push(0.2); 463 | airplanePositionArray.push(0.0); 464 | airplanePositionArray.push(-1.0); 465 | airplanePositionArray.push(0.6); 466 | airplaneNormalArray.push(1.0); 467 | airplaneNormalArray.push(0.0); 468 | airplaneNormalArray.push(0.0); 469 | airplaneNormalArray.push(1.0); 470 | airplaneNormalArray.push(0.0); 471 | airplaneNormalArray.push(0.0); 472 | airplaneNormalArray.push(1.0); 473 | airplaneNormalArray.push(0.0); 474 | airplaneNormalArray.push(0.0); 475 | airplanePushColor(); 476 | airplanePushColor(); 477 | airplanePushColor(); 478 | airplaneIndexArray.push(positionOffset+0); 479 | airplaneIndexArray.push(positionOffset+1); 480 | airplaneIndexArray.push(positionOffset+2); 481 | 482 | positionOffset += 3; 483 | 484 | } 485 | 486 | /** Helper function to push a color of a vertex */ 487 | function airplanePushColor(){ 488 | airplaneColorArray.push(AIRPLANE_BODY_COLOR[0]); 489 | airplaneColorArray.push(AIRPLANE_BODY_COLOR[1]); 490 | airplaneColorArray.push(AIRPLANE_BODY_COLOR[2]); 491 | } 492 | 493 | /** Initialize obstacles. */ 494 | function airplaneInitObstacles(){ 495 | for(var i = 0; i < OBSTACLE_COUNT; i++){ 496 | 497 | /** Generate random positions. */ 498 | obstacleOrigin[i] = vec3.fromValues(airplaneRandomGenerator(-1.0,1.0), 499 | airplaneRandomGenerator(-1.0,1.0), 500 | airplaneRandomGenerator(0.4,1.0)); 501 | 502 | /** Generate random velocity and direction. */ 503 | var angle = airplaneRandomGenerator(-Math.PI, Math.PI); 504 | var velocity = airplaneRandomGenerator(0.005, 0.1); 505 | 506 | obstacleAngle[i] = angle; 507 | 508 | obstacleRotation[i] = quat.create(); 509 | quat.setAxisAngle(obstacleRotation[i], Z_AXIS, angle); 510 | 511 | obstacleVelocity[i] = vec3.fromValues(-velocity * Math.sin(angle), velocity * Math.cos(angle), 0.0); 512 | 513 | /** Initialize model-to-world matrix. */ 514 | obstacleModelMatrix[i] = mat4.create(); 515 | } 516 | } 517 | 518 | /** Helper function to generate random values within a range 519 | * @param {float} min 520 | * @param {float} max 521 | */ 522 | function airplaneRandomGenerator(min, max){ 523 | return min + Math.random() * (max - min); 524 | } 525 | 526 | -------------------------------------------------------------------------------- /terrain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * terrain.js 3 | * 4 | * @fileoverview Manages buffers, arrays, and other data related to terrain. 5 | * @author Po-Han Huang 6 | */ 7 | 8 | /** Identification prefix for terrain shader. */ 9 | var TERRAIN_PREFIX = "terrain"; 10 | 11 | /** GL buffers for terrain. */ 12 | var terrainPositionBuffer; 13 | var terrainIndexBuffer; 14 | var terrainColorBuffer; 15 | var terrainNormalBuffer; 16 | 17 | /** Data array for terrain. */ 18 | var terrainPositionArray; 19 | var terrainIndexArray; 20 | var terrainColorArray; 21 | var terrainNormalArray; 22 | 23 | /** Shader program for terrain. */ 24 | var terrainShaderProgram; 25 | /** Variable locations in shader program. */ 26 | var terrainLocations = {}; 27 | 28 | /** Whether terrain is ready. (Terrain is generated asynchronously.) */ 29 | var terrainReady = false; 30 | 31 | /** Detail level of terrain. */ 32 | var TERRAIN_DETAIL_LEVEL = 8; 33 | /** Number of vertices on each side of terrain. */ 34 | var TERRAIN_SIZE = Math.pow(2, TERRAIN_DETAIL_LEVEL) + 1; 35 | 36 | /** Random generator parameters. See terrainRandomFunction(). */ 37 | var TERRAIN_RANDOM_INITIAL = 0.75; 38 | var TERRAIN_RANDOM_DECAY = 0.6; 39 | 40 | /** Initialize terrain's shader programs and variable locations. */ 41 | function terrainShaderInit(){ 42 | terrainShaderProgram = shaderPrograms[TERRAIN_PREFIX]; 43 | 44 | /** Attributes */ 45 | terrainLocations["aVertexPosition"] = gl.getAttribLocation(terrainShaderProgram, "aVertexPosition"); 46 | gl.enableVertexAttribArray(terrainLocations["aVertexPosition"]); 47 | 48 | terrainLocations["aVertexColor"] = gl.getAttribLocation(terrainShaderProgram, "aVertexColor"); 49 | gl.enableVertexAttribArray(terrainLocations["aVertexColor"]); 50 | 51 | 52 | terrainLocations["aVertexNormal"] = gl.getAttribLocation(terrainShaderProgram, "aVertexNormal"); 53 | gl.enableVertexAttribArray(terrainLocations["aVertexNormal"]); 54 | 55 | /** Uniforms */ 56 | terrainLocations["uPMatrix"] = gl.getUniformLocation(terrainShaderProgram, "uPMatrix"); 57 | terrainLocations["uMVMatrix"] = gl.getUniformLocation(terrainShaderProgram, "uMVMatrix"); 58 | 59 | terrainLocations["uViewOrigin"] = gl.getUniformLocation(terrainShaderProgram, "uViewOrigin"); 60 | terrainLocations["uLightDirection"] = gl.getUniformLocation(terrainShaderProgram, "uLightDirection"); 61 | terrainLocations["uAmbientLight"] = gl.getUniformLocation(terrainShaderProgram, "uAmbientLight"); 62 | terrainLocations["uDiffuseLight"] = gl.getUniformLocation(terrainShaderProgram, "uDiffuseLight"); 63 | terrainLocations["uSpecularLight"] = gl.getUniformLocation(terrainShaderProgram, "uSpecularLight"); 64 | 65 | } 66 | 67 | /** Initialize airplane's buffer. */ 68 | function terrainBufferInit(){ 69 | 70 | terrainPositionBuffer = gl.createBuffer(); 71 | terrainPositionBuffer.itemSize = 3; 72 | terrainPositionBuffer.numOfItems = TERRAIN_SIZE * TERRAIN_SIZE; 73 | 74 | terrainIndexBuffer = gl.createBuffer(); 75 | terrainIndexBuffer.itemSize = 1; 76 | terrainIndexBuffer.numOfItems = 3 * 2 * (TERRAIN_SIZE - 1) * (TERRAIN_SIZE - 1); 77 | 78 | terrainColorBuffer = gl.createBuffer(); 79 | terrainColorBuffer.itemSize = 3; 80 | terrainColorBuffer.numOfItems = TERRAIN_SIZE * TERRAIN_SIZE; 81 | 82 | terrainNormalBuffer = gl.createBuffer(); 83 | terrainNormalBuffer.itemSize = 3; 84 | terrainNormalBuffer.numOfItems = TERRAIN_SIZE * TERRAIN_SIZE; 85 | 86 | } 87 | 88 | /** Terrain draw call */ 89 | function terrainDraw(){ 90 | if(terrainReady){ 91 | 92 | /** Setup variables. */ 93 | gl.useProgram(terrainShaderProgram); 94 | 95 | gl.bindBuffer(gl.ARRAY_BUFFER, terrainPositionBuffer); 96 | gl.vertexAttribPointer(terrainLocations["aVertexPosition"], terrainPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); 97 | 98 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, terrainIndexBuffer); 99 | 100 | gl.bindBuffer(gl.ARRAY_BUFFER, terrainColorBuffer); 101 | gl.vertexAttribPointer(terrainLocations["aVertexColor"], terrainColorBuffer.itemSize, gl.FLOAT, false, 0, 0); 102 | 103 | gl.bindBuffer(gl.ARRAY_BUFFER, terrainNormalBuffer); 104 | gl.vertexAttribPointer(terrainLocations["aVertexNormal"], terrainNormalBuffer.itemSize, gl.FLOAT, false, 0, 0); 105 | 106 | gl.uniformMatrix4fv(terrainLocations["uPMatrix"], false, pMatrix); 107 | gl.uniformMatrix4fv(terrainLocations["uMVMatrix"], false, mvMatrix); 108 | 109 | gl.uniform3fv(terrainLocations["uViewOrigin"], viewOrigin); 110 | gl.uniform3fv(terrainLocations["uLightDirection"], LIGHT_DIRECTION); 111 | gl.uniform3fv(terrainLocations["uAmbientLight"], AMBIENT_LIGHT); 112 | gl.uniform3fv(terrainLocations["uDiffuseLight"], DIFFUSE_LIGHT); 113 | gl.uniform3fv(terrainLocations["uSpecularLight"], SPECULAR_LIGHT); 114 | 115 | /** Draw! */ 116 | gl.drawElements(gl.TRIANGLES, terrainIndexBuffer.numOfItems, gl.UNSIGNED_INT, 0); 117 | } 118 | } 119 | 120 | /** 121 | * Terrain animate call 122 | * 123 | * @param {float} lapse timelapse since last frame in sec 124 | */ 125 | function terrainAnimate(lapse){ 126 | 127 | } 128 | 129 | /** Initialization of terrain.js */ 130 | function terrainInit(){ 131 | 132 | /** Register shaders, draw calls, animate calls. */ 133 | var prefix = "terrain"; 134 | shaderPrefix.push(prefix); 135 | shaderInit[prefix] = terrainShaderInit; 136 | bufferInit[prefix] = terrainBufferInit; 137 | drawFunctions[prefix] = terrainDraw; 138 | animateFunctions[prefix] = terrainAnimate; 139 | 140 | /** Initialize arrays. */ 141 | terrainPositionArray = new Float32Array(3 * TERRAIN_SIZE * TERRAIN_SIZE); 142 | terrainIndexArray = new Int32Array(3 * 2 * (TERRAIN_SIZE - 1) * (TERRAIN_SIZE - 1)); 143 | terrainColorArray = new Float32Array(3 * TERRAIN_SIZE * TERRAIN_SIZE); 144 | terrainNormalArray = new Float32Array(3 * TERRAIN_SIZE * TERRAIN_SIZE); 145 | 146 | /** Start asynchronous terrain generation. 147 | * Terrain is generated asynchronously such that the user would not feel that 148 | * the browser freezes. 149 | * The functions are called on after another in the following order: 150 | *
    151 | *
  • terrainGenerateTerrain()
  • 152 | *
  • terrainGenerateTerrainInterator()
  • 153 | *
  • terrainAddField()
  • 154 | *
  • terrainGenerateIndex()
  • 155 | *
  • terrainGenerateNormal()
  • 156 | *
  • terrainGenerateColor()
  • 157 | *
  • terrainBindBuffer()
  • 158 | *
159 | */ 160 | terrainGenerateTerrain(); 161 | } 162 | 163 | /** Prepare for terrain generation. */ 164 | function terrainGenerateTerrain(){ 165 | 166 | /** Set X and Y coordinates. */ 167 | for(var row = 0; row < TERRAIN_SIZE; row++){ 168 | for(var col = 0; col < TERRAIN_SIZE; col++){ 169 | terrainPositionArray[terrainIndex(row, col) + 0] = -1.0 + 2.0 / (TERRAIN_SIZE - 1) * col; 170 | terrainPositionArray[terrainIndex(row, col) + 1] = -1.0 + 2.0 / (TERRAIN_SIZE - 1) * row; 171 | } 172 | } 173 | 174 | /** Specify the first 9 heights. */ 175 | terrainPositionArray[terrainIndex(0 , 0 ) + 2] = 0.1; 176 | terrainPositionArray[terrainIndex(0 , (TERRAIN_SIZE - 1) / 2) + 2] = -0.3; 177 | terrainPositionArray[terrainIndex(0 , (TERRAIN_SIZE - 1) ) + 2] = 0.1; 178 | terrainPositionArray[terrainIndex((TERRAIN_SIZE - 1) / 2, 0 ) + 2] = 0.6; 179 | terrainPositionArray[terrainIndex((TERRAIN_SIZE - 1) / 2, (TERRAIN_SIZE - 1) / 2) + 2] = 0.8; 180 | terrainPositionArray[terrainIndex((TERRAIN_SIZE - 1) / 2, (TERRAIN_SIZE - 1) ) + 2] = 0.6; 181 | terrainPositionArray[terrainIndex((TERRAIN_SIZE - 1) , 0 ) + 2] = 0.1; 182 | terrainPositionArray[terrainIndex((TERRAIN_SIZE - 1) , (TERRAIN_SIZE - 1) / 2) + 2] = -0.3; 183 | terrainPositionArray[terrainIndex((TERRAIN_SIZE - 1) , (TERRAIN_SIZE - 1) ) + 2] = 0.1; 184 | 185 | /** Asynchronously call the terrain generation iterator. */ 186 | msg("Generating terrain vertices..... 0%", 187 | function(){ 188 | terrainGenerateTerrainInterator( TERRAIN_DETAIL_LEVEL, TERRAIN_DETAIL_LEVEL - 1, terrainRandomFunction)}); 189 | 190 | } 191 | 192 | /** 193 | * An interation of diamond-square algorithm. 194 | * 195 | * This function automatically calls the next iteration. 196 | * 197 | * @param {int} toplevel 198 | * @param {int} currentLevel 199 | * @param {function} randFunction 200 | */ 201 | function terrainGenerateTerrainInterator(topLevel, currentLevel, randFunction){ 202 | 203 | /** Check if the end of interation. */ 204 | if(currentLevel > 0){ 205 | 206 | /** Stride width at current level. */ 207 | var stride = Math.pow(2,currentLevel); 208 | /** Number of strides at current level. */ 209 | var times = Math.pow(2,topLevel - currentLevel); 210 | 211 | /** Diamond step */ 212 | for(var strideRow = 0; strideRow < times; strideRow++){ 213 | for(var strideCol = 0; strideCol < times; strideCol++){ 214 | var row = strideRow * stride; 215 | var col = strideCol * stride; 216 | var corners = [ terrainPositionArray[terrainIndex(row , col ) + 2], 217 | terrainPositionArray[terrainIndex(row , col + stride) + 2], 218 | terrainPositionArray[terrainIndex(row + stride, col + stride) + 2], 219 | terrainPositionArray[terrainIndex(row + stride, col ) + 2]]; 220 | 221 | /** Assign value with average of four corners plus rand. */ 222 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride / 2) + 2] = 223 | (corners[0] + corners[1] + corners[2] + corners[3]) / 4 224 | + terrainRandomFunction(topLevel, currentLevel); 225 | 226 | } 227 | } 228 | 229 | /** Square step */ 230 | for(var strideRow = 0; strideRow < times; strideRow++){ 231 | for(var strideCol = 0; strideCol < times; strideCol++){ 232 | var row = strideRow * stride; 233 | var col = strideCol * stride; 234 | var corners; 235 | 236 | /** Assign height to bottom vertex. */ 237 | if(strideRow == 0){ 238 | corners = [ terrainPositionArray[terrainIndex(row , col ) + 2], 239 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride / 2) + 2], 240 | terrainPositionArray[terrainIndex(row , col + stride ) + 2]]; 241 | terrainPositionArray[terrainIndex(row, col + stride / 2) + 2] = 242 | (corners[0] + corners[1] + corners[2]) / 3 243 | + terrainRandomFunction(topLevel, currentLevel); 244 | } 245 | 246 | /** Assign height to left vertex. */ 247 | if(strideCol == 0){ 248 | corners = [ terrainPositionArray[terrainIndex(row , col ) + 2], 249 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride / 2) + 2], 250 | terrainPositionArray[terrainIndex(row + stride , col ) + 2]]; 251 | terrainPositionArray[terrainIndex(row + stride / 2, col) + 2] = 252 | (corners[0] + corners[1] + corners[2]) / 3 253 | + terrainRandomFunction(topLevel, currentLevel); 254 | } 255 | 256 | /** Assign height to top vertex. */ 257 | if(strideRow == times - 1){ 258 | corners = [ terrainPositionArray[terrainIndex(row + stride , col ) + 2], 259 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride / 2) + 2], 260 | terrainPositionArray[terrainIndex(row + stride , col + stride ) + 2]]; 261 | terrainPositionArray[terrainIndex(row + stride, col + stride / 2) + 2] = 262 | (corners[0] + corners[1] + corners[2]) / 3 263 | + terrainRandomFunction(topLevel, currentLevel); 264 | }else{ 265 | corners = [ terrainPositionArray[terrainIndex(row + stride , col ) + 2], 266 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride / 2) + 2], 267 | terrainPositionArray[terrainIndex(row + stride , col + stride ) + 2], 268 | terrainPositionArray[terrainIndex(row + stride/2*3, col + stride / 2) + 2]]; 269 | terrainPositionArray[terrainIndex(row + stride, col + stride / 2) + 2] = 270 | (corners[0] + corners[1] + corners[2] + corners[3]) / 4 271 | + terrainRandomFunction(topLevel, currentLevel); 272 | } 273 | 274 | /** Assign height to right vertex. */ 275 | if(strideCol == times - 1){ 276 | corners = [ terrainPositionArray[terrainIndex(row , col + stride ) + 2], 277 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride / 2) + 2], 278 | terrainPositionArray[terrainIndex(row + stride , col + stride ) + 2]]; 279 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride) + 2] = 280 | (corners[0] + corners[1] + corners[2]) / 3 281 | + terrainRandomFunction(topLevel, currentLevel); 282 | }else{ 283 | corners = [ terrainPositionArray[terrainIndex(row , col + stride ) + 2], 284 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride / 2) + 2], 285 | terrainPositionArray[terrainIndex(row + stride , col + stride ) + 2], 286 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride/2*3) + 2]]; 287 | terrainPositionArray[terrainIndex(row + stride / 2, col + stride) + 2] = 288 | (corners[0] + corners[1] + corners[2] + corners[3]) / 4 289 | + terrainRandomFunction(topLevel, currentLevel); 290 | } 291 | } 292 | } 293 | 294 | /** Asynchronously call next iteration. */ 295 | msg("Generating terrain vertices..... " + ((topLevel - currentLevel) / topLevel * 100).toFixed(0) + "%", 296 | function(){ 297 | terrainGenerateTerrainInterator(topLevel, currentLevel - 1, randFunction)}); 298 | }else{ 299 | 300 | /** Asynchronously call terrainAddField(). */ 301 | msg("Generating terrain fields..... 100%", 302 | function(){ 303 | terrainAddField();}); 304 | } 305 | } 306 | 307 | /** Random number generator with decay along levels. 308 | * @param {int} topLevel 309 | * @param {int} currentLevel 310 | */ 311 | function terrainRandomFunction(topLevel, currentLevel){ 312 | /** rand is uniform distibution within 313 | * +- 0.5 * TERRAIN_RANDOM_INITIAL * (TERRAIN_RANDOM_DECAY)^(topLevel - currentLevel) 314 | */ 315 | return (Math.random() - 0.5) * TERRAIN_RANDOM_INITIAL * Math.pow(TERRAIN_RANDOM_DECAY, topLevel - currentLevel); 316 | } 317 | 318 | /** Generate flat regions. */ 319 | function terrainAddField(){ 320 | 321 | for(var row = 0; row < TERRAIN_SIZE; row++){ 322 | for(var col = 0; col < TERRAIN_SIZE; col++){ 323 | 324 | var x = terrainPositionArray[terrainIndex(row,col) + 0]; 325 | var y = terrainPositionArray[terrainIndex(row,col) + 1]; 326 | var z = terrainPositionArray[terrainIndex(row,col) + 2]; 327 | 328 | if(z < 0.0){ 329 | /** If original height is negative. */ 330 | terrainPositionArray[terrainIndex(row,col) + 2] = 0.0; 331 | }else if(x <= 0.004 && x >= -0.004 && ((y >= -1.0 && y <= -0.9)||(y >= 0.9 && y <= 1.0))){ 332 | /** If location is in runways. */ 333 | terrainPositionArray[terrainIndex(row,col) + 2] = 0.0; 334 | }else if(row === 0 || col === 0 || row === TERRAIN_SIZE-1 || col === TERRAIN_SIZE-1){ 335 | /** Generate cliffs for the outer vertices. */ 336 | terrainPositionArray[terrainIndex(row,col) + 2] = 0.0; 337 | } 338 | } 339 | } 340 | 341 | /** Asynchronously call terrainGenerateIndex(). */ 342 | msg("Generating terrain indices..... 100%", 343 | function(){ 344 | terrainGenerateIndex();}); 345 | } 346 | 347 | /** Generate indices. */ 348 | function terrainGenerateIndex(){ 349 | 350 | var idx = 0; 351 | for(var row = 0; row < TERRAIN_SIZE - 1; row++){ 352 | for(var col = 0; col < TERRAIN_SIZE - 1; col++){ 353 | /** Lower triangle */ 354 | terrainIndexArray[idx + 0] = row * TERRAIN_SIZE + col; 355 | terrainIndexArray[idx + 1] = row * TERRAIN_SIZE + col + 1; 356 | terrainIndexArray[idx + 2] = (row + 1) * TERRAIN_SIZE + col; 357 | /** Upper triangle */ 358 | terrainIndexArray[idx + 3] = row * TERRAIN_SIZE + col + 1; 359 | terrainIndexArray[idx + 4] = (row + 1) * TERRAIN_SIZE + col + 1; 360 | terrainIndexArray[idx + 5] = (row + 1) * TERRAIN_SIZE + col; 361 | idx = idx + 6; 362 | } 363 | } 364 | 365 | /** Asynchronously call terrainGenerateNormal(). */ 366 | msg("Generating terrain indices.....", 367 | function(){ 368 | terrainGenerateNormal();}); 369 | } 370 | 371 | /** Generate normals. */ 372 | function terrainGenerateNormal(){ 373 | 374 | for(var row = 0; row < TERRAIN_SIZE; row++){ 375 | for(var col = 0; col < TERRAIN_SIZE; col++){ 376 | 377 | /** Normal of a vertex is calculated by averaging the normals of the six 378 | * triangles connected to the vertex. 379 | */ 380 | 381 | var count = 0; 382 | var sum = vec3.create(); 383 | var self = vec3.fromValues( terrainPositionArray[terrainIndex(row, col) + 0], 384 | terrainPositionArray[terrainIndex(row, col) + 1], 385 | terrainPositionArray[terrainIndex(row, col) + 2]); 386 | 387 | /** Add normal of bottom left triangle. */ 388 | if(row > 0 && col > 0){ 389 | var first = vec3.fromValues(terrainPositionArray[terrainIndex(row + 0, col - 1) + 0], 390 | terrainPositionArray[terrainIndex(row + 0, col - 1) + 1], 391 | terrainPositionArray[terrainIndex(row + 0, col - 1) + 2]); 392 | var second = vec3.fromValues(terrainPositionArray[terrainIndex(row - 1, col + 0) + 0], 393 | terrainPositionArray[terrainIndex(row - 1, col + 0) + 1], 394 | terrainPositionArray[terrainIndex(row - 1, col + 0) + 2]); 395 | var normal = vec3.create(); 396 | vec3.subtract(first, self, first); 397 | vec3.subtract(second, self, second); 398 | vec3.cross(normal, first, second); 399 | vec3.normalize(normal, normal); 400 | vec3.add(sum, sum, normal); 401 | count++; 402 | } 403 | 404 | /** Add normals of the two bottom right triangles. */ 405 | if(row > 0 && col < TERRAIN_SIZE - 1){ 406 | var first = vec3.fromValues(terrainPositionArray[terrainIndex(row - 1, col + 0) + 0], 407 | terrainPositionArray[terrainIndex(row - 1, col + 0) + 1], 408 | terrainPositionArray[terrainIndex(row - 1, col + 0) + 2]); 409 | var second = vec3.fromValues(terrainPositionArray[terrainIndex(row - 1, col + 1) + 0], 410 | terrainPositionArray[terrainIndex(row - 1, col + 1) + 1], 411 | terrainPositionArray[terrainIndex(row - 1, col + 1) + 2]); 412 | var normal = vec3.create(); 413 | vec3.subtract(first, self, first); 414 | vec3.subtract(second, self, second); 415 | vec3.cross(normal, first, second); 416 | vec3.normalize(normal, normal); 417 | vec3.add(sum, sum, normal); 418 | count++; 419 | } 420 | 421 | if(row > 0 && col < TERRAIN_SIZE - 1){ 422 | var first = vec3.fromValues(terrainPositionArray[terrainIndex(row - 1, col + 1) + 0], 423 | terrainPositionArray[terrainIndex(row - 1, col + 1) + 1], 424 | terrainPositionArray[terrainIndex(row - 1, col + 1) + 2]); 425 | var second = vec3.fromValues(terrainPositionArray[terrainIndex(row + 0, col + 1)+ 0], 426 | terrainPositionArray[terrainIndex(row + 0, col + 1)+ 1], 427 | terrainPositionArray[terrainIndex(row + 0, col + 1)+ 2]); 428 | var normal = vec3.create(); 429 | vec3.subtract(first, self, first); 430 | vec3.subtract(second, self, second); 431 | vec3.cross(normal, first, second); 432 | vec3.normalize(normal, normal); 433 | vec3.add(sum, sum, normal); 434 | count++; 435 | } 436 | 437 | /** Add normal of top left triangle. */ 438 | if(row < TERRAIN_SIZE - 1 && col < TERRAIN_SIZE - 1){ 439 | var first = vec3.fromValues(terrainPositionArray[terrainIndex(row + 0, col + 1)+ 0], 440 | terrainPositionArray[terrainIndex(row + 0, col + 1)+ 1], 441 | terrainPositionArray[terrainIndex(row + 0, col + 1)+ 2]); 442 | var second = vec3.fromValues(terrainPositionArray[terrainIndex(row + 1, col + 0)+ 0], 443 | terrainPositionArray[terrainIndex(row + 1, col + 0)+ 1], 444 | terrainPositionArray[terrainIndex(row + 1, col + 0)+ 2]); 445 | var normal = vec3.create(); 446 | vec3.subtract(first, self, first); 447 | vec3.subtract(second, self, second); 448 | vec3.cross(normal, first, second); 449 | vec3.normalize(normal, normal); 450 | vec3.add(sum, sum, normal); 451 | count++; 452 | } 453 | 454 | /** Add normals of the two top left triangles. */ 455 | if(row < TERRAIN_SIZE - 1 && col > 0){ 456 | var first = vec3.fromValues(terrainPositionArray[terrainIndex(row + 1, col + 0)+ 0], 457 | terrainPositionArray[terrainIndex(row + 1, col + 0)+ 1], 458 | terrainPositionArray[terrainIndex(row + 1, col + 0)+ 2]); 459 | var second = vec3.fromValues(terrainPositionArray[terrainIndex(row + 1, col - 1) + 0], 460 | terrainPositionArray[terrainIndex(row + 1, col - 1) + 1], 461 | terrainPositionArray[terrainIndex(row + 1, col - 1) + 2]); 462 | var normal = vec3.create(); 463 | vec3.subtract(first, self, first); 464 | vec3.subtract(second, self, second); 465 | vec3.cross(normal, first, second); 466 | vec3.normalize(normal, normal); 467 | vec3.add(sum, sum, normal); 468 | count++; 469 | } 470 | 471 | if(row < TERRAIN_SIZE - 1 && col > 0){ 472 | var first = vec3.fromValues(terrainPositionArray[terrainIndex(row + 1, col - 1) + 0], 473 | terrainPositionArray[terrainIndex(row + 1, col - 1) + 1], 474 | terrainPositionArray[terrainIndex(row + 1, col - 1) + 2]); 475 | var second = vec3.fromValues(terrainPositionArray[terrainIndex(row + 0, col - 1) + 0], 476 | terrainPositionArray[terrainIndex(row + 0, col - 1) + 1], 477 | terrainPositionArray[terrainIndex(row + 0, col - 1) + 2]); 478 | var normal = vec3.create(); 479 | vec3.subtract(first, self, first); 480 | vec3.subtract(second, self, second); 481 | vec3.cross(normal, first, second); 482 | vec3.normalize(normal, normal); 483 | vec3.add(sum, sum, normal); 484 | count++; 485 | } 486 | 487 | /** Take average. */ 488 | vec3.scale(sum, sum, 1 / count); 489 | 490 | terrainNormalArray[terrainIndex(row, col) + 0] = sum[0]; 491 | terrainNormalArray[terrainIndex(row, col) + 1] = sum[1]; 492 | terrainNormalArray[terrainIndex(row, col) + 2] = sum[2]; 493 | 494 | } 495 | } 496 | 497 | /** Asynchronously call terrainGenerateColor(). */ 498 | msg("Generating terrain colors.....", 499 | function(){ 500 | terrainGenerateColor();}); 501 | } 502 | 503 | /** Generate colors. */ 504 | function terrainGenerateColor(){ 505 | 506 | for(var row = 0; row < TERRAIN_SIZE; row++){ 507 | for(var col = 0; col < TERRAIN_SIZE; col++){ 508 | 509 | /** Get height of the vertex. */ 510 | var height = terrainPositionArray[terrainIndex(row, col) + 2]; 511 | 512 | /** Colors from high to low: White -> Dark green -> Bright green */ 513 | if(height > 0.6){ 514 | terrainColorArray[terrainIndex(row, col) + 0] = 1.4; 515 | terrainColorArray[terrainIndex(row, col) + 1] = 1.4; 516 | terrainColorArray[terrainIndex(row, col) + 2] = 1.4; 517 | }else{ 518 | terrainColorArray[terrainIndex(row, col) + 0] = 0.0; 519 | terrainColorArray[terrainIndex(row, col) + 1] = 1.2-height/0.6*0.9; 520 | terrainColorArray[terrainIndex(row, col) + 2] = 0.0; 521 | } 522 | 523 | } 524 | } 525 | 526 | /** Asynchronously call terrainBindBuffer(). */ 527 | msg("Binding terrain buffers.....", 528 | function(){ 529 | terrainBindBuffer();}); 530 | } 531 | 532 | /** Bind terrain buffers. */ 533 | function terrainBindBuffer(){ 534 | 535 | gl.bindBuffer(gl.ARRAY_BUFFER, terrainPositionBuffer); 536 | gl.bufferData(gl.ARRAY_BUFFER, terrainPositionArray, gl.STATIC_DRAW); 537 | gl.vertexAttribPointer(terrainLocations["aVertexPosition"], terrainPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); 538 | 539 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, terrainIndexBuffer); 540 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, terrainIndexArray, gl.STATIC_DRAW); 541 | 542 | gl.bindBuffer(gl.ARRAY_BUFFER, terrainColorBuffer); 543 | gl.bufferData(gl.ARRAY_BUFFER, terrainColorArray, gl.STATIC_DRAW); 544 | gl.vertexAttribPointer(terrainLocations["aVertexColor"], terrainColorBuffer.itemSize, gl.FLOAT, false, 0, 0); 545 | 546 | gl.bindBuffer(gl.ARRAY_BUFFER, terrainNormalBuffer); 547 | gl.bufferData(gl.ARRAY_BUFFER, terrainNormalArray, gl.STATIC_DRAW); 548 | gl.vertexAttribPointer(terrainLocations["aVertexNormal"], terrainNormalBuffer.itemSize, gl.FLOAT, false, 0, 0); 549 | 550 | /** Finally, the terrain is ready! */ 551 | terrainReady = true; 552 | msg("Initialization done! Start having fun!"); 553 | } 554 | 555 | /** Helper function to get index of a vertex. 556 | * @param {int} row 557 | * @param {int} col 558 | */ 559 | function terrainIndex(row, col){ 560 | return (row * TERRAIN_SIZE + col) * 3; 561 | } 562 | 563 | -------------------------------------------------------------------------------- /webgl-debug.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright (c) 2012 The Khronos Group Inc. 3 | ** 4 | ** Permission is hereby granted, free of charge, to any person obtaining a 5 | ** copy of this software and/or associated documentation files (the 6 | ** "Materials"), to deal in the Materials without restriction, including 7 | ** without limitation the rights to use, copy, modify, merge, publish, 8 | ** distribute, sublicense, and/or sell copies of the Materials, and to 9 | ** permit persons to whom the Materials are furnished to do so, subject to 10 | ** the following conditions: 11 | ** 12 | ** The above copyright notice and this permission notice shall be included 13 | ** in all copies or substantial portions of the Materials. 14 | ** 15 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 22 | */ 23 | 24 | // Various functions for helping debug WebGL apps. 25 | 26 | WebGLDebugUtils = function() { 27 | 28 | /** 29 | * Wrapped logging function. 30 | * @param {string} msg Message to log. 31 | */ 32 | var log = function(msg) { 33 | if (window.console && window.console.log) { 34 | window.console.log(msg); 35 | } 36 | }; 37 | 38 | /** 39 | * Wrapped error logging function. 40 | * @param {string} msg Message to log. 41 | */ 42 | var error = function(msg) { 43 | if (window.console && window.console.error) { 44 | window.console.error(msg); 45 | } else { 46 | log(msg); 47 | } 48 | }; 49 | 50 | 51 | /** 52 | * Which arguments are enums based on the number of arguments to the function. 53 | * So 54 | * 'texImage2D': { 55 | * 9: { 0:true, 2:true, 6:true, 7:true }, 56 | * 6: { 0:true, 2:true, 3:true, 4:true }, 57 | * }, 58 | * 59 | * means if there are 9 arguments then 6 and 7 are enums, if there are 6 60 | * arguments 3 and 4 are enums 61 | * 62 | * @type {!Object.} 63 | */ 64 | var glValidEnumContexts = { 65 | // Generic setters and getters 66 | 67 | 'enable': {1: { 0:true }}, 68 | 'disable': {1: { 0:true }}, 69 | 'getParameter': {1: { 0:true }}, 70 | 71 | // Rendering 72 | 73 | 'drawArrays': {3:{ 0:true }}, 74 | 'drawElements': {4:{ 0:true, 2:true }}, 75 | 76 | // Shaders 77 | 78 | 'createShader': {1: { 0:true }}, 79 | 'getShaderParameter': {2: { 1:true }}, 80 | 'getProgramParameter': {2: { 1:true }}, 81 | 'getShaderPrecisionFormat': {2: { 0: true, 1:true }}, 82 | 83 | // Vertex attributes 84 | 85 | 'getVertexAttrib': {2: { 1:true }}, 86 | 'vertexAttribPointer': {6: { 2:true }}, 87 | 88 | // Textures 89 | 90 | 'bindTexture': {2: { 0:true }}, 91 | 'activeTexture': {1: { 0:true }}, 92 | 'getTexParameter': {2: { 0:true, 1:true }}, 93 | 'texParameterf': {3: { 0:true, 1:true }}, 94 | 'texParameteri': {3: { 0:true, 1:true, 2:true }}, 95 | 'texImage2D': { 96 | 9: { 0:true, 2:true, 6:true, 7:true }, 97 | 6: { 0:true, 2:true, 3:true, 4:true } 98 | }, 99 | 'texSubImage2D': { 100 | 9: { 0:true, 6:true, 7:true }, 101 | 7: { 0:true, 4:true, 5:true } 102 | }, 103 | 'copyTexImage2D': {8: { 0:true, 2:true }}, 104 | 'copyTexSubImage2D': {8: { 0:true }}, 105 | 'generateMipmap': {1: { 0:true }}, 106 | 'compressedTexImage2D': {7: { 0: true, 2:true }}, 107 | 'compressedTexSubImage2D': {8: { 0: true, 6:true }}, 108 | 109 | // Buffer objects 110 | 111 | 'bindBuffer': {2: { 0:true }}, 112 | 'bufferData': {3: { 0:true, 2:true }}, 113 | 'bufferSubData': {3: { 0:true }}, 114 | 'getBufferParameter': {2: { 0:true, 1:true }}, 115 | 116 | // Renderbuffers and framebuffers 117 | 118 | 'pixelStorei': {2: { 0:true, 1:true }}, 119 | 'readPixels': {7: { 4:true, 5:true }}, 120 | 'bindRenderbuffer': {2: { 0:true }}, 121 | 'bindFramebuffer': {2: { 0:true }}, 122 | 'checkFramebufferStatus': {1: { 0:true }}, 123 | 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }}, 124 | 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }}, 125 | 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }}, 126 | 'getRenderbufferParameter': {2: { 0:true, 1:true }}, 127 | 'renderbufferStorage': {4: { 0:true, 1:true }}, 128 | 129 | // Frame buffer operations (clear, blend, depth test, stencil) 130 | 131 | 'clear': {1: { 0: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }}}, 132 | 'depthFunc': {1: { 0:true }}, 133 | 'blendFunc': {2: { 0:true, 1:true }}, 134 | 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, 135 | 'blendEquation': {1: { 0:true }}, 136 | 'blendEquationSeparate': {2: { 0:true, 1:true }}, 137 | 'stencilFunc': {3: { 0:true }}, 138 | 'stencilFuncSeparate': {4: { 0:true, 1:true }}, 139 | 'stencilMaskSeparate': {2: { 0:true }}, 140 | 'stencilOp': {3: { 0:true, 1:true, 2:true }}, 141 | 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, 142 | 143 | // Culling 144 | 145 | 'cullFace': {1: { 0:true }}, 146 | 'frontFace': {1: { 0:true }}, 147 | 148 | // ANGLE_instanced_arrays extension 149 | 150 | 'drawArraysInstancedANGLE': {4: { 0:true }}, 151 | 'drawElementsInstancedANGLE': {5: { 0:true, 2:true }}, 152 | 153 | // EXT_blend_minmax extension 154 | 155 | 'blendEquationEXT': {1: { 0:true }} 156 | }; 157 | 158 | /** 159 | * Map of numbers to names. 160 | * @type {Object} 161 | */ 162 | var glEnums = null; 163 | 164 | /** 165 | * Map of names to numbers. 166 | * @type {Object} 167 | */ 168 | var enumStringToValue = null; 169 | 170 | /** 171 | * Initializes this module. Safe to call more than once. 172 | * @param {!WebGLRenderingContext} ctx A WebGL context. If 173 | * you have more than one context it doesn't matter which one 174 | * you pass in, it is only used to pull out constants. 175 | */ 176 | function init(ctx) { 177 | if (glEnums == null) { 178 | glEnums = { }; 179 | enumStringToValue = { }; 180 | for (var propertyName in ctx) { 181 | if (typeof ctx[propertyName] == 'number') { 182 | glEnums[ctx[propertyName]] = propertyName; 183 | enumStringToValue[propertyName] = ctx[propertyName]; 184 | } 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * Checks the utils have been initialized. 191 | */ 192 | function checkInit() { 193 | if (glEnums == null) { 194 | throw 'WebGLDebugUtils.init(ctx) not called'; 195 | } 196 | } 197 | 198 | /** 199 | * Returns true or false if value matches any WebGL enum 200 | * @param {*} value Value to check if it might be an enum. 201 | * @return {boolean} True if value matches one of the WebGL defined enums 202 | */ 203 | function mightBeEnum(value) { 204 | checkInit(); 205 | return (glEnums[value] !== undefined); 206 | } 207 | 208 | /** 209 | * Gets an string version of an WebGL enum. 210 | * 211 | * Example: 212 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); 213 | * 214 | * @param {number} value Value to return an enum for 215 | * @return {string} The string version of the enum. 216 | */ 217 | function glEnumToString(value) { 218 | checkInit(); 219 | var name = glEnums[value]; 220 | return (name !== undefined) ? ("gl." + name) : 221 | ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + ""); 222 | } 223 | 224 | /** 225 | * Returns the string version of a WebGL argument. 226 | * Attempts to convert enum arguments to strings. 227 | * @param {string} functionName the name of the WebGL function. 228 | * @param {number} numArgs the number of arguments passed to the function. 229 | * @param {number} argumentIndx the index of the argument. 230 | * @param {*} value The value of the argument. 231 | * @return {string} The value as a string. 232 | */ 233 | function glFunctionArgToString(functionName, numArgs, argumentIndex, value) { 234 | var funcInfo = glValidEnumContexts[functionName]; 235 | if (funcInfo !== undefined) { 236 | var funcInfo = funcInfo[numArgs]; 237 | if (funcInfo !== undefined) { 238 | if (funcInfo[argumentIndex]) { 239 | if (typeof funcInfo[argumentIndex] === 'object' && 240 | funcInfo[argumentIndex]['enumBitwiseOr'] !== undefined) { 241 | var enums = funcInfo[argumentIndex]['enumBitwiseOr']; 242 | var orResult = 0; 243 | var orEnums = []; 244 | for (var i = 0; i < enums.length; ++i) { 245 | var enumValue = enumStringToValue[enums[i]]; 246 | if ((value & enumValue) !== 0) { 247 | orResult |= enumValue; 248 | orEnums.push(glEnumToString(enumValue)); 249 | } 250 | } 251 | if (orResult === value) { 252 | return orEnums.join(' | '); 253 | } else { 254 | return glEnumToString(value); 255 | } 256 | } else { 257 | return glEnumToString(value); 258 | } 259 | } 260 | } 261 | } 262 | if (value === null) { 263 | return "null"; 264 | } else if (value === undefined) { 265 | return "undefined"; 266 | } else { 267 | return value.toString(); 268 | } 269 | } 270 | 271 | /** 272 | * Converts the arguments of a WebGL function to a string. 273 | * Attempts to convert enum arguments to strings. 274 | * 275 | * @param {string} functionName the name of the WebGL function. 276 | * @param {number} args The arguments. 277 | * @return {string} The arguments as a string. 278 | */ 279 | function glFunctionArgsToString(functionName, args) { 280 | // apparently we can't do args.join(","); 281 | var argStr = ""; 282 | var numArgs = args.length; 283 | for (var ii = 0; ii < numArgs; ++ii) { 284 | argStr += ((ii == 0) ? '' : ', ') + 285 | glFunctionArgToString(functionName, numArgs, ii, args[ii]); 286 | } 287 | return argStr; 288 | }; 289 | 290 | 291 | function makePropertyWrapper(wrapper, original, propertyName) { 292 | //log("wrap prop: " + propertyName); 293 | wrapper.__defineGetter__(propertyName, function() { 294 | return original[propertyName]; 295 | }); 296 | // TODO(gmane): this needs to handle properties that take more than 297 | // one value? 298 | wrapper.__defineSetter__(propertyName, function(value) { 299 | //log("set: " + propertyName); 300 | original[propertyName] = value; 301 | }); 302 | } 303 | 304 | // Makes a function that calls a function on another object. 305 | function makeFunctionWrapper(original, functionName) { 306 | //log("wrap fn: " + functionName); 307 | var f = original[functionName]; 308 | return function() { 309 | //log("call: " + functionName); 310 | var result = f.apply(original, arguments); 311 | return result; 312 | }; 313 | } 314 | 315 | /** 316 | * Given a WebGL context returns a wrapped context that calls 317 | * gl.getError after every command and calls a function if the 318 | * result is not gl.NO_ERROR. 319 | * 320 | * @param {!WebGLRenderingContext} ctx The webgl context to 321 | * wrap. 322 | * @param {!function(err, funcName, args): void} opt_onErrorFunc 323 | * The function to call when gl.getError returns an 324 | * error. If not specified the default function calls 325 | * console.log with a message. 326 | * @param {!function(funcName, args): void} opt_onFunc The 327 | * function to call when each webgl function is called. 328 | * You can use this to log all calls for example. 329 | * @param {!WebGLRenderingContext} opt_err_ctx The webgl context 330 | * to call getError on if different than ctx. 331 | */ 332 | function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc, opt_err_ctx) { 333 | opt_err_ctx = opt_err_ctx || ctx; 334 | init(ctx); 335 | opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { 336 | // apparently we can't do args.join(","); 337 | var argStr = ""; 338 | var numArgs = args.length; 339 | for (var ii = 0; ii < numArgs; ++ii) { 340 | argStr += ((ii == 0) ? '' : ', ') + 341 | glFunctionArgToString(functionName, numArgs, ii, args[ii]); 342 | } 343 | error("WebGL error "+ glEnumToString(err) + " in "+ functionName + 344 | "(" + argStr + ")"); 345 | }; 346 | 347 | // Holds booleans for each GL error so after we get the error ourselves 348 | // we can still return it to the client app. 349 | var glErrorShadow = { }; 350 | 351 | // Makes a function that calls a WebGL function and then calls getError. 352 | function makeErrorWrapper(ctx, functionName) { 353 | return function() { 354 | if (opt_onFunc) { 355 | opt_onFunc(functionName, arguments); 356 | } 357 | var result = ctx[functionName].apply(ctx, arguments); 358 | var err = opt_err_ctx.getError(); 359 | if (err != 0) { 360 | glErrorShadow[err] = true; 361 | opt_onErrorFunc(err, functionName, arguments); 362 | } 363 | return result; 364 | }; 365 | } 366 | 367 | // Make a an object that has a copy of every property of the WebGL context 368 | // but wraps all functions. 369 | var wrapper = {}; 370 | for (var propertyName in ctx) { 371 | if (typeof ctx[propertyName] == 'function') { 372 | if (propertyName != 'getExtension') { 373 | wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); 374 | } else { 375 | var wrapped = makeErrorWrapper(ctx, propertyName); 376 | wrapper[propertyName] = function () { 377 | var result = wrapped.apply(ctx, arguments); 378 | return makeDebugContext(result, opt_onErrorFunc, opt_onFunc, opt_err_ctx); 379 | }; 380 | } 381 | } else { 382 | makePropertyWrapper(wrapper, ctx, propertyName); 383 | } 384 | } 385 | 386 | // Override the getError function with one that returns our saved results. 387 | wrapper.getError = function() { 388 | for (var err in glErrorShadow) { 389 | if (glErrorShadow.hasOwnProperty(err)) { 390 | if (glErrorShadow[err]) { 391 | glErrorShadow[err] = false; 392 | return err; 393 | } 394 | } 395 | } 396 | return ctx.NO_ERROR; 397 | }; 398 | 399 | return wrapper; 400 | } 401 | 402 | function resetToInitialState(ctx) { 403 | var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); 404 | var tmp = ctx.createBuffer(); 405 | ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); 406 | for (var ii = 0; ii < numAttribs; ++ii) { 407 | ctx.disableVertexAttribArray(ii); 408 | ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); 409 | ctx.vertexAttrib1f(ii, 0); 410 | } 411 | ctx.deleteBuffer(tmp); 412 | 413 | var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); 414 | for (var ii = 0; ii < numTextureUnits; ++ii) { 415 | ctx.activeTexture(ctx.TEXTURE0 + ii); 416 | ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); 417 | ctx.bindTexture(ctx.TEXTURE_2D, null); 418 | } 419 | 420 | ctx.activeTexture(ctx.TEXTURE0); 421 | ctx.useProgram(null); 422 | ctx.bindBuffer(ctx.ARRAY_BUFFER, null); 423 | ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); 424 | ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); 425 | ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); 426 | ctx.disable(ctx.BLEND); 427 | ctx.disable(ctx.CULL_FACE); 428 | ctx.disable(ctx.DEPTH_TEST); 429 | ctx.disable(ctx.DITHER); 430 | ctx.disable(ctx.SCISSOR_TEST); 431 | ctx.blendColor(0, 0, 0, 0); 432 | ctx.blendEquation(ctx.FUNC_ADD); 433 | ctx.blendFunc(ctx.ONE, ctx.ZERO); 434 | ctx.clearColor(0, 0, 0, 0); 435 | ctx.clearDepth(1); 436 | ctx.clearStencil(-1); 437 | ctx.colorMask(true, true, true, true); 438 | ctx.cullFace(ctx.BACK); 439 | ctx.depthFunc(ctx.LESS); 440 | ctx.depthMask(true); 441 | ctx.depthRange(0, 1); 442 | ctx.frontFace(ctx.CCW); 443 | ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); 444 | ctx.lineWidth(1); 445 | ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); 446 | ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); 447 | ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); 448 | ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); 449 | // TODO: Delete this IF. 450 | if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { 451 | ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); 452 | } 453 | ctx.polygonOffset(0, 0); 454 | ctx.sampleCoverage(1, false); 455 | ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); 456 | ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); 457 | ctx.stencilMask(0xFFFFFFFF); 458 | ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); 459 | ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); 460 | ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); 461 | 462 | // TODO: This should NOT be needed but Firefox fails with 'hint' 463 | while(ctx.getError()); 464 | } 465 | 466 | function makeLostContextSimulatingCanvas(canvas) { 467 | var unwrappedContext_; 468 | var wrappedContext_; 469 | var onLost_ = []; 470 | var onRestored_ = []; 471 | var wrappedContext_ = {}; 472 | var contextId_ = 1; 473 | var contextLost_ = false; 474 | var resourceId_ = 0; 475 | var resourceDb_ = []; 476 | var numCallsToLoseContext_ = 0; 477 | var numCalls_ = 0; 478 | var canRestore_ = false; 479 | var restoreTimeout_ = 0; 480 | 481 | // Holds booleans for each GL error so can simulate errors. 482 | var glErrorShadow_ = { }; 483 | 484 | canvas.getContext = function(f) { 485 | return function() { 486 | var ctx = f.apply(canvas, arguments); 487 | // Did we get a context and is it a WebGL context? 488 | if (ctx instanceof WebGLRenderingContext) { 489 | if (ctx != unwrappedContext_) { 490 | if (unwrappedContext_) { 491 | throw "got different context" 492 | } 493 | unwrappedContext_ = ctx; 494 | wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_); 495 | } 496 | return wrappedContext_; 497 | } 498 | return ctx; 499 | } 500 | }(canvas.getContext); 501 | 502 | function wrapEvent(listener) { 503 | if (typeof(listener) == "function") { 504 | return listener; 505 | } else { 506 | return function(info) { 507 | listener.handleEvent(info); 508 | } 509 | } 510 | } 511 | 512 | var addOnContextLostListener = function(listener) { 513 | onLost_.push(wrapEvent(listener)); 514 | }; 515 | 516 | var addOnContextRestoredListener = function(listener) { 517 | onRestored_.push(wrapEvent(listener)); 518 | }; 519 | 520 | 521 | function wrapAddEventListener(canvas) { 522 | var f = canvas.addEventListener; 523 | canvas.addEventListener = function(type, listener, bubble) { 524 | switch (type) { 525 | case 'webglcontextlost': 526 | addOnContextLostListener(listener); 527 | break; 528 | case 'webglcontextrestored': 529 | addOnContextRestoredListener(listener); 530 | break; 531 | default: 532 | f.apply(canvas, arguments); 533 | } 534 | }; 535 | } 536 | 537 | wrapAddEventListener(canvas); 538 | 539 | canvas.loseContext = function() { 540 | if (!contextLost_) { 541 | contextLost_ = true; 542 | numCallsToLoseContext_ = 0; 543 | ++contextId_; 544 | while (unwrappedContext_.getError()); 545 | clearErrors(); 546 | glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true; 547 | var event = makeWebGLContextEvent("context lost"); 548 | var callbacks = onLost_.slice(); 549 | setTimeout(function() { 550 | //log("numCallbacks:" + callbacks.length); 551 | for (var ii = 0; ii < callbacks.length; ++ii) { 552 | //log("calling callback:" + ii); 553 | callbacks[ii](event); 554 | } 555 | if (restoreTimeout_ >= 0) { 556 | setTimeout(function() { 557 | canvas.restoreContext(); 558 | }, restoreTimeout_); 559 | } 560 | }, 0); 561 | } 562 | }; 563 | 564 | canvas.restoreContext = function() { 565 | if (contextLost_) { 566 | if (onRestored_.length) { 567 | setTimeout(function() { 568 | if (!canRestore_) { 569 | throw "can not restore. webglcontestlost listener did not call event.preventDefault"; 570 | } 571 | freeResources(); 572 | resetToInitialState(unwrappedContext_); 573 | contextLost_ = false; 574 | numCalls_ = 0; 575 | canRestore_ = false; 576 | var callbacks = onRestored_.slice(); 577 | var event = makeWebGLContextEvent("context restored"); 578 | for (var ii = 0; ii < callbacks.length; ++ii) { 579 | callbacks[ii](event); 580 | } 581 | }, 0); 582 | } 583 | } 584 | }; 585 | 586 | canvas.loseContextInNCalls = function(numCalls) { 587 | if (contextLost_) { 588 | throw "You can not ask a lost contet to be lost"; 589 | } 590 | numCallsToLoseContext_ = numCalls_ + numCalls; 591 | }; 592 | 593 | canvas.getNumCalls = function() { 594 | return numCalls_; 595 | }; 596 | 597 | canvas.setRestoreTimeout = function(timeout) { 598 | restoreTimeout_ = timeout; 599 | }; 600 | 601 | function isWebGLObject(obj) { 602 | //return false; 603 | return (obj instanceof WebGLBuffer || 604 | obj instanceof WebGLFramebuffer || 605 | obj instanceof WebGLProgram || 606 | obj instanceof WebGLRenderbuffer || 607 | obj instanceof WebGLShader || 608 | obj instanceof WebGLTexture); 609 | } 610 | 611 | function checkResources(args) { 612 | for (var ii = 0; ii < args.length; ++ii) { 613 | var arg = args[ii]; 614 | if (isWebGLObject(arg)) { 615 | return arg.__webglDebugContextLostId__ == contextId_; 616 | } 617 | } 618 | return true; 619 | } 620 | 621 | function clearErrors() { 622 | var k = Object.keys(glErrorShadow_); 623 | for (var ii = 0; ii < k.length; ++ii) { 624 | delete glErrorShadow_[k]; 625 | } 626 | } 627 | 628 | function loseContextIfTime() { 629 | ++numCalls_; 630 | if (!contextLost_) { 631 | if (numCallsToLoseContext_ == numCalls_) { 632 | canvas.loseContext(); 633 | } 634 | } 635 | } 636 | 637 | // Makes a function that simulates WebGL when out of context. 638 | function makeLostContextFunctionWrapper(ctx, functionName) { 639 | var f = ctx[functionName]; 640 | return function() { 641 | // log("calling:" + functionName); 642 | // Only call the functions if the context is not lost. 643 | loseContextIfTime(); 644 | if (!contextLost_) { 645 | //if (!checkResources(arguments)) { 646 | // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true; 647 | // return; 648 | //} 649 | var result = f.apply(ctx, arguments); 650 | return result; 651 | } 652 | }; 653 | } 654 | 655 | function freeResources() { 656 | for (var ii = 0; ii < resourceDb_.length; ++ii) { 657 | var resource = resourceDb_[ii]; 658 | if (resource instanceof WebGLBuffer) { 659 | unwrappedContext_.deleteBuffer(resource); 660 | } else if (resource instanceof WebGLFramebuffer) { 661 | unwrappedContext_.deleteFramebuffer(resource); 662 | } else if (resource instanceof WebGLProgram) { 663 | unwrappedContext_.deleteProgram(resource); 664 | } else if (resource instanceof WebGLRenderbuffer) { 665 | unwrappedContext_.deleteRenderbuffer(resource); 666 | } else if (resource instanceof WebGLShader) { 667 | unwrappedContext_.deleteShader(resource); 668 | } else if (resource instanceof WebGLTexture) { 669 | unwrappedContext_.deleteTexture(resource); 670 | } 671 | } 672 | } 673 | 674 | function makeWebGLContextEvent(statusMessage) { 675 | return { 676 | statusMessage: statusMessage, 677 | preventDefault: function() { 678 | canRestore_ = true; 679 | } 680 | }; 681 | } 682 | 683 | return canvas; 684 | 685 | function makeLostContextSimulatingContext(ctx) { 686 | // copy all functions and properties to wrapper 687 | for (var propertyName in ctx) { 688 | if (typeof ctx[propertyName] == 'function') { 689 | wrappedContext_[propertyName] = makeLostContextFunctionWrapper( 690 | ctx, propertyName); 691 | } else { 692 | makePropertyWrapper(wrappedContext_, ctx, propertyName); 693 | } 694 | } 695 | 696 | // Wrap a few functions specially. 697 | wrappedContext_.getError = function() { 698 | loseContextIfTime(); 699 | if (!contextLost_) { 700 | var err; 701 | while (err = unwrappedContext_.getError()) { 702 | glErrorShadow_[err] = true; 703 | } 704 | } 705 | for (var err in glErrorShadow_) { 706 | if (glErrorShadow_[err]) { 707 | delete glErrorShadow_[err]; 708 | return err; 709 | } 710 | } 711 | return wrappedContext_.NO_ERROR; 712 | }; 713 | 714 | var creationFunctions = [ 715 | "createBuffer", 716 | "createFramebuffer", 717 | "createProgram", 718 | "createRenderbuffer", 719 | "createShader", 720 | "createTexture" 721 | ]; 722 | for (var ii = 0; ii < creationFunctions.length; ++ii) { 723 | var functionName = creationFunctions[ii]; 724 | wrappedContext_[functionName] = function(f) { 725 | return function() { 726 | loseContextIfTime(); 727 | if (contextLost_) { 728 | return null; 729 | } 730 | var obj = f.apply(ctx, arguments); 731 | obj.__webglDebugContextLostId__ = contextId_; 732 | resourceDb_.push(obj); 733 | return obj; 734 | }; 735 | }(ctx[functionName]); 736 | } 737 | 738 | var functionsThatShouldReturnNull = [ 739 | "getActiveAttrib", 740 | "getActiveUniform", 741 | "getBufferParameter", 742 | "getContextAttributes", 743 | "getAttachedShaders", 744 | "getFramebufferAttachmentParameter", 745 | "getParameter", 746 | "getProgramParameter", 747 | "getProgramInfoLog", 748 | "getRenderbufferParameter", 749 | "getShaderParameter", 750 | "getShaderInfoLog", 751 | "getShaderSource", 752 | "getTexParameter", 753 | "getUniform", 754 | "getUniformLocation", 755 | "getVertexAttrib" 756 | ]; 757 | for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { 758 | var functionName = functionsThatShouldReturnNull[ii]; 759 | wrappedContext_[functionName] = function(f) { 760 | return function() { 761 | loseContextIfTime(); 762 | if (contextLost_) { 763 | return null; 764 | } 765 | return f.apply(ctx, arguments); 766 | } 767 | }(wrappedContext_[functionName]); 768 | } 769 | 770 | var isFunctions = [ 771 | "isBuffer", 772 | "isEnabled", 773 | "isFramebuffer", 774 | "isProgram", 775 | "isRenderbuffer", 776 | "isShader", 777 | "isTexture" 778 | ]; 779 | for (var ii = 0; ii < isFunctions.length; ++ii) { 780 | var functionName = isFunctions[ii]; 781 | wrappedContext_[functionName] = function(f) { 782 | return function() { 783 | loseContextIfTime(); 784 | if (contextLost_) { 785 | return false; 786 | } 787 | return f.apply(ctx, arguments); 788 | } 789 | }(wrappedContext_[functionName]); 790 | } 791 | 792 | wrappedContext_.checkFramebufferStatus = function(f) { 793 | return function() { 794 | loseContextIfTime(); 795 | if (contextLost_) { 796 | return wrappedContext_.FRAMEBUFFER_UNSUPPORTED; 797 | } 798 | return f.apply(ctx, arguments); 799 | }; 800 | }(wrappedContext_.checkFramebufferStatus); 801 | 802 | wrappedContext_.getAttribLocation = function(f) { 803 | return function() { 804 | loseContextIfTime(); 805 | if (contextLost_) { 806 | return -1; 807 | } 808 | return f.apply(ctx, arguments); 809 | }; 810 | }(wrappedContext_.getAttribLocation); 811 | 812 | wrappedContext_.getVertexAttribOffset = function(f) { 813 | return function() { 814 | loseContextIfTime(); 815 | if (contextLost_) { 816 | return 0; 817 | } 818 | return f.apply(ctx, arguments); 819 | }; 820 | }(wrappedContext_.getVertexAttribOffset); 821 | 822 | wrappedContext_.isContextLost = function() { 823 | return contextLost_; 824 | }; 825 | 826 | return wrappedContext_; 827 | } 828 | } 829 | 830 | return { 831 | /** 832 | * Initializes this module. Safe to call more than once. 833 | * @param {!WebGLRenderingContext} ctx A WebGL context. If 834 | * you have more than one context it doesn't matter which one 835 | * you pass in, it is only used to pull out constants. 836 | */ 837 | 'init': init, 838 | 839 | /** 840 | * Returns true or false if value matches any WebGL enum 841 | * @param {*} value Value to check if it might be an enum. 842 | * @return {boolean} True if value matches one of the WebGL defined enums 843 | */ 844 | 'mightBeEnum': mightBeEnum, 845 | 846 | /** 847 | * Gets an string version of an WebGL enum. 848 | * 849 | * Example: 850 | * WebGLDebugUtil.init(ctx); 851 | * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); 852 | * 853 | * @param {number} value Value to return an enum for 854 | * @return {string} The string version of the enum. 855 | */ 856 | 'glEnumToString': glEnumToString, 857 | 858 | /** 859 | * Converts the argument of a WebGL function to a string. 860 | * Attempts to convert enum arguments to strings. 861 | * 862 | * Example: 863 | * WebGLDebugUtil.init(ctx); 864 | * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D); 865 | * 866 | * would return 'TEXTURE_2D' 867 | * 868 | * @param {string} functionName the name of the WebGL function. 869 | * @param {number} numArgs The number of arguments 870 | * @param {number} argumentIndx the index of the argument. 871 | * @param {*} value The value of the argument. 872 | * @return {string} The value as a string. 873 | */ 874 | 'glFunctionArgToString': glFunctionArgToString, 875 | 876 | /** 877 | * Converts the arguments of a WebGL function to a string. 878 | * Attempts to convert enum arguments to strings. 879 | * 880 | * @param {string} functionName the name of the WebGL function. 881 | * @param {number} args The arguments. 882 | * @return {string} The arguments as a string. 883 | */ 884 | 'glFunctionArgsToString': glFunctionArgsToString, 885 | 886 | /** 887 | * Given a WebGL context returns a wrapped context that calls 888 | * gl.getError after every command and calls a function if the 889 | * result is not NO_ERROR. 890 | * 891 | * You can supply your own function if you want. For example, if you'd like 892 | * an exception thrown on any GL error you could do this 893 | * 894 | * function throwOnGLError(err, funcName, args) { 895 | * throw WebGLDebugUtils.glEnumToString(err) + 896 | * " was caused by call to " + funcName; 897 | * }; 898 | * 899 | * ctx = WebGLDebugUtils.makeDebugContext( 900 | * canvas.getContext("webgl"), throwOnGLError); 901 | * 902 | * @param {!WebGLRenderingContext} ctx The webgl context to wrap. 903 | * @param {!function(err, funcName, args): void} opt_onErrorFunc The function 904 | * to call when gl.getError returns an error. If not specified the default 905 | * function calls console.log with a message. 906 | * @param {!function(funcName, args): void} opt_onFunc The 907 | * function to call when each webgl function is called. You 908 | * can use this to log all calls for example. 909 | */ 910 | 'makeDebugContext': makeDebugContext, 911 | 912 | /** 913 | * Given a canvas element returns a wrapped canvas element that will 914 | * simulate lost context. The canvas returned adds the following functions. 915 | * 916 | * loseContext: 917 | * simulates a lost context event. 918 | * 919 | * restoreContext: 920 | * simulates the context being restored. 921 | * 922 | * lostContextInNCalls: 923 | * loses the context after N gl calls. 924 | * 925 | * getNumCalls: 926 | * tells you how many gl calls there have been so far. 927 | * 928 | * setRestoreTimeout: 929 | * sets the number of milliseconds until the context is restored 930 | * after it has been lost. Defaults to 0. Pass -1 to prevent 931 | * automatic restoring. 932 | * 933 | * @param {!Canvas} canvas The canvas element to wrap. 934 | */ 935 | 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas, 936 | 937 | /** 938 | * Resets a context to the initial state. 939 | * @param {!WebGLRenderingContext} ctx The webgl context to 940 | * reset. 941 | */ 942 | 'resetToInitialState': resetToInitialState 943 | }; 944 | 945 | }(); 946 | 947 | -------------------------------------------------------------------------------- /gl-matrix-min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview gl-matrix - High performance matrix and vector operations 3 | * @author Brandon Jones 4 | * @author Colin MacKenzie IV 5 | * @version 2.3.2 6 | */ 7 | 8 | /* Copyright (c) 2015, Brandon Jones, Colin MacKenzie IV. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. */ 27 | 28 | !function(t,a){if("object"==typeof exports&&"object"==typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define(a);else{var n=a();for(var r in n)("object"==typeof exports?exports:t)[r]=n[r]}}(this,function(){return function(t){function a(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,a),o.loaded=!0,o.exports}var n={};return a.m=t,a.c=n,a.p="",a(0)}([function(t,a,n){a.glMatrix=n(1),a.mat2=n(2),a.mat2d=n(3),a.mat3=n(4),a.mat4=n(5),a.quat=n(6),a.vec2=n(9),a.vec3=n(7),a.vec4=n(8)},function(t,a){var n={};n.EPSILON=1e-6,n.ARRAY_TYPE="undefined"!=typeof Float32Array?Float32Array:Array,n.RANDOM=Math.random,n.ENABLE_SIMD=!1,n.SIMD_AVAILABLE=n.ARRAY_TYPE===Float32Array&&"SIMD"in this,n.USE_SIMD=n.ENABLE_SIMD&&n.SIMD_AVAILABLE,n.setMatrixArrayType=function(t){n.ARRAY_TYPE=t};var r=Math.PI/180;n.toRadian=function(t){return t*r},t.exports=n},function(t,a,n){var r=n(1),o={};o.create=function(){var t=new r.ARRAY_TYPE(4);return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t},o.clone=function(t){var a=new r.ARRAY_TYPE(4);return a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=t[3],a},o.copy=function(t,a){return t[0]=a[0],t[1]=a[1],t[2]=a[2],t[3]=a[3],t},o.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t},o.transpose=function(t,a){if(t===a){var n=a[1];t[1]=a[2],t[2]=n}else t[0]=a[0],t[1]=a[2],t[2]=a[1],t[3]=a[3];return t},o.invert=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=n*l-o*r;return u?(u=1/u,t[0]=l*u,t[1]=-r*u,t[2]=-o*u,t[3]=n*u,t):null},o.adjoint=function(t,a){var n=a[0];return t[0]=a[3],t[1]=-a[1],t[2]=-a[2],t[3]=n,t},o.determinant=function(t){return t[0]*t[3]-t[2]*t[1]},o.multiply=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=n[0],M=n[1],i=n[2],s=n[3];return t[0]=r*e+l*M,t[1]=o*e+u*M,t[2]=r*i+l*s,t[3]=o*i+u*s,t},o.mul=o.multiply,o.rotate=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=Math.sin(n),M=Math.cos(n);return t[0]=r*M+l*e,t[1]=o*M+u*e,t[2]=r*-e+l*M,t[3]=o*-e+u*M,t},o.scale=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=n[0],M=n[1];return t[0]=r*e,t[1]=o*e,t[2]=l*M,t[3]=u*M,t},o.fromRotation=function(t,a){var n=Math.sin(a),r=Math.cos(a);return t[0]=r,t[1]=n,t[2]=-n,t[3]=r,t},o.fromScaling=function(t,a){return t[0]=a[0],t[1]=0,t[2]=0,t[3]=a[1],t},o.str=function(t){return"mat2("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},o.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2))},o.LDU=function(t,a,n,r){return t[2]=r[2]/r[0],n[0]=r[0],n[1]=r[1],n[3]=r[3]-t[2]*n[1],[t,a,n]},t.exports=o},function(t,a,n){var r=n(1),o={};o.create=function(){var t=new r.ARRAY_TYPE(6);return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t},o.clone=function(t){var a=new r.ARRAY_TYPE(6);return a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=t[3],a[4]=t[4],a[5]=t[5],a},o.copy=function(t,a){return t[0]=a[0],t[1]=a[1],t[2]=a[2],t[3]=a[3],t[4]=a[4],t[5]=a[5],t},o.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t},o.invert=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=a[4],e=a[5],M=n*l-r*o;return M?(M=1/M,t[0]=l*M,t[1]=-r*M,t[2]=-o*M,t[3]=n*M,t[4]=(o*e-l*u)*M,t[5]=(r*u-n*e)*M,t):null},o.determinant=function(t){return t[0]*t[3]-t[1]*t[2]},o.multiply=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=n[0],s=n[1],c=n[2],f=n[3],D=n[4],S=n[5];return t[0]=r*i+l*s,t[1]=o*i+u*s,t[2]=r*c+l*f,t[3]=o*c+u*f,t[4]=r*D+l*S+e,t[5]=o*D+u*S+M,t},o.mul=o.multiply,o.rotate=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=Math.sin(n),s=Math.cos(n);return t[0]=r*s+l*i,t[1]=o*s+u*i,t[2]=r*-i+l*s,t[3]=o*-i+u*s,t[4]=e,t[5]=M,t},o.scale=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=n[0],s=n[1];return t[0]=r*i,t[1]=o*i,t[2]=l*s,t[3]=u*s,t[4]=e,t[5]=M,t},o.translate=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=n[0],s=n[1];return t[0]=r,t[1]=o,t[2]=l,t[3]=u,t[4]=r*i+l*s+e,t[5]=o*i+u*s+M,t},o.fromRotation=function(t,a){var n=Math.sin(a),r=Math.cos(a);return t[0]=r,t[1]=n,t[2]=-n,t[3]=r,t[4]=0,t[5]=0,t},o.fromScaling=function(t,a){return t[0]=a[0],t[1]=0,t[2]=0,t[3]=a[1],t[4]=0,t[5]=0,t},o.fromTranslation=function(t,a){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=a[0],t[5]=a[1],t},o.str=function(t){return"mat2d("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+")"},o.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+1)},t.exports=o},function(t,a,n){var r=n(1),o={};o.create=function(){var t=new r.ARRAY_TYPE(9);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},o.fromMat4=function(t,a){return t[0]=a[0],t[1]=a[1],t[2]=a[2],t[3]=a[4],t[4]=a[5],t[5]=a[6],t[6]=a[8],t[7]=a[9],t[8]=a[10],t},o.clone=function(t){var a=new r.ARRAY_TYPE(9);return a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=t[3],a[4]=t[4],a[5]=t[5],a[6]=t[6],a[7]=t[7],a[8]=t[8],a},o.copy=function(t,a){return t[0]=a[0],t[1]=a[1],t[2]=a[2],t[3]=a[3],t[4]=a[4],t[5]=a[5],t[6]=a[6],t[7]=a[7],t[8]=a[8],t},o.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},o.transpose=function(t,a){if(t===a){var n=a[1],r=a[2],o=a[5];t[1]=a[3],t[2]=a[6],t[3]=n,t[5]=a[7],t[6]=r,t[7]=o}else t[0]=a[0],t[1]=a[3],t[2]=a[6],t[3]=a[1],t[4]=a[4],t[5]=a[7],t[6]=a[2],t[7]=a[5],t[8]=a[8];return t},o.invert=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=a[4],e=a[5],M=a[6],i=a[7],s=a[8],c=s*u-e*i,f=-s*l+e*M,D=i*l-u*M,S=n*c+r*f+o*D;return S?(S=1/S,t[0]=c*S,t[1]=(-s*r+o*i)*S,t[2]=(e*r-o*u)*S,t[3]=f*S,t[4]=(s*n-o*M)*S,t[5]=(-e*n+o*l)*S,t[6]=D*S,t[7]=(-i*n+r*M)*S,t[8]=(u*n-r*l)*S,t):null},o.adjoint=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=a[4],e=a[5],M=a[6],i=a[7],s=a[8];return t[0]=u*s-e*i,t[1]=o*i-r*s,t[2]=r*e-o*u,t[3]=e*M-l*s,t[4]=n*s-o*M,t[5]=o*l-n*e,t[6]=l*i-u*M,t[7]=r*M-n*i,t[8]=n*u-r*l,t},o.determinant=function(t){var a=t[0],n=t[1],r=t[2],o=t[3],l=t[4],u=t[5],e=t[6],M=t[7],i=t[8];return a*(i*l-u*M)+n*(-i*o+u*e)+r*(M*o-l*e)},o.multiply=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=a[6],s=a[7],c=a[8],f=n[0],D=n[1],S=n[2],I=n[3],x=n[4],F=n[5],m=n[6],h=n[7],d=n[8];return t[0]=f*r+D*u+S*i,t[1]=f*o+D*e+S*s,t[2]=f*l+D*M+S*c,t[3]=I*r+x*u+F*i,t[4]=I*o+x*e+F*s,t[5]=I*l+x*M+F*c,t[6]=m*r+h*u+d*i,t[7]=m*o+h*e+d*s,t[8]=m*l+h*M+d*c,t},o.mul=o.multiply,o.translate=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=a[6],s=a[7],c=a[8],f=n[0],D=n[1];return t[0]=r,t[1]=o,t[2]=l,t[3]=u,t[4]=e,t[5]=M,t[6]=f*r+D*u+i,t[7]=f*o+D*e+s,t[8]=f*l+D*M+c,t},o.rotate=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=a[6],s=a[7],c=a[8],f=Math.sin(n),D=Math.cos(n);return t[0]=D*r+f*u,t[1]=D*o+f*e,t[2]=D*l+f*M,t[3]=D*u-f*r,t[4]=D*e-f*o,t[5]=D*M-f*l,t[6]=i,t[7]=s,t[8]=c,t},o.scale=function(t,a,n){var r=n[0],o=n[1];return t[0]=r*a[0],t[1]=r*a[1],t[2]=r*a[2],t[3]=o*a[3],t[4]=o*a[4],t[5]=o*a[5],t[6]=a[6],t[7]=a[7],t[8]=a[8],t},o.fromTranslation=function(t,a){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=a[0],t[7]=a[1],t[8]=1,t},o.fromRotation=function(t,a){var n=Math.sin(a),r=Math.cos(a);return t[0]=r,t[1]=n,t[2]=0,t[3]=-n,t[4]=r,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},o.fromScaling=function(t,a){return t[0]=a[0],t[1]=0,t[2]=0,t[3]=0,t[4]=a[1],t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},o.fromMat2d=function(t,a){return t[0]=a[0],t[1]=a[1],t[2]=0,t[3]=a[2],t[4]=a[3],t[5]=0,t[6]=a[4],t[7]=a[5],t[8]=1,t},o.fromQuat=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=n+n,e=r+r,M=o+o,i=n*u,s=r*u,c=r*e,f=o*u,D=o*e,S=o*M,I=l*u,x=l*e,F=l*M;return t[0]=1-c-S,t[3]=s-F,t[6]=f+x,t[1]=s+F,t[4]=1-i-S,t[7]=D-I,t[2]=f-x,t[5]=D+I,t[8]=1-i-c,t},o.normalFromMat4=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=a[4],e=a[5],M=a[6],i=a[7],s=a[8],c=a[9],f=a[10],D=a[11],S=a[12],I=a[13],x=a[14],F=a[15],m=n*e-r*u,h=n*M-o*u,d=n*i-l*u,v=r*M-o*e,z=r*i-l*e,p=o*i-l*M,w=s*I-c*S,A=s*x-f*S,R=s*F-D*S,b=c*x-f*I,q=c*F-D*I,y=f*F-D*x,Y=m*y-h*q+d*b+v*R-z*A+p*w;return Y?(Y=1/Y,t[0]=(e*y-M*q+i*b)*Y,t[1]=(M*R-u*y-i*A)*Y,t[2]=(u*q-e*R+i*w)*Y,t[3]=(o*q-r*y-l*b)*Y,t[4]=(n*y-o*R+l*A)*Y,t[5]=(r*R-n*q-l*w)*Y,t[6]=(I*p-x*z+F*v)*Y,t[7]=(x*d-S*p-F*h)*Y,t[8]=(S*z-I*d+F*m)*Y,t):null},o.str=function(t){return"mat3("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+")"},o.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2))},t.exports=o},function(t,a,n){var r=n(1),o={scalar:{},SIMD:{}};o.create=function(){var t=new r.ARRAY_TYPE(16);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},o.clone=function(t){var a=new r.ARRAY_TYPE(16);return a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=t[3],a[4]=t[4],a[5]=t[5],a[6]=t[6],a[7]=t[7],a[8]=t[8],a[9]=t[9],a[10]=t[10],a[11]=t[11],a[12]=t[12],a[13]=t[13],a[14]=t[14],a[15]=t[15],a},o.copy=function(t,a){return t[0]=a[0],t[1]=a[1],t[2]=a[2],t[3]=a[3],t[4]=a[4],t[5]=a[5],t[6]=a[6],t[7]=a[7],t[8]=a[8],t[9]=a[9],t[10]=a[10],t[11]=a[11],t[12]=a[12],t[13]=a[13],t[14]=a[14],t[15]=a[15],t},o.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},o.scalar.transpose=function(t,a){if(t===a){var n=a[1],r=a[2],o=a[3],l=a[6],u=a[7],e=a[11];t[1]=a[4],t[2]=a[8],t[3]=a[12],t[4]=n,t[6]=a[9],t[7]=a[13],t[8]=r,t[9]=l,t[11]=a[14],t[12]=o,t[13]=u,t[14]=e}else t[0]=a[0],t[1]=a[4],t[2]=a[8],t[3]=a[12],t[4]=a[1],t[5]=a[5],t[6]=a[9],t[7]=a[13],t[8]=a[2],t[9]=a[6],t[10]=a[10],t[11]=a[14],t[12]=a[3],t[13]=a[7],t[14]=a[11],t[15]=a[15];return t},o.SIMD.transpose=function(t,a){var n,r,o,l,u,e,M,i,s,c;return n=SIMD.Float32x4.load(a,0),r=SIMD.Float32x4.load(a,4),o=SIMD.Float32x4.load(a,8),l=SIMD.Float32x4.load(a,12),u=SIMD.Float32x4.shuffle(n,r,0,1,4,5),e=SIMD.Float32x4.shuffle(o,l,0,1,4,5),M=SIMD.Float32x4.shuffle(u,e,0,2,4,6),i=SIMD.Float32x4.shuffle(u,e,1,3,5,7),SIMD.Float32x4.store(t,0,M),SIMD.Float32x4.store(t,4,i),u=SIMD.Float32x4.shuffle(n,r,2,3,6,7),e=SIMD.Float32x4.shuffle(o,l,2,3,6,7),s=SIMD.Float32x4.shuffle(u,e,0,2,4,6),c=SIMD.Float32x4.shuffle(u,e,1,3,5,7),SIMD.Float32x4.store(t,8,s),SIMD.Float32x4.store(t,12,c),t},o.transpose=r.USE_SIMD?o.SIMD.transpose:o.scalar.transpose,o.scalar.invert=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=a[4],e=a[5],M=a[6],i=a[7],s=a[8],c=a[9],f=a[10],D=a[11],S=a[12],I=a[13],x=a[14],F=a[15],m=n*e-r*u,h=n*M-o*u,d=n*i-l*u,v=r*M-o*e,z=r*i-l*e,p=o*i-l*M,w=s*I-c*S,A=s*x-f*S,R=s*F-D*S,b=c*x-f*I,q=c*F-D*I,y=f*F-D*x,Y=m*y-h*q+d*b+v*R-z*A+p*w;return Y?(Y=1/Y,t[0]=(e*y-M*q+i*b)*Y,t[1]=(o*q-r*y-l*b)*Y,t[2]=(I*p-x*z+F*v)*Y,t[3]=(f*z-c*p-D*v)*Y,t[4]=(M*R-u*y-i*A)*Y,t[5]=(n*y-o*R+l*A)*Y,t[6]=(x*d-S*p-F*h)*Y,t[7]=(s*p-f*d+D*h)*Y,t[8]=(u*q-e*R+i*w)*Y,t[9]=(r*R-n*q-l*w)*Y,t[10]=(S*z-I*d+F*m)*Y,t[11]=(c*d-s*z-D*m)*Y,t[12]=(e*A-u*b-M*w)*Y,t[13]=(n*b-r*A+o*w)*Y,t[14]=(I*h-S*v-x*m)*Y,t[15]=(s*v-c*h+f*m)*Y,t):null},o.SIMD.invert=function(t,a){var n,r,o,l,u,e,M,i,s,c,f=SIMD.Float32x4.load(a,0),D=SIMD.Float32x4.load(a,4),S=SIMD.Float32x4.load(a,8),I=SIMD.Float32x4.load(a,12);return u=SIMD.Float32x4.shuffle(f,D,0,1,4,5),r=SIMD.Float32x4.shuffle(S,I,0,1,4,5),n=SIMD.Float32x4.shuffle(u,r,0,2,4,6),r=SIMD.Float32x4.shuffle(r,u,1,3,5,7),u=SIMD.Float32x4.shuffle(f,D,2,3,6,7),l=SIMD.Float32x4.shuffle(S,I,2,3,6,7),o=SIMD.Float32x4.shuffle(u,l,0,2,4,6),l=SIMD.Float32x4.shuffle(l,u,1,3,5,7),u=SIMD.Float32x4.mul(o,l),u=SIMD.Float32x4.swizzle(u,1,0,3,2),e=SIMD.Float32x4.mul(r,u),M=SIMD.Float32x4.mul(n,u),u=SIMD.Float32x4.swizzle(u,2,3,0,1),e=SIMD.Float32x4.sub(SIMD.Float32x4.mul(r,u),e),M=SIMD.Float32x4.sub(SIMD.Float32x4.mul(n,u),M),M=SIMD.Float32x4.swizzle(M,2,3,0,1),u=SIMD.Float32x4.mul(r,o),u=SIMD.Float32x4.swizzle(u,1,0,3,2),e=SIMD.Float32x4.add(SIMD.Float32x4.mul(l,u),e),s=SIMD.Float32x4.mul(n,u),u=SIMD.Float32x4.swizzle(u,2,3,0,1),e=SIMD.Float32x4.sub(e,SIMD.Float32x4.mul(l,u)),s=SIMD.Float32x4.sub(SIMD.Float32x4.mul(n,u),s),s=SIMD.Float32x4.swizzle(s,2,3,0,1),u=SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(r,2,3,0,1),l),u=SIMD.Float32x4.swizzle(u,1,0,3,2),o=SIMD.Float32x4.swizzle(o,2,3,0,1),e=SIMD.Float32x4.add(SIMD.Float32x4.mul(o,u),e),i=SIMD.Float32x4.mul(n,u),u=SIMD.Float32x4.swizzle(u,2,3,0,1),e=SIMD.Float32x4.sub(e,SIMD.Float32x4.mul(o,u)),i=SIMD.Float32x4.sub(SIMD.Float32x4.mul(n,u),i),i=SIMD.Float32x4.swizzle(i,2,3,0,1),u=SIMD.Float32x4.mul(n,r),u=SIMD.Float32x4.swizzle(u,1,0,3,2),i=SIMD.Float32x4.add(SIMD.Float32x4.mul(l,u),i),s=SIMD.Float32x4.sub(SIMD.Float32x4.mul(o,u),s),u=SIMD.Float32x4.swizzle(u,2,3,0,1),i=SIMD.Float32x4.sub(SIMD.Float32x4.mul(l,u),i),s=SIMD.Float32x4.sub(s,SIMD.Float32x4.mul(o,u)),u=SIMD.Float32x4.mul(n,l),u=SIMD.Float32x4.swizzle(u,1,0,3,2),M=SIMD.Float32x4.sub(M,SIMD.Float32x4.mul(o,u)),i=SIMD.Float32x4.add(SIMD.Float32x4.mul(r,u),i),u=SIMD.Float32x4.swizzle(u,2,3,0,1),M=SIMD.Float32x4.add(SIMD.Float32x4.mul(o,u),M),i=SIMD.Float32x4.sub(i,SIMD.Float32x4.mul(r,u)),u=SIMD.Float32x4.mul(n,o),u=SIMD.Float32x4.swizzle(u,1,0,3,2),M=SIMD.Float32x4.add(SIMD.Float32x4.mul(l,u),M),s=SIMD.Float32x4.sub(s,SIMD.Float32x4.mul(r,u)),u=SIMD.Float32x4.swizzle(u,2,3,0,1),M=SIMD.Float32x4.sub(M,SIMD.Float32x4.mul(l,u)),s=SIMD.Float32x4.add(SIMD.Float32x4.mul(r,u),s),c=SIMD.Float32x4.mul(n,e),c=SIMD.Float32x4.add(SIMD.Float32x4.swizzle(c,2,3,0,1),c),c=SIMD.Float32x4.add(SIMD.Float32x4.swizzle(c,1,0,3,2),c),u=SIMD.Float32x4.reciprocalApproximation(c),c=SIMD.Float32x4.sub(SIMD.Float32x4.add(u,u),SIMD.Float32x4.mul(c,SIMD.Float32x4.mul(u,u))),(c=SIMD.Float32x4.swizzle(c,0,0,0,0))?(SIMD.Float32x4.store(t,0,SIMD.Float32x4.mul(c,e)),SIMD.Float32x4.store(t,4,SIMD.Float32x4.mul(c,M)),SIMD.Float32x4.store(t,8,SIMD.Float32x4.mul(c,i)),SIMD.Float32x4.store(t,12,SIMD.Float32x4.mul(c,s)),t):null},o.invert=r.USE_SIMD?o.SIMD.invert:o.scalar.invert,o.scalar.adjoint=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=a[4],e=a[5],M=a[6],i=a[7],s=a[8],c=a[9],f=a[10],D=a[11],S=a[12],I=a[13],x=a[14],F=a[15];return t[0]=e*(f*F-D*x)-c*(M*F-i*x)+I*(M*D-i*f),t[1]=-(r*(f*F-D*x)-c*(o*F-l*x)+I*(o*D-l*f)),t[2]=r*(M*F-i*x)-e*(o*F-l*x)+I*(o*i-l*M),t[3]=-(r*(M*D-i*f)-e*(o*D-l*f)+c*(o*i-l*M)),t[4]=-(u*(f*F-D*x)-s*(M*F-i*x)+S*(M*D-i*f)),t[5]=n*(f*F-D*x)-s*(o*F-l*x)+S*(o*D-l*f),t[6]=-(n*(M*F-i*x)-u*(o*F-l*x)+S*(o*i-l*M)),t[7]=n*(M*D-i*f)-u*(o*D-l*f)+s*(o*i-l*M),t[8]=u*(c*F-D*I)-s*(e*F-i*I)+S*(e*D-i*c),t[9]=-(n*(c*F-D*I)-s*(r*F-l*I)+S*(r*D-l*c)),t[10]=n*(e*F-i*I)-u*(r*F-l*I)+S*(r*i-l*e),t[11]=-(n*(e*D-i*c)-u*(r*D-l*c)+s*(r*i-l*e)),t[12]=-(u*(c*x-f*I)-s*(e*x-M*I)+S*(e*f-M*c)),t[13]=n*(c*x-f*I)-s*(r*x-o*I)+S*(r*f-o*c),t[14]=-(n*(e*x-M*I)-u*(r*x-o*I)+S*(r*M-o*e)),t[15]=n*(e*f-M*c)-u*(r*f-o*c)+s*(r*M-o*e),t},o.SIMD.adjoint=function(t,a){var n,r,o,l,u,e,M,i,s,c,f,D,S,n=SIMD.Float32x4.load(a,0),r=SIMD.Float32x4.load(a,4),o=SIMD.Float32x4.load(a,8),l=SIMD.Float32x4.load(a,12);return s=SIMD.Float32x4.shuffle(n,r,0,1,4,5),e=SIMD.Float32x4.shuffle(o,l,0,1,4,5),u=SIMD.Float32x4.shuffle(s,e,0,2,4,6),e=SIMD.Float32x4.shuffle(e,s,1,3,5,7),s=SIMD.Float32x4.shuffle(n,r,2,3,6,7),i=SIMD.Float32x4.shuffle(o,l,2,3,6,7),M=SIMD.Float32x4.shuffle(s,i,0,2,4,6),i=SIMD.Float32x4.shuffle(i,s,1,3,5,7),s=SIMD.Float32x4.mul(M,i),s=SIMD.Float32x4.swizzle(s,1,0,3,2),c=SIMD.Float32x4.mul(e,s),f=SIMD.Float32x4.mul(u,s),s=SIMD.Float32x4.swizzle(s,2,3,0,1),c=SIMD.Float32x4.sub(SIMD.Float32x4.mul(e,s),c),f=SIMD.Float32x4.sub(SIMD.Float32x4.mul(u,s),f),f=SIMD.Float32x4.swizzle(f,2,3,0,1),s=SIMD.Float32x4.mul(e,M),s=SIMD.Float32x4.swizzle(s,1,0,3,2),c=SIMD.Float32x4.add(SIMD.Float32x4.mul(i,s),c),S=SIMD.Float32x4.mul(u,s),s=SIMD.Float32x4.swizzle(s,2,3,0,1),c=SIMD.Float32x4.sub(c,SIMD.Float32x4.mul(i,s)),S=SIMD.Float32x4.sub(SIMD.Float32x4.mul(u,s),S),S=SIMD.Float32x4.swizzle(S,2,3,0,1),s=SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(e,2,3,0,1),i),s=SIMD.Float32x4.swizzle(s,1,0,3,2),M=SIMD.Float32x4.swizzle(M,2,3,0,1),c=SIMD.Float32x4.add(SIMD.Float32x4.mul(M,s),c),D=SIMD.Float32x4.mul(u,s),s=SIMD.Float32x4.swizzle(s,2,3,0,1),c=SIMD.Float32x4.sub(c,SIMD.Float32x4.mul(M,s)),D=SIMD.Float32x4.sub(SIMD.Float32x4.mul(u,s),D),D=SIMD.Float32x4.swizzle(D,2,3,0,1),s=SIMD.Float32x4.mul(u,e),s=SIMD.Float32x4.swizzle(s,1,0,3,2),D=SIMD.Float32x4.add(SIMD.Float32x4.mul(i,s),D),S=SIMD.Float32x4.sub(SIMD.Float32x4.mul(M,s),S),s=SIMD.Float32x4.swizzle(s,2,3,0,1),D=SIMD.Float32x4.sub(SIMD.Float32x4.mul(i,s),D),S=SIMD.Float32x4.sub(S,SIMD.Float32x4.mul(M,s)),s=SIMD.Float32x4.mul(u,i),s=SIMD.Float32x4.swizzle(s,1,0,3,2),f=SIMD.Float32x4.sub(f,SIMD.Float32x4.mul(M,s)),D=SIMD.Float32x4.add(SIMD.Float32x4.mul(e,s),D),s=SIMD.Float32x4.swizzle(s,2,3,0,1),f=SIMD.Float32x4.add(SIMD.Float32x4.mul(M,s),f),D=SIMD.Float32x4.sub(D,SIMD.Float32x4.mul(e,s)),s=SIMD.Float32x4.mul(u,M),s=SIMD.Float32x4.swizzle(s,1,0,3,2),f=SIMD.Float32x4.add(SIMD.Float32x4.mul(i,s),f),S=SIMD.Float32x4.sub(S,SIMD.Float32x4.mul(e,s)),s=SIMD.Float32x4.swizzle(s,2,3,0,1),f=SIMD.Float32x4.sub(f,SIMD.Float32x4.mul(i,s)),S=SIMD.Float32x4.add(SIMD.Float32x4.mul(e,s),S),SIMD.Float32x4.store(t,0,c),SIMD.Float32x4.store(t,4,f),SIMD.Float32x4.store(t,8,D),SIMD.Float32x4.store(t,12,S),t},o.adjoint=r.USE_SIMD?o.SIMD.adjoint:o.scalar.adjoint,o.determinant=function(t){var a=t[0],n=t[1],r=t[2],o=t[3],l=t[4],u=t[5],e=t[6],M=t[7],i=t[8],s=t[9],c=t[10],f=t[11],D=t[12],S=t[13],I=t[14],x=t[15],F=a*u-n*l,m=a*e-r*l,h=a*M-o*l,d=n*e-r*u,v=n*M-o*u,z=r*M-o*e,p=i*S-s*D,w=i*I-c*D,A=i*x-f*D,R=s*I-c*S,b=s*x-f*S,q=c*x-f*I;return F*q-m*b+h*R+d*A-v*w+z*p},o.SIMD.multiply=function(t,a,n){var r=SIMD.Float32x4.load(a,0),o=SIMD.Float32x4.load(a,4),l=SIMD.Float32x4.load(a,8),u=SIMD.Float32x4.load(a,12),e=SIMD.Float32x4.load(n,0),M=SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(e,0,0,0,0),r),SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(e,1,1,1,1),o),SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(e,2,2,2,2),l),SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(e,3,3,3,3),u))));SIMD.Float32x4.store(t,0,M);var i=SIMD.Float32x4.load(n,4),s=SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(i,0,0,0,0),r),SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(i,1,1,1,1),o),SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(i,2,2,2,2),l),SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(i,3,3,3,3),u))));SIMD.Float32x4.store(t,4,s);var c=SIMD.Float32x4.load(n,8),f=SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(c,0,0,0,0),r),SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(c,1,1,1,1),o),SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(c,2,2,2,2),l),SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(c,3,3,3,3),u))));SIMD.Float32x4.store(t,8,f);var D=SIMD.Float32x4.load(n,12),S=SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(D,0,0,0,0),r),SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(D,1,1,1,1),o),SIMD.Float32x4.add(SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(D,2,2,2,2),l),SIMD.Float32x4.mul(SIMD.Float32x4.swizzle(D,3,3,3,3),u))));return SIMD.Float32x4.store(t,12,S),t},o.scalar.multiply=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=a[6],s=a[7],c=a[8],f=a[9],D=a[10],S=a[11],I=a[12],x=a[13],F=a[14],m=a[15],h=n[0],d=n[1],v=n[2],z=n[3];return t[0]=h*r+d*e+v*c+z*I,t[1]=h*o+d*M+v*f+z*x,t[2]=h*l+d*i+v*D+z*F,t[3]=h*u+d*s+v*S+z*m,h=n[4],d=n[5],v=n[6],z=n[7],t[4]=h*r+d*e+v*c+z*I,t[5]=h*o+d*M+v*f+z*x,t[6]=h*l+d*i+v*D+z*F,t[7]=h*u+d*s+v*S+z*m,h=n[8],d=n[9],v=n[10],z=n[11],t[8]=h*r+d*e+v*c+z*I,t[9]=h*o+d*M+v*f+z*x,t[10]=h*l+d*i+v*D+z*F,t[11]=h*u+d*s+v*S+z*m,h=n[12],d=n[13],v=n[14],z=n[15],t[12]=h*r+d*e+v*c+z*I,t[13]=h*o+d*M+v*f+z*x,t[14]=h*l+d*i+v*D+z*F,t[15]=h*u+d*s+v*S+z*m,t},o.multiply=r.USE_SIMD?o.SIMD.multiply:o.scalar.multiply,o.mul=o.multiply,o.scalar.translate=function(t,a,n){var r,o,l,u,e,M,i,s,c,f,D,S,I=n[0],x=n[1],F=n[2];return a===t?(t[12]=a[0]*I+a[4]*x+a[8]*F+a[12],t[13]=a[1]*I+a[5]*x+a[9]*F+a[13],t[14]=a[2]*I+a[6]*x+a[10]*F+a[14],t[15]=a[3]*I+a[7]*x+a[11]*F+a[15]):(r=a[0],o=a[1],l=a[2],u=a[3],e=a[4],M=a[5],i=a[6],s=a[7],c=a[8],f=a[9],D=a[10],S=a[11],t[0]=r,t[1]=o,t[2]=l,t[3]=u,t[4]=e,t[5]=M,t[6]=i,t[7]=s,t[8]=c,t[9]=f,t[10]=D,t[11]=S,t[12]=r*I+e*x+c*F+a[12],t[13]=o*I+M*x+f*F+a[13],t[14]=l*I+i*x+D*F+a[14],t[15]=u*I+s*x+S*F+a[15]),t},o.SIMD.translate=function(t,a,n){var r=SIMD.Float32x4.load(a,0),o=SIMD.Float32x4.load(a,4),l=SIMD.Float32x4.load(a,8),u=SIMD.Float32x4.load(a,12),e=SIMD.Float32x4(n[0],n[1],n[2],0);a!==t&&(t[0]=a[0],t[1]=a[1],t[2]=a[2],t[3]=a[3],t[4]=a[4],t[5]=a[5],t[6]=a[6],t[7]=a[7],t[8]=a[8],t[9]=a[9],t[10]=a[10],t[11]=a[11]),r=SIMD.Float32x4.mul(r,SIMD.Float32x4.swizzle(e,0,0,0,0)),o=SIMD.Float32x4.mul(o,SIMD.Float32x4.swizzle(e,1,1,1,1)),l=SIMD.Float32x4.mul(l,SIMD.Float32x4.swizzle(e,2,2,2,2));var M=SIMD.Float32x4.add(r,SIMD.Float32x4.add(o,SIMD.Float32x4.add(l,u)));return SIMD.Float32x4.store(t,12,M),t},o.translate=r.USE_SIMD?o.SIMD.translate:o.scalar.translate,o.scalar.scale=function(t,a,n){var r=n[0],o=n[1],l=n[2];return t[0]=a[0]*r,t[1]=a[1]*r,t[2]=a[2]*r,t[3]=a[3]*r,t[4]=a[4]*o,t[5]=a[5]*o,t[6]=a[6]*o,t[7]=a[7]*o,t[8]=a[8]*l,t[9]=a[9]*l,t[10]=a[10]*l,t[11]=a[11]*l,t[12]=a[12],t[13]=a[13],t[14]=a[14],t[15]=a[15],t},o.SIMD.scale=function(t,a,n){var r,o,l,u=SIMD.Float32x4(n[0],n[1],n[2],0);return r=SIMD.Float32x4.load(a,0),SIMD.Float32x4.store(t,0,SIMD.Float32x4.mul(r,SIMD.Float32x4.swizzle(u,0,0,0,0))),o=SIMD.Float32x4.load(a,4),SIMD.Float32x4.store(t,4,SIMD.Float32x4.mul(o,SIMD.Float32x4.swizzle(u,1,1,1,1))),l=SIMD.Float32x4.load(a,8),SIMD.Float32x4.store(t,8,SIMD.Float32x4.mul(l,SIMD.Float32x4.swizzle(u,2,2,2,2))),t[12]=a[12],t[13]=a[13],t[14]=a[14],t[15]=a[15],t},o.scale=r.USE_SIMD?o.SIMD.scale:o.scalar.scale,o.rotate=function(t,a,n,o){var l,u,e,M,i,s,c,f,D,S,I,x,F,m,h,d,v,z,p,w,A,R,b,q,y=o[0],Y=o[1],g=o[2],E=Math.sqrt(y*y+Y*Y+g*g);return Math.abs(E)M?(l.cross(t,a,o),l.length(t)<1e-6&&l.cross(t,n,o),l.normalize(t,t),e.setAxisAngle(r,t,Math.PI),r):M>.999999?(r[0]=0,r[1]=0,r[2]=0,r[3]=1,r):(l.cross(t,o,u),r[0]=t[0],r[1]=t[1],r[2]=t[2],r[3]=1+M,e.normalize(r,r))}}(),e.setAxes=function(){var t=o.create();return function(a,n,r,o){return t[0]=r[0],t[3]=r[1],t[6]=r[2],t[1]=o[0],t[4]=o[1],t[7]=o[2],t[2]=-n[0],t[5]=-n[1],t[8]=-n[2],e.normalize(a,e.fromMat3(a,t))}}(),e.clone=u.clone,e.fromValues=u.fromValues,e.copy=u.copy,e.set=u.set,e.identity=function(t){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t},e.setAxisAngle=function(t,a,n){n=.5*n;var r=Math.sin(n);return t[0]=r*a[0],t[1]=r*a[1],t[2]=r*a[2],t[3]=Math.cos(n),t},e.add=u.add,e.multiply=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3],e=n[0],M=n[1],i=n[2],s=n[3];return t[0]=r*s+u*e+o*i-l*M,t[1]=o*s+u*M+l*e-r*i,t[2]=l*s+u*i+r*M-o*e,t[3]=u*s-r*e-o*M-l*i,t},e.mul=e.multiply,e.scale=u.scale,e.rotateX=function(t,a,n){n*=.5;var r=a[0],o=a[1],l=a[2],u=a[3],e=Math.sin(n),M=Math.cos(n);return t[0]=r*M+u*e,t[1]=o*M+l*e,t[2]=l*M-o*e,t[3]=u*M-r*e,t},e.rotateY=function(t,a,n){n*=.5;var r=a[0],o=a[1],l=a[2],u=a[3],e=Math.sin(n),M=Math.cos(n);return t[0]=r*M-l*e,t[1]=o*M+u*e,t[2]=l*M+r*e,t[3]=u*M-o*e,t},e.rotateZ=function(t,a,n){n*=.5;var r=a[0],o=a[1],l=a[2],u=a[3],e=Math.sin(n),M=Math.cos(n);return t[0]=r*M+o*e,t[1]=o*M-r*e,t[2]=l*M+u*e,t[3]=u*M-l*e,t},e.calculateW=function(t,a){var n=a[0],r=a[1],o=a[2];return t[0]=n,t[1]=r,t[2]=o,t[3]=Math.sqrt(Math.abs(1-n*n-r*r-o*o)),t},e.dot=u.dot,e.lerp=u.lerp,e.slerp=function(t,a,n,r){var o,l,u,e,M,i=a[0],s=a[1],c=a[2],f=a[3],D=n[0],S=n[1],I=n[2],x=n[3];return l=i*D+s*S+c*I+f*x,0>l&&(l=-l,D=-D,S=-S,I=-I,x=-x),1-l>1e-6?(o=Math.acos(l),u=Math.sin(o),e=Math.sin((1-r)*o)/u,M=Math.sin(r*o)/u):(e=1-r,M=r),t[0]=e*i+M*D,t[1]=e*s+M*S,t[2]=e*c+M*I,t[3]=e*f+M*x,t},e.sqlerp=function(){var t=e.create(),a=e.create();return function(n,r,o,l,u,M){return e.slerp(t,r,u,M),e.slerp(a,o,l,M),e.slerp(n,t,a,2*M*(1-M)),n}}(),e.invert=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=n*n+r*r+o*o+l*l,e=u?1/u:0;return t[0]=-n*e,t[1]=-r*e,t[2]=-o*e,t[3]=l*e,t},e.conjugate=function(t,a){return t[0]=-a[0],t[1]=-a[1],t[2]=-a[2],t[3]=a[3],t},e.length=u.length,e.len=e.length,e.squaredLength=u.squaredLength,e.sqrLen=e.squaredLength,e.normalize=u.normalize,e.fromMat3=function(t,a){var n,r=a[0]+a[4]+a[8];if(r>0)n=Math.sqrt(r+1),t[3]=.5*n,n=.5/n,t[0]=(a[5]-a[7])*n,t[1]=(a[6]-a[2])*n,t[2]=(a[1]-a[3])*n;else{var o=0;a[4]>a[0]&&(o=1),a[8]>a[3*o+o]&&(o=2);var l=(o+1)%3,u=(o+2)%3;n=Math.sqrt(a[3*o+o]-a[3*l+l]-a[3*u+u]+1),t[o]=.5*n,n=.5/n,t[3]=(a[3*l+u]-a[3*u+l])*n,t[l]=(a[3*l+o]+a[3*o+l])*n,t[u]=(a[3*u+o]+a[3*o+u])*n}return t},e.str=function(t){return"quat("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},t.exports=e},function(t,a,n){var r=n(1),o={};o.create=function(){var t=new r.ARRAY_TYPE(3);return t[0]=0,t[1]=0,t[2]=0,t},o.clone=function(t){var a=new r.ARRAY_TYPE(3);return a[0]=t[0],a[1]=t[1],a[2]=t[2],a},o.fromValues=function(t,a,n){var o=new r.ARRAY_TYPE(3);return o[0]=t,o[1]=a,o[2]=n,o},o.copy=function(t,a){return t[0]=a[0],t[1]=a[1],t[2]=a[2],t},o.set=function(t,a,n,r){return t[0]=a,t[1]=n,t[2]=r,t},o.add=function(t,a,n){return t[0]=a[0]+n[0],t[1]=a[1]+n[1],t[2]=a[2]+n[2],t},o.subtract=function(t,a,n){return t[0]=a[0]-n[0],t[1]=a[1]-n[1],t[2]=a[2]-n[2],t},o.sub=o.subtract,o.multiply=function(t,a,n){return t[0]=a[0]*n[0],t[1]=a[1]*n[1],t[2]=a[2]*n[2],t},o.mul=o.multiply,o.divide=function(t,a,n){return t[0]=a[0]/n[0],t[1]=a[1]/n[1],t[2]=a[2]/n[2],t},o.div=o.divide,o.min=function(t,a,n){return t[0]=Math.min(a[0],n[0]),t[1]=Math.min(a[1],n[1]),t[2]=Math.min(a[2],n[2]), 29 | t},o.max=function(t,a,n){return t[0]=Math.max(a[0],n[0]),t[1]=Math.max(a[1],n[1]),t[2]=Math.max(a[2],n[2]),t},o.scale=function(t,a,n){return t[0]=a[0]*n,t[1]=a[1]*n,t[2]=a[2]*n,t},o.scaleAndAdd=function(t,a,n,r){return t[0]=a[0]+n[0]*r,t[1]=a[1]+n[1]*r,t[2]=a[2]+n[2]*r,t},o.distance=function(t,a){var n=a[0]-t[0],r=a[1]-t[1],o=a[2]-t[2];return Math.sqrt(n*n+r*r+o*o)},o.dist=o.distance,o.squaredDistance=function(t,a){var n=a[0]-t[0],r=a[1]-t[1],o=a[2]-t[2];return n*n+r*r+o*o},o.sqrDist=o.squaredDistance,o.length=function(t){var a=t[0],n=t[1],r=t[2];return Math.sqrt(a*a+n*n+r*r)},o.len=o.length,o.squaredLength=function(t){var a=t[0],n=t[1],r=t[2];return a*a+n*n+r*r},o.sqrLen=o.squaredLength,o.negate=function(t,a){return t[0]=-a[0],t[1]=-a[1],t[2]=-a[2],t},o.inverse=function(t,a){return t[0]=1/a[0],t[1]=1/a[1],t[2]=1/a[2],t},o.normalize=function(t,a){var n=a[0],r=a[1],o=a[2],l=n*n+r*r+o*o;return l>0&&(l=1/Math.sqrt(l),t[0]=a[0]*l,t[1]=a[1]*l,t[2]=a[2]*l),t},o.dot=function(t,a){return t[0]*a[0]+t[1]*a[1]+t[2]*a[2]},o.cross=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=n[0],e=n[1],M=n[2];return t[0]=o*M-l*e,t[1]=l*u-r*M,t[2]=r*e-o*u,t},o.lerp=function(t,a,n,r){var o=a[0],l=a[1],u=a[2];return t[0]=o+r*(n[0]-o),t[1]=l+r*(n[1]-l),t[2]=u+r*(n[2]-u),t},o.hermite=function(t,a,n,r,o,l){var u=l*l,e=u*(2*l-3)+1,M=u*(l-2)+l,i=u*(l-1),s=u*(3-2*l);return t[0]=a[0]*e+n[0]*M+r[0]*i+o[0]*s,t[1]=a[1]*e+n[1]*M+r[1]*i+o[1]*s,t[2]=a[2]*e+n[2]*M+r[2]*i+o[2]*s,t},o.bezier=function(t,a,n,r,o,l){var u=1-l,e=u*u,M=l*l,i=e*u,s=3*l*e,c=3*M*u,f=M*l;return t[0]=a[0]*i+n[0]*s+r[0]*c+o[0]*f,t[1]=a[1]*i+n[1]*s+r[1]*c+o[1]*f,t[2]=a[2]*i+n[2]*s+r[2]*c+o[2]*f,t},o.random=function(t,a){a=a||1;var n=2*r.RANDOM()*Math.PI,o=2*r.RANDOM()-1,l=Math.sqrt(1-o*o)*a;return t[0]=Math.cos(n)*l,t[1]=Math.sin(n)*l,t[2]=o*a,t},o.transformMat4=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=n[3]*r+n[7]*o+n[11]*l+n[15];return u=u||1,t[0]=(n[0]*r+n[4]*o+n[8]*l+n[12])/u,t[1]=(n[1]*r+n[5]*o+n[9]*l+n[13])/u,t[2]=(n[2]*r+n[6]*o+n[10]*l+n[14])/u,t},o.transformMat3=function(t,a,n){var r=a[0],o=a[1],l=a[2];return t[0]=r*n[0]+o*n[3]+l*n[6],t[1]=r*n[1]+o*n[4]+l*n[7],t[2]=r*n[2]+o*n[5]+l*n[8],t},o.transformQuat=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=n[0],e=n[1],M=n[2],i=n[3],s=i*r+e*l-M*o,c=i*o+M*r-u*l,f=i*l+u*o-e*r,D=-u*r-e*o-M*l;return t[0]=s*i+D*-u+c*-M-f*-e,t[1]=c*i+D*-e+f*-u-s*-M,t[2]=f*i+D*-M+s*-e-c*-u,t},o.rotateX=function(t,a,n,r){var o=[],l=[];return o[0]=a[0]-n[0],o[1]=a[1]-n[1],o[2]=a[2]-n[2],l[0]=o[0],l[1]=o[1]*Math.cos(r)-o[2]*Math.sin(r),l[2]=o[1]*Math.sin(r)+o[2]*Math.cos(r),t[0]=l[0]+n[0],t[1]=l[1]+n[1],t[2]=l[2]+n[2],t},o.rotateY=function(t,a,n,r){var o=[],l=[];return o[0]=a[0]-n[0],o[1]=a[1]-n[1],o[2]=a[2]-n[2],l[0]=o[2]*Math.sin(r)+o[0]*Math.cos(r),l[1]=o[1],l[2]=o[2]*Math.cos(r)-o[0]*Math.sin(r),t[0]=l[0]+n[0],t[1]=l[1]+n[1],t[2]=l[2]+n[2],t},o.rotateZ=function(t,a,n,r){var o=[],l=[];return o[0]=a[0]-n[0],o[1]=a[1]-n[1],o[2]=a[2]-n[2],l[0]=o[0]*Math.cos(r)-o[1]*Math.sin(r),l[1]=o[0]*Math.sin(r)+o[1]*Math.cos(r),l[2]=o[2],t[0]=l[0]+n[0],t[1]=l[1]+n[1],t[2]=l[2]+n[2],t},o.forEach=function(){var t=o.create();return function(a,n,r,o,l,u){var e,M;for(n||(n=3),r||(r=0),M=o?Math.min(o*n+r,a.length):a.length,e=r;M>e;e+=n)t[0]=a[e],t[1]=a[e+1],t[2]=a[e+2],l(t,t,u),a[e]=t[0],a[e+1]=t[1],a[e+2]=t[2];return a}}(),o.angle=function(t,a){var n=o.fromValues(t[0],t[1],t[2]),r=o.fromValues(a[0],a[1],a[2]);o.normalize(n,n),o.normalize(r,r);var l=o.dot(n,r);return l>1?0:Math.acos(l)},o.str=function(t){return"vec3("+t[0]+", "+t[1]+", "+t[2]+")"},t.exports=o},function(t,a,n){var r=n(1),o={};o.create=function(){var t=new r.ARRAY_TYPE(4);return t[0]=0,t[1]=0,t[2]=0,t[3]=0,t},o.clone=function(t){var a=new r.ARRAY_TYPE(4);return a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=t[3],a},o.fromValues=function(t,a,n,o){var l=new r.ARRAY_TYPE(4);return l[0]=t,l[1]=a,l[2]=n,l[3]=o,l},o.copy=function(t,a){return t[0]=a[0],t[1]=a[1],t[2]=a[2],t[3]=a[3],t},o.set=function(t,a,n,r,o){return t[0]=a,t[1]=n,t[2]=r,t[3]=o,t},o.add=function(t,a,n){return t[0]=a[0]+n[0],t[1]=a[1]+n[1],t[2]=a[2]+n[2],t[3]=a[3]+n[3],t},o.subtract=function(t,a,n){return t[0]=a[0]-n[0],t[1]=a[1]-n[1],t[2]=a[2]-n[2],t[3]=a[3]-n[3],t},o.sub=o.subtract,o.multiply=function(t,a,n){return t[0]=a[0]*n[0],t[1]=a[1]*n[1],t[2]=a[2]*n[2],t[3]=a[3]*n[3],t},o.mul=o.multiply,o.divide=function(t,a,n){return t[0]=a[0]/n[0],t[1]=a[1]/n[1],t[2]=a[2]/n[2],t[3]=a[3]/n[3],t},o.div=o.divide,o.min=function(t,a,n){return t[0]=Math.min(a[0],n[0]),t[1]=Math.min(a[1],n[1]),t[2]=Math.min(a[2],n[2]),t[3]=Math.min(a[3],n[3]),t},o.max=function(t,a,n){return t[0]=Math.max(a[0],n[0]),t[1]=Math.max(a[1],n[1]),t[2]=Math.max(a[2],n[2]),t[3]=Math.max(a[3],n[3]),t},o.scale=function(t,a,n){return t[0]=a[0]*n,t[1]=a[1]*n,t[2]=a[2]*n,t[3]=a[3]*n,t},o.scaleAndAdd=function(t,a,n,r){return t[0]=a[0]+n[0]*r,t[1]=a[1]+n[1]*r,t[2]=a[2]+n[2]*r,t[3]=a[3]+n[3]*r,t},o.distance=function(t,a){var n=a[0]-t[0],r=a[1]-t[1],o=a[2]-t[2],l=a[3]-t[3];return Math.sqrt(n*n+r*r+o*o+l*l)},o.dist=o.distance,o.squaredDistance=function(t,a){var n=a[0]-t[0],r=a[1]-t[1],o=a[2]-t[2],l=a[3]-t[3];return n*n+r*r+o*o+l*l},o.sqrDist=o.squaredDistance,o.length=function(t){var a=t[0],n=t[1],r=t[2],o=t[3];return Math.sqrt(a*a+n*n+r*r+o*o)},o.len=o.length,o.squaredLength=function(t){var a=t[0],n=t[1],r=t[2],o=t[3];return a*a+n*n+r*r+o*o},o.sqrLen=o.squaredLength,o.negate=function(t,a){return t[0]=-a[0],t[1]=-a[1],t[2]=-a[2],t[3]=-a[3],t},o.inverse=function(t,a){return t[0]=1/a[0],t[1]=1/a[1],t[2]=1/a[2],t[3]=1/a[3],t},o.normalize=function(t,a){var n=a[0],r=a[1],o=a[2],l=a[3],u=n*n+r*r+o*o+l*l;return u>0&&(u=1/Math.sqrt(u),t[0]=n*u,t[1]=r*u,t[2]=o*u,t[3]=l*u),t},o.dot=function(t,a){return t[0]*a[0]+t[1]*a[1]+t[2]*a[2]+t[3]*a[3]},o.lerp=function(t,a,n,r){var o=a[0],l=a[1],u=a[2],e=a[3];return t[0]=o+r*(n[0]-o),t[1]=l+r*(n[1]-l),t[2]=u+r*(n[2]-u),t[3]=e+r*(n[3]-e),t},o.random=function(t,a){return a=a||1,t[0]=r.RANDOM(),t[1]=r.RANDOM(),t[2]=r.RANDOM(),t[3]=r.RANDOM(),o.normalize(t,t),o.scale(t,t,a),t},o.transformMat4=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=a[3];return t[0]=n[0]*r+n[4]*o+n[8]*l+n[12]*u,t[1]=n[1]*r+n[5]*o+n[9]*l+n[13]*u,t[2]=n[2]*r+n[6]*o+n[10]*l+n[14]*u,t[3]=n[3]*r+n[7]*o+n[11]*l+n[15]*u,t},o.transformQuat=function(t,a,n){var r=a[0],o=a[1],l=a[2],u=n[0],e=n[1],M=n[2],i=n[3],s=i*r+e*l-M*o,c=i*o+M*r-u*l,f=i*l+u*o-e*r,D=-u*r-e*o-M*l;return t[0]=s*i+D*-u+c*-M-f*-e,t[1]=c*i+D*-e+f*-u-s*-M,t[2]=f*i+D*-M+s*-e-c*-u,t[3]=a[3],t},o.forEach=function(){var t=o.create();return function(a,n,r,o,l,u){var e,M;for(n||(n=4),r||(r=0),M=o?Math.min(o*n+r,a.length):a.length,e=r;M>e;e+=n)t[0]=a[e],t[1]=a[e+1],t[2]=a[e+2],t[3]=a[e+3],l(t,t,u),a[e]=t[0],a[e+1]=t[1],a[e+2]=t[2],a[e+3]=t[3];return a}}(),o.str=function(t){return"vec4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},t.exports=o},function(t,a,n){var r=n(1),o={};o.create=function(){var t=new r.ARRAY_TYPE(2);return t[0]=0,t[1]=0,t},o.clone=function(t){var a=new r.ARRAY_TYPE(2);return a[0]=t[0],a[1]=t[1],a},o.fromValues=function(t,a){var n=new r.ARRAY_TYPE(2);return n[0]=t,n[1]=a,n},o.copy=function(t,a){return t[0]=a[0],t[1]=a[1],t},o.set=function(t,a,n){return t[0]=a,t[1]=n,t},o.add=function(t,a,n){return t[0]=a[0]+n[0],t[1]=a[1]+n[1],t},o.subtract=function(t,a,n){return t[0]=a[0]-n[0],t[1]=a[1]-n[1],t},o.sub=o.subtract,o.multiply=function(t,a,n){return t[0]=a[0]*n[0],t[1]=a[1]*n[1],t},o.mul=o.multiply,o.divide=function(t,a,n){return t[0]=a[0]/n[0],t[1]=a[1]/n[1],t},o.div=o.divide,o.min=function(t,a,n){return t[0]=Math.min(a[0],n[0]),t[1]=Math.min(a[1],n[1]),t},o.max=function(t,a,n){return t[0]=Math.max(a[0],n[0]),t[1]=Math.max(a[1],n[1]),t},o.scale=function(t,a,n){return t[0]=a[0]*n,t[1]=a[1]*n,t},o.scaleAndAdd=function(t,a,n,r){return t[0]=a[0]+n[0]*r,t[1]=a[1]+n[1]*r,t},o.distance=function(t,a){var n=a[0]-t[0],r=a[1]-t[1];return Math.sqrt(n*n+r*r)},o.dist=o.distance,o.squaredDistance=function(t,a){var n=a[0]-t[0],r=a[1]-t[1];return n*n+r*r},o.sqrDist=o.squaredDistance,o.length=function(t){var a=t[0],n=t[1];return Math.sqrt(a*a+n*n)},o.len=o.length,o.squaredLength=function(t){var a=t[0],n=t[1];return a*a+n*n},o.sqrLen=o.squaredLength,o.negate=function(t,a){return t[0]=-a[0],t[1]=-a[1],t},o.inverse=function(t,a){return t[0]=1/a[0],t[1]=1/a[1],t},o.normalize=function(t,a){var n=a[0],r=a[1],o=n*n+r*r;return o>0&&(o=1/Math.sqrt(o),t[0]=a[0]*o,t[1]=a[1]*o),t},o.dot=function(t,a){return t[0]*a[0]+t[1]*a[1]},o.cross=function(t,a,n){var r=a[0]*n[1]-a[1]*n[0];return t[0]=t[1]=0,t[2]=r,t},o.lerp=function(t,a,n,r){var o=a[0],l=a[1];return t[0]=o+r*(n[0]-o),t[1]=l+r*(n[1]-l),t},o.random=function(t,a){a=a||1;var n=2*r.RANDOM()*Math.PI;return t[0]=Math.cos(n)*a,t[1]=Math.sin(n)*a,t},o.transformMat2=function(t,a,n){var r=a[0],o=a[1];return t[0]=n[0]*r+n[2]*o,t[1]=n[1]*r+n[3]*o,t},o.transformMat2d=function(t,a,n){var r=a[0],o=a[1];return t[0]=n[0]*r+n[2]*o+n[4],t[1]=n[1]*r+n[3]*o+n[5],t},o.transformMat3=function(t,a,n){var r=a[0],o=a[1];return t[0]=n[0]*r+n[3]*o+n[6],t[1]=n[1]*r+n[4]*o+n[7],t},o.transformMat4=function(t,a,n){var r=a[0],o=a[1];return t[0]=n[0]*r+n[4]*o+n[12],t[1]=n[1]*r+n[5]*o+n[13],t},o.forEach=function(){var t=o.create();return function(a,n,r,o,l,u){var e,M;for(n||(n=2),r||(r=0),M=o?Math.min(o*n+r,a.length):a.length,e=r;M>e;e+=n)t[0]=a[e],t[1]=a[e+1],l(t,t,u),a[e]=t[0],a[e+1]=t[1];return a}}(),o.str=function(t){return"vec2("+t[0]+", "+t[1]+")"},t.exports=o}])}); --------------------------------------------------------------------------------