├── .editorconfig ├── .gitattributes ├── .github ├── flame-ball.jpeg └── preview.png ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── dist_github ├── app.js ├── images │ └── circle-particle.png ├── js │ ├── OrbitControl.js │ └── stats.d.ts └── shader │ ├── fragmentFlameShader.glsl │ ├── fragmentParticleShader.glsl │ ├── vertexFlameShader.glsl │ └── vertexParticleShader.glsl ├── fire_simulation_report.pdf ├── index.html ├── package.json ├── src ├── animation │ ├── Interpolation.ts │ ├── explosionController.ts │ └── flameAnimation.ts ├── assetsManager.ts ├── constants.ts ├── controller.ts ├── images │ ├── circle-particle.png │ └── spark.png ├── js │ ├── OrbitControl.js │ └── stats.d.ts ├── main.ts ├── object │ ├── flameSphere.ts │ └── flareParticle.ts ├── renderer.ts ├── shader │ ├── fragmentFlameShader.glsl │ ├── fragmentParticleShader.glsl │ ├── vertexFlameShader.glsl │ └── vertexParticleShader.glsl └── utils.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Exclude Vendored 2 | dist_github/* linguist-vendored 3 | src/js/OrbitControl.js linguist-vendored 4 | -------------------------------------------------------------------------------- /.github/flame-ball.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neungkl/fire-simulation/52b75e8685694a9cb320be7d827ba2b1b546002f/.github/flame-ball.jpeg -------------------------------------------------------------------------------- /.github/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neungkl/fire-simulation/52b75e8685694a9cb320be7d827ba2b1b546002f/.github/preview.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | .DS_Store 6 | 7 | # Visual Studio Project 8 | .vscode 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Dependency directories 17 | node_modules 18 | 19 | # Optional npm cache directory 20 | .npm 21 | 22 | # TypeScript Ignore 23 | .baseDir.ts 24 | .tscache/ 25 | tscommand-*.tmp.txt 26 | 27 | # Build Directory 28 | src/*.js 29 | !src/js/ 30 | dist/ 31 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | webpack: { 5 | default: { 6 | entry: "./dist/main.js", 7 | output: { 8 | path: "dist/", 9 | filename: "app.js", 10 | } 11 | } 12 | }, 13 | copy: { 14 | main: { 15 | files: [{ 16 | expand: true, 17 | cwd: 'src/', 18 | src: ['js/**/*','shader/**/*', 'images/**/*'], 19 | dest: 'dist/' 20 | }], 21 | }, 22 | }, 23 | ts: { 24 | default: { 25 | src: ["src/**/*.ts", "!node_modules/**"], 26 | outDir: "dist/", 27 | options: { 28 | sourceMap: false 29 | } 30 | } 31 | }, 32 | watch: { 33 | scripts: { 34 | files: ['src/**/*'], 35 | tasks: ['copy', 'ts', 'webpack'], 36 | options: { 37 | spawn: false, 38 | }, 39 | }, 40 | } 41 | }); 42 | 43 | grunt.loadNpmTasks('grunt-contrib-copy'); 44 | grunt.loadNpmTasks('grunt-contrib-watch'); 45 | grunt.loadNpmTasks('grunt-webpack'); 46 | grunt.loadNpmTasks('grunt-ts'); 47 | 48 | grunt.registerTask('default', ['copy', 'ts', 'webpack']); 49 | }; 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Kosate Limpongsa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fire Simulation 2 | === 3 | 4 | 5 | 6 | *This is my original project for propose to 2110594 Computer Graphics and Physics Simulation class 7 | in Chulalongkorn University.* 8 | 9 | Fire simulation base on WebGL using THREE.JS library. Using volumetric fire technique for this implementation. Fully customizing on fire color and particle system in real-time. 10 | The simulator are run smoothly with 60 fps frame rate, 11 | optmized by object pooling method. 12 | 13 | ## Demo 14 | 15 | :point_right: Available demo : [https://neungkl.github.io/fire-simulation/](https://neungkl.github.io/fire-simulation/) 16 | 17 | ## Report 18 | 19 | :page_facing_up: You can read full PDF report in [fire_simulation_report.pdf](fire_simulation_report.pdf) 20 | 21 | 22 | ## Works 23 | 24 | - I starting with building a volumetric fire called flame ball, with customize vertex shader and fragment shader, 25 | the result look similar to below figure. 26 | 27 | 28 | 29 | - The flame ball part I using [Perlin Noise](https://en.wikipedia.org/wiki/Perlin_noise) algorithm for making the 30 | randomize texture of flame ball and apply the color depends on the deep 31 | of surface. 32 | - Combine each flame ball to a large fire using [Interpolation](https://en.wikipedia.org/wiki/Interpolation) for animation 33 | each frame of flame ball. 34 | - Add particle system for spark system. 35 | - Fully customization for fire color and spark color. 36 | - Renderer time scale included. 37 | - Optimized by using object pooling approach. 38 | 39 | ## Usage 40 | 41 | ### Run the simulator 42 | 43 | 1. Enter following command. 44 | 45 | ```sh 46 | $ npm install -g typescript grunt 47 | $ npm install 48 | ``` 49 | 2. Run build script 50 | 51 | ``` 52 | $ grunt 53 | ``` 54 | 3. Change code in `index.html` 55 | 56 | ```html 57 | 58 | 59 | 60 | 61 | 62 | ``` 63 | 4. Open file `index.html` 64 | 65 | ### Developement 66 | 67 | ``` 68 | $ grunt watch 69 | ``` 70 | 71 | ## Thanks 72 | 73 | **Reference** 74 | 75 | - Palin Noise [https://en.wikipedia.org/wiki/Perlin_noise](https://en.wikipedia.org/wiki/Perlin_noise) 76 | - Vertex Displacement with a noise function using GLSL and three.js [https://www.clicktorelease.com/blog/vertex-displacement-noise-3d-webgl-glsl-three-js](https://www.clicktorelease.com/blog/vertex-displacement-noise-3d-webgl-glsl-three-js) 77 | - Easing Equation by Robert Penner [http://gizma.com/easing/](http://gizma.com/easing/) 78 | - Object Pool Pattern [https://en.wikipedia.org/wiki/Object_pool_pattern](https://en.wikipedia.org/wiki/Object_pool_pattern) 79 | 80 | **Tools** 81 | 82 | - THREE.js — [https://threejs.org/](https://threejs.org/) 83 | - Dat GUI — [https://github.com/dataarts/dat.gui](https://github.com/dataarts/dat.gui) 84 | - Stats.js — [https://github.com/mrdoob/stats.js/](https://github.com/mrdoob/stats.js/) 85 | - TypeScript — [https://www.typescriptlang.org/](https://www.typescriptlang.org/) 86 | 87 | ## License 88 | 89 | [MIT](LICENSE) © Kosate Limpongsa 90 | -------------------------------------------------------------------------------- /dist_github/app.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ([ 44 | /* 0 */ 45 | /***/ function(module, exports, __webpack_require__) { 46 | 47 | "use strict"; 48 | var renderer_1 = __webpack_require__(1); 49 | var controller_1 = __webpack_require__(3); 50 | var explosionController_1 = __webpack_require__(4); 51 | window.onload = function () { 52 | var time = Date.now(); 53 | var timeScale; 54 | var stats = new Stats(); 55 | stats.showPanel(0); 56 | document.getElementById("stats").appendChild(stats.domElement); 57 | controller_1.Controller.init(); 58 | renderer_1.Renderer.init(); 59 | explosionController_1.ExplosionController.init(); 60 | timeScale = controller_1.Controller.getParams().TimeScale; 61 | var onRequestAnimationFrame = function () { 62 | requestAnimationFrame(onRequestAnimationFrame); 63 | stats.begin(); 64 | renderer_1.Renderer.animate(); 65 | stats.end(); 66 | }; 67 | var deltaTimeMaximum = 1000 / 65; 68 | renderer_1.Renderer.setUpdateFunc(function () { 69 | var timeDiff = (Date.now() - time); 70 | explosionController_1.ExplosionController.update(timeDiff > deltaTimeMaximum ? deltaTimeMaximum : timeDiff); 71 | time = Date.now(); 72 | }); 73 | controller_1.Controller.setRestartFunc(function () { 74 | explosionController_1.ExplosionController.reset(); 75 | }); 76 | requestAnimationFrame(onRequestAnimationFrame); 77 | window.addEventListener('resize', function () { renderer_1.Renderer.onWindowResize(); }, false); 78 | }; 79 | 80 | 81 | /***/ }, 82 | /* 1 */ 83 | /***/ function(module, exports, __webpack_require__) { 84 | 85 | "use strict"; 86 | __webpack_require__(2); 87 | var controller_1 = __webpack_require__(3); 88 | var Renderer = (function () { 89 | function Renderer() { 90 | } 91 | Renderer.init = function () { 92 | var _this = this; 93 | this.scene = new THREE.Scene(); 94 | this.scene.background = new THREE.Color(0xf8f8f8); 95 | this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); 96 | this.renderer = new THREE.WebGLRenderer({ antialias: true }); 97 | this.renderer.setSize(window.innerWidth, window.innerHeight); 98 | document.body.appendChild(this.renderer.domElement); 99 | this.gridHelper = new THREE.GridHelper(100, 40, 0xdddddd, 0xdddddd); 100 | this.scene.add(this.gridHelper); 101 | this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); 102 | this.controls.enableDamping = true; 103 | this.controls.dampingFactor = 0.25; 104 | this.controls.enableZoom = true; 105 | this.camera.position.z = 75; 106 | this.camera.position.y = 75; 107 | this.camera.position.x = 75; 108 | controller_1.Controller.attachEvent(controller_1.Controller.INVERTED_BACKGROUND, function (value) { 109 | if (value) { 110 | _this.scene.background = new THREE.Color(0x111111); 111 | _this.scene.remove(_this.gridHelper); 112 | _this.gridHelper = new THREE.GridHelper(100, 40, 0x444444, 0x444444); 113 | _this.scene.add(_this.gridHelper); 114 | } 115 | else { 116 | _this.scene.background = new THREE.Color(0xf8f8f8); 117 | _this.scene.remove(_this.gridHelper); 118 | _this.gridHelper = new THREE.GridHelper(100, 40, 0xdddddd, 0xdddddd); 119 | _this.scene.add(_this.gridHelper); 120 | } 121 | }); 122 | controller_1.Controller.attachEvent(controller_1.Controller.SHOW_GRID, function (value) { 123 | _this.gridHelper.visible = value; 124 | }); 125 | }; 126 | Renderer.animate = function () { 127 | this.controls.update(); 128 | if (this.updateCallback != null) { 129 | this.updateCallback(); 130 | } 131 | this.renderer.render(this.scene, this.camera); 132 | }; 133 | Renderer.addToScene = function (obj) { 134 | this.scene.add(obj); 135 | }; 136 | Renderer.removeFromScene = function (obj) { 137 | this.scene.remove(obj); 138 | }; 139 | Renderer.setUpdateFunc = function (func) { 140 | this.updateCallback = func; 141 | }; 142 | Renderer.onWindowResize = function () { 143 | this.camera.aspect = window.innerWidth / window.innerHeight; 144 | this.camera.updateProjectionMatrix(); 145 | this.renderer.setSize(window.innerWidth, window.innerHeight); 146 | }; 147 | Renderer.updateCallback = null; 148 | return Renderer; 149 | }()); 150 | exports.Renderer = Renderer; 151 | 152 | 153 | /***/ }, 154 | /* 2 */ 155 | /***/ function(module, exports) { 156 | 157 | /** 158 | * @author qiao / https://github.com/qiao 159 | * @author mrdoob / http://mrdoob.com 160 | * @author alteredq / http://alteredqualia.com/ 161 | * @author WestLangley / http://github.com/WestLangley 162 | * @author erich666 / http://erichaines.com 163 | */ 164 | 165 | // This set of controls performs orbiting, dollying (zooming), and panning. 166 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 167 | // 168 | // Orbit - left mouse / touch: one finger move 169 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 170 | // Pan - right mouse, or arrow keys / touch: three finter swipe 171 | 172 | THREE.OrbitControls = function ( object, domElement ) { 173 | 174 | this.object = object; 175 | 176 | this.domElement = ( domElement !== undefined ) ? domElement : document; 177 | 178 | // Set to false to disable this control 179 | this.enabled = true; 180 | 181 | // "target" sets the location of focus, where the object orbits around 182 | this.target = new THREE.Vector3(); 183 | 184 | // How far you can dolly in and out ( PerspectiveCamera only ) 185 | this.minDistance = 0; 186 | this.maxDistance = Infinity; 187 | 188 | // How far you can zoom in and out ( OrthographicCamera only ) 189 | this.minZoom = 0; 190 | this.maxZoom = Infinity; 191 | 192 | // How far you can orbit vertically, upper and lower limits. 193 | // Range is 0 to Math.PI radians. 194 | this.minPolarAngle = 0; // radians 195 | this.maxPolarAngle = Math.PI; // radians 196 | 197 | // How far you can orbit horizontally, upper and lower limits. 198 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 199 | this.minAzimuthAngle = - Infinity; // radians 200 | this.maxAzimuthAngle = Infinity; // radians 201 | 202 | // Set to true to enable damping (inertia) 203 | // If damping is enabled, you must call controls.update() in your animation loop 204 | this.enableDamping = false; 205 | this.dampingFactor = 0.25; 206 | 207 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 208 | // Set to false to disable zooming 209 | this.enableZoom = true; 210 | this.zoomSpeed = 1.0; 211 | 212 | // Set to false to disable rotating 213 | this.enableRotate = true; 214 | this.rotateSpeed = 1.0; 215 | 216 | // Set to false to disable panning 217 | this.enablePan = true; 218 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 219 | 220 | // Set to true to automatically rotate around the target 221 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 222 | this.autoRotate = false; 223 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 224 | 225 | // Set to false to disable use of the keys 226 | this.enableKeys = true; 227 | 228 | // The four arrow keys 229 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 230 | 231 | // Mouse buttons 232 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 233 | 234 | // for reset 235 | this.target0 = this.target.clone(); 236 | this.position0 = this.object.position.clone(); 237 | this.zoom0 = this.object.zoom; 238 | 239 | // 240 | // public methods 241 | // 242 | 243 | this.getPolarAngle = function () { 244 | 245 | return spherical.phi; 246 | 247 | }; 248 | 249 | this.getAzimuthalAngle = function () { 250 | 251 | return spherical.theta; 252 | 253 | }; 254 | 255 | this.reset = function () { 256 | 257 | scope.target.copy( scope.target0 ); 258 | scope.object.position.copy( scope.position0 ); 259 | scope.object.zoom = scope.zoom0; 260 | 261 | scope.object.updateProjectionMatrix(); 262 | scope.dispatchEvent( changeEvent ); 263 | 264 | scope.update(); 265 | 266 | state = STATE.NONE; 267 | 268 | }; 269 | 270 | // this method is exposed, but perhaps it would be better if we can make it private... 271 | this.update = function() { 272 | 273 | var offset = new THREE.Vector3(); 274 | 275 | // so camera.up is the orbit axis 276 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 277 | var quatInverse = quat.clone().inverse(); 278 | 279 | var lastPosition = new THREE.Vector3(); 280 | var lastQuaternion = new THREE.Quaternion(); 281 | 282 | return function update () { 283 | 284 | var position = scope.object.position; 285 | 286 | offset.copy( position ).sub( scope.target ); 287 | 288 | // rotate offset to "y-axis-is-up" space 289 | offset.applyQuaternion( quat ); 290 | 291 | // angle from z-axis around y-axis 292 | spherical.setFromVector3( offset ); 293 | 294 | if ( scope.autoRotate && state === STATE.NONE ) { 295 | 296 | rotateLeft( getAutoRotationAngle() ); 297 | 298 | } 299 | 300 | spherical.theta += sphericalDelta.theta; 301 | spherical.phi += sphericalDelta.phi; 302 | 303 | // restrict theta to be between desired limits 304 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 305 | 306 | // restrict phi to be between desired limits 307 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 308 | 309 | spherical.makeSafe(); 310 | 311 | 312 | spherical.radius *= scale; 313 | 314 | // restrict radius to be between desired limits 315 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 316 | 317 | // move target to panned location 318 | scope.target.add( panOffset ); 319 | 320 | offset.setFromSpherical( spherical ); 321 | 322 | // rotate offset back to "camera-up-vector-is-up" space 323 | offset.applyQuaternion( quatInverse ); 324 | 325 | position.copy( scope.target ).add( offset ); 326 | 327 | scope.object.lookAt( scope.target ); 328 | 329 | if ( scope.enableDamping === true ) { 330 | 331 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 332 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 333 | 334 | } else { 335 | 336 | sphericalDelta.set( 0, 0, 0 ); 337 | 338 | } 339 | 340 | scale = 1; 341 | panOffset.set( 0, 0, 0 ); 342 | 343 | // update condition is: 344 | // min(camera displacement, camera rotation in radians)^2 > EPS 345 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 346 | 347 | if ( zoomChanged || 348 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 349 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 350 | 351 | scope.dispatchEvent( changeEvent ); 352 | 353 | lastPosition.copy( scope.object.position ); 354 | lastQuaternion.copy( scope.object.quaternion ); 355 | zoomChanged = false; 356 | 357 | return true; 358 | 359 | } 360 | 361 | return false; 362 | 363 | }; 364 | 365 | }(); 366 | 367 | this.dispose = function() { 368 | 369 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 370 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 371 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 372 | 373 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 374 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 375 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 376 | 377 | document.removeEventListener( 'mousemove', onMouseMove, false ); 378 | document.removeEventListener( 'mouseup', onMouseUp, false ); 379 | 380 | window.removeEventListener( 'keydown', onKeyDown, false ); 381 | 382 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 383 | 384 | }; 385 | 386 | // 387 | // internals 388 | // 389 | 390 | var scope = this; 391 | 392 | var changeEvent = { type: 'change' }; 393 | var startEvent = { type: 'start' }; 394 | var endEvent = { type: 'end' }; 395 | 396 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 397 | 398 | var state = STATE.NONE; 399 | 400 | var EPS = 0.000001; 401 | 402 | // current position in spherical coordinates 403 | var spherical = new THREE.Spherical(); 404 | var sphericalDelta = new THREE.Spherical(); 405 | 406 | var scale = 1; 407 | var panOffset = new THREE.Vector3(); 408 | var zoomChanged = false; 409 | 410 | var rotateStart = new THREE.Vector2(); 411 | var rotateEnd = new THREE.Vector2(); 412 | var rotateDelta = new THREE.Vector2(); 413 | 414 | var panStart = new THREE.Vector2(); 415 | var panEnd = new THREE.Vector2(); 416 | var panDelta = new THREE.Vector2(); 417 | 418 | var dollyStart = new THREE.Vector2(); 419 | var dollyEnd = new THREE.Vector2(); 420 | var dollyDelta = new THREE.Vector2(); 421 | 422 | function getAutoRotationAngle() { 423 | 424 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 425 | 426 | } 427 | 428 | function getZoomScale() { 429 | 430 | return Math.pow( 0.95, scope.zoomSpeed ); 431 | 432 | } 433 | 434 | function rotateLeft( angle ) { 435 | 436 | sphericalDelta.theta -= angle; 437 | 438 | } 439 | 440 | function rotateUp( angle ) { 441 | 442 | sphericalDelta.phi -= angle; 443 | 444 | } 445 | 446 | var panLeft = function() { 447 | 448 | var v = new THREE.Vector3(); 449 | 450 | return function panLeft( distance, objectMatrix ) { 451 | 452 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 453 | v.multiplyScalar( - distance ); 454 | 455 | panOffset.add( v ); 456 | 457 | }; 458 | 459 | }(); 460 | 461 | var panUp = function() { 462 | 463 | var v = new THREE.Vector3(); 464 | 465 | return function panUp( distance, objectMatrix ) { 466 | 467 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 468 | v.multiplyScalar( distance ); 469 | 470 | panOffset.add( v ); 471 | 472 | }; 473 | 474 | }(); 475 | 476 | // deltaX and deltaY are in pixels; right and down are positive 477 | var pan = function() { 478 | 479 | var offset = new THREE.Vector3(); 480 | 481 | return function pan ( deltaX, deltaY ) { 482 | 483 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 484 | 485 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 486 | 487 | // perspective 488 | var position = scope.object.position; 489 | offset.copy( position ).sub( scope.target ); 490 | var targetDistance = offset.length(); 491 | 492 | // half of the fov is center to top of screen 493 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 494 | 495 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 496 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 497 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 498 | 499 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 500 | 501 | // orthographic 502 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 503 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 504 | 505 | } else { 506 | 507 | // camera neither orthographic nor perspective 508 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 509 | scope.enablePan = false; 510 | 511 | } 512 | 513 | }; 514 | 515 | }(); 516 | 517 | function dollyIn( dollyScale ) { 518 | 519 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 520 | 521 | scale /= dollyScale; 522 | 523 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 524 | 525 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 526 | scope.object.updateProjectionMatrix(); 527 | zoomChanged = true; 528 | 529 | } else { 530 | 531 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 532 | scope.enableZoom = false; 533 | 534 | } 535 | 536 | } 537 | 538 | function dollyOut( dollyScale ) { 539 | 540 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 541 | 542 | scale *= dollyScale; 543 | 544 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 545 | 546 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 547 | scope.object.updateProjectionMatrix(); 548 | zoomChanged = true; 549 | 550 | } else { 551 | 552 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 553 | scope.enableZoom = false; 554 | 555 | } 556 | 557 | } 558 | 559 | // 560 | // event callbacks - update the object state 561 | // 562 | 563 | function handleMouseDownRotate( event ) { 564 | 565 | //console.log( 'handleMouseDownRotate' ); 566 | 567 | rotateStart.set( event.clientX, event.clientY ); 568 | 569 | } 570 | 571 | function handleMouseDownDolly( event ) { 572 | 573 | //console.log( 'handleMouseDownDolly' ); 574 | 575 | dollyStart.set( event.clientX, event.clientY ); 576 | 577 | } 578 | 579 | function handleMouseDownPan( event ) { 580 | 581 | //console.log( 'handleMouseDownPan' ); 582 | 583 | panStart.set( event.clientX, event.clientY ); 584 | 585 | } 586 | 587 | function handleMouseMoveRotate( event ) { 588 | 589 | //console.log( 'handleMouseMoveRotate' ); 590 | 591 | rotateEnd.set( event.clientX, event.clientY ); 592 | rotateDelta.subVectors( rotateEnd, rotateStart ); 593 | 594 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 595 | 596 | // rotating across whole screen goes 360 degrees around 597 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 598 | 599 | // rotating up and down along whole screen attempts to go 360, but limited to 180 600 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 601 | 602 | rotateStart.copy( rotateEnd ); 603 | 604 | scope.update(); 605 | 606 | } 607 | 608 | function handleMouseMoveDolly( event ) { 609 | 610 | //console.log( 'handleMouseMoveDolly' ); 611 | 612 | dollyEnd.set( event.clientX, event.clientY ); 613 | 614 | dollyDelta.subVectors( dollyEnd, dollyStart ); 615 | 616 | if ( dollyDelta.y > 0 ) { 617 | 618 | dollyIn( getZoomScale() ); 619 | 620 | } else if ( dollyDelta.y < 0 ) { 621 | 622 | dollyOut( getZoomScale() ); 623 | 624 | } 625 | 626 | dollyStart.copy( dollyEnd ); 627 | 628 | scope.update(); 629 | 630 | } 631 | 632 | function handleMouseMovePan( event ) { 633 | 634 | //console.log( 'handleMouseMovePan' ); 635 | 636 | panEnd.set( event.clientX, event.clientY ); 637 | 638 | panDelta.subVectors( panEnd, panStart ); 639 | 640 | pan( panDelta.x, panDelta.y ); 641 | 642 | panStart.copy( panEnd ); 643 | 644 | scope.update(); 645 | 646 | } 647 | 648 | function handleMouseUp( event ) { 649 | 650 | //console.log( 'handleMouseUp' ); 651 | 652 | } 653 | 654 | function handleMouseWheel( event ) { 655 | 656 | //console.log( 'handleMouseWheel' ); 657 | 658 | if ( event.deltaY < 0 ) { 659 | 660 | dollyOut( getZoomScale() ); 661 | 662 | } else if ( event.deltaY > 0 ) { 663 | 664 | dollyIn( getZoomScale() ); 665 | 666 | } 667 | 668 | scope.update(); 669 | 670 | } 671 | 672 | function handleKeyDown( event ) { 673 | 674 | //console.log( 'handleKeyDown' ); 675 | 676 | switch ( event.keyCode ) { 677 | 678 | case scope.keys.UP: 679 | pan( 0, scope.keyPanSpeed ); 680 | scope.update(); 681 | break; 682 | 683 | case scope.keys.BOTTOM: 684 | pan( 0, - scope.keyPanSpeed ); 685 | scope.update(); 686 | break; 687 | 688 | case scope.keys.LEFT: 689 | pan( scope.keyPanSpeed, 0 ); 690 | scope.update(); 691 | break; 692 | 693 | case scope.keys.RIGHT: 694 | pan( - scope.keyPanSpeed, 0 ); 695 | scope.update(); 696 | break; 697 | 698 | } 699 | 700 | } 701 | 702 | function handleTouchStartRotate( event ) { 703 | 704 | //console.log( 'handleTouchStartRotate' ); 705 | 706 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 707 | 708 | } 709 | 710 | function handleTouchStartDolly( event ) { 711 | 712 | //console.log( 'handleTouchStartDolly' ); 713 | 714 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 715 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 716 | 717 | var distance = Math.sqrt( dx * dx + dy * dy ); 718 | 719 | dollyStart.set( 0, distance ); 720 | 721 | } 722 | 723 | function handleTouchStartPan( event ) { 724 | 725 | //console.log( 'handleTouchStartPan' ); 726 | 727 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 728 | 729 | } 730 | 731 | function handleTouchMoveRotate( event ) { 732 | 733 | //console.log( 'handleTouchMoveRotate' ); 734 | 735 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 736 | rotateDelta.subVectors( rotateEnd, rotateStart ); 737 | 738 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 739 | 740 | // rotating across whole screen goes 360 degrees around 741 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 742 | 743 | // rotating up and down along whole screen attempts to go 360, but limited to 180 744 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 745 | 746 | rotateStart.copy( rotateEnd ); 747 | 748 | scope.update(); 749 | 750 | } 751 | 752 | function handleTouchMoveDolly( event ) { 753 | 754 | //console.log( 'handleTouchMoveDolly' ); 755 | 756 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 757 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 758 | 759 | var distance = Math.sqrt( dx * dx + dy * dy ); 760 | 761 | dollyEnd.set( 0, distance ); 762 | 763 | dollyDelta.subVectors( dollyEnd, dollyStart ); 764 | 765 | if ( dollyDelta.y > 0 ) { 766 | 767 | dollyOut( getZoomScale() ); 768 | 769 | } else if ( dollyDelta.y < 0 ) { 770 | 771 | dollyIn( getZoomScale() ); 772 | 773 | } 774 | 775 | dollyStart.copy( dollyEnd ); 776 | 777 | scope.update(); 778 | 779 | } 780 | 781 | function handleTouchMovePan( event ) { 782 | 783 | //console.log( 'handleTouchMovePan' ); 784 | 785 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 786 | 787 | panDelta.subVectors( panEnd, panStart ); 788 | 789 | pan( panDelta.x, panDelta.y ); 790 | 791 | panStart.copy( panEnd ); 792 | 793 | scope.update(); 794 | 795 | } 796 | 797 | function handleTouchEnd( event ) { 798 | 799 | //console.log( 'handleTouchEnd' ); 800 | 801 | } 802 | 803 | // 804 | // event handlers - FSM: listen for events and reset state 805 | // 806 | 807 | function onMouseDown( event ) { 808 | 809 | if ( scope.enabled === false ) return; 810 | 811 | event.preventDefault(); 812 | 813 | if ( event.button === scope.mouseButtons.ORBIT ) { 814 | 815 | if ( scope.enableRotate === false ) return; 816 | 817 | handleMouseDownRotate( event ); 818 | 819 | state = STATE.ROTATE; 820 | 821 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 822 | 823 | if ( scope.enableZoom === false ) return; 824 | 825 | handleMouseDownDolly( event ); 826 | 827 | state = STATE.DOLLY; 828 | 829 | } else if ( event.button === scope.mouseButtons.PAN ) { 830 | 831 | if ( scope.enablePan === false ) return; 832 | 833 | handleMouseDownPan( event ); 834 | 835 | state = STATE.PAN; 836 | 837 | } 838 | 839 | if ( state !== STATE.NONE ) { 840 | 841 | document.addEventListener( 'mousemove', onMouseMove, false ); 842 | document.addEventListener( 'mouseup', onMouseUp, false ); 843 | 844 | scope.dispatchEvent( startEvent ); 845 | 846 | } 847 | 848 | } 849 | 850 | function onMouseMove( event ) { 851 | 852 | if ( scope.enabled === false ) return; 853 | 854 | event.preventDefault(); 855 | 856 | if ( state === STATE.ROTATE ) { 857 | 858 | if ( scope.enableRotate === false ) return; 859 | 860 | handleMouseMoveRotate( event ); 861 | 862 | } else if ( state === STATE.DOLLY ) { 863 | 864 | if ( scope.enableZoom === false ) return; 865 | 866 | handleMouseMoveDolly( event ); 867 | 868 | } else if ( state === STATE.PAN ) { 869 | 870 | if ( scope.enablePan === false ) return; 871 | 872 | handleMouseMovePan( event ); 873 | 874 | } 875 | 876 | } 877 | 878 | function onMouseUp( event ) { 879 | 880 | if ( scope.enabled === false ) return; 881 | 882 | handleMouseUp( event ); 883 | 884 | document.removeEventListener( 'mousemove', onMouseMove, false ); 885 | document.removeEventListener( 'mouseup', onMouseUp, false ); 886 | 887 | scope.dispatchEvent( endEvent ); 888 | 889 | state = STATE.NONE; 890 | 891 | } 892 | 893 | function onMouseWheel( event ) { 894 | 895 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 896 | 897 | event.preventDefault(); 898 | event.stopPropagation(); 899 | 900 | handleMouseWheel( event ); 901 | 902 | scope.dispatchEvent( startEvent ); // not sure why these are here... 903 | scope.dispatchEvent( endEvent ); 904 | 905 | } 906 | 907 | function onKeyDown( event ) { 908 | 909 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 910 | 911 | handleKeyDown( event ); 912 | 913 | } 914 | 915 | function onTouchStart( event ) { 916 | 917 | if ( scope.enabled === false ) return; 918 | 919 | switch ( event.touches.length ) { 920 | 921 | case 1: // one-fingered touch: rotate 922 | 923 | if ( scope.enableRotate === false ) return; 924 | 925 | handleTouchStartRotate( event ); 926 | 927 | state = STATE.TOUCH_ROTATE; 928 | 929 | break; 930 | 931 | case 2: // two-fingered touch: dolly 932 | 933 | if ( scope.enableZoom === false ) return; 934 | 935 | handleTouchStartDolly( event ); 936 | 937 | state = STATE.TOUCH_DOLLY; 938 | 939 | break; 940 | 941 | case 3: // three-fingered touch: pan 942 | 943 | if ( scope.enablePan === false ) return; 944 | 945 | handleTouchStartPan( event ); 946 | 947 | state = STATE.TOUCH_PAN; 948 | 949 | break; 950 | 951 | default: 952 | 953 | state = STATE.NONE; 954 | 955 | } 956 | 957 | if ( state !== STATE.NONE ) { 958 | 959 | scope.dispatchEvent( startEvent ); 960 | 961 | } 962 | 963 | } 964 | 965 | function onTouchMove( event ) { 966 | 967 | if ( scope.enabled === false ) return; 968 | 969 | event.preventDefault(); 970 | event.stopPropagation(); 971 | 972 | switch ( event.touches.length ) { 973 | 974 | case 1: // one-fingered touch: rotate 975 | 976 | if ( scope.enableRotate === false ) return; 977 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 978 | 979 | handleTouchMoveRotate( event ); 980 | 981 | break; 982 | 983 | case 2: // two-fingered touch: dolly 984 | 985 | if ( scope.enableZoom === false ) return; 986 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 987 | 988 | handleTouchMoveDolly( event ); 989 | 990 | break; 991 | 992 | case 3: // three-fingered touch: pan 993 | 994 | if ( scope.enablePan === false ) return; 995 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 996 | 997 | handleTouchMovePan( event ); 998 | 999 | break; 1000 | 1001 | default: 1002 | 1003 | state = STATE.NONE; 1004 | 1005 | } 1006 | 1007 | } 1008 | 1009 | function onTouchEnd( event ) { 1010 | 1011 | if ( scope.enabled === false ) return; 1012 | 1013 | handleTouchEnd( event ); 1014 | 1015 | scope.dispatchEvent( endEvent ); 1016 | 1017 | state = STATE.NONE; 1018 | 1019 | } 1020 | 1021 | function onContextMenu( event ) { 1022 | 1023 | event.preventDefault(); 1024 | 1025 | } 1026 | 1027 | // 1028 | 1029 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1030 | 1031 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 1032 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1033 | 1034 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1035 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1036 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1037 | 1038 | window.addEventListener( 'keydown', onKeyDown, false ); 1039 | 1040 | // force an update at start 1041 | 1042 | this.update(); 1043 | 1044 | }; 1045 | 1046 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1047 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 1048 | 1049 | Object.defineProperties( THREE.OrbitControls.prototype, { 1050 | 1051 | center: { 1052 | 1053 | get: function () { 1054 | 1055 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 1056 | return this.target; 1057 | 1058 | } 1059 | 1060 | }, 1061 | 1062 | // backward compatibility 1063 | 1064 | noZoom: { 1065 | 1066 | get: function () { 1067 | 1068 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1069 | return ! this.enableZoom; 1070 | 1071 | }, 1072 | 1073 | set: function ( value ) { 1074 | 1075 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1076 | this.enableZoom = ! value; 1077 | 1078 | } 1079 | 1080 | }, 1081 | 1082 | noRotate: { 1083 | 1084 | get: function () { 1085 | 1086 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1087 | return ! this.enableRotate; 1088 | 1089 | }, 1090 | 1091 | set: function ( value ) { 1092 | 1093 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1094 | this.enableRotate = ! value; 1095 | 1096 | } 1097 | 1098 | }, 1099 | 1100 | noPan: { 1101 | 1102 | get: function () { 1103 | 1104 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1105 | return ! this.enablePan; 1106 | 1107 | }, 1108 | 1109 | set: function ( value ) { 1110 | 1111 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1112 | this.enablePan = ! value; 1113 | 1114 | } 1115 | 1116 | }, 1117 | 1118 | noKeys: { 1119 | 1120 | get: function () { 1121 | 1122 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1123 | return ! this.enableKeys; 1124 | 1125 | }, 1126 | 1127 | set: function ( value ) { 1128 | 1129 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1130 | this.enableKeys = ! value; 1131 | 1132 | } 1133 | 1134 | }, 1135 | 1136 | staticMoving : { 1137 | 1138 | get: function () { 1139 | 1140 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1141 | return ! this.enableDamping; 1142 | 1143 | }, 1144 | 1145 | set: function ( value ) { 1146 | 1147 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1148 | this.enableDamping = ! value; 1149 | 1150 | } 1151 | 1152 | }, 1153 | 1154 | dynamicDampingFactor : { 1155 | 1156 | get: function () { 1157 | 1158 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1159 | return this.dampingFactor; 1160 | 1161 | }, 1162 | 1163 | set: function ( value ) { 1164 | 1165 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1166 | this.dampingFactor = value; 1167 | 1168 | } 1169 | 1170 | } 1171 | 1172 | } ); 1173 | 1174 | 1175 | /***/ }, 1176 | /* 3 */ 1177 | /***/ function(module, exports) { 1178 | 1179 | "use strict"; 1180 | var Controller = (function () { 1181 | function Controller() { 1182 | } 1183 | Controller.init = function () { 1184 | this.eventListener = []; 1185 | var ControlParam = function () { 1186 | this.LightColor2 = '#ff8700'; 1187 | this.LightColor = '#f7f342'; 1188 | this.NormalColor = '#f7a90e'; 1189 | this.DarkColor2 = '#ff9800'; 1190 | this.GreyColor = '#3c342f'; 1191 | this.DarkColor = "#181818"; 1192 | this.TimeScale = 3; 1193 | this.ParticleSpread = 1; 1194 | this.ParticleColor = '#ffb400'; 1195 | this.InvertedBackground = false; 1196 | this.ShowGrid = true; 1197 | this.restart = function () { }; 1198 | }; 1199 | var params = new ControlParam(); 1200 | var gui = new dat.GUI(); 1201 | var f1 = gui.addFolder('Spawn Color'); 1202 | this.eventListener[Controller.DARK_COLOR] = f1.addColor(params, 'DarkColor'); 1203 | this.eventListener[Controller.DARK_COLOR_2] = f1.addColor(params, 'GreyColor'); 1204 | this.eventListener[Controller.DARK_COLOR_2] = f1.addColor(params, 'DarkColor2'); 1205 | this.eventListener[Controller.NORMAL_COLOR] = f1.addColor(params, 'NormalColor'); 1206 | this.eventListener[Controller.LIGHT_COLOR] = f1.addColor(params, 'LightColor'); 1207 | this.eventListener[Controller.LIGHT_COLOR_2] = f1.addColor(params, 'LightColor2'); 1208 | f1.open(); 1209 | var f2 = gui.addFolder('Flare Particle'); 1210 | this.eventListener[Controller.PARTICLE_SPREAD] = f2.add(params, 'ParticleSpread', 0, 2); 1211 | this.eventListener[Controller.PARTICLE_COLOR] = f2.addColor(params, 'ParticleColor'); 1212 | f2.open(); 1213 | this.eventListener[Controller.INVERTED_BACKGROUND] = gui.add(params, 'InvertedBackground'); 1214 | this.eventListener[Controller.SHOW_GRID] = gui.add(params, 'ShowGrid'); 1215 | this.eventListener[Controller.TIME_SCALE] = gui.add(params, 'TimeScale', 0, 10); 1216 | gui.add(params, 'restart'); 1217 | this.gui = gui; 1218 | this.params = params; 1219 | }; 1220 | Controller.getParams = function () { 1221 | return this.params; 1222 | }; 1223 | Controller.setRestartFunc = function (func) { 1224 | this.params.restart = func; 1225 | }; 1226 | Controller.attachEvent = function (key, callback) { 1227 | this.eventListener[key].onChange(callback); 1228 | }; 1229 | Controller.DARK_COLOR = 0; 1230 | Controller.NORMAL_COLOR = 1; 1231 | Controller.LIGHT_COLOR = 2; 1232 | Controller.LIGHT_COLOR_2 = 3; 1233 | Controller.DARK_COLOR_2 = 4; 1234 | Controller.RESTART = 5; 1235 | Controller.TIME_SCALE = 6; 1236 | Controller.PARTICLE_SPREAD = 7; 1237 | Controller.PARTICLE_COLOR = 8; 1238 | Controller.INVERTED_BACKGROUND = 9; 1239 | Controller.SHOW_GRID = 10; 1240 | return Controller; 1241 | }()); 1242 | exports.Controller = Controller; 1243 | 1244 | 1245 | /***/ }, 1246 | /* 4 */ 1247 | /***/ function(module, exports, __webpack_require__) { 1248 | 1249 | "use strict"; 1250 | var flameSphere_1 = __webpack_require__(5); 1251 | var flameAnimation_1 = __webpack_require__(8); 1252 | var flareParticle_1 = __webpack_require__(9); 1253 | var controller_1 = __webpack_require__(3); 1254 | var renderer_1 = __webpack_require__(1); 1255 | var ExplosionController = (function () { 1256 | function ExplosionController() { 1257 | } 1258 | ExplosionController.init = function () { 1259 | var _this = this; 1260 | this.objs = []; 1261 | this.objectPool = []; 1262 | this.spawnTime = 0; 1263 | this.flareParticle = new flareParticle_1.FlareParticle(); 1264 | this.spawnNewFlame(); 1265 | controller_1.Controller.attachEvent(controller_1.Controller.DARK_COLOR, function (value) { 1266 | for (var i = 0; i < _this.objs.length; i++) { 1267 | _this.currentCol['colDark'] = value; 1268 | _this.objs[i].instance.setColor({ colDark: value }); 1269 | } 1270 | }); 1271 | controller_1.Controller.attachEvent(controller_1.Controller.NORMAL_COLOR, function (value) { 1272 | for (var i = 0; i < _this.objs.length; i++) { 1273 | _this.currentCol['colNormal'] = value; 1274 | _this.objs[i].instance.setColor({ colNormal: value }); 1275 | } 1276 | }); 1277 | controller_1.Controller.attachEvent(controller_1.Controller.LIGHT_COLOR, function (value) { 1278 | for (var i = 0; i < _this.objs.length; i++) { 1279 | _this.currentCol['colLight'] = value; 1280 | _this.objs[i].instance.setColor({ colLight: value }); 1281 | } 1282 | }); 1283 | this.reset(); 1284 | }; 1285 | ExplosionController.reset = function () { 1286 | for (var i = 0; i < this.objs.length; i++) { 1287 | this.objs[i].reset(); 1288 | renderer_1.Renderer.removeFromScene(this.objs[i].instance.getMesh()); 1289 | } 1290 | this.objectPool = []; 1291 | this.objs = []; 1292 | this.flareParticle.reset(); 1293 | }; 1294 | ExplosionController.spawnNewFlame = function () { 1295 | var i = this.objs.length; 1296 | if (this.objectPool.length > 0) { 1297 | i = this.objectPool.shift(); 1298 | this.objs[i].instance.getMesh().visible = true; 1299 | this.objs[i].instance.setColor(this.currentCol); 1300 | this.objs[i].reset(); 1301 | } 1302 | else { 1303 | var obj = new flameAnimation_1.FlameAnimation(new flameSphere_1.FlameSphere(Math.random() * 5 + 8), Math.random() * 7 - 4, Math.random() * 7 - 4, Math.random() * 0.4 + 0.35, Math.random() * 0.4 + 0.3); 1304 | obj.instance.setColor(this.currentCol); 1305 | this.objs.push(obj); 1306 | renderer_1.Renderer.addToScene(this.objs[i].instance.getMesh()); 1307 | } 1308 | }; 1309 | ExplosionController.update = function (deltaTime) { 1310 | var timeScale = controller_1.Controller.getParams().TimeScale; 1311 | this.spawnTime += deltaTime * timeScale; 1312 | if (this.spawnTime > 200) { 1313 | while (this.spawnTime > 200) 1314 | this.spawnTime -= 200; 1315 | this.spawnNewFlame(); 1316 | } 1317 | for (var i = 0; i < this.objs.length; i++) { 1318 | if (this.objs[i].isDie()) { 1319 | if (this.objs[i].inPolling()) 1320 | continue; 1321 | this.objs[i].setInPolling(true); 1322 | this.objs[i].instance.getMesh().visible = false; 1323 | this.objectPool.push(i); 1324 | } 1325 | else { 1326 | this.objs[i].update(deltaTime); 1327 | } 1328 | } 1329 | this.flareParticle.update(deltaTime * timeScale); 1330 | }; 1331 | ExplosionController.currentCol = {}; 1332 | return ExplosionController; 1333 | }()); 1334 | exports.ExplosionController = ExplosionController; 1335 | 1336 | 1337 | /***/ }, 1338 | /* 5 */ 1339 | /***/ function(module, exports, __webpack_require__) { 1340 | 1341 | "use strict"; 1342 | var assetsManager_1 = __webpack_require__(6); 1343 | var utils_1 = __webpack_require__(7); 1344 | var FlameSphere = (function () { 1345 | function FlameSphere(radius) { 1346 | this.flowRatio = 1; 1347 | radius = radius || 20; 1348 | var glsl = assetsManager_1.AssetsManager.instance.getTexture(); 1349 | this.material = new THREE.ShaderMaterial({ 1350 | uniforms: { 1351 | time: { 1352 | type: "f", 1353 | value: 0.0 1354 | }, 1355 | seed: { 1356 | type: 'f', 1357 | value: Math.random() * 1000.0 1358 | }, 1359 | detail: { 1360 | type: 'f', 1361 | value: Math.random() * 3.5 + 5 1362 | }, 1363 | opacity: { 1364 | type: 'f', 1365 | value: 1 1366 | }, 1367 | colLight: { 1368 | value: utils_1.Utils.hexToVec3(FlameSphere.defaultColor.colLight) 1369 | }, 1370 | colNormal: { 1371 | value: utils_1.Utils.hexToVec3(FlameSphere.defaultColor.colNormal) 1372 | }, 1373 | colDark: { 1374 | value: utils_1.Utils.hexToVec3(FlameSphere.defaultColor.colDark) 1375 | } 1376 | }, 1377 | vertexShader: glsl.vertexFlameShader, 1378 | fragmentShader: glsl.fragmentFlameShader 1379 | }); 1380 | this.material.transparent = true; 1381 | this.mesh = new THREE.Mesh(new THREE.IcosahedronGeometry(radius, 3), this.material); 1382 | this.mesh.position.set(0, 0, 0); 1383 | } 1384 | FlameSphere.prototype.setColor = function (prop) { 1385 | if (prop.colDark != null) { 1386 | if (typeof prop.colDark === 'string') { 1387 | this.material.uniforms['colDark'].value = utils_1.Utils.hexToVec3(prop.colDark); 1388 | } 1389 | else { 1390 | this.material.uniforms['colDark'].value = prop.colDark; 1391 | } 1392 | } 1393 | if (prop.colNormal != null) { 1394 | if (typeof prop.colNormal === 'string') { 1395 | this.material.uniforms['colNormal'].value = utils_1.Utils.hexToVec3(prop.colNormal); 1396 | } 1397 | else { 1398 | this.material.uniforms['colNormal'].value = prop.colNormal; 1399 | } 1400 | } 1401 | if (prop.colLight != null) { 1402 | if (typeof prop.colLight === 'string') { 1403 | this.material.uniforms['colLight'].value = utils_1.Utils.hexToVec3(prop.colLight); 1404 | } 1405 | else { 1406 | this.material.uniforms['colLight'].value = prop.colLight; 1407 | } 1408 | } 1409 | }; 1410 | FlameSphere.prototype.setOpacity = function (value) { 1411 | this.material.uniforms['opacity'].value = value; 1412 | }; 1413 | FlameSphere.prototype.setDetail = function (value) { 1414 | this.material.uniforms['detail'].value = value; 1415 | }; 1416 | FlameSphere.prototype.update = function (timeDiff) { 1417 | this.material.uniforms['time'].value += .0005 * timeDiff * this.flowRatio; 1418 | }; 1419 | FlameSphere.prototype.setFlowRatio = function (val) { 1420 | this.flowRatio = val; 1421 | }; 1422 | FlameSphere.prototype.getMesh = function () { 1423 | return this.mesh; 1424 | }; 1425 | FlameSphere.defaultColor = { 1426 | colDark: '#000000', 1427 | colNormal: '#f7a90e', 1428 | colLight: '#ede92a' 1429 | }; 1430 | return FlameSphere; 1431 | }()); 1432 | exports.FlameSphere = FlameSphere; 1433 | 1434 | 1435 | /***/ }, 1436 | /* 6 */ 1437 | /***/ function(module, exports) { 1438 | 1439 | "use strict"; 1440 | var AssetsManager = (function () { 1441 | function AssetsManager() { 1442 | var _this = this; 1443 | this.vertexFlameShader = null; 1444 | this.fragmentFlameShader = null; 1445 | this.vertexParticleShader = null; 1446 | this.fragmentParticleShader = null; 1447 | $.ajax({ 1448 | url: './dist_github/shader/vertexFlameShader.glsl', 1449 | async: false, 1450 | success: function (vs) { 1451 | _this.vertexFlameShader = vs; 1452 | } 1453 | }); 1454 | $.ajax({ 1455 | url: './dist_github/shader/fragmentFlameShader.glsl', 1456 | async: false, 1457 | success: function (fs) { 1458 | _this.fragmentFlameShader = fs; 1459 | } 1460 | }); 1461 | $.ajax({ 1462 | url: './dist_github/shader/vertexParticleShader.glsl', 1463 | async: false, 1464 | success: function (fs) { 1465 | _this.vertexParticleShader = fs; 1466 | } 1467 | }); 1468 | $.ajax({ 1469 | url: './dist_github/shader/fragmentParticleShader.glsl', 1470 | async: false, 1471 | success: function (fs) { 1472 | _this.fragmentParticleShader = fs; 1473 | } 1474 | }); 1475 | } 1476 | AssetsManager.prototype.getTexture = function () { 1477 | return { 1478 | vertexFlameShader: this.vertexFlameShader, 1479 | fragmentFlameShader: this.fragmentFlameShader, 1480 | vectexParticleShader: this.vertexParticleShader, 1481 | fragmentParticleShader: this.fragmentParticleShader 1482 | }; 1483 | }; 1484 | AssetsManager.instance = new AssetsManager(); 1485 | return AssetsManager; 1486 | }()); 1487 | exports.AssetsManager = AssetsManager; 1488 | 1489 | 1490 | /***/ }, 1491 | /* 7 */ 1492 | /***/ function(module, exports) { 1493 | 1494 | "use strict"; 1495 | var Utils = (function () { 1496 | function Utils() { 1497 | } 1498 | Utils.hexToVec3 = function (col) { 1499 | var num = parseInt(col.substr(1), 16); 1500 | var r = (num / 256 / 256) % 256; 1501 | var g = (num / 256) % 256; 1502 | var b = num % 256; 1503 | return [r / 255.0, g / 255.0, b / 255.0]; 1504 | }; 1505 | Utils.formatZero = function (val) { 1506 | if (val.length == 1) 1507 | return '0' + val; 1508 | return val; 1509 | }; 1510 | Utils.vec3ToHex = function (col) { 1511 | return '#' + 1512 | this.formatZero(col[0].toString(16)) + 1513 | this.formatZero(col[1].toString(16)) + 1514 | this.formatZero(col[2].toString(16)); 1515 | }; 1516 | Utils.vec3Blend = function (cola, colb, t) { 1517 | var a = this.hexToVec3(cola); 1518 | var b = this.hexToVec3(colb); 1519 | return [ 1520 | a[0] + (b[0] - a[0]) * t, 1521 | a[1] + (b[1] - a[1]) * t, 1522 | a[2] + (b[2] - a[2]) * t 1523 | ]; 1524 | }; 1525 | return Utils; 1526 | }()); 1527 | exports.Utils = Utils; 1528 | 1529 | 1530 | /***/ }, 1531 | /* 8 */ 1532 | /***/ function(module, exports, __webpack_require__) { 1533 | 1534 | "use strict"; 1535 | var controller_1 = __webpack_require__(3); 1536 | var utils_1 = __webpack_require__(7); 1537 | var FlameAnimation = (function () { 1538 | function FlameAnimation(instance, distX, distZ, yRatio, animationTimeRatio) { 1539 | distX = distX || 0; 1540 | distZ = distZ || 0; 1541 | yRatio = yRatio || 1; 1542 | animationTimeRatio = animationTimeRatio || 1; 1543 | this.instance = instance; 1544 | this.distX = distX; 1545 | this.distZ = distZ; 1546 | this.yRatio = yRatio; 1547 | this.animationTimeRatio = animationTimeRatio; 1548 | this.reset(); 1549 | } 1550 | FlameAnimation.prototype.reset = function () { 1551 | this.randFlyX = Math.random() * 0.1 - 0.05; 1552 | this.randFlyZ = Math.random() * 0.1 - 0.05; 1553 | this.posX = -1; 1554 | this.currentTime = 0; 1555 | this.timeCount = 0; 1556 | this.spawnTime = 0; 1557 | this.isObjDie = false; 1558 | this.isInPooling = false; 1559 | this.currentState = FlameAnimation.STATE_BEFORE_START; 1560 | this.colorTransitionRandom = Math.random() * 2000 - 1000; 1561 | this.instance.getMesh().position.set(0, 0, 0); 1562 | this.instance.getMesh().scale.set(0, 0, 0); 1563 | this.instance.setFlowRatio(1); 1564 | this.instance.setOpacity(1); 1565 | }; 1566 | FlameAnimation.prototype.setColor = function () { 1567 | var params = controller_1.Controller.getParams(); 1568 | var tc = this.timeCount + this.colorTransitionRandom; 1569 | if (tc < 2500 + this.colorTransitionRandom) { 1570 | var t = tc / 2500 + this.colorTransitionRandom; 1571 | this.instance.setColor({ 1572 | colDark: params.NormalColor, 1573 | colNormal: params.LightColor, 1574 | colLight: params.LightColor2 1575 | }); 1576 | } 1577 | else if (tc < 4000) { 1578 | var t = (tc - 2500) / 1500; 1579 | this.instance.setColor({ 1580 | colDark: utils_1.Utils.vec3Blend(params.NormalColor, params.DarkColor2, t), 1581 | colNormal: utils_1.Utils.vec3Blend(params.LightColor, params.NormalColor, t), 1582 | colLight: utils_1.Utils.vec3Blend(params.LightColor2, params.LightColor, t) 1583 | }); 1584 | } 1585 | else if (tc < 7000) { 1586 | var t = (tc - 4000) / 3000; 1587 | this.instance.setColor({ 1588 | colDark: utils_1.Utils.vec3Blend(params.DarkColor2, params.DarkColor2, t), 1589 | colNormal: utils_1.Utils.vec3Blend(params.NormalColor, params.NormalColor, t), 1590 | colLight: utils_1.Utils.vec3Blend(params.LightColor, params.LightColor, t) 1591 | }); 1592 | } 1593 | else if (tc < 12000) { 1594 | var t = Math.min(1, (tc - 7000) / 5000); 1595 | this.instance.setColor({ 1596 | colDark: utils_1.Utils.vec3Blend(params.DarkColor2, params.DarkColor, t), 1597 | colNormal: utils_1.Utils.vec3Blend(params.NormalColor, params.DarkColor2, t), 1598 | colLight: utils_1.Utils.vec3Blend(params.LightColor, params.NormalColor, t) 1599 | }); 1600 | } 1601 | else if (tc < 17000) { 1602 | var t = Math.min(1, (tc - 12000) / 5000); 1603 | this.instance.setColor({ 1604 | colDark: utils_1.Utils.vec3Blend(params.DarkColor, params.DarkColor, t), 1605 | colNormal: utils_1.Utils.vec3Blend(params.DarkColor2, params.DarkColor, t), 1606 | colLight: utils_1.Utils.vec3Blend(params.NormalColor, params.DarkColor2, t) 1607 | }); 1608 | } 1609 | else { 1610 | var t = Math.min(1, (tc - 17000) / 6000); 1611 | this.instance.setColor({ 1612 | colDark: utils_1.Utils.vec3Blend(params.DarkColor, params.GreyColor, t), 1613 | colNormal: utils_1.Utils.vec3Blend(params.DarkColor, params.GreyColor, t), 1614 | colLight: utils_1.Utils.vec3Blend(params.DarkColor2, params.DarkColor, t) 1615 | }); 1616 | } 1617 | }; 1618 | FlameAnimation.prototype.updateState = function (deltaTime) { 1619 | var cTime = this.currentTime + deltaTime; 1620 | if (this.currentState == FlameAnimation.STATE_BEFORE_START) { 1621 | if (cTime > FlameAnimation.BEFORE_INTERVAL) { 1622 | cTime -= FlameAnimation.BEFORE_INTERVAL; 1623 | this.currentState = FlameAnimation.STATE_SPAWN; 1624 | } 1625 | } 1626 | else if (this.currentState == FlameAnimation.STATE_SPAWN) { 1627 | if (cTime > FlameAnimation.SPAWN_INTERVAL) { 1628 | cTime -= FlameAnimation.SPAWN_INTERVAL; 1629 | this.posX = -1; 1630 | this.currentState = FlameAnimation.STATE_SPAWN_DOWN; 1631 | } 1632 | } 1633 | else if (this.currentState == FlameAnimation.STATE_SPAWN_DOWN) { 1634 | if (cTime > FlameAnimation.SPAWN_DOWN_INTERVAL) { 1635 | cTime -= FlameAnimation.SPAWN_DOWN_INTERVAL; 1636 | this.currentState = FlameAnimation.STATE_FLOATING; 1637 | } 1638 | } 1639 | else if (this.currentState == FlameAnimation.STATE_FLOATING) { 1640 | if (cTime > FlameAnimation.FLOATING_INTERVAL) { 1641 | this.randFlyX += Math.random() * 0.2; 1642 | this.randFlyZ += Math.random() * 0.2; 1643 | cTime -= FlameAnimation.FLOATING_INTERVAL; 1644 | this.posX = -1; 1645 | this.currentState = FlameAnimation.STATE_IDLE; 1646 | } 1647 | } 1648 | else if (this.currentState == FlameAnimation.STATE_IDLE) { 1649 | if (cTime > FlameAnimation.IDLE_INTERVAL) { 1650 | this.isObjDie = true; 1651 | } 1652 | } 1653 | this.currentTime = cTime; 1654 | }; 1655 | FlameAnimation.prototype.update = function (deltaTime) { 1656 | if (this.isObjDie) 1657 | return; 1658 | var mesh = this.instance.getMesh(); 1659 | var timeScale = controller_1.Controller.getParams().TimeScale; 1660 | this.updateState(deltaTime * timeScale); 1661 | this.timeCount += deltaTime * timeScale; 1662 | if (this.currentState == FlameAnimation.STATE_SPAWN) { 1663 | var t = this.currentTime / FlameAnimation.SPAWN_INTERVAL; 1664 | var t2 = this.currentTime / (FlameAnimation.SPAWN_INTERVAL + FlameAnimation.SPAWN_DOWN_INTERVAL); 1665 | mesh.position.set(this.distX * t2, mesh.position.y + t * 0.4 * this.yRatio * timeScale, this.distZ * t2); 1666 | var scale = t; 1667 | mesh.scale.set(scale, scale, scale); 1668 | } 1669 | else if (this.currentState == FlameAnimation.STATE_SPAWN_DOWN) { 1670 | var t2 = (this.currentTime + FlameAnimation.SPAWN_INTERVAL) / 1671 | (FlameAnimation.SPAWN_INTERVAL + FlameAnimation.SPAWN_DOWN_INTERVAL); 1672 | mesh.position.set(this.distX * t2, mesh.position.y + 1673 | (0.6 * timeScale * 1674 | (1 - this.currentTime / FlameAnimation.SPAWN_DOWN_INTERVAL) + 1675 | 0.2 * timeScale) * this.yRatio, this.distZ * t2); 1676 | } 1677 | else if (this.currentState == FlameAnimation.STATE_FLOATING) { 1678 | if (this.posX == -1) { 1679 | this.posX = mesh.position.x; 1680 | this.posY = mesh.position.y; 1681 | this.posZ = mesh.position.z; 1682 | this.instance.setFlowRatio(0.5); 1683 | } 1684 | mesh.position.set(mesh.position.x + this.randFlyX * timeScale, mesh.position.y + 0.2 * timeScale, mesh.position.z + this.randFlyZ * timeScale); 1685 | var scale = mesh.scale.x + 0.003 * timeScale; 1686 | mesh.scale.set(scale, scale, scale); 1687 | } 1688 | else if (this.currentState == FlameAnimation.STATE_IDLE) { 1689 | if (this.posX == -1) { 1690 | this.posX = mesh.position.x; 1691 | this.posY = mesh.position.y; 1692 | this.posZ = mesh.position.z; 1693 | this.instance.setFlowRatio(0.2); 1694 | } 1695 | mesh.position.setY(this.posY + this.currentTime / 100); 1696 | if (this.currentTime > FlameAnimation.IDLE_INTERVAL - 5000) { 1697 | this.instance.setOpacity(1 - (this.currentTime - (FlameAnimation.IDLE_INTERVAL - 5000)) / 5000); 1698 | } 1699 | var scale = mesh.scale.x + 0.002 * timeScale; 1700 | mesh.scale.set(scale, scale, scale); 1701 | } 1702 | this.setColor(); 1703 | this.instance.update(deltaTime * timeScale * this.animationTimeRatio); 1704 | }; 1705 | FlameAnimation.prototype.isDie = function () { 1706 | return this.isObjDie; 1707 | }; 1708 | FlameAnimation.prototype.inPolling = function () { 1709 | return this.isInPooling; 1710 | }; 1711 | FlameAnimation.prototype.setInPolling = function (val) { 1712 | this.isInPooling = val; 1713 | }; 1714 | FlameAnimation.STATE_BEFORE_START = 0; 1715 | FlameAnimation.STATE_SPAWN = 1; 1716 | FlameAnimation.STATE_SPAWN_DOWN = 2; 1717 | FlameAnimation.STATE_FLOATING = 3; 1718 | FlameAnimation.STATE_IDLE = 4; 1719 | FlameAnimation.BEFORE_INTERVAL = 300; 1720 | FlameAnimation.SPAWN_INTERVAL = 400; 1721 | FlameAnimation.SPAWN_DOWN_INTERVAL = 2000; 1722 | FlameAnimation.FLOATING_INTERVAL = 8000; 1723 | FlameAnimation.IDLE_INTERVAL = 20000; 1724 | return FlameAnimation; 1725 | }()); 1726 | exports.FlameAnimation = FlameAnimation; 1727 | 1728 | 1729 | /***/ }, 1730 | /* 9 */ 1731 | /***/ function(module, exports, __webpack_require__) { 1732 | 1733 | "use strict"; 1734 | var renderer_1 = __webpack_require__(1); 1735 | var assetsManager_1 = __webpack_require__(6); 1736 | var controller_1 = __webpack_require__(3); 1737 | var constants_1 = __webpack_require__(10); 1738 | var utils_1 = __webpack_require__(7); 1739 | var FlareParticle = (function () { 1740 | function FlareParticle() { 1741 | var _this = this; 1742 | this.particlesNumber = 500; 1743 | var shaderMaterial = new THREE.ShaderMaterial({ 1744 | uniforms: { 1745 | color: { value: new THREE.Color(0xffffff) }, 1746 | texture: { value: new THREE.TextureLoader().load("./dist_github/images/circle-particle.png") } 1747 | }, 1748 | vertexShader: assetsManager_1.AssetsManager.instance.getTexture().vectexParticleShader, 1749 | fragmentShader: assetsManager_1.AssetsManager.instance.getTexture().fragmentParticleShader, 1750 | blending: THREE.NormalBlending, 1751 | depthTest: false, 1752 | transparent: true 1753 | }); 1754 | this.geometry = new THREE.BufferGeometry(); 1755 | var positions = new Float32Array(this.particlesNumber * 3); 1756 | var colors = new Float32Array(this.particlesNumber * 3); 1757 | var sizes = new Float32Array(this.particlesNumber); 1758 | this.needsUpdate = []; 1759 | this.originalSizes = new Float32Array(this.particlesNumber); 1760 | this.moveDest = new Float32Array(this.particlesNumber * 3); 1761 | this.particleTime = new Float32Array(this.particlesNumber); 1762 | this.particleColor = utils_1.Utils.hexToVec3(controller_1.Controller.getParams().ParticleColor); 1763 | for (var i = 0, i3 = 0; i < this.particlesNumber; i++, i3 += 3) { 1764 | positions[i3 + 0] = 0; 1765 | positions[i3 + 1] = 0; 1766 | positions[i3 + 2] = 0; 1767 | this.moveDest[i3] = Math.random() * 200 - 100; 1768 | this.moveDest[i3 + 1] = Math.random() * 0.3 + 0.45; 1769 | this.moveDest[i3 + 2] = Math.random() * 200 - 100; 1770 | colors[i3 + 0] = this.particleColor[0]; 1771 | colors[i3 + 1] = this.particleColor[1]; 1772 | colors[i3 + 2] = this.particleColor[2]; 1773 | sizes[i] = Math.random() * 1 + 0.5; 1774 | this.originalSizes[i] = sizes[i]; 1775 | } 1776 | this.geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3)); 1777 | this.geometry.addAttribute('customColor', new THREE.BufferAttribute(colors, 3)); 1778 | this.geometry.addAttribute('size', new THREE.BufferAttribute(sizes, 1)); 1779 | this.particleSystem = new THREE.Points(this.geometry, shaderMaterial); 1780 | renderer_1.Renderer.addToScene(this.particleSystem); 1781 | this.reset(); 1782 | FlareParticle.setController(); 1783 | controller_1.Controller.attachEvent(controller_1.Controller.PARTICLE_COLOR, function (value) { 1784 | _this.particleColor = utils_1.Utils.hexToVec3(value); 1785 | }); 1786 | } 1787 | FlareParticle.setController = function () { 1788 | var _this = this; 1789 | this.particleSpreadingRatio = controller_1.Controller.getParams().ParticleSpread; 1790 | controller_1.Controller.attachEvent(controller_1.Controller.PARTICLE_SPREAD, function (value) { 1791 | _this.particleSpreadingRatio = value; 1792 | }); 1793 | }; 1794 | FlareParticle.prototype.reset = function () { 1795 | this.time = 0; 1796 | this.spawnParticleTime = 0; 1797 | this.spawnParticleInterval = 1; 1798 | var sizes = this.geometry.attributes['size'].array; 1799 | var positions = this.geometry.attributes['position'].array; 1800 | for (var i = 0; i < this.particlesNumber; i++) { 1801 | sizes[i] = 0; 1802 | positions[i * 3] = 0; 1803 | positions[i * 3 + 1] = 0; 1804 | positions[i * 3 + 2] = 0; 1805 | this.needsUpdate[i] = false; 1806 | this.particleTime[i] = 0; 1807 | } 1808 | this.geometry.attributes['size'].needsUpdate = true; 1809 | this.geometry.attributes['position'].needsUpdate = true; 1810 | }; 1811 | FlareParticle.prototype.spawnParticle = function () { 1812 | for (var i = 0; i < this.particlesNumber; i++) { 1813 | if (this.needsUpdate[i] == false) { 1814 | this.needsUpdate[i] = true; 1815 | return; 1816 | } 1817 | } 1818 | }; 1819 | FlareParticle.prototype.update = function (deltaTime) { 1820 | this.spawnParticleTime += deltaTime; 1821 | if (this.spawnParticleTime > this.spawnParticleInterval) { 1822 | this.spawnParticleTime = 0; 1823 | this.spawnParticleInterval = Math.random() * 300 + 50; 1824 | this.spawnParticle(); 1825 | } 1826 | deltaTime /= 1000; 1827 | this.time += deltaTime; 1828 | this.particleSystem.rotation.y += 0.01 * deltaTime; 1829 | var timeScale = controller_1.Controller.getParams().TimeScale / 3; 1830 | var sizes = this.geometry.attributes['size'].array; 1831 | var positions = this.geometry.attributes['position'].array; 1832 | var colors = this.geometry.attributes['customColor'].array; 1833 | for (var i = 0, i3 = 0; i < this.particlesNumber; i++, i3 += 3) { 1834 | if (this.needsUpdate[i]) { 1835 | if (this.particleTime[i] > constants_1.Constants.MAXIMUM_LIVE_TIME / 1000) { 1836 | positions[i3] = 0; 1837 | positions[i3 + 1] = 0; 1838 | positions[i3 + 2] = 0; 1839 | this.particleTime[i] = 0; 1840 | sizes[i] = 0.01; 1841 | } 1842 | else { 1843 | var ac = FlareParticle.particleSpreadingRatio * 1844 | this.particleTime[i] / (constants_1.Constants.MAXIMUM_LIVE_TIME / 1000) + 1845 | 0.01 * Math.sin(this.time); 1846 | var randDist = (10 * Math.sin(0.3 * i + this.time + Math.random() / 10)) * timeScale; 1847 | sizes[i] = this.originalSizes[i] * (3 + Math.sin(0.4 * i + this.time)); 1848 | positions[i3] = ac * this.moveDest[i3] + randDist; 1849 | positions[i3 + 1] += (Math.random() * 0.4 + 0.9) * this.moveDest[i3 + 1] * timeScale; 1850 | positions[i3 + 2] = ac * this.moveDest[i3 + 2] + randDist; 1851 | this.particleTime[i] += deltaTime; 1852 | } 1853 | } 1854 | colors[i3] = this.particleColor[0]; 1855 | colors[i3 + 1] = this.particleColor[1]; 1856 | colors[i3 + 2] = this.particleColor[2]; 1857 | } 1858 | this.geometry.attributes['customColor'].needsUpdate = true; 1859 | this.geometry.attributes['size'].needsUpdate = true; 1860 | this.geometry.attributes['position'].needsUpdate = true; 1861 | }; 1862 | return FlareParticle; 1863 | }()); 1864 | exports.FlareParticle = FlareParticle; 1865 | 1866 | 1867 | /***/ }, 1868 | /* 10 */ 1869 | /***/ function(module, exports) { 1870 | 1871 | "use strict"; 1872 | var Constants = (function () { 1873 | function Constants() { 1874 | } 1875 | Constants.MAXIMUM_LIVE_TIME = 20000; 1876 | return Constants; 1877 | }()); 1878 | exports.Constants = Constants; 1879 | 1880 | 1881 | /***/ } 1882 | /******/ ]); -------------------------------------------------------------------------------- /dist_github/images/circle-particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neungkl/fire-simulation/52b75e8685694a9cb320be7d827ba2b1b546002f/dist_github/images/circle-particle.png -------------------------------------------------------------------------------- /dist_github/js/OrbitControl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finter swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | // 84 | // public methods 85 | // 86 | 87 | this.getPolarAngle = function () { 88 | 89 | return spherical.phi; 90 | 91 | }; 92 | 93 | this.getAzimuthalAngle = function () { 94 | 95 | return spherical.theta; 96 | 97 | }; 98 | 99 | this.reset = function () { 100 | 101 | scope.target.copy( scope.target0 ); 102 | scope.object.position.copy( scope.position0 ); 103 | scope.object.zoom = scope.zoom0; 104 | 105 | scope.object.updateProjectionMatrix(); 106 | scope.dispatchEvent( changeEvent ); 107 | 108 | scope.update(); 109 | 110 | state = STATE.NONE; 111 | 112 | }; 113 | 114 | // this method is exposed, but perhaps it would be better if we can make it private... 115 | this.update = function() { 116 | 117 | var offset = new THREE.Vector3(); 118 | 119 | // so camera.up is the orbit axis 120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 121 | var quatInverse = quat.clone().inverse(); 122 | 123 | var lastPosition = new THREE.Vector3(); 124 | var lastQuaternion = new THREE.Quaternion(); 125 | 126 | return function update () { 127 | 128 | var position = scope.object.position; 129 | 130 | offset.copy( position ).sub( scope.target ); 131 | 132 | // rotate offset to "y-axis-is-up" space 133 | offset.applyQuaternion( quat ); 134 | 135 | // angle from z-axis around y-axis 136 | spherical.setFromVector3( offset ); 137 | 138 | if ( scope.autoRotate && state === STATE.NONE ) { 139 | 140 | rotateLeft( getAutoRotationAngle() ); 141 | 142 | } 143 | 144 | spherical.theta += sphericalDelta.theta; 145 | spherical.phi += sphericalDelta.phi; 146 | 147 | // restrict theta to be between desired limits 148 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 149 | 150 | // restrict phi to be between desired limits 151 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 152 | 153 | spherical.makeSafe(); 154 | 155 | 156 | spherical.radius *= scale; 157 | 158 | // restrict radius to be between desired limits 159 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 160 | 161 | // move target to panned location 162 | scope.target.add( panOffset ); 163 | 164 | offset.setFromSpherical( spherical ); 165 | 166 | // rotate offset back to "camera-up-vector-is-up" space 167 | offset.applyQuaternion( quatInverse ); 168 | 169 | position.copy( scope.target ).add( offset ); 170 | 171 | scope.object.lookAt( scope.target ); 172 | 173 | if ( scope.enableDamping === true ) { 174 | 175 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 176 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 177 | 178 | } else { 179 | 180 | sphericalDelta.set( 0, 0, 0 ); 181 | 182 | } 183 | 184 | scale = 1; 185 | panOffset.set( 0, 0, 0 ); 186 | 187 | // update condition is: 188 | // min(camera displacement, camera rotation in radians)^2 > EPS 189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 190 | 191 | if ( zoomChanged || 192 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 194 | 195 | scope.dispatchEvent( changeEvent ); 196 | 197 | lastPosition.copy( scope.object.position ); 198 | lastQuaternion.copy( scope.object.quaternion ); 199 | zoomChanged = false; 200 | 201 | return true; 202 | 203 | } 204 | 205 | return false; 206 | 207 | }; 208 | 209 | }(); 210 | 211 | this.dispose = function() { 212 | 213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 215 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 216 | 217 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 218 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 219 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 220 | 221 | document.removeEventListener( 'mousemove', onMouseMove, false ); 222 | document.removeEventListener( 'mouseup', onMouseUp, false ); 223 | 224 | window.removeEventListener( 'keydown', onKeyDown, false ); 225 | 226 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 227 | 228 | }; 229 | 230 | // 231 | // internals 232 | // 233 | 234 | var scope = this; 235 | 236 | var changeEvent = { type: 'change' }; 237 | var startEvent = { type: 'start' }; 238 | var endEvent = { type: 'end' }; 239 | 240 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 241 | 242 | var state = STATE.NONE; 243 | 244 | var EPS = 0.000001; 245 | 246 | // current position in spherical coordinates 247 | var spherical = new THREE.Spherical(); 248 | var sphericalDelta = new THREE.Spherical(); 249 | 250 | var scale = 1; 251 | var panOffset = new THREE.Vector3(); 252 | var zoomChanged = false; 253 | 254 | var rotateStart = new THREE.Vector2(); 255 | var rotateEnd = new THREE.Vector2(); 256 | var rotateDelta = new THREE.Vector2(); 257 | 258 | var panStart = new THREE.Vector2(); 259 | var panEnd = new THREE.Vector2(); 260 | var panDelta = new THREE.Vector2(); 261 | 262 | var dollyStart = new THREE.Vector2(); 263 | var dollyEnd = new THREE.Vector2(); 264 | var dollyDelta = new THREE.Vector2(); 265 | 266 | function getAutoRotationAngle() { 267 | 268 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 269 | 270 | } 271 | 272 | function getZoomScale() { 273 | 274 | return Math.pow( 0.95, scope.zoomSpeed ); 275 | 276 | } 277 | 278 | function rotateLeft( angle ) { 279 | 280 | sphericalDelta.theta -= angle; 281 | 282 | } 283 | 284 | function rotateUp( angle ) { 285 | 286 | sphericalDelta.phi -= angle; 287 | 288 | } 289 | 290 | var panLeft = function() { 291 | 292 | var v = new THREE.Vector3(); 293 | 294 | return function panLeft( distance, objectMatrix ) { 295 | 296 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 297 | v.multiplyScalar( - distance ); 298 | 299 | panOffset.add( v ); 300 | 301 | }; 302 | 303 | }(); 304 | 305 | var panUp = function() { 306 | 307 | var v = new THREE.Vector3(); 308 | 309 | return function panUp( distance, objectMatrix ) { 310 | 311 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 312 | v.multiplyScalar( distance ); 313 | 314 | panOffset.add( v ); 315 | 316 | }; 317 | 318 | }(); 319 | 320 | // deltaX and deltaY are in pixels; right and down are positive 321 | var pan = function() { 322 | 323 | var offset = new THREE.Vector3(); 324 | 325 | return function pan ( deltaX, deltaY ) { 326 | 327 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 328 | 329 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 330 | 331 | // perspective 332 | var position = scope.object.position; 333 | offset.copy( position ).sub( scope.target ); 334 | var targetDistance = offset.length(); 335 | 336 | // half of the fov is center to top of screen 337 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 338 | 339 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 340 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 341 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 342 | 343 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 344 | 345 | // orthographic 346 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 347 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 348 | 349 | } else { 350 | 351 | // camera neither orthographic nor perspective 352 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 353 | scope.enablePan = false; 354 | 355 | } 356 | 357 | }; 358 | 359 | }(); 360 | 361 | function dollyIn( dollyScale ) { 362 | 363 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 364 | 365 | scale /= dollyScale; 366 | 367 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 368 | 369 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 370 | scope.object.updateProjectionMatrix(); 371 | zoomChanged = true; 372 | 373 | } else { 374 | 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 376 | scope.enableZoom = false; 377 | 378 | } 379 | 380 | } 381 | 382 | function dollyOut( dollyScale ) { 383 | 384 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 385 | 386 | scale *= dollyScale; 387 | 388 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 389 | 390 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 391 | scope.object.updateProjectionMatrix(); 392 | zoomChanged = true; 393 | 394 | } else { 395 | 396 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 397 | scope.enableZoom = false; 398 | 399 | } 400 | 401 | } 402 | 403 | // 404 | // event callbacks - update the object state 405 | // 406 | 407 | function handleMouseDownRotate( event ) { 408 | 409 | //console.log( 'handleMouseDownRotate' ); 410 | 411 | rotateStart.set( event.clientX, event.clientY ); 412 | 413 | } 414 | 415 | function handleMouseDownDolly( event ) { 416 | 417 | //console.log( 'handleMouseDownDolly' ); 418 | 419 | dollyStart.set( event.clientX, event.clientY ); 420 | 421 | } 422 | 423 | function handleMouseDownPan( event ) { 424 | 425 | //console.log( 'handleMouseDownPan' ); 426 | 427 | panStart.set( event.clientX, event.clientY ); 428 | 429 | } 430 | 431 | function handleMouseMoveRotate( event ) { 432 | 433 | //console.log( 'handleMouseMoveRotate' ); 434 | 435 | rotateEnd.set( event.clientX, event.clientY ); 436 | rotateDelta.subVectors( rotateEnd, rotateStart ); 437 | 438 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 439 | 440 | // rotating across whole screen goes 360 degrees around 441 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 442 | 443 | // rotating up and down along whole screen attempts to go 360, but limited to 180 444 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 445 | 446 | rotateStart.copy( rotateEnd ); 447 | 448 | scope.update(); 449 | 450 | } 451 | 452 | function handleMouseMoveDolly( event ) { 453 | 454 | //console.log( 'handleMouseMoveDolly' ); 455 | 456 | dollyEnd.set( event.clientX, event.clientY ); 457 | 458 | dollyDelta.subVectors( dollyEnd, dollyStart ); 459 | 460 | if ( dollyDelta.y > 0 ) { 461 | 462 | dollyIn( getZoomScale() ); 463 | 464 | } else if ( dollyDelta.y < 0 ) { 465 | 466 | dollyOut( getZoomScale() ); 467 | 468 | } 469 | 470 | dollyStart.copy( dollyEnd ); 471 | 472 | scope.update(); 473 | 474 | } 475 | 476 | function handleMouseMovePan( event ) { 477 | 478 | //console.log( 'handleMouseMovePan' ); 479 | 480 | panEnd.set( event.clientX, event.clientY ); 481 | 482 | panDelta.subVectors( panEnd, panStart ); 483 | 484 | pan( panDelta.x, panDelta.y ); 485 | 486 | panStart.copy( panEnd ); 487 | 488 | scope.update(); 489 | 490 | } 491 | 492 | function handleMouseUp( event ) { 493 | 494 | //console.log( 'handleMouseUp' ); 495 | 496 | } 497 | 498 | function handleMouseWheel( event ) { 499 | 500 | //console.log( 'handleMouseWheel' ); 501 | 502 | if ( event.deltaY < 0 ) { 503 | 504 | dollyOut( getZoomScale() ); 505 | 506 | } else if ( event.deltaY > 0 ) { 507 | 508 | dollyIn( getZoomScale() ); 509 | 510 | } 511 | 512 | scope.update(); 513 | 514 | } 515 | 516 | function handleKeyDown( event ) { 517 | 518 | //console.log( 'handleKeyDown' ); 519 | 520 | switch ( event.keyCode ) { 521 | 522 | case scope.keys.UP: 523 | pan( 0, scope.keyPanSpeed ); 524 | scope.update(); 525 | break; 526 | 527 | case scope.keys.BOTTOM: 528 | pan( 0, - scope.keyPanSpeed ); 529 | scope.update(); 530 | break; 531 | 532 | case scope.keys.LEFT: 533 | pan( scope.keyPanSpeed, 0 ); 534 | scope.update(); 535 | break; 536 | 537 | case scope.keys.RIGHT: 538 | pan( - scope.keyPanSpeed, 0 ); 539 | scope.update(); 540 | break; 541 | 542 | } 543 | 544 | } 545 | 546 | function handleTouchStartRotate( event ) { 547 | 548 | //console.log( 'handleTouchStartRotate' ); 549 | 550 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 551 | 552 | } 553 | 554 | function handleTouchStartDolly( event ) { 555 | 556 | //console.log( 'handleTouchStartDolly' ); 557 | 558 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 559 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 560 | 561 | var distance = Math.sqrt( dx * dx + dy * dy ); 562 | 563 | dollyStart.set( 0, distance ); 564 | 565 | } 566 | 567 | function handleTouchStartPan( event ) { 568 | 569 | //console.log( 'handleTouchStartPan' ); 570 | 571 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 572 | 573 | } 574 | 575 | function handleTouchMoveRotate( event ) { 576 | 577 | //console.log( 'handleTouchMoveRotate' ); 578 | 579 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 580 | rotateDelta.subVectors( rotateEnd, rotateStart ); 581 | 582 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 583 | 584 | // rotating across whole screen goes 360 degrees around 585 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 586 | 587 | // rotating up and down along whole screen attempts to go 360, but limited to 180 588 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 589 | 590 | rotateStart.copy( rotateEnd ); 591 | 592 | scope.update(); 593 | 594 | } 595 | 596 | function handleTouchMoveDolly( event ) { 597 | 598 | //console.log( 'handleTouchMoveDolly' ); 599 | 600 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 601 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 602 | 603 | var distance = Math.sqrt( dx * dx + dy * dy ); 604 | 605 | dollyEnd.set( 0, distance ); 606 | 607 | dollyDelta.subVectors( dollyEnd, dollyStart ); 608 | 609 | if ( dollyDelta.y > 0 ) { 610 | 611 | dollyOut( getZoomScale() ); 612 | 613 | } else if ( dollyDelta.y < 0 ) { 614 | 615 | dollyIn( getZoomScale() ); 616 | 617 | } 618 | 619 | dollyStart.copy( dollyEnd ); 620 | 621 | scope.update(); 622 | 623 | } 624 | 625 | function handleTouchMovePan( event ) { 626 | 627 | //console.log( 'handleTouchMovePan' ); 628 | 629 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 630 | 631 | panDelta.subVectors( panEnd, panStart ); 632 | 633 | pan( panDelta.x, panDelta.y ); 634 | 635 | panStart.copy( panEnd ); 636 | 637 | scope.update(); 638 | 639 | } 640 | 641 | function handleTouchEnd( event ) { 642 | 643 | //console.log( 'handleTouchEnd' ); 644 | 645 | } 646 | 647 | // 648 | // event handlers - FSM: listen for events and reset state 649 | // 650 | 651 | function onMouseDown( event ) { 652 | 653 | if ( scope.enabled === false ) return; 654 | 655 | event.preventDefault(); 656 | 657 | if ( event.button === scope.mouseButtons.ORBIT ) { 658 | 659 | if ( scope.enableRotate === false ) return; 660 | 661 | handleMouseDownRotate( event ); 662 | 663 | state = STATE.ROTATE; 664 | 665 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 666 | 667 | if ( scope.enableZoom === false ) return; 668 | 669 | handleMouseDownDolly( event ); 670 | 671 | state = STATE.DOLLY; 672 | 673 | } else if ( event.button === scope.mouseButtons.PAN ) { 674 | 675 | if ( scope.enablePan === false ) return; 676 | 677 | handleMouseDownPan( event ); 678 | 679 | state = STATE.PAN; 680 | 681 | } 682 | 683 | if ( state !== STATE.NONE ) { 684 | 685 | document.addEventListener( 'mousemove', onMouseMove, false ); 686 | document.addEventListener( 'mouseup', onMouseUp, false ); 687 | 688 | scope.dispatchEvent( startEvent ); 689 | 690 | } 691 | 692 | } 693 | 694 | function onMouseMove( event ) { 695 | 696 | if ( scope.enabled === false ) return; 697 | 698 | event.preventDefault(); 699 | 700 | if ( state === STATE.ROTATE ) { 701 | 702 | if ( scope.enableRotate === false ) return; 703 | 704 | handleMouseMoveRotate( event ); 705 | 706 | } else if ( state === STATE.DOLLY ) { 707 | 708 | if ( scope.enableZoom === false ) return; 709 | 710 | handleMouseMoveDolly( event ); 711 | 712 | } else if ( state === STATE.PAN ) { 713 | 714 | if ( scope.enablePan === false ) return; 715 | 716 | handleMouseMovePan( event ); 717 | 718 | } 719 | 720 | } 721 | 722 | function onMouseUp( event ) { 723 | 724 | if ( scope.enabled === false ) return; 725 | 726 | handleMouseUp( event ); 727 | 728 | document.removeEventListener( 'mousemove', onMouseMove, false ); 729 | document.removeEventListener( 'mouseup', onMouseUp, false ); 730 | 731 | scope.dispatchEvent( endEvent ); 732 | 733 | state = STATE.NONE; 734 | 735 | } 736 | 737 | function onMouseWheel( event ) { 738 | 739 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 740 | 741 | event.preventDefault(); 742 | event.stopPropagation(); 743 | 744 | handleMouseWheel( event ); 745 | 746 | scope.dispatchEvent( startEvent ); // not sure why these are here... 747 | scope.dispatchEvent( endEvent ); 748 | 749 | } 750 | 751 | function onKeyDown( event ) { 752 | 753 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 754 | 755 | handleKeyDown( event ); 756 | 757 | } 758 | 759 | function onTouchStart( event ) { 760 | 761 | if ( scope.enabled === false ) return; 762 | 763 | switch ( event.touches.length ) { 764 | 765 | case 1: // one-fingered touch: rotate 766 | 767 | if ( scope.enableRotate === false ) return; 768 | 769 | handleTouchStartRotate( event ); 770 | 771 | state = STATE.TOUCH_ROTATE; 772 | 773 | break; 774 | 775 | case 2: // two-fingered touch: dolly 776 | 777 | if ( scope.enableZoom === false ) return; 778 | 779 | handleTouchStartDolly( event ); 780 | 781 | state = STATE.TOUCH_DOLLY; 782 | 783 | break; 784 | 785 | case 3: // three-fingered touch: pan 786 | 787 | if ( scope.enablePan === false ) return; 788 | 789 | handleTouchStartPan( event ); 790 | 791 | state = STATE.TOUCH_PAN; 792 | 793 | break; 794 | 795 | default: 796 | 797 | state = STATE.NONE; 798 | 799 | } 800 | 801 | if ( state !== STATE.NONE ) { 802 | 803 | scope.dispatchEvent( startEvent ); 804 | 805 | } 806 | 807 | } 808 | 809 | function onTouchMove( event ) { 810 | 811 | if ( scope.enabled === false ) return; 812 | 813 | event.preventDefault(); 814 | event.stopPropagation(); 815 | 816 | switch ( event.touches.length ) { 817 | 818 | case 1: // one-fingered touch: rotate 819 | 820 | if ( scope.enableRotate === false ) return; 821 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 822 | 823 | handleTouchMoveRotate( event ); 824 | 825 | break; 826 | 827 | case 2: // two-fingered touch: dolly 828 | 829 | if ( scope.enableZoom === false ) return; 830 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 831 | 832 | handleTouchMoveDolly( event ); 833 | 834 | break; 835 | 836 | case 3: // three-fingered touch: pan 837 | 838 | if ( scope.enablePan === false ) return; 839 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 840 | 841 | handleTouchMovePan( event ); 842 | 843 | break; 844 | 845 | default: 846 | 847 | state = STATE.NONE; 848 | 849 | } 850 | 851 | } 852 | 853 | function onTouchEnd( event ) { 854 | 855 | if ( scope.enabled === false ) return; 856 | 857 | handleTouchEnd( event ); 858 | 859 | scope.dispatchEvent( endEvent ); 860 | 861 | state = STATE.NONE; 862 | 863 | } 864 | 865 | function onContextMenu( event ) { 866 | 867 | event.preventDefault(); 868 | 869 | } 870 | 871 | // 872 | 873 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 874 | 875 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 876 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 877 | 878 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 879 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 880 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 881 | 882 | window.addEventListener( 'keydown', onKeyDown, false ); 883 | 884 | // force an update at start 885 | 886 | this.update(); 887 | 888 | }; 889 | 890 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 891 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 892 | 893 | Object.defineProperties( THREE.OrbitControls.prototype, { 894 | 895 | center: { 896 | 897 | get: function () { 898 | 899 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 900 | return this.target; 901 | 902 | } 903 | 904 | }, 905 | 906 | // backward compatibility 907 | 908 | noZoom: { 909 | 910 | get: function () { 911 | 912 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 913 | return ! this.enableZoom; 914 | 915 | }, 916 | 917 | set: function ( value ) { 918 | 919 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 920 | this.enableZoom = ! value; 921 | 922 | } 923 | 924 | }, 925 | 926 | noRotate: { 927 | 928 | get: function () { 929 | 930 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 931 | return ! this.enableRotate; 932 | 933 | }, 934 | 935 | set: function ( value ) { 936 | 937 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 938 | this.enableRotate = ! value; 939 | 940 | } 941 | 942 | }, 943 | 944 | noPan: { 945 | 946 | get: function () { 947 | 948 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 949 | return ! this.enablePan; 950 | 951 | }, 952 | 953 | set: function ( value ) { 954 | 955 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 956 | this.enablePan = ! value; 957 | 958 | } 959 | 960 | }, 961 | 962 | noKeys: { 963 | 964 | get: function () { 965 | 966 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 967 | return ! this.enableKeys; 968 | 969 | }, 970 | 971 | set: function ( value ) { 972 | 973 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 974 | this.enableKeys = ! value; 975 | 976 | } 977 | 978 | }, 979 | 980 | staticMoving : { 981 | 982 | get: function () { 983 | 984 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 985 | return ! this.enableDamping; 986 | 987 | }, 988 | 989 | set: function ( value ) { 990 | 991 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 992 | this.enableDamping = ! value; 993 | 994 | } 995 | 996 | }, 997 | 998 | dynamicDampingFactor : { 999 | 1000 | get: function () { 1001 | 1002 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1003 | return this.dampingFactor; 1004 | 1005 | }, 1006 | 1007 | set: function ( value ) { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1010 | this.dampingFactor = value; 1011 | 1012 | } 1013 | 1014 | } 1015 | 1016 | } ); 1017 | -------------------------------------------------------------------------------- /dist_github/js/stats.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for Stats.js 0.16.0 2 | // Project: http://github.com/mrdoob/stats.js 3 | // Definitions by: Gregory Dalton , Harm Berntsen 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | declare class Stats { 7 | REVISION: number; 8 | domElement: HTMLDivElement; 9 | 10 | /** 11 | * @param value 0:fps, 1: ms, 2: mb, 3+: custom 12 | */ 13 | showPanel(value: number): void; 14 | begin(): void; 15 | end(): number; 16 | update(): void; 17 | } 18 | 19 | declare module "stats.js" { 20 | export = Stats; 21 | } 22 | -------------------------------------------------------------------------------- /dist_github/shader/fragmentFlameShader.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | varying float noise; 3 | uniform vec3 colLight; 4 | uniform vec3 colNormal; 5 | uniform vec3 colDark; 6 | uniform float opacity; 7 | 8 | vec3 blend( vec3 cola, vec3 colb, float percent ) { 9 | return vec3( 10 | cola.r + (colb.r - cola.r) * percent, 11 | cola.g + (colb.g - cola.g) * percent, 12 | cola.b + (colb.b - cola.b) * percent 13 | ); 14 | } 15 | 16 | void main() { 17 | 18 | vec3 col; 19 | float range = 1.0 * noise; 20 | 21 | if(range > .6) col = colDark; 22 | else if(range > .4) col = blend(colNormal, colDark, (range - .4) / .2); 23 | else col = blend(colLight, colNormal, range / .4); 24 | 25 | gl_FragColor = vec4( col, opacity ); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /dist_github/shader/fragmentParticleShader.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 color; 2 | uniform sampler2D texture; 3 | varying vec3 vColor; 4 | 5 | void main() { 6 | gl_FragColor = vec4( vColor, 1.0 ); 7 | gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord ); 8 | } -------------------------------------------------------------------------------- /dist_github/shader/vertexFlameShader.glsl: -------------------------------------------------------------------------------- 1 | // 2 | // GLSL textureless classic 3D noise "cnoise", 3 | // with an RSL-style periodic variant "pnoise". 4 | // Author: Stefan Gustavson (stefan.gustavson@liu.se) 5 | // Version: 2011-10-11 6 | // 7 | // Many thanks to Ian McEwan of Ashima Arts for the 8 | // ideas for permutation and gradient selection. 9 | // 10 | // Copyright (c) 2011 Stefan Gustavson. All rights reserved. 11 | // Distributed under the MIT license. See LICENSE file. 12 | // https://github.com/ashima/webgl-noise 13 | // 14 | 15 | vec3 mod289(vec3 x) 16 | { 17 | return x - floor(x * (1.0 / 289.0)) * 289.0; 18 | } 19 | 20 | vec4 mod289(vec4 x) 21 | { 22 | return x - floor(x * (1.0 / 289.0)) * 289.0; 23 | } 24 | 25 | vec4 permute(vec4 x) 26 | { 27 | return mod289(((x*34.0)+1.0)*x); 28 | } 29 | 30 | vec4 taylorInvSqrt(vec4 r) 31 | { 32 | return 1.79284291400159 - 0.85373472095314 * r; 33 | } 34 | 35 | vec3 fade(vec3 t) { 36 | return t*t*t*(t*(t*6.0-15.0)+10.0); 37 | } 38 | 39 | // Classic Perlin noise 40 | float cnoise(vec3 P) 41 | { 42 | vec3 Pi0 = floor(P); // Integer part for indexing 43 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 44 | Pi0 = mod289(Pi0); 45 | Pi1 = mod289(Pi1); 46 | vec3 Pf0 = fract(P); // Fractional part for interpolation 47 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 48 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 49 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 50 | vec4 iz0 = Pi0.zzzz; 51 | vec4 iz1 = Pi1.zzzz; 52 | 53 | vec4 ixy = permute(permute(ix) + iy); 54 | vec4 ixy0 = permute(ixy + iz0); 55 | vec4 ixy1 = permute(ixy + iz1); 56 | 57 | vec4 gx0 = ixy0 * (1.0 / 7.0); 58 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 59 | gx0 = fract(gx0); 60 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 61 | vec4 sz0 = step(gz0, vec4(0.0)); 62 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 63 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 64 | 65 | vec4 gx1 = ixy1 * (1.0 / 7.0); 66 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 67 | gx1 = fract(gx1); 68 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 69 | vec4 sz1 = step(gz1, vec4(0.0)); 70 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 71 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 72 | 73 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 74 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 75 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 76 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 77 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 78 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 79 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 80 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 81 | 82 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 83 | g000 *= norm0.x; 84 | g010 *= norm0.y; 85 | g100 *= norm0.z; 86 | g110 *= norm0.w; 87 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 88 | g001 *= norm1.x; 89 | g011 *= norm1.y; 90 | g101 *= norm1.z; 91 | g111 *= norm1.w; 92 | 93 | float n000 = dot(g000, Pf0); 94 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 95 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 96 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 97 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 98 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 99 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 100 | float n111 = dot(g111, Pf1); 101 | 102 | vec3 fade_xyz = fade(Pf0); 103 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 104 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 105 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 106 | return 2.2 * n_xyz; 107 | } 108 | 109 | // Classic Perlin noise, periodic variant 110 | float pnoise(vec3 P, vec3 rep) 111 | { 112 | vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period 113 | vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period 114 | Pi0 = mod289(Pi0); 115 | Pi1 = mod289(Pi1); 116 | vec3 Pf0 = fract(P); // Fractional part for interpolation 117 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 118 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 119 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 120 | vec4 iz0 = Pi0.zzzz; 121 | vec4 iz1 = Pi1.zzzz; 122 | 123 | vec4 ixy = permute(permute(ix) + iy); 124 | vec4 ixy0 = permute(ixy + iz0); 125 | vec4 ixy1 = permute(ixy + iz1); 126 | 127 | vec4 gx0 = ixy0 * (1.0 / 7.0); 128 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 129 | gx0 = fract(gx0); 130 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 131 | vec4 sz0 = step(gz0, vec4(0.0)); 132 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 133 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 134 | 135 | vec4 gx1 = ixy1 * (1.0 / 7.0); 136 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 137 | gx1 = fract(gx1); 138 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 139 | vec4 sz1 = step(gz1, vec4(0.0)); 140 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 141 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 142 | 143 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 144 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 145 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 146 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 147 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 148 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 149 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 150 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 151 | 152 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 153 | g000 *= norm0.x; 154 | g010 *= norm0.y; 155 | g100 *= norm0.z; 156 | g110 *= norm0.w; 157 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 158 | g001 *= norm1.x; 159 | g011 *= norm1.y; 160 | g101 *= norm1.z; 161 | g111 *= norm1.w; 162 | 163 | float n000 = dot(g000, Pf0); 164 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 165 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 166 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 167 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 168 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 169 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 170 | float n111 = dot(g111, Pf1); 171 | 172 | vec3 fade_xyz = fade(Pf0); 173 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 174 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 175 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 176 | return 2.2 * n_xyz; 177 | } 178 | 179 | // Include the Ashima code here! 180 | 181 | varying float noise; 182 | uniform float time; 183 | uniform float seed; 184 | uniform float detail; 185 | 186 | float turbulence( vec3 p ) { 187 | float w = 100.0; 188 | float t = -.5; 189 | for (float f = 1.0 ; f <= 10.0 ; f++ ){ 190 | float power = pow( 2.0, f ); 191 | t += abs( pnoise( vec3( power * p ), vec3( 10.0, 10.0, 10.0 ) ) / power ); 192 | } 193 | return t; 194 | } 195 | 196 | void main() { 197 | 198 | noise = detail * -.10 * turbulence( 0.6 * normal + time + seed ); 199 | float b = 2.0 * pnoise( 0.05 * position + vec3( 2.0 * time ), vec3( 100.0 ) ); 200 | float displacement = - 10. * noise + b; 201 | 202 | vec3 newPosition = position + normal * displacement; 203 | gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); 204 | 205 | } 206 | -------------------------------------------------------------------------------- /dist_github/shader/vertexParticleShader.glsl: -------------------------------------------------------------------------------- 1 | attribute float size; 2 | attribute vec3 customColor; 3 | varying vec3 vColor; 4 | 5 | void main() { 6 | vColor = customColor; 7 | vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); 8 | float cameraDist = length(mvPosition.xyz - position.xyz); 9 | gl_PointSize = size * 300.0 / cameraDist; 10 | gl_Position = projectionMatrix * mvPosition; 11 | } -------------------------------------------------------------------------------- /fire_simulation_report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neungkl/fire-simulation/52b75e8685694a9cb320be7d827ba2b1b546002f/fire_simulation_report.pdf -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Explosion Simulator 6 | 10 | 11 | 12 |
13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "explosion-simulator", 3 | "version": "1.0.0", 4 | "description": "Simulation of Explosion", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/neungkl/explosion-simulator.git" 12 | }, 13 | "author": "Kosate Limpongsa", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/neungkl/explosion-simulator/issues" 17 | }, 18 | "homepage": "https://github.com/neungkl/explosion-simulator#readme", 19 | "dependencies": { 20 | "dat-gui": "^0.5.0", 21 | "jquery": "^3.1.1", 22 | "three": "^0.82.1" 23 | }, 24 | "devDependencies": { 25 | "@types/dat-gui": "^0.6.1", 26 | "@types/es6-promise": "0.0.32", 27 | "@types/jquery": "^2.0.34", 28 | "@types/three": "0.0.27", 29 | "grunt": "^1.0.1", 30 | "grunt-contrib-copy": "^1.0.0", 31 | "grunt-contrib-watch": "^1.0.0", 32 | "grunt-ts": "^6.0.0-beta.3", 33 | "grunt-webpack": "^1.0.18", 34 | "typescript": "^2.0.10", 35 | "webpack": "^1.14.0", 36 | "webpack-dev-server": "^1.16.2" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/animation/Interpolation.ts: -------------------------------------------------------------------------------- 1 | export class Interpolation { 2 | public static easeOutCubic(percent, start, end) { 3 | let t = percent; 4 | t--; 5 | return (end - start) * (t*t*t + 1) + start; 6 | } 7 | 8 | public static easeInCubic(percent, start, end) { 9 | let t = percent; 10 | return (end - start)*t*t*t + start; 11 | }; 12 | 13 | public static easeOutSine(percent, start, end) { 14 | return (end - start) * Math.sin(percent * (Math.PI/2)) + start; 15 | }; 16 | } -------------------------------------------------------------------------------- /src/animation/explosionController.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { FlameSphere } from "../object/flameSphere"; 4 | import { FlameAnimation } from "./flameAnimation"; 5 | import { FlareParticle } from "../object/flareParticle"; 6 | import { Interpolation } from "./interpolation"; 7 | import { Controller } from "../controller"; 8 | import { Renderer } from "../renderer"; 9 | 10 | class ExplosionController { 11 | 12 | private static objs: FlameAnimation[]; 13 | private static objectPool: number[]; 14 | private static spawnTime; 15 | 16 | private static flareParticle: FlareParticle; 17 | 18 | private static currentCol = {}; 19 | 20 | public static init() { 21 | 22 | this.objs = []; 23 | this.objectPool = []; 24 | this.spawnTime = 0; 25 | this.flareParticle = new FlareParticle(); 26 | 27 | this.spawnNewFlame(); 28 | 29 | Controller.attachEvent(Controller.DARK_COLOR, (value) => { 30 | for(let i=0; i { 37 | for(let i=0; i { 44 | for(let i=0; i 0) { 68 | i = this.objectPool.shift(); 69 | this.objs[i].instance.getMesh().visible = true; 70 | this.objs[i].instance.setColor(this.currentCol); 71 | this.objs[i].reset(); 72 | } else { 73 | let obj = new FlameAnimation( 74 | new FlameSphere(Math.random() * 5 + 8), 75 | Math.random() * 7 - 4, 76 | Math.random() * 7 - 4, 77 | Math.random() * 0.4 + 0.35, 78 | Math.random() * 0.4 + 0.3 79 | ); 80 | obj.instance.setColor(this.currentCol); 81 | this.objs.push(obj); 82 | Renderer.addToScene(this.objs[i].instance.getMesh()); 83 | } 84 | } 85 | 86 | public static update(deltaTime: number) { 87 | 88 | let timeScale = Controller.getParams().TimeScale; 89 | this.spawnTime += deltaTime * timeScale; 90 | if(this.spawnTime > 200) { 91 | while(this.spawnTime > 200) this.spawnTime -= 200; 92 | this.spawnNewFlame(); 93 | } 94 | 95 | for(let i=0; i FlameAnimation.BEFORE_INTERVAL) { 136 | cTime -= FlameAnimation.BEFORE_INTERVAL; 137 | this.currentState = FlameAnimation.STATE_SPAWN; 138 | } 139 | } else if (this.currentState == FlameAnimation.STATE_SPAWN) { 140 | if (cTime > FlameAnimation.SPAWN_INTERVAL) { 141 | cTime -= FlameAnimation.SPAWN_INTERVAL; 142 | this.posX = -1; 143 | this.currentState = FlameAnimation.STATE_SPAWN_DOWN; 144 | } 145 | } else if (this.currentState == FlameAnimation.STATE_SPAWN_DOWN) { 146 | if (cTime > FlameAnimation.SPAWN_DOWN_INTERVAL) { 147 | cTime -= FlameAnimation.SPAWN_DOWN_INTERVAL; 148 | this.currentState = FlameAnimation.STATE_FLOATING; 149 | } 150 | } else if (this.currentState == FlameAnimation.STATE_FLOATING) { 151 | if (cTime > FlameAnimation.FLOATING_INTERVAL) { 152 | this.randFlyX += Math.random() * 0.2; 153 | this.randFlyZ += Math.random() * 0.2; 154 | cTime -= FlameAnimation.FLOATING_INTERVAL; 155 | this.posX = -1; 156 | this.currentState = FlameAnimation.STATE_IDLE 157 | } 158 | } else if (this.currentState == FlameAnimation.STATE_IDLE) { 159 | if (cTime > FlameAnimation.IDLE_INTERVAL) { 160 | this.isObjDie = true; 161 | } 162 | } 163 | 164 | this.currentTime = cTime; 165 | } 166 | 167 | public update(deltaTime: number) { 168 | 169 | if (this.isObjDie) return; 170 | 171 | let mesh = this.instance.getMesh(); 172 | let timeScale = Controller.getParams().TimeScale; 173 | 174 | this.updateState(deltaTime * timeScale); 175 | this.timeCount += deltaTime * timeScale; 176 | 177 | if (this.currentState == FlameAnimation.STATE_SPAWN) { 178 | 179 | let t = this.currentTime / FlameAnimation.SPAWN_INTERVAL; 180 | 181 | let t2 = this.currentTime / (FlameAnimation.SPAWN_INTERVAL + FlameAnimation.SPAWN_DOWN_INTERVAL); 182 | 183 | mesh.position.set( 184 | this.distX * t2, 185 | mesh.position.y + t * 0.4 * this.yRatio * timeScale, 186 | this.distZ * t2 187 | ); 188 | 189 | let scale = t; 190 | mesh.scale.set(scale, scale, scale); 191 | } 192 | else if (this.currentState == FlameAnimation.STATE_SPAWN_DOWN) { 193 | 194 | let t2 = (this.currentTime + FlameAnimation.SPAWN_INTERVAL) / 195 | (FlameAnimation.SPAWN_INTERVAL + FlameAnimation.SPAWN_DOWN_INTERVAL); 196 | 197 | mesh.position.set( 198 | this.distX * t2, 199 | mesh.position.y + 200 | (0.6 * timeScale * 201 | (1 - this.currentTime / FlameAnimation.SPAWN_DOWN_INTERVAL) + 202 | 0.2 * timeScale) * this.yRatio, 203 | this.distZ * t2 204 | ); 205 | } 206 | else if (this.currentState == FlameAnimation.STATE_FLOATING) { 207 | if (this.posX == -1) { 208 | this.posX = mesh.position.x; 209 | this.posY = mesh.position.y; 210 | this.posZ = mesh.position.z; 211 | this.instance.setFlowRatio(0.5); 212 | } 213 | mesh.position.set( 214 | mesh.position.x + this.randFlyX * timeScale, 215 | mesh.position.y + 0.2 * timeScale, 216 | mesh.position.z + this.randFlyZ * timeScale 217 | ); 218 | 219 | let scale = mesh.scale.x + 0.003 * timeScale; 220 | mesh.scale.set(scale, scale, scale); 221 | } 222 | else if (this.currentState == FlameAnimation.STATE_IDLE) { 223 | if (this.posX == -1) { 224 | this.posX = mesh.position.x; 225 | this.posY = mesh.position.y; 226 | this.posZ = mesh.position.z; 227 | this.instance.setFlowRatio(0.2); 228 | } 229 | mesh.position.setY(this.posY + this.currentTime / 100); 230 | 231 | if (this.currentTime > FlameAnimation.IDLE_INTERVAL - 5000) { 232 | this.instance.setOpacity(1 - (this.currentTime - (FlameAnimation.IDLE_INTERVAL - 5000)) / 5000); 233 | } 234 | 235 | let scale = mesh.scale.x + 0.002 * timeScale; 236 | mesh.scale.set(scale, scale, scale); 237 | } 238 | 239 | this.setColor(); 240 | this.instance.update(deltaTime * timeScale * this.animationTimeRatio); 241 | } 242 | 243 | public isDie(): boolean { 244 | return this.isObjDie; 245 | } 246 | 247 | public inPolling(): boolean { 248 | return this.isInPooling; 249 | } 250 | public setInPolling(val: boolean): void { 251 | this.isInPooling = val; 252 | } 253 | } 254 | 255 | export { FlameAnimation } -------------------------------------------------------------------------------- /src/assetsManager.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class AssetsManager { 4 | 5 | public static instance: AssetsManager = new AssetsManager(); 6 | 7 | private vertexFlameShader = null; 8 | private fragmentFlameShader = null; 9 | private vertexParticleShader = null; 10 | private fragmentParticleShader = null; 11 | 12 | constructor() { 13 | $.ajax({ 14 | url: './dist/shader/vertexFlameShader.glsl', 15 | async: false, 16 | success: (vs) => { 17 | this.vertexFlameShader = vs; 18 | } 19 | }); 20 | 21 | $.ajax({ 22 | url: './dist/shader/fragmentFlameShader.glsl', 23 | async: false, 24 | success: (fs) => { 25 | this.fragmentFlameShader = fs; 26 | } 27 | }); 28 | 29 | $.ajax({ 30 | url: './dist/shader/vertexParticleShader.glsl', 31 | async: false, 32 | success: (fs) => { 33 | this.vertexParticleShader = fs; 34 | } 35 | }); 36 | 37 | $.ajax({ 38 | url: './dist/shader/fragmentParticleShader.glsl', 39 | async: false, 40 | success: (fs) => { 41 | this.fragmentParticleShader = fs; 42 | } 43 | }); 44 | } 45 | 46 | public getTexture() { 47 | return { 48 | vertexFlameShader: this.vertexFlameShader, 49 | fragmentFlameShader: this.fragmentFlameShader, 50 | vectexParticleShader: this.vertexParticleShader, 51 | fragmentParticleShader: this.fragmentParticleShader 52 | }; 53 | } 54 | } 55 | 56 | export { AssetsManager }; 57 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | class Constants { 2 | public static MAXIMUM_LIVE_TIME = 20000; 3 | } 4 | 5 | export { Constants } -------------------------------------------------------------------------------- /src/controller.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Controller { 4 | 5 | public static DARK_COLOR: number = 0; 6 | public static NORMAL_COLOR: number = 1; 7 | public static LIGHT_COLOR: number = 2; 8 | public static LIGHT_COLOR_2: number = 3; 9 | public static DARK_COLOR_2: number = 4; 10 | public static RESTART: number = 5; 11 | public static TIME_SCALE: number = 6; 12 | public static PARTICLE_SPREAD: number = 7; 13 | public static PARTICLE_COLOR: number = 8; 14 | public static INVERTED_BACKGROUND: number = 9; 15 | public static SHOW_GRID: number = 10; 16 | 17 | private static gui; 18 | private static eventListener; 19 | 20 | private static params; 21 | 22 | public static init() { 23 | 24 | this.eventListener = []; 25 | 26 | let ControlParam = function() { 27 | 28 | this.LightColor2 = '#ff8700'; 29 | this.LightColor = '#f7f342'; 30 | this.NormalColor = '#f7a90e'; 31 | this.DarkColor2 = '#ff9800'; 32 | this.GreyColor = '#3c342f'; 33 | this.DarkColor = "#181818"; 34 | 35 | this.TimeScale = 3; 36 | 37 | this.ParticleSpread = 1; 38 | this.ParticleColor = '#ffb400'; 39 | 40 | this.InvertedBackground = false; 41 | this.ShowGrid = true; 42 | 43 | this.restart = function() { } 44 | }; 45 | 46 | let params = new ControlParam(); 47 | var gui = new dat.GUI(); 48 | 49 | var f1 = gui.addFolder('Spawn Color'); 50 | this.eventListener[Controller.DARK_COLOR] = f1.addColor(params, 'DarkColor'); 51 | this.eventListener[Controller.DARK_COLOR_2] = f1.addColor(params, 'GreyColor'); 52 | this.eventListener[Controller.DARK_COLOR_2] = f1.addColor(params, 'DarkColor2'); 53 | this.eventListener[Controller.NORMAL_COLOR] = f1.addColor(params, 'NormalColor'); 54 | this.eventListener[Controller.LIGHT_COLOR] = f1.addColor(params, 'LightColor'); 55 | this.eventListener[Controller.LIGHT_COLOR_2] = f1.addColor(params, 'LightColor2'); 56 | f1.open(); 57 | 58 | var f2 = gui.addFolder('Flare Particle') 59 | this.eventListener[Controller.PARTICLE_SPREAD] = f2.add(params, 'ParticleSpread', 0, 2); 60 | this.eventListener[Controller.PARTICLE_COLOR] = f2.addColor(params, 'ParticleColor'); 61 | f2.open(); 62 | 63 | this.eventListener[Controller.INVERTED_BACKGROUND] = gui.add(params, 'InvertedBackground'); 64 | this.eventListener[Controller.SHOW_GRID] = gui.add(params, 'ShowGrid'); 65 | this.eventListener[Controller.TIME_SCALE] = gui.add(params, 'TimeScale', 0, 10); 66 | 67 | gui.add(params, 'restart'); 68 | 69 | this.gui = gui; 70 | this.params = params; 71 | } 72 | 73 | public static getParams() { 74 | return this.params; 75 | } 76 | 77 | public static setRestartFunc(func: Function) { 78 | this.params.restart = func; 79 | } 80 | 81 | public static attachEvent(key, callback) { 82 | this.eventListener[key].onChange(callback); 83 | } 84 | } 85 | 86 | export { Controller } -------------------------------------------------------------------------------- /src/images/circle-particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neungkl/fire-simulation/52b75e8685694a9cb320be7d827ba2b1b546002f/src/images/circle-particle.png -------------------------------------------------------------------------------- /src/images/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neungkl/fire-simulation/52b75e8685694a9cb320be7d827ba2b1b546002f/src/images/spark.png -------------------------------------------------------------------------------- /src/js/OrbitControl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finter swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | // 84 | // public methods 85 | // 86 | 87 | this.getPolarAngle = function () { 88 | 89 | return spherical.phi; 90 | 91 | }; 92 | 93 | this.getAzimuthalAngle = function () { 94 | 95 | return spherical.theta; 96 | 97 | }; 98 | 99 | this.reset = function () { 100 | 101 | scope.target.copy( scope.target0 ); 102 | scope.object.position.copy( scope.position0 ); 103 | scope.object.zoom = scope.zoom0; 104 | 105 | scope.object.updateProjectionMatrix(); 106 | scope.dispatchEvent( changeEvent ); 107 | 108 | scope.update(); 109 | 110 | state = STATE.NONE; 111 | 112 | }; 113 | 114 | // this method is exposed, but perhaps it would be better if we can make it private... 115 | this.update = function() { 116 | 117 | var offset = new THREE.Vector3(); 118 | 119 | // so camera.up is the orbit axis 120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 121 | var quatInverse = quat.clone().inverse(); 122 | 123 | var lastPosition = new THREE.Vector3(); 124 | var lastQuaternion = new THREE.Quaternion(); 125 | 126 | return function update () { 127 | 128 | var position = scope.object.position; 129 | 130 | offset.copy( position ).sub( scope.target ); 131 | 132 | // rotate offset to "y-axis-is-up" space 133 | offset.applyQuaternion( quat ); 134 | 135 | // angle from z-axis around y-axis 136 | spherical.setFromVector3( offset ); 137 | 138 | if ( scope.autoRotate && state === STATE.NONE ) { 139 | 140 | rotateLeft( getAutoRotationAngle() ); 141 | 142 | } 143 | 144 | spherical.theta += sphericalDelta.theta; 145 | spherical.phi += sphericalDelta.phi; 146 | 147 | // restrict theta to be between desired limits 148 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 149 | 150 | // restrict phi to be between desired limits 151 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 152 | 153 | spherical.makeSafe(); 154 | 155 | 156 | spherical.radius *= scale; 157 | 158 | // restrict radius to be between desired limits 159 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 160 | 161 | // move target to panned location 162 | scope.target.add( panOffset ); 163 | 164 | offset.setFromSpherical( spherical ); 165 | 166 | // rotate offset back to "camera-up-vector-is-up" space 167 | offset.applyQuaternion( quatInverse ); 168 | 169 | position.copy( scope.target ).add( offset ); 170 | 171 | scope.object.lookAt( scope.target ); 172 | 173 | if ( scope.enableDamping === true ) { 174 | 175 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 176 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 177 | 178 | } else { 179 | 180 | sphericalDelta.set( 0, 0, 0 ); 181 | 182 | } 183 | 184 | scale = 1; 185 | panOffset.set( 0, 0, 0 ); 186 | 187 | // update condition is: 188 | // min(camera displacement, camera rotation in radians)^2 > EPS 189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 190 | 191 | if ( zoomChanged || 192 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 194 | 195 | scope.dispatchEvent( changeEvent ); 196 | 197 | lastPosition.copy( scope.object.position ); 198 | lastQuaternion.copy( scope.object.quaternion ); 199 | zoomChanged = false; 200 | 201 | return true; 202 | 203 | } 204 | 205 | return false; 206 | 207 | }; 208 | 209 | }(); 210 | 211 | this.dispose = function() { 212 | 213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 215 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 216 | 217 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 218 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 219 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 220 | 221 | document.removeEventListener( 'mousemove', onMouseMove, false ); 222 | document.removeEventListener( 'mouseup', onMouseUp, false ); 223 | 224 | window.removeEventListener( 'keydown', onKeyDown, false ); 225 | 226 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 227 | 228 | }; 229 | 230 | // 231 | // internals 232 | // 233 | 234 | var scope = this; 235 | 236 | var changeEvent = { type: 'change' }; 237 | var startEvent = { type: 'start' }; 238 | var endEvent = { type: 'end' }; 239 | 240 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 241 | 242 | var state = STATE.NONE; 243 | 244 | var EPS = 0.000001; 245 | 246 | // current position in spherical coordinates 247 | var spherical = new THREE.Spherical(); 248 | var sphericalDelta = new THREE.Spherical(); 249 | 250 | var scale = 1; 251 | var panOffset = new THREE.Vector3(); 252 | var zoomChanged = false; 253 | 254 | var rotateStart = new THREE.Vector2(); 255 | var rotateEnd = new THREE.Vector2(); 256 | var rotateDelta = new THREE.Vector2(); 257 | 258 | var panStart = new THREE.Vector2(); 259 | var panEnd = new THREE.Vector2(); 260 | var panDelta = new THREE.Vector2(); 261 | 262 | var dollyStart = new THREE.Vector2(); 263 | var dollyEnd = new THREE.Vector2(); 264 | var dollyDelta = new THREE.Vector2(); 265 | 266 | function getAutoRotationAngle() { 267 | 268 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 269 | 270 | } 271 | 272 | function getZoomScale() { 273 | 274 | return Math.pow( 0.95, scope.zoomSpeed ); 275 | 276 | } 277 | 278 | function rotateLeft( angle ) { 279 | 280 | sphericalDelta.theta -= angle; 281 | 282 | } 283 | 284 | function rotateUp( angle ) { 285 | 286 | sphericalDelta.phi -= angle; 287 | 288 | } 289 | 290 | var panLeft = function() { 291 | 292 | var v = new THREE.Vector3(); 293 | 294 | return function panLeft( distance, objectMatrix ) { 295 | 296 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 297 | v.multiplyScalar( - distance ); 298 | 299 | panOffset.add( v ); 300 | 301 | }; 302 | 303 | }(); 304 | 305 | var panUp = function() { 306 | 307 | var v = new THREE.Vector3(); 308 | 309 | return function panUp( distance, objectMatrix ) { 310 | 311 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 312 | v.multiplyScalar( distance ); 313 | 314 | panOffset.add( v ); 315 | 316 | }; 317 | 318 | }(); 319 | 320 | // deltaX and deltaY are in pixels; right and down are positive 321 | var pan = function() { 322 | 323 | var offset = new THREE.Vector3(); 324 | 325 | return function pan ( deltaX, deltaY ) { 326 | 327 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 328 | 329 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 330 | 331 | // perspective 332 | var position = scope.object.position; 333 | offset.copy( position ).sub( scope.target ); 334 | var targetDistance = offset.length(); 335 | 336 | // half of the fov is center to top of screen 337 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 338 | 339 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 340 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 341 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 342 | 343 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 344 | 345 | // orthographic 346 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 347 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 348 | 349 | } else { 350 | 351 | // camera neither orthographic nor perspective 352 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 353 | scope.enablePan = false; 354 | 355 | } 356 | 357 | }; 358 | 359 | }(); 360 | 361 | function dollyIn( dollyScale ) { 362 | 363 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 364 | 365 | scale /= dollyScale; 366 | 367 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 368 | 369 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 370 | scope.object.updateProjectionMatrix(); 371 | zoomChanged = true; 372 | 373 | } else { 374 | 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 376 | scope.enableZoom = false; 377 | 378 | } 379 | 380 | } 381 | 382 | function dollyOut( dollyScale ) { 383 | 384 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 385 | 386 | scale *= dollyScale; 387 | 388 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 389 | 390 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 391 | scope.object.updateProjectionMatrix(); 392 | zoomChanged = true; 393 | 394 | } else { 395 | 396 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 397 | scope.enableZoom = false; 398 | 399 | } 400 | 401 | } 402 | 403 | // 404 | // event callbacks - update the object state 405 | // 406 | 407 | function handleMouseDownRotate( event ) { 408 | 409 | //console.log( 'handleMouseDownRotate' ); 410 | 411 | rotateStart.set( event.clientX, event.clientY ); 412 | 413 | } 414 | 415 | function handleMouseDownDolly( event ) { 416 | 417 | //console.log( 'handleMouseDownDolly' ); 418 | 419 | dollyStart.set( event.clientX, event.clientY ); 420 | 421 | } 422 | 423 | function handleMouseDownPan( event ) { 424 | 425 | //console.log( 'handleMouseDownPan' ); 426 | 427 | panStart.set( event.clientX, event.clientY ); 428 | 429 | } 430 | 431 | function handleMouseMoveRotate( event ) { 432 | 433 | //console.log( 'handleMouseMoveRotate' ); 434 | 435 | rotateEnd.set( event.clientX, event.clientY ); 436 | rotateDelta.subVectors( rotateEnd, rotateStart ); 437 | 438 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 439 | 440 | // rotating across whole screen goes 360 degrees around 441 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 442 | 443 | // rotating up and down along whole screen attempts to go 360, but limited to 180 444 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 445 | 446 | rotateStart.copy( rotateEnd ); 447 | 448 | scope.update(); 449 | 450 | } 451 | 452 | function handleMouseMoveDolly( event ) { 453 | 454 | //console.log( 'handleMouseMoveDolly' ); 455 | 456 | dollyEnd.set( event.clientX, event.clientY ); 457 | 458 | dollyDelta.subVectors( dollyEnd, dollyStart ); 459 | 460 | if ( dollyDelta.y > 0 ) { 461 | 462 | dollyIn( getZoomScale() ); 463 | 464 | } else if ( dollyDelta.y < 0 ) { 465 | 466 | dollyOut( getZoomScale() ); 467 | 468 | } 469 | 470 | dollyStart.copy( dollyEnd ); 471 | 472 | scope.update(); 473 | 474 | } 475 | 476 | function handleMouseMovePan( event ) { 477 | 478 | //console.log( 'handleMouseMovePan' ); 479 | 480 | panEnd.set( event.clientX, event.clientY ); 481 | 482 | panDelta.subVectors( panEnd, panStart ); 483 | 484 | pan( panDelta.x, panDelta.y ); 485 | 486 | panStart.copy( panEnd ); 487 | 488 | scope.update(); 489 | 490 | } 491 | 492 | function handleMouseUp( event ) { 493 | 494 | //console.log( 'handleMouseUp' ); 495 | 496 | } 497 | 498 | function handleMouseWheel( event ) { 499 | 500 | //console.log( 'handleMouseWheel' ); 501 | 502 | if ( event.deltaY < 0 ) { 503 | 504 | dollyOut( getZoomScale() ); 505 | 506 | } else if ( event.deltaY > 0 ) { 507 | 508 | dollyIn( getZoomScale() ); 509 | 510 | } 511 | 512 | scope.update(); 513 | 514 | } 515 | 516 | function handleKeyDown( event ) { 517 | 518 | //console.log( 'handleKeyDown' ); 519 | 520 | switch ( event.keyCode ) { 521 | 522 | case scope.keys.UP: 523 | pan( 0, scope.keyPanSpeed ); 524 | scope.update(); 525 | break; 526 | 527 | case scope.keys.BOTTOM: 528 | pan( 0, - scope.keyPanSpeed ); 529 | scope.update(); 530 | break; 531 | 532 | case scope.keys.LEFT: 533 | pan( scope.keyPanSpeed, 0 ); 534 | scope.update(); 535 | break; 536 | 537 | case scope.keys.RIGHT: 538 | pan( - scope.keyPanSpeed, 0 ); 539 | scope.update(); 540 | break; 541 | 542 | } 543 | 544 | } 545 | 546 | function handleTouchStartRotate( event ) { 547 | 548 | //console.log( 'handleTouchStartRotate' ); 549 | 550 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 551 | 552 | } 553 | 554 | function handleTouchStartDolly( event ) { 555 | 556 | //console.log( 'handleTouchStartDolly' ); 557 | 558 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 559 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 560 | 561 | var distance = Math.sqrt( dx * dx + dy * dy ); 562 | 563 | dollyStart.set( 0, distance ); 564 | 565 | } 566 | 567 | function handleTouchStartPan( event ) { 568 | 569 | //console.log( 'handleTouchStartPan' ); 570 | 571 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 572 | 573 | } 574 | 575 | function handleTouchMoveRotate( event ) { 576 | 577 | //console.log( 'handleTouchMoveRotate' ); 578 | 579 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 580 | rotateDelta.subVectors( rotateEnd, rotateStart ); 581 | 582 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 583 | 584 | // rotating across whole screen goes 360 degrees around 585 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 586 | 587 | // rotating up and down along whole screen attempts to go 360, but limited to 180 588 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 589 | 590 | rotateStart.copy( rotateEnd ); 591 | 592 | scope.update(); 593 | 594 | } 595 | 596 | function handleTouchMoveDolly( event ) { 597 | 598 | //console.log( 'handleTouchMoveDolly' ); 599 | 600 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 601 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 602 | 603 | var distance = Math.sqrt( dx * dx + dy * dy ); 604 | 605 | dollyEnd.set( 0, distance ); 606 | 607 | dollyDelta.subVectors( dollyEnd, dollyStart ); 608 | 609 | if ( dollyDelta.y > 0 ) { 610 | 611 | dollyOut( getZoomScale() ); 612 | 613 | } else if ( dollyDelta.y < 0 ) { 614 | 615 | dollyIn( getZoomScale() ); 616 | 617 | } 618 | 619 | dollyStart.copy( dollyEnd ); 620 | 621 | scope.update(); 622 | 623 | } 624 | 625 | function handleTouchMovePan( event ) { 626 | 627 | //console.log( 'handleTouchMovePan' ); 628 | 629 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 630 | 631 | panDelta.subVectors( panEnd, panStart ); 632 | 633 | pan( panDelta.x, panDelta.y ); 634 | 635 | panStart.copy( panEnd ); 636 | 637 | scope.update(); 638 | 639 | } 640 | 641 | function handleTouchEnd( event ) { 642 | 643 | //console.log( 'handleTouchEnd' ); 644 | 645 | } 646 | 647 | // 648 | // event handlers - FSM: listen for events and reset state 649 | // 650 | 651 | function onMouseDown( event ) { 652 | 653 | if ( scope.enabled === false ) return; 654 | 655 | event.preventDefault(); 656 | 657 | if ( event.button === scope.mouseButtons.ORBIT ) { 658 | 659 | if ( scope.enableRotate === false ) return; 660 | 661 | handleMouseDownRotate( event ); 662 | 663 | state = STATE.ROTATE; 664 | 665 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 666 | 667 | if ( scope.enableZoom === false ) return; 668 | 669 | handleMouseDownDolly( event ); 670 | 671 | state = STATE.DOLLY; 672 | 673 | } else if ( event.button === scope.mouseButtons.PAN ) { 674 | 675 | if ( scope.enablePan === false ) return; 676 | 677 | handleMouseDownPan( event ); 678 | 679 | state = STATE.PAN; 680 | 681 | } 682 | 683 | if ( state !== STATE.NONE ) { 684 | 685 | document.addEventListener( 'mousemove', onMouseMove, false ); 686 | document.addEventListener( 'mouseup', onMouseUp, false ); 687 | 688 | scope.dispatchEvent( startEvent ); 689 | 690 | } 691 | 692 | } 693 | 694 | function onMouseMove( event ) { 695 | 696 | if ( scope.enabled === false ) return; 697 | 698 | event.preventDefault(); 699 | 700 | if ( state === STATE.ROTATE ) { 701 | 702 | if ( scope.enableRotate === false ) return; 703 | 704 | handleMouseMoveRotate( event ); 705 | 706 | } else if ( state === STATE.DOLLY ) { 707 | 708 | if ( scope.enableZoom === false ) return; 709 | 710 | handleMouseMoveDolly( event ); 711 | 712 | } else if ( state === STATE.PAN ) { 713 | 714 | if ( scope.enablePan === false ) return; 715 | 716 | handleMouseMovePan( event ); 717 | 718 | } 719 | 720 | } 721 | 722 | function onMouseUp( event ) { 723 | 724 | if ( scope.enabled === false ) return; 725 | 726 | handleMouseUp( event ); 727 | 728 | document.removeEventListener( 'mousemove', onMouseMove, false ); 729 | document.removeEventListener( 'mouseup', onMouseUp, false ); 730 | 731 | scope.dispatchEvent( endEvent ); 732 | 733 | state = STATE.NONE; 734 | 735 | } 736 | 737 | function onMouseWheel( event ) { 738 | 739 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 740 | 741 | event.preventDefault(); 742 | event.stopPropagation(); 743 | 744 | handleMouseWheel( event ); 745 | 746 | scope.dispatchEvent( startEvent ); // not sure why these are here... 747 | scope.dispatchEvent( endEvent ); 748 | 749 | } 750 | 751 | function onKeyDown( event ) { 752 | 753 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 754 | 755 | handleKeyDown( event ); 756 | 757 | } 758 | 759 | function onTouchStart( event ) { 760 | 761 | if ( scope.enabled === false ) return; 762 | 763 | switch ( event.touches.length ) { 764 | 765 | case 1: // one-fingered touch: rotate 766 | 767 | if ( scope.enableRotate === false ) return; 768 | 769 | handleTouchStartRotate( event ); 770 | 771 | state = STATE.TOUCH_ROTATE; 772 | 773 | break; 774 | 775 | case 2: // two-fingered touch: dolly 776 | 777 | if ( scope.enableZoom === false ) return; 778 | 779 | handleTouchStartDolly( event ); 780 | 781 | state = STATE.TOUCH_DOLLY; 782 | 783 | break; 784 | 785 | case 3: // three-fingered touch: pan 786 | 787 | if ( scope.enablePan === false ) return; 788 | 789 | handleTouchStartPan( event ); 790 | 791 | state = STATE.TOUCH_PAN; 792 | 793 | break; 794 | 795 | default: 796 | 797 | state = STATE.NONE; 798 | 799 | } 800 | 801 | if ( state !== STATE.NONE ) { 802 | 803 | scope.dispatchEvent( startEvent ); 804 | 805 | } 806 | 807 | } 808 | 809 | function onTouchMove( event ) { 810 | 811 | if ( scope.enabled === false ) return; 812 | 813 | event.preventDefault(); 814 | event.stopPropagation(); 815 | 816 | switch ( event.touches.length ) { 817 | 818 | case 1: // one-fingered touch: rotate 819 | 820 | if ( scope.enableRotate === false ) return; 821 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 822 | 823 | handleTouchMoveRotate( event ); 824 | 825 | break; 826 | 827 | case 2: // two-fingered touch: dolly 828 | 829 | if ( scope.enableZoom === false ) return; 830 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 831 | 832 | handleTouchMoveDolly( event ); 833 | 834 | break; 835 | 836 | case 3: // three-fingered touch: pan 837 | 838 | if ( scope.enablePan === false ) return; 839 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 840 | 841 | handleTouchMovePan( event ); 842 | 843 | break; 844 | 845 | default: 846 | 847 | state = STATE.NONE; 848 | 849 | } 850 | 851 | } 852 | 853 | function onTouchEnd( event ) { 854 | 855 | if ( scope.enabled === false ) return; 856 | 857 | handleTouchEnd( event ); 858 | 859 | scope.dispatchEvent( endEvent ); 860 | 861 | state = STATE.NONE; 862 | 863 | } 864 | 865 | function onContextMenu( event ) { 866 | 867 | event.preventDefault(); 868 | 869 | } 870 | 871 | // 872 | 873 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 874 | 875 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 876 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 877 | 878 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 879 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 880 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 881 | 882 | window.addEventListener( 'keydown', onKeyDown, false ); 883 | 884 | // force an update at start 885 | 886 | this.update(); 887 | 888 | }; 889 | 890 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 891 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 892 | 893 | Object.defineProperties( THREE.OrbitControls.prototype, { 894 | 895 | center: { 896 | 897 | get: function () { 898 | 899 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 900 | return this.target; 901 | 902 | } 903 | 904 | }, 905 | 906 | // backward compatibility 907 | 908 | noZoom: { 909 | 910 | get: function () { 911 | 912 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 913 | return ! this.enableZoom; 914 | 915 | }, 916 | 917 | set: function ( value ) { 918 | 919 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 920 | this.enableZoom = ! value; 921 | 922 | } 923 | 924 | }, 925 | 926 | noRotate: { 927 | 928 | get: function () { 929 | 930 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 931 | return ! this.enableRotate; 932 | 933 | }, 934 | 935 | set: function ( value ) { 936 | 937 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 938 | this.enableRotate = ! value; 939 | 940 | } 941 | 942 | }, 943 | 944 | noPan: { 945 | 946 | get: function () { 947 | 948 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 949 | return ! this.enablePan; 950 | 951 | }, 952 | 953 | set: function ( value ) { 954 | 955 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 956 | this.enablePan = ! value; 957 | 958 | } 959 | 960 | }, 961 | 962 | noKeys: { 963 | 964 | get: function () { 965 | 966 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 967 | return ! this.enableKeys; 968 | 969 | }, 970 | 971 | set: function ( value ) { 972 | 973 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 974 | this.enableKeys = ! value; 975 | 976 | } 977 | 978 | }, 979 | 980 | staticMoving : { 981 | 982 | get: function () { 983 | 984 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 985 | return ! this.enableDamping; 986 | 987 | }, 988 | 989 | set: function ( value ) { 990 | 991 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 992 | this.enableDamping = ! value; 993 | 994 | } 995 | 996 | }, 997 | 998 | dynamicDampingFactor : { 999 | 1000 | get: function () { 1001 | 1002 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1003 | return this.dampingFactor; 1004 | 1005 | }, 1006 | 1007 | set: function ( value ) { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1010 | this.dampingFactor = value; 1011 | 1012 | } 1013 | 1014 | } 1015 | 1016 | } ); 1017 | -------------------------------------------------------------------------------- /src/js/stats.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for Stats.js 0.16.0 2 | // Project: http://github.com/mrdoob/stats.js 3 | // Definitions by: Gregory Dalton , Harm Berntsen 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | declare class Stats { 7 | REVISION: number; 8 | domElement: HTMLDivElement; 9 | 10 | /** 11 | * @param value 0:fps, 1: ms, 2: mb, 3+: custom 12 | */ 13 | showPanel(value: number): void; 14 | begin(): void; 15 | end(): number; 16 | update(): void; 17 | } 18 | 19 | declare module "stats.js" { 20 | export = Stats; 21 | } 22 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | "use strict"; 4 | 5 | import { Renderer } from "./renderer"; 6 | import { AssetsManager } from "./assetsManager"; 7 | import { FlameSphere } from "./object/flameSphere"; 8 | import { Controller } from "./controller"; 9 | import { ExplosionController } from "./animation/explosionController"; 10 | 11 | window.onload = () => { 12 | 13 | var time = Date.now(); 14 | var timeScale; 15 | 16 | var stats = new Stats(); 17 | stats.showPanel(0); 18 | 19 | document.getElementById("stats").appendChild(stats.domElement); 20 | 21 | // Initialize 22 | Controller.init(); 23 | Renderer.init(); 24 | ExplosionController.init(); 25 | 26 | timeScale = Controller.getParams().TimeScale; 27 | 28 | const onRequestAnimationFrame = () => { 29 | requestAnimationFrame(onRequestAnimationFrame); 30 | stats.begin(); 31 | Renderer.animate(); 32 | stats.end(); 33 | } 34 | 35 | let deltaTimeMaximum = 1000 / 65; 36 | 37 | Renderer.setUpdateFunc(() => { 38 | let timeDiff = (Date.now() - time); 39 | ExplosionController.update(timeDiff > deltaTimeMaximum ? deltaTimeMaximum : timeDiff); 40 | time = Date.now(); 41 | }); 42 | Controller.setRestartFunc(() => { 43 | ExplosionController.reset(); 44 | }); 45 | 46 | requestAnimationFrame(onRequestAnimationFrame); 47 | 48 | window.addEventListener('resize', () => { Renderer.onWindowResize() }, false); 49 | }; 50 | -------------------------------------------------------------------------------- /src/object/flameSphere.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import { AssetsManager } from "../assetsManager"; 4 | import { Controller } from "../controller"; 5 | import { Utils } from "../utils"; 6 | 7 | class FlameSphere { 8 | 9 | private mesh: THREE.Mesh; 10 | private flowRatio: number = 1; 11 | private material; 12 | 13 | private static defaultColor = { 14 | colDark: '#000000', 15 | colNormal: '#f7a90e', 16 | colLight: '#ede92a' 17 | }; 18 | 19 | constructor(radius?: number) { 20 | 21 | radius = radius || 20; 22 | 23 | let glsl = AssetsManager.instance.getTexture(); 24 | 25 | this.material = new THREE.ShaderMaterial({ 26 | uniforms: { 27 | time: { 28 | type: "f", 29 | value: 0.0 30 | }, 31 | seed: { 32 | type: 'f', 33 | value: Math.random() * 1000.0 34 | }, 35 | detail: { 36 | type: 'f', 37 | value: Math.random() * 3.5 + 5 38 | }, 39 | opacity: { 40 | type: 'f', 41 | value: 1 42 | }, 43 | colLight: { 44 | value: Utils.hexToVec3(FlameSphere.defaultColor.colLight) 45 | }, 46 | colNormal: { 47 | value: Utils.hexToVec3(FlameSphere.defaultColor.colNormal) 48 | }, 49 | colDark: { 50 | value: Utils.hexToVec3(FlameSphere.defaultColor.colDark) 51 | } 52 | }, 53 | vertexShader: glsl.vertexFlameShader, 54 | fragmentShader: glsl.fragmentFlameShader 55 | }); 56 | this.material.transparent = true; 57 | 58 | this.mesh = new THREE.Mesh( 59 | new THREE.IcosahedronGeometry( radius, 3 ), 60 | this.material 61 | ); 62 | 63 | this.mesh.position.set(0, 0, 0); 64 | } 65 | 66 | public setColor(prop) { 67 | if(prop.colDark != null) { 68 | if(typeof prop.colDark === 'string') { 69 | this.material.uniforms['colDark'].value = Utils.hexToVec3(prop.colDark); 70 | } else { 71 | this.material.uniforms['colDark'].value = prop.colDark; 72 | } 73 | } 74 | if(prop.colNormal != null) { 75 | if(typeof prop.colNormal === 'string') { 76 | this.material.uniforms['colNormal'].value = Utils.hexToVec3(prop.colNormal); 77 | } else { 78 | this.material.uniforms['colNormal'].value = prop.colNormal; 79 | } 80 | } 81 | if(prop.colLight != null) { 82 | if(typeof prop.colLight === 'string') { 83 | this.material.uniforms['colLight'].value = Utils.hexToVec3(prop.colLight); 84 | } else { 85 | this.material.uniforms['colLight'].value = prop.colLight; 86 | } 87 | } 88 | } 89 | 90 | public setOpacity(value: number) { 91 | this.material.uniforms['opacity'].value = value; 92 | } 93 | 94 | public setDetail(value: number) { 95 | this.material.uniforms['detail'].value = value; 96 | } 97 | 98 | public update(timeDiff: number): void { 99 | this.material.uniforms['time'].value += .0005 * timeDiff * this.flowRatio; 100 | } 101 | 102 | public setFlowRatio(val: number): void { 103 | this.flowRatio = val; 104 | } 105 | 106 | public getMesh() { 107 | return this.mesh; 108 | } 109 | 110 | } 111 | 112 | export { FlameSphere } 113 | -------------------------------------------------------------------------------- /src/object/flareParticle.ts: -------------------------------------------------------------------------------- 1 | import { Renderer } from "../renderer"; 2 | import { AssetsManager } from "../assetsManager"; 3 | import { Controller } from "../controller"; 4 | import { Constants } from "../constants"; 5 | import { Utils } from "../utils"; 6 | 7 | class FlareParticle { 8 | 9 | private geometry: THREE.BufferGeometry; 10 | private particlesNumber = 500; 11 | private particleSystem; 12 | 13 | private time: number; 14 | private spawnParticleTime: number; 15 | private spawnParticleInterval: number; 16 | private needsUpdate: boolean[]; 17 | private positions: Float32Array; 18 | private originalSizes: Float32Array; 19 | private moveDest: Float32Array; 20 | private particleTime: Float32Array; 21 | private particleColor: number[]; 22 | 23 | private static particleSpreadingRatio; 24 | 25 | constructor() { 26 | 27 | let shaderMaterial = new THREE.ShaderMaterial({ 28 | uniforms: { 29 | color: { value: new THREE.Color(0xffffff) }, 30 | texture: { value: new THREE.TextureLoader().load("./dist/images/circle-particle.png") } 31 | }, 32 | vertexShader: AssetsManager.instance.getTexture().vectexParticleShader, 33 | fragmentShader: AssetsManager.instance.getTexture().fragmentParticleShader, 34 | blending: THREE.NormalBlending, 35 | depthTest: false, 36 | transparent: true 37 | }); 38 | 39 | this.geometry = new THREE.BufferGeometry(); 40 | 41 | let positions = new Float32Array(this.particlesNumber * 3); 42 | let colors = new Float32Array(this.particlesNumber * 3); 43 | let sizes = new Float32Array(this.particlesNumber); 44 | 45 | this.needsUpdate = []; 46 | this.originalSizes = new Float32Array(this.particlesNumber); 47 | this.moveDest = new Float32Array(this.particlesNumber * 3); 48 | this.particleTime = new Float32Array(this.particlesNumber); 49 | this.particleColor = Utils.hexToVec3(Controller.getParams().ParticleColor); 50 | 51 | for (let i = 0, i3 = 0; i < this.particlesNumber; i++ , i3 += 3) { 52 | positions[i3 + 0] = 0; 53 | positions[i3 + 1] = 0; 54 | positions[i3 + 2] = 0; 55 | 56 | this.moveDest[i3] = Math.random() * 200 - 100; 57 | this.moveDest[i3 + 1] = Math.random() * 0.3 + 0.45; 58 | this.moveDest[i3 + 2] = Math.random() * 200 - 100; 59 | 60 | colors[i3 + 0] = this.particleColor[0]; 61 | colors[i3 + 1] = this.particleColor[1]; 62 | colors[i3 + 2] = this.particleColor[2]; 63 | sizes[i] = Math.random() * 1 + 0.5; 64 | this.originalSizes[i] = sizes[i]; 65 | } 66 | this.geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3)); 67 | this.geometry.addAttribute('customColor', new THREE.BufferAttribute(colors, 3)); 68 | this.geometry.addAttribute('size', new THREE.BufferAttribute(sizes, 1)); 69 | this.particleSystem = new THREE.Points(this.geometry, shaderMaterial); 70 | 71 | Renderer.addToScene(this.particleSystem); 72 | 73 | this.reset(); 74 | FlareParticle.setController(); 75 | 76 | Controller.attachEvent(Controller.PARTICLE_COLOR, (value) => { 77 | this.particleColor = Utils.hexToVec3(value); 78 | }); 79 | } 80 | 81 | private static setController() { 82 | this.particleSpreadingRatio = Controller.getParams().ParticleSpread; 83 | Controller.attachEvent(Controller.PARTICLE_SPREAD, (value) => { 84 | this.particleSpreadingRatio = value; 85 | }); 86 | } 87 | 88 | public reset() { 89 | this.time = 0; 90 | this.spawnParticleTime = 0; 91 | this.spawnParticleInterval = 1; 92 | 93 | let sizes = this.geometry.attributes['size'].array; 94 | let positions = this.geometry.attributes['position'].array; 95 | 96 | for (let i = 0; i < this.particlesNumber; i++) { 97 | sizes[i] = 0; 98 | positions[i*3] = 0; 99 | positions[i*3 + 1] = 0; 100 | positions[i*3 + 2] = 0; 101 | this.needsUpdate[i] = false; 102 | this.particleTime[i] = 0; 103 | } 104 | 105 | this.geometry.attributes['size'].needsUpdate = true; 106 | this.geometry.attributes['position'].needsUpdate = true; 107 | } 108 | 109 | private spawnParticle() { 110 | for (let i = 0; i < this.particlesNumber; i++) { 111 | if (this.needsUpdate[i] == false) { 112 | this.needsUpdate[i] = true; 113 | return; 114 | } 115 | } 116 | } 117 | 118 | public update(deltaTime: number) { 119 | 120 | this.spawnParticleTime += deltaTime; 121 | if (this.spawnParticleTime > this.spawnParticleInterval) { 122 | this.spawnParticleTime = 0; 123 | this.spawnParticleInterval = Math.random() * 300 + 50; 124 | this.spawnParticle(); 125 | } 126 | 127 | deltaTime /= 1000; 128 | this.time += deltaTime; 129 | 130 | this.particleSystem.rotation.y += 0.01 * deltaTime; 131 | let timeScale = Controller.getParams().TimeScale / 3; 132 | let sizes = this.geometry.attributes['size'].array; 133 | let positions = this.geometry.attributes['position'].array; 134 | let colors = this.geometry.attributes['customColor'].array; 135 | 136 | for (let i = 0, i3 = 0; i < this.particlesNumber; i++ , i3 += 3) { 137 | if (this.needsUpdate[i]) { 138 | if (this.particleTime[i] > Constants.MAXIMUM_LIVE_TIME / 1000) { 139 | positions[i3] = 0; 140 | positions[i3 + 1] = 0; 141 | positions[i3 + 2] = 0; 142 | this.particleTime[i] = 0; 143 | sizes[i] = 0.01; 144 | } else { 145 | let ac = FlareParticle.particleSpreadingRatio * 146 | this.particleTime[i] / (Constants.MAXIMUM_LIVE_TIME / 1000) + 147 | 0.01 * Math.sin(this.time); 148 | let randDist = (10 * Math.sin(0.3 * i + this.time + Math.random() / 10)) * timeScale; 149 | sizes[i] = this.originalSizes[i] * (3 + Math.sin(0.4 * i + this.time)); 150 | positions[i3] = ac * this.moveDest[i3] + randDist; 151 | positions[i3 + 1] += (Math.random() * 0.4 + 0.9) * this.moveDest[i3 + 1] * timeScale; 152 | positions[i3 + 2] = ac * this.moveDest[i3 + 2] + randDist; 153 | this.particleTime[i] += deltaTime; 154 | } 155 | } 156 | 157 | colors[i3] = this.particleColor[0]; 158 | colors[i3 + 1] = this.particleColor[1]; 159 | colors[i3 + 2] = this.particleColor[2]; 160 | } 161 | 162 | this.geometry.attributes['customColor'].needsUpdate = true; 163 | this.geometry.attributes['size'].needsUpdate = true; 164 | this.geometry.attributes['position'].needsUpdate = true; 165 | } 166 | 167 | } 168 | 169 | export { FlareParticle }; -------------------------------------------------------------------------------- /src/renderer.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import "./js/OrbitControl.js"; 4 | import { Controller } from "./controller"; 5 | 6 | class Renderer { 7 | private static scene: THREE.Scene; 8 | private static camera: THREE.PerspectiveCamera; 9 | private static renderer: THREE.WebGLRenderer; 10 | private static controls: THREE.OrbitControls; 11 | 12 | private static updateCallback = null; 13 | 14 | private static vertexFlameShader; 15 | private static fragmentFlameShader; 16 | 17 | private static gridHelper: THREE.GridHelper; 18 | 19 | public static init() { 20 | 21 | this.scene = new THREE.Scene(); 22 | this.scene.background = new THREE.Color(0xf8f8f8); 23 | 24 | this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000); 25 | 26 | this.renderer = new THREE.WebGLRenderer({ antialias: true }); 27 | this.renderer.setSize(window.innerWidth, window.innerHeight); 28 | document.body.appendChild(this.renderer.domElement); 29 | 30 | // Grid Helper 31 | this.gridHelper = new THREE.GridHelper(100, 40, 0xdddddd, 0xdddddd); 32 | this.scene.add(this.gridHelper); 33 | 34 | this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); 35 | // add this only if there is no animation loop (requestAnimationFrame) 36 | this.controls.enableDamping = true; 37 | this.controls.dampingFactor = 0.25; 38 | this.controls.enableZoom = true; 39 | 40 | this.camera.position.z = 75; 41 | this.camera.position.y = 75; 42 | this.camera.position.x = 75; 43 | 44 | Controller.attachEvent(Controller.INVERTED_BACKGROUND, (value) => { 45 | if(value) { 46 | this.scene.background = new THREE.Color(0x111111); 47 | this.scene.remove(this.gridHelper); 48 | this.gridHelper = new THREE.GridHelper(100, 40, 0x444444, 0x444444); 49 | this.scene.add(this.gridHelper); 50 | } else { 51 | this.scene.background = new THREE.Color(0xf8f8f8); 52 | this.scene.remove(this.gridHelper); 53 | this.gridHelper = new THREE.GridHelper(100, 40, 0xdddddd, 0xdddddd); 54 | this.scene.add(this.gridHelper); 55 | } 56 | }); 57 | 58 | Controller.attachEvent(Controller.SHOW_GRID, (value) => { 59 | this.gridHelper.visible = value; 60 | }); 61 | } 62 | 63 | public static animate() { 64 | this.controls.update(); 65 | 66 | if (this.updateCallback != null) { 67 | this.updateCallback(); 68 | } 69 | 70 | this.renderer.render(this.scene, this.camera); 71 | } 72 | 73 | public static addToScene(obj: any) { 74 | this.scene.add(obj); 75 | } 76 | 77 | public static removeFromScene(obj) { 78 | this.scene.remove(obj); 79 | } 80 | 81 | public static setUpdateFunc(func) { 82 | this.updateCallback = func; 83 | } 84 | 85 | public static onWindowResize() { 86 | this.camera.aspect = window.innerWidth / window.innerHeight; 87 | this.camera.updateProjectionMatrix(); 88 | this.renderer.setSize(window.innerWidth, window.innerHeight); 89 | } 90 | } 91 | 92 | export { Renderer }; 93 | -------------------------------------------------------------------------------- /src/shader/fragmentFlameShader.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | varying float noise; 3 | uniform vec3 colLight; 4 | uniform vec3 colNormal; 5 | uniform vec3 colDark; 6 | uniform float opacity; 7 | 8 | vec3 blend( vec3 cola, vec3 colb, float percent ) { 9 | return vec3( 10 | cola.r + (colb.r - cola.r) * percent, 11 | cola.g + (colb.g - cola.g) * percent, 12 | cola.b + (colb.b - cola.b) * percent 13 | ); 14 | } 15 | 16 | void main() { 17 | 18 | vec3 col; 19 | float range = 1.0 * noise; 20 | 21 | if(range > .6) col = colDark; 22 | else if(range > .4) col = blend(colNormal, colDark, (range - .4) / .2); 23 | else col = blend(colLight, colNormal, range / .4); 24 | 25 | gl_FragColor = vec4( col, opacity ); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/shader/fragmentParticleShader.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 color; 2 | uniform sampler2D texture; 3 | varying vec3 vColor; 4 | 5 | void main() { 6 | gl_FragColor = vec4( vColor, 1.0 ); 7 | gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord ); 8 | } -------------------------------------------------------------------------------- /src/shader/vertexFlameShader.glsl: -------------------------------------------------------------------------------- 1 | // 2 | // GLSL textureless classic 3D noise "cnoise", 3 | // with an RSL-style periodic variant "pnoise". 4 | // Author: Stefan Gustavson (stefan.gustavson@liu.se) 5 | // Version: 2011-10-11 6 | // 7 | // Many thanks to Ian McEwan of Ashima Arts for the 8 | // ideas for permutation and gradient selection. 9 | // 10 | // Copyright (c) 2011 Stefan Gustavson. All rights reserved. 11 | // Distributed under the MIT license. See LICENSE file. 12 | // https://github.com/ashima/webgl-noise 13 | // 14 | 15 | vec3 mod289(vec3 x) 16 | { 17 | return x - floor(x * (1.0 / 289.0)) * 289.0; 18 | } 19 | 20 | vec4 mod289(vec4 x) 21 | { 22 | return x - floor(x * (1.0 / 289.0)) * 289.0; 23 | } 24 | 25 | vec4 permute(vec4 x) 26 | { 27 | return mod289(((x*34.0)+1.0)*x); 28 | } 29 | 30 | vec4 taylorInvSqrt(vec4 r) 31 | { 32 | return 1.79284291400159 - 0.85373472095314 * r; 33 | } 34 | 35 | vec3 fade(vec3 t) { 36 | return t*t*t*(t*(t*6.0-15.0)+10.0); 37 | } 38 | 39 | // Classic Perlin noise 40 | float cnoise(vec3 P) 41 | { 42 | vec3 Pi0 = floor(P); // Integer part for indexing 43 | vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1 44 | Pi0 = mod289(Pi0); 45 | Pi1 = mod289(Pi1); 46 | vec3 Pf0 = fract(P); // Fractional part for interpolation 47 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 48 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 49 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 50 | vec4 iz0 = Pi0.zzzz; 51 | vec4 iz1 = Pi1.zzzz; 52 | 53 | vec4 ixy = permute(permute(ix) + iy); 54 | vec4 ixy0 = permute(ixy + iz0); 55 | vec4 ixy1 = permute(ixy + iz1); 56 | 57 | vec4 gx0 = ixy0 * (1.0 / 7.0); 58 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 59 | gx0 = fract(gx0); 60 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 61 | vec4 sz0 = step(gz0, vec4(0.0)); 62 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 63 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 64 | 65 | vec4 gx1 = ixy1 * (1.0 / 7.0); 66 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 67 | gx1 = fract(gx1); 68 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 69 | vec4 sz1 = step(gz1, vec4(0.0)); 70 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 71 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 72 | 73 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 74 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 75 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 76 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 77 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 78 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 79 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 80 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 81 | 82 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 83 | g000 *= norm0.x; 84 | g010 *= norm0.y; 85 | g100 *= norm0.z; 86 | g110 *= norm0.w; 87 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 88 | g001 *= norm1.x; 89 | g011 *= norm1.y; 90 | g101 *= norm1.z; 91 | g111 *= norm1.w; 92 | 93 | float n000 = dot(g000, Pf0); 94 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 95 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 96 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 97 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 98 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 99 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 100 | float n111 = dot(g111, Pf1); 101 | 102 | vec3 fade_xyz = fade(Pf0); 103 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 104 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 105 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 106 | return 2.2 * n_xyz; 107 | } 108 | 109 | // Classic Perlin noise, periodic variant 110 | float pnoise(vec3 P, vec3 rep) 111 | { 112 | vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period 113 | vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period 114 | Pi0 = mod289(Pi0); 115 | Pi1 = mod289(Pi1); 116 | vec3 Pf0 = fract(P); // Fractional part for interpolation 117 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 118 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 119 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 120 | vec4 iz0 = Pi0.zzzz; 121 | vec4 iz1 = Pi1.zzzz; 122 | 123 | vec4 ixy = permute(permute(ix) + iy); 124 | vec4 ixy0 = permute(ixy + iz0); 125 | vec4 ixy1 = permute(ixy + iz1); 126 | 127 | vec4 gx0 = ixy0 * (1.0 / 7.0); 128 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 129 | gx0 = fract(gx0); 130 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 131 | vec4 sz0 = step(gz0, vec4(0.0)); 132 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 133 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 134 | 135 | vec4 gx1 = ixy1 * (1.0 / 7.0); 136 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 137 | gx1 = fract(gx1); 138 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 139 | vec4 sz1 = step(gz1, vec4(0.0)); 140 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 141 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 142 | 143 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 144 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 145 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 146 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 147 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 148 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 149 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 150 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 151 | 152 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 153 | g000 *= norm0.x; 154 | g010 *= norm0.y; 155 | g100 *= norm0.z; 156 | g110 *= norm0.w; 157 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 158 | g001 *= norm1.x; 159 | g011 *= norm1.y; 160 | g101 *= norm1.z; 161 | g111 *= norm1.w; 162 | 163 | float n000 = dot(g000, Pf0); 164 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 165 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 166 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 167 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 168 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 169 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 170 | float n111 = dot(g111, Pf1); 171 | 172 | vec3 fade_xyz = fade(Pf0); 173 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 174 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 175 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 176 | return 2.2 * n_xyz; 177 | } 178 | 179 | // Include the Ashima code here! 180 | 181 | varying float noise; 182 | uniform float time; 183 | uniform float seed; 184 | uniform float detail; 185 | 186 | float turbulence( vec3 p ) { 187 | float w = 100.0; 188 | float t = -.5; 189 | for (float f = 1.0 ; f <= 10.0 ; f++ ){ 190 | float power = pow( 2.0, f ); 191 | t += abs( pnoise( vec3( power * p ), vec3( 10.0, 10.0, 10.0 ) ) / power ); 192 | } 193 | return t; 194 | } 195 | 196 | void main() { 197 | 198 | noise = detail * -.10 * turbulence( 0.6 * normal + time + seed ); 199 | float b = 2.0 * pnoise( 0.05 * position + vec3( 2.0 * time ), vec3( 100.0 ) ); 200 | float displacement = - 10. * noise + b; 201 | 202 | vec3 newPosition = position + normal * displacement; 203 | gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 ); 204 | 205 | } 206 | -------------------------------------------------------------------------------- /src/shader/vertexParticleShader.glsl: -------------------------------------------------------------------------------- 1 | attribute float size; 2 | attribute vec3 customColor; 3 | varying vec3 vColor; 4 | 5 | void main() { 6 | vColor = customColor; 7 | vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); 8 | float cameraDist = length(mvPosition.xyz - position.xyz); 9 | gl_PointSize = size * 300.0 / cameraDist; 10 | gl_Position = projectionMatrix * mvPosition; 11 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class Utils { 4 | public static hexToVec3(col: string): number[] { 5 | let num = parseInt(col.substr(1), 16); 6 | let r = (num / 256 / 256) % 256; 7 | let g = (num / 256) % 256; 8 | let b = num % 256; 9 | return [r / 255.0, g / 255.0, b / 255.0]; 10 | } 11 | 12 | public static formatZero(val: string) { 13 | if (val.length == 1) return '0' + val; 14 | return val; 15 | } 16 | 17 | public static vec3ToHex(col: number[]): string { 18 | return '#' + 19 | this.formatZero(col[0].toString(16)) + 20 | this.formatZero(col[1].toString(16)) + 21 | this.formatZero(col[2].toString(16)); 22 | } 23 | 24 | public static vec3Blend(cola: string, colb: string, t: number) { 25 | let a = this.hexToVec3(cola); 26 | let b = this.hexToVec3(colb); 27 | return [ 28 | a[0] + (b[0] - a[0]) * t, 29 | a[1] + (b[1] - a[1]) * t, 30 | a[2] + (b[2] - a[2]) * t 31 | ]; 32 | } 33 | } 34 | 35 | export { Utils } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/**/*" 4 | ] 5 | } 6 | --------------------------------------------------------------------------------