├── images ├── city1.png └── city2.png ├── textures ├── sky.jpg ├── start.jpg └── warning.png ├── models └── obj │ └── city_test │ ├── edge_02_diffuse.jpg │ ├── edge_03_diffuse.jpg │ ├── edge_04_diffuse.jpg │ ├── edge_05_diffuse.jpg │ ├── edge_06_diffuse.jpg │ ├── edge_07_diffuse.jpg │ ├── SanFran_roadDiffuse_repeat.jpg │ └── city.mtl ├── js ├── shaders │ ├── CopyShader.js │ └── LuminosityHighPassShader.js ├── postprocessing │ ├── RenderPass.js │ ├── ShaderPass.js │ ├── EffectComposer.js │ └── UnrealBloomPass.js ├── libs │ ├── stats.min.js │ └── dat.gui.min.js ├── loaders │ ├── MTLLoader.js │ └── OBJLoader.js └── controls │ └── OrbitControls.js ├── README.md └── index.html /images/city1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/images/city1.png -------------------------------------------------------------------------------- /images/city2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/images/city2.png -------------------------------------------------------------------------------- /textures/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/textures/sky.jpg -------------------------------------------------------------------------------- /textures/start.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/textures/start.jpg -------------------------------------------------------------------------------- /textures/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/textures/warning.png -------------------------------------------------------------------------------- /models/obj/city_test/edge_02_diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/models/obj/city_test/edge_02_diffuse.jpg -------------------------------------------------------------------------------- /models/obj/city_test/edge_03_diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/models/obj/city_test/edge_03_diffuse.jpg -------------------------------------------------------------------------------- /models/obj/city_test/edge_04_diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/models/obj/city_test/edge_04_diffuse.jpg -------------------------------------------------------------------------------- /models/obj/city_test/edge_05_diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/models/obj/city_test/edge_05_diffuse.jpg -------------------------------------------------------------------------------- /models/obj/city_test/edge_06_diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/models/obj/city_test/edge_06_diffuse.jpg -------------------------------------------------------------------------------- /models/obj/city_test/edge_07_diffuse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/models/obj/city_test/edge_07_diffuse.jpg -------------------------------------------------------------------------------- /models/obj/city_test/SanFran_roadDiffuse_repeat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/542154968/threejs-city-model/HEAD/models/obj/city_test/SanFran_roadDiffuse_repeat.jpg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Threejs 城市模型渲染 demo 版本 2 | 3 | 1. **代码比较乱** 4 | 2. **注释少** 5 | 3. **没有 Threejs 基础的建议补基础再来看实现** 6 | 7 | **注意 1** 8 | 9 | - 城市的道路也是建模画出来的 10 | - 如果要道路光效可以让建模把道路贴图或者色值调亮点 11 | - 配上辉光自然而然就发光 12 | 13 | **注意 2** 14 | 15 | - 因为这个是 demo 模型 16 | - 所以模型的中心点并不是正中央 17 | - 导致你想旋转模型的时候 18 | - 它不是绕着中央旋转的 19 | - 可以在模型工具中调整中心点 20 | - threejs 如何调整还没想到 21 | - 利用了 center 方法模型会全部居中 无效 22 | 23 | ---------------------------------------------- yeah ---------------------------------------------- 24 | 25 | ![效果图 1](https://github.com/542154968/threejs-city-model/blob/master/images/city1.png) 26 | 27 | ![效果图 2](https://github.com/542154968/threejs-city-model/blob/master/images/city2.png) 28 | 29 | https://www.hsrj.group/city/index.html 在线访问 服务器比较慢 贴图加载慢 见谅 【服务器到期 没钱续费 over】 30 | 31 | 32 | !!!!!! 33 | 详细思路可以看 我写的这个文章 https://blog.csdn.net/qq_37540004/article/details/102862348 34 | -------------------------------------------------------------------------------- /js/shaders/LuminosityHighPassShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author bhouston / http://clara.io/ 3 | * 4 | * Luminosity 5 | * http://en.wikipedia.org/wiki/Luminosity 6 | */ 7 | 8 | THREE.LuminosityHighPassShader = { 9 | 10 | shaderID: "luminosityHighPass", 11 | 12 | uniforms: { 13 | 14 | "tDiffuse": { type: "t", value: null }, 15 | "luminosityThreshold": { type: "f", value: 1.0 }, 16 | "smoothWidth": { type: "f", value: 1.0 }, 17 | "defaultColor": { type: "c", value: new THREE.Color( 0x000000 ) }, 18 | "defaultOpacity": { type: "f", value: 0.0 } 19 | 20 | }, 21 | 22 | vertexShader: [ 23 | 24 | "varying vec2 vUv;", 25 | 26 | "void main() {", 27 | 28 | "vUv = uv;", 29 | 30 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 31 | 32 | "}" 33 | 34 | ].join("\n"), 35 | 36 | fragmentShader: [ 37 | 38 | "uniform sampler2D tDiffuse;", 39 | "uniform vec3 defaultColor;", 40 | "uniform float defaultOpacity;", 41 | "uniform float luminosityThreshold;", 42 | "uniform float smoothWidth;", 43 | 44 | "varying vec2 vUv;", 45 | 46 | "void main() {", 47 | 48 | "vec4 texel = texture2D( tDiffuse, vUv );", 49 | 50 | "vec3 luma = vec3( 0.299, 0.587, 0.114 );", 51 | 52 | "float v = dot( texel.xyz, luma );", 53 | 54 | "vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity );", 55 | 56 | "float alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v );", 57 | 58 | "gl_FragColor = mix( outputColor, texel, alpha );", 59 | 60 | "}" 61 | 62 | ].join("\n") 63 | 64 | }; 65 | -------------------------------------------------------------------------------- /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, deltaTime, 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.setRenderTarget( this.renderToScreen ? null : readBuffer ); 52 | if ( this.clear ) renderer.clear(); 53 | renderer.render( this.scene, this.camera ); 54 | 55 | if ( this.clearColor ) { 56 | 57 | renderer.setClearColor( oldClearColor, oldClearAlpha ); 58 | 59 | } 60 | 61 | this.scene.overrideMaterial = null; 62 | renderer.autoClear = oldAutoClear; 63 | } 64 | 65 | } ); 66 | -------------------------------------------------------------------------------- /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/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, deltaTime, 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.setRenderTarget( null ); 58 | renderer.render( this.scene, this.camera ); 59 | 60 | } else { 61 | 62 | renderer.setRenderTarget( writeBuffer ); 63 | if ( this.clear ) renderer.clear(); 64 | renderer.render( this.scene, this.camera ); 65 | 66 | } 67 | 68 | } 69 | 70 | } ); 71 | -------------------------------------------------------------------------------- /models/obj/city_test/city.mtl: -------------------------------------------------------------------------------- 1 | # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware 2 | # �������ļ�:15.10.2019 10:23:00 3 | 4 | newmtl _Lumiandefault 5 | Ns 10.0000 6 | Ni 1.5000 7 | d 1.0000 8 | Tr 0.0000 9 | Tf 1.0000 1.0000 1.0000 10 | illum 2 11 | Ka 0.5880 0.5880 0.5880 12 | Kd 0.5880 0.5880 0.5880 13 | Ks 0.0000 0.0000 0.0000 14 | Ke 0.0000 0.0000 0.0000 15 | 16 | newmtl _LumianinitialShadingGroup 17 | Ns 30.0000 18 | Ni 1.5000 19 | d 1.0000 20 | Tr 0.0000 21 | Tf 1.0000 1.0000 1.0000 22 | illum 2 23 | Ka 0.0000 0.0000 0.0000 24 | Kd 0.0500 0.0500 0.0500 25 | Ks 0.0000 0.0000 0.0000 26 | Ke 0.0000 0.0000 0.0000 27 | 28 | newmtl _Citylambert2SG 29 | Ns 30.0000 30 | Ni 1.5000 31 | d 1.0000 32 | Tr 0.0000 33 | Tf 1.0000 1.0000 1.0000 34 | illum 2 35 | Ka 0.0500 5.1000 5.0000 36 | Kd 0.0000 5.1000 5.0000 37 | Ks 0.0000 0.0000 0.0000 38 | Ke 0.0000 0.0000 0.0000 39 | map_Ka edge_02_diffuse.jpg 40 | map_Kd edge_02_diffuse.jpg 41 | map_bump edge_02_diffuse.jpg 42 | bump edge_02_diffuse.jpg 43 | 44 | newmtl _Citylou3SG 45 | Ns 30.0000 46 | Ni 1.5000 47 | d 1.0000 48 | Tr 0.0000 49 | Tf 1.0000 1.0000 1.0000 50 | illum 2 51 | Ka 0.0000 10.0000 10.0000 52 | Kd 0.0000 10.0000 10.0000 53 | Ks 0.0000 0.0000 0.0000 54 | Ke 0.0000 0.0000 0.0000 55 | map_Ka edge_03_diffuse.jpg 56 | map_Kd edge_03_diffuse.jpg 57 | map_bump edge_03_diffuse.jpg 58 | bump edge_03_diffuse.jpg 59 | 60 | newmtl _Citylou4SG 61 | Ns 30.0000 62 | Ni 1.5000 63 | d 1.0000 64 | Tr 0.0000 65 | Tf 1.0000 1.0000 1.0000 66 | illum 2 67 | Ka 0.0000 5.0000 5.0000 68 | Kd 0.0000 5.0000 5.0000 69 | Ks 0.0000 0.0000 0.0000 70 | Ke 0.0000 0.0000 0.0000 71 | map_Ka edge_04_diffuse.jpg 72 | map_Kd edge_04_diffuse.jpg 73 | map_bump edge_02_diffuse.jpg 74 | bump edge_02_diffuse.jpg 75 | 76 | newmtl _Citylou5SG 77 | Ns 30.0000 78 | Ni 1.5000 79 | d 1.0000 80 | Tr 0.0000 81 | Tf 1.0000 1.0000 1.0000 82 | illum 2 83 | Ka 0.0000 5.0000 5.0000 84 | Kd 0.0000 5.0000 5.0000 85 | Ks 0.0000 0.0000 0.0000 86 | Ke 0.0000 0.0000 0.0000 87 | map_Ka edge_05_diffuse.jpg 88 | map_Kd edge_05_diffuse.jpg 89 | map_bump edge_02_diffuse.jpg 90 | bump edge_02_diffuse.jpg 91 | 92 | newmtl _Citylou6SG 93 | Ns 30.0000 94 | Ni 1.5000 95 | d 1.0000 96 | Tr 0.0000 97 | Tf 1.0000 1.0000 1.0000 98 | illum 2 99 | Ka 0.0000 10.0000 10.0000 100 | Kd 0.0000 10.0000 10.0000 101 | Ks 0.0000 0.0000 0.0000 102 | Ke 0.0000 0.0000 0.0000 103 | map_Ka edge_06_diffuse.jpg 104 | map_Kd edge_06_diffuse.jpg 105 | map_bump edge_02_diffuse.jpg 106 | bump edge_02_diffuse.jpg 107 | 108 | newmtl _Citylou7SG 109 | Ns 30.0000 110 | Ni 1.5000 111 | d 1.0000 112 | Tr 0.0000 113 | Tf 1.0000 1.0000 1.0000 114 | illum 2 115 | Ka 0.0000 5.0000 5.0000 116 | Kd 0.0000 5.0000 5.0000 117 | Ks 0.0000 0.0000 0.0000 118 | Ke 0.0000 0.0000 0.0000 119 | map_Ka edge_07_diffuse.jpg 120 | map_Kd edge_07_diffuse.jpg 121 | map_bump edge_02_diffuse.jpg 122 | bump edge_02_diffuse.jpg 123 | -------------------------------------------------------------------------------- /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( new THREE.Vector2() ); 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 | this._previousFrameTime = Date.now(); 50 | 51 | }; 52 | 53 | Object.assign( THREE.EffectComposer.prototype, { 54 | 55 | swapBuffers: function () { 56 | 57 | var tmp = this.readBuffer; 58 | this.readBuffer = this.writeBuffer; 59 | this.writeBuffer = tmp; 60 | 61 | }, 62 | 63 | addPass: function ( pass ) { 64 | 65 | this.passes.push( pass ); 66 | 67 | var size = this.renderer.getDrawingBufferSize( new THREE.Vector2() ); 68 | pass.setSize( size.width, size.height ); 69 | 70 | }, 71 | 72 | insertPass: function ( pass, index ) { 73 | 74 | this.passes.splice( index, 0, pass ); 75 | 76 | }, 77 | 78 | render: function ( deltaTime ) { 79 | 80 | // deltaTime value is in seconds 81 | 82 | if ( deltaTime === undefined ) { 83 | 84 | deltaTime = ( Date.now() - this._previousFrameTime ) * 0.001; 85 | 86 | } 87 | 88 | this._previousFrameTime = Date.now(); 89 | 90 | var currentRenderTarget = this.renderer.getRenderTarget(); 91 | 92 | var maskActive = false; 93 | 94 | var pass, i, il = this.passes.length; 95 | 96 | for ( i = 0; i < il; i ++ ) { 97 | 98 | pass = this.passes[ i ]; 99 | 100 | if ( pass.enabled === false ) continue; 101 | 102 | pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive ); 103 | 104 | if ( pass.needsSwap ) { 105 | 106 | if ( maskActive ) { 107 | 108 | var context = this.renderer.context; 109 | 110 | context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); 111 | 112 | this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime ); 113 | 114 | context.stencilFunc( context.EQUAL, 1, 0xffffffff ); 115 | 116 | } 117 | 118 | this.swapBuffers(); 119 | 120 | } 121 | 122 | if ( THREE.MaskPass !== undefined ) { 123 | 124 | if ( pass instanceof THREE.MaskPass ) { 125 | 126 | maskActive = true; 127 | 128 | } else if ( pass instanceof THREE.ClearMaskPass ) { 129 | 130 | maskActive = false; 131 | 132 | } 133 | 134 | } 135 | 136 | } 137 | 138 | this.renderer.setRenderTarget( currentRenderTarget ); 139 | 140 | }, 141 | 142 | reset: function ( renderTarget ) { 143 | 144 | if ( renderTarget === undefined ) { 145 | 146 | var size = this.renderer.getDrawingBufferSize( new THREE.Vector2() ); 147 | 148 | renderTarget = this.renderTarget1.clone(); 149 | renderTarget.setSize( size.width, size.height ); 150 | 151 | } 152 | 153 | this.renderTarget1.dispose(); 154 | this.renderTarget2.dispose(); 155 | this.renderTarget1 = renderTarget; 156 | this.renderTarget2 = renderTarget.clone(); 157 | 158 | this.writeBuffer = this.renderTarget1; 159 | this.readBuffer = this.renderTarget2; 160 | 161 | }, 162 | 163 | setSize: function ( width, height ) { 164 | 165 | this.renderTarget1.setSize( width, height ); 166 | this.renderTarget2.setSize( width, height ); 167 | 168 | for ( var i = 0; i < this.passes.length; i ++ ) { 169 | 170 | this.passes[ i ].setSize( width, height ); 171 | 172 | } 173 | 174 | } 175 | 176 | } ); 177 | 178 | 179 | THREE.Pass = function () { 180 | 181 | // if set to true, the pass is processed by the composer 182 | this.enabled = true; 183 | 184 | // if set to true, the pass indicates to swap read and write buffer after rendering 185 | this.needsSwap = true; 186 | 187 | // if set to true, the pass clears its buffer before rendering 188 | this.clear = false; 189 | 190 | // if set to true, the result of the pass is rendered to screen 191 | this.renderToScreen = false; 192 | 193 | }; 194 | 195 | Object.assign( THREE.Pass.prototype, { 196 | 197 | setSize: function ( width, height ) {}, 198 | 199 | render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { 200 | 201 | console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); 202 | 203 | } 204 | 205 | } ); 206 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | three.js webgl - OBJLoader + MTLLoader 5 | 6 | 10 | 35 | 36 | 37 | 38 |

正在加载中....

39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 292 | 293 | 294 | -------------------------------------------------------------------------------- /js/loaders/MTLLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a Wavefront .mtl file specifying materials 3 | * 4 | * @author angelxuanchang 5 | */ 6 | 7 | THREE.MTLLoader = function ( manager ) { 8 | 9 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 10 | 11 | }; 12 | 13 | THREE.MTLLoader.prototype = { 14 | 15 | constructor: THREE.MTLLoader, 16 | 17 | /** 18 | * Loads and parses a MTL asset from a URL. 19 | * 20 | * @param {String} url - URL to the MTL file. 21 | * @param {Function} [onLoad] - Callback invoked with the loaded object. 22 | * @param {Function} [onProgress] - Callback for download progress. 23 | * @param {Function} [onError] - Callback for download errors. 24 | * 25 | * @see setPath setResourcePath 26 | * 27 | * @note In order for relative texture references to resolve correctly 28 | * you must call setResourcePath() explicitly prior to load. 29 | */ 30 | load: function ( url, onLoad, onProgress, onError ) { 31 | 32 | var scope = this; 33 | 34 | var path = ( this.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path; 35 | 36 | var loader = new THREE.FileLoader( this.manager ); 37 | loader.setPath( this.path ); 38 | loader.load( url, function ( text ) { 39 | 40 | onLoad( scope.parse( text, path ) ); 41 | 42 | }, onProgress, onError ); 43 | 44 | }, 45 | 46 | /** 47 | * Set base path for resolving references. 48 | * If set this path will be prepended to each loaded and found reference. 49 | * 50 | * @see setResourcePath 51 | * @param {String} path 52 | * @return {THREE.MTLLoader} 53 | * 54 | * @example 55 | * mtlLoader.setPath( 'assets/obj/' ); 56 | * mtlLoader.load( 'my.mtl', ... ); 57 | */ 58 | setPath: function ( path ) { 59 | 60 | this.path = path; 61 | return this; 62 | 63 | }, 64 | 65 | /** 66 | * Set base path for additional resources like textures. 67 | * 68 | * @see setPath 69 | * @param {String} path 70 | * @return {THREE.MTLLoader} 71 | * 72 | * @example 73 | * mtlLoader.setPath( 'assets/obj/' ); 74 | * mtlLoader.setResourcePath( 'assets/textures/' ); 75 | * mtlLoader.load( 'my.mtl', ... ); 76 | */ 77 | setResourcePath: function ( path ) { 78 | 79 | this.resourcePath = path; 80 | return this; 81 | 82 | }, 83 | 84 | setTexturePath: function ( path ) { 85 | 86 | console.warn( 'THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath().' ); 87 | return this.setResourcePath( path ); 88 | 89 | }, 90 | 91 | setCrossOrigin: function ( value ) { 92 | 93 | this.crossOrigin = value; 94 | return this; 95 | 96 | }, 97 | 98 | setMaterialOptions: function ( value ) { 99 | 100 | this.materialOptions = value; 101 | return this; 102 | 103 | }, 104 | 105 | /** 106 | * Parses a MTL file. 107 | * 108 | * @param {String} text - Content of MTL file 109 | * @return {THREE.MTLLoader.MaterialCreator} 110 | * 111 | * @see setPath setResourcePath 112 | * 113 | * @note In order for relative texture references to resolve correctly 114 | * you must call setResourcePath() explicitly prior to parse. 115 | */ 116 | parse: function ( text, path ) { 117 | 118 | var lines = text.split( '\n' ); 119 | var info = {}; 120 | var delimiter_pattern = /\s+/; 121 | var materialsInfo = {}; 122 | 123 | for ( var i = 0; i < lines.length; i ++ ) { 124 | 125 | var line = lines[ i ]; 126 | line = line.trim(); 127 | 128 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 129 | 130 | // Blank line or comment ignore 131 | continue; 132 | 133 | } 134 | 135 | var pos = line.indexOf( ' ' ); 136 | 137 | var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; 138 | key = key.toLowerCase(); 139 | 140 | var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; 141 | value = value.trim(); 142 | 143 | if ( key === 'newmtl' ) { 144 | 145 | // New material 146 | 147 | info = { name: value }; 148 | materialsInfo[ value ] = info; 149 | 150 | } else { 151 | 152 | if ( key === 'ka' || key === 'kd' || key === 'ks' || key ==='ke' ) { 153 | 154 | var ss = value.split( delimiter_pattern, 3 ); 155 | info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; 156 | 157 | } else { 158 | 159 | info[ key ] = value; 160 | 161 | } 162 | 163 | } 164 | 165 | } 166 | 167 | var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions ); 168 | materialCreator.setCrossOrigin( this.crossOrigin ); 169 | materialCreator.setManager( this.manager ); 170 | materialCreator.setMaterials( materialsInfo ); 171 | return materialCreator; 172 | 173 | } 174 | 175 | }; 176 | 177 | /** 178 | * Create a new THREE-MTLLoader.MaterialCreator 179 | * @param baseUrl - Url relative to which textures are loaded 180 | * @param options - Set of options on how to construct the materials 181 | * side: Which side to apply the material 182 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide 183 | * wrap: What type of wrapping to apply for textures 184 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping 185 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 186 | * Default: false, assumed to be already normalized 187 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's 188 | * Default: false 189 | * @constructor 190 | */ 191 | 192 | THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) { 193 | 194 | this.baseUrl = baseUrl || ''; 195 | this.options = options; 196 | this.materialsInfo = {}; 197 | this.materials = {}; 198 | this.materialsArray = []; 199 | this.nameLookup = {}; 200 | 201 | this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; 202 | this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; 203 | 204 | }; 205 | 206 | THREE.MTLLoader.MaterialCreator.prototype = { 207 | 208 | constructor: THREE.MTLLoader.MaterialCreator, 209 | 210 | crossOrigin: 'anonymous', 211 | 212 | setCrossOrigin: function ( value ) { 213 | 214 | this.crossOrigin = value; 215 | return this; 216 | 217 | }, 218 | 219 | setManager: function ( value ) { 220 | 221 | this.manager = value; 222 | 223 | }, 224 | 225 | setMaterials: function ( materialsInfo ) { 226 | 227 | this.materialsInfo = this.convert( materialsInfo ); 228 | this.materials = {}; 229 | this.materialsArray = []; 230 | this.nameLookup = {}; 231 | 232 | }, 233 | 234 | convert: function ( materialsInfo ) { 235 | 236 | if ( ! this.options ) return materialsInfo; 237 | 238 | var converted = {}; 239 | 240 | for ( var mn in materialsInfo ) { 241 | 242 | // Convert materials info into normalized form based on options 243 | 244 | var mat = materialsInfo[ mn ]; 245 | 246 | var covmat = {}; 247 | 248 | converted[ mn ] = covmat; 249 | 250 | for ( var prop in mat ) { 251 | 252 | var save = true; 253 | var value = mat[ prop ]; 254 | var lprop = prop.toLowerCase(); 255 | 256 | switch ( lprop ) { 257 | 258 | case 'kd': 259 | case 'ka': 260 | case 'ks': 261 | 262 | // Diffuse color (color under white light) using RGB values 263 | 264 | if ( this.options && this.options.normalizeRGB ) { 265 | 266 | value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; 267 | 268 | } 269 | 270 | if ( this.options && this.options.ignoreZeroRGBs ) { 271 | 272 | if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) { 273 | 274 | // ignore 275 | 276 | save = false; 277 | 278 | } 279 | 280 | } 281 | 282 | break; 283 | 284 | default: 285 | 286 | break; 287 | 288 | } 289 | 290 | if ( save ) { 291 | 292 | covmat[ lprop ] = value; 293 | 294 | } 295 | 296 | } 297 | 298 | } 299 | 300 | return converted; 301 | 302 | }, 303 | 304 | preload: function () { 305 | 306 | for ( var mn in this.materialsInfo ) { 307 | 308 | this.create( mn ); 309 | 310 | } 311 | 312 | }, 313 | 314 | getIndex: function ( materialName ) { 315 | 316 | return this.nameLookup[ materialName ]; 317 | 318 | }, 319 | 320 | getAsArray: function () { 321 | 322 | var index = 0; 323 | 324 | for ( var mn in this.materialsInfo ) { 325 | 326 | this.materialsArray[ index ] = this.create( mn ); 327 | this.nameLookup[ mn ] = index; 328 | index ++; 329 | 330 | } 331 | 332 | return this.materialsArray; 333 | 334 | }, 335 | 336 | create: function ( materialName ) { 337 | 338 | if ( this.materials[ materialName ] === undefined ) { 339 | 340 | this.createMaterial_( materialName ); 341 | 342 | } 343 | 344 | return this.materials[ materialName ]; 345 | 346 | }, 347 | 348 | createMaterial_: function ( materialName ) { 349 | 350 | // Create material 351 | 352 | var scope = this; 353 | var mat = this.materialsInfo[ materialName ]; 354 | var params = { 355 | 356 | name: materialName, 357 | side: this.side 358 | 359 | }; 360 | 361 | function resolveURL( baseUrl, url ) { 362 | 363 | if ( typeof url !== 'string' || url === '' ) 364 | return ''; 365 | 366 | // Absolute URL 367 | if ( /^https?:\/\//i.test( url ) ) return url; 368 | 369 | return baseUrl + url; 370 | 371 | } 372 | 373 | function setMapForType( mapType, value ) { 374 | 375 | if ( params[ mapType ] ) return; // Keep the first encountered texture 376 | 377 | var texParams = scope.getTextureParams( value, params ); 378 | var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); 379 | 380 | map.repeat.copy( texParams.scale ); 381 | map.offset.copy( texParams.offset ); 382 | 383 | map.wrapS = scope.wrap; 384 | map.wrapT = scope.wrap; 385 | 386 | params[ mapType ] = map; 387 | 388 | } 389 | 390 | for ( var prop in mat ) { 391 | 392 | var value = mat[ prop ]; 393 | var n; 394 | 395 | if ( value === '' ) continue; 396 | 397 | switch ( prop.toLowerCase() ) { 398 | 399 | // Ns is material specular exponent 400 | 401 | case 'kd': 402 | 403 | // Diffuse color (color under white light) using RGB values 404 | 405 | params.color = new THREE.Color().fromArray( value ); 406 | 407 | break; 408 | 409 | case 'ks': 410 | 411 | // Specular color (color when light is reflected from shiny surface) using RGB values 412 | params.specular = new THREE.Color().fromArray( value ); 413 | 414 | break; 415 | 416 | case 'ke': 417 | 418 | // Emissive using RGB values 419 | params.emissive = new THREE.Color().fromArray( value ); 420 | 421 | break; 422 | 423 | case 'map_kd': 424 | 425 | // Diffuse texture map 426 | 427 | setMapForType( "map", value ); 428 | 429 | break; 430 | 431 | case 'map_ks': 432 | 433 | // Specular map 434 | 435 | setMapForType( "specularMap", value ); 436 | 437 | break; 438 | 439 | case 'map_ke': 440 | 441 | // Emissive map 442 | 443 | setMapForType( "emissiveMap", value ); 444 | 445 | break; 446 | 447 | case 'norm': 448 | 449 | setMapForType( "normalMap", value ); 450 | 451 | break; 452 | 453 | case 'map_bump': 454 | case 'bump': 455 | 456 | // Bump texture map 457 | 458 | setMapForType( "bumpMap", value ); 459 | 460 | break; 461 | 462 | case 'map_d': 463 | 464 | // Alpha map 465 | 466 | setMapForType( "alphaMap", value ); 467 | params.transparent = true; 468 | 469 | break; 470 | 471 | case 'ns': 472 | 473 | // The specular exponent (defines the focus of the specular highlight) 474 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. 475 | 476 | params.shininess = parseFloat( value ); 477 | 478 | break; 479 | 480 | case 'd': 481 | n = parseFloat( value ); 482 | 483 | if ( n < 1 ) { 484 | 485 | params.opacity = n; 486 | params.transparent = true; 487 | 488 | } 489 | 490 | break; 491 | 492 | case 'tr': 493 | n = parseFloat( value ); 494 | 495 | if ( this.options && this.options.invertTrProperty ) n = 1 - n; 496 | 497 | if ( n > 0 ) { 498 | 499 | params.opacity = 1 - n; 500 | params.transparent = true; 501 | 502 | } 503 | 504 | break; 505 | 506 | default: 507 | break; 508 | 509 | } 510 | 511 | } 512 | 513 | this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); 514 | return this.materials[ materialName ]; 515 | 516 | }, 517 | 518 | getTextureParams: function ( value, matParams ) { 519 | 520 | var texParams = { 521 | 522 | scale: new THREE.Vector2( 1, 1 ), 523 | offset: new THREE.Vector2( 0, 0 ) 524 | 525 | }; 526 | 527 | var items = value.split( /\s+/ ); 528 | var pos; 529 | 530 | pos = items.indexOf( '-bm' ); 531 | 532 | if ( pos >= 0 ) { 533 | 534 | matParams.bumpScale = parseFloat( items[ pos + 1 ] ); 535 | items.splice( pos, 2 ); 536 | 537 | } 538 | 539 | pos = items.indexOf( '-s' ); 540 | 541 | if ( pos >= 0 ) { 542 | 543 | texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); 544 | items.splice( pos, 4 ); // we expect 3 parameters here! 545 | 546 | } 547 | 548 | pos = items.indexOf( '-o' ); 549 | 550 | if ( pos >= 0 ) { 551 | 552 | texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); 553 | items.splice( pos, 4 ); // we expect 3 parameters here! 554 | 555 | } 556 | 557 | texParams.url = items.join( ' ' ).trim(); 558 | return texParams; 559 | 560 | }, 561 | 562 | loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { 563 | 564 | var texture; 565 | var loader = THREE.Loader.Handlers.get( url ); 566 | var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager; 567 | 568 | if ( loader === null ) { 569 | 570 | loader = new THREE.TextureLoader( manager ); 571 | 572 | } 573 | 574 | if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); 575 | texture = loader.load( url, onLoad, onProgress, onError ); 576 | 577 | if ( mapping !== undefined ) texture.mapping = mapping; 578 | 579 | return texture; 580 | 581 | } 582 | 583 | }; 584 | -------------------------------------------------------------------------------- /js/postprocessing/UnrealBloomPass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author spidersharma / http://eduperiment.com/ 3 | * 4 | * Inspired from Unreal Engine 5 | * https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ 6 | */ 7 | THREE.UnrealBloomPass = function ( resolution, strength, radius, threshold ) { 8 | 9 | THREE.Pass.call( this ); 10 | 11 | this.strength = ( strength !== undefined ) ? strength : 1; 12 | this.radius = radius; 13 | this.threshold = threshold; 14 | this.resolution = ( resolution !== undefined ) ? new THREE.Vector2( resolution.x, resolution.y ) : new THREE.Vector2( 256, 256 ); 15 | 16 | // create color only once here, reuse it later inside the render function 17 | this.clearColor = new THREE.Color( 0, 0, 0 ); 18 | 19 | // render targets 20 | var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat }; 21 | this.renderTargetsHorizontal = []; 22 | this.renderTargetsVertical = []; 23 | this.nMips = 5; 24 | var resx = Math.round( this.resolution.x / 2 ); 25 | var resy = Math.round( this.resolution.y / 2 ); 26 | 27 | this.renderTargetBright = new THREE.WebGLRenderTarget( resx, resy, pars ); 28 | this.renderTargetBright.texture.name = "UnrealBloomPass.bright"; 29 | this.renderTargetBright.texture.generateMipmaps = false; 30 | 31 | for ( var i = 0; i < this.nMips; i ++ ) { 32 | 33 | var renderTargetHorizonal = new THREE.WebGLRenderTarget( resx, resy, pars ); 34 | 35 | renderTargetHorizonal.texture.name = "UnrealBloomPass.h" + i; 36 | renderTargetHorizonal.texture.generateMipmaps = false; 37 | 38 | this.renderTargetsHorizontal.push( renderTargetHorizonal ); 39 | 40 | var renderTargetVertical = new THREE.WebGLRenderTarget( resx, resy, pars ); 41 | 42 | renderTargetVertical.texture.name = "UnrealBloomPass.v" + i; 43 | renderTargetVertical.texture.generateMipmaps = false; 44 | 45 | this.renderTargetsVertical.push( renderTargetVertical ); 46 | 47 | resx = Math.round( resx / 2 ); 48 | 49 | resy = Math.round( resy / 2 ); 50 | 51 | } 52 | 53 | // luminosity high pass material 54 | 55 | if ( THREE.LuminosityHighPassShader === undefined ) 56 | console.error( "THREE.UnrealBloomPass relies on THREE.LuminosityHighPassShader" ); 57 | 58 | var highPassShader = THREE.LuminosityHighPassShader; 59 | this.highPassUniforms = THREE.UniformsUtils.clone( highPassShader.uniforms ); 60 | 61 | this.highPassUniforms[ "luminosityThreshold" ].value = threshold; 62 | this.highPassUniforms[ "smoothWidth" ].value = 0.01; 63 | 64 | this.materialHighPassFilter = new THREE.ShaderMaterial( { 65 | uniforms: this.highPassUniforms, 66 | vertexShader: highPassShader.vertexShader, 67 | fragmentShader: highPassShader.fragmentShader, 68 | defines: {} 69 | } ); 70 | 71 | // Gaussian Blur Materials 72 | this.separableBlurMaterials = []; 73 | var kernelSizeArray = [ 3, 5, 7, 9, 11 ]; 74 | var resx = Math.round( this.resolution.x / 2 ); 75 | var resy = Math.round( this.resolution.y / 2 ); 76 | 77 | for ( var i = 0; i < this.nMips; i ++ ) { 78 | 79 | this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); 80 | 81 | this.separableBlurMaterials[ i ].uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 82 | 83 | resx = Math.round( resx / 2 ); 84 | 85 | resy = Math.round( resy / 2 ); 86 | 87 | } 88 | 89 | // Composite material 90 | this.compositeMaterial = this.getCompositeMaterial( this.nMips ); 91 | this.compositeMaterial.uniforms[ "blurTexture1" ].value = this.renderTargetsVertical[ 0 ].texture; 92 | this.compositeMaterial.uniforms[ "blurTexture2" ].value = this.renderTargetsVertical[ 1 ].texture; 93 | this.compositeMaterial.uniforms[ "blurTexture3" ].value = this.renderTargetsVertical[ 2 ].texture; 94 | this.compositeMaterial.uniforms[ "blurTexture4" ].value = this.renderTargetsVertical[ 3 ].texture; 95 | this.compositeMaterial.uniforms[ "blurTexture5" ].value = this.renderTargetsVertical[ 4 ].texture; 96 | this.compositeMaterial.uniforms[ "bloomStrength" ].value = strength; 97 | this.compositeMaterial.uniforms[ "bloomRadius" ].value = 0.1; 98 | this.compositeMaterial.needsUpdate = true; 99 | 100 | var bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; 101 | this.compositeMaterial.uniforms[ "bloomFactors" ].value = bloomFactors; 102 | this.bloomTintColors = [ new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 1, 1, 1 ), 103 | new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 1, 1, 1 ) ]; 104 | this.compositeMaterial.uniforms[ "bloomTintColors" ].value = this.bloomTintColors; 105 | 106 | // copy material 107 | if ( THREE.CopyShader === undefined ) { 108 | 109 | console.error( "THREE.BloomPass relies on THREE.CopyShader" ); 110 | 111 | } 112 | 113 | var copyShader = THREE.CopyShader; 114 | 115 | this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms ); 116 | this.copyUniforms[ "opacity" ].value = 1.0; 117 | 118 | this.materialCopy = new THREE.ShaderMaterial( { 119 | uniforms: this.copyUniforms, 120 | vertexShader: copyShader.vertexShader, 121 | fragmentShader: copyShader.fragmentShader, 122 | blending: THREE.AdditiveBlending, 123 | depthTest: false, 124 | depthWrite: false, 125 | transparent: true 126 | } ); 127 | 128 | this.enabled = true; 129 | this.needsSwap = false; 130 | 131 | this.oldClearColor = new THREE.Color(); 132 | this.oldClearAlpha = 1; 133 | 134 | this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); 135 | this.scene = new THREE.Scene(); 136 | 137 | this.basic = new THREE.MeshBasicMaterial(); 138 | 139 | this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null ); 140 | this.quad.frustumCulled = false; // Avoid getting clipped 141 | this.scene.add( this.quad ); 142 | 143 | }; 144 | 145 | THREE.UnrealBloomPass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), { 146 | 147 | constructor: THREE.UnrealBloomPass, 148 | 149 | dispose: function () { 150 | 151 | for ( var i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { 152 | 153 | this.renderTargetsHorizontal[ i ].dispose(); 154 | 155 | } 156 | 157 | for ( var i = 0; i < this.renderTargetsVertical.length; i ++ ) { 158 | 159 | this.renderTargetsVertical[ i ].dispose(); 160 | 161 | } 162 | 163 | this.renderTargetBright.dispose(); 164 | 165 | }, 166 | 167 | setSize: function ( width, height ) { 168 | 169 | var resx = Math.round( width / 2 ); 170 | var resy = Math.round( height / 2 ); 171 | 172 | this.renderTargetBright.setSize( resx, resy ); 173 | 174 | for ( var i = 0; i < this.nMips; i ++ ) { 175 | 176 | this.renderTargetsHorizontal[ i ].setSize( resx, resy ); 177 | this.renderTargetsVertical[ i ].setSize( resx, resy ); 178 | 179 | this.separableBlurMaterials[ i ].uniforms[ "texSize" ].value = new THREE.Vector2( resx, resy ); 180 | 181 | resx = Math.round( resx / 2 ); 182 | resy = Math.round( resy / 2 ); 183 | 184 | } 185 | 186 | }, 187 | 188 | render: function ( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { 189 | 190 | this.oldClearColor.copy( renderer.getClearColor() ); 191 | this.oldClearAlpha = renderer.getClearAlpha(); 192 | var oldAutoClear = renderer.autoClear; 193 | renderer.autoClear = false; 194 | 195 | renderer.setClearColor( this.clearColor, 0 ); 196 | 197 | if ( maskActive ) renderer.context.disable( renderer.context.STENCIL_TEST ); 198 | 199 | // Render input to screen 200 | 201 | if ( this.renderToScreen ) { 202 | 203 | this.quad.material = this.basic; 204 | this.basic.map = readBuffer.texture; 205 | 206 | renderer.setRenderTarget( null ); 207 | renderer.clear(); 208 | renderer.render( this.scene, this.camera ); 209 | 210 | } 211 | 212 | // 1. Extract Bright Areas 213 | 214 | this.highPassUniforms[ "tDiffuse" ].value = readBuffer.texture; 215 | this.highPassUniforms[ "luminosityThreshold" ].value = this.threshold; 216 | this.quad.material = this.materialHighPassFilter; 217 | 218 | renderer.setRenderTarget( this.renderTargetBright ); 219 | renderer.clear(); 220 | renderer.render( this.scene, this.camera ); 221 | 222 | // 2. Blur All the mips progressively 223 | 224 | var inputRenderTarget = this.renderTargetBright; 225 | 226 | for ( var i = 0; i < this.nMips; i ++ ) { 227 | 228 | this.quad.material = this.separableBlurMaterials[ i ]; 229 | 230 | this.separableBlurMaterials[ i ].uniforms[ "colorTexture" ].value = inputRenderTarget.texture; 231 | this.separableBlurMaterials[ i ].uniforms[ "direction" ].value = THREE.UnrealBloomPass.BlurDirectionX; 232 | renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); 233 | renderer.clear(); 234 | renderer.render( this.scene, this.camera ); 235 | 236 | this.separableBlurMaterials[ i ].uniforms[ "colorTexture" ].value = this.renderTargetsHorizontal[ i ].texture; 237 | this.separableBlurMaterials[ i ].uniforms[ "direction" ].value = THREE.UnrealBloomPass.BlurDirectionY; 238 | renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); 239 | renderer.clear(); 240 | renderer.render( this.scene, this.camera ); 241 | 242 | inputRenderTarget = this.renderTargetsVertical[ i ]; 243 | 244 | } 245 | 246 | // Composite All the mips 247 | 248 | this.quad.material = this.compositeMaterial; 249 | this.compositeMaterial.uniforms[ "bloomStrength" ].value = this.strength; 250 | this.compositeMaterial.uniforms[ "bloomRadius" ].value = this.radius; 251 | this.compositeMaterial.uniforms[ "bloomTintColors" ].value = this.bloomTintColors; 252 | 253 | renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); 254 | renderer.clear(); 255 | renderer.render( this.scene, this.camera ); 256 | 257 | // Blend it additively over the input texture 258 | 259 | this.quad.material = this.materialCopy; 260 | this.copyUniforms[ "tDiffuse" ].value = this.renderTargetsHorizontal[ 0 ].texture; 261 | 262 | if ( maskActive ) renderer.context.enable( renderer.context.STENCIL_TEST ); 263 | 264 | 265 | if ( this.renderToScreen ) { 266 | 267 | renderer.setRenderTarget( null ); 268 | renderer.render( this.scene, this.camera ); 269 | 270 | } else { 271 | 272 | renderer.setRenderTarget( readBuffer ); 273 | renderer.render( this.scene, this.camera ); 274 | 275 | } 276 | 277 | // Restore renderer settings 278 | 279 | renderer.setClearColor( this.oldClearColor, this.oldClearAlpha ); 280 | renderer.autoClear = oldAutoClear; 281 | 282 | }, 283 | 284 | getSeperableBlurMaterial: function ( kernelRadius ) { 285 | 286 | return new THREE.ShaderMaterial( { 287 | 288 | defines: { 289 | "KERNEL_RADIUS": kernelRadius, 290 | "SIGMA": kernelRadius 291 | }, 292 | 293 | uniforms: { 294 | "colorTexture": { value: null }, 295 | "texSize": { value: new THREE.Vector2( 0.5, 0.5 ) }, 296 | "direction": { value: new THREE.Vector2( 0.5, 0.5 ) } 297 | }, 298 | 299 | vertexShader: 300 | "varying vec2 vUv;\n\ 301 | void main() {\n\ 302 | vUv = uv;\n\ 303 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 304 | }", 305 | 306 | fragmentShader: 307 | "#include \ 308 | varying vec2 vUv;\n\ 309 | uniform sampler2D colorTexture;\n\ 310 | uniform vec2 texSize;\ 311 | uniform vec2 direction;\ 312 | \ 313 | float gaussianPdf(in float x, in float sigma) {\ 314 | return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;\ 315 | }\ 316 | void main() {\n\ 317 | vec2 invSize = 1.0 / texSize;\ 318 | float fSigma = float(SIGMA);\ 319 | float weightSum = gaussianPdf(0.0, fSigma);\ 320 | vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;\ 321 | for( int i = 1; i < KERNEL_RADIUS; i ++ ) {\ 322 | float x = float(i);\ 323 | float w = gaussianPdf(x, fSigma);\ 324 | vec2 uvOffset = direction * invSize * x;\ 325 | vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb;\ 326 | vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb;\ 327 | diffuseSum += (sample1 + sample2) * w;\ 328 | weightSum += 2.0 * w;\ 329 | }\ 330 | gl_FragColor = vec4(diffuseSum/weightSum, 1.0);\n\ 331 | }" 332 | } ); 333 | 334 | }, 335 | 336 | getCompositeMaterial: function ( nMips ) { 337 | 338 | return new THREE.ShaderMaterial( { 339 | 340 | defines: { 341 | "NUM_MIPS": nMips 342 | }, 343 | 344 | uniforms: { 345 | "blurTexture1": { value: null }, 346 | "blurTexture2": { value: null }, 347 | "blurTexture3": { value: null }, 348 | "blurTexture4": { value: null }, 349 | "blurTexture5": { value: null }, 350 | "dirtTexture": { value: null }, 351 | "bloomStrength": { value: 1.0 }, 352 | "bloomFactors": { value: null }, 353 | "bloomTintColors": { value: null }, 354 | "bloomRadius": { value: 0.0 } 355 | }, 356 | 357 | vertexShader: 358 | "varying vec2 vUv;\n\ 359 | void main() {\n\ 360 | vUv = uv;\n\ 361 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\ 362 | }", 363 | 364 | fragmentShader: 365 | "varying vec2 vUv;\ 366 | uniform sampler2D blurTexture1;\ 367 | uniform sampler2D blurTexture2;\ 368 | uniform sampler2D blurTexture3;\ 369 | uniform sampler2D blurTexture4;\ 370 | uniform sampler2D blurTexture5;\ 371 | uniform sampler2D dirtTexture;\ 372 | uniform float bloomStrength;\ 373 | uniform float bloomRadius;\ 374 | uniform float bloomFactors[NUM_MIPS];\ 375 | uniform vec3 bloomTintColors[NUM_MIPS];\ 376 | \ 377 | float lerpBloomFactor(const in float factor) { \ 378 | float mirrorFactor = 1.2 - factor;\ 379 | return mix(factor, mirrorFactor, bloomRadius);\ 380 | }\ 381 | \ 382 | void main() {\ 383 | gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + \ 384 | lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + \ 385 | lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + \ 386 | lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + \ 387 | lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );\ 388 | }" 389 | } ); 390 | 391 | } 392 | 393 | } ); 394 | 395 | THREE.UnrealBloomPass.BlurDirectionX = new THREE.Vector2( 1.0, 0.0 ); 396 | THREE.UnrealBloomPass.BlurDirectionY = new THREE.Vector2( 0.0, 1.0 ); 397 | -------------------------------------------------------------------------------- /js/loaders/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = ( function () { 6 | 7 | // o object_name | g group_name 8 | var object_pattern = /^[og]\s*(.+)?/; 9 | // mtllib file_reference 10 | var material_library_pattern = /^mtllib /; 11 | // usemtl material_name 12 | var material_use_pattern = /^usemtl /; 13 | 14 | function ParserState() { 15 | 16 | var state = { 17 | objects: [], 18 | object: {}, 19 | 20 | vertices: [], 21 | normals: [], 22 | colors: [], 23 | uvs: [], 24 | 25 | materialLibraries: [], 26 | 27 | startObject: function ( name, fromDeclaration ) { 28 | 29 | // If the current object (initial from reset) is not from a g/o declaration in the parsed 30 | // file. We need to use it for the first parsed g/o to keep things in sync. 31 | if ( this.object && this.object.fromDeclaration === false ) { 32 | 33 | this.object.name = name; 34 | this.object.fromDeclaration = ( fromDeclaration !== false ); 35 | return; 36 | 37 | } 38 | 39 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 40 | 41 | if ( this.object && typeof this.object._finalize === 'function' ) { 42 | 43 | this.object._finalize( true ); 44 | 45 | } 46 | 47 | this.object = { 48 | name: name || '', 49 | fromDeclaration: ( fromDeclaration !== false ), 50 | 51 | geometry: { 52 | vertices: [], 53 | normals: [], 54 | colors: [], 55 | uvs: [] 56 | }, 57 | materials: [], 58 | smooth: true, 59 | 60 | startMaterial: function ( name, libraries ) { 61 | 62 | var previous = this._finalize( false ); 63 | 64 | // New usemtl declaration overwrites an inherited material, except if faces were declared 65 | // after the material, then it must be preserved for proper MultiMaterial continuation. 66 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { 67 | 68 | this.materials.splice( previous.index, 1 ); 69 | 70 | } 71 | 72 | var material = { 73 | index: this.materials.length, 74 | name: name || '', 75 | mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), 76 | smooth: ( previous !== undefined ? previous.smooth : this.smooth ), 77 | groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), 78 | groupEnd: - 1, 79 | groupCount: - 1, 80 | inherited: false, 81 | 82 | clone: function ( index ) { 83 | 84 | var cloned = { 85 | index: ( typeof index === 'number' ? index : this.index ), 86 | name: this.name, 87 | mtllib: this.mtllib, 88 | smooth: this.smooth, 89 | groupStart: 0, 90 | groupEnd: - 1, 91 | groupCount: - 1, 92 | inherited: false 93 | }; 94 | cloned.clone = this.clone.bind( cloned ); 95 | return cloned; 96 | 97 | } 98 | }; 99 | 100 | this.materials.push( material ); 101 | 102 | return material; 103 | 104 | }, 105 | 106 | currentMaterial: function () { 107 | 108 | if ( this.materials.length > 0 ) { 109 | 110 | return this.materials[ this.materials.length - 1 ]; 111 | 112 | } 113 | 114 | return undefined; 115 | 116 | }, 117 | 118 | _finalize: function ( end ) { 119 | 120 | var lastMultiMaterial = this.currentMaterial(); 121 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { 122 | 123 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 124 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 125 | lastMultiMaterial.inherited = false; 126 | 127 | } 128 | 129 | // Ignore objects tail materials if no face declarations followed them before a new o/g started. 130 | if ( end && this.materials.length > 1 ) { 131 | 132 | for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { 133 | 134 | if ( this.materials[ mi ].groupCount <= 0 ) { 135 | 136 | this.materials.splice( mi, 1 ); 137 | 138 | } 139 | 140 | } 141 | 142 | } 143 | 144 | // Guarantee at least one empty material, this makes the creation later more straight forward. 145 | if ( end && this.materials.length === 0 ) { 146 | 147 | this.materials.push( { 148 | name: '', 149 | smooth: this.smooth 150 | } ); 151 | 152 | } 153 | 154 | return lastMultiMaterial; 155 | 156 | } 157 | }; 158 | 159 | // Inherit previous objects material. 160 | // Spec tells us that a declared material must be set to all objects until a new material is declared. 161 | // If a usemtl declaration is encountered while this new object is being parsed, it will 162 | // overwrite the inherited material. Exception being that there was already face declarations 163 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation. 164 | 165 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { 166 | 167 | var declared = previousMaterial.clone( 0 ); 168 | declared.inherited = true; 169 | this.object.materials.push( declared ); 170 | 171 | } 172 | 173 | this.objects.push( this.object ); 174 | 175 | }, 176 | 177 | finalize: function () { 178 | 179 | if ( this.object && typeof this.object._finalize === 'function' ) { 180 | 181 | this.object._finalize( true ); 182 | 183 | } 184 | 185 | }, 186 | 187 | parseVertexIndex: function ( value, len ) { 188 | 189 | var index = parseInt( value, 10 ); 190 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 191 | 192 | }, 193 | 194 | parseNormalIndex: function ( value, len ) { 195 | 196 | var index = parseInt( value, 10 ); 197 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 198 | 199 | }, 200 | 201 | parseUVIndex: function ( value, len ) { 202 | 203 | var index = parseInt( value, 10 ); 204 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; 205 | 206 | }, 207 | 208 | addVertex: function ( a, b, c ) { 209 | 210 | var src = this.vertices; 211 | var dst = this.object.geometry.vertices; 212 | 213 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 214 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); 215 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); 216 | 217 | }, 218 | 219 | addVertexPoint: function ( a ) { 220 | 221 | var src = this.vertices; 222 | var dst = this.object.geometry.vertices; 223 | 224 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 225 | 226 | }, 227 | 228 | addVertexLine: function ( a ) { 229 | 230 | var src = this.vertices; 231 | var dst = this.object.geometry.vertices; 232 | 233 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 234 | 235 | }, 236 | 237 | addNormal: function ( a, b, c ) { 238 | 239 | var src = this.normals; 240 | var dst = this.object.geometry.normals; 241 | 242 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 243 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); 244 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); 245 | 246 | }, 247 | 248 | addColor: function ( a, b, c ) { 249 | 250 | var src = this.colors; 251 | var dst = this.object.geometry.colors; 252 | 253 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 254 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); 255 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); 256 | 257 | }, 258 | 259 | addUV: function ( a, b, c ) { 260 | 261 | var src = this.uvs; 262 | var dst = this.object.geometry.uvs; 263 | 264 | dst.push( src[ a + 0 ], src[ a + 1 ] ); 265 | dst.push( src[ b + 0 ], src[ b + 1 ] ); 266 | dst.push( src[ c + 0 ], src[ c + 1 ] ); 267 | 268 | }, 269 | 270 | addUVLine: function ( a ) { 271 | 272 | var src = this.uvs; 273 | var dst = this.object.geometry.uvs; 274 | 275 | dst.push( src[ a + 0 ], src[ a + 1 ] ); 276 | 277 | }, 278 | 279 | addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { 280 | 281 | var vLen = this.vertices.length; 282 | 283 | var ia = this.parseVertexIndex( a, vLen ); 284 | var ib = this.parseVertexIndex( b, vLen ); 285 | var ic = this.parseVertexIndex( c, vLen ); 286 | 287 | this.addVertex( ia, ib, ic ); 288 | 289 | if ( ua !== undefined && ua !== '' ) { 290 | 291 | var uvLen = this.uvs.length; 292 | ia = this.parseUVIndex( ua, uvLen ); 293 | ib = this.parseUVIndex( ub, uvLen ); 294 | ic = this.parseUVIndex( uc, uvLen ); 295 | this.addUV( ia, ib, ic ); 296 | 297 | } 298 | 299 | if ( na !== undefined && na !== '' ) { 300 | 301 | // Normals are many times the same. If so, skip function call and parseInt. 302 | var nLen = this.normals.length; 303 | ia = this.parseNormalIndex( na, nLen ); 304 | 305 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 306 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 307 | 308 | this.addNormal( ia, ib, ic ); 309 | 310 | } 311 | 312 | if ( this.colors.length > 0 ) { 313 | 314 | this.addColor( ia, ib, ic ); 315 | 316 | } 317 | 318 | }, 319 | 320 | addPointGeometry: function ( vertices ) { 321 | 322 | this.object.geometry.type = 'Points'; 323 | 324 | var vLen = this.vertices.length; 325 | 326 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 327 | 328 | this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) ); 329 | 330 | } 331 | 332 | }, 333 | 334 | addLineGeometry: function ( vertices, uvs ) { 335 | 336 | this.object.geometry.type = 'Line'; 337 | 338 | var vLen = this.vertices.length; 339 | var uvLen = this.uvs.length; 340 | 341 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 342 | 343 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); 344 | 345 | } 346 | 347 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { 348 | 349 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); 350 | 351 | } 352 | 353 | } 354 | 355 | }; 356 | 357 | state.startObject( '', false ); 358 | 359 | return state; 360 | 361 | } 362 | 363 | // 364 | 365 | function OBJLoader( manager ) { 366 | 367 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 368 | 369 | this.materials = null; 370 | 371 | } 372 | 373 | OBJLoader.prototype = { 374 | 375 | constructor: OBJLoader, 376 | 377 | load: function ( url, onLoad, onProgress, onError ) { 378 | 379 | var scope = this; 380 | 381 | var loader = new THREE.FileLoader( scope.manager ); 382 | loader.setPath( this.path ); 383 | loader.load( url, function ( text ) { 384 | 385 | onLoad( scope.parse( text ) ); 386 | 387 | }, onProgress, onError ); 388 | 389 | }, 390 | 391 | setPath: function ( value ) { 392 | 393 | this.path = value; 394 | 395 | return this; 396 | 397 | }, 398 | 399 | setMaterials: function ( materials ) { 400 | 401 | this.materials = materials; 402 | 403 | return this; 404 | 405 | }, 406 | 407 | parse: function ( text ) { 408 | 409 | console.time( 'OBJLoader' ); 410 | 411 | var state = new ParserState(); 412 | 413 | if ( text.indexOf( '\r\n' ) !== - 1 ) { 414 | 415 | // This is faster than String.split with regex that splits on both 416 | text = text.replace( /\r\n/g, '\n' ); 417 | 418 | } 419 | 420 | if ( text.indexOf( '\\\n' ) !== - 1 ) { 421 | 422 | // join lines separated by a line continuation character (\) 423 | text = text.replace( /\\\n/g, '' ); 424 | 425 | } 426 | 427 | var lines = text.split( '\n' ); 428 | var line = '', lineFirstChar = ''; 429 | var lineLength = 0; 430 | var result = []; 431 | 432 | // Faster to just trim left side of the line. Use if available. 433 | var trimLeft = ( typeof ''.trimLeft === 'function' ); 434 | 435 | for ( var i = 0, l = lines.length; i < l; i ++ ) { 436 | 437 | line = lines[ i ]; 438 | 439 | line = trimLeft ? line.trimLeft() : line.trim(); 440 | 441 | lineLength = line.length; 442 | 443 | if ( lineLength === 0 ) continue; 444 | 445 | lineFirstChar = line.charAt( 0 ); 446 | 447 | // @todo invoke passed in handler if any 448 | if ( lineFirstChar === '#' ) continue; 449 | 450 | if ( lineFirstChar === 'v' ) { 451 | 452 | var data = line.split( /\s+/ ); 453 | 454 | switch ( data[ 0 ] ) { 455 | 456 | case 'v': 457 | state.vertices.push( 458 | parseFloat( data[ 1 ] ), 459 | parseFloat( data[ 2 ] ), 460 | parseFloat( data[ 3 ] ) 461 | ); 462 | if ( data.length === 8 ) { 463 | 464 | state.colors.push( 465 | parseFloat( data[ 4 ] ), 466 | parseFloat( data[ 5 ] ), 467 | parseFloat( data[ 6 ] ) 468 | 469 | ); 470 | 471 | } 472 | break; 473 | case 'vn': 474 | state.normals.push( 475 | parseFloat( data[ 1 ] ), 476 | parseFloat( data[ 2 ] ), 477 | parseFloat( data[ 3 ] ) 478 | ); 479 | break; 480 | case 'vt': 481 | state.uvs.push( 482 | parseFloat( data[ 1 ] ), 483 | parseFloat( data[ 2 ] ) 484 | ); 485 | break; 486 | 487 | } 488 | 489 | } else if ( lineFirstChar === 'f' ) { 490 | 491 | var lineData = line.substr( 1 ).trim(); 492 | var vertexData = lineData.split( /\s+/ ); 493 | var faceVertices = []; 494 | 495 | // Parse the face vertex data into an easy to work with format 496 | 497 | for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { 498 | 499 | var vertex = vertexData[ j ]; 500 | 501 | if ( vertex.length > 0 ) { 502 | 503 | var vertexParts = vertex.split( '/' ); 504 | faceVertices.push( vertexParts ); 505 | 506 | } 507 | 508 | } 509 | 510 | // Draw an edge between the first vertex and all subsequent vertices to form an n-gon 511 | 512 | var v1 = faceVertices[ 0 ]; 513 | 514 | for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { 515 | 516 | var v2 = faceVertices[ j ]; 517 | var v3 = faceVertices[ j + 1 ]; 518 | 519 | state.addFace( 520 | v1[ 0 ], v2[ 0 ], v3[ 0 ], 521 | v1[ 1 ], v2[ 1 ], v3[ 1 ], 522 | v1[ 2 ], v2[ 2 ], v3[ 2 ] 523 | ); 524 | 525 | } 526 | 527 | } else if ( lineFirstChar === 'l' ) { 528 | 529 | var lineParts = line.substring( 1 ).trim().split( " " ); 530 | var lineVertices = [], lineUVs = []; 531 | 532 | if ( line.indexOf( "/" ) === - 1 ) { 533 | 534 | lineVertices = lineParts; 535 | 536 | } else { 537 | 538 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 539 | 540 | var parts = lineParts[ li ].split( "/" ); 541 | 542 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 543 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 544 | 545 | } 546 | 547 | } 548 | state.addLineGeometry( lineVertices, lineUVs ); 549 | 550 | } else if ( lineFirstChar === 'p' ) { 551 | 552 | var lineData = line.substr( 1 ).trim(); 553 | var pointData = lineData.split( " " ); 554 | 555 | state.addPointGeometry( pointData ); 556 | 557 | } else if ( ( result = object_pattern.exec( line ) ) !== null ) { 558 | 559 | // o object_name 560 | // or 561 | // g group_name 562 | 563 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 564 | // var name = result[ 0 ].substr( 1 ).trim(); 565 | var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); 566 | 567 | state.startObject( name ); 568 | 569 | } else if ( material_use_pattern.test( line ) ) { 570 | 571 | // material 572 | 573 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 574 | 575 | } else if ( material_library_pattern.test( line ) ) { 576 | 577 | // mtl file 578 | 579 | state.materialLibraries.push( line.substring( 7 ).trim() ); 580 | 581 | } else if ( lineFirstChar === 's' ) { 582 | 583 | result = line.split( ' ' ); 584 | 585 | // smooth shading 586 | 587 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 588 | // but does not define a usemtl for each face set. 589 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 590 | // This requires some care to not create extra material on each smooth value for "normal" obj files. 591 | // where explicit usemtl defines geometry groups. 592 | // Example asset: examples/models/obj/cerberus/Cerberus.obj 593 | 594 | /* 595 | * http://paulbourke.net/dataformats/obj/ 596 | * or 597 | * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf 598 | * 599 | * From chapter "Grouping" Syntax explanation "s group_number": 600 | * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. 601 | * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form 602 | * surfaces, smoothing groups are either turned on or off; there is no difference between values greater 603 | * than 0." 604 | */ 605 | if ( result.length > 1 ) { 606 | 607 | var value = result[ 1 ].trim().toLowerCase(); 608 | state.object.smooth = ( value !== '0' && value !== 'off' ); 609 | 610 | } else { 611 | 612 | // ZBrush can produce "s" lines #11707 613 | state.object.smooth = true; 614 | 615 | } 616 | var material = state.object.currentMaterial(); 617 | if ( material ) material.smooth = state.object.smooth; 618 | 619 | } else { 620 | 621 | // Handle null terminated files without exception 622 | if ( line === '\0' ) continue; 623 | 624 | throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); 625 | 626 | } 627 | 628 | } 629 | 630 | state.finalize(); 631 | 632 | var container = new THREE.Group(); 633 | container.materialLibraries = [].concat( state.materialLibraries ); 634 | 635 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 636 | 637 | var object = state.objects[ i ]; 638 | var geometry = object.geometry; 639 | var materials = object.materials; 640 | var isLine = ( geometry.type === 'Line' ); 641 | var isPoints = ( geometry.type === 'Points' ); 642 | var hasVertexColors = false; 643 | 644 | // Skip o/g line declarations that did not follow with any faces 645 | if ( geometry.vertices.length === 0 ) continue; 646 | 647 | var buffergeometry = new THREE.BufferGeometry(); 648 | 649 | buffergeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); 650 | 651 | if ( geometry.normals.length > 0 ) { 652 | 653 | buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); 654 | 655 | } else { 656 | 657 | buffergeometry.computeVertexNormals(); 658 | 659 | } 660 | 661 | if ( geometry.colors.length > 0 ) { 662 | 663 | hasVertexColors = true; 664 | buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) ); 665 | 666 | } 667 | 668 | if ( geometry.uvs.length > 0 ) { 669 | 670 | buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) ); 671 | 672 | } 673 | 674 | // Create materials 675 | 676 | var createdMaterials = []; 677 | 678 | for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { 679 | 680 | var sourceMaterial = materials[ mi ]; 681 | var material = undefined; 682 | 683 | if ( this.materials !== null ) { 684 | 685 | material = this.materials.create( sourceMaterial.name ); 686 | 687 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 688 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { 689 | 690 | var materialLine = new THREE.LineBasicMaterial(); 691 | THREE.Material.prototype.copy.call( materialLine, material ); 692 | materialLine.color.copy( material.color ); 693 | materialLine.lights = false; 694 | material = materialLine; 695 | 696 | } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) { 697 | 698 | var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } ); 699 | THREE.Material.prototype.copy.call( materialPoints, material ); 700 | materialPoints.color.copy( material.color ); 701 | materialPoints.map = material.map; 702 | materialPoints.lights = false; 703 | material = materialPoints; 704 | 705 | } 706 | 707 | } 708 | 709 | if ( ! material ) { 710 | 711 | if ( isLine ) { 712 | 713 | material = new THREE.LineBasicMaterial(); 714 | 715 | } else if ( isPoints ) { 716 | 717 | material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } ); 718 | 719 | } else { 720 | 721 | material = new THREE.MeshPhongMaterial(); 722 | 723 | } 724 | 725 | material.name = sourceMaterial.name; 726 | 727 | } 728 | 729 | material.flatShading = sourceMaterial.smooth ? false : true; 730 | material.vertexColors = hasVertexColors ? THREE.VertexColors : THREE.NoColors; 731 | 732 | createdMaterials.push( material ); 733 | 734 | } 735 | 736 | // Create mesh 737 | 738 | var mesh; 739 | 740 | if ( createdMaterials.length > 1 ) { 741 | 742 | for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { 743 | 744 | var sourceMaterial = materials[ mi ]; 745 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 746 | 747 | } 748 | 749 | if ( isLine ) { 750 | 751 | mesh = new THREE.LineSegments( buffergeometry, createdMaterials ); 752 | 753 | } else if ( isPoints ) { 754 | 755 | mesh = new THREE.Points( buffergeometry, createdMaterials ); 756 | 757 | } else { 758 | 759 | mesh = new THREE.Mesh( buffergeometry, createdMaterials ); 760 | 761 | } 762 | 763 | } else { 764 | 765 | if ( isLine ) { 766 | 767 | mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ); 768 | 769 | } else if ( isPoints ) { 770 | 771 | mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] ); 772 | 773 | } else { 774 | 775 | mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ); 776 | 777 | } 778 | 779 | } 780 | 781 | mesh.name = object.name; 782 | 783 | container.add( mesh ); 784 | 785 | } 786 | 787 | console.timeEnd( 'OBJLoader' ); 788 | 789 | return container; 790 | 791 | } 792 | 793 | }; 794 | 795 | return OBJLoader; 796 | 797 | } )(); 798 | -------------------------------------------------------------------------------- /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 | var needsUpdate = false; 543 | 544 | switch ( event.keyCode ) { 545 | 546 | case scope.keys.UP: 547 | pan( 0, scope.keyPanSpeed ); 548 | needsUpdate = true; 549 | break; 550 | 551 | case scope.keys.BOTTOM: 552 | pan( 0, - scope.keyPanSpeed ); 553 | needsUpdate = true; 554 | break; 555 | 556 | case scope.keys.LEFT: 557 | pan( scope.keyPanSpeed, 0 ); 558 | needsUpdate = true; 559 | break; 560 | 561 | case scope.keys.RIGHT: 562 | pan( - scope.keyPanSpeed, 0 ); 563 | needsUpdate = true; 564 | break; 565 | 566 | } 567 | 568 | if ( needsUpdate ) { 569 | 570 | // prevent the browser from scrolling on cursor keys 571 | event.preventDefault(); 572 | 573 | scope.update(); 574 | 575 | } 576 | 577 | 578 | } 579 | 580 | function handleTouchStartRotate( event ) { 581 | 582 | //console.log( 'handleTouchStartRotate' ); 583 | 584 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 585 | 586 | } 587 | 588 | function handleTouchStartDollyPan( event ) { 589 | 590 | //console.log( 'handleTouchStartDollyPan' ); 591 | 592 | if ( scope.enableZoom ) { 593 | 594 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 595 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 596 | 597 | var distance = Math.sqrt( dx * dx + dy * dy ); 598 | 599 | dollyStart.set( 0, distance ); 600 | 601 | } 602 | 603 | if ( scope.enablePan ) { 604 | 605 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 606 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 607 | 608 | panStart.set( x, y ); 609 | 610 | } 611 | 612 | } 613 | 614 | function handleTouchMoveRotate( event ) { 615 | 616 | //console.log( 'handleTouchMoveRotate' ); 617 | 618 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 619 | 620 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 621 | 622 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 623 | 624 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 625 | 626 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 627 | 628 | rotateStart.copy( rotateEnd ); 629 | 630 | scope.update(); 631 | 632 | } 633 | 634 | function handleTouchMoveDollyPan( event ) { 635 | 636 | //console.log( 'handleTouchMoveDollyPan' ); 637 | 638 | if ( scope.enableZoom ) { 639 | 640 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 641 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 642 | 643 | var distance = Math.sqrt( dx * dx + dy * dy ); 644 | 645 | dollyEnd.set( 0, distance ); 646 | 647 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 648 | 649 | dollyIn( dollyDelta.y ); 650 | 651 | dollyStart.copy( dollyEnd ); 652 | 653 | } 654 | 655 | if ( scope.enablePan ) { 656 | 657 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 658 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 659 | 660 | panEnd.set( x, y ); 661 | 662 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 663 | 664 | pan( panDelta.x, panDelta.y ); 665 | 666 | panStart.copy( panEnd ); 667 | 668 | } 669 | 670 | scope.update(); 671 | 672 | } 673 | 674 | function handleTouchEnd( event ) { 675 | 676 | //console.log( 'handleTouchEnd' ); 677 | 678 | } 679 | 680 | // 681 | // event handlers - FSM: listen for events and reset state 682 | // 683 | 684 | function onMouseDown( event ) { 685 | 686 | if ( scope.enabled === false ) return; 687 | 688 | // Prevent the browser from scrolling. 689 | 690 | event.preventDefault(); 691 | 692 | // Manually set the focus since calling preventDefault above 693 | // prevents the browser from setting it automatically. 694 | 695 | scope.domElement.focus ? scope.domElement.focus() : window.focus(); 696 | 697 | switch ( event.button ) { 698 | 699 | case scope.mouseButtons.LEFT: 700 | 701 | if ( event.ctrlKey || event.metaKey || event.shiftKey ) { 702 | 703 | if ( scope.enablePan === false ) return; 704 | 705 | handleMouseDownPan( event ); 706 | 707 | state = STATE.PAN; 708 | 709 | } else { 710 | 711 | if ( scope.enableRotate === false ) return; 712 | 713 | handleMouseDownRotate( event ); 714 | 715 | state = STATE.ROTATE; 716 | 717 | } 718 | 719 | break; 720 | 721 | case scope.mouseButtons.MIDDLE: 722 | 723 | if ( scope.enableZoom === false ) return; 724 | 725 | handleMouseDownDolly( event ); 726 | 727 | state = STATE.DOLLY; 728 | 729 | break; 730 | 731 | case scope.mouseButtons.RIGHT: 732 | 733 | if ( scope.enablePan === false ) return; 734 | 735 | handleMouseDownPan( event ); 736 | 737 | state = STATE.PAN; 738 | 739 | break; 740 | 741 | } 742 | 743 | if ( state !== STATE.NONE ) { 744 | 745 | document.addEventListener( 'mousemove', onMouseMove, false ); 746 | document.addEventListener( 'mouseup', onMouseUp, false ); 747 | 748 | scope.dispatchEvent( startEvent ); 749 | 750 | } 751 | 752 | } 753 | 754 | function onMouseMove( event ) { 755 | 756 | if ( scope.enabled === false ) return; 757 | 758 | event.preventDefault(); 759 | 760 | switch ( state ) { 761 | 762 | case STATE.ROTATE: 763 | 764 | if ( scope.enableRotate === false ) return; 765 | 766 | handleMouseMoveRotate( event ); 767 | 768 | break; 769 | 770 | case STATE.DOLLY: 771 | 772 | if ( scope.enableZoom === false ) return; 773 | 774 | handleMouseMoveDolly( event ); 775 | 776 | break; 777 | 778 | case STATE.PAN: 779 | 780 | if ( scope.enablePan === false ) return; 781 | 782 | handleMouseMovePan( event ); 783 | 784 | break; 785 | 786 | } 787 | 788 | } 789 | 790 | function onMouseUp( event ) { 791 | 792 | if ( scope.enabled === false ) return; 793 | 794 | handleMouseUp( event ); 795 | 796 | document.removeEventListener( 'mousemove', onMouseMove, false ); 797 | document.removeEventListener( 'mouseup', onMouseUp, false ); 798 | 799 | scope.dispatchEvent( endEvent ); 800 | 801 | state = STATE.NONE; 802 | 803 | } 804 | 805 | function onMouseWheel( event ) { 806 | 807 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 808 | 809 | event.preventDefault(); 810 | event.stopPropagation(); 811 | 812 | scope.dispatchEvent( startEvent ); 813 | 814 | handleMouseWheel( event ); 815 | 816 | scope.dispatchEvent( endEvent ); 817 | 818 | } 819 | 820 | function onKeyDown( event ) { 821 | 822 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 823 | 824 | handleKeyDown( event ); 825 | 826 | } 827 | 828 | function onTouchStart( event ) { 829 | 830 | if ( scope.enabled === false ) return; 831 | 832 | event.preventDefault(); 833 | 834 | switch ( event.touches.length ) { 835 | 836 | case 1: // one-fingered touch: rotate 837 | 838 | if ( scope.enableRotate === false ) return; 839 | 840 | handleTouchStartRotate( event ); 841 | 842 | state = STATE.TOUCH_ROTATE; 843 | 844 | break; 845 | 846 | case 2: // two-fingered touch: dolly-pan 847 | 848 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 849 | 850 | handleTouchStartDollyPan( event ); 851 | 852 | state = STATE.TOUCH_DOLLY_PAN; 853 | 854 | break; 855 | 856 | default: 857 | 858 | state = STATE.NONE; 859 | 860 | } 861 | 862 | if ( state !== STATE.NONE ) { 863 | 864 | scope.dispatchEvent( startEvent ); 865 | 866 | } 867 | 868 | } 869 | 870 | function onTouchMove( event ) { 871 | 872 | if ( scope.enabled === false ) return; 873 | 874 | event.preventDefault(); 875 | event.stopPropagation(); 876 | 877 | switch ( event.touches.length ) { 878 | 879 | case 1: // one-fingered touch: rotate 880 | 881 | if ( scope.enableRotate === false ) return; 882 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed? 883 | 884 | handleTouchMoveRotate( event ); 885 | 886 | break; 887 | 888 | case 2: // two-fingered touch: dolly-pan 889 | 890 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 891 | if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed? 892 | 893 | handleTouchMoveDollyPan( event ); 894 | 895 | break; 896 | 897 | default: 898 | 899 | state = STATE.NONE; 900 | 901 | } 902 | 903 | } 904 | 905 | function onTouchEnd( event ) { 906 | 907 | if ( scope.enabled === false ) return; 908 | 909 | handleTouchEnd( event ); 910 | 911 | scope.dispatchEvent( endEvent ); 912 | 913 | state = STATE.NONE; 914 | 915 | } 916 | 917 | function onContextMenu( event ) { 918 | 919 | if ( scope.enabled === false ) return; 920 | 921 | event.preventDefault(); 922 | 923 | } 924 | 925 | // 926 | 927 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 928 | 929 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 930 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 931 | 932 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 933 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 934 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 935 | 936 | window.addEventListener( 'keydown', onKeyDown, false ); 937 | 938 | // force an update at start 939 | 940 | this.update(); 941 | 942 | }; 943 | 944 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 945 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 946 | 947 | Object.defineProperties( THREE.OrbitControls.prototype, { 948 | 949 | center: { 950 | 951 | get: function () { 952 | 953 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 954 | return this.target; 955 | 956 | } 957 | 958 | }, 959 | 960 | // backward compatibility 961 | 962 | noZoom: { 963 | 964 | get: function () { 965 | 966 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 967 | return ! this.enableZoom; 968 | 969 | }, 970 | 971 | set: function ( value ) { 972 | 973 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 974 | this.enableZoom = ! value; 975 | 976 | } 977 | 978 | }, 979 | 980 | noRotate: { 981 | 982 | get: function () { 983 | 984 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 985 | return ! this.enableRotate; 986 | 987 | }, 988 | 989 | set: function ( value ) { 990 | 991 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 992 | this.enableRotate = ! value; 993 | 994 | } 995 | 996 | }, 997 | 998 | noPan: { 999 | 1000 | get: function () { 1001 | 1002 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1003 | return ! this.enablePan; 1004 | 1005 | }, 1006 | 1007 | set: function ( value ) { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1010 | this.enablePan = ! value; 1011 | 1012 | } 1013 | 1014 | }, 1015 | 1016 | noKeys: { 1017 | 1018 | get: function () { 1019 | 1020 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1021 | return ! this.enableKeys; 1022 | 1023 | }, 1024 | 1025 | set: function ( value ) { 1026 | 1027 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1028 | this.enableKeys = ! value; 1029 | 1030 | } 1031 | 1032 | }, 1033 | 1034 | staticMoving: { 1035 | 1036 | get: function () { 1037 | 1038 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1039 | return ! this.enableDamping; 1040 | 1041 | }, 1042 | 1043 | set: function ( value ) { 1044 | 1045 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1046 | this.enableDamping = ! value; 1047 | 1048 | } 1049 | 1050 | }, 1051 | 1052 | dynamicDampingFactor: { 1053 | 1054 | get: function () { 1055 | 1056 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1057 | return this.dampingFactor; 1058 | 1059 | }, 1060 | 1061 | set: function ( value ) { 1062 | 1063 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1064 | this.dampingFactor = value; 1065 | 1066 | } 1067 | 1068 | } 1069 | 1070 | } ); 1071 | -------------------------------------------------------------------------------- /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