├── .gitignore ├── img ├── mesh.jpg ├── basic.jpg ├── image.jpg ├── noise.jpg ├── animation.jpg ├── morph_0.jpg ├── morph_1.jpg └── morph_2.jpg ├── model ├── bust.bin └── bust.js ├── glsl ├── basic │ ├── render_fs.glsl │ ├── simulation_vs.glsl │ ├── simulation_fs.glsl │ └── render_vs.glsl ├── image │ ├── render_fs.glsl │ ├── simulation_vs.glsl │ ├── simulation_fs.glsl │ └── render_vs.glsl ├── morph │ ├── render_fs.glsl │ ├── simulation_vs.glsl │ ├── simulation_fs.glsl │ └── render_vs.glsl ├── mesh │ ├── simulation_vs.glsl │ ├── simulation_fs.glsl │ ├── render_fs.glsl │ └── render_vs.glsl └── noise │ ├── simulation_vs.glsl │ ├── render_fs.glsl │ ├── render_vs.glsl │ └── simulation_fs.glsl ├── README.md ├── ShaderLoader.js ├── fbo.js ├── basic.html ├── mesh.html ├── image.html ├── noise.html ├── morph.html └── vendor ├── BinaryLoader.js └── OrbitControls.js /.gitignore: -------------------------------------------------------------------------------- 1 | /img/compo.psd 2 | 3 | .idea/ 4 | /convert 5 | -------------------------------------------------------------------------------- /img/mesh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/img/mesh.jpg -------------------------------------------------------------------------------- /img/basic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/img/basic.jpg -------------------------------------------------------------------------------- /img/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/img/image.jpg -------------------------------------------------------------------------------- /img/noise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/img/noise.jpg -------------------------------------------------------------------------------- /model/bust.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/model/bust.bin -------------------------------------------------------------------------------- /img/animation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/img/animation.jpg -------------------------------------------------------------------------------- /img/morph_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/img/morph_0.jpg -------------------------------------------------------------------------------- /img/morph_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/img/morph_1.jpg -------------------------------------------------------------------------------- /img/morph_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicoptere/FBO/HEAD/img/morph_2.jpg -------------------------------------------------------------------------------- /glsl/basic/render_fs.glsl: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | gl_FragColor = vec4( vec3( 1. ), .25 ); 4 | } -------------------------------------------------------------------------------- /glsl/image/render_fs.glsl: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | gl_FragColor = vec4( vec3( .5 ), 1. ); 4 | } -------------------------------------------------------------------------------- /glsl/morph/render_fs.glsl: -------------------------------------------------------------------------------- 1 | void main() 2 | { 3 | gl_FragColor = vec4( vec3( 1. ), .25 ); 4 | } -------------------------------------------------------------------------------- /glsl/mesh/simulation_vs.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | void main() { 3 | vUv = vec2(uv.x, uv.y); 4 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 5 | } -------------------------------------------------------------------------------- /glsl/basic/simulation_vs.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | void main() { 3 | vUv = vec2(uv.x, uv.y); 4 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 5 | } -------------------------------------------------------------------------------- /glsl/image/simulation_vs.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | void main() { 3 | vUv = vec2(uv.x, uv.y); 4 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 5 | } -------------------------------------------------------------------------------- /glsl/morph/simulation_vs.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | void main() { 3 | vUv = vec2(uv.x, uv.y); 4 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 5 | } -------------------------------------------------------------------------------- /glsl/noise/simulation_vs.glsl: -------------------------------------------------------------------------------- 1 | varying vec2 vUv; 2 | varying float fragDepth; 3 | void main() { 4 | vUv = vec2(uv.x, uv.y); 5 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); 6 | } -------------------------------------------------------------------------------- /glsl/basic/simulation_fs.glsl: -------------------------------------------------------------------------------- 1 | //basic simulation: displays the particles in place. 2 | uniform sampler2D positions; 3 | varying vec2 vUv; 4 | void main() { 5 | 6 | vec3 pos = texture2D( positions, vUv ).rgb; 7 | gl_FragColor = vec4( pos,1.0 ); 8 | } -------------------------------------------------------------------------------- /glsl/image/simulation_fs.glsl: -------------------------------------------------------------------------------- 1 | //basic simulation: displays the particles in place. 2 | uniform sampler2D positions; 3 | varying vec2 vUv; 4 | void main() { 5 | 6 | vec3 pos = texture2D( positions, vUv ).rgb; 7 | gl_FragColor = vec4( pos,1.0 ); 8 | } -------------------------------------------------------------------------------- /glsl/mesh/simulation_fs.glsl: -------------------------------------------------------------------------------- 1 | //basic simulation: displays the particles in place. 2 | uniform sampler2D positions; 3 | varying vec2 vUv; 4 | void main() { 5 | 6 | vec3 pos = texture2D( positions, vUv ).rgb; 7 | gl_FragColor = vec4( pos,1.0 ); 8 | } -------------------------------------------------------------------------------- /glsl/noise/render_fs.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 nearFar; 2 | uniform vec3 small; 3 | uniform vec3 big; 4 | 5 | varying float size; 6 | void main() 7 | { 8 | 9 | 10 | 11 | gl_FragColor = vec4( small, .2 ); 12 | 13 | if( size > 1. ) 14 | { 15 | gl_FragColor = vec4( big * vec3( 1. - length( gl_PointCoord.xy-vec2(.5) ) ) * 1.5, .95 ); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FBO 2 | using FBO to render particles in THREE.js 3 | [longer article](http://barradeau.com/blog/?p=621) 4 | 5 | 6 | ## UPDATE 210525 7 | 8 | [Mario Carrillo](https://twitter.com/marioecg) was kind enough to port the code samples to ES6, something I’ve been willing to do for years. 9 | so check out his repo: https://github.com/marioecg/gpu-party/ ( and check out his work while you’re at it ) 10 | -------------------------------------------------------------------------------- /glsl/morph/simulation_fs.glsl: -------------------------------------------------------------------------------- 1 | 2 | // simulation 3 | uniform sampler2D textureA; 4 | uniform sampler2D textureB; 5 | uniform float timer; 6 | 7 | varying vec2 vUv; 8 | void main() { 9 | 10 | //origin 11 | vec3 origin = texture2D( textureA, vUv ).xyz; 12 | 13 | //destination 14 | vec3 destination = texture2D( textureB, vUv ).xyz; 15 | 16 | //lerp 17 | vec3 pos = mix( origin, destination, timer ); 18 | gl_FragColor = vec4( pos,1.0 ); 19 | 20 | } -------------------------------------------------------------------------------- /model/bust.js: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "metadata" : 4 | { 5 | "formatVersion" : 3.1, 6 | "sourceFile" : "bust.obj", 7 | "generatedBy" : "OBJConverter", 8 | "vertices" : 47516, 9 | "faces" : 95028, 10 | "normals" : 0, 11 | "uvs" : 0, 12 | "materials" : 0 13 | }, 14 | 15 | "materials": [ { 16 | "DbgColor" : 15658734, 17 | "DbgIndex" : 0, 18 | "DbgName" : "default" 19 | }], 20 | 21 | "buffers": "bust.bin" 22 | 23 | } 24 | -------------------------------------------------------------------------------- /glsl/basic/render_vs.glsl: -------------------------------------------------------------------------------- 1 | 2 | //float texture containing the positions of each particle 3 | uniform sampler2D positions; 4 | 5 | //size 6 | uniform float pointSize; 7 | 8 | void main() { 9 | 10 | //the mesh is a nomrliazed square so the uvs = the xy positions of the vertices 11 | vec3 pos = texture2D( positions, position.xy ).xyz; 12 | 13 | //pos now contains the position of a point in space taht can be transformed 14 | gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 ); 15 | 16 | gl_PointSize = pointSize; 17 | } -------------------------------------------------------------------------------- /glsl/image/render_vs.glsl: -------------------------------------------------------------------------------- 1 | 2 | //float texture containing the positions of each particle 3 | uniform sampler2D positions; 4 | 5 | //size 6 | uniform float pointSize; 7 | 8 | void main() { 9 | 10 | //the mesh is a nomrliazed square so the uvs = the xy positions of the vertices 11 | vec3 pos = texture2D( positions, position.xy ).xyz; 12 | 13 | //pos now contains the position of a point in space taht can be transformed 14 | gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 ); 15 | 16 | gl_PointSize = pointSize; 17 | } -------------------------------------------------------------------------------- /glsl/morph/render_vs.glsl: -------------------------------------------------------------------------------- 1 | 2 | //float texture containing the positions of each particle 3 | uniform sampler2D positions; 4 | 5 | //size 6 | uniform float pointSize; 7 | 8 | void main() { 9 | 10 | //the mesh is a nomrliazed square so the uvs = the xy positions of the vertices 11 | vec3 pos = texture2D( positions, position.xy ).xyz; 12 | 13 | //pos now contains the position of a point in space taht can be transformed 14 | gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 ); 15 | 16 | gl_PointSize = pointSize; 17 | } -------------------------------------------------------------------------------- /glsl/noise/render_vs.glsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | //float texture containing the positions of each particle 4 | uniform sampler2D positions; 5 | uniform vec2 nearFar; 6 | uniform float pointSize; 7 | 8 | varying float size; 9 | void main() { 10 | 11 | //the mesh is a nomrliazed square so the uvs = the xy positions of the vertices 12 | vec3 pos = texture2D( positions, position.xy ).xyz; 13 | 14 | //pos now contains the position of a point in space taht can be transformed 15 | gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 ); 16 | 17 | //size 18 | gl_PointSize = size = max( 1., ( step( 1. - ( 1. / 512. ), position.x ) ) * pointSize ); 19 | 20 | 21 | } -------------------------------------------------------------------------------- /glsl/mesh/render_fs.glsl: -------------------------------------------------------------------------------- 1 | uniform vec2 nearFar; 2 | varying float size; 3 | #ifdef USE_LOGDEPTHBUF 4 | uniform float logDepthBufFC; 5 | #ifdef USE_LOGDEPTHBUF_EXT 6 | varying float vFragDepth; 7 | #endif 8 | #endif 9 | void main() 10 | { 11 | 12 | if( size < 8. )discard; 13 | 14 | #if defined(USE_LOGDEPTHBUF) && defined(USE_LOGDEPTHBUF_EXT) 15 | gl_FragDepthEXT = log2(vFragDepth) * logDepthBufFC * 0.5; 16 | #endif 17 | 18 | #ifdef USE_LOGDEPTHBUF_EXT 19 | float depth = gl_FragDepthEXT / gl_FragCoord.w; 20 | #else 21 | float depth = gl_FragCoord.z / gl_FragCoord.w; 22 | #endif 23 | 24 | float color = 1.0 - smoothstep( nearFar.x, nearFar.y, depth ); 25 | gl_FragColor = vec4( vec3( color ), 1. ); 26 | 27 | } -------------------------------------------------------------------------------- /glsl/mesh/render_vs.glsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | //float texture containing the positions of each particle 4 | uniform sampler2D positions; 5 | 6 | //size 7 | uniform vec2 nearFar; 8 | uniform float pointSize; 9 | varying float size; 10 | 11 | #define EPSILON 1e-6 12 | #ifdef USE_LOGDEPTHBUF 13 | #ifdef USE_LOGDEPTHBUF_EXT 14 | varying float vFragDepth; 15 | #endif 16 | uniform float logDepthBufFC; 17 | #endif 18 | void main() { 19 | 20 | //the mesh is a nomrliazed square so the uvs = the xy positions of the vertices 21 | vec3 pos = texture2D( positions, position.xy ).xyz; 22 | 23 | //pos now contains the position of a point in space taht can be transformed 24 | gl_Position = projectionMatrix * modelViewMatrix * vec4( pos, 1.0 ); 25 | 26 | #ifdef USE_LOGDEPTHBUF 27 | gl_Position.z = log2(max( EPSILON, gl_Position.w + 1.0 )) * logDepthBufFC; 28 | 29 | #ifdef USE_LOGDEPTHBUF_EXT 30 | vFragDepth = 1.0 + gl_Position.w; 31 | #else 32 | gl_Position.z = (gl_Position.z - 1.0) * gl_Position.w; 33 | #endif 34 | #endif 35 | 36 | 37 | float depth = 1.0 - smoothstep( nearFar.x, nearFar.y, gl_Position.z / gl_Position.w ); 38 | gl_PointSize = size = depth * pointSize; 39 | 40 | 41 | } -------------------------------------------------------------------------------- /ShaderLoader.js: -------------------------------------------------------------------------------- 1 | var ShaderLoader = function() 2 | { 3 | // Shaders 4 | 5 | ShaderLoader.get = function( id ) 6 | { 7 | return ShaderLoader.shaders[ id ]; 8 | }; 9 | 10 | }; 11 | 12 | ShaderLoader.prototype = 13 | { 14 | 15 | loadShaders : function( shaders, baseUrl, callback ) 16 | { 17 | ShaderLoader.shaders = shaders; 18 | 19 | this.baseUrl = baseUrl || "./"; 20 | this.callback = callback; 21 | this.batchLoad( this, 'onShadersReady' ); 22 | 23 | }, 24 | 25 | batchLoad : function( scope, callback ) 26 | { 27 | var queue = 0; 28 | for ( var name in ShaderLoader.shaders ) { 29 | 30 | queue++; 31 | var req = new XMLHttpRequest(); 32 | req.onload = loadHandler( name, req ); 33 | req.open( 'get', scope.baseUrl + name + '.glsl', true ); 34 | req.send(); 35 | } 36 | 37 | function loadHandler( name, req ) { 38 | return function() 39 | { 40 | ShaderLoader.shaders[ name ] = req.responseText; 41 | if ( --queue <= 0 ) scope[ callback ](); 42 | }; 43 | } 44 | }, 45 | 46 | onShadersReady : function() 47 | { 48 | if( this.callback ) this.callback(); 49 | } 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /fbo.js: -------------------------------------------------------------------------------- 1 | var FBO = function( exports ){ 2 | 3 | var scene, orthoCamera, rtt; 4 | exports.init = function( width, height, renderer, simulationMaterial, renderMaterial ){ 5 | 6 | var gl = renderer.getContext(); 7 | 8 | //1 we need FLOAT Textures to store positions 9 | //https://github.com/KhronosGroup/WebGL/blob/master/sdk/tests/conformance/extensions/oes-texture-float.html 10 | if (!gl.getExtension("OES_texture_float")){ 11 | throw new Error( "float textures not supported" ); 12 | } 13 | 14 | //2 we need to access textures from within the vertex shader 15 | //https://github.com/KhronosGroup/WebGL/blob/90ceaac0c4546b1aad634a6a5c4d2dfae9f4d124/conformance-suites/1.0.0/extra/webgl-info.html 16 | if( gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) == 0 ) { 17 | throw new Error( "vertex shader cannot read textures" ); 18 | } 19 | 20 | //3 rtt setup 21 | scene = new THREE.Scene(); 22 | orthoCamera = new THREE.OrthographicCamera(-1,1,1,-1,1/Math.pow( 2, 53 ),1 ); 23 | 24 | //4 create a target texture 25 | var options = { 26 | minFilter: THREE.NearestFilter,//important as we want to sample square pixels 27 | magFilter: THREE.NearestFilter,// 28 | format: THREE.RGBAFormat,//180407 changed to RGBAFormat 29 | type:THREE.FloatType//important as we need precise coordinates (not ints) 30 | }; 31 | rtt = new THREE.WebGLRenderTarget( width,height, options); 32 | 33 | 34 | //5 the simulation: 35 | //create a bi-unit quadrilateral and uses the simulation material to update the Float Texture 36 | var geom = new THREE.BufferGeometry(); 37 | geom.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array([ -1,-1,0, 1,-1,0, 1,1,0, -1,-1, 0, 1, 1, 0, -1,1,0 ]), 3 ) ); 38 | geom.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array([ 0,1, 1,1, 1,0, 0,1, 1,0, 0,0 ]), 2 ) ); 39 | scene.add( new THREE.Mesh( geom, simulationMaterial ) ); 40 | 41 | 42 | //6 the particles: 43 | //create a vertex buffer of size width * height with normalized coordinates 44 | var l = (width * height ); 45 | var vertices = new Float32Array( l * 3 ); 46 | for ( var i = 0; i < l; i++ ) { 47 | 48 | var i3 = i * 3; 49 | vertices[ i3 ] = ( i % width ) / width ; 50 | vertices[ i3 + 1 ] = ( i / width ) / height; 51 | } 52 | 53 | //create the particles geometry 54 | var geometry = new THREE.BufferGeometry(); 55 | geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); 56 | 57 | //the rendermaterial is used to render the particles 58 | exports.particles = new THREE.Points( geometry, renderMaterial ); 59 | exports.renderer = renderer; 60 | 61 | }; 62 | 63 | //7 update loop 64 | exports.update = function(){ 65 | 66 | //1 update the simulation and render the result in a target texture 67 | exports.renderer.render( scene, orthoCamera, rtt, true ); 68 | 69 | //2 use the result of the swap as the new position for the particles' renderer 70 | exports.particles.material.uniforms.positions.value = rtt; 71 | 72 | }; 73 | return exports; 74 | }({}); 75 | -------------------------------------------------------------------------------- /glsl/noise/simulation_fs.glsl: -------------------------------------------------------------------------------- 1 | 2 | // simulation 3 | 4 | varying vec2 vUv; 5 | uniform sampler2D texture; 6 | uniform float timer; 7 | uniform float frequency; 8 | uniform float amplitude; 9 | uniform float maxDistance; 10 | 11 | // 12 | // Description : Array and textureless GLSL 2D simplex noise function. 13 | // Author : Ian McEwan, Ashima Arts. 14 | // Maintainer : ijm 15 | // Lastmod : 20110822 (ijm) 16 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 17 | // Distributed under the MIT License. See LICENSE file. 18 | // https://github.com/ashima/webgl-noise 19 | // 20 | 21 | vec3 mod289(vec3 x) { 22 | return x - floor(x * (1.0 / 289.0)) * 289.0; 23 | } 24 | 25 | vec2 mod289(vec2 x) { 26 | return x - floor(x * (1.0 / 289.0)) * 289.0; 27 | } 28 | 29 | vec3 permute(vec3 x) { 30 | return mod289(((x*34.0)+1.0)*x); 31 | } 32 | 33 | float noise(vec2 v) 34 | { 35 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 36 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 37 | -0.577350269189626, // -1.0 + 2.0 * C.x 38 | 0.024390243902439); // 1.0 / 41.0 39 | // First corner 40 | vec2 i = floor(v + dot(v, C.yy) ); 41 | vec2 x0 = v - i + dot(i, C.xx); 42 | 43 | // Other corners 44 | vec2 i1; 45 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 46 | //i1.y = 1.0 - i1.x; 47 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 48 | // x0 = x0 - 0.0 + 0.0 * C.xx ; 49 | // x1 = x0 - i1 + 1.0 * C.xx ; 50 | // x2 = x0 - 1.0 + 2.0 * C.xx ; 51 | vec4 x12 = x0.xyxy + C.xxzz; 52 | x12.xy -= i1; 53 | 54 | // Permutations 55 | i = mod289(i); // Avoid truncation effects in permutation 56 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) 57 | + i.x + vec3(0.0, i1.x, 1.0 )); 58 | 59 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); 60 | m = m*m ; 61 | m = m*m ; 62 | 63 | // Gradients: 41 points uniformly over a line, mapped onto a diamond. 64 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 65 | 66 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 67 | vec3 h = abs(x) - 0.5; 68 | vec3 ox = floor(x + 0.5); 69 | vec3 a0 = x - ox; 70 | 71 | // Normalise gradients implicitly by scaling m 72 | // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 73 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); 74 | 75 | // Compute final noise value at P 76 | vec3 g; 77 | g.x = a0.x * x0.x + h.x * x0.y; 78 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; 79 | return 130.0 * dot(m, g); 80 | } 81 | 82 | vec3 curl(float x, float y, float z) 83 | { 84 | 85 | float eps = 1., eps2 = 2. * eps; 86 | float n1, n2, a, b; 87 | 88 | x += timer * .05; 89 | y += timer * .05; 90 | z += timer * .05; 91 | 92 | vec3 curl = vec3(0.); 93 | 94 | n1 = noise(vec2( x, y + eps )); 95 | n2 = noise(vec2( x, y - eps )); 96 | a = (n1 - n2)/eps2; 97 | 98 | n1 = noise(vec2( x, z + eps)); 99 | n2 = noise(vec2( x, z - eps)); 100 | b = (n1 - n2)/eps2; 101 | 102 | curl.x = a - b; 103 | 104 | n1 = noise(vec2( y, z + eps)); 105 | n2 = noise(vec2( y, z - eps)); 106 | a = (n1 - n2)/eps2; 107 | 108 | n1 = noise(vec2( x + eps, z)); 109 | n2 = noise(vec2( x + eps, z)); 110 | b = (n1 - n2)/eps2; 111 | 112 | curl.y = a - b; 113 | 114 | n1 = noise(vec2( x + eps, y)); 115 | n2 = noise(vec2( x - eps, y)); 116 | a = (n1 - n2)/eps2; 117 | 118 | n1 = noise(vec2( y + eps, z)); 119 | n2 = noise(vec2( y - eps, z)); 120 | b = (n1 - n2)/eps2; 121 | 122 | curl.z = a - b; 123 | 124 | return curl; 125 | } 126 | 127 | 128 | 129 | void main() { 130 | 131 | vec3 pos = texture2D( texture, vUv ).xyz; 132 | 133 | vec3 tar = pos + curl( pos.x * frequency, pos.y * frequency, pos.z * frequency ) * amplitude; 134 | 135 | float d = length( pos-tar ) / maxDistance; 136 | pos = mix( pos, tar, pow( d, 5. ) ); 137 | 138 | gl_FragColor = vec4( pos, 1. ); 139 | 140 | } -------------------------------------------------------------------------------- /basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FBO 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /mesh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FBO 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FBO 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /noise.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FBO 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /morph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FBO 6 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /vendor/BinaryLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.BinaryLoader = function ( manager ) { 6 | 7 | if ( typeof manager === 'boolean' ) { 8 | 9 | console.warn( 'THREE.BinaryLoader: showStatus parameter has been removed from constructor.' ); 10 | manager = undefined; 11 | 12 | } 13 | 14 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 15 | 16 | }; 17 | 18 | THREE.BinaryLoader.prototype = { 19 | 20 | constructor: THREE.BinaryLoader, 21 | 22 | // Deprecated 23 | 24 | get statusDomElement () { 25 | 26 | if ( this._statusDomElement === undefined ) { 27 | 28 | this._statusDomElement = document.createElement( 'div' ); 29 | 30 | } 31 | 32 | console.warn( 'THREE.BinaryLoader: .statusDomElement has been removed.' ); 33 | return this._statusDomElement; 34 | 35 | }, 36 | 37 | // Load models generated by slim OBJ converter with BINARY option (converter_obj_three_slim.py -t binary) 38 | // - binary models consist of two files: JS and BIN 39 | // - parameters 40 | // - url (required) 41 | // - callback (required) 42 | // - texturePath (optional: if not specified, textures will be assumed to be in the same folder as JS model file) 43 | // - binaryPath (optional: if not specified, binary file will be assumed to be in the same folder as JS model file) 44 | load: function ( url, onLoad, onProgress, onError ) { 45 | 46 | // todo: unify load API to for easier SceneLoader use 47 | 48 | var texturePath = this.texturePath || THREE.Loader.prototype.extractUrlBase( url ); 49 | var binaryPath = this.binaryPath || THREE.Loader.prototype.extractUrlBase( url ); 50 | 51 | // #1 load JS part via web worker 52 | 53 | var scope = this; 54 | 55 | var jsonloader = new THREE.XHRLoader( this.manager ); 56 | jsonloader.setCrossOrigin( this.crossOrigin ); 57 | jsonloader.load( url, function ( data ) { 58 | 59 | var json = JSON.parse( data ); 60 | 61 | var bufferUrl = binaryPath + json.buffers; 62 | 63 | var bufferLoader = new THREE.XHRLoader( scope.manager ); 64 | bufferLoader.setCrossOrigin( scope.crossOrigin ); 65 | bufferLoader.setResponseType( 'arraybuffer' ); 66 | bufferLoader.load( bufferUrl, function ( bufData ) { 67 | 68 | // IEWEBGL needs this ??? 69 | //buffer = ( new Uint8Array( xhr.responseBody ) ).buffer; 70 | 71 | //// iOS and other XMLHttpRequest level 1 ??? 72 | 73 | scope.parse( bufData, onLoad, texturePath, json.materials ); 74 | 75 | }, onProgress, onError ); 76 | 77 | }, onProgress, onError ); 78 | 79 | }, 80 | 81 | setBinaryPath: function ( value ) { 82 | 83 | this.binaryPath = value; 84 | 85 | }, 86 | 87 | setCrossOrigin: function ( value ) { 88 | 89 | this.crossOrigin = value; 90 | 91 | }, 92 | 93 | setTexturePath: function ( value ) { 94 | 95 | this.texturePath = value; 96 | 97 | }, 98 | 99 | parse: function ( data, callback, texturePath, jsonMaterials ) { 100 | 101 | var Model = function ( texturePath ) { 102 | 103 | var scope = this, 104 | currentOffset = 0, 105 | md, 106 | normals = [], 107 | uvs = [], 108 | start_tri_flat, start_tri_smooth, start_tri_flat_uv, start_tri_smooth_uv, 109 | start_quad_flat, start_quad_smooth, start_quad_flat_uv, start_quad_smooth_uv, 110 | tri_size, quad_size, 111 | len_tri_flat, len_tri_smooth, len_tri_flat_uv, len_tri_smooth_uv, 112 | len_quad_flat, len_quad_smooth, len_quad_flat_uv, len_quad_smooth_uv; 113 | 114 | 115 | THREE.Geometry.call( this ); 116 | 117 | md = parseMetaData( data, currentOffset ); 118 | 119 | currentOffset += md.header_bytes; 120 | /* 121 | md.vertex_index_bytes = Uint32Array.BYTES_PER_ELEMENT; 122 | md.material_index_bytes = Uint16Array.BYTES_PER_ELEMENT; 123 | md.normal_index_bytes = Uint32Array.BYTES_PER_ELEMENT; 124 | md.uv_index_bytes = Uint32Array.BYTES_PER_ELEMENT; 125 | */ 126 | // buffers sizes 127 | 128 | tri_size = md.vertex_index_bytes * 3 + md.material_index_bytes; 129 | quad_size = md.vertex_index_bytes * 4 + md.material_index_bytes; 130 | 131 | len_tri_flat = md.ntri_flat * ( tri_size ); 132 | len_tri_smooth = md.ntri_smooth * ( tri_size + md.normal_index_bytes * 3 ); 133 | len_tri_flat_uv = md.ntri_flat_uv * ( tri_size + md.uv_index_bytes * 3 ); 134 | len_tri_smooth_uv = md.ntri_smooth_uv * ( tri_size + md.normal_index_bytes * 3 + md.uv_index_bytes * 3 ); 135 | 136 | len_quad_flat = md.nquad_flat * ( quad_size ); 137 | len_quad_smooth = md.nquad_smooth * ( quad_size + md.normal_index_bytes * 4 ); 138 | len_quad_flat_uv = md.nquad_flat_uv * ( quad_size + md.uv_index_bytes * 4 ); 139 | len_quad_smooth_uv = md.nquad_smooth_uv * ( quad_size + md.normal_index_bytes * 4 + md.uv_index_bytes * 4 ); 140 | 141 | // read buffers 142 | 143 | currentOffset += init_vertices( currentOffset ); 144 | 145 | currentOffset += init_normals( currentOffset ); 146 | currentOffset += handlePadding( md.nnormals * 3 ); 147 | 148 | currentOffset += init_uvs( currentOffset ); 149 | 150 | start_tri_flat = currentOffset; 151 | start_tri_smooth = start_tri_flat + len_tri_flat + handlePadding( md.ntri_flat * 2 ); 152 | start_tri_flat_uv = start_tri_smooth + len_tri_smooth + handlePadding( md.ntri_smooth * 2 ); 153 | start_tri_smooth_uv = start_tri_flat_uv + len_tri_flat_uv + handlePadding( md.ntri_flat_uv * 2 ); 154 | 155 | start_quad_flat = start_tri_smooth_uv + len_tri_smooth_uv + handlePadding( md.ntri_smooth_uv * 2 ); 156 | start_quad_smooth = start_quad_flat + len_quad_flat + handlePadding( md.nquad_flat * 2 ); 157 | start_quad_flat_uv = start_quad_smooth + len_quad_smooth + handlePadding( md.nquad_smooth * 2 ); 158 | start_quad_smooth_uv = start_quad_flat_uv + len_quad_flat_uv + handlePadding( md.nquad_flat_uv * 2 ); 159 | 160 | // have to first process faces with uvs 161 | // so that face and uv indices match 162 | 163 | init_triangles_flat_uv( start_tri_flat_uv ); 164 | init_triangles_smooth_uv( start_tri_smooth_uv ); 165 | 166 | init_quads_flat_uv( start_quad_flat_uv ); 167 | init_quads_smooth_uv( start_quad_smooth_uv ); 168 | 169 | // now we can process untextured faces 170 | 171 | init_triangles_flat( start_tri_flat ); 172 | init_triangles_smooth( start_tri_smooth ); 173 | 174 | init_quads_flat( start_quad_flat ); 175 | init_quads_smooth( start_quad_smooth ); 176 | 177 | this.computeFaceNormals(); 178 | 179 | function handlePadding( n ) { 180 | 181 | return ( n % 4 ) ? ( 4 - n % 4 ) : 0; 182 | 183 | } 184 | 185 | function parseMetaData( data, offset ) { 186 | 187 | var metaData = { 188 | 189 | 'signature' : parseString( data, offset, 12 ), 190 | 'header_bytes' : parseUChar8( data, offset + 12 ), 191 | 192 | 'vertex_coordinate_bytes' : parseUChar8( data, offset + 13 ), 193 | 'normal_coordinate_bytes' : parseUChar8( data, offset + 14 ), 194 | 'uv_coordinate_bytes' : parseUChar8( data, offset + 15 ), 195 | 196 | 'vertex_index_bytes' : parseUChar8( data, offset + 16 ), 197 | 'normal_index_bytes' : parseUChar8( data, offset + 17 ), 198 | 'uv_index_bytes' : parseUChar8( data, offset + 18 ), 199 | 'material_index_bytes' : parseUChar8( data, offset + 19 ), 200 | 201 | 'nvertices' : parseUInt32( data, offset + 20 ), 202 | 'nnormals' : parseUInt32( data, offset + 20 + 4 * 1 ), 203 | 'nuvs' : parseUInt32( data, offset + 20 + 4 * 2 ), 204 | 205 | 'ntri_flat' : parseUInt32( data, offset + 20 + 4 * 3 ), 206 | 'ntri_smooth' : parseUInt32( data, offset + 20 + 4 * 4 ), 207 | 'ntri_flat_uv' : parseUInt32( data, offset + 20 + 4 * 5 ), 208 | 'ntri_smooth_uv' : parseUInt32( data, offset + 20 + 4 * 6 ), 209 | 210 | 'nquad_flat' : parseUInt32( data, offset + 20 + 4 * 7 ), 211 | 'nquad_smooth' : parseUInt32( data, offset + 20 + 4 * 8 ), 212 | 'nquad_flat_uv' : parseUInt32( data, offset + 20 + 4 * 9 ), 213 | 'nquad_smooth_uv' : parseUInt32( data, offset + 20 + 4 * 10 ) 214 | 215 | }; 216 | /* 217 | console.log( "signature: " + metaData.signature ); 218 | 219 | console.log( "header_bytes: " + metaData.header_bytes ); 220 | console.log( "vertex_coordinate_bytes: " + metaData.vertex_coordinate_bytes ); 221 | console.log( "normal_coordinate_bytes: " + metaData.normal_coordinate_bytes ); 222 | console.log( "uv_coordinate_bytes: " + metaData.uv_coordinate_bytes ); 223 | 224 | console.log( "vertex_index_bytes: " + metaData.vertex_index_bytes ); 225 | console.log( "normal_index_bytes: " + metaData.normal_index_bytes ); 226 | console.log( "uv_index_bytes: " + metaData.uv_index_bytes ); 227 | console.log( "material_index_bytes: " + metaData.material_index_bytes ); 228 | 229 | console.log( "nvertices: " + metaData.nvertices ); 230 | console.log( "nnormals: " + metaData.nnormals ); 231 | console.log( "nuvs: " + metaData.nuvs ); 232 | 233 | console.log( "ntri_flat: " + metaData.ntri_flat ); 234 | console.log( "ntri_smooth: " + metaData.ntri_smooth ); 235 | console.log( "ntri_flat_uv: " + metaData.ntri_flat_uv ); 236 | console.log( "ntri_smooth_uv: " + metaData.ntri_smooth_uv ); 237 | 238 | console.log( "nquad_flat: " + metaData.nquad_flat ); 239 | console.log( "nquad_smooth: " + metaData.nquad_smooth ); 240 | console.log( "nquad_flat_uv: " + metaData.nquad_flat_uv ); 241 | console.log( "nquad_smooth_uv: " + metaData.nquad_smooth_uv ); 242 | 243 | var total = metaData.header_bytes 244 | + metaData.nvertices * metaData.vertex_coordinate_bytes * 3 245 | + metaData.nnormals * metaData.normal_coordinate_bytes * 3 246 | + metaData.nuvs * metaData.uv_coordinate_bytes * 2 247 | + metaData.ntri_flat * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes ) 248 | + metaData.ntri_smooth * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 ) 249 | + metaData.ntri_flat_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.uv_index_bytes*3 ) 250 | + metaData.ntri_smooth_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 + metaData.uv_index_bytes*3 ) 251 | + metaData.nquad_flat * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes ) 252 | + metaData.nquad_smooth * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 ) 253 | + metaData.nquad_flat_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.uv_index_bytes*4 ) 254 | + metaData.nquad_smooth_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 + metaData.uv_index_bytes*4 ); 255 | console.log( "total bytes: " + total ); 256 | */ 257 | 258 | return metaData; 259 | 260 | } 261 | 262 | function parseString( data, offset, length ) { 263 | 264 | var charArray = new Uint8Array( data, offset, length ); 265 | 266 | var text = ""; 267 | 268 | for ( var i = 0; i < length; i ++ ) { 269 | 270 | text += String.fromCharCode( charArray[ offset + i ] ); 271 | 272 | } 273 | 274 | return text; 275 | 276 | } 277 | 278 | function parseUChar8( data, offset ) { 279 | 280 | var charArray = new Uint8Array( data, offset, 1 ); 281 | 282 | return charArray[ 0 ]; 283 | 284 | } 285 | 286 | function parseUInt32( data, offset ) { 287 | 288 | var intArray = new Uint32Array( data, offset, 1 ); 289 | 290 | return intArray[ 0 ]; 291 | 292 | } 293 | 294 | function init_vertices( start ) { 295 | 296 | var nElements = md.nvertices; 297 | 298 | var coordArray = new Float32Array( data, start, nElements * 3 ); 299 | 300 | var i, x, y, z; 301 | 302 | for ( i = 0; i < nElements; i ++ ) { 303 | 304 | x = coordArray[ i * 3 ]; 305 | y = coordArray[ i * 3 + 1 ]; 306 | z = coordArray[ i * 3 + 2 ]; 307 | 308 | scope.vertices.push( new THREE.Vector3( x, y, z ) ); 309 | 310 | } 311 | 312 | return nElements * 3 * Float32Array.BYTES_PER_ELEMENT; 313 | 314 | } 315 | 316 | function init_normals( start ) { 317 | 318 | var nElements = md.nnormals; 319 | 320 | if ( nElements ) { 321 | 322 | var normalArray = new Int8Array( data, start, nElements * 3 ); 323 | 324 | var i, x, y, z; 325 | 326 | for ( i = 0; i < nElements; i ++ ) { 327 | 328 | x = normalArray[ i * 3 ]; 329 | y = normalArray[ i * 3 + 1 ]; 330 | z = normalArray[ i * 3 + 2 ]; 331 | 332 | normals.push( x / 127, y / 127, z / 127 ); 333 | 334 | } 335 | 336 | } 337 | 338 | return nElements * 3 * Int8Array.BYTES_PER_ELEMENT; 339 | 340 | } 341 | 342 | function init_uvs( start ) { 343 | 344 | var nElements = md.nuvs; 345 | 346 | if ( nElements ) { 347 | 348 | var uvArray = new Float32Array( data, start, nElements * 2 ); 349 | 350 | var i, u, v; 351 | 352 | for ( i = 0; i < nElements; i ++ ) { 353 | 354 | u = uvArray[ i * 2 ]; 355 | v = uvArray[ i * 2 + 1 ]; 356 | 357 | uvs.push( u, v ); 358 | 359 | } 360 | 361 | } 362 | 363 | return nElements * 2 * Float32Array.BYTES_PER_ELEMENT; 364 | 365 | } 366 | 367 | function init_uvs3( nElements, offset ) { 368 | 369 | var i, uva, uvb, uvc, u1, u2, u3, v1, v2, v3; 370 | 371 | var uvIndexBuffer = new Uint32Array( data, offset, 3 * nElements ); 372 | 373 | for ( i = 0; i < nElements; i ++ ) { 374 | 375 | uva = uvIndexBuffer[ i * 3 ]; 376 | uvb = uvIndexBuffer[ i * 3 + 1 ]; 377 | uvc = uvIndexBuffer[ i * 3 + 2 ]; 378 | 379 | u1 = uvs[ uva * 2 ]; 380 | v1 = uvs[ uva * 2 + 1 ]; 381 | 382 | u2 = uvs[ uvb * 2 ]; 383 | v2 = uvs[ uvb * 2 + 1 ]; 384 | 385 | u3 = uvs[ uvc * 2 ]; 386 | v3 = uvs[ uvc * 2 + 1 ]; 387 | 388 | scope.faceVertexUvs[ 0 ].push( [ 389 | new THREE.Vector2( u1, v1 ), 390 | new THREE.Vector2( u2, v2 ), 391 | new THREE.Vector2( u3, v3 ) 392 | ] ); 393 | 394 | } 395 | 396 | } 397 | 398 | function init_uvs4( nElements, offset ) { 399 | 400 | var i, uva, uvb, uvc, uvd, u1, u2, u3, u4, v1, v2, v3, v4; 401 | 402 | var uvIndexBuffer = new Uint32Array( data, offset, 4 * nElements ); 403 | 404 | for ( i = 0; i < nElements; i ++ ) { 405 | 406 | uva = uvIndexBuffer[ i * 4 ]; 407 | uvb = uvIndexBuffer[ i * 4 + 1 ]; 408 | uvc = uvIndexBuffer[ i * 4 + 2 ]; 409 | uvd = uvIndexBuffer[ i * 4 + 3 ]; 410 | 411 | u1 = uvs[ uva * 2 ]; 412 | v1 = uvs[ uva * 2 + 1 ]; 413 | 414 | u2 = uvs[ uvb * 2 ]; 415 | v2 = uvs[ uvb * 2 + 1 ]; 416 | 417 | u3 = uvs[ uvc * 2 ]; 418 | v3 = uvs[ uvc * 2 + 1 ]; 419 | 420 | u4 = uvs[ uvd * 2 ]; 421 | v4 = uvs[ uvd * 2 + 1 ]; 422 | 423 | scope.faceVertexUvs[ 0 ].push( [ 424 | new THREE.Vector2( u1, v1 ), 425 | new THREE.Vector2( u2, v2 ), 426 | new THREE.Vector2( u4, v4 ) 427 | ] ); 428 | 429 | scope.faceVertexUvs[ 0 ].push( [ 430 | new THREE.Vector2( u2, v2 ), 431 | new THREE.Vector2( u3, v3 ), 432 | new THREE.Vector2( u4, v4 ) 433 | ] ); 434 | 435 | } 436 | 437 | } 438 | 439 | function init_faces3_flat( nElements, offsetVertices, offsetMaterials ) { 440 | 441 | var i, a, b, c, m; 442 | 443 | var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements ); 444 | var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); 445 | 446 | for ( i = 0; i < nElements; i ++ ) { 447 | 448 | a = vertexIndexBuffer[ i * 3 ]; 449 | b = vertexIndexBuffer[ i * 3 + 1 ]; 450 | c = vertexIndexBuffer[ i * 3 + 2 ]; 451 | 452 | m = materialIndexBuffer[ i ]; 453 | 454 | scope.faces.push( new THREE.Face3( a, b, c, null, null, m ) ); 455 | 456 | } 457 | 458 | } 459 | 460 | function init_faces4_flat( nElements, offsetVertices, offsetMaterials ) { 461 | 462 | var i, a, b, c, d, m; 463 | 464 | var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements ); 465 | var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); 466 | 467 | for ( i = 0; i < nElements; i ++ ) { 468 | 469 | a = vertexIndexBuffer[ i * 4 ]; 470 | b = vertexIndexBuffer[ i * 4 + 1 ]; 471 | c = vertexIndexBuffer[ i * 4 + 2 ]; 472 | d = vertexIndexBuffer[ i * 4 + 3 ]; 473 | 474 | m = materialIndexBuffer[ i ]; 475 | 476 | scope.faces.push( new THREE.Face3( a, b, d, null, null, m ) ); 477 | scope.faces.push( new THREE.Face3( b, c, d, null, null, m ) ); 478 | 479 | } 480 | 481 | } 482 | 483 | function init_faces3_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) { 484 | 485 | var i, a, b, c, m; 486 | var na, nb, nc; 487 | 488 | var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements ); 489 | var normalIndexBuffer = new Uint32Array( data, offsetNormals, 3 * nElements ); 490 | var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); 491 | 492 | for ( i = 0; i < nElements; i ++ ) { 493 | 494 | a = vertexIndexBuffer[ i * 3 ]; 495 | b = vertexIndexBuffer[ i * 3 + 1 ]; 496 | c = vertexIndexBuffer[ i * 3 + 2 ]; 497 | 498 | na = normalIndexBuffer[ i * 3 ]; 499 | nb = normalIndexBuffer[ i * 3 + 1 ]; 500 | nc = normalIndexBuffer[ i * 3 + 2 ]; 501 | 502 | m = materialIndexBuffer[ i ]; 503 | 504 | var nax = normals[ na * 3 ], 505 | nay = normals[ na * 3 + 1 ], 506 | naz = normals[ na * 3 + 2 ], 507 | 508 | nbx = normals[ nb * 3 ], 509 | nby = normals[ nb * 3 + 1 ], 510 | nbz = normals[ nb * 3 + 2 ], 511 | 512 | ncx = normals[ nc * 3 ], 513 | ncy = normals[ nc * 3 + 1 ], 514 | ncz = normals[ nc * 3 + 2 ]; 515 | 516 | scope.faces.push( new THREE.Face3( a, b, c, [ 517 | new THREE.Vector3( nax, nay, naz ), 518 | new THREE.Vector3( nbx, nby, nbz ), 519 | new THREE.Vector3( ncx, ncy, ncz ) 520 | ], null, m ) ); 521 | 522 | } 523 | 524 | } 525 | 526 | function init_faces4_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) { 527 | 528 | var i, a, b, c, d, m; 529 | var na, nb, nc, nd; 530 | 531 | var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements ); 532 | var normalIndexBuffer = new Uint32Array( data, offsetNormals, 4 * nElements ); 533 | var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); 534 | 535 | for ( i = 0; i < nElements; i ++ ) { 536 | 537 | a = vertexIndexBuffer[ i * 4 ]; 538 | b = vertexIndexBuffer[ i * 4 + 1 ]; 539 | c = vertexIndexBuffer[ i * 4 + 2 ]; 540 | d = vertexIndexBuffer[ i * 4 + 3 ]; 541 | 542 | na = normalIndexBuffer[ i * 4 ]; 543 | nb = normalIndexBuffer[ i * 4 + 1 ]; 544 | nc = normalIndexBuffer[ i * 4 + 2 ]; 545 | nd = normalIndexBuffer[ i * 4 + 3 ]; 546 | 547 | m = materialIndexBuffer[ i ]; 548 | 549 | var nax = normals[ na * 3 ], 550 | nay = normals[ na * 3 + 1 ], 551 | naz = normals[ na * 3 + 2 ], 552 | 553 | nbx = normals[ nb * 3 ], 554 | nby = normals[ nb * 3 + 1 ], 555 | nbz = normals[ nb * 3 + 2 ], 556 | 557 | ncx = normals[ nc * 3 ], 558 | ncy = normals[ nc * 3 + 1 ], 559 | ncz = normals[ nc * 3 + 2 ], 560 | 561 | ndx = normals[ nd * 3 ], 562 | ndy = normals[ nd * 3 + 1 ], 563 | ndz = normals[ nd * 3 + 2 ]; 564 | 565 | scope.faces.push( new THREE.Face3( a, b, d, [ 566 | new THREE.Vector3( nax, nay, naz ), 567 | new THREE.Vector3( nbx, nby, nbz ), 568 | new THREE.Vector3( ndx, ndy, ndz ) 569 | ], null, m ) ); 570 | 571 | scope.faces.push( new THREE.Face3( b, c, d, [ 572 | new THREE.Vector3( nbx, nby, nbz ), 573 | new THREE.Vector3( ncx, ncy, ncz ), 574 | new THREE.Vector3( ndx, ndy, ndz ) 575 | ], null, m ) ); 576 | 577 | } 578 | 579 | } 580 | 581 | function init_triangles_flat( start ) { 582 | 583 | var nElements = md.ntri_flat; 584 | 585 | if ( nElements ) { 586 | 587 | var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 588 | init_faces3_flat( nElements, start, offsetMaterials ); 589 | 590 | } 591 | 592 | } 593 | 594 | function init_triangles_flat_uv( start ) { 595 | 596 | var nElements = md.ntri_flat_uv; 597 | 598 | if ( nElements ) { 599 | 600 | var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 601 | var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 602 | 603 | init_faces3_flat( nElements, start, offsetMaterials ); 604 | init_uvs3( nElements, offsetUvs ); 605 | 606 | } 607 | 608 | } 609 | 610 | function init_triangles_smooth( start ) { 611 | 612 | var nElements = md.ntri_smooth; 613 | 614 | if ( nElements ) { 615 | 616 | var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 617 | var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 618 | 619 | init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials ); 620 | 621 | } 622 | 623 | } 624 | 625 | function init_triangles_smooth_uv( start ) { 626 | 627 | var nElements = md.ntri_smooth_uv; 628 | 629 | if ( nElements ) { 630 | 631 | var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 632 | var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 633 | var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 634 | 635 | init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials ); 636 | init_uvs3( nElements, offsetUvs ); 637 | 638 | } 639 | 640 | } 641 | 642 | function init_quads_flat( start ) { 643 | 644 | var nElements = md.nquad_flat; 645 | 646 | if ( nElements ) { 647 | 648 | var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 649 | init_faces4_flat( nElements, start, offsetMaterials ); 650 | 651 | } 652 | 653 | } 654 | 655 | function init_quads_flat_uv( start ) { 656 | 657 | var nElements = md.nquad_flat_uv; 658 | 659 | if ( nElements ) { 660 | 661 | var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 662 | var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 663 | 664 | init_faces4_flat( nElements, start, offsetMaterials ); 665 | init_uvs4( nElements, offsetUvs ); 666 | 667 | } 668 | 669 | } 670 | 671 | function init_quads_smooth( start ) { 672 | 673 | var nElements = md.nquad_smooth; 674 | 675 | if ( nElements ) { 676 | 677 | var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 678 | var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 679 | 680 | init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials ); 681 | 682 | } 683 | 684 | } 685 | 686 | function init_quads_smooth_uv( start ) { 687 | 688 | var nElements = md.nquad_smooth_uv; 689 | 690 | if ( nElements ) { 691 | 692 | var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 693 | var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 694 | var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 695 | 696 | init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials ); 697 | init_uvs4( nElements, offsetUvs ); 698 | 699 | } 700 | 701 | } 702 | 703 | }; 704 | 705 | Model.prototype = Object.create( THREE.Geometry.prototype ); 706 | Model.prototype.constructor = Model; 707 | 708 | var geometry = new Model( texturePath ); 709 | var materials = THREE.Loader.prototype.initMaterials( jsonMaterials, texturePath, this.crossOrigin ); 710 | 711 | callback( geometry, materials ); 712 | 713 | } 714 | 715 | }; 716 | -------------------------------------------------------------------------------- /vendor/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 | /*global THREE, console */ 9 | 10 | ( function () { 11 | 12 | function OrbitConstraint ( object ) { 13 | 14 | this.object = object; 15 | 16 | // "target" sets the location of focus, where the object orbits around 17 | // and where it pans with respect to. 18 | this.target = new THREE.Vector3(); 19 | 20 | // Limits to how far you can dolly in and out ( PerspectiveCamera only ) 21 | this.minDistance = 0; 22 | this.maxDistance = Infinity; 23 | 24 | // Limits to how far you can zoom in and out ( OrthographicCamera only ) 25 | this.minZoom = 0; 26 | this.maxZoom = Infinity; 27 | 28 | // How far you can orbit vertically, upper and lower limits. 29 | // Range is 0 to Math.PI radians. 30 | this.minPolarAngle = 0; // radians 31 | this.maxPolarAngle = Math.PI; // radians 32 | 33 | // How far you can orbit horizontally, upper and lower limits. 34 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 35 | this.minAzimuthAngle = - Infinity; // radians 36 | this.maxAzimuthAngle = Infinity; // radians 37 | 38 | // Set to true to enable damping (inertia) 39 | // If damping is enabled, you must call controls.update() in your animation loop 40 | this.enableDamping = false; 41 | this.dampingFactor = 0.25; 42 | 43 | //////////// 44 | // internals 45 | 46 | var scope = this; 47 | 48 | var EPS = 0.000001; 49 | 50 | // Current position in spherical coordinate system. 51 | var theta; 52 | var phi; 53 | 54 | // Pending changes 55 | var phiDelta = 0; 56 | var thetaDelta = 0; 57 | var scale = 1; 58 | var panOffset = new THREE.Vector3(); 59 | var zoomChanged = false; 60 | 61 | // API 62 | 63 | this.getPolarAngle = function () { 64 | 65 | return phi; 66 | 67 | }; 68 | 69 | this.getAzimuthalAngle = function () { 70 | 71 | return theta; 72 | 73 | }; 74 | 75 | this.rotateLeft = function ( angle ) { 76 | 77 | thetaDelta -= angle; 78 | 79 | }; 80 | 81 | this.rotateUp = function ( angle ) { 82 | 83 | phiDelta -= angle; 84 | 85 | }; 86 | 87 | // pass in distance in world space to move left 88 | this.panLeft = function() { 89 | 90 | var v = new THREE.Vector3(); 91 | 92 | return function panLeft ( distance ) { 93 | 94 | var te = this.object.matrix.elements; 95 | 96 | // get X column of matrix 97 | v.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 98 | v.multiplyScalar( - distance ); 99 | 100 | panOffset.add( v ); 101 | 102 | }; 103 | 104 | }(); 105 | 106 | // pass in distance in world space to move up 107 | this.panUp = function() { 108 | 109 | var v = new THREE.Vector3(); 110 | 111 | return function panUp ( distance ) { 112 | 113 | var te = this.object.matrix.elements; 114 | 115 | // get Y column of matrix 116 | v.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 117 | v.multiplyScalar( distance ); 118 | 119 | panOffset.add( v ); 120 | 121 | }; 122 | 123 | }(); 124 | 125 | // pass in x,y of change desired in pixel space, 126 | // right and down are positive 127 | this.pan = function ( deltaX, deltaY, screenWidth, screenHeight ) { 128 | 129 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 130 | 131 | // perspective 132 | var position = scope.object.position; 133 | var offset = position.clone().sub( scope.target ); 134 | var targetDistance = offset.length(); 135 | 136 | // half of the fov is center to top of screen 137 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 138 | 139 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 140 | scope.panLeft( 2 * deltaX * targetDistance / screenHeight ); 141 | scope.panUp( 2 * deltaY * targetDistance / screenHeight ); 142 | 143 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 144 | 145 | // orthographic 146 | scope.panLeft( deltaX * ( scope.object.right - scope.object.left ) / screenWidth ); 147 | scope.panUp( deltaY * ( scope.object.top - scope.object.bottom ) / screenHeight ); 148 | 149 | } else { 150 | 151 | // camera neither orthographic or perspective 152 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 153 | 154 | } 155 | 156 | }; 157 | 158 | this.dollyIn = function ( dollyScale ) { 159 | 160 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 161 | 162 | scale /= dollyScale; 163 | 164 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 165 | 166 | scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); 167 | scope.object.updateProjectionMatrix(); 168 | zoomChanged = true; 169 | 170 | } else { 171 | 172 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 173 | 174 | } 175 | 176 | }; 177 | 178 | this.dollyOut = function ( dollyScale ) { 179 | 180 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 181 | 182 | scale *= dollyScale; 183 | 184 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 185 | 186 | scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); 187 | scope.object.updateProjectionMatrix(); 188 | zoomChanged = true; 189 | 190 | } else { 191 | 192 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 193 | 194 | } 195 | 196 | }; 197 | 198 | this.update = function() { 199 | 200 | var offset = new THREE.Vector3(); 201 | 202 | // so camera.up is the orbit axis 203 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 204 | var quatInverse = quat.clone().inverse(); 205 | 206 | var lastPosition = new THREE.Vector3(); 207 | var lastQuaternion = new THREE.Quaternion(); 208 | 209 | return function () { 210 | 211 | var position = this.object.position; 212 | 213 | offset.copy( position ).sub( this.target ); 214 | 215 | // rotate offset to "y-axis-is-up" space 216 | offset.applyQuaternion( quat ); 217 | 218 | // angle from z-axis around y-axis 219 | 220 | theta = Math.atan2( offset.x, offset.z ); 221 | 222 | // angle from y-axis 223 | 224 | phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 225 | 226 | theta += thetaDelta; 227 | phi += phiDelta; 228 | 229 | // restrict theta to be between desired limits 230 | theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); 231 | 232 | // restrict phi to be between desired limits 233 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 234 | 235 | // restrict phi to be betwee EPS and PI-EPS 236 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 237 | 238 | var radius = offset.length() * scale; 239 | 240 | // restrict radius to be between desired limits 241 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 242 | 243 | // move target to panned location 244 | this.target.add( panOffset ); 245 | 246 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 247 | offset.y = radius * Math.cos( phi ); 248 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 249 | 250 | // rotate offset back to "camera-up-vector-is-up" space 251 | offset.applyQuaternion( quatInverse ); 252 | 253 | position.copy( this.target ).add( offset ); 254 | 255 | this.object.lookAt( this.target ); 256 | 257 | if ( this.enableDamping === true ) { 258 | 259 | thetaDelta *= ( 1 - this.dampingFactor ); 260 | phiDelta *= ( 1 - this.dampingFactor ); 261 | 262 | } else { 263 | 264 | thetaDelta = 0; 265 | phiDelta = 0; 266 | 267 | } 268 | 269 | scale = 1; 270 | panOffset.set( 0, 0, 0 ); 271 | 272 | // update condition is: 273 | // min(camera displacement, camera rotation in radians)^2 > EPS 274 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 275 | 276 | if ( zoomChanged || 277 | lastPosition.distanceToSquared( this.object.position ) > EPS || 278 | 8 * ( 1 - lastQuaternion.dot( this.object.quaternion ) ) > EPS ) { 279 | 280 | lastPosition.copy( this.object.position ); 281 | lastQuaternion.copy( this.object.quaternion ); 282 | zoomChanged = false; 283 | 284 | return true; 285 | 286 | } 287 | 288 | return false; 289 | 290 | }; 291 | 292 | }(); 293 | 294 | }; 295 | 296 | 297 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 298 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 299 | // supported. 300 | // 301 | // Orbit - left mouse / touch: one finger move 302 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 303 | // Pan - right mouse, or arrow keys / touch: three finter swipe 304 | 305 | THREE.OrbitControls = function ( object, domElement ) { 306 | 307 | var constraint = new OrbitConstraint( object ); 308 | 309 | this.domElement = ( domElement !== undefined ) ? domElement : document; 310 | 311 | // API 312 | 313 | Object.defineProperty( this, 'constraint', { 314 | 315 | get: function() { 316 | 317 | return constraint; 318 | 319 | } 320 | 321 | } ); 322 | 323 | this.getPolarAngle = function () { 324 | 325 | return constraint.getPolarAngle(); 326 | 327 | }; 328 | 329 | this.getAzimuthalAngle = function () { 330 | 331 | return constraint.getAzimuthalAngle(); 332 | 333 | }; 334 | 335 | // Set to false to disable this control 336 | this.enabled = true; 337 | 338 | // center is old, deprecated; use "target" instead 339 | this.center = this.target; 340 | 341 | // This option actually enables dollying in and out; left as "zoom" for 342 | // backwards compatibility. 343 | // Set to false to disable zooming 344 | this.enableZoom = true; 345 | this.zoomSpeed = 1.0; 346 | 347 | // Set to false to disable rotating 348 | this.enableRotate = true; 349 | this.rotateSpeed = 1.0; 350 | 351 | // Set to false to disable panning 352 | this.enablePan = true; 353 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 354 | 355 | // Set to true to automatically rotate around the target 356 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 357 | this.autoRotate = false; 358 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 359 | 360 | // Set to false to disable use of the keys 361 | this.enableKeys = true; 362 | 363 | // The four arrow keys 364 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 365 | 366 | // Mouse buttons 367 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 368 | 369 | //////////// 370 | // internals 371 | 372 | var scope = this; 373 | 374 | var rotateStart = new THREE.Vector2(); 375 | var rotateEnd = new THREE.Vector2(); 376 | var rotateDelta = new THREE.Vector2(); 377 | 378 | var panStart = new THREE.Vector2(); 379 | var panEnd = new THREE.Vector2(); 380 | var panDelta = new THREE.Vector2(); 381 | 382 | var dollyStart = new THREE.Vector2(); 383 | var dollyEnd = new THREE.Vector2(); 384 | var dollyDelta = new THREE.Vector2(); 385 | 386 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 387 | 388 | var state = STATE.NONE; 389 | 390 | // for reset 391 | 392 | this.target0 = this.target.clone(); 393 | this.position0 = this.object.position.clone(); 394 | this.zoom0 = this.object.zoom; 395 | 396 | // events 397 | 398 | var changeEvent = { type: 'change' }; 399 | var startEvent = { type: 'start' }; 400 | var endEvent = { type: 'end' }; 401 | 402 | // pass in x,y of change desired in pixel space, 403 | // right and down are positive 404 | function pan( deltaX, deltaY ) { 405 | 406 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 407 | 408 | constraint.pan( deltaX, deltaY, element.clientWidth, element.clientHeight ); 409 | 410 | } 411 | 412 | this.update = function () { 413 | 414 | if ( this.autoRotate && state === STATE.NONE ) { 415 | 416 | constraint.rotateLeft( getAutoRotationAngle() ); 417 | 418 | } 419 | 420 | if ( constraint.update() === true ) { 421 | 422 | this.dispatchEvent( changeEvent ); 423 | 424 | } 425 | 426 | }; 427 | 428 | this.reset = function () { 429 | 430 | state = STATE.NONE; 431 | 432 | this.target.copy( this.target0 ); 433 | this.object.position.copy( this.position0 ); 434 | this.object.zoom = this.zoom0; 435 | 436 | this.object.updateProjectionMatrix(); 437 | this.dispatchEvent( changeEvent ); 438 | 439 | this.update(); 440 | 441 | }; 442 | 443 | function getAutoRotationAngle() { 444 | 445 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 446 | 447 | } 448 | 449 | function getZoomScale() { 450 | 451 | return Math.pow( 0.95, scope.zoomSpeed ); 452 | 453 | } 454 | 455 | function onMouseDown( event ) { 456 | 457 | if ( scope.enabled === false ) return; 458 | 459 | event.preventDefault(); 460 | 461 | if ( event.button === scope.mouseButtons.ORBIT ) { 462 | 463 | if ( scope.enableRotate === false ) return; 464 | 465 | state = STATE.ROTATE; 466 | 467 | rotateStart.set( event.clientX, event.clientY ); 468 | 469 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 470 | 471 | if ( scope.enableZoom === false ) return; 472 | 473 | state = STATE.DOLLY; 474 | 475 | dollyStart.set( event.clientX, event.clientY ); 476 | 477 | } else if ( event.button === scope.mouseButtons.PAN ) { 478 | 479 | if ( scope.enablePan === false ) return; 480 | 481 | state = STATE.PAN; 482 | 483 | panStart.set( event.clientX, event.clientY ); 484 | 485 | } 486 | 487 | if ( state !== STATE.NONE ) { 488 | 489 | document.addEventListener( 'mousemove', onMouseMove, false ); 490 | document.addEventListener( 'mouseup', onMouseUp, false ); 491 | scope.dispatchEvent( startEvent ); 492 | 493 | } 494 | 495 | } 496 | 497 | function onMouseMove( event ) { 498 | 499 | if ( scope.enabled === false ) return; 500 | 501 | event.preventDefault(); 502 | 503 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 504 | 505 | if ( state === STATE.ROTATE ) { 506 | 507 | if ( scope.enableRotate === false ) return; 508 | 509 | rotateEnd.set( event.clientX, event.clientY ); 510 | rotateDelta.subVectors( rotateEnd, rotateStart ); 511 | 512 | // rotating across whole screen goes 360 degrees around 513 | constraint.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 514 | 515 | // rotating up and down along whole screen attempts to go 360, but limited to 180 516 | constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 517 | 518 | rotateStart.copy( rotateEnd ); 519 | 520 | } else if ( state === STATE.DOLLY ) { 521 | 522 | if ( scope.enableZoom === false ) return; 523 | 524 | dollyEnd.set( event.clientX, event.clientY ); 525 | dollyDelta.subVectors( dollyEnd, dollyStart ); 526 | 527 | if ( dollyDelta.y > 0 ) { 528 | 529 | constraint.dollyIn( getZoomScale() ); 530 | 531 | } else if ( dollyDelta.y < 0 ) { 532 | 533 | constraint.dollyOut( getZoomScale() ); 534 | 535 | } 536 | 537 | dollyStart.copy( dollyEnd ); 538 | 539 | } else if ( state === STATE.PAN ) { 540 | 541 | if ( scope.enablePan === false ) return; 542 | 543 | panEnd.set( event.clientX, event.clientY ); 544 | panDelta.subVectors( panEnd, panStart ); 545 | 546 | pan( panDelta.x, panDelta.y ); 547 | 548 | panStart.copy( panEnd ); 549 | 550 | } 551 | 552 | if ( state !== STATE.NONE ) scope.update(); 553 | 554 | } 555 | 556 | function onMouseUp( /* event */ ) { 557 | 558 | if ( scope.enabled === false ) return; 559 | 560 | document.removeEventListener( 'mousemove', onMouseMove, false ); 561 | document.removeEventListener( 'mouseup', onMouseUp, false ); 562 | scope.dispatchEvent( endEvent ); 563 | state = STATE.NONE; 564 | 565 | } 566 | 567 | function onMouseWheel( event ) { 568 | 569 | if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; 570 | 571 | event.preventDefault(); 572 | event.stopPropagation(); 573 | 574 | var delta = 0; 575 | 576 | if ( event.wheelDelta !== undefined ) { 577 | 578 | // WebKit / Opera / Explorer 9 579 | 580 | delta = event.wheelDelta; 581 | 582 | } else if ( event.detail !== undefined ) { 583 | 584 | // Firefox 585 | 586 | delta = - event.detail; 587 | 588 | } 589 | 590 | if ( delta > 0 ) { 591 | 592 | constraint.dollyOut( getZoomScale() ); 593 | 594 | } else if ( delta < 0 ) { 595 | 596 | constraint.dollyIn( getZoomScale() ); 597 | 598 | } 599 | 600 | scope.update(); 601 | scope.dispatchEvent( startEvent ); 602 | scope.dispatchEvent( endEvent ); 603 | 604 | } 605 | 606 | function onKeyDown( event ) { 607 | 608 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 609 | 610 | switch ( event.keyCode ) { 611 | 612 | case scope.keys.UP: 613 | pan( 0, scope.keyPanSpeed ); 614 | scope.update(); 615 | break; 616 | 617 | case scope.keys.BOTTOM: 618 | pan( 0, - scope.keyPanSpeed ); 619 | scope.update(); 620 | break; 621 | 622 | case scope.keys.LEFT: 623 | pan( scope.keyPanSpeed, 0 ); 624 | scope.update(); 625 | break; 626 | 627 | case scope.keys.RIGHT: 628 | pan( - scope.keyPanSpeed, 0 ); 629 | scope.update(); 630 | break; 631 | 632 | } 633 | 634 | } 635 | 636 | function touchstart( event ) { 637 | 638 | if ( scope.enabled === false ) return; 639 | 640 | switch ( event.touches.length ) { 641 | 642 | case 1: // one-fingered touch: rotate 643 | 644 | if ( scope.enableRotate === false ) return; 645 | 646 | state = STATE.TOUCH_ROTATE; 647 | 648 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 649 | break; 650 | 651 | case 2: // two-fingered touch: dolly 652 | 653 | if ( scope.enableZoom === false ) return; 654 | 655 | state = STATE.TOUCH_DOLLY; 656 | 657 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 658 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 659 | var distance = Math.sqrt( dx * dx + dy * dy ); 660 | dollyStart.set( 0, distance ); 661 | break; 662 | 663 | case 3: // three-fingered touch: pan 664 | 665 | if ( scope.enablePan === false ) return; 666 | 667 | state = STATE.TOUCH_PAN; 668 | 669 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 670 | break; 671 | 672 | default: 673 | 674 | state = STATE.NONE; 675 | 676 | } 677 | 678 | if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); 679 | 680 | } 681 | 682 | function touchmove( event ) { 683 | 684 | if ( scope.enabled === false ) return; 685 | 686 | event.preventDefault(); 687 | event.stopPropagation(); 688 | 689 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 690 | 691 | switch ( event.touches.length ) { 692 | 693 | case 1: // one-fingered touch: rotate 694 | 695 | if ( scope.enableRotate === false ) return; 696 | if ( state !== STATE.TOUCH_ROTATE ) return; 697 | 698 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 699 | rotateDelta.subVectors( rotateEnd, rotateStart ); 700 | 701 | // rotating across whole screen goes 360 degrees around 702 | constraint.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 703 | // rotating up and down along whole screen attempts to go 360, but limited to 180 704 | constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 705 | 706 | rotateStart.copy( rotateEnd ); 707 | 708 | scope.update(); 709 | break; 710 | 711 | case 2: // two-fingered touch: dolly 712 | 713 | if ( scope.enableZoom === false ) return; 714 | if ( state !== STATE.TOUCH_DOLLY ) return; 715 | 716 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 717 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 718 | var distance = Math.sqrt( dx * dx + dy * dy ); 719 | 720 | dollyEnd.set( 0, distance ); 721 | dollyDelta.subVectors( dollyEnd, dollyStart ); 722 | 723 | if ( dollyDelta.y > 0 ) { 724 | 725 | constraint.dollyOut( getZoomScale() ); 726 | 727 | } else if ( dollyDelta.y < 0 ) { 728 | 729 | constraint.dollyIn( getZoomScale() ); 730 | 731 | } 732 | 733 | dollyStart.copy( dollyEnd ); 734 | 735 | scope.update(); 736 | break; 737 | 738 | case 3: // three-fingered touch: pan 739 | 740 | if ( scope.enablePan === false ) return; 741 | if ( state !== STATE.TOUCH_PAN ) return; 742 | 743 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 744 | panDelta.subVectors( panEnd, panStart ); 745 | 746 | pan( panDelta.x, panDelta.y ); 747 | 748 | panStart.copy( panEnd ); 749 | 750 | scope.update(); 751 | break; 752 | 753 | default: 754 | 755 | state = STATE.NONE; 756 | 757 | } 758 | 759 | } 760 | 761 | function touchend( /* event */ ) { 762 | 763 | if ( scope.enabled === false ) return; 764 | 765 | scope.dispatchEvent( endEvent ); 766 | state = STATE.NONE; 767 | 768 | } 769 | 770 | function contextmenu( event ) { 771 | 772 | event.preventDefault(); 773 | 774 | } 775 | 776 | this.dispose = function() { 777 | 778 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 779 | this.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 780 | this.domElement.removeEventListener( 'mousewheel', onMouseWheel, false ); 781 | this.domElement.removeEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox 782 | 783 | this.domElement.removeEventListener( 'touchstart', touchstart, false ); 784 | this.domElement.removeEventListener( 'touchend', touchend, false ); 785 | this.domElement.removeEventListener( 'touchmove', touchmove, false ); 786 | 787 | document.removeEventListener( 'mousemove', onMouseMove, false ); 788 | document.removeEventListener( 'mouseup', onMouseUp, false ); 789 | 790 | window.removeEventListener( 'keydown', onKeyDown, false ); 791 | 792 | } 793 | 794 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 795 | 796 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 797 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 798 | this.domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox 799 | 800 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 801 | this.domElement.addEventListener( 'touchend', touchend, false ); 802 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 803 | 804 | window.addEventListener( 'keydown', onKeyDown, false ); 805 | 806 | // force an update at start 807 | this.update(); 808 | 809 | }; 810 | 811 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 812 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 813 | 814 | Object.defineProperties( THREE.OrbitControls.prototype, { 815 | 816 | object: { 817 | 818 | get: function () { 819 | 820 | return this.constraint.object; 821 | 822 | } 823 | 824 | }, 825 | 826 | target: { 827 | 828 | get: function () { 829 | 830 | return this.constraint.target; 831 | 832 | }, 833 | 834 | set: function ( value ) { 835 | 836 | console.warn( 'THREE.OrbitControls: target is now immutable. Use target.set() instead.' ); 837 | this.constraint.target.copy( value ); 838 | 839 | } 840 | 841 | }, 842 | 843 | minDistance : { 844 | 845 | get: function () { 846 | 847 | return this.constraint.minDistance; 848 | 849 | }, 850 | 851 | set: function ( value ) { 852 | 853 | this.constraint.minDistance = value; 854 | 855 | } 856 | 857 | }, 858 | 859 | maxDistance : { 860 | 861 | get: function () { 862 | 863 | return this.constraint.maxDistance; 864 | 865 | }, 866 | 867 | set: function ( value ) { 868 | 869 | this.constraint.maxDistance = value; 870 | 871 | } 872 | 873 | }, 874 | 875 | minZoom : { 876 | 877 | get: function () { 878 | 879 | return this.constraint.minZoom; 880 | 881 | }, 882 | 883 | set: function ( value ) { 884 | 885 | this.constraint.minZoom = value; 886 | 887 | } 888 | 889 | }, 890 | 891 | maxZoom : { 892 | 893 | get: function () { 894 | 895 | return this.constraint.maxZoom; 896 | 897 | }, 898 | 899 | set: function ( value ) { 900 | 901 | this.constraint.maxZoom = value; 902 | 903 | } 904 | 905 | }, 906 | 907 | minPolarAngle : { 908 | 909 | get: function () { 910 | 911 | return this.constraint.minPolarAngle; 912 | 913 | }, 914 | 915 | set: function ( value ) { 916 | 917 | this.constraint.minPolarAngle = value; 918 | 919 | } 920 | 921 | }, 922 | 923 | maxPolarAngle : { 924 | 925 | get: function () { 926 | 927 | return this.constraint.maxPolarAngle; 928 | 929 | }, 930 | 931 | set: function ( value ) { 932 | 933 | this.constraint.maxPolarAngle = value; 934 | 935 | } 936 | 937 | }, 938 | 939 | minAzimuthAngle : { 940 | 941 | get: function () { 942 | 943 | return this.constraint.minAzimuthAngle; 944 | 945 | }, 946 | 947 | set: function ( value ) { 948 | 949 | this.constraint.minAzimuthAngle = value; 950 | 951 | } 952 | 953 | }, 954 | 955 | maxAzimuthAngle : { 956 | 957 | get: function () { 958 | 959 | return this.constraint.maxAzimuthAngle; 960 | 961 | }, 962 | 963 | set: function ( value ) { 964 | 965 | this.constraint.maxAzimuthAngle = value; 966 | 967 | } 968 | 969 | }, 970 | 971 | enableDamping : { 972 | 973 | get: function () { 974 | 975 | return this.constraint.enableDamping; 976 | 977 | }, 978 | 979 | set: function ( value ) { 980 | 981 | this.constraint.enableDamping = value; 982 | 983 | } 984 | 985 | }, 986 | 987 | dampingFactor : { 988 | 989 | get: function () { 990 | 991 | return this.constraint.dampingFactor; 992 | 993 | }, 994 | 995 | set: function ( value ) { 996 | 997 | this.constraint.dampingFactor = value; 998 | 999 | } 1000 | 1001 | }, 1002 | 1003 | // backward compatibility 1004 | 1005 | noZoom: { 1006 | 1007 | get: function () { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1010 | return ! this.enableZoom; 1011 | 1012 | }, 1013 | 1014 | set: function ( value ) { 1015 | 1016 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1017 | this.enableZoom = ! value; 1018 | 1019 | } 1020 | 1021 | }, 1022 | 1023 | noRotate: { 1024 | 1025 | get: function () { 1026 | 1027 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1028 | return ! this.enableRotate; 1029 | 1030 | }, 1031 | 1032 | set: function ( value ) { 1033 | 1034 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1035 | this.enableRotate = ! value; 1036 | 1037 | } 1038 | 1039 | }, 1040 | 1041 | noPan: { 1042 | 1043 | get: function () { 1044 | 1045 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1046 | return ! this.enablePan; 1047 | 1048 | }, 1049 | 1050 | set: function ( value ) { 1051 | 1052 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1053 | this.enablePan = ! value; 1054 | 1055 | } 1056 | 1057 | }, 1058 | 1059 | noKeys: { 1060 | 1061 | get: function () { 1062 | 1063 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1064 | return ! this.enableKeys; 1065 | 1066 | }, 1067 | 1068 | set: function ( value ) { 1069 | 1070 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1071 | this.enableKeys = ! value; 1072 | 1073 | } 1074 | 1075 | }, 1076 | 1077 | staticMoving : { 1078 | 1079 | get: function () { 1080 | 1081 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1082 | return ! this.constraint.enableDamping; 1083 | 1084 | }, 1085 | 1086 | set: function ( value ) { 1087 | 1088 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1089 | this.constraint.enableDamping = ! value; 1090 | 1091 | } 1092 | 1093 | }, 1094 | 1095 | dynamicDampingFactor : { 1096 | 1097 | get: function () { 1098 | 1099 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1100 | return this.constraint.dampingFactor; 1101 | 1102 | }, 1103 | 1104 | set: function ( value ) { 1105 | 1106 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1107 | this.constraint.dampingFactor = value; 1108 | 1109 | } 1110 | 1111 | } 1112 | 1113 | } ); 1114 | 1115 | }() ); 1116 | --------------------------------------------------------------------------------