├── textures └── cube │ └── Bridge2 │ ├── negx.jpg │ ├── negy.jpg │ ├── negz.jpg │ ├── posx.jpg │ ├── posy.jpg │ ├── posz.jpg │ └── readme.txt ├── README.md ├── js ├── shaders │ └── CopyShader.js ├── postprocessing │ ├── RenderPass.js │ ├── ShaderPass.js │ ├── EffectComposer.js │ └── OutlinePass.js ├── libs │ ├── stats.min.js │ └── dat.gui.min.js ├── WebGL.js ├── objects │ └── LightningStorm.js ├── SimplexNoise.js ├── controls │ └── OrbitControls.js └── geometries │ └── LightningStrike.js ├── LICENSE └── webgl_lightningstrike.html /textures/cube/Bridge2/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yomboprime/lightning_strike_demo/HEAD/textures/cube/Bridge2/negx.jpg -------------------------------------------------------------------------------- /textures/cube/Bridge2/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yomboprime/lightning_strike_demo/HEAD/textures/cube/Bridge2/negy.jpg -------------------------------------------------------------------------------- /textures/cube/Bridge2/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yomboprime/lightning_strike_demo/HEAD/textures/cube/Bridge2/negz.jpg -------------------------------------------------------------------------------- /textures/cube/Bridge2/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yomboprime/lightning_strike_demo/HEAD/textures/cube/Bridge2/posx.jpg -------------------------------------------------------------------------------- /textures/cube/Bridge2/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yomboprime/lightning_strike_demo/HEAD/textures/cube/Bridge2/posy.jpg -------------------------------------------------------------------------------- /textures/cube/Bridge2/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yomboprime/lightning_strike_demo/HEAD/textures/cube/Bridge2/posz.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lightning_strike_demo 2 | 3D lightning strike demo in Three.js 3 | 4 | This is a library to create 3D rays and other voltaic arcs in [Three.js](https://github.com/mrdoob/three.js) 5 | 6 | MIT Licensed 7 | -------------------------------------------------------------------------------- /textures/cube/Bridge2/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | humus@comhem.se 7 | 8 | 9 | 10 | Legal stuff 11 | =========== 12 | 13 | This work is free and may be used by anyone for any purpose 14 | and may be distributed freely to anyone using any distribution 15 | media or distribution method as long as this file is included. 16 | Distribution without this file is allowed if it's distributed 17 | with free non-commercial software; however, fair credit of the 18 | original author is expected. 19 | Any commercial distribution of this software requires the written 20 | approval of Emil Persson. 21 | -------------------------------------------------------------------------------- /js/shaders/CopyShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Full-screen textured quad shader 5 | */ 6 | 7 | THREE.CopyShader = { 8 | 9 | uniforms: { 10 | 11 | "tDiffuse": { value: null }, 12 | "opacity": { value: 1.0 } 13 | 14 | }, 15 | 16 | vertexShader: [ 17 | 18 | "varying vec2 vUv;", 19 | 20 | "void main() {", 21 | 22 | "vUv = uv;", 23 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 24 | 25 | "}" 26 | 27 | ].join( "\n" ), 28 | 29 | fragmentShader: [ 30 | 31 | "uniform float opacity;", 32 | 33 | "uniform sampler2D tDiffuse;", 34 | 35 | "varying vec2 vUv;", 36 | 37 | "void main() {", 38 | 39 | "vec4 texel = texture2D( tDiffuse, vUv );", 40 | "gl_FragColor = opacity * texel;", 41 | 42 | "}" 43 | 44 | ].join( "\n" ) 45 | 46 | }; 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Juan Jose Luna Espinosa 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 | -------------------------------------------------------------------------------- /js/postprocessing/RenderPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) { 6 | 7 | THREE.Pass.call( this ); 8 | 9 | this.scene = scene; 10 | this.camera = camera; 11 | 12 | this.overrideMaterial = overrideMaterial; 13 | 14 | this.clearColor = clearColor; 15 | this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; 16 | 17 | this.clear = true; 18 | this.clearDepth = false; 19 | this.needsSwap = false; 20 | 21 | }; 22 | 23 | THREE.RenderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 24 | 25 | constructor: THREE.RenderPass, 26 | 27 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 28 | 29 | var oldAutoClear = renderer.autoClear; 30 | renderer.autoClear = false; 31 | 32 | this.scene.overrideMaterial = this.overrideMaterial; 33 | 34 | var oldClearColor, oldClearAlpha; 35 | 36 | if ( this.clearColor ) { 37 | 38 | oldClearColor = renderer.getClearColor().getHex(); 39 | oldClearAlpha = renderer.getClearAlpha(); 40 | 41 | renderer.setClearColor( this.clearColor, this.clearAlpha ); 42 | 43 | } 44 | 45 | if ( this.clearDepth ) { 46 | 47 | renderer.clearDepth(); 48 | 49 | } 50 | 51 | renderer.render( this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear ); 52 | 53 | if ( this.clearColor ) { 54 | 55 | renderer.setClearColor( oldClearColor, oldClearAlpha ); 56 | 57 | } 58 | 59 | this.scene.overrideMaterial = null; 60 | renderer.autoClear = oldAutoClear; 61 | } 62 | 63 | } ); 64 | -------------------------------------------------------------------------------- /js/postprocessing/ShaderPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.ShaderPass = function ( shader, textureID ) { 6 | 7 | THREE.Pass.call( this ); 8 | 9 | this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse"; 10 | 11 | if ( shader instanceof THREE.ShaderMaterial ) { 12 | 13 | this.uniforms = shader.uniforms; 14 | 15 | this.material = shader; 16 | 17 | } else if ( shader ) { 18 | 19 | this.uniforms = THREE.UniformsUtils.clone( shader.uniforms ); 20 | 21 | this.material = new THREE.ShaderMaterial( { 22 | 23 | defines: Object.assign( {}, shader.defines ), 24 | uniforms: this.uniforms, 25 | vertexShader: shader.vertexShader, 26 | fragmentShader: shader.fragmentShader 27 | 28 | } ); 29 | 30 | } 31 | 32 | this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); 33 | this.scene = new THREE.Scene(); 34 | 35 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 36 | this.quad.frustumCulled = false; // Avoid getting clipped 37 | this.scene.add( this.quad ); 38 | 39 | }; 40 | 41 | THREE.ShaderPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 42 | 43 | constructor: THREE.ShaderPass, 44 | 45 | render: function( renderer, writeBuffer, readBuffer, delta, maskActive ) { 46 | 47 | if ( this.uniforms[ this.textureID ] ) { 48 | 49 | this.uniforms[ this.textureID ].value = readBuffer.texture; 50 | 51 | } 52 | 53 | this.quad.material = this.material; 54 | 55 | if ( this.renderToScreen ) { 56 | 57 | renderer.render( this.scene, this.camera ); 58 | 59 | } else { 60 | 61 | renderer.render( this.scene, this.camera, writeBuffer, this.clear ); 62 | 63 | } 64 | 65 | } 66 | 67 | } ); 68 | -------------------------------------------------------------------------------- /js/libs/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function h(a){c.appendChild(a.dom);return a}function k(a){for(var d=0;de+1E3&&(r.update(1E3*a/(c-e),100),e=c,a=0,t)){var d=performance.memory;t.update(d.usedJSHeapSize/1048576,d.jsHeapSizeLimit/1048576)}return c},update:function(){g=this.end()},domElement:c,setMode:k}}; 4 | Stats.Panel=function(h,k,l){var c=Infinity,g=0,e=Math.round,a=e(window.devicePixelRatio||1),r=80*a,f=48*a,t=3*a,u=2*a,d=3*a,m=15*a,n=74*a,p=30*a,q=document.createElement("canvas");q.width=r;q.height=f;q.style.cssText="width:80px;height:48px";var b=q.getContext("2d");b.font="bold "+9*a+"px Helvetica,Arial,sans-serif";b.textBaseline="top";b.fillStyle=l;b.fillRect(0,0,r,f);b.fillStyle=k;b.fillText(h,t,u);b.fillRect(d,m,n,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d,m,n,p);return{dom:q,update:function(f, 5 | v){c=Math.min(c,f);g=Math.max(g,f);b.fillStyle=l;b.globalAlpha=1;b.fillRect(0,0,r,m);b.fillStyle=k;b.fillText(e(f)+" "+h+" ("+e(c)+"-"+e(g)+")",t,u);b.drawImage(q,d+a,m,n-a,p,d,m,n-a,p);b.fillRect(d+n-a,m,a,p);b.fillStyle=l;b.globalAlpha=.9;b.fillRect(d+n-a,m,a,e((1-f/v)*p))}}};"object"===typeof module&&(module.exports=Stats); 6 | -------------------------------------------------------------------------------- /js/WebGL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | */ 5 | 6 | var WEBGL = { 7 | 8 | isWebGLAvailable: function () { 9 | 10 | try { 11 | 12 | var canvas = document.createElement( 'canvas' ); 13 | return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); 14 | 15 | } catch ( e ) { 16 | 17 | return false; 18 | 19 | } 20 | 21 | }, 22 | 23 | isWebGL2Available: function () { 24 | 25 | try { 26 | 27 | var canvas = document.createElement( 'canvas' ); 28 | return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) ); 29 | 30 | } catch ( e ) { 31 | 32 | return false; 33 | 34 | } 35 | 36 | }, 37 | 38 | getWebGLErrorMessage: function () { 39 | 40 | return this.getErrorMessage( 1 ); 41 | 42 | }, 43 | 44 | getWebGL2ErrorMessage: function () { 45 | 46 | return this.getErrorMessage( 2 ); 47 | 48 | }, 49 | 50 | getErrorMessage: function ( version ) { 51 | 52 | var names = { 53 | 1: 'WebGL', 54 | 2: 'WebGL 2' 55 | }; 56 | 57 | var contexts = { 58 | 1: window.WebGLRenderingContext, 59 | 2: window.WebGL2RenderingContext 60 | }; 61 | 62 | var message = 'Your $0 does not seem to support $1'; 63 | 64 | var element = document.createElement( 'div' ); 65 | element.id = 'webglmessage'; 66 | element.style.fontFamily = 'monospace'; 67 | element.style.fontSize = '13px'; 68 | element.style.fontWeight = 'normal'; 69 | element.style.textAlign = 'center'; 70 | element.style.background = '#fff'; 71 | element.style.color = '#000'; 72 | element.style.padding = '1.5em'; 73 | element.style.width = '400px'; 74 | element.style.margin = '5em auto 0'; 75 | 76 | if ( contexts[ version ] ) { 77 | 78 | message = message.replace( '$0', 'graphics card' ); 79 | 80 | } else { 81 | 82 | message = message.replace( '$0', 'browser' ); 83 | 84 | } 85 | 86 | message = message.replace( '$1', names[ version ] ); 87 | 88 | element.innerHTML = message; 89 | 90 | return element; 91 | 92 | } 93 | 94 | }; 95 | -------------------------------------------------------------------------------- /js/postprocessing/EffectComposer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.EffectComposer = function ( renderer, renderTarget ) { 6 | 7 | this.renderer = renderer; 8 | 9 | if ( renderTarget === undefined ) { 10 | 11 | var parameters = { 12 | minFilter: THREE.LinearFilter, 13 | magFilter: THREE.LinearFilter, 14 | format: THREE.RGBAFormat, 15 | stencilBuffer: false 16 | }; 17 | 18 | var size = renderer.getDrawingBufferSize(); 19 | renderTarget = new THREE.WebGLRenderTarget( size.width, size.height, parameters ); 20 | renderTarget.texture.name = 'EffectComposer.rt1'; 21 | 22 | } 23 | 24 | this.renderTarget1 = renderTarget; 25 | this.renderTarget2 = renderTarget.clone(); 26 | this.renderTarget2.texture.name = 'EffectComposer.rt2'; 27 | 28 | this.writeBuffer = this.renderTarget1; 29 | this.readBuffer = this.renderTarget2; 30 | 31 | this.passes = []; 32 | 33 | // dependencies 34 | 35 | if ( THREE.CopyShader === undefined ) { 36 | 37 | console.error( 'THREE.EffectComposer relies on THREE.CopyShader' ); 38 | 39 | } 40 | 41 | if ( THREE.ShaderPass === undefined ) { 42 | 43 | console.error( 'THREE.EffectComposer relies on THREE.ShaderPass' ); 44 | 45 | } 46 | 47 | this.copyPass = new THREE.ShaderPass( THREE.CopyShader ); 48 | 49 | }; 50 | 51 | Object.assign( THREE.EffectComposer.prototype, { 52 | 53 | swapBuffers: function () { 54 | 55 | var tmp = this.readBuffer; 56 | this.readBuffer = this.writeBuffer; 57 | this.writeBuffer = tmp; 58 | 59 | }, 60 | 61 | addPass: function ( pass ) { 62 | 63 | this.passes.push( pass ); 64 | 65 | var size = this.renderer.getDrawingBufferSize(); 66 | pass.setSize( size.width, size.height ); 67 | 68 | }, 69 | 70 | insertPass: function ( pass, index ) { 71 | 72 | this.passes.splice( index, 0, pass ); 73 | 74 | }, 75 | 76 | render: function ( delta ) { 77 | 78 | var maskActive = false; 79 | 80 | var pass, i, il = this.passes.length; 81 | 82 | for ( i = 0; i < il; i ++ ) { 83 | 84 | pass = this.passes[ i ]; 85 | 86 | if ( pass.enabled === false ) continue; 87 | 88 | pass.render( this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive ); 89 | 90 | if ( pass.needsSwap ) { 91 | 92 | if ( maskActive ) { 93 | 94 | var context = this.renderer.context; 95 | 96 | context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); 97 | 98 | this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, delta ); 99 | 100 | context.stencilFunc( context.EQUAL, 1, 0xffffffff ); 101 | 102 | } 103 | 104 | this.swapBuffers(); 105 | 106 | } 107 | 108 | if ( THREE.MaskPass !== undefined ) { 109 | 110 | if ( pass instanceof THREE.MaskPass ) { 111 | 112 | maskActive = true; 113 | 114 | } else if ( pass instanceof THREE.ClearMaskPass ) { 115 | 116 | maskActive = false; 117 | 118 | } 119 | 120 | } 121 | 122 | } 123 | 124 | }, 125 | 126 | reset: function ( renderTarget ) { 127 | 128 | if ( renderTarget === undefined ) { 129 | 130 | var size = this.renderer.getDrawingBufferSize(); 131 | 132 | renderTarget = this.renderTarget1.clone(); 133 | renderTarget.setSize( size.width, size.height ); 134 | 135 | } 136 | 137 | this.renderTarget1.dispose(); 138 | this.renderTarget2.dispose(); 139 | this.renderTarget1 = renderTarget; 140 | this.renderTarget2 = renderTarget.clone(); 141 | 142 | this.writeBuffer = this.renderTarget1; 143 | this.readBuffer = this.renderTarget2; 144 | 145 | }, 146 | 147 | setSize: function ( width, height ) { 148 | 149 | this.renderTarget1.setSize( width, height ); 150 | this.renderTarget2.setSize( width, height ); 151 | 152 | for ( var i = 0; i < this.passes.length; i ++ ) { 153 | 154 | this.passes[ i ].setSize( width, height ); 155 | 156 | } 157 | 158 | } 159 | 160 | } ); 161 | 162 | 163 | THREE.Pass = function () { 164 | 165 | // if set to true, the pass is processed by the composer 166 | this.enabled = true; 167 | 168 | // if set to true, the pass indicates to swap read and write buffer after rendering 169 | this.needsSwap = true; 170 | 171 | // if set to true, the pass clears its buffer before rendering 172 | this.clear = false; 173 | 174 | // if set to true, the result of the pass is rendered to screen 175 | this.renderToScreen = false; 176 | 177 | }; 178 | 179 | Object.assign( THREE.Pass.prototype, { 180 | 181 | setSize: function ( width, height ) {}, 182 | 183 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 184 | 185 | console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); 186 | 187 | } 188 | 189 | } ); 190 | -------------------------------------------------------------------------------- /js/objects/LightningStorm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author yomboprime https://github.com/yomboprime 3 | * 4 | * @fileoverview Lightning strike object generator 5 | * 6 | * 7 | * Usage 8 | * 9 | * var myStorm = new THREE.LightningStorm( paramsObject ); 10 | * myStorm.position.set( ... ); 11 | * scene.add( myStorm ); 12 | * ... 13 | * myStorm.update( currentTime ); 14 | * 15 | * The "currentTime" can only go forwards or be stopped. 16 | * 17 | * 18 | * LightningStorm parameters: 19 | * 20 | * @param {double} size Size of the storm. If no 'onRayPosition' parameter is defined, it means the side of the rectangle the storm covers. 21 | * 22 | * @param {double} minHeight Minimum height a ray can start at. If no 'onRayPosition' parameter is defined, it means the height above plane y = 0. 23 | * 24 | * @param {double} maxHeight Maximum height a ray can start at. If no 'onRayPosition' parameter is defined, it means the height above plane y = 0. 25 | * 26 | * @param {double} maxSlope The maximum inclination slope of a ray. If no 'onRayPosition' parameter is defined, it means the slope relative to plane y = 0. 27 | * 28 | * @param {integer} maxLightnings Greater than 0. The maximum number of simultaneous rays. 29 | * 30 | * @param {double} lightningMinPeriod minimum time between two consecutive rays. 31 | * 32 | * @param {double} lightningMaxPeriod maximum time between two consecutive rays. 33 | * 34 | * @param {double} lightningMinDuration The minimum time a ray can last. 35 | * 36 | * @param {double} lightningMaxDuration The maximum time a ray can last. 37 | * 38 | * @param {Object} lightningParameters The parameters for created rays. See THREE.LightningStrike (geometry) 39 | * 40 | * @param {Material} lightningMaterial The THREE.Material used for the created rays. 41 | * 42 | * @param {function} onRayPosition Optional callback with two Vector3 parameters (source, dest). You can set here the start and end points for each created ray, using the standard size, minHeight, etc parameters and other values in your algorithm. 43 | * 44 | * @param {function} onLightningDown This optional callback is called with one parameter (lightningStrike) when a ray ends propagating, so it has hit the ground. 45 | * 46 | * 47 | */ 48 | 49 | THREE.LightningStorm = function ( stormParams ) { 50 | 51 | THREE.Object3D.call( this ); 52 | 53 | // Parameters 54 | 55 | stormParams = stormParams || {}; 56 | this.stormParams = stormParams; 57 | 58 | stormParams.size = stormParams.size !== undefined ? stormParams.size : 1000.0; 59 | stormParams.minHeight = stormParams.minHeight !== undefined ? stormParams.minHeight : 80.0; 60 | stormParams.maxHeight = stormParams.maxHeight !== undefined ? stormParams.maxHeight : 100.0; 61 | stormParams.maxSlope = stormParams.maxSlope !== undefined ? stormParams.maxSlope : 1.1; 62 | 63 | stormParams.maxLightnings = stormParams.maxLightnings !== undefined ? stormParams.maxLightnings : 3; 64 | 65 | stormParams.lightningMinPeriod = stormParams.lightningMinPeriod !== undefined ? stormParams.lightningMinPeriod : 3.0; 66 | stormParams.lightningMaxPeriod = stormParams.lightningMaxPeriod !== undefined ? stormParams.lightningMaxPeriod : 7.0; 67 | 68 | stormParams.lightningMinDuration = stormParams.lightningMinDuration !== undefined ? stormParams.lightningMinDuration : 1.0; 69 | stormParams.lightningMaxDuration = stormParams.lightningMaxDuration !== undefined ? stormParams.lightningMaxDuration : 2.5; 70 | 71 | this.lightningParameters = THREE.LightningStrike.copyParameters( stormParams.lightningParameters, stormParams.lightningParameters ); 72 | 73 | this.lightningParameters.isEternal = false; 74 | 75 | this.lightningMaterial = stormParams.lightningMaterial !== undefined ? stormParams.lightningMaterial : new THREE.MeshBasicMaterial( { color: 0xB0FFFF } ); 76 | 77 | if ( stormParams.onRayPosition !== undefined ) { 78 | 79 | this.onRayPosition = stormParams.onRayPosition; 80 | 81 | } 82 | else { 83 | 84 | this.onRayPosition = function( source, dest ) { 85 | 86 | dest.set( ( Math.random() - 0.5 ) * stormParams.size, 0, ( Math.random() - 0.5 ) * stormParams.size ); 87 | 88 | var height = THREE.Math.lerp( stormParams.minHeight, stormParams.maxHeight, Math.random() );; 89 | 90 | source.set( stormParams.maxSlope * ( 2 * Math.random() - 1 ), 1, stormParams.maxSlope * ( 2 * Math.random() - 1 ) ).multiplyScalar( height ).add( dest ); 91 | 92 | }; 93 | 94 | } 95 | 96 | this.onLightningDown = stormParams.onLightningDown; 97 | 98 | // Internal state 99 | 100 | this.inited = false; 101 | this.nextLightningTime = 0; 102 | this.lightningsMeshes = []; 103 | this.deadLightningsMeshes = []; 104 | 105 | for ( var i = 0; i < this.stormParams.maxLightnings; i++ ) { 106 | 107 | var lightning = new THREE.LightningStrike( THREE.LightningStrike.copyParameters( {}, this.lightningParameters ) ); 108 | var mesh = new THREE.Mesh( lightning, this.lightningMaterial ); 109 | this.deadLightningsMeshes.push( mesh ); 110 | 111 | } 112 | 113 | }; 114 | 115 | THREE.LightningStorm.prototype = Object.create( THREE.Object3D.prototype ); 116 | 117 | THREE.LightningStorm.prototype.constructor = THREE.LightningStorm; 118 | 119 | THREE.LightningStorm.prototype.isLightningStorm = true; 120 | 121 | THREE.LightningStorm.prototype.update = function ( time ) { 122 | 123 | if ( ! this.inited ) { 124 | 125 | this.nextLightningTime = this.getNextLightningTime( time ) * Math.random(); 126 | this.inited = true; 127 | 128 | } 129 | 130 | if ( time >= this.nextLightningTime ) { 131 | 132 | // Lightning creation 133 | 134 | var lightningMesh = this.deadLightningsMeshes.pop(); 135 | 136 | if ( lightningMesh ) { 137 | 138 | var lightningParams1 = THREE.LightningStrike.copyParameters( lightningMesh.geometry.rayParameters, this.lightningParameters ); 139 | 140 | lightningParams1.birthTime = time; 141 | lightningParams1.deathTime = time + THREE.Math.lerp( this.stormParams.lightningMinDuration, this.stormParams.lightningMaxDuration, Math.random() ); 142 | 143 | this.onRayPosition( lightningParams1.sourceOffset, lightningParams1.destOffset ); 144 | 145 | lightningParams1.noiseSeed = Math.random(); 146 | 147 | this.add( lightningMesh ); 148 | 149 | this.lightningsMeshes.push( lightningMesh ); 150 | 151 | } 152 | 153 | // Schedule next lightning 154 | this.nextLightningTime = this.getNextLightningTime( time ); 155 | 156 | } 157 | 158 | var i = 0; il = this.lightningsMeshes.length; 159 | 160 | while ( i < il ){ 161 | 162 | var mesh = this.lightningsMeshes[ i ]; 163 | 164 | var lightning = mesh.geometry; 165 | 166 | var prevState = lightning.state; 167 | 168 | lightning.update( time ); 169 | 170 | if ( prevState === THREE.LightningStrike.RAY_PROPAGATING && lightning.state > prevState ) { 171 | 172 | if ( this.onLightningDown ) { 173 | 174 | this.onLightningDown( lightning ); 175 | 176 | } 177 | 178 | } 179 | 180 | if ( lightning.state === THREE.LightningStrike.RAY_EXTINGUISHED ) { 181 | 182 | // Lightning is to be destroyed 183 | 184 | this.lightningsMeshes.splice( this.lightningsMeshes.indexOf( mesh ), 1 ); 185 | 186 | this.deadLightningsMeshes.push( mesh ); 187 | 188 | this.remove( mesh ); 189 | 190 | il--; 191 | 192 | } 193 | else { 194 | 195 | i++; 196 | 197 | } 198 | 199 | } 200 | 201 | }; 202 | 203 | THREE.LightningStorm.prototype.getNextLightningTime = function ( currentTime ) { 204 | 205 | return currentTime + THREE.Math.lerp( this.stormParams.lightningMinPeriod, this.stormParams.lightningMaxPeriod, Math.random() ) / ( this.stormParams.maxLightnings + 1 ); 206 | 207 | }; 208 | 209 | THREE.LightningStorm.prototype.copy = function ( source ) { 210 | 211 | Object3D.prototype.copy.call( this, source ); 212 | 213 | this.stormParams.size = source.stormParams.size; 214 | this.stormParams.minHeight = source.stormParams.minHeight; 215 | this.stormParams.maxHeight = source.stormParams.maxHeight; 216 | this.stormParams.maxSlope = source.stormParams.maxSlope; 217 | 218 | this.stormParams.maxLightnings = source.stormParams.maxLightnings; 219 | 220 | this.stormParams.lightningMinPeriod = source.stormParams.lightningMinPeriod; 221 | this.stormParams.lightningMaxPeriod = source.stormParams.lightningMaxPeriod; 222 | 223 | this.stormParams.lightningMinDuration = source.stormParams.lightningMinDuration; 224 | this.stormParams.lightningMaxDuration = source.stormParams.lightningMaxDuration; 225 | 226 | this.lightningParameters = THREE.LightningStrike.copyParameters( {}, source.lightningParameters ); 227 | 228 | this.lightningMaterial = source.stormParams.lightningMaterial; 229 | 230 | this.onLightningDown = source.onLightningDown; 231 | 232 | return this; 233 | 234 | }; 235 | 236 | THREE.LightningStrike.prototype.clone = function () { 237 | 238 | return new this.constructor( this.stormParams ).copy( this ); 239 | 240 | }; 241 | -------------------------------------------------------------------------------- /js/SimplexNoise.js: -------------------------------------------------------------------------------- 1 | // Ported from Stefan Gustavson's java implementation 2 | // http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf 3 | // Read Stefan's excellent paper for details on how this code works. 4 | // 5 | // Sean McCullough banksean@gmail.com 6 | // 7 | // Added 4D noise 8 | // Joshua Koo zz85nus@gmail.com 9 | 10 | /** 11 | * You can pass in a random number generator object if you like. 12 | * It is assumed to have a random() method. 13 | */ 14 | var SimplexNoise = function(r) { 15 | if (r == undefined) r = Math; 16 | this.grad3 = [[ 1,1,0 ],[ -1,1,0 ],[ 1,-1,0 ],[ -1,-1,0 ], 17 | [ 1,0,1 ],[ -1,0,1 ],[ 1,0,-1 ],[ -1,0,-1 ], 18 | [ 0,1,1 ],[ 0,-1,1 ],[ 0,1,-1 ],[ 0,-1,-1 ]]; 19 | 20 | this.grad4 = [[ 0,1,1,1 ], [ 0,1,1,-1 ], [ 0,1,-1,1 ], [ 0,1,-1,-1 ], 21 | [ 0,-1,1,1 ], [ 0,-1,1,-1 ], [ 0,-1,-1,1 ], [ 0,-1,-1,-1 ], 22 | [ 1,0,1,1 ], [ 1,0,1,-1 ], [ 1,0,-1,1 ], [ 1,0,-1,-1 ], 23 | [ -1,0,1,1 ], [ -1,0,1,-1 ], [ -1,0,-1,1 ], [ -1,0,-1,-1 ], 24 | [ 1,1,0,1 ], [ 1,1,0,-1 ], [ 1,-1,0,1 ], [ 1,-1,0,-1 ], 25 | [ -1,1,0,1 ], [ -1,1,0,-1 ], [ -1,-1,0,1 ], [ -1,-1,0,-1 ], 26 | [ 1,1,1,0 ], [ 1,1,-1,0 ], [ 1,-1,1,0 ], [ 1,-1,-1,0 ], 27 | [ -1,1,1,0 ], [ -1,1,-1,0 ], [ -1,-1,1,0 ], [ -1,-1,-1,0 ]]; 28 | 29 | this.p = []; 30 | for (var i = 0; i < 256; i ++) { 31 | this.p[i] = Math.floor(r.random() * 256); 32 | } 33 | // To remove the need for index wrapping, double the permutation table length 34 | this.perm = []; 35 | for (var i = 0; i < 512; i ++) { 36 | this.perm[i] = this.p[i & 255]; 37 | } 38 | 39 | // A lookup table to traverse the simplex around a given point in 4D. 40 | // Details can be found where this table is used, in the 4D noise method. 41 | this.simplex = [ 42 | [ 0,1,2,3 ],[ 0,1,3,2 ],[ 0,0,0,0 ],[ 0,2,3,1 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 1,2,3,0 ], 43 | [ 0,2,1,3 ],[ 0,0,0,0 ],[ 0,3,1,2 ],[ 0,3,2,1 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 1,3,2,0 ], 44 | [ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ], 45 | [ 1,2,0,3 ],[ 0,0,0,0 ],[ 1,3,0,2 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 2,3,0,1 ],[ 2,3,1,0 ], 46 | [ 1,0,2,3 ],[ 1,0,3,2 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 2,0,3,1 ],[ 0,0,0,0 ],[ 2,1,3,0 ], 47 | [ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ], 48 | [ 2,0,1,3 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 3,0,1,2 ],[ 3,0,2,1 ],[ 0,0,0,0 ],[ 3,1,2,0 ], 49 | [ 2,1,0,3 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 0,0,0,0 ],[ 3,1,0,2 ],[ 0,0,0,0 ],[ 3,2,0,1 ],[ 3,2,1,0 ]]; 50 | }; 51 | 52 | SimplexNoise.prototype.dot = function(g, x, y) { 53 | return g[0] * x + g[1] * y; 54 | }; 55 | 56 | SimplexNoise.prototype.dot3 = function(g, x, y, z) { 57 | return g[0] * x + g[1] * y + g[2] * z; 58 | }; 59 | 60 | SimplexNoise.prototype.dot4 = function(g, x, y, z, w) { 61 | return g[0] * x + g[1] * y + g[2] * z + g[3] * w; 62 | }; 63 | 64 | SimplexNoise.prototype.noise = function(xin, yin) { 65 | var n0, n1, n2; // Noise contributions from the three corners 66 | // Skew the input space to determine which simplex cell we're in 67 | var F2 = 0.5 * (Math.sqrt(3.0) - 1.0); 68 | var s = (xin + yin) * F2; // Hairy factor for 2D 69 | var i = Math.floor(xin + s); 70 | var j = Math.floor(yin + s); 71 | var G2 = (3.0 - Math.sqrt(3.0)) / 6.0; 72 | var t = (i + j) * G2; 73 | var X0 = i - t; // Unskew the cell origin back to (x,y) space 74 | var Y0 = j - t; 75 | var x0 = xin - X0; // The x,y distances from the cell origin 76 | var y0 = yin - Y0; 77 | // For the 2D case, the simplex shape is an equilateral triangle. 78 | // Determine which simplex we are in. 79 | var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords 80 | if (x0 > y0) {i1 = 1; j1 = 0;} // lower triangle, XY order: (0,0)->(1,0)->(1,1) 81 | else {i1 = 0; j1 = 1;} // upper triangle, YX order: (0,0)->(0,1)->(1,1) 82 | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 83 | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 84 | // c = (3-sqrt(3))/6 85 | var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords 86 | var y1 = y0 - j1 + G2; 87 | var x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords 88 | var y2 = y0 - 1.0 + 2.0 * G2; 89 | // Work out the hashed gradient indices of the three simplex corners 90 | var ii = i & 255; 91 | var jj = j & 255; 92 | var gi0 = this.perm[ii + this.perm[jj]] % 12; 93 | var gi1 = this.perm[ii + i1 + this.perm[jj + j1]] % 12; 94 | var gi2 = this.perm[ii + 1 + this.perm[jj + 1]] % 12; 95 | // Calculate the contribution from the three corners 96 | var t0 = 0.5 - x0 * x0 - y0 * y0; 97 | if (t0 < 0) n0 = 0.0; 98 | else { 99 | t0 *= t0; 100 | n0 = t0 * t0 * this.dot(this.grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient 101 | } 102 | var t1 = 0.5 - x1 * x1 - y1 * y1; 103 | if (t1 < 0) n1 = 0.0; 104 | else { 105 | t1 *= t1; 106 | n1 = t1 * t1 * this.dot(this.grad3[gi1], x1, y1); 107 | } 108 | var t2 = 0.5 - x2 * x2 - y2 * y2; 109 | if (t2 < 0) n2 = 0.0; 110 | else { 111 | t2 *= t2; 112 | n2 = t2 * t2 * this.dot(this.grad3[gi2], x2, y2); 113 | } 114 | // Add contributions from each corner to get the final noise value. 115 | // The result is scaled to return values in the interval [-1,1]. 116 | return 70.0 * (n0 + n1 + n2); 117 | }; 118 | 119 | // 3D simplex noise 120 | SimplexNoise.prototype.noise3d = function(xin, yin, zin) { 121 | var n0, n1, n2, n3; // Noise contributions from the four corners 122 | // Skew the input space to determine which simplex cell we're in 123 | var F3 = 1.0 / 3.0; 124 | var s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D 125 | var i = Math.floor(xin + s); 126 | var j = Math.floor(yin + s); 127 | var k = Math.floor(zin + s); 128 | var G3 = 1.0 / 6.0; // Very nice and simple unskew factor, too 129 | var t = (i + j + k) * G3; 130 | var X0 = i - t; // Unskew the cell origin back to (x,y,z) space 131 | var Y0 = j - t; 132 | var Z0 = k - t; 133 | var x0 = xin - X0; // The x,y,z distances from the cell origin 134 | var y0 = yin - Y0; 135 | var z0 = zin - Z0; 136 | // For the 3D case, the simplex shape is a slightly irregular tetrahedron. 137 | // Determine which simplex we are in. 138 | var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords 139 | var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords 140 | if (x0 >= y0) { 141 | if (y0 >= z0) 142 | { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; } // X Y Z order 143 | else if (x0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; } // X Z Y order 144 | else { i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; } // Z X Y order 145 | } 146 | else { // x0 y0) ? 32 : 0; 238 | var c2 = (x0 > z0) ? 16 : 0; 239 | var c3 = (y0 > z0) ? 8 : 0; 240 | var c4 = (x0 > w0) ? 4 : 0; 241 | var c5 = (y0 > w0) ? 2 : 0; 242 | var c6 = (z0 > w0) ? 1 : 0; 243 | var c = c1 + c2 + c3 + c4 + c5 + c6; 244 | var i1, j1, k1, l1; // The integer offsets for the second simplex corner 245 | var i2, j2, k2, l2; // The integer offsets for the third simplex corner 246 | var i3, j3, k3, l3; // The integer offsets for the fourth simplex corner 247 | // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. 248 | // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; 253 | j1 = simplex[c][1] >= 3 ? 1 : 0; 254 | k1 = simplex[c][2] >= 3 ? 1 : 0; 255 | l1 = simplex[c][3] >= 3 ? 1 : 0; 256 | // The number 2 in the "simplex" array is at the second largest coordinate. 257 | i2 = simplex[c][0] >= 2 ? 1 : 0; 258 | j2 = simplex[c][1] >= 2 ? 1 : 0; k2 = simplex[c][2] >= 2 ? 1 : 0; 259 | l2 = simplex[c][3] >= 2 ? 1 : 0; 260 | // The number 1 in the "simplex" array is at the second smallest coordinate. 261 | i3 = simplex[c][0] >= 1 ? 1 : 0; 262 | j3 = simplex[c][1] >= 1 ? 1 : 0; 263 | k3 = simplex[c][2] >= 1 ? 1 : 0; 264 | l3 = simplex[c][3] >= 1 ? 1 : 0; 265 | // The fifth corner has all coordinate offsets = 1, so no need to look that up. 266 | var x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords 267 | var y1 = y0 - j1 + G4; 268 | var z1 = z0 - k1 + G4; 269 | var w1 = w0 - l1 + G4; 270 | var x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords 271 | var y2 = y0 - j2 + 2.0 * G4; 272 | var z2 = z0 - k2 + 2.0 * G4; 273 | var w2 = w0 - l2 + 2.0 * G4; 274 | var x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords 275 | var y3 = y0 - j3 + 3.0 * G4; 276 | var z3 = z0 - k3 + 3.0 * G4; 277 | var w3 = w0 - l3 + 3.0 * G4; 278 | var x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords 279 | var y4 = y0 - 1.0 + 4.0 * G4; 280 | var z4 = z0 - 1.0 + 4.0 * G4; 281 | var w4 = w0 - 1.0 + 4.0 * G4; 282 | // Work out the hashed gradient indices of the five simplex corners 283 | var ii = i & 255; 284 | var jj = j & 255; 285 | var kk = k & 255; 286 | var ll = l & 255; 287 | var gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32; 288 | var gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32; 289 | var gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32; 290 | var gi3 = perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32; 291 | var gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32; 292 | // Calculate the contribution from the five corners 293 | var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; 294 | if (t0 < 0) n0 = 0.0; 295 | else { 296 | t0 *= t0; 297 | n0 = t0 * t0 * this.dot4(grad4[gi0], x0, y0, z0, w0); 298 | } 299 | var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; 300 | if (t1 < 0) n1 = 0.0; 301 | else { 302 | t1 *= t1; 303 | n1 = t1 * t1 * this.dot4(grad4[gi1], x1, y1, z1, w1); 304 | } 305 | var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; 306 | if (t2 < 0) n2 = 0.0; 307 | else { 308 | t2 *= t2; 309 | n2 = t2 * t2 * this.dot4(grad4[gi2], x2, y2, z2, w2); 310 | } var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; 311 | if (t3 < 0) n3 = 0.0; 312 | else { 313 | t3 *= t3; 314 | n3 = t3 * t3 * this.dot4(grad4[gi3], x3, y3, z3, w3); 315 | } 316 | var t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; 317 | if (t4 < 0) n4 = 0.0; 318 | else { 319 | t4 *= t4; 320 | n4 = t4 * t4 * this.dot4(grad4[gi4], x4, y4, z4, w4); 321 | } 322 | // Sum up and scale the result to cover the range [-1,1] 323 | return 27.0 * (n0 + n1 + n2 + n3 + n4); 324 | }; 325 | -------------------------------------------------------------------------------- /js/postprocessing/OutlinePass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author spidersharma / http://eduperiment.com/ 3 | */ 4 | 5 | THREE.OutlinePass = function ( resolution, scene, camera, selectedObjects ) { 6 | 7 | this.renderScene = scene; 8 | this.renderCamera = camera; 9 | this.selectedObjects = selectedObjects !== undefined ? selectedObjects : []; 10 | this.visibleEdgeColor = new THREE.Color( 1, 1, 1 ); 11 | this.hiddenEdgeColor = new THREE.Color( 0.1, 0.04, 0.02 ); 12 | this.edgeGlow = 0.0; 13 | this.usePatternTexture = false; 14 | this.edgeThickness = 1.0; 15 | this.edgeStrength = 3.0; 16 | this.downSampleRatio = 2; 17 | this.pulsePeriod = 0; 18 | 19 | THREE.Pass.call( this ); 20 | 21 | this.resolution = ( resolution !== undefined ) ? new THREE.Vector2( resolution.x, resolution.y ) : new THREE.Vector2( 256, 256 ); 22 | 23 | var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat }; 24 | 25 | var resx = Math.round( this.resolution.x / this.downSampleRatio ); 26 | var resy = Math.round( this.resolution.y / this.downSampleRatio ); 27 | 28 | this.maskBufferMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff } ); 29 | this.maskBufferMaterial.side = THREE.DoubleSide; 30 | this.renderTargetMaskBuffer = new THREE.WebGLRenderTarget( this.resolution.x, this.resolution.y, pars ); 31 | this.renderTargetMaskBuffer.texture.name = "OutlinePass.mask"; 32 | this.renderTargetMaskBuffer.texture.generateMipmaps = false; 33 | 34 | this.depthMaterial = new THREE.MeshDepthMaterial(); 35 | this.depthMaterial.side = THREE.DoubleSide; 36 | this.depthMaterial.depthPacking = THREE.RGBADepthPacking; 37 | this.depthMaterial.blending = THREE.NoBlending; 38 | 39 | this.prepareMaskMaterial = this.getPrepareMaskMaterial(); 40 | this.prepareMaskMaterial.side = THREE.DoubleSide; 41 | this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera ); 42 | 43 | this.renderTargetDepthBuffer = new THREE.WebGLRenderTarget( this.resolution.x, this.resolution.y, pars ); 44 | this.renderTargetDepthBuffer.texture.name = "OutlinePass.depth"; 45 | this.renderTargetDepthBuffer.texture.generateMipmaps = false; 46 | 47 | this.renderTargetMaskDownSampleBuffer = new THREE.WebGLRenderTarget( resx, resy, pars ); 48 | this.renderTargetMaskDownSampleBuffer.texture.name = "OutlinePass.depthDownSample"; 49 | this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false; 50 | 51 | this.renderTargetBlurBuffer1 = new THREE.WebGLRenderTarget( resx, resy, pars ); 52 | this.renderTargetBlurBuffer1.texture.name = "OutlinePass.blur1"; 53 | this.renderTargetBlurBuffer1.texture.generateMipmaps = false; 54 | this.renderTargetBlurBuffer2 = new THREE.WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), pars ); 55 | this.renderTargetBlurBuffer2.texture.name = "OutlinePass.blur2"; 56 | this.renderTargetBlurBuffer2.texture.generateMipmaps = false; 57 | 58 | this.edgeDetectionMaterial = this.getEdgeDetectionMaterial(); 59 | this.renderTargetEdgeBuffer1 = new THREE.WebGLRenderTarget( resx, resy, pars ); 60 | this.renderTargetEdgeBuffer1.texture.name = "OutlinePass.edge1"; 61 | this.renderTargetEdgeBuffer1.texture.generateMipmaps = false; 62 | this.renderTargetEdgeBuffer2 = new THREE.WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), pars ); 63 | this.renderTargetEdgeBuffer2.texture.name = "OutlinePass.edge2"; 64 | this.renderTargetEdgeBuffer2.texture.generateMipmaps = false; 65 | 66 | var MAX_EDGE_THICKNESS = 4; 67 | var MAX_EDGE_GLOW = 4; 68 | 69 | this.separableBlurMaterial1 = this.getSeperableBlurMaterial( MAX_EDGE_THICKNESS ); 70 | this.separableBlurMaterial1.uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 71 | this.separableBlurMaterial1.uniforms[ "kernelRadius" ].value = 1; 72 | this.separableBlurMaterial2 = this.getSeperableBlurMaterial( MAX_EDGE_GLOW ); 73 | this.separableBlurMaterial2.uniforms[ "texSize" ].value = new THREE.Vector2( Math.round( resx / 2 ), Math.round( resy / 2 ) ); 74 | this.separableBlurMaterial2.uniforms[ "kernelRadius" ].value = MAX_EDGE_GLOW; 75 | 76 | // Overlay material 77 | this.overlayMaterial = this.getOverlayMaterial(); 78 | 79 | // copy material 80 | if ( THREE.CopyShader === undefined ) 81 | console.error( "THREE.OutlinePass relies on THREE.CopyShader" ); 82 | 83 | var copyShader = THREE.CopyShader; 84 | 85 | this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms ); 86 | this.copyUniforms[ "opacity" ].value = 1.0; 87 | 88 | this.materialCopy = new THREE.ShaderMaterial( { 89 | uniforms: this.copyUniforms, 90 | vertexShader: copyShader.vertexShader, 91 | fragmentShader: copyShader.fragmentShader, 92 | blending: THREE.NoBlending, 93 | depthTest: false, 94 | depthWrite: false, 95 | transparent: true 96 | } ); 97 | 98 | this.enabled = true; 99 | this.needsSwap = false; 100 | 101 | this.oldClearColor = new THREE.Color(); 102 | this.oldClearAlpha = 1; 103 | 104 | this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); 105 | this.scene = new THREE.Scene(); 106 | 107 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 108 | this.quad.frustumCulled = false; // Avoid getting clipped 109 | this.scene.add( this.quad ); 110 | 111 | this.tempPulseColor1 = new THREE.Color(); 112 | this.tempPulseColor2 = new THREE.Color(); 113 | this.textureMatrix = new THREE.Matrix4(); 114 | 115 | function replaceDepthToViewZ( string, camera ) { 116 | 117 | var type = camera.isPerspectiveCamera ? 'perspective' : 'orthographic'; 118 | 119 | return string.replace( /DEPTH_TO_VIEW_Z/g, type + 'DepthToViewZ' ); 120 | 121 | } 122 | 123 | }; 124 | 125 | THREE.OutlinePass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 126 | 127 | constructor: THREE.OutlinePass, 128 | 129 | dispose: function () { 130 | 131 | this.renderTargetMaskBuffer.dispose(); 132 | this.renderTargetDepthBuffer.dispose(); 133 | this.renderTargetMaskDownSampleBuffer.dispose(); 134 | this.renderTargetBlurBuffer1.dispose(); 135 | this.renderTargetBlurBuffer2.dispose(); 136 | this.renderTargetEdgeBuffer1.dispose(); 137 | this.renderTargetEdgeBuffer2.dispose(); 138 | 139 | }, 140 | 141 | setSize: function ( width, height ) { 142 | 143 | this.renderTargetMaskBuffer.setSize( width, height ); 144 | 145 | var resx = Math.round( width / this.downSampleRatio ); 146 | var resy = Math.round( height / this.downSampleRatio ); 147 | this.renderTargetMaskDownSampleBuffer.setSize( resx, resy ); 148 | this.renderTargetBlurBuffer1.setSize( resx, resy ); 149 | this.renderTargetEdgeBuffer1.setSize( resx, resy ); 150 | this.separableBlurMaterial1.uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 151 | 152 | resx = Math.round( resx / 2 ); 153 | resy = Math.round( resy / 2 ); 154 | 155 | this.renderTargetBlurBuffer2.setSize( resx, resy ); 156 | this.renderTargetEdgeBuffer2.setSize( resx, resy ); 157 | 158 | this.separableBlurMaterial2.uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 159 | 160 | }, 161 | 162 | changeVisibilityOfSelectedObjects: function ( bVisible ) { 163 | 164 | function gatherSelectedMeshesCallBack( object ) { 165 | 166 | if ( object.isMesh ) { 167 | 168 | if ( bVisible ) { 169 | 170 | object.visible = object.userData.oldVisible; 171 | delete object.userData.oldVisible; 172 | 173 | } else { 174 | 175 | object.userData.oldVisible = object.visible; 176 | object.visible = bVisible; 177 | 178 | } 179 | 180 | } 181 | 182 | } 183 | 184 | for ( var i = 0; i < this.selectedObjects.length; i ++ ) { 185 | 186 | var selectedObject = this.selectedObjects[ i ]; 187 | selectedObject.traverse( gatherSelectedMeshesCallBack ); 188 | 189 | } 190 | 191 | }, 192 | 193 | changeVisibilityOfNonSelectedObjects: function ( bVisible ) { 194 | 195 | var selectedMeshes = []; 196 | 197 | function gatherSelectedMeshesCallBack( object ) { 198 | 199 | if ( object.isMesh ) selectedMeshes.push( object ); 200 | 201 | } 202 | 203 | for ( var i = 0; i < this.selectedObjects.length; i ++ ) { 204 | 205 | var selectedObject = this.selectedObjects[ i ]; 206 | selectedObject.traverse( gatherSelectedMeshesCallBack ); 207 | 208 | } 209 | 210 | function VisibilityChangeCallBack( object ) { 211 | 212 | if ( object.isMesh || object.isLine || object.isSprite ) { 213 | 214 | var bFound = false; 215 | 216 | for ( var i = 0; i < selectedMeshes.length; i ++ ) { 217 | 218 | var selectedObjectId = selectedMeshes[ i ].id; 219 | 220 | if ( selectedObjectId === object.id ) { 221 | 222 | bFound = true; 223 | break; 224 | 225 | } 226 | 227 | } 228 | 229 | if ( ! bFound ) { 230 | 231 | var visibility = object.visible; 232 | 233 | if ( ! bVisible || object.bVisible ) object.visible = bVisible; 234 | 235 | object.bVisible = visibility; 236 | 237 | } 238 | 239 | } 240 | 241 | } 242 | 243 | this.renderScene.traverse( VisibilityChangeCallBack ); 244 | 245 | }, 246 | 247 | updateTextureMatrix: function () { 248 | 249 | this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, 250 | 0.0, 0.5, 0.0, 0.5, 251 | 0.0, 0.0, 0.5, 0.5, 252 | 0.0, 0.0, 0.0, 1.0 ); 253 | this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); 254 | this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); 255 | 256 | }, 257 | 258 | render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) { 259 | 260 | if ( this.selectedObjects.length > 0 ) { 261 | 262 | this.oldClearColor.copy( renderer.getClearColor() ); 263 | this.oldClearAlpha = renderer.getClearAlpha(); 264 | var oldAutoClear = renderer.autoClear; 265 | 266 | renderer.autoClear = false; 267 | 268 | if ( maskActive ) renderer.context.disable( renderer.context.STENCIL_TEST ); 269 | 270 | renderer.setClearColor( 0xffffff, 1 ); 271 | 272 | // Make selected objects invisible 273 | this.changeVisibilityOfSelectedObjects( false ); 274 | 275 | var currentBackground = this.renderScene.background; 276 | this.renderScene.background = null; 277 | 278 | // 1. Draw Non Selected objects in the depth buffer 279 | this.renderScene.overrideMaterial = this.depthMaterial; 280 | renderer.render( this.renderScene, this.renderCamera, this.renderTargetDepthBuffer, true ); 281 | 282 | // Make selected objects visible 283 | this.changeVisibilityOfSelectedObjects( true ); 284 | 285 | // Update Texture Matrix for Depth compare 286 | this.updateTextureMatrix(); 287 | 288 | // Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects 289 | this.changeVisibilityOfNonSelectedObjects( false ); 290 | this.renderScene.overrideMaterial = this.prepareMaskMaterial; 291 | this.prepareMaskMaterial.uniforms[ "cameraNearFar" ].value = new THREE.Vector2( this.renderCamera.near, this.renderCamera.far ); 292 | this.prepareMaskMaterial.uniforms[ "depthTexture" ].value = this.renderTargetDepthBuffer.texture; 293 | this.prepareMaskMaterial.uniforms[ "textureMatrix" ].value = this.textureMatrix; 294 | renderer.render( this.renderScene, this.renderCamera, this.renderTargetMaskBuffer, true ); 295 | this.renderScene.overrideMaterial = null; 296 | this.changeVisibilityOfNonSelectedObjects( true ); 297 | 298 | this.renderScene.background = currentBackground; 299 | 300 | // 2. Downsample to Half resolution 301 | this.quad.material = this.materialCopy; 302 | this.copyUniforms[ "tDiffuse" ].value = this.renderTargetMaskBuffer.texture; 303 | renderer.render( this.scene, this.camera, this.renderTargetMaskDownSampleBuffer, true ); 304 | 305 | this.tempPulseColor1.copy( this.visibleEdgeColor ); 306 | this.tempPulseColor2.copy( this.hiddenEdgeColor ); 307 | 308 | if ( this.pulsePeriod > 0 ) { 309 | 310 | var scalar = ( 1 + 0.25 ) / 2 + Math.cos( performance.now() * 0.01 / this.pulsePeriod ) * ( 1.0 - 0.25 ) / 2; 311 | this.tempPulseColor1.multiplyScalar( scalar ); 312 | this.tempPulseColor2.multiplyScalar( scalar ); 313 | 314 | } 315 | 316 | // 3. Apply Edge Detection Pass 317 | this.quad.material = this.edgeDetectionMaterial; 318 | this.edgeDetectionMaterial.uniforms[ "maskTexture" ].value = this.renderTargetMaskDownSampleBuffer.texture; 319 | this.edgeDetectionMaterial.uniforms[ "texSize" ].value = new THREE.Vector2( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height ); 320 | this.edgeDetectionMaterial.uniforms[ "visibleEdgeColor" ].value = this.tempPulseColor1; 321 | this.edgeDetectionMaterial.uniforms[ "hiddenEdgeColor" ].value = this.tempPulseColor2; 322 | renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer1, true ); 323 | 324 | // 4. Apply Blur on Half res 325 | this.quad.material = this.separableBlurMaterial1; 326 | this.separableBlurMaterial1.uniforms[ "colorTexture" ].value = this.renderTargetEdgeBuffer1.texture; 327 | this.separableBlurMaterial1.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionX; 328 | this.separableBlurMaterial1.uniforms[ "kernelRadius" ].value = this.edgeThickness; 329 | renderer.render( this.scene, this.camera, this.renderTargetBlurBuffer1, true ); 330 | this.separableBlurMaterial1.uniforms[ "colorTexture" ].value = this.renderTargetBlurBuffer1.texture; 331 | this.separableBlurMaterial1.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionY; 332 | renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer1, true ); 333 | 334 | // Apply Blur on quarter res 335 | this.quad.material = this.separableBlurMaterial2; 336 | this.separableBlurMaterial2.uniforms[ "colorTexture" ].value = this.renderTargetEdgeBuffer1.texture; 337 | this.separableBlurMaterial2.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionX; 338 | renderer.render( this.scene, this.camera, this.renderTargetBlurBuffer2, true ); 339 | this.separableBlurMaterial2.uniforms[ "colorTexture" ].value = this.renderTargetBlurBuffer2.texture; 340 | this.separableBlurMaterial2.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionY; 341 | renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer2, true ); 342 | 343 | // Blend it additively over the input texture 344 | this.quad.material = this.overlayMaterial; 345 | this.overlayMaterial.uniforms[ "maskTexture" ].value = this.renderTargetMaskBuffer.texture; 346 | this.overlayMaterial.uniforms[ "edgeTexture1" ].value = this.renderTargetEdgeBuffer1.texture; 347 | this.overlayMaterial.uniforms[ "edgeTexture2" ].value = this.renderTargetEdgeBuffer2.texture; 348 | this.overlayMaterial.uniforms[ "patternTexture" ].value = this.patternTexture; 349 | this.overlayMaterial.uniforms[ "edgeStrength" ].value = this.edgeStrength; 350 | this.overlayMaterial.uniforms[ "edgeGlow" ].value = this.edgeGlow; 351 | this.overlayMaterial.uniforms[ "usePatternTexture" ].value = this.usePatternTexture; 352 | 353 | 354 | if ( maskActive ) renderer.context.enable( renderer.context.STENCIL_TEST ); 355 | 356 | renderer.render( this.scene, this.camera, readBuffer, false ); 357 | 358 | renderer.setClearColor( this.oldClearColor, this.oldClearAlpha ); 359 | renderer.autoClear = oldAutoClear; 360 | 361 | } 362 | 363 | if ( this.renderToScreen ) { 364 | 365 | this.quad.material = this.materialCopy; 366 | this.copyUniforms[ "tDiffuse" ].value = readBuffer.texture; 367 | renderer.render( this.scene, this.camera ); 368 | 369 | } 370 | 371 | }, 372 | 373 | getPrepareMaskMaterial: function () { 374 | 375 | return new THREE.ShaderMaterial( { 376 | 377 | uniforms: { 378 | "depthTexture": { value: null }, 379 | "cameraNearFar": { value: new THREE.Vector2( 0.5, 0.5 ) }, 380 | "textureMatrix": { value: new THREE.Matrix4() } 381 | }, 382 | 383 | vertexShader: [ 384 | 'varying vec4 projTexCoord;', 385 | 'varying vec4 vPosition;', 386 | 'uniform mat4 textureMatrix;', 387 | 388 | 'void main() {', 389 | 390 | ' vPosition = modelViewMatrix * vec4( position, 1.0 );', 391 | ' vec4 worldPosition = modelMatrix * vec4( position, 1.0 );', 392 | ' projTexCoord = textureMatrix * worldPosition;', 393 | ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', 394 | 395 | '}' 396 | ].join( '\n' ), 397 | 398 | fragmentShader: [ 399 | '#include ', 400 | 'varying vec4 vPosition;', 401 | 'varying vec4 projTexCoord;', 402 | 'uniform sampler2D depthTexture;', 403 | 'uniform vec2 cameraNearFar;', 404 | 405 | 'void main() {', 406 | 407 | ' float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord ));', 408 | ' float viewZ = - DEPTH_TO_VIEW_Z( depth, cameraNearFar.x, cameraNearFar.y );', 409 | ' float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0;', 410 | ' gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0);', 411 | 412 | '}' 413 | ].join( '\n' ) 414 | 415 | } ); 416 | 417 | }, 418 | 419 | getEdgeDetectionMaterial: function () { 420 | 421 | return new THREE.ShaderMaterial( { 422 | 423 | uniforms: { 424 | "maskTexture": { value: null }, 425 | "texSize": { value: new THREE.Vector2( 0.5, 0.5 ) }, 426 | "visibleEdgeColor": { value: new THREE.Vector3( 1.0, 1.0, 1.0 ) }, 427 | "hiddenEdgeColor": { value: new THREE.Vector3( 1.0, 1.0, 1.0 ) }, 428 | }, 429 | 430 | vertexShader: 431 | "varying vec2 vUv;\n\ 432 | void main() {\n\ 433 | vUv = uv;\n\ 434 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 435 | }", 436 | 437 | fragmentShader: 438 | "varying vec2 vUv;\ 439 | uniform sampler2D maskTexture;\ 440 | uniform vec2 texSize;\ 441 | uniform vec3 visibleEdgeColor;\ 442 | uniform vec3 hiddenEdgeColor;\ 443 | \ 444 | void main() {\n\ 445 | vec2 invSize = 1.0 / texSize;\ 446 | vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize);\ 447 | vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy);\ 448 | vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy);\ 449 | vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw);\ 450 | vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw);\ 451 | float diff1 = (c1.r - c2.r)*0.5;\ 452 | float diff2 = (c3.r - c4.r)*0.5;\ 453 | float d = length( vec2(diff1, diff2) );\ 454 | float a1 = min(c1.g, c2.g);\ 455 | float a2 = min(c3.g, c4.g);\ 456 | float visibilityFactor = min(a1, a2);\ 457 | vec3 edgeColor = 1.0 - visibilityFactor > 0.001 ? visibleEdgeColor : hiddenEdgeColor;\ 458 | gl_FragColor = vec4(edgeColor, 1.0) * vec4(d);\ 459 | }" 460 | } ); 461 | 462 | }, 463 | 464 | getSeperableBlurMaterial: function ( maxRadius ) { 465 | 466 | return new THREE.ShaderMaterial( { 467 | 468 | defines: { 469 | "MAX_RADIUS": maxRadius, 470 | }, 471 | 472 | uniforms: { 473 | "colorTexture": { value: null }, 474 | "texSize": { value: new THREE.Vector2( 0.5, 0.5 ) }, 475 | "direction": { value: new THREE.Vector2( 0.5, 0.5 ) }, 476 | "kernelRadius": { value: 1.0 } 477 | }, 478 | 479 | vertexShader: 480 | "varying vec2 vUv;\n\ 481 | void main() {\n\ 482 | vUv = uv;\n\ 483 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 484 | }", 485 | 486 | fragmentShader: 487 | "#include \ 488 | varying vec2 vUv;\ 489 | uniform sampler2D colorTexture;\ 490 | uniform vec2 texSize;\ 491 | uniform vec2 direction;\ 492 | uniform float kernelRadius;\ 493 | \ 494 | float gaussianPdf(in float x, in float sigma) {\ 495 | return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;\ 496 | }\ 497 | void main() {\ 498 | vec2 invSize = 1.0 / texSize;\ 499 | float weightSum = gaussianPdf(0.0, kernelRadius);\ 500 | vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;\ 501 | vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS);\ 502 | vec2 uvOffset = delta;\ 503 | for( int i = 1; i <= MAX_RADIUS; i ++ ) {\ 504 | float w = gaussianPdf(uvOffset.x, kernelRadius);\ 505 | vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb;\ 506 | vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb;\ 507 | diffuseSum += ((sample1 + sample2) * w);\ 508 | weightSum += (2.0 * w);\ 509 | uvOffset += delta;\ 510 | }\ 511 | gl_FragColor = vec4(diffuseSum/weightSum, 1.0);\ 512 | }" 513 | } ); 514 | 515 | }, 516 | 517 | getOverlayMaterial: function () { 518 | 519 | return new THREE.ShaderMaterial( { 520 | 521 | uniforms: { 522 | "maskTexture": { value: null }, 523 | "edgeTexture1": { value: null }, 524 | "edgeTexture2": { value: null }, 525 | "patternTexture": { value: null }, 526 | "edgeStrength": { value: 1.0 }, 527 | "edgeGlow": { value: 1.0 }, 528 | "usePatternTexture": { value: 0.0 } 529 | }, 530 | 531 | vertexShader: 532 | "varying vec2 vUv;\n\ 533 | void main() {\n\ 534 | vUv = uv;\n\ 535 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 536 | }", 537 | 538 | fragmentShader: 539 | "varying vec2 vUv;\ 540 | uniform sampler2D maskTexture;\ 541 | uniform sampler2D edgeTexture1;\ 542 | uniform sampler2D edgeTexture2;\ 543 | uniform sampler2D patternTexture;\ 544 | uniform float edgeStrength;\ 545 | uniform float edgeGlow;\ 546 | uniform bool usePatternTexture;\ 547 | \ 548 | void main() {\ 549 | vec4 edgeValue1 = texture2D(edgeTexture1, vUv);\ 550 | vec4 edgeValue2 = texture2D(edgeTexture2, vUv);\ 551 | vec4 maskColor = texture2D(maskTexture, vUv);\ 552 | vec4 patternColor = texture2D(patternTexture, 6.0 * vUv);\ 553 | float visibilityFactor = 1.0 - maskColor.g > 0.0 ? 1.0 : 0.5;\ 554 | vec4 edgeValue = edgeValue1 + edgeValue2 * edgeGlow;\ 555 | vec4 finalColor = edgeStrength * maskColor.r * edgeValue;\ 556 | if(usePatternTexture)\ 557 | finalColor += + visibilityFactor * (1.0 - maskColor.r) * (1.0 - patternColor.r);\ 558 | gl_FragColor = finalColor;\ 559 | }", 560 | blending: THREE.AdditiveBlending, 561 | depthTest: false, 562 | depthWrite: false, 563 | transparent: true 564 | } ); 565 | 566 | } 567 | 568 | } ); 569 | 570 | THREE.OutlinePass.BlurDirectionX = new THREE.Vector2( 1.0, 0.0 ); 571 | THREE.OutlinePass.BlurDirectionY = new THREE.Vector2( 0.0, 1.0 ); 572 | -------------------------------------------------------------------------------- /webgl_lightningstrike.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | three.js webgl - lightning strike 5 | 6 | 7 | 34 | 35 | 36 | 37 |
38 |
three.js webgl - lightning strike
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 832 | 833 | 834 | 835 | -------------------------------------------------------------------------------- /js/controls/OrbitControls.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 left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move 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.panSpeed = 1.0; 63 | this.screenSpacePanning = false; // if true, pan in screen-space 64 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 65 | 66 | // Set to true to automatically rotate around the target 67 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 68 | this.autoRotate = false; 69 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 70 | 71 | // Set to false to disable use of the keys 72 | this.enableKeys = true; 73 | 74 | // The four arrow keys 75 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 76 | 77 | // Mouse buttons 78 | this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT }; 79 | 80 | // for reset 81 | this.target0 = this.target.clone(); 82 | this.position0 = this.object.position.clone(); 83 | this.zoom0 = this.object.zoom; 84 | 85 | // 86 | // public methods 87 | // 88 | 89 | this.getPolarAngle = function () { 90 | 91 | return spherical.phi; 92 | 93 | }; 94 | 95 | this.getAzimuthalAngle = function () { 96 | 97 | return spherical.theta; 98 | 99 | }; 100 | 101 | this.saveState = function () { 102 | 103 | scope.target0.copy( scope.target ); 104 | scope.position0.copy( scope.object.position ); 105 | scope.zoom0 = scope.object.zoom; 106 | 107 | }; 108 | 109 | this.reset = function () { 110 | 111 | scope.target.copy( scope.target0 ); 112 | scope.object.position.copy( scope.position0 ); 113 | scope.object.zoom = scope.zoom0; 114 | 115 | scope.object.updateProjectionMatrix(); 116 | scope.dispatchEvent( changeEvent ); 117 | 118 | scope.update(); 119 | 120 | state = STATE.NONE; 121 | 122 | }; 123 | 124 | // this method is exposed, but perhaps it would be better if we can make it private... 125 | this.update = function () { 126 | 127 | var offset = new THREE.Vector3(); 128 | 129 | // so camera.up is the orbit axis 130 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 131 | var quatInverse = quat.clone().inverse(); 132 | 133 | var lastPosition = new THREE.Vector3(); 134 | var lastQuaternion = new THREE.Quaternion(); 135 | 136 | return function update() { 137 | 138 | var position = scope.object.position; 139 | 140 | offset.copy( position ).sub( scope.target ); 141 | 142 | // rotate offset to "y-axis-is-up" space 143 | offset.applyQuaternion( quat ); 144 | 145 | // angle from z-axis around y-axis 146 | spherical.setFromVector3( offset ); 147 | 148 | if ( scope.autoRotate && state === STATE.NONE ) { 149 | 150 | rotateLeft( getAutoRotationAngle() ); 151 | 152 | } 153 | 154 | spherical.theta += sphericalDelta.theta; 155 | spherical.phi += sphericalDelta.phi; 156 | 157 | // restrict theta to be between desired limits 158 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 159 | 160 | // restrict phi to be between desired limits 161 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 162 | 163 | spherical.makeSafe(); 164 | 165 | 166 | spherical.radius *= scale; 167 | 168 | // restrict radius to be between desired limits 169 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 170 | 171 | // move target to panned location 172 | scope.target.add( panOffset ); 173 | 174 | offset.setFromSpherical( spherical ); 175 | 176 | // rotate offset back to "camera-up-vector-is-up" space 177 | offset.applyQuaternion( quatInverse ); 178 | 179 | position.copy( scope.target ).add( offset ); 180 | 181 | scope.object.lookAt( scope.target ); 182 | 183 | if ( scope.enableDamping === true ) { 184 | 185 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 186 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 187 | 188 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 189 | 190 | } else { 191 | 192 | sphericalDelta.set( 0, 0, 0 ); 193 | 194 | panOffset.set( 0, 0, 0 ); 195 | 196 | } 197 | 198 | scale = 1; 199 | 200 | // update condition is: 201 | // min(camera displacement, camera rotation in radians)^2 > EPS 202 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 203 | 204 | if ( zoomChanged || 205 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 206 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 207 | 208 | scope.dispatchEvent( changeEvent ); 209 | 210 | lastPosition.copy( scope.object.position ); 211 | lastQuaternion.copy( scope.object.quaternion ); 212 | zoomChanged = false; 213 | 214 | return true; 215 | 216 | } 217 | 218 | return false; 219 | 220 | }; 221 | 222 | }(); 223 | 224 | this.dispose = function () { 225 | 226 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 227 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 228 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 229 | 230 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 231 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 232 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 233 | 234 | document.removeEventListener( 'mousemove', onMouseMove, false ); 235 | document.removeEventListener( 'mouseup', onMouseUp, false ); 236 | 237 | window.removeEventListener( 'keydown', onKeyDown, false ); 238 | 239 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 240 | 241 | }; 242 | 243 | // 244 | // internals 245 | // 246 | 247 | var scope = this; 248 | 249 | var changeEvent = { type: 'change' }; 250 | var startEvent = { type: 'start' }; 251 | var endEvent = { type: 'end' }; 252 | 253 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 }; 254 | 255 | var state = STATE.NONE; 256 | 257 | var EPS = 0.000001; 258 | 259 | // current position in spherical coordinates 260 | var spherical = new THREE.Spherical(); 261 | var sphericalDelta = new THREE.Spherical(); 262 | 263 | var scale = 1; 264 | var panOffset = new THREE.Vector3(); 265 | var zoomChanged = false; 266 | 267 | var rotateStart = new THREE.Vector2(); 268 | var rotateEnd = new THREE.Vector2(); 269 | var rotateDelta = new THREE.Vector2(); 270 | 271 | var panStart = new THREE.Vector2(); 272 | var panEnd = new THREE.Vector2(); 273 | var panDelta = new THREE.Vector2(); 274 | 275 | var dollyStart = new THREE.Vector2(); 276 | var dollyEnd = new THREE.Vector2(); 277 | var dollyDelta = new THREE.Vector2(); 278 | 279 | function getAutoRotationAngle() { 280 | 281 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 282 | 283 | } 284 | 285 | function getZoomScale() { 286 | 287 | return Math.pow( 0.95, scope.zoomSpeed ); 288 | 289 | } 290 | 291 | function rotateLeft( angle ) { 292 | 293 | sphericalDelta.theta -= angle; 294 | 295 | } 296 | 297 | function rotateUp( angle ) { 298 | 299 | sphericalDelta.phi -= angle; 300 | 301 | } 302 | 303 | var panLeft = function () { 304 | 305 | var v = new THREE.Vector3(); 306 | 307 | return function panLeft( distance, objectMatrix ) { 308 | 309 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 310 | v.multiplyScalar( - distance ); 311 | 312 | panOffset.add( v ); 313 | 314 | }; 315 | 316 | }(); 317 | 318 | var panUp = function () { 319 | 320 | var v = new THREE.Vector3(); 321 | 322 | return function panUp( distance, objectMatrix ) { 323 | 324 | if ( scope.screenSpacePanning === true ) { 325 | 326 | v.setFromMatrixColumn( objectMatrix, 1 ); 327 | 328 | } else { 329 | 330 | v.setFromMatrixColumn( objectMatrix, 0 ); 331 | v.crossVectors( scope.object.up, v ); 332 | 333 | } 334 | 335 | v.multiplyScalar( distance ); 336 | 337 | panOffset.add( v ); 338 | 339 | }; 340 | 341 | }(); 342 | 343 | // deltaX and deltaY are in pixels; right and down are positive 344 | var pan = function () { 345 | 346 | var offset = new THREE.Vector3(); 347 | 348 | return function pan( deltaX, deltaY ) { 349 | 350 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 351 | 352 | if ( scope.object.isPerspectiveCamera ) { 353 | 354 | // perspective 355 | var position = scope.object.position; 356 | offset.copy( position ).sub( scope.target ); 357 | var targetDistance = offset.length(); 358 | 359 | // half of the fov is center to top of screen 360 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 361 | 362 | // we use only clientHeight here so aspect ratio does not distort speed 363 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 364 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 365 | 366 | } else if ( scope.object.isOrthographicCamera ) { 367 | 368 | // orthographic 369 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 370 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 371 | 372 | } else { 373 | 374 | // camera neither orthographic nor perspective 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 376 | scope.enablePan = false; 377 | 378 | } 379 | 380 | }; 381 | 382 | }(); 383 | 384 | function dollyIn( dollyScale ) { 385 | 386 | if ( scope.object.isPerspectiveCamera ) { 387 | 388 | scale /= dollyScale; 389 | 390 | } else if ( scope.object.isOrthographicCamera ) { 391 | 392 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 393 | scope.object.updateProjectionMatrix(); 394 | zoomChanged = true; 395 | 396 | } else { 397 | 398 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 399 | scope.enableZoom = false; 400 | 401 | } 402 | 403 | } 404 | 405 | function dollyOut( dollyScale ) { 406 | 407 | if ( scope.object.isPerspectiveCamera ) { 408 | 409 | scale *= dollyScale; 410 | 411 | } else if ( scope.object.isOrthographicCamera ) { 412 | 413 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 414 | scope.object.updateProjectionMatrix(); 415 | zoomChanged = true; 416 | 417 | } else { 418 | 419 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 420 | scope.enableZoom = false; 421 | 422 | } 423 | 424 | } 425 | 426 | // 427 | // event callbacks - update the object state 428 | // 429 | 430 | function handleMouseDownRotate( event ) { 431 | 432 | //console.log( 'handleMouseDownRotate' ); 433 | 434 | rotateStart.set( event.clientX, event.clientY ); 435 | 436 | } 437 | 438 | function handleMouseDownDolly( event ) { 439 | 440 | //console.log( 'handleMouseDownDolly' ); 441 | 442 | dollyStart.set( event.clientX, event.clientY ); 443 | 444 | } 445 | 446 | function handleMouseDownPan( event ) { 447 | 448 | //console.log( 'handleMouseDownPan' ); 449 | 450 | panStart.set( event.clientX, event.clientY ); 451 | 452 | } 453 | 454 | function handleMouseMoveRotate( event ) { 455 | 456 | //console.log( 'handleMouseMoveRotate' ); 457 | 458 | rotateEnd.set( event.clientX, event.clientY ); 459 | 460 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 461 | 462 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 463 | 464 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 465 | 466 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 467 | 468 | rotateStart.copy( rotateEnd ); 469 | 470 | scope.update(); 471 | 472 | } 473 | 474 | function handleMouseMoveDolly( event ) { 475 | 476 | //console.log( 'handleMouseMoveDolly' ); 477 | 478 | dollyEnd.set( event.clientX, event.clientY ); 479 | 480 | dollyDelta.subVectors( dollyEnd, dollyStart ); 481 | 482 | if ( dollyDelta.y > 0 ) { 483 | 484 | dollyIn( getZoomScale() ); 485 | 486 | } else if ( dollyDelta.y < 0 ) { 487 | 488 | dollyOut( getZoomScale() ); 489 | 490 | } 491 | 492 | dollyStart.copy( dollyEnd ); 493 | 494 | scope.update(); 495 | 496 | } 497 | 498 | function handleMouseMovePan( event ) { 499 | 500 | //console.log( 'handleMouseMovePan' ); 501 | 502 | panEnd.set( event.clientX, event.clientY ); 503 | 504 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 505 | 506 | pan( panDelta.x, panDelta.y ); 507 | 508 | panStart.copy( panEnd ); 509 | 510 | scope.update(); 511 | 512 | } 513 | 514 | function handleMouseUp( event ) { 515 | 516 | // console.log( 'handleMouseUp' ); 517 | 518 | } 519 | 520 | function handleMouseWheel( event ) { 521 | 522 | // console.log( 'handleMouseWheel' ); 523 | 524 | if ( event.deltaY < 0 ) { 525 | 526 | dollyOut( getZoomScale() ); 527 | 528 | } else if ( event.deltaY > 0 ) { 529 | 530 | dollyIn( getZoomScale() ); 531 | 532 | } 533 | 534 | scope.update(); 535 | 536 | } 537 | 538 | function handleKeyDown( event ) { 539 | 540 | //console.log( 'handleKeyDown' ); 541 | 542 | switch ( event.keyCode ) { 543 | 544 | case scope.keys.UP: 545 | pan( 0, scope.keyPanSpeed ); 546 | scope.update(); 547 | break; 548 | 549 | case scope.keys.BOTTOM: 550 | pan( 0, - scope.keyPanSpeed ); 551 | scope.update(); 552 | break; 553 | 554 | case scope.keys.LEFT: 555 | pan( scope.keyPanSpeed, 0 ); 556 | scope.update(); 557 | break; 558 | 559 | case scope.keys.RIGHT: 560 | pan( - scope.keyPanSpeed, 0 ); 561 | scope.update(); 562 | break; 563 | 564 | } 565 | 566 | } 567 | 568 | function handleTouchStartRotate( event ) { 569 | 570 | //console.log( 'handleTouchStartRotate' ); 571 | 572 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 573 | 574 | } 575 | 576 | function handleTouchStartDollyPan( event ) { 577 | 578 | //console.log( 'handleTouchStartDollyPan' ); 579 | 580 | if ( scope.enableZoom ) { 581 | 582 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 583 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 584 | 585 | var distance = Math.sqrt( dx * dx + dy * dy ); 586 | 587 | dollyStart.set( 0, distance ); 588 | 589 | } 590 | 591 | if ( scope.enablePan ) { 592 | 593 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 594 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 595 | 596 | panStart.set( x, y ); 597 | 598 | } 599 | 600 | } 601 | 602 | function handleTouchMoveRotate( event ) { 603 | 604 | //console.log( 'handleTouchMoveRotate' ); 605 | 606 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 607 | 608 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 609 | 610 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 611 | 612 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 613 | 614 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 615 | 616 | rotateStart.copy( rotateEnd ); 617 | 618 | scope.update(); 619 | 620 | } 621 | 622 | function handleTouchMoveDollyPan( event ) { 623 | 624 | //console.log( 'handleTouchMoveDollyPan' ); 625 | 626 | if ( scope.enableZoom ) { 627 | 628 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 629 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 630 | 631 | var distance = Math.sqrt( dx * dx + dy * dy ); 632 | 633 | dollyEnd.set( 0, distance ); 634 | 635 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 636 | 637 | dollyIn( dollyDelta.y ); 638 | 639 | dollyStart.copy( dollyEnd ); 640 | 641 | } 642 | 643 | if ( scope.enablePan ) { 644 | 645 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 646 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 647 | 648 | panEnd.set( x, y ); 649 | 650 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 651 | 652 | pan( panDelta.x, panDelta.y ); 653 | 654 | panStart.copy( panEnd ); 655 | 656 | } 657 | 658 | scope.update(); 659 | 660 | } 661 | 662 | function handleTouchEnd( event ) { 663 | 664 | //console.log( 'handleTouchEnd' ); 665 | 666 | } 667 | 668 | // 669 | // event handlers - FSM: listen for events and reset state 670 | // 671 | 672 | function onMouseDown( event ) { 673 | 674 | if ( scope.enabled === false ) return; 675 | 676 | event.preventDefault(); 677 | 678 | switch ( event.button ) { 679 | 680 | case scope.mouseButtons.LEFT: 681 | 682 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 683 | 684 | if ( scope.enablePan === false ) return; 685 | 686 | handleMouseDownPan( event ); 687 | 688 | state = STATE.PAN; 689 | 690 | } else { 691 | 692 | if ( scope.enableRotate === false ) return; 693 | 694 | handleMouseDownRotate( event ); 695 | 696 | state = STATE.ROTATE; 697 | 698 | } 699 | 700 | break; 701 | 702 | case scope.mouseButtons.MIDDLE: 703 | 704 | if ( scope.enableZoom === false ) return; 705 | 706 | handleMouseDownDolly( event ); 707 | 708 | state = STATE.DOLLY; 709 | 710 | break; 711 | 712 | case scope.mouseButtons.RIGHT: 713 | 714 | if ( scope.enablePan === false ) return; 715 | 716 | handleMouseDownPan( event ); 717 | 718 | state = STATE.PAN; 719 | 720 | break; 721 | 722 | } 723 | 724 | if ( state !== STATE.NONE ) { 725 | 726 | document.addEventListener( 'mousemove', onMouseMove, false ); 727 | document.addEventListener( 'mouseup', onMouseUp, false ); 728 | 729 | scope.dispatchEvent( startEvent ); 730 | 731 | } 732 | 733 | } 734 | 735 | function onMouseMove( event ) { 736 | 737 | if ( scope.enabled === false ) return; 738 | 739 | event.preventDefault(); 740 | 741 | switch ( state ) { 742 | 743 | case STATE.ROTATE: 744 | 745 | if ( scope.enableRotate === false ) return; 746 | 747 | handleMouseMoveRotate( event ); 748 | 749 | break; 750 | 751 | case STATE.DOLLY: 752 | 753 | if ( scope.enableZoom === false ) return; 754 | 755 | handleMouseMoveDolly( event ); 756 | 757 | break; 758 | 759 | case STATE.PAN: 760 | 761 | if ( scope.enablePan === false ) return; 762 | 763 | handleMouseMovePan( event ); 764 | 765 | break; 766 | 767 | } 768 | 769 | } 770 | 771 | function onMouseUp( event ) { 772 | 773 | if ( scope.enabled === false ) return; 774 | 775 | handleMouseUp( event ); 776 | 777 | document.removeEventListener( 'mousemove', onMouseMove, false ); 778 | document.removeEventListener( 'mouseup', onMouseUp, false ); 779 | 780 | scope.dispatchEvent( endEvent ); 781 | 782 | state = STATE.NONE; 783 | 784 | } 785 | 786 | function onMouseWheel( event ) { 787 | 788 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 789 | 790 | event.preventDefault(); 791 | event.stopPropagation(); 792 | 793 | scope.dispatchEvent( startEvent ); 794 | 795 | handleMouseWheel( event ); 796 | 797 | scope.dispatchEvent( endEvent ); 798 | 799 | } 800 | 801 | function onKeyDown( event ) { 802 | 803 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 804 | 805 | handleKeyDown( event ); 806 | 807 | } 808 | 809 | function onTouchStart( event ) { 810 | 811 | if ( scope.enabled === false ) return; 812 | 813 | event.preventDefault(); 814 | 815 | switch ( event.touches.length ) { 816 | 817 | case 1: // one-fingered touch: rotate 818 | 819 | if ( scope.enableRotate === false ) return; 820 | 821 | handleTouchStartRotate( event ); 822 | 823 | state = STATE.TOUCH_ROTATE; 824 | 825 | break; 826 | 827 | case 2: // two-fingered touch: dolly-pan 828 | 829 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 830 | 831 | handleTouchStartDollyPan( event ); 832 | 833 | state = STATE.TOUCH_DOLLY_PAN; 834 | 835 | break; 836 | 837 | default: 838 | 839 | state = STATE.NONE; 840 | 841 | } 842 | 843 | if ( state !== STATE.NONE ) { 844 | 845 | scope.dispatchEvent( startEvent ); 846 | 847 | } 848 | 849 | } 850 | 851 | function onTouchMove( event ) { 852 | 853 | if ( scope.enabled === false ) return; 854 | 855 | event.preventDefault(); 856 | event.stopPropagation(); 857 | 858 | switch ( event.touches.length ) { 859 | 860 | case 1: // one-fingered touch: rotate 861 | 862 | if ( scope.enableRotate === false ) return; 863 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed? 864 | 865 | handleTouchMoveRotate( event ); 866 | 867 | break; 868 | 869 | case 2: // two-fingered touch: dolly-pan 870 | 871 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 872 | if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed? 873 | 874 | handleTouchMoveDollyPan( event ); 875 | 876 | break; 877 | 878 | default: 879 | 880 | state = STATE.NONE; 881 | 882 | } 883 | 884 | } 885 | 886 | function onTouchEnd( event ) { 887 | 888 | if ( scope.enabled === false ) return; 889 | 890 | handleTouchEnd( event ); 891 | 892 | scope.dispatchEvent( endEvent ); 893 | 894 | state = STATE.NONE; 895 | 896 | } 897 | 898 | function onContextMenu( event ) { 899 | 900 | if ( scope.enabled === false ) return; 901 | 902 | event.preventDefault(); 903 | 904 | } 905 | 906 | // 907 | 908 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 909 | 910 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 911 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 912 | 913 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 914 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 915 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 916 | 917 | window.addEventListener( 'keydown', onKeyDown, false ); 918 | 919 | // force an update at start 920 | 921 | this.update(); 922 | 923 | }; 924 | 925 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 926 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 927 | 928 | Object.defineProperties( THREE.OrbitControls.prototype, { 929 | 930 | center: { 931 | 932 | get: function () { 933 | 934 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 935 | return this.target; 936 | 937 | } 938 | 939 | }, 940 | 941 | // backward compatibility 942 | 943 | noZoom: { 944 | 945 | get: function () { 946 | 947 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 948 | return ! this.enableZoom; 949 | 950 | }, 951 | 952 | set: function ( value ) { 953 | 954 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 955 | this.enableZoom = ! value; 956 | 957 | } 958 | 959 | }, 960 | 961 | noRotate: { 962 | 963 | get: function () { 964 | 965 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 966 | return ! this.enableRotate; 967 | 968 | }, 969 | 970 | set: function ( value ) { 971 | 972 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 973 | this.enableRotate = ! value; 974 | 975 | } 976 | 977 | }, 978 | 979 | noPan: { 980 | 981 | get: function () { 982 | 983 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 984 | return ! this.enablePan; 985 | 986 | }, 987 | 988 | set: function ( value ) { 989 | 990 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 991 | this.enablePan = ! value; 992 | 993 | } 994 | 995 | }, 996 | 997 | noKeys: { 998 | 999 | get: function () { 1000 | 1001 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1002 | return ! this.enableKeys; 1003 | 1004 | }, 1005 | 1006 | set: function ( value ) { 1007 | 1008 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1009 | this.enableKeys = ! value; 1010 | 1011 | } 1012 | 1013 | }, 1014 | 1015 | staticMoving: { 1016 | 1017 | get: function () { 1018 | 1019 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1020 | return ! this.enableDamping; 1021 | 1022 | }, 1023 | 1024 | set: function ( value ) { 1025 | 1026 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1027 | this.enableDamping = ! value; 1028 | 1029 | } 1030 | 1031 | }, 1032 | 1033 | dynamicDampingFactor: { 1034 | 1035 | get: function () { 1036 | 1037 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1038 | return this.dampingFactor; 1039 | 1040 | }, 1041 | 1042 | set: function ( value ) { 1043 | 1044 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1045 | this.dampingFactor = value; 1046 | 1047 | } 1048 | 1049 | } 1050 | 1051 | } ); 1052 | -------------------------------------------------------------------------------- /js/geometries/LightningStrike.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author yomboprime https://github.com/yomboprime 3 | * 4 | * @fileoverview LightningStrike object for creating lightning strikes and voltaic arcs. 5 | * 6 | * 7 | * Usage 8 | * 9 | * var myRay = new THREE.LightningStrike( paramsObject ); 10 | * var myRayMesh = new THREE.Mesh( myRay, myMaterial ); 11 | * scene.add( myRayMesh ); 12 | * ... 13 | * myRay.update( currentTime ); 14 | * 15 | * The "currentTime" can vary its rate, go forwards, backwards or even jump, but it cannot be negative. 16 | * 17 | * You should normally leave the ray position to (0, 0, 0). You should control it by changing the sourceOffset and destOffset parameters. 18 | * 19 | * 20 | * LightningStrike parameters 21 | * 22 | * The paramsObject can contain any of the following parameters. 23 | * 24 | * Legend: 25 | * 'LightningStrike' (also called 'ray'): An independent voltaic arc with its ramifications and defined with a set of parameters. 26 | * 'Subray': A ramification of the ray. It is not a LightningStrike object. 27 | * 'Segment': A linear segment piece of a subray. 28 | * 'Leaf segment': A ray segment which cannot be smaller. 29 | * 30 | * 31 | * The following parameters can be changed any time and if they vary smoothly, the ray form will also change smoothly: 32 | * 33 | * @param {Vector3} sourceOffset The point where the ray starts. 34 | * 35 | * @param {Vector3} destOffset The point where the ray ends. 36 | * 37 | * @param {double} timeScale The rate at wich the ray form changes in time. Default: 1 38 | * 39 | * @param {double} roughness From 0 to 1. The higher the value, the more wrinkled is the ray. Default: 0.9 40 | * 41 | * @param {double} straightness From 0 to 1. The higher the value, the more straight will be a subray path. Default: 0.7 42 | * 43 | * @param {Vector3} up0 Ray 'up' direction at the ray starting point. Must be normalized. It should be perpendicular to the ray forward direction but it doesn't matter much. 44 | * 45 | * @param {Vector3} up1 Like the up0 parameter but at the end of the ray. Must be normalized. 46 | * 47 | * @param {double} radius0 Radius of the main ray trunk at the start point. Default: 1 48 | * 49 | * @param {double} radius1 Radius of the main ray trunk at the end point. Default: 1 50 | * 51 | * @param {double} radius0Factor The radius0 of a subray is this factor times the radius0 of its parent subray. Default: 0.5 52 | * 53 | * @param {double} radius1Factor The radius1 of a subray is this factor times the radius1 of its parent subray. Default: 0.2 54 | * 55 | * @param {minRadius} Minimum value a subray radius0 or radius1 can get. Default: 0.1 56 | * 57 | * 58 | * The following parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: 59 | * 60 | * @param {boolean} isEternal If true the ray never extinguishes. Otherwise its life is controlled by the 'birthTime' and 'deathTime' parameters. Default: true if any of those two parameters is undefined. 61 | * 62 | * @param {double} birthTime The time at which the ray starts its life and begins propagating. Only if isEternal is false. Default: None. 63 | * 64 | * @param {double} deathTime The time at which the ray ends vanishing and its life. Only if isEternal is false. Default: None. 65 | * 66 | * @param {double} propagationTimeFactor From 0 to 1. Lifetime factor at which the ray ends propagating and enters the steady phase. For example, 0.1 means it is propagating 1/10 of its lifetime. Default: 0.1 67 | * 68 | * @param {double} vanishingTimeFactor From 0 to 1. Lifetime factor at which the ray ends the steady phase and begins vanishing. For example, 0.9 means it is vanishing 1/10 of its lifetime. Default: 0.9 69 | * 70 | * @param {double} subrayPeriod Subrays cycle periodically. This is their time period. Default: 4 71 | * 72 | * @param {double} subrayDutyCycle From 0 to 1. This is the fraction of time a subray is active. Default: 0.6 73 | * 74 | * 75 | * These parameters cannot change after lightning creation: 76 | * 77 | * @param {integer} maxIterations: Greater than 0. The number of ray's leaf segments is 2**maxIterations. Default: 9 78 | * 79 | * @param {boolean} isStatic Set to true only for rays which won't change over time and are not attached to moving objects (Rare case). It is used to set the vertex buffers non-dynamic. You can omit calling update() for these rays. 80 | * 81 | * @param {integer} ramification Greater than 0. Maximum number of child subrays a subray can have. Default: 5 82 | * 83 | * @param {integer} maxSubrayRecursion Greater than 0. Maximum level of recursion (subray descendant generations). Default: 3 84 | * 85 | * @param {double} recursionProbability From 0 to 1. The lower the value, the less chance each new generation of subrays has to generate new subrays. Default: 0.6 86 | * 87 | * @param {boolean} generateUVs If true, the ray geometry will have uv coordinates generated. u runs along the ray, and v across its perimeter. Default: false. 88 | * 89 | * @param {Object} randomGenerator Set here your random number generator which will seed the SimplexNoise and other decisions during ray tree creation. 90 | * It can be used to generate repeatable rays. For that, set also the noiseSeed parameter, and each ray created with that generator and seed pair will be identical in time. 91 | * The randomGenerator parameter should be an object with a random() function similar to Math.random, but seedable. 92 | * It must have also a getSeed() method, which returns the current seed, and a setSeed( seed ) method, which accepts as seed a fractional number from 0 to 1, as well as any other number. 93 | * The default value is an internal generator for some uses and Math.random for others (It is non-repeatable even if noiseSeed is supplied) 94 | * 95 | * @param {double} noiseSeed Seed used to make repeatable rays (see the randomGenerator) 96 | * 97 | * @param {function} onDecideSubrayCreation Set this to change the callback which decides subray creation. You can look at the default callback in the code (createDefaultSubrayCreationCallbacks)for more info. 98 | * 99 | * @param {function} onSubrayCreation This is another callback, more simple than the previous one. It can be used to adapt the form of subrays or other parameters once a subray has been created and initialized. It is used in the examples to adapt subrays to a sphere or to a plane. 100 | * 101 | * 102 | */ 103 | 104 | THREE.LightningStrike = function ( rayParameters ) { 105 | 106 | THREE.BufferGeometry.call( this ); 107 | 108 | this.type = 'LightningStrike'; 109 | 110 | // Set parameters, and set undefined parameters to default values 111 | rayParameters = rayParameters || {}; 112 | this.init( THREE.LightningStrike.copyParameters( rayParameters, rayParameters ) ); 113 | 114 | // Creates and populates the mesh 115 | this.createMesh(); 116 | 117 | }; 118 | 119 | THREE.LightningStrike.prototype = Object.create( THREE.BufferGeometry.prototype ); 120 | 121 | THREE.LightningStrike.prototype.constructor = THREE.LightningStrike; 122 | 123 | THREE.LightningStrike.prototype.isLightningStrike = true; 124 | 125 | // Ray states 126 | THREE.LightningStrike.RAY_INITIALIZED = 0; 127 | THREE.LightningStrike.RAY_UNBORN = 1; 128 | THREE.LightningStrike.RAY_PROPAGATING = 2; 129 | THREE.LightningStrike.RAY_STEADY = 3; 130 | THREE.LightningStrike.RAY_VANISHING = 4; 131 | THREE.LightningStrike.RAY_EXTINGUISHED = 5; 132 | 133 | THREE.LightningStrike.COS30DEG = Math.cos( 30 * Math.PI / 180 ); 134 | THREE.LightningStrike.SIN30DEG = Math.sin( 30 * Math.PI / 180 ); 135 | 136 | THREE.LightningStrike.createRandomGenerator = function () { 137 | 138 | var numSeeds = 2053; 139 | var seeds = []; 140 | 141 | for ( var i = 0; i < numSeeds; i++ ) { 142 | 143 | seeds.push( Math.random() ); 144 | 145 | } 146 | 147 | var generator = { 148 | 149 | currentSeed: 0, 150 | 151 | random: function () { 152 | 153 | var value = seeds[ generator.currentSeed ]; 154 | 155 | generator.currentSeed = ( generator.currentSeed + 1 ) % numSeeds; 156 | 157 | return value; 158 | 159 | }, 160 | 161 | getSeed: function () { 162 | 163 | return generator.currentSeed / numSeeds; 164 | 165 | }, 166 | 167 | setSeed: function ( seed ) { 168 | 169 | generator.currentSeed = Math.floor( seed * numSeeds ) % numSeeds; 170 | 171 | } 172 | 173 | }; 174 | 175 | return generator; 176 | 177 | }; 178 | 179 | THREE.LightningStrike.copyParameters = function ( dest, source) { 180 | 181 | source = source || {}; 182 | dest = dest || {}; 183 | 184 | var vecCopy = function( v ) { 185 | 186 | if ( source === dest ) { 187 | 188 | return v; 189 | 190 | } 191 | else { 192 | 193 | return v.clone(); 194 | 195 | } 196 | 197 | } 198 | 199 | dest.sourceOffset = source.sourceOffset !== undefined ? vecCopy( source.sourceOffset ) : new THREE.Vector3( 0, 100, 0 ), 200 | dest.destOffset = source.destOffset !== undefined ? vecCopy( source.destOffset ) : new THREE.Vector3( 0, 0, 0 ), 201 | 202 | dest.timeScale = source.timeScale !== undefined ? source.timeScale : 1, 203 | dest.roughness = source.roughness !== undefined ? source.roughness : 0.9, 204 | dest.straightness = source.straightness !== undefined ? source.straightness : 0.7, 205 | 206 | dest.up0 = source.up0 !== undefined ? vecCopy( source.up0 ) : new THREE.Vector3( 0, 0, 1 ); 207 | dest.up1 = source.up1 !== undefined ? vecCopy( source.up1 ) : new THREE.Vector3( 0, 0, 1 ), 208 | dest.radius0 = source.radius0 !== undefined ? source.radius0 : 1, 209 | dest.radius1 = source.radius1 !== undefined ? source.radius1 : 1, 210 | dest.radius0Factor = source.radius0Factor !== undefined ? source.radius0Factor : 0.5, 211 | dest.radius1Factor = source.radius1Factor !== undefined ? source.radius1Factor : 0.2, 212 | dest.minRadius = source.minRadius !== undefined ? source.minRadius : 0.2, 213 | 214 | // These parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: 215 | 216 | dest.isEternal = source.isEternal !== undefined ? source.isEternal : ( source.birthTime === undefined || source.deathTime === undefined ), 217 | dest.birthTime = source.birthTime, 218 | dest.deathTime = source.deathTime, 219 | dest.propagationTimeFactor = source.propagationTimeFactor !== undefined ? source.propagationTimeFactor : 0.1, 220 | dest.vanishingTimeFactor = source.vanishingTimeFactor !== undefined ? source.vanishingTimeFactor : 0.9, 221 | dest.subrayPeriod = source.subrayPeriod !== undefined ? source.subrayPeriod : 4, 222 | dest.subrayDutyCycle = source.subrayDutyCycle !== undefined ? source.subrayDutyCycle : 0.6; 223 | 224 | // These parameters cannot change after lightning creation: 225 | 226 | dest.maxIterations = source.maxIterations !== undefined ? source.maxIterations : 9; 227 | dest.isStatic = source.isStatic !== undefined ? source.isStatic : false; 228 | dest.ramification = source.ramification !== undefined ? source.ramification : 5; 229 | dest.maxSubrayRecursion = source.maxSubrayRecursion !== undefined ? source.maxSubrayRecursion : 3; 230 | dest.recursionProbability = source.recursionProbability !== undefined ? source.recursionProbability : 0.6; 231 | dest.generateUVs = source.generateUVs !== undefined ? source.generateUVs : false; 232 | dest.randomGenerator = source.randomGenerator, 233 | dest.noiseSeed = source.noiseSeed, 234 | dest.onDecideSubrayCreation = source.onDecideSubrayCreation, 235 | dest.onSubrayCreation = source.onSubrayCreation; 236 | 237 | return dest; 238 | 239 | }; 240 | 241 | THREE.LightningStrike.prototype.update = function ( time ) { 242 | 243 | if ( this.isStatic ) { 244 | return; 245 | } 246 | 247 | if ( this.rayParameters.isEternal || ( this.rayParameters.birthTime <= time && time <= this.rayParameters.deathTime ) ) { 248 | 249 | this.updateMesh( time ); 250 | 251 | if ( time < this.subrays[ 0 ].endPropagationTime ) { 252 | 253 | this.state = THREE.LightningStrike.RAY_PROPAGATING; 254 | 255 | } 256 | else if ( time > this.subrays[ 0 ].beginVanishingTime ) { 257 | 258 | this.state = THREE.LightningStrike.RAY_VANISHING; 259 | 260 | } 261 | else { 262 | 263 | this.state = THREE.LightningStrike.RAY_STEADY; 264 | 265 | } 266 | 267 | this.visible = true; 268 | 269 | } 270 | else { 271 | 272 | this.visible = false; 273 | 274 | if ( time < this.rayParameters.birthTime ) { 275 | 276 | this.state = THREE.LightningStrike.RAY_UNBORN; 277 | 278 | } 279 | else { 280 | 281 | this.state = THREE.LightningStrike.RAY_EXTINGUISHED; 282 | 283 | } 284 | 285 | } 286 | 287 | }; 288 | 289 | THREE.LightningStrike.prototype.init = function ( rayParameters ) { 290 | 291 | // Init all the state from the parameters 292 | 293 | this.rayParameters = rayParameters; 294 | 295 | // These parameters cannot change after lightning creation: 296 | 297 | this.maxIterations = rayParameters.maxIterations !== undefined ? Math.floor( rayParameters.maxIterations ) : 9; 298 | rayParameters.maxIterations = this.maxIterations; 299 | this.isStatic = rayParameters.isStatic !== undefined ? rayParameters.isStatic : false; 300 | rayParameters.isStatic = this.isStatic; 301 | this.ramification = rayParameters.ramification !== undefined ? Math.floor( rayParameters.ramification ) : 5; 302 | rayParameters.ramification = this.ramification; 303 | this.maxSubrayRecursion = rayParameters.maxSubrayRecursion !== undefined ? Math.floor( rayParameters.maxSubrayRecursion ) : 3; 304 | rayParameters.maxSubrayRecursion = this.maxSubrayRecursion; 305 | this.recursionProbability = rayParameters.recursionProbability !== undefined ? rayParameters.recursionProbability : 0.6; 306 | rayParameters.recursionProbability = this.recursionProbability; 307 | this.generateUVs = rayParameters.generateUVs !== undefined ? rayParameters.generateUVs : false; 308 | rayParameters.generateUVs = this.generateUVs; 309 | 310 | // Random generator 311 | if ( rayParameters.randomGenerator !== undefined ) { 312 | 313 | this.randomGenerator = rayParameters.randomGenerator; 314 | this.seedGenerator = rayParameters.randomGenerator; 315 | 316 | if ( rayParameters.noiseSeed !== undefined ) { 317 | 318 | this.seedGenerator.setSeed( rayParameters.noiseSeed ); 319 | 320 | } 321 | 322 | } 323 | else { 324 | 325 | this.randomGenerator = THREE.LightningStrike.createRandomGenerator(); 326 | this.seedGenerator = Math; 327 | 328 | } 329 | 330 | // Ray creation callbacks 331 | if ( rayParameters.onDecideSubrayCreation !== undefined ) { 332 | 333 | this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation; 334 | 335 | } 336 | else { 337 | 338 | this.createDefaultSubrayCreationCallbacks(); 339 | 340 | if ( rayParameters.onSubrayCreation !== undefined ) { 341 | 342 | this.onSubrayCreation = rayParameters.onSubrayCreation; 343 | 344 | } 345 | 346 | } 347 | 348 | // Internal state 349 | 350 | this.state = THREE.LightningStrike.RAY_INITIALIZED; 351 | 352 | this.maxSubrays = Math.ceil( 1 + Math.pow( this.ramification, Math.max( 0, this.maxSubrayRecursion - 1 ) ) ); 353 | rayParameters.maxSubrays = this.maxSubrays; 354 | 355 | this.maxRaySegments = 2 * ( 1 << this.maxIterations ); 356 | 357 | this.subrays = []; 358 | 359 | for ( var i = 0; i < this.maxSubrays; i++ ) { 360 | 361 | this.subrays.push( this.createSubray() ); 362 | 363 | } 364 | 365 | this.raySegments = []; 366 | 367 | for ( var i = 0; i < this.maxRaySegments; i++ ) { 368 | 369 | this.raySegments.push( this.createSegment() ); 370 | 371 | } 372 | 373 | this.time = 0; 374 | this.timeFraction = 0; 375 | this.currentSegmentCallback = null; 376 | this.currentCreateTriangleVertices = this.generateUVs ? this.createTriangleVerticesWithUVs : this.createTriangleVerticesWithoutUVs; 377 | this.numSubrays = 0; 378 | this.currentSubray = null; 379 | this.currentSegmentIndex = 0; 380 | this.isInitialSegment = false; 381 | this.subrayProbability = 0; 382 | 383 | this.currentVertex = 0; 384 | this.currentIndex = 0; 385 | this.currentCoordinate = 0; 386 | this.currentUVCoordinate = 0; 387 | this.vertices = null; 388 | this.uvs = null; 389 | this.indices = null; 390 | this.positionAttribute = null; 391 | this.uvsAttribute = null; 392 | 393 | this.simplexX = new SimplexNoise( this.seedGenerator ); 394 | this.simplexY = new SimplexNoise( this.seedGenerator ); 395 | this.simplexZ = new SimplexNoise( this.seedGenerator ); 396 | 397 | // Temp vectors 398 | this.forwards = new THREE.Vector3(); 399 | this.forwardsFill = new THREE.Vector3(); 400 | this.side = new THREE.Vector3(); 401 | this.down = new THREE.Vector3(); 402 | this.middlePos = new THREE.Vector3(); 403 | this.middleLinPos = new THREE.Vector3(); 404 | this.newPos = new THREE.Vector3(); 405 | this.vPos = new THREE.Vector3(); 406 | this.cross1 = new THREE.Vector3(); 407 | 408 | }; 409 | 410 | THREE.LightningStrike.prototype.createMesh = function () { 411 | 412 | var maxDrawableSegmentsPerSubRay = 1 << this.maxIterations; 413 | 414 | var maxVerts = 3 * ( maxDrawableSegmentsPerSubRay + 1 ) * this.maxSubrays; 415 | var maxIndices = 18 * maxDrawableSegmentsPerSubRay * this.maxSubrays; 416 | 417 | this.vertices = new Float32Array( maxVerts * 3 ); 418 | this.indices = new Uint32Array( maxIndices ); 419 | if ( this.generateUVs ) { 420 | this.uvs = new Float32Array( maxVerts * 2 ); 421 | } 422 | 423 | // Populate the mesh 424 | this.fillMesh( 0 ); 425 | 426 | this.setIndex( new THREE.Uint32BufferAttribute( this.indices, 1 ) ); 427 | 428 | this.positionAttribute = new THREE.Float32BufferAttribute( this.vertices, 3 ); 429 | this.addAttribute( 'position', this.positionAttribute ); 430 | 431 | if ( this.generateUVs ) {1 432 | this.uvsAttribute = new THREE.Float32BufferAttribute( new Float32Array( this.uvs ), 2 ); 433 | this.addAttribute( 'uv', this.uvsAttribute ); 434 | } 435 | 436 | if ( ! this.isStatic ) { 437 | this.index.dynamic = true; 438 | this.positionAttribute.dynamic = true; 439 | if ( this.generateUVs ) { 440 | this.uvsAttribute.dynamic = true; 441 | } 442 | } 443 | 444 | // Store buffers for later modification 445 | this.vertices = this.positionAttribute.array; 446 | this.indices = this.index.array; 447 | if ( this.generateUVs ) { 448 | this.uvs = this.uvsAttribute.array; 449 | } 450 | 451 | }; 452 | 453 | THREE.LightningStrike.prototype.updateMesh = function ( time ) { 454 | 455 | this.fillMesh( time ); 456 | 457 | this.drawRange.count = this.currentIndex; 458 | 459 | this.index.needsUpdate = true; 460 | 461 | this.positionAttribute.needsUpdate = true; 462 | 463 | if ( this.generateUVs ) { 464 | this.uvsAttribute.needsUpdate = true; 465 | } 466 | 467 | }; 468 | 469 | THREE.LightningStrike.prototype.fillMesh = function ( time ) { 470 | 471 | var scope = this; 472 | 473 | this.currentVertex = 0; 474 | this.currentIndex = 0; 475 | this.currentCoordinate = 0; 476 | this.currentUVCoordinate = 0; 477 | 478 | this.fractalRay( time, function fillVertices ( segment ) { 479 | 480 | var subray = scope.currentSubray; 481 | 482 | if ( time < subray.birthTime ) {//&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) { 483 | 484 | return; 485 | 486 | } 487 | else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) { 488 | 489 | // Eternal rays don't propagate nor vanish, but its subrays do 490 | 491 | scope.createPrism( segment ); 492 | 493 | scope.onDecideSubrayCreation( segment, scope ); 494 | 495 | } 496 | else if ( time < subray.endPropagationTime ) { 497 | 498 | if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) { 499 | 500 | // Ray propagation has arrived to this segment 501 | 502 | scope.createPrism( segment ); 503 | 504 | scope.onDecideSubrayCreation( segment, scope ); 505 | 506 | } 507 | 508 | } 509 | else if ( time < subray.beginVanishingTime ) { 510 | 511 | // Ray is steady (nor propagating nor vanishing) 512 | 513 | scope.createPrism( segment ); 514 | 515 | scope.onDecideSubrayCreation( segment, scope ); 516 | 517 | } 518 | else { 519 | 520 | if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) { 521 | 522 | // Segment has not yet vanished 523 | 524 | scope.createPrism( segment ); 525 | 526 | } 527 | 528 | scope.onDecideSubrayCreation( segment, scope ); 529 | 530 | } 531 | 532 | } ); 533 | 534 | }; 535 | 536 | THREE.LightningStrike.prototype.addNewSubray = function ( rayParameters ) { 537 | 538 | return this.subrays[ this.numSubrays++ ]; 539 | 540 | }; 541 | 542 | THREE.LightningStrike.prototype.initSubray = function ( subray, rayParameters ) { 543 | 544 | subray.pos0.copy( rayParameters.sourceOffset ); 545 | subray.pos1.copy( rayParameters.destOffset ); 546 | subray.up0.copy( rayParameters.up0 ); 547 | subray.up1.copy( rayParameters.up1 ); 548 | subray.radius0 = rayParameters.radius0; 549 | subray.radius1 = rayParameters.radius1; 550 | subray.birthTime = rayParameters.birthTime; 551 | subray.deathTime = rayParameters.deathTime; 552 | subray.timeScale = rayParameters.timeScale; 553 | subray.roughness = rayParameters.roughness; 554 | subray.straightness = rayParameters.straightness; 555 | subray.propagationTimeFactor = rayParameters.propagationTimeFactor; 556 | subray.vanishingTimeFactor = rayParameters.vanishingTimeFactor; 557 | 558 | subray.maxIterations = this.maxIterations; 559 | subray.seed = rayParameters.noiseSeed !== undefined ? rayParameters.noiseSeed : 0; 560 | subray.recursion = 0; 561 | 562 | }; 563 | 564 | THREE.LightningStrike.prototype.fractalRay = function ( time, segmentCallback ) { 565 | 566 | this.time = time; 567 | this.currentSegmentCallback = segmentCallback; 568 | this.numSubrays = 0; 569 | 570 | // Add the top level subray 571 | this.initSubray( this.addNewSubray(), this.rayParameters ); 572 | 573 | // Process all subrays that are being generated until consuming all of them 574 | for ( var subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex++ ) { 575 | 576 | var subray = this.subrays[ subrayIndex ]; 577 | this.currentSubray = subray; 578 | 579 | this.randomGenerator.setSeed( subray.seed ); 580 | 581 | subray.endPropagationTime = THREE.Math.lerp( subray.birthTime, subray.deathTime, subray.propagationTimeFactor ); 582 | subray.beginVanishingTime = THREE.Math.lerp( subray.deathTime, subray.birthTime, 1 - subray.vanishingTimeFactor ); 583 | 584 | var random1 = this.randomGenerator.random; 585 | subray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); 586 | subray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); 587 | 588 | this.timeFraction = ( time - subray.birthTime ) / ( subray.deathTime - subray.birthTime ); 589 | 590 | this.currentSegmentIndex = 0; 591 | this.isInitialSegment = true; 592 | 593 | var segment = this.getNewSegment(); 594 | segment.iteration = 0; 595 | segment.pos0.copy( subray.pos0 ); 596 | segment.pos1.copy( subray.pos1 ); 597 | segment.linPos0.copy( subray.linPos0 ); 598 | segment.linPos1.copy( subray.linPos1 ); 599 | segment.up0.copy( subray.up0 ); 600 | segment.up1.copy( subray.up1 ); 601 | segment.radius0 = subray.radius0; 602 | segment.radius1 = subray.radius1; 603 | segment.fraction0 = 0; 604 | segment.fraction1 = 1; 605 | segment.positionVariationFactor = 1 - subray.straightness; 606 | 607 | this.subrayProbability = this.ramification * Math.pow( this.recursionProbability, subray.recursion ) / ( 1 << subray.maxIterations ); 608 | 609 | this.fractalRayRecursive( segment ); 610 | 611 | } 612 | 613 | this.currentSegmentCallback = null; 614 | this.currentSubray = null; 615 | 616 | }; 617 | 618 | THREE.LightningStrike.prototype.fractalRayRecursive = function ( segment ) { 619 | 620 | // Leave recursion condition 621 | if ( segment.iteration >= this.currentSubray.maxIterations ) { 622 | 623 | this.currentSegmentCallback( segment ); 624 | 625 | return; 626 | 627 | } 628 | 629 | // Interpolation 630 | this.forwards.subVectors( segment.pos1, segment.pos0 ); 631 | var lForwards = this.forwards.length(); 632 | 633 | if ( lForwards < 0.000001) { 634 | this.forwards.set( 0, 0, 0.01 ); 635 | lForwards = this.forwards.length(); 636 | } 637 | 638 | var middleRadius = ( segment.radius0 + segment.radius1 ) * 0.5; 639 | var middleFraction = ( segment.fraction0 + segment.fraction1 ) * 0.5; 640 | 641 | var timeDimension = this.time * this.currentSubray.timeScale * Math.pow( 2, segment.iteration ); 642 | 643 | this.middlePos.lerpVectors( segment.pos0, segment.pos1, 0.5 ); 644 | this.middleLinPos.lerpVectors( segment.linPos0, segment.linPos1, 0.5 ); 645 | var p = this.middleLinPos; 646 | 647 | // Noise 648 | this.newPos.set( this.simplexX.noise4d( p.x, p.y, p.z, timeDimension ), 649 | this.simplexY.noise4d( p.x, p.y, p.z, timeDimension ), 650 | this.simplexZ.noise4d( p.x, p.y, p.z, timeDimension ) ); 651 | 652 | this.newPos.multiplyScalar( segment.positionVariationFactor * lForwards ); 653 | this.newPos.add( this.middlePos ); 654 | 655 | // Recursion 656 | 657 | var newSegment1 = this.getNewSegment(); 658 | newSegment1.pos0.copy( segment.pos0 ); 659 | newSegment1.pos1.copy( this.newPos ); 660 | newSegment1.linPos0.copy( segment.linPos0 ); 661 | newSegment1.linPos1.copy( this.middleLinPos ); 662 | newSegment1.up0.copy( segment.up0 ); 663 | newSegment1.up1.copy( segment.up1 ); 664 | newSegment1.radius0 = segment.radius0; 665 | newSegment1.radius1 = middleRadius; 666 | newSegment1.fraction0 = segment.fraction0; 667 | newSegment1.fraction1 = middleFraction; 668 | newSegment1.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness; 669 | newSegment1.iteration = segment.iteration + 1; 670 | 671 | var newSegment2 = this.getNewSegment(); 672 | newSegment2.pos0.copy( this.newPos ); 673 | newSegment2.pos1.copy( segment.pos1 ); 674 | newSegment2.linPos0.copy( this.middleLinPos ); 675 | newSegment2.linPos1.copy( segment.linPos1 ); 676 | this.cross1.crossVectors( segment.up0, this.forwards.normalize() ); 677 | newSegment2.up0.crossVectors( this.forwards, this.cross1 ).normalize(); 678 | newSegment2.up1.copy( segment.up1 ); 679 | newSegment2.radius0 = middleRadius; 680 | newSegment2.radius1 = segment.radius1; 681 | newSegment2.fraction0 = middleFraction; 682 | newSegment2.fraction1 = segment.fraction1; 683 | newSegment2.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness; 684 | newSegment2.iteration = segment.iteration + 1; 685 | 686 | this.fractalRayRecursive( newSegment1 ); 687 | 688 | this.fractalRayRecursive( newSegment2 ); 689 | 690 | }; 691 | 692 | THREE.LightningStrike.prototype.createPrism = function ( segment ) { 693 | 694 | // Creates one triangular prism and its vertices at the segment 695 | 696 | this.forwardsFill.subVectors( segment.pos1, segment.pos0 ).normalize(); 697 | 698 | if ( this.isInitialSegment ) { 699 | 700 | this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 ); 701 | 702 | this.isInitialSegment = false; 703 | 704 | } 705 | 706 | this.currentCreateTriangleVertices( segment.pos1, segment.up0, this.forwardsFill, segment.radius1, segment.fraction1 ); 707 | 708 | this.createPrismFaces(); 709 | 710 | }; 711 | 712 | THREE.LightningStrike.prototype.createTriangleVerticesWithoutUVs = function ( pos, up, forwards, radius ) { 713 | 714 | // Create an equilateral triangle (only vertices) 715 | 716 | this.side.crossVectors( up, forwards ).multiplyScalar( radius * THREE.LightningStrike.COS30DEG ); 717 | this.down.copy( up ).multiplyScalar( - radius * THREE.LightningStrike.SIN30DEG ); 718 | 719 | var p = this.vPos; 720 | var v = this.vertices; 721 | 722 | p.copy( pos ).sub( this.side ).add( this.down ); 723 | 724 | v[ this.currentCoordinate++ ] = p.x; 725 | v[ this.currentCoordinate++ ] = p.y; 726 | v[ this.currentCoordinate++ ] = p.z; 727 | 728 | p.copy( pos ).add( this.side ).add( this.down ); 729 | 730 | v[ this.currentCoordinate++ ] = p.x; 731 | v[ this.currentCoordinate++ ] = p.y; 732 | v[ this.currentCoordinate++ ] = p.z; 733 | 734 | p.copy( up ).multiplyScalar( radius ).add( pos ); 735 | 736 | v[ this.currentCoordinate++ ] = p.x; 737 | v[ this.currentCoordinate++ ] = p.y; 738 | v[ this.currentCoordinate++ ] = p.z; 739 | 740 | this.currentVertex += 3; 741 | 742 | }; 743 | 744 | THREE.LightningStrike.prototype.createTriangleVerticesWithUVs = function ( pos, up, forwards, radius, u ) { 745 | 746 | // Create an equilateral triangle (only vertices) 747 | 748 | this.side.crossVectors( up, forwards ).multiplyScalar( radius * THREE.LightningStrike.COS30DEG ); 749 | this.down.copy( up ).multiplyScalar( - radius * THREE.LightningStrike.SIN30DEG ); 750 | 751 | var p = this.vPos; 752 | var v = this.vertices; 753 | var uv = this.uvs; 754 | 755 | p.copy( pos ).sub( this.side ).add( this.down ); 756 | 757 | v[ this.currentCoordinate++ ] = p.x; 758 | v[ this.currentCoordinate++ ] = p.y; 759 | v[ this.currentCoordinate++ ] = p.z; 760 | 761 | uv[ this.currentUVCoordinate++ ] = u; 762 | uv[ this.currentUVCoordinate++ ] = 0; 763 | 764 | p.copy( pos ).add( this.side ).add( this.down ); 765 | 766 | v[ this.currentCoordinate++ ] = p.x; 767 | v[ this.currentCoordinate++ ] = p.y; 768 | v[ this.currentCoordinate++ ] = p.z; 769 | 770 | uv[ this.currentUVCoordinate++ ] = u; 771 | uv[ this.currentUVCoordinate++ ] = 0.5; 772 | 773 | p.copy( up ).multiplyScalar( radius ).add( pos ); 774 | 775 | v[ this.currentCoordinate++ ] = p.x; 776 | v[ this.currentCoordinate++ ] = p.y; 777 | v[ this.currentCoordinate++ ] = p.z; 778 | 779 | uv[ this.currentUVCoordinate++ ] = u; 780 | uv[ this.currentUVCoordinate++ ] = 1; 781 | 782 | this.currentVertex += 3; 783 | 784 | }; 785 | 786 | THREE.LightningStrike.prototype.createPrismFaces = function ( vertex, index ) { 787 | 788 | var indices = this.indices; 789 | var vertex = this.currentVertex - 6; 790 | 791 | indices[ this.currentIndex++ ] = vertex + 1; 792 | indices[ this.currentIndex++ ] = vertex + 2; 793 | indices[ this.currentIndex++ ] = vertex + 5; 794 | indices[ this.currentIndex++ ] = vertex + 1; 795 | indices[ this.currentIndex++ ] = vertex + 5; 796 | indices[ this.currentIndex++ ] = vertex + 4; 797 | indices[ this.currentIndex++ ] = vertex + 0; 798 | indices[ this.currentIndex++ ] = vertex + 1; 799 | indices[ this.currentIndex++ ] = vertex + 4; 800 | indices[ this.currentIndex++ ] = vertex + 0; 801 | indices[ this.currentIndex++ ] = vertex + 4; 802 | indices[ this.currentIndex++ ] = vertex + 3; 803 | indices[ this.currentIndex++ ] = vertex + 2; 804 | indices[ this.currentIndex++ ] = vertex + 0; 805 | indices[ this.currentIndex++ ] = vertex + 3; 806 | indices[ this.currentIndex++ ] = vertex + 2; 807 | indices[ this.currentIndex++ ] = vertex + 3; 808 | indices[ this.currentIndex++ ] = vertex + 5; 809 | 810 | }; 811 | 812 | THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function () { 813 | 814 | var random1 = this.randomGenerator.random; 815 | 816 | this.onDecideSubrayCreation = function ( segment, lightningStrike ) { 817 | 818 | // Decide subrays creation at parent (sub)ray segment 819 | 820 | var subray = lightningStrike.currentSubray; 821 | 822 | var period = lightningStrike.rayParameters.subrayPeriod; 823 | var dutyCycle = lightningStrike.rayParameters.subrayDutyCycle; 824 | 825 | var phase0 = ( lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) ? - random1() * period : THREE.Math.lerp( subray.birthTime, subray.endPropagationTime, segment.fraction0 ) - random1() * period; 826 | 827 | var phase = lightningStrike.time - phase0; 828 | var currentCycle = Math.floor( phase / period ); 829 | 830 | var childSubraySeed = random1() * ( currentCycle + 1 ); 831 | 832 | var isActive = phase % period <= dutyCycle * period; 833 | 834 | probability = lightningStrike.subrayProbability; 835 | var probability = 0; 836 | if ( isActive ) { 837 | probability = lightningStrike.subrayProbability; 838 | // Distribution test: probability *= segment.fraction0 > 0.5 && segment.fraction0 < 0.9 ? 1 / 0.4 : 0; 839 | } 840 | 841 | if ( subray.recursion < lightningStrike.maxSubrayRecursion && lightningStrike.numSubrays < lightningStrike.maxSubrays && random1() < probability ) { 842 | 843 | var childSubray = lightningStrike.addNewSubray(); 844 | 845 | var parentSeed = lightningStrike.randomGenerator.getSeed(); 846 | childSubray.seed = childSubraySeed; 847 | lightningStrike.randomGenerator.setSeed( childSubraySeed ); 848 | 849 | childSubray.recursion = subray.recursion + 1; 850 | childSubray.maxIterations = Math.max( 1, subray.maxIterations - 1 ); 851 | 852 | childSubray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); 853 | childSubray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 );; 854 | childSubray.up0.copy( subray.up0 ); 855 | childSubray.up1.copy( subray.up1 ); 856 | childSubray.radius0 = segment.radius0 * lightningStrike.rayParameters.radius0Factor; 857 | childSubray.radius1 = Math.min( lightningStrike.rayParameters.minRadius, segment.radius1 * lightningStrike.rayParameters.radius1Factor ); 858 | 859 | childSubray.birthTime = phase0 + ( currentCycle ) * period; 860 | childSubray.deathTime = childSubray.birthTime + period * dutyCycle; 861 | 862 | if ( ! lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) { 863 | 864 | childSubray.birthTime = Math.max( childSubray.birthTime, subray.birthTime ); 865 | childSubray.deathTime = Math.min( childSubray.deathTime, subray.deathTime ); 866 | 867 | } 868 | 869 | childSubray.timeScale = subray.timeScale * 2; 870 | childSubray.roughness = subray.roughness; 871 | childSubray.straightness = subray.straightness; 872 | childSubray.propagationTimeFactor = subray.propagationTimeFactor; 873 | childSubray.vanishingTimeFactor = subray.vanishingTimeFactor; 874 | 875 | lightningStrike.onSubrayCreation( segment, subray, childSubray, lightningStrike ); 876 | 877 | lightningStrike.randomGenerator.setSeed( parentSeed ); 878 | 879 | } 880 | 881 | }; 882 | 883 | var vec1Pos = new THREE.Vector3(); 884 | var vec2Forward = new THREE.Vector3(); 885 | var vec3Side = new THREE.Vector3(); 886 | var vec4Up = new THREE.Vector3(); 887 | 888 | this.onSubrayCreation = function ( segment, parentSubray, childSubray, lightningStrike ) { 889 | 890 | // Decide childSubray origin and destination positions (pos0 and pos1) and possibly other properties of childSubray 891 | 892 | // Just use the default cone position generator 893 | lightningStrike.subrayCylinderPosition( segment, parentSubray, childSubray, 0.5, 0.6, 0.2 ); 894 | 895 | }; 896 | 897 | this.subrayConePosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) { 898 | 899 | // Sets childSubray pos0 and pos1 in a cone 900 | 901 | childSubray.pos0.copy( segment.pos0 ); 902 | 903 | vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 ); 904 | vec2Forward.copy( vec1Pos ).normalize(); 905 | vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( random1() * heightFactor ) ); 906 | var length = vec1Pos.length(); 907 | vec3Side.crossVectors( parentSubray.up0, vec2Forward ); 908 | var angle = 2 * Math.PI * random1(); 909 | vec3Side.multiplyScalar( Math.cos ( angle ) ); 910 | vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin ( angle ) ); 911 | 912 | childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 ); 913 | 914 | } 915 | 916 | this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) { 917 | 918 | // Sets childSubray pos0 and pos1 in a cylinder 919 | 920 | childSubray.pos0.copy( segment.pos0 ); 921 | 922 | vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 ); 923 | vec2Forward.copy( vec1Pos ).normalize(); 924 | vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( ( 2 * random1() - 1 ) * heightFactor ) ); 925 | var length = vec1Pos.length(); 926 | vec3Side.crossVectors( parentSubray.up0, vec2Forward ); 927 | var angle = 2 * Math.PI * random1(); 928 | vec3Side.multiplyScalar( Math.cos ( angle ) ); 929 | vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin ( angle ) ); 930 | 931 | childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 ); 932 | 933 | } 934 | 935 | }; 936 | 937 | THREE.LightningStrike.prototype.createSubray = function () { 938 | 939 | return { 940 | 941 | seed: 0, 942 | maxIterations: 0, 943 | recursion: 0, 944 | pos0: new THREE.Vector3(), 945 | pos1: new THREE.Vector3(), 946 | linPos0: new THREE.Vector3(), 947 | linPos1: new THREE.Vector3(), 948 | up0: new THREE.Vector3(), 949 | up1: new THREE.Vector3(), 950 | radius0: 0, 951 | radius1: 0, 952 | birthTime: 0, 953 | deathTime: 0, 954 | timeScale: 0, 955 | roughness: 0, 956 | straightness: 0, 957 | propagationTimeFactor: 0, 958 | vanishingTimeFactor: 0, 959 | endPropagationTime: 0, 960 | beginVanishingTime: 0 961 | 962 | }; 963 | 964 | }; 965 | 966 | THREE.LightningStrike.prototype.createSegment = function () { 967 | 968 | return { 969 | iteration: 0, 970 | pos0: new THREE.Vector3(), 971 | pos1: new THREE.Vector3(), 972 | linPos0: new THREE.Vector3(), 973 | linPos1: new THREE.Vector3(), 974 | up0: new THREE.Vector3(), 975 | up1: new THREE.Vector3(), 976 | radius0: 0, 977 | radius1: 0, 978 | fraction0: 0, 979 | fraction1: 0, 980 | positionVariationFactor: 0 981 | } 982 | 983 | }; 984 | 985 | THREE.LightningStrike.prototype.getNewSegment = function () { 986 | 987 | return this.raySegments[ this.currentSegmentIndex++ ]; 988 | 989 | }; 990 | 991 | THREE.LightningStrike.prototype.copy = function ( source ) { 992 | 993 | BufferGeometry.prototype.copy.call( this, source ); 994 | 995 | this.init( THREE.LightningStrike.copyParameters( {}, source.rayParameters ) ); 996 | 997 | return this; 998 | 999 | }; 1000 | 1001 | THREE.LightningStrike.prototype.clone = function () { 1002 | 1003 | return new this.constructor( THREE.LightningStrike.copyParameters( {}, this.rayParameters ) ); 1004 | 1005 | }; 1006 | -------------------------------------------------------------------------------- /js/libs/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * https://github.com/dataarts/dat.gui 4 | * 5 | * Copyright 2016 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.dat=t():e.dat=t()}(this,function(){return function(e){function t(o){if(n[o])return n[o].exports;var i=n[o]={exports:{},id:o,loaded:!1};return e[o].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}var i=n(1),r=o(i);e.exports=r["default"]},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}t.__esModule=!0;var i=n(2),r=o(i),a=n(6),l=o(a),s=n(3),u=o(s),d=n(7),c=o(d),f=n(8),_=o(f),p=n(10),h=o(p),m=n(11),b=o(m),g=n(12),v=o(g),y=n(13),w=o(y),x=n(14),E=o(x),C=n(15),A=o(C),S=n(16),k=o(S),O=n(9),T=o(O),R=n(17),L=o(R);t["default"]={color:{Color:r["default"],math:l["default"],interpret:u["default"]},controllers:{Controller:c["default"],BooleanController:_["default"],OptionController:h["default"],StringController:b["default"],NumberController:v["default"],NumberControllerBox:w["default"],NumberControllerSlider:E["default"],FunctionController:A["default"],ColorController:k["default"]},dom:{dom:T["default"]},gui:{GUI:L["default"]},GUI:L["default"]}},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t,n){Object.defineProperty(e,t,{get:function(){return"RGB"===this.__state.space?this.__state[t]:(h.recalculateRGB(this,t,n),this.__state[t])},set:function(e){"RGB"!==this.__state.space&&(h.recalculateRGB(this,t,n),this.__state.space="RGB"),this.__state[t]=e}})}function a(e,t){Object.defineProperty(e,t,{get:function(){return"HSV"===this.__state.space?this.__state[t]:(h.recalculateHSV(this),this.__state[t])},set:function(e){"HSV"!==this.__state.space&&(h.recalculateHSV(this),this.__state.space="HSV"),this.__state[t]=e}})}t.__esModule=!0;var l=n(3),s=o(l),u=n(6),d=o(u),c=n(4),f=o(c),_=n(5),p=o(_),h=function(){function e(){if(i(this,e),this.__state=s["default"].apply(this,arguments),this.__state===!1)throw new Error("Failed to interpret color arguments");this.__state.a=this.__state.a||1}return e.prototype.toString=function(){return(0,f["default"])(this)},e.prototype.toHexString=function(){return(0,f["default"])(this,!0)},e.prototype.toOriginal=function(){return this.__state.conversion.write(this)},e}();h.recalculateRGB=function(e,t,n){if("HEX"===e.__state.space)e.__state[t]=d["default"].component_from_hex(e.__state.hex,n);else{if("HSV"!==e.__state.space)throw new Error("Corrupted color state");p["default"].extend(e.__state,d["default"].hsv_to_rgb(e.__state.h,e.__state.s,e.__state.v))}},h.recalculateHSV=function(e){var t=d["default"].rgb_to_hsv(e.r,e.g,e.b);p["default"].extend(e.__state,{s:t.s,v:t.v}),p["default"].isNaN(t.h)?p["default"].isUndefined(e.__state.h)&&(e.__state.h=0):e.__state.h=t.h},h.COMPONENTS=["r","g","b","h","s","v","hex","a"],r(h.prototype,"r",2),r(h.prototype,"g",1),r(h.prototype,"b",0),a(h.prototype,"h"),a(h.prototype,"s"),a(h.prototype,"v"),Object.defineProperty(h.prototype,"a",{get:function(){return this.__state.a},set:function(e){this.__state.a=e}}),Object.defineProperty(h.prototype,"hex",{get:function(){return"HEX"!==!this.__state.space&&(this.__state.hex=d["default"].rgb_to_hex(this.r,this.g,this.b)),this.__state.hex},set:function(e){this.__state.space="HEX",this.__state.hex=e}}),t["default"]=h},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}t.__esModule=!0;var i=n(4),r=o(i),a=n(5),l=o(a),s=[{litmus:l["default"].isString,conversions:{THREE_CHAR_HEX:{read:function(e){var t=e.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null!==t&&{space:"HEX",hex:parseInt("0x"+t[1].toString()+t[1].toString()+t[2].toString()+t[2].toString()+t[3].toString()+t[3].toString(),0)}},write:r["default"]},SIX_CHAR_HEX:{read:function(e){var t=e.match(/^#([A-F0-9]{6})$/i);return null!==t&&{space:"HEX",hex:parseInt("0x"+t[1].toString(),0)}},write:r["default"]},CSS_RGB:{read:function(e){var t=e.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);return null!==t&&{space:"RGB",r:parseFloat(t[1]),g:parseFloat(t[2]),b:parseFloat(t[3])}},write:r["default"]},CSS_RGBA:{read:function(e){var t=e.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);return null!==t&&{space:"RGB",r:parseFloat(t[1]),g:parseFloat(t[2]),b:parseFloat(t[3]),a:parseFloat(t[4])}},write:r["default"]}}},{litmus:l["default"].isNumber,conversions:{HEX:{read:function(e){return{space:"HEX",hex:e,conversionName:"HEX"}},write:function(e){return e.hex}}}},{litmus:l["default"].isArray,conversions:{RGB_ARRAY:{read:function(e){return 3===e.length&&{space:"RGB",r:e[0],g:e[1],b:e[2]}},write:function(e){return[e.r,e.g,e.b]}},RGBA_ARRAY:{read:function(e){return 4===e.length&&{space:"RGB",r:e[0],g:e[1],b:e[2],a:e[3]}},write:function(e){return[e.r,e.g,e.b,e.a]}}}},{litmus:l["default"].isObject,conversions:{RGBA_OBJ:{read:function(e){return!!(l["default"].isNumber(e.r)&&l["default"].isNumber(e.g)&&l["default"].isNumber(e.b)&&l["default"].isNumber(e.a))&&{space:"RGB",r:e.r,g:e.g,b:e.b,a:e.a}},write:function(e){return{r:e.r,g:e.g,b:e.b,a:e.a}}},RGB_OBJ:{read:function(e){return!!(l["default"].isNumber(e.r)&&l["default"].isNumber(e.g)&&l["default"].isNumber(e.b))&&{space:"RGB",r:e.r,g:e.g,b:e.b}},write:function(e){return{r:e.r,g:e.g,b:e.b}}},HSVA_OBJ:{read:function(e){return!!(l["default"].isNumber(e.h)&&l["default"].isNumber(e.s)&&l["default"].isNumber(e.v)&&l["default"].isNumber(e.a))&&{space:"HSV",h:e.h,s:e.s,v:e.v,a:e.a}},write:function(e){return{h:e.h,s:e.s,v:e.v,a:e.a}}},HSV_OBJ:{read:function(e){return!!(l["default"].isNumber(e.h)&&l["default"].isNumber(e.s)&&l["default"].isNumber(e.v))&&{space:"HSV",h:e.h,s:e.s,v:e.v}},write:function(e){return{h:e.h,s:e.s,v:e.v}}}}}],u=void 0,d=void 0,c=function(){d=!1;var e=arguments.length>1?l["default"].toArray(arguments):arguments[0];return l["default"].each(s,function(t){if(t.litmus(e))return l["default"].each(t.conversions,function(t,n){if(u=t.read(e),d===!1&&u!==!1)return d=u,u.conversionName=n,u.conversion=t,l["default"].BREAK}),l["default"].BREAK}),d};t["default"]=c},function(e,t){"use strict";t.__esModule=!0,t["default"]=function(e,t){var n=e.__state.conversionName.toString(),o=Math.round(e.r),i=Math.round(e.g),r=Math.round(e.b),a=e.a,l=Math.round(e.h),s=e.s.toFixed(1),u=e.v.toFixed(1);if(t||"THREE_CHAR_HEX"===n||"SIX_CHAR_HEX"===n){for(var d=e.hex.toString(16);d.length<6;)d="0"+d;return"#"+d}return"CSS_RGB"===n?"rgb("+o+","+i+","+r+")":"CSS_RGBA"===n?"rgba("+o+","+i+","+r+","+a+")":"HEX"===n?"0x"+e.hex.toString(16):"RGB_ARRAY"===n?"["+o+","+i+","+r+"]":"RGBA_ARRAY"===n?"["+o+","+i+","+r+","+a+"]":"RGB_OBJ"===n?"{r:"+o+",g:"+i+",b:"+r+"}":"RGBA_OBJ"===n?"{r:"+o+",g:"+i+",b:"+r+",a:"+a+"}":"HSV_OBJ"===n?"{h:"+l+",s:"+s+",v:"+u+"}":"HSVA_OBJ"===n?"{h:"+l+",s:"+s+",v:"+u+",a:"+a+"}":"unknown format"}},function(e,t){"use strict";t.__esModule=!0;var n=Array.prototype.forEach,o=Array.prototype.slice,i={BREAK:{},extend:function(e){return this.each(o.call(arguments,1),function(t){var n=this.isObject(t)?Object.keys(t):[];n.forEach(function(n){this.isUndefined(t[n])||(e[n]=t[n])}.bind(this))},this),e},defaults:function(e){return this.each(o.call(arguments,1),function(t){var n=this.isObject(t)?Object.keys(t):[];n.forEach(function(n){this.isUndefined(e[n])&&(e[n]=t[n])}.bind(this))},this),e},compose:function(){var e=o.call(arguments);return function(){for(var t=o.call(arguments),n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},each:function(e,t,o){if(e)if(n&&e.forEach&&e.forEach===n)e.forEach(t,o);else if(e.length===e.length+0){var i=void 0,r=void 0;for(i=0,r=e.length;i>8*t&255},hex_with_component:function(e,t,o){return o<<(n=8*t)|e&~(255<-1?t.length-t.indexOf(".")-1:0}t.__esModule=!0;var s=n(7),u=o(s),d=n(5),c=o(d),f=function(e){function t(n,o,a){i(this,t);var s=r(this,e.call(this,n,o)),u=a||{};return s.__min=u.min,s.__max=u.max,s.__step=u.step,c["default"].isUndefined(s.__step)?0===s.initialValue?s.__impliedStep=1:s.__impliedStep=Math.pow(10,Math.floor(Math.log(Math.abs(s.initialValue))/Math.LN10))/10:s.__impliedStep=s.__step,s.__precision=l(s.__impliedStep),s}return a(t,e),t.prototype.setValue=function(t){var n=t;return void 0!==this.__min&&nthis.__max&&(n=this.__max),void 0!==this.__step&&n%this.__step!==0&&(n=Math.round(n/this.__step)*this.__step),e.prototype.setValue.call(this,n)},t.prototype.min=function(e){return this.__min=e,this},t.prototype.max=function(e){return this.__max=e,this},t.prototype.step=function(e){return this.__step=e,this.__impliedStep=e,this.__precision=l(e),this},t}(u["default"]);t["default"]=f},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t){var n=Math.pow(10,t);return Math.round(e*n)/n}t.__esModule=!0;var s=n(12),u=o(s),d=n(9),c=o(d),f=n(5),_=o(f),p=function(e){function t(n,o,a){function l(){var e=parseFloat(m.__input.value);_["default"].isNaN(e)||m.setValue(e)}function s(){m.__onFinishChange&&m.__onFinishChange.call(m,m.getValue())}function u(){s()}function d(e){var t=b-e.clientY;m.setValue(m.getValue()+t*m.__impliedStep),b=e.clientY}function f(){c["default"].unbind(window,"mousemove",d),c["default"].unbind(window,"mouseup",f),s()}function p(e){c["default"].bind(window,"mousemove",d),c["default"].bind(window,"mouseup",f),b=e.clientY}i(this,t);var h=r(this,e.call(this,n,o,a));h.__truncationSuspended=!1;var m=h,b=void 0;return h.__input=document.createElement("input"),h.__input.setAttribute("type","text"),c["default"].bind(h.__input,"change",l),c["default"].bind(h.__input,"blur",u),c["default"].bind(h.__input,"mousedown",p),c["default"].bind(h.__input,"keydown",function(e){13===e.keyCode&&(m.__truncationSuspended=!0,this.blur(),m.__truncationSuspended=!1,s())}),h.updateDisplay(),h.domElement.appendChild(h.__input),h}return a(t,e),t.prototype.updateDisplay=function(){return this.__input.value=this.__truncationSuspended?this.getValue():l(this.getValue(),this.__precision),e.prototype.updateDisplay.call(this)},t}(u["default"]);t["default"]=p},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t,n,o,i){return o+(i-o)*((e-t)/(n-t))}t.__esModule=!0;var s=n(12),u=o(s),d=n(9),c=o(d),f=function(e){function t(n,o,a,s,u){function d(e){document.activeElement.blur(),c["default"].bind(window,"mousemove",f),c["default"].bind(window,"mouseup",_),f(e)}function f(e){e.preventDefault();var t=h.__background.getBoundingClientRect();return h.setValue(l(e.clientX,t.left,t.right,h.__min,h.__max)),!1}function _(){c["default"].unbind(window,"mousemove",f),c["default"].unbind(window,"mouseup",_),h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())}i(this,t);var p=r(this,e.call(this,n,o,{min:a,max:s,step:u})),h=p;return p.__background=document.createElement("div"),p.__foreground=document.createElement("div"),c["default"].bind(p.__background,"mousedown",d),c["default"].addClass(p.__background,"slider"),c["default"].addClass(p.__foreground,"slider-fg"),p.updateDisplay(),p.__background.appendChild(p.__foreground),p.domElement.appendChild(p.__background),p}return a(t,e),t.prototype.updateDisplay=function(){var t=(this.getValue()-this.__min)/(this.__max-this.__min);return this.__foreground.style.width=100*t+"%",e.prototype.updateDisplay.call(this)},t}(u["default"]);t["default"]=f},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}t.__esModule=!0;var l=n(7),s=o(l),u=n(9),d=o(u),c=function(e){function t(n,o,a){i(this,t);var l=r(this,e.call(this,n,o)),s=l;return l.__button=document.createElement("div"),l.__button.innerHTML=void 0===a?"Fire":a,d["default"].bind(l.__button,"click",function(e){return e.preventDefault(),s.fire(),!1}),d["default"].addClass(l.__button,"button"),l.domElement.appendChild(l.__button),l}return a(t,e),t.prototype.fire=function(){this.__onChange&&this.__onChange.call(this),this.getValue().call(this.object),this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())},t}(s["default"]);t["default"]=c},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t,n,o){e.style.background="",g["default"].each(y,function(i){e.style.cssText+="background: "+i+"linear-gradient("+t+", "+n+" 0%, "+o+" 100%); "})}function s(e){e.style.background="",e.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);",e.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}t.__esModule=!0;var u=n(7),d=o(u),c=n(9),f=o(c),_=n(2),p=o(_),h=n(3),m=o(h),b=n(5),g=o(b),v=function(e){function t(n,o){function a(e){h(e),f["default"].bind(window,"mousemove",h),f["default"].bind(window,"mouseup",u)}function u(){f["default"].unbind(window,"mousemove",h),f["default"].unbind(window,"mouseup",u),_()}function d(){var e=(0,m["default"])(this.value);e!==!1?(y.__color.__state=e,y.setValue(y.__color.toOriginal())):this.value=y.__color.toString()}function c(){f["default"].unbind(window,"mousemove",b),f["default"].unbind(window,"mouseup",c),_()}function _(){y.__onFinishChange&&y.__onFinishChange.call(y,y.__color.toOriginal())}function h(e){e.preventDefault();var t=y.__saturation_field.getBoundingClientRect(),n=(e.clientX-t.left)/(t.right-t.left),o=1-(e.clientY-t.top)/(t.bottom-t.top);return o>1?o=1:o<0&&(o=0),n>1?n=1:n<0&&(n=0),y.__color.v=o,y.__color.s=n,y.setValue(y.__color.toOriginal()),!1}function b(e){e.preventDefault();var t=y.__hue_field.getBoundingClientRect(),n=1-(e.clientY-t.top)/(t.bottom-t.top);return n>1?n=1:n<0&&(n=0),y.__color.h=360*n,y.setValue(y.__color.toOriginal()),!1}i(this,t);var v=r(this,e.call(this,n,o));v.__color=new p["default"](v.getValue()),v.__temp=new p["default"](0);var y=v;v.domElement=document.createElement("div"),f["default"].makeSelectable(v.domElement,!1),v.__selector=document.createElement("div"),v.__selector.className="selector",v.__saturation_field=document.createElement("div"),v.__saturation_field.className="saturation-field",v.__field_knob=document.createElement("div"),v.__field_knob.className="field-knob",v.__field_knob_border="2px solid ",v.__hue_knob=document.createElement("div"),v.__hue_knob.className="hue-knob",v.__hue_field=document.createElement("div"),v.__hue_field.className="hue-field",v.__input=document.createElement("input"),v.__input.type="text",v.__input_textShadow="0 1px 1px ",f["default"].bind(v.__input,"keydown",function(e){13===e.keyCode&&d.call(this)}),f["default"].bind(v.__input,"blur",d),f["default"].bind(v.__selector,"mousedown",function(){f["default"].addClass(this,"drag").bind(window,"mouseup",function(){f["default"].removeClass(y.__selector,"drag")})});var w=document.createElement("div");return g["default"].extend(v.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}),g["default"].extend(v.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:v.__field_knob_border+(v.__color.v<.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1}),g["default"].extend(v.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1}),g["default"].extend(v.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"}),g["default"].extend(w.style,{width:"100%",height:"100%",background:"none"}),l(w,"top","rgba(0,0,0,0)","#000"),g["default"].extend(v.__hue_field.style,{width:"15px",height:"100px",border:"1px solid #555",cursor:"ns-resize",position:"absolute",top:"3px",right:"3px"}),s(v.__hue_field),g["default"].extend(v.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:v.__input_textShadow+"rgba(0,0,0,0.7)"}),f["default"].bind(v.__saturation_field,"mousedown",a),f["default"].bind(v.__field_knob,"mousedown",a),f["default"].bind(v.__hue_field,"mousedown",function(e){b(e),f["default"].bind(window,"mousemove",b),f["default"].bind(window,"mouseup",c)}),v.__saturation_field.appendChild(w),v.__selector.appendChild(v.__field_knob),v.__selector.appendChild(v.__saturation_field),v.__selector.appendChild(v.__hue_field),v.__hue_field.appendChild(v.__hue_knob),v.domElement.appendChild(v.__input),v.domElement.appendChild(v.__selector),v.updateDisplay(),v}return a(t,e),t.prototype.updateDisplay=function(){var e=(0,m["default"])(this.getValue());if(e!==!1){var t=!1;g["default"].each(p["default"].COMPONENTS,function(n){if(!g["default"].isUndefined(e[n])&&!g["default"].isUndefined(this.__color.__state[n])&&e[n]!==this.__color.__state[n])return t=!0,{}},this),t&&g["default"].extend(this.__color.__state,e)}g["default"].extend(this.__temp.__state,this.__color.__state),this.__temp.a=1;var n=this.__color.v<.5||this.__color.s>.5?255:0,o=255-n;g["default"].extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toHexString(),border:this.__field_knob_border+"rgb("+n+","+n+","+n+")"}),this.__hue_knob.style.marginTop=100*(1-this.__color.h/360)+"px",this.__temp.s=1,this.__temp.v=1,l(this.__saturation_field,"left","#fff",this.__temp.toHexString()),this.__input.value=this.__color.toString(),g["default"].extend(this.__input.style,{backgroundColor:this.__color.toHexString(),color:"rgb("+n+","+n+","+n+")",textShadow:this.__input_textShadow+"rgba("+o+","+o+","+o+",.7)"})},t}(d["default"]),y=["-moz-","-o-","-webkit-","-ms-",""];t["default"]=v},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function i(e,t,n){var o=document.createElement("li");return t&&o.appendChild(t),n?e.__ul.insertBefore(o,n):e.__ul.appendChild(o),e.onResize(),o}function r(e,t){var n=e.__preset_select[e.__preset_select.selectedIndex];t?n.innerHTML=n.value+"*":n.innerHTML=n.value}function a(e,t,n){if(n.__li=t,n.__gui=e,U["default"].extend(n,{options:function(t){if(arguments.length>1){var o=n.__li.nextElementSibling;return n.remove(),s(e,n.object,n.property,{before:o,factoryArgs:[U["default"].toArray(arguments)]})}if(U["default"].isArray(t)||U["default"].isObject(t)){var i=n.__li.nextElementSibling;return n.remove(),s(e,n.object,n.property,{before:i,factoryArgs:[t]})}},name:function(e){return n.__li.firstElementChild.firstElementChild.innerHTML=e,n},listen:function(){return n.__gui.listen(n),n},remove:function(){ 14 | return n.__gui.remove(n),n}}),n instanceof B["default"])!function(){var e=new N["default"](n.object,n.property,{min:n.__min,max:n.__max,step:n.__step});U["default"].each(["updateDisplay","onChange","onFinishChange","step"],function(t){var o=n[t],i=e[t];n[t]=e[t]=function(){var t=Array.prototype.slice.call(arguments);return i.apply(e,t),o.apply(n,t)}}),z["default"].addClass(t,"has-slider"),n.domElement.insertBefore(e.domElement,n.domElement.firstElementChild)}();else if(n instanceof N["default"]){var o=function(t){if(U["default"].isNumber(n.__min)&&U["default"].isNumber(n.__max)){var o=n.__li.firstElementChild.firstElementChild.innerHTML,i=n.__gui.__listening.indexOf(n)>-1;n.remove();var r=s(e,n.object,n.property,{before:n.__li.nextElementSibling,factoryArgs:[n.__min,n.__max,n.__step]});return r.name(o),i&&r.listen(),r}return t};n.min=U["default"].compose(o,n.min),n.max=U["default"].compose(o,n.max)}else n instanceof O["default"]?(z["default"].bind(t,"click",function(){z["default"].fakeEvent(n.__checkbox,"click")}),z["default"].bind(n.__checkbox,"click",function(e){e.stopPropagation()})):n instanceof R["default"]?(z["default"].bind(t,"click",function(){z["default"].fakeEvent(n.__button,"click")}),z["default"].bind(t,"mouseover",function(){z["default"].addClass(n.__button,"hover")}),z["default"].bind(t,"mouseout",function(){z["default"].removeClass(n.__button,"hover")})):n instanceof j["default"]&&(z["default"].addClass(t,"color"),n.updateDisplay=U["default"].compose(function(e){return t.style.borderLeftColor=n.__color.toString(),e},n.updateDisplay),n.updateDisplay());n.setValue=U["default"].compose(function(t){return e.getRoot().__preset_select&&n.isModified()&&r(e.getRoot(),!0),t},n.setValue)}function l(e,t){var n=e.getRoot(),o=n.__rememberedObjects.indexOf(t.object);if(o!==-1){var i=n.__rememberedObjectIndecesToControllers[o];if(void 0===i&&(i={},n.__rememberedObjectIndecesToControllers[o]=i),i[t.property]=t,n.load&&n.load.remembered){var r=n.load.remembered,a=void 0;if(r[e.preset])a=r[e.preset];else{if(!r[Q])return;a=r[Q]}if(a[o]&&void 0!==a[o][t.property]){var l=a[o][t.property];t.initialValue=l,t.setValue(l)}}}}function s(e,t,n,o){if(void 0===t[n])throw new Error('Object "'+t+'" has no property "'+n+'"');var r=void 0;if(o.color)r=new j["default"](t,n);else{var s=[t,n].concat(o.factoryArgs);r=C["default"].apply(e,s)}o.before instanceof S["default"]&&(o.before=o.before.__li),l(e,r),z["default"].addClass(r.domElement,"c");var u=document.createElement("span");z["default"].addClass(u,"property-name"),u.innerHTML=r.property;var d=document.createElement("div");d.appendChild(u),d.appendChild(r.domElement);var c=i(e,d,o.before);return z["default"].addClass(c,oe.CLASS_CONTROLLER_ROW),r instanceof j["default"]?z["default"].addClass(c,"color"):z["default"].addClass(c,g(r.getValue())),a(e,c,r),e.__controllers.push(r),r}function u(e,t){return document.location.href+"."+t}function d(e,t,n){var o=document.createElement("option");o.innerHTML=t,o.value=t,e.__preset_select.appendChild(o),n&&(e.__preset_select.selectedIndex=e.__preset_select.length-1)}function c(e,t){t.style.display=e.useLocalStorage?"block":"none"}function f(e){var t=e.__save_row=document.createElement("li");z["default"].addClass(e.domElement,"has-save"),e.__ul.insertBefore(t,e.__ul.firstChild),z["default"].addClass(t,"save-row");var n=document.createElement("span");n.innerHTML=" ",z["default"].addClass(n,"button gears");var o=document.createElement("span");o.innerHTML="Save",z["default"].addClass(o,"button"),z["default"].addClass(o,"save");var i=document.createElement("span");i.innerHTML="New",z["default"].addClass(i,"button"),z["default"].addClass(i,"save-as");var r=document.createElement("span");r.innerHTML="Revert",z["default"].addClass(r,"button"),z["default"].addClass(r,"revert");var a=e.__preset_select=document.createElement("select");e.load&&e.load.remembered?U["default"].each(e.load.remembered,function(t,n){d(e,n,n===e.preset)}):d(e,Q,!1),z["default"].bind(a,"change",function(){for(var t=0;t0&&(e.preset=this.preset,e.remembered||(e.remembered={}),e.remembered[this.preset]=h(this)),e.folders={},U["default"].each(this.__folders,function(t,n){e.folders[n]=t.getSaveObject()}),e},save:function(){this.load.remembered||(this.load.remembered={}),this.load.remembered[this.preset]=h(this),r(this,!1),this.saveToLocalStorageIfPossible()},saveAs:function(e){this.load.remembered||(this.load.remembered={},this.load.remembered[Q]=h(this,!0)),this.load.remembered[e]=h(this),this.preset=e,d(this,e,!0),this.saveToLocalStorageIfPossible()},revert:function(e){U["default"].each(this.__controllers,function(t){this.getRoot().load.remembered?l(e||this.getRoot(),t):t.setValue(t.initialValue),t.__onFinishChange&&t.__onFinishChange.call(t,t.getValue())},this),U["default"].each(this.__folders,function(e){e.revert(e)}),e||r(this.getRoot(),!1)},listen:function(e){var t=0===this.__listening.length;this.__listening.push(e),t&&b(this.__listening)},updateDisplay:function(){U["default"].each(this.__controllers,function(e){e.updateDisplay()}),U["default"].each(this.__folders,function(e){e.updateDisplay()})}}),e.exports=oe},function(e,t){"use strict";e.exports={load:function(e,t){var n=t||document,o=n.createElement("link");o.type="text/css",o.rel="stylesheet",o.href=e,n.getElementsByTagName("head")[0].appendChild(o)},inject:function(e,t){var n=t||document,o=document.createElement("style");o.type="text/css",o.innerHTML=e;var i=n.getElementsByTagName("head")[0];try{i.appendChild(o)}catch(r){}}}},function(e,t){e.exports="
Here's the new load parameter for your GUI's constructor:
Automatically save values to localStorage on exit.
The values saved to localStorage will override those passed to dat.GUI's constructor. This makes it easier to work incrementally, but localStorage is fragile, and your friends may not see the same values you do.
"},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}t.__esModule=!0;var i=n(10),r=o(i),a=n(13),l=o(a),s=n(14),u=o(s),d=n(11),c=o(d),f=n(15),_=o(f),p=n(8),h=o(p),m=n(5),b=o(m),g=function(e,t){var n=e[t];return b["default"].isArray(arguments[2])||b["default"].isObject(arguments[2])?new r["default"](e,t,arguments[2]):b["default"].isNumber(n)?b["default"].isNumber(arguments[2])&&b["default"].isNumber(arguments[3])?b["default"].isNumber(arguments[4])?new u["default"](e,t,arguments[2],arguments[3],arguments[4]):new u["default"](e,t,arguments[2],arguments[3]):b["default"].isNumber(arguments[4])?new l["default"](e,t,{min:arguments[2],max:arguments[3],step:arguments[4]}):new l["default"](e,t,{min:arguments[2],max:arguments[3]}):b["default"].isString(n)?new c["default"](e,t):b["default"].isFunction(n)?new _["default"](e,t,""):b["default"].isBoolean(n)?new h["default"](e,t):null};t["default"]=g},function(e,t){"use strict";function n(e){setTimeout(e,1e3/60)}t.__esModule=!0,t["default"]=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||n},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}t.__esModule=!0;var r=n(9),a=o(r),l=n(5),s=o(l),u=function(){function e(){i(this,e),this.backgroundElement=document.createElement("div"),s["default"].extend(this.backgroundElement.style,{backgroundColor:"rgba(0,0,0,0.8)",top:0,left:0,display:"none",zIndex:"1000",opacity:0,WebkitTransition:"opacity 0.2s linear",transition:"opacity 0.2s linear"}),a["default"].makeFullscreen(this.backgroundElement),this.backgroundElement.style.position="fixed",this.domElement=document.createElement("div"),s["default"].extend(this.domElement.style,{position:"fixed",display:"none",zIndex:"1001",opacity:0,WebkitTransition:"-webkit-transform 0.2s ease-out, opacity 0.2s linear",transition:"transform 0.2s ease-out, opacity 0.2s linear"}),document.body.appendChild(this.backgroundElement),document.body.appendChild(this.domElement);var t=this;a["default"].bind(this.backgroundElement,"click",function(){t.hide()})}return e.prototype.show=function(){var e=this;this.backgroundElement.style.display="block",this.domElement.style.display="block",this.domElement.style.opacity=0,this.domElement.style.webkitTransform="scale(1.1)",this.layout(),s["default"].defer(function(){e.backgroundElement.style.opacity=1,e.domElement.style.opacity=1,e.domElement.style.webkitTransform="scale(1)"})},e.prototype.hide=function t(){var e=this,t=function n(){e.domElement.style.display="none",e.backgroundElement.style.display="none",a["default"].unbind(e.domElement,"webkitTransitionEnd",n),a["default"].unbind(e.domElement,"transitionend",n),a["default"].unbind(e.domElement,"oTransitionEnd",n)};a["default"].bind(this.domElement,"webkitTransitionEnd",t),a["default"].bind(this.domElement,"transitionend",t),a["default"].bind(this.domElement,"oTransitionEnd",t),this.backgroundElement.style.opacity=0,this.domElement.style.opacity=0,this.domElement.style.webkitTransform="scale(1.1)"},e.prototype.layout=function(){this.domElement.style.left=window.innerWidth/2-a["default"].getWidth(this.domElement)/2+"px",this.domElement.style.top=window.innerHeight/2-a["default"].getHeight(this.domElement)/2+"px"},e}();t["default"]=u},function(e,t,n){t=e.exports=n(24)(),t.push([e.id,".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity .1s linear;transition:opacity .1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1!important}.dg.main .close-button.drag,.dg.main:hover .close-button{opacity:1}.dg.main .close-button{-webkit-transition:opacity .1s linear;transition:opacity .1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save>ul{margin-top:27px}.dg.a.has-save>ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height .1s ease-out;transition:height .1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid transparent}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li>*{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:9px}.dg .c select{margin-top:5px}.dg .cr.boolean,.dg .cr.boolean *,.dg .cr.function,.dg .cr.function *,.dg .cr.function .property-name{cursor:pointer}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco,monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px Lucida Grande,sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid hsla(0,0%,100%,.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.color{border-left:3px solid}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2fa1d6}.dg .cr.number input[type=text]{color:#2fa1d6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.boolean:hover,.dg .cr.function:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2fa1d6;max-width:100%}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}",""])},function(e,t){e.exports=function(){var e=[];return e.toString=function(){for(var e=[],t=0;t