├── LICENSE ├── README.md ├── about └── screenshot.jpg ├── assets └── text.png ├── index.html ├── js ├── Maf.js ├── OrbitControls.js ├── THREE.ConstantSpline.js ├── THREE.MeshLine.js ├── isMobile.min.js └── three.min.js └── matcap.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jaume Sanchez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3dwebfest-2016 2 | Ambient effect for 3DWebFest https://www.clicktorelease.com/tmp/threejs/3dwebfest/ 3 | 4 | 5 | [![Demo](about/screenshot.jpg)](https://www.clicktorelease.com/tmp/threejs/3dwebfest/) 6 | 7 | # License 8 | 9 | MIT licensed 10 | 11 | Copyright (C) 2016 Jaume Sanchez Elias, http://www.clicktorelease.com 12 | 13 | 14 | -------------------------------------------------------------------------------- /about/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/3dwebfest-2016/e738ab53c8ba7b55a1dcaddcbb3a0dff412fd4ed/about/screenshot.jpg -------------------------------------------------------------------------------- /assets/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/3dwebfest-2016/e738ab53c8ba7b55a1dcaddcbb3a0dff412fd4ed/assets/text.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 3DWebFest 5 | 6 | 7 | 37 | 38 | 39 | 40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 75 | 76 | 98 | 99 | 124 | 125 | 148 | 149 | 242 | 243 | 278 | 279 | 775 | 776 | 777 | 778 | -------------------------------------------------------------------------------- /js/Maf.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Module code from underscore.js 4 | 5 | // Establish the root object, `window` (`self`) in the browser, `global` 6 | // on the server, or `this` in some virtual machines. We use `self` 7 | // instead of `window` for `WebWorker` support. 8 | var root = typeof self == 'object' && self.self === self && self || 9 | typeof global == 'object' && global.global === global && global || 10 | this; 11 | 12 | var Maf = function(obj) { 13 | if (obj instanceof Maf ) return obj; 14 | if (!(this instanceof Maf )) return new Maf(obj); 15 | this._wrapped = obj; 16 | }; 17 | 18 | // Export the Underscore object for **Node.js**, with 19 | // backwards-compatibility for their old module API. If we're in 20 | // the browser, add `Maf` as a global object. 21 | // (`nodeType` is checked to ensure that `module` 22 | // and `exports` are not HTML elements.) 23 | if (typeof exports != 'undefined' && !exports.nodeType) { 24 | if (typeof module != 'undefined' && !module.nodeType && module.exports) { 25 | exports = module.exports = Maf; 26 | } 27 | exports.Maf = Maf; 28 | } else { 29 | root.Maf = Maf; 30 | } 31 | 32 | // Current version. 33 | Maf.VERSION = '1.0.0'; 34 | 35 | Maf.PI = Math.PI; 36 | 37 | // https://www.opengl.org/sdk/docs/man/html/clamp.xhtml 38 | 39 | Maf.clamp = function( v, minVal, maxVal ) { 40 | return Math.min( maxVal, Math.max( minVal, v ) ); 41 | }; 42 | 43 | // https://www.opengl.org/sdk/docs/man/html/step.xhtml 44 | 45 | Maf.step = function( edge, v ) { 46 | return ( v < edge ) ? 0 : 1; 47 | } 48 | 49 | // https://www.opengl.org/sdk/docs/man/html/smoothstep.xhtml 50 | 51 | Maf.smoothStep = function ( edge0, edge1, v ) { 52 | var t = Maf.clamp( ( v - edge0 ) / ( edge1 - edge0 ), 0.0, 1.0 ); 53 | return t * t * ( 3.0 - 2.0 * t ); 54 | }; 55 | 56 | // http://docs.unity3d.com/ScriptReference/Mathf.html 57 | // http://www.shaderific.com/glsl-functions/ 58 | // https://www.opengl.org/sdk/docs/man4/html/ 59 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ff471376(v=vs.85).aspx 60 | // http://moutjs.com/docs/v0.11/math.html#map 61 | // https://code.google.com/p/kuda/source/browse/public/js/hemi/utils/mathUtils.js?r=8d581c02651077c4ac3f5fc4725323210b6b13cc 62 | 63 | // Converts from degrees to radians. 64 | Maf.deg2Rad = function( degrees ) { 65 | return degrees * Math.PI / 180; 66 | }; 67 | 68 | Maf.toRadians = Maf.deg2Rad; 69 | 70 | // Converts from radians to degrees. 71 | Maf.rad2Deg = function(radians) { 72 | return radians * 180 / Math.PI; 73 | }; 74 | 75 | Maf.toDegrees = Maf.rad2Deg; 76 | 77 | Maf.clamp01 = function( v ) { 78 | return Maf.clamp( v, 0, 1 ); 79 | }; 80 | 81 | // https://www.opengl.org/sdk/docs/man/html/mix.xhtml 82 | 83 | Maf.mix = function( x, y, a ) { 84 | if( a <= 0 ) return x; 85 | if( a >= 1 ) return y; 86 | return x + a * (y - x) 87 | }; 88 | 89 | Maf.lerp = Maf.mix; 90 | 91 | Maf.inverseMix = function( a, b, v ) { 92 | return ( v - a ) / ( b - a ); 93 | }; 94 | 95 | Maf.inverseLerp = Maf.inverseMix; 96 | 97 | Maf.mixUnclamped = function( x, y, a ) { 98 | if( a <= 0 ) return x; 99 | if( a >= 1 ) return y; 100 | return x + a * (y - x) 101 | }; 102 | 103 | Maf.lerpUnclamped = Maf.mixUnclamped; 104 | 105 | // https://www.opengl.org/sdk/docs/man/html/fract.xhtml 106 | 107 | Maf.fract = function( v ) { 108 | return v - Math.floor( v ); 109 | }; 110 | 111 | Maf.frac = Maf.fract; 112 | 113 | // http://stackoverflow.com/questions/4965301/finding-if-a-number-is-a-power-of-2 114 | 115 | Maf.isPowerOfTwo = function( v ) { 116 | return ( ( ( v - 1) & v ) == 0 ); 117 | }; 118 | 119 | // https://bocoup.com/weblog/find-the-closest-power-of-2-with-javascript 120 | 121 | Maf.closestPowerOfTwo = function( v ) { 122 | return Math.pow( 2, Math.round( Math.log( v ) / Math.log( 2 ) ) ); 123 | }; 124 | 125 | Maf.nextPowerOfTwo = function( v ) { 126 | return Math.pow( 2, Math.ceil( Math.log( v ) / Math.log( 2 ) ) ); 127 | } 128 | 129 | // http://stackoverflow.com/questions/1878907/the-smallest-difference-between-2-angles 130 | 131 | //function mod(a, n) { return a - Math.floor(a/n) * n; } 132 | Maf.mod = function(a, n) { return (a % n + n) % n; } 133 | 134 | Maf.deltaAngle = function( a, b ) { 135 | var d = Maf.mod( b - a, 360 ); 136 | if( d > 180 ) d = Math.abs( d - 360 ); 137 | return d; 138 | }; 139 | 140 | Maf.deltaAngleDeg = Maf.deltaAngle; 141 | 142 | Maf.deltaAngleRad = function( a, b ) { 143 | return Maf.toRadians( Maf.deltaAngle( Maf.toDegrees( a ), Maf.toDegrees( b ) ) ); 144 | }; 145 | 146 | Maf.lerpAngle = function( a, b, t ) { 147 | var angle = Maf.deltaAngle( a, b ); 148 | return Maf.mod( a + Maf.lerp( 0, angle, t ), 360 ); 149 | }; 150 | 151 | Maf.lerpAngleDeg = Maf.lerpAngle; 152 | 153 | Maf.lerpAngleRad = function( a, b, t ) { 154 | return Maf.toRadians( Maf.lerpAngleDeg( Maf.toDegrees( a ), Maf.toDegrees( b ), t ) ); 155 | }; 156 | 157 | // http://gamedev.stackexchange.com/questions/74324/gamma-space-and-linear-space-with-shader 158 | 159 | Maf.gammaToLinearSpace = function( v ) { 160 | return Math.pow( v, 2.2 ); 161 | }; 162 | 163 | Maf.linearToGammaSpace = function( v ) { 164 | return Math.pow( v, 1 / 2.2 ); 165 | }; 166 | 167 | Maf.map = function( from1, to1, from2, to2, v ) { 168 | return from2 + ( v - from1 ) * ( to2 - from2 ) / ( to1 - from1 ); 169 | } 170 | 171 | Maf.scale = Maf.map; 172 | 173 | // http://www.iquilezles.org/www/articles/functions/functions.htm 174 | 175 | Maf.almostIdentity = function( x, m, n ) { 176 | 177 | if( x > m ) return x; 178 | 179 | var a = 2 * n - m; 180 | var b = 2 * m - 3 * n; 181 | var t = x / m; 182 | 183 | return ( a * t + b) * t * t + n; 184 | } 185 | 186 | Maf.impulse = function( k, x ) { 187 | var h = k * x; 188 | return h * Math.exp( 1 - h ); 189 | }; 190 | 191 | Maf.cubicPulse = function( c, w, x ) { 192 | x = Math.abs( x - c ); 193 | if( x > w ) return 0; 194 | x /= w; 195 | return 1 - x * x * ( 3 - 2 * x ); 196 | } 197 | 198 | Maf.expStep = function( x, k, n ) { 199 | return Math.exp( -k * Math.pow( x, n ) ); 200 | } 201 | 202 | Maf.parabola = function( x, k ) { 203 | return Math.pow( 4 * x * ( 1 - x ), k ); 204 | } 205 | 206 | Maf.powerCurve = function( x, a, b ) { 207 | var k = Math.pow( a + b, a + b ) / ( Math.pow( a, a ) * Math.pow( b, b ) ); 208 | return k * Math.pow( x, a ) * Math.pow( 1 - x, b ); 209 | } 210 | 211 | // http://iquilezles.org/www/articles/smin/smin.htm ? 212 | 213 | Maf.latLonToCartesian = function( lat, lon ) { 214 | 215 | lon += 180; 216 | lat = Maf.clamp( lat, -85, 85 ); 217 | var phi = Maf.toRadians( 90 - lat ); 218 | var theta = Maf.toRadians( 180 - lon ); 219 | var x = Math.sin( phi ) * Math.cos( theta ); 220 | var y = Math.cos( phi ); 221 | var z = Math.sin( phi ) * Math.sin( theta ); 222 | 223 | return { x: x, y: y, z: z } 224 | 225 | } 226 | 227 | Maf.cartesianToLatLon = function( x, y, z ) { 228 | var n = Math.sqrt( x * x + y * y + z * z ); 229 | return{ lat: Math.asin( z / n ), lon: Math.atan2( y, x ) }; 230 | } 231 | 232 | Maf.randomInRange = function( min, max ) { 233 | return min + Math.random() * ( max - min ); 234 | } 235 | 236 | Maf.norm = function( v, minVal, maxVal ) { 237 | return ( v - minVal ) / ( maxVal - minVal ); 238 | } 239 | 240 | Maf.hash = function( n ) { 241 | return Maf.fract( (1.0 + Math.cos(n)) * 415.92653); 242 | } 243 | 244 | Maf.noise2d = function( x, y ) { 245 | var xhash = Maf.hash( x * 37.0 ); 246 | var yhash = Maf.hash( y * 57.0 ); 247 | return Maf.fract( xhash + yhash ); 248 | } 249 | 250 | // http://iquilezles.org/www/articles/smin/smin.htm 251 | 252 | Maf.smoothMin = function( a, b, k ) { 253 | var res = Math.exp( -k*a ) + Math.exp( -k*b ); 254 | return - Math.log( res )/k; 255 | } 256 | 257 | Maf.smoothMax = function( a, b, k ){ 258 | return Math.log( Math.exp(a) + Math.exp(b) )/k; 259 | } 260 | 261 | 262 | }()); -------------------------------------------------------------------------------- /js/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 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 12 | // supported. 13 | // 14 | // Orbit - left mouse / touch: one finger move 15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 16 | // Pan - right mouse, or arrow keys / touch: three finter swipe 17 | // 18 | // This is a drop-in replacement for (most) TrackballControls used in examples. 19 | // That is, include this js file and wherever you see: 20 | // controls = new THREE.TrackballControls( camera ); 21 | // controls.target.z = 150; 22 | // Simple substitute "OrbitControls" and the control should work as-is. 23 | 24 | THREE.OrbitControls = function ( object, domElement ) { 25 | 26 | this.object = object; 27 | this.domElement = ( domElement !== undefined ) ? domElement : document; 28 | 29 | // API 30 | 31 | // Set to false to disable this control 32 | this.enabled = true; 33 | 34 | // "target" sets the location of focus, where the control orbits around 35 | // and where it pans with respect to. 36 | this.target = new THREE.Vector3(); 37 | 38 | // center is old, deprecated; use "target" instead 39 | this.center = this.target; 40 | 41 | // This option actually enables dollying in and out; left as "zoom" for 42 | // backwards compatibility 43 | this.noZoom = false; 44 | this.zoomSpeed = 1.0; 45 | 46 | // Limits to how far you can dolly in and out 47 | this.minDistance = 0; 48 | this.maxDistance = Infinity; 49 | 50 | // Set to true to disable this control 51 | this.noRotate = false; 52 | this.rotateSpeed = 1.0; 53 | 54 | // Set to true to disable this control 55 | this.noPan = false; 56 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 57 | 58 | // Set to true to automatically rotate around the target 59 | this.autoRotate = false; 60 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 61 | 62 | // How far you can orbit vertically, upper and lower limits. 63 | // Range is 0 to Math.PI radians. 64 | this.minPolarAngle = 0; // radians 65 | this.maxPolarAngle = Math.PI; // radians 66 | 67 | // Set to true to disable use of the keys 68 | this.noKeys = false; 69 | 70 | // The four arrow keys 71 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 72 | 73 | //////////// 74 | // internals 75 | 76 | var scope = this; 77 | 78 | var EPS = 0.000001; 79 | 80 | var rotateStart = new THREE.Vector2(); 81 | var rotateEnd = new THREE.Vector2(); 82 | var rotateDelta = new THREE.Vector2(); 83 | 84 | var panStart = new THREE.Vector2(); 85 | var panEnd = new THREE.Vector2(); 86 | var panDelta = new THREE.Vector2(); 87 | var panOffset = new THREE.Vector3(); 88 | 89 | var offset = new THREE.Vector3(); 90 | 91 | var dollyStart = new THREE.Vector2(); 92 | var dollyEnd = new THREE.Vector2(); 93 | var dollyDelta = new THREE.Vector2(); 94 | 95 | var phiDelta = 0; 96 | var thetaDelta = 0; 97 | var scale = 1; 98 | var pan = new THREE.Vector3(); 99 | 100 | var lastPosition = new THREE.Vector3(); 101 | 102 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 103 | 104 | var state = STATE.NONE; 105 | 106 | // for reset 107 | 108 | this.target0 = this.target.clone(); 109 | this.position0 = this.object.position.clone(); 110 | 111 | // so camera.up is the orbit axis 112 | 113 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 114 | var quatInverse = quat.clone().inverse(); 115 | 116 | // events 117 | 118 | var changeEvent = { type: 'change' }; 119 | var startEvent = { type: 'start'}; 120 | var endEvent = { type: 'end'}; 121 | 122 | this.rotateLeft = function ( angle ) { 123 | 124 | if ( angle === undefined ) { 125 | 126 | angle = getAutoRotationAngle(); 127 | 128 | } 129 | 130 | thetaDelta -= angle; 131 | 132 | }; 133 | 134 | this.rotateUp = function ( angle ) { 135 | 136 | if ( angle === undefined ) { 137 | 138 | angle = getAutoRotationAngle(); 139 | 140 | } 141 | 142 | phiDelta -= angle; 143 | 144 | }; 145 | 146 | // pass in distance in world space to move left 147 | this.panLeft = function ( distance ) { 148 | 149 | var te = this.object.matrix.elements; 150 | 151 | // get X column of matrix 152 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 153 | panOffset.multiplyScalar( - distance ); 154 | 155 | pan.add( panOffset ); 156 | 157 | }; 158 | 159 | // pass in distance in world space to move up 160 | this.panUp = function ( distance ) { 161 | 162 | var te = this.object.matrix.elements; 163 | 164 | // get Y column of matrix 165 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 166 | panOffset.multiplyScalar( distance ); 167 | 168 | pan.add( panOffset ); 169 | 170 | }; 171 | 172 | // pass in x,y of change desired in pixel space, 173 | // right and down are positive 174 | this.pan = function ( deltaX, deltaY ) { 175 | 176 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 177 | 178 | if ( scope.object.fov !== undefined ) { 179 | 180 | // perspective 181 | var position = scope.object.position; 182 | var offset = position.clone().sub( scope.target ); 183 | var targetDistance = offset.length(); 184 | 185 | // half of the fov is center to top of screen 186 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 187 | 188 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 189 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); 190 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); 191 | 192 | } else if ( scope.object.top !== undefined ) { 193 | 194 | // orthographic 195 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); 196 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); 197 | 198 | } else { 199 | 200 | // camera neither orthographic or perspective 201 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 202 | 203 | } 204 | 205 | }; 206 | 207 | this.dollyIn = function ( dollyScale ) { 208 | 209 | if ( dollyScale === undefined ) { 210 | 211 | dollyScale = getZoomScale(); 212 | 213 | } 214 | 215 | scale /= dollyScale; 216 | 217 | }; 218 | 219 | this.dollyOut = function ( dollyScale ) { 220 | 221 | if ( dollyScale === undefined ) { 222 | 223 | dollyScale = getZoomScale(); 224 | 225 | } 226 | 227 | scale *= dollyScale; 228 | 229 | }; 230 | 231 | this.update = function () { 232 | 233 | var position = this.object.position; 234 | 235 | offset.copy( position ).sub( this.target ); 236 | 237 | // rotate offset to "y-axis-is-up" space 238 | offset.applyQuaternion( quat ); 239 | 240 | // angle from z-axis around y-axis 241 | 242 | var theta = Math.atan2( offset.x, offset.z ); 243 | 244 | // angle from y-axis 245 | 246 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 247 | 248 | if ( this.autoRotate ) { 249 | 250 | this.rotateLeft( getAutoRotationAngle() ); 251 | 252 | } 253 | 254 | theta += thetaDelta; 255 | phi += phiDelta; 256 | 257 | // restrict phi to be between desired limits 258 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 259 | 260 | // restrict phi to be betwee EPS and PI-EPS 261 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 262 | 263 | var radius = offset.length() * scale; 264 | 265 | // restrict radius to be between desired limits 266 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 267 | 268 | // move target to panned location 269 | this.target.add( pan ); 270 | 271 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 272 | offset.y = radius * Math.cos( phi ); 273 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 274 | 275 | // rotate offset back to "camera-up-vector-is-up" space 276 | offset.applyQuaternion( quatInverse ); 277 | 278 | position.copy( this.target ).add( offset ); 279 | 280 | this.object.lookAt( this.target ); 281 | 282 | thetaDelta = 0; 283 | phiDelta = 0; 284 | scale = 1; 285 | pan.set( 0, 0, 0 ); 286 | 287 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS ) { 288 | 289 | this.dispatchEvent( changeEvent ); 290 | 291 | lastPosition.copy( this.object.position ); 292 | 293 | } 294 | 295 | }; 296 | 297 | 298 | this.reset = function () { 299 | 300 | state = STATE.NONE; 301 | 302 | this.target.copy( this.target0 ); 303 | this.object.position.copy( this.position0 ); 304 | 305 | this.update(); 306 | 307 | }; 308 | 309 | function getAutoRotationAngle() { 310 | 311 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 312 | 313 | } 314 | 315 | function getZoomScale() { 316 | 317 | return Math.pow( 0.95, scope.zoomSpeed ); 318 | 319 | } 320 | 321 | function onMouseDown( event ) { 322 | 323 | if ( scope.enabled === false ) return; 324 | event.preventDefault(); 325 | 326 | if ( event.button === 0 ) { 327 | if ( scope.noRotate === true ) return; 328 | 329 | state = STATE.ROTATE; 330 | 331 | rotateStart.set( event.clientX, event.clientY ); 332 | 333 | } else if ( event.button === 1 ) { 334 | if ( scope.noZoom === true ) return; 335 | 336 | state = STATE.DOLLY; 337 | 338 | dollyStart.set( event.clientX, event.clientY ); 339 | 340 | } else if ( event.button === 2 ) { 341 | if ( scope.noPan === true ) return; 342 | 343 | state = STATE.PAN; 344 | 345 | panStart.set( event.clientX, event.clientY ); 346 | 347 | } 348 | 349 | scope.domElement.addEventListener( 'mousemove', onMouseMove, false ); 350 | scope.domElement.addEventListener( 'mouseup', onMouseUp, false ); 351 | scope.dispatchEvent( startEvent ); 352 | 353 | } 354 | 355 | function onMouseMove( event ) { 356 | 357 | if ( scope.enabled === false ) return; 358 | 359 | event.preventDefault(); 360 | 361 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 362 | 363 | if ( state === STATE.ROTATE ) { 364 | 365 | if ( scope.noRotate === true ) return; 366 | 367 | rotateEnd.set( event.clientX, event.clientY ); 368 | rotateDelta.subVectors( rotateEnd, rotateStart ); 369 | 370 | // rotating across whole screen goes 360 degrees around 371 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 372 | 373 | // rotating up and down along whole screen attempts to go 360, but limited to 180 374 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 375 | 376 | rotateStart.copy( rotateEnd ); 377 | 378 | } else if ( state === STATE.DOLLY ) { 379 | 380 | if ( scope.noZoom === true ) return; 381 | 382 | dollyEnd.set( event.clientX, event.clientY ); 383 | dollyDelta.subVectors( dollyEnd, dollyStart ); 384 | 385 | if ( dollyDelta.y > 0 ) { 386 | 387 | scope.dollyIn(); 388 | 389 | } else { 390 | 391 | scope.dollyOut(); 392 | 393 | } 394 | 395 | dollyStart.copy( dollyEnd ); 396 | 397 | } else if ( state === STATE.PAN ) { 398 | 399 | if ( scope.noPan === true ) return; 400 | 401 | panEnd.set( event.clientX, event.clientY ); 402 | panDelta.subVectors( panEnd, panStart ); 403 | 404 | scope.pan( panDelta.x, panDelta.y ); 405 | 406 | panStart.copy( panEnd ); 407 | 408 | } 409 | 410 | scope.update(); 411 | 412 | } 413 | 414 | function onMouseUp( /* event */ ) { 415 | 416 | if ( scope.enabled === false ) return; 417 | 418 | scope.domElement.removeEventListener( 'mousemove', onMouseMove, false ); 419 | scope.domElement.removeEventListener( 'mouseup', onMouseUp, false ); 420 | scope.dispatchEvent( endEvent ); 421 | state = STATE.NONE; 422 | 423 | } 424 | 425 | function onMouseWheel( event ) { 426 | 427 | if ( scope.enabled === false || scope.noZoom === true ) return; 428 | 429 | event.preventDefault(); 430 | event.stopPropagation(); 431 | 432 | var delta = 0; 433 | 434 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 435 | 436 | delta = event.wheelDelta; 437 | 438 | } else if ( event.detail !== undefined ) { // Firefox 439 | 440 | delta = - event.detail; 441 | 442 | } 443 | 444 | if ( delta > 0 ) { 445 | 446 | scope.dollyOut(); 447 | 448 | } else { 449 | 450 | scope.dollyIn(); 451 | 452 | } 453 | 454 | scope.update(); 455 | scope.dispatchEvent( startEvent ); 456 | scope.dispatchEvent( changeEvent ); 457 | scope.dispatchEvent( endEvent ); 458 | 459 | } 460 | 461 | function onKeyDown( event ) { 462 | 463 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; 464 | 465 | switch ( event.keyCode ) { 466 | 467 | case scope.keys.UP: 468 | scope.pan( 0, scope.keyPanSpeed ); 469 | scope.update(); 470 | break; 471 | 472 | case scope.keys.BOTTOM: 473 | scope.pan( 0, - scope.keyPanSpeed ); 474 | scope.update(); 475 | break; 476 | 477 | case scope.keys.LEFT: 478 | scope.pan( scope.keyPanSpeed, 0 ); 479 | scope.update(); 480 | break; 481 | 482 | case scope.keys.RIGHT: 483 | scope.pan( - scope.keyPanSpeed, 0 ); 484 | scope.update(); 485 | break; 486 | 487 | } 488 | 489 | } 490 | 491 | function touchstart( event ) { 492 | 493 | if ( scope.enabled === false ) return; 494 | 495 | switch ( event.touches.length ) { 496 | 497 | case 1: // one-fingered touch: rotate 498 | 499 | if ( scope.noRotate === true ) return; 500 | 501 | state = STATE.TOUCH_ROTATE; 502 | 503 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 504 | break; 505 | 506 | case 2: // two-fingered touch: dolly 507 | 508 | if ( scope.noZoom === true ) return; 509 | 510 | state = STATE.TOUCH_DOLLY; 511 | 512 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 513 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 514 | var distance = Math.sqrt( dx * dx + dy * dy ); 515 | dollyStart.set( 0, distance ); 516 | break; 517 | 518 | case 3: // three-fingered touch: pan 519 | 520 | if ( scope.noPan === true ) return; 521 | 522 | state = STATE.TOUCH_PAN; 523 | 524 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 525 | break; 526 | 527 | default: 528 | 529 | state = STATE.NONE; 530 | 531 | } 532 | 533 | scope.dispatchEvent( startEvent ); 534 | 535 | } 536 | 537 | function touchmove( event ) { 538 | 539 | if ( scope.enabled === false ) return; 540 | 541 | event.preventDefault(); 542 | event.stopPropagation(); 543 | 544 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 545 | 546 | switch ( event.touches.length ) { 547 | 548 | case 1: // one-fingered touch: rotate 549 | 550 | if ( scope.noRotate === true ) return; 551 | if ( state !== STATE.TOUCH_ROTATE ) return; 552 | 553 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 554 | rotateDelta.subVectors( rotateEnd, rotateStart ); 555 | 556 | // rotating across whole screen goes 360 degrees around 557 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 558 | // rotating up and down along whole screen attempts to go 360, but limited to 180 559 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 560 | 561 | rotateStart.copy( rotateEnd ); 562 | 563 | scope.update(); 564 | break; 565 | 566 | case 2: // two-fingered touch: dolly 567 | 568 | if ( scope.noZoom === true ) return; 569 | if ( state !== STATE.TOUCH_DOLLY ) return; 570 | 571 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 572 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 573 | var distance = Math.sqrt( dx * dx + dy * dy ); 574 | 575 | dollyEnd.set( 0, distance ); 576 | dollyDelta.subVectors( dollyEnd, dollyStart ); 577 | 578 | if ( dollyDelta.y > 0 ) { 579 | 580 | scope.dollyOut(); 581 | 582 | } else { 583 | 584 | scope.dollyIn(); 585 | 586 | } 587 | 588 | dollyStart.copy( dollyEnd ); 589 | 590 | scope.update(); 591 | break; 592 | 593 | case 3: // three-fingered touch: pan 594 | 595 | if ( scope.noPan === true ) return; 596 | if ( state !== STATE.TOUCH_PAN ) return; 597 | 598 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 599 | panDelta.subVectors( panEnd, panStart ); 600 | 601 | scope.pan( panDelta.x, panDelta.y ); 602 | 603 | panStart.copy( panEnd ); 604 | 605 | scope.update(); 606 | break; 607 | 608 | default: 609 | 610 | state = STATE.NONE; 611 | 612 | } 613 | 614 | } 615 | 616 | function touchend( /* event */ ) { 617 | 618 | if ( scope.enabled === false ) return; 619 | 620 | scope.dispatchEvent( endEvent ); 621 | state = STATE.NONE; 622 | 623 | } 624 | 625 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 626 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 627 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 628 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 629 | 630 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 631 | this.domElement.addEventListener( 'touchend', touchend, false ); 632 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 633 | 634 | window.addEventListener( 'keydown', onKeyDown, false ); 635 | 636 | // force an update at start 637 | this.update(); 638 | 639 | }; 640 | 641 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); -------------------------------------------------------------------------------- /js/THREE.ConstantSpline.js: -------------------------------------------------------------------------------- 1 | THREE.ConstantSpline = function() { 2 | 3 | this.p0 = new THREE.Vector3(); 4 | this.p1 = new THREE.Vector3(); 5 | this.p2 = new THREE.Vector3(); 6 | this.p3 = new THREE.Vector3(); 7 | 8 | this.tmp = new THREE.Vector3(); 9 | this.res = new THREE.Vector3(); 10 | this.o = new THREE.Vector3(); 11 | 12 | this.points = []; 13 | this.lPoints = []; 14 | this.steps = []; 15 | 16 | this.inc = .01; 17 | this.d = 0; 18 | 19 | this.distancesNeedUpdate = false; 20 | 21 | }; 22 | 23 | THREE.ConstantSpline.prototype.calculate = function() { 24 | 25 | this.d = 0; 26 | this.points = []; 27 | 28 | this.o.copy( this.p0 ); 29 | 30 | for( var j = 0; j <= 1; j += this.inc ) { 31 | 32 | var i = ( 1 - j ); 33 | var ii = i * i; 34 | var iii = ii * i; 35 | var jj = j * j; 36 | var jjj = jj * j; 37 | 38 | this.res.set( 0, 0, 0 ); 39 | 40 | this.tmp.copy( this.p0 ); 41 | this.tmp.multiplyScalar( iii ); 42 | this.res.add( this.tmp ); 43 | 44 | this.tmp.copy( this.p1 ); 45 | this.tmp.multiplyScalar( 3 * j * ii ); 46 | this.res.add( this.tmp ); 47 | 48 | this.tmp.copy( this.p2 ); 49 | this.tmp.multiplyScalar( 3 * jj * i ); 50 | this.res.add( this.tmp ); 51 | 52 | this.tmp.copy( this.p3 ); 53 | this.tmp.multiplyScalar( jjj ); 54 | this.res.add( this.tmp ); 55 | 56 | this.points.push( this.res.clone() ); 57 | 58 | } 59 | 60 | this.points.push( this.p3.clone() ); 61 | 62 | this.distancesNeedUpdate = true; 63 | 64 | }; 65 | 66 | THREE.ConstantSpline.prototype.calculateDistances = function() { 67 | 68 | this.steps = []; 69 | this.d = 0; 70 | 71 | var from, to, td = 0; 72 | 73 | for( var j = 0; j < this.points.length - 1; j++ ) { 74 | 75 | this.points[ j ].distance = td; 76 | this.points[ j ].ac = this.d; 77 | 78 | from = this.points[ j ], 79 | to = this.points[ j + 1 ], 80 | td = to.distanceTo( from ); 81 | 82 | this.d += td; 83 | 84 | } 85 | 86 | this.points[ this.points.length - 1 ].distance = 0; 87 | this.points[ this.points.length - 1 ].ac = this.d; 88 | 89 | } 90 | 91 | THREE.ConstantSpline.prototype.reticulate = function( settings ) { 92 | 93 | if( this.distancesNeedUpdate ) { 94 | this.calculateDistances(); 95 | this.distancesNeedUpdate = false; 96 | } 97 | 98 | this.lPoints = []; 99 | 100 | var l = []; 101 | 102 | var steps, distancePerStep; 103 | 104 | if( settings.steps) { 105 | steps = settings.steps; 106 | distancePerStep = this.d / steps; 107 | } 108 | 109 | if( settings.distancePerStep ) { 110 | distancePerStep = settings.distancePerStep; 111 | steps = this.d / distancePerStep; 112 | } 113 | 114 | var d = 0, 115 | p = 0; 116 | 117 | this.lPoints = []; 118 | 119 | var current = new THREE.Vector3(); 120 | current.copy( this.points[ 0 ].clone() ); 121 | this.lPoints.push( current.clone() ); 122 | 123 | function splitSegment( a, b, l ) { 124 | 125 | var t = b.clone(); 126 | var d = 0; 127 | t.sub( a ); 128 | var rd = t.length(); 129 | t.normalize(); 130 | t.multiplyScalar( distancePerStep ); 131 | var s = Math.floor( rd / distancePerStep ); 132 | for( var j = 0; j < s; j++ ) { 133 | a.add( t ); 134 | l.push( a.clone() ); 135 | d += distancePerStep; 136 | } 137 | return d; 138 | } 139 | 140 | for( var j = 0; j < this.points.length; j++ ) { 141 | 142 | if( this.points[ j ].ac - d > distancePerStep ) { 143 | 144 | d += splitSegment( current, this.points[ j ], this.lPoints ); 145 | 146 | } 147 | 148 | } 149 | this.lPoints.push( this.points[ this.points.length - 1 ].clone() ); 150 | 151 | 152 | }; -------------------------------------------------------------------------------- /js/THREE.MeshLine.js: -------------------------------------------------------------------------------- 1 | THREE.MeshLine = function() { 2 | 3 | this.positions = []; 4 | 5 | this.previous = []; 6 | this.next = []; 7 | this.side = []; 8 | this.width = []; 9 | this.indices_array = []; 10 | this.uvs = []; 11 | 12 | this.geometry = new THREE.BufferGeometry(); 13 | 14 | this.widthCallback = null; 15 | 16 | } 17 | 18 | THREE.MeshLine.prototype.setGeometry = function( g, c ) { 19 | 20 | this.widthCallback = c; 21 | 22 | this.positions = []; 23 | 24 | if( g instanceof THREE.Geometry ) { 25 | for( var j = 0; j < g.vertices.length; j++ ) { 26 | var v = g.vertices[ j ]; 27 | this.positions.push( v.x, v.y, v.z ); 28 | this.positions.push( v.x, v.y, v.z ); 29 | } 30 | } 31 | 32 | if( g instanceof THREE.BufferGeometry ) { 33 | // read attribute positions ? 34 | } 35 | 36 | if( g instanceof Float32Array || g instanceof Array ) { 37 | for( var j = 0; j < g.length; j += 3 ) { 38 | this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] ); 39 | this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] ); 40 | } 41 | } 42 | 43 | this.process(); 44 | 45 | } 46 | 47 | THREE.MeshLine.prototype.compareV3 = function( a, b ) { 48 | 49 | var aa = a * 6; 50 | var ab = b * 6; 51 | return ( this.positions[ aa ] === this.positions[ ab ] ) && ( this.positions[ aa + 1 ] === this.positions[ ab + 1 ] ) && ( this.positions[ aa + 2 ] === this.positions[ ab + 2 ] ); 52 | 53 | } 54 | 55 | THREE.MeshLine.prototype.copyV3 = function( a ) { 56 | 57 | var aa = a * 6; 58 | return [ this.positions[ aa ], this.positions[ aa + 1 ], this.positions[ aa + 2 ] ]; 59 | 60 | } 61 | 62 | THREE.MeshLine.prototype.process = function() { 63 | 64 | var l = this.positions.length / 6; 65 | 66 | this.previous = []; 67 | this.next = []; 68 | this.side = []; 69 | this.width = []; 70 | this.indices_array = []; 71 | this.uvs = []; 72 | 73 | for( var j = 0; j < l; j++ ) { 74 | this.side.push( 1 ); 75 | this.side.push( -1 ); 76 | } 77 | 78 | var w; 79 | for( var j = 0; j < l; j++ ) { 80 | if( this.widthCallback ) w = this.widthCallback( j / ( l -1 ) ); 81 | else w = 1; 82 | this.width.push( w ); 83 | this.width.push( w ); 84 | } 85 | 86 | for( var j = 0; j < l; j++ ) { 87 | this.uvs.push( j / ( l - 1 ), 0 ); 88 | this.uvs.push( j / ( l - 1 ), 1 ); 89 | } 90 | 91 | var v; 92 | 93 | if( this.compareV3( 0, l - 1 ) ){ 94 | v = this.copyV3( l - 2 ); 95 | } else { 96 | v = this.copyV3( 0 ); 97 | } 98 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 99 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 100 | for( var j = 0; j < l - 1; j++ ) { 101 | v = this.copyV3( j ); 102 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 103 | this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 104 | } 105 | 106 | for( var j = 1; j < l; j++ ) { 107 | v = this.copyV3( j ); 108 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 109 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 110 | } 111 | 112 | if( this.compareV3( l - 1, 0 ) ){ 113 | v = this.copyV3( 1 ); 114 | } else { 115 | v = this.copyV3( l - 1 ); 116 | } 117 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 118 | this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 119 | 120 | for( var j = 0; j < l - 1; j++ ) { 121 | var n = j * 2; 122 | this.indices_array.push( n, n + 1, n + 2 ); 123 | this.indices_array.push( n + 2, n + 1, n + 3 ); 124 | } 125 | 126 | if (!this.attributes) { 127 | this.attributes = { 128 | position: new THREE.BufferAttribute( new Float32Array( this.positions ), 3 ), 129 | previous: new THREE.BufferAttribute( new Float32Array( this.previous ), 3 ), 130 | next: new THREE.BufferAttribute( new Float32Array( this.next ), 3 ), 131 | side: new THREE.BufferAttribute( new Float32Array( this.side ), 1 ), 132 | width: new THREE.BufferAttribute( new Float32Array( this.width ), 1 ), 133 | uv: new THREE.BufferAttribute( new Float32Array( this.uvs ), 2 ), 134 | index: new THREE.BufferAttribute( new Uint16Array( this.indices_array ), 1 ) 135 | } 136 | } else { 137 | this.attributes.position.copyArray(new Float32Array(this.positions)); 138 | this.attributes.position.needsUpdate = true; 139 | this.attributes.previous.copyArray(new Float32Array(this.previous)); 140 | this.attributes.previous.needsUpdate = true; 141 | this.attributes.next.copyArray(new Float32Array(this.next)); 142 | this.attributes.next.needsUpdate = true; 143 | this.attributes.side.copyArray(new Float32Array(this.side)); 144 | this.attributes.side.needsUpdate = true; 145 | this.attributes.width.copyArray(new Float32Array(this.width)); 146 | this.attributes.width.needsUpdate = true; 147 | this.attributes.uv.copyArray(new Float32Array(this.uvs)); 148 | this.attributes.uv.needsUpdate = true; 149 | this.attributes.index.copyArray(new Uint16Array(this.index)); 150 | this.attributes.index.needsUpdate = true; 151 | } 152 | 153 | this.geometry.addAttribute( 'position', this.attributes.position ); 154 | this.geometry.addAttribute( 'previous', this.attributes.previous ); 155 | this.geometry.addAttribute( 'next', this.attributes.next ); 156 | this.geometry.addAttribute( 'side', this.attributes.side ); 157 | this.geometry.addAttribute( 'width', this.attributes.width ); 158 | this.geometry.addAttribute( 'uv', this.attributes.uv ); 159 | 160 | this.geometry.setIndex( this.attributes.index ); 161 | 162 | } 163 | 164 | THREE.MeshLineMaterial = function ( parameters ) { 165 | 166 | var vertexShaderSource = [ 167 | 'precision highp float;', 168 | '', 169 | 'attribute vec3 position;', 170 | 'attribute vec3 previous;', 171 | 'attribute vec3 next;', 172 | 'attribute float side;', 173 | 'attribute float width;', 174 | 'attribute vec2 uv;', 175 | '', 176 | 'uniform mat4 projectionMatrix;', 177 | 'uniform mat4 modelViewMatrix;', 178 | 'uniform vec2 resolution;', 179 | 'uniform float lineWidth;', 180 | 'uniform vec3 color;', 181 | 'uniform float opacity;', 182 | 'uniform float near;', 183 | 'uniform float far;', 184 | 'uniform float sizeAttenuation;', 185 | '', 186 | 'varying vec2 vUV;', 187 | 'varying vec4 vColor;', 188 | 'varying float depth;', 189 | '', 190 | 'vec2 fix( vec4 i, float aspect ) {', 191 | '', 192 | ' vec2 res = i.xy / i.w;', 193 | ' res.x *= aspect;', 194 | ' return res;', 195 | '', 196 | '}', 197 | '', 198 | 'void main() {', 199 | '', 200 | ' float aspect = resolution.x / resolution.y;', 201 | ' float pixelWidthRatio = 1. / (resolution.x * projectionMatrix[0][0]);', 202 | '', 203 | ' vColor = vec4( color, opacity );', 204 | ' vUV = uv;', 205 | '', 206 | ' mat4 m = projectionMatrix * modelViewMatrix;', 207 | ' vec4 finalPosition = m * vec4( position, 1.0 );', 208 | ' vec4 prevPos = m * vec4( previous, 1.0 );', 209 | ' vec4 nextPos = m * vec4( next, 1.0 );', 210 | '', 211 | ' vec2 currentP = fix( finalPosition, aspect );', 212 | ' vec2 prevP = fix( prevPos, aspect );', 213 | ' vec2 nextP = fix( nextPos, aspect );', 214 | '', 215 | ' float pixelWidth = finalPosition.w * pixelWidthRatio;', 216 | ' float w = 1.8 * pixelWidth * lineWidth * width;', 217 | '', 218 | ' if( sizeAttenuation == 1. ) {', 219 | ' w = 1.8 * lineWidth * width;', 220 | ' }', 221 | '', 222 | ' vec2 dir;', 223 | ' if( nextP == currentP ) dir = normalize( currentP - prevP );', 224 | ' else if( prevP == currentP ) dir = normalize( nextP - currentP );', 225 | ' else {', 226 | ' vec2 dir1 = normalize( currentP - prevP );', 227 | ' vec2 dir2 = normalize( nextP - currentP );', 228 | ' dir = normalize( dir1 + dir2 );', 229 | '', 230 | ' vec2 perp = vec2( -dir1.y, dir1.x );', 231 | ' vec2 miter = vec2( -dir.y, dir.x );', 232 | ' //w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );', 233 | '', 234 | ' }', 235 | '', 236 | ' //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;', 237 | ' vec2 normal = vec2( -dir.y, dir.x );', 238 | ' normal.x /= aspect;', 239 | ' normal *= .5 * w;', 240 | '', 241 | ' vec4 offset = vec4( normal * side, 0.0, 1.0 );', 242 | ' finalPosition.xy += offset.xy;', 243 | '', 244 | ' gl_Position = finalPosition;', 245 | ' depth = gl_Position.z / 1100.;', 246 | '', 247 | '}' ]; 248 | 249 | var fragmentShaderSource = [ 250 | '#extension GL_OES_standard_derivatives : enable', 251 | 'precision mediump float;', 252 | '', 253 | 'uniform sampler2D map;', 254 | 'uniform float useMap;', 255 | 'uniform float useDash;', 256 | 'uniform vec2 dashArray;', 257 | 'uniform float drawDepth;', 258 | '', 259 | 'varying vec2 vUV;', 260 | 'varying vec4 vColor;', 261 | 'varying float depth;', 262 | '', 263 | 'void main() {', 264 | '', 265 | ' vec4 c = vColor;', 266 | ' if( useMap == 1. ) c *= texture2D( map, vUV );', 267 | ' if( useDash == 1. ){', 268 | ' ', 269 | ' }', 270 | ' if( drawDepth == 0. ) gl_FragColor = c; else gl_FragColor = vec4( vec3( depth ), 1. );', 271 | '', 272 | '}' ]; 273 | 274 | function check( v, d ) { 275 | if( v === undefined ) return d; 276 | return v; 277 | } 278 | 279 | THREE.Material.call( this ); 280 | 281 | parameters = parameters || {}; 282 | 283 | this.lineWidth = check( parameters.lineWidth, 1 ); 284 | this.map = check( parameters.map, null ); 285 | this.useMap = check( parameters.useMap, 0 ); 286 | this.color = check( parameters.color, new THREE.Color( 0xffffff ) ); 287 | this.opacity = check( parameters.opacity, 1 ); 288 | this.resolution = check( parameters.resolution, new THREE.Vector2( 1, 1 ) ); 289 | this.sizeAttenuation = check( parameters.sizeAttenuation, 1 ); 290 | this.near = check( parameters.near, 1 ); 291 | this.far = check( parameters.far, 1 ); 292 | this.dashArray = check( parameters.dashArray, [] ); 293 | this.useDash = ( this.dashArray !== [] ) ? 1 : 0; 294 | 295 | var material = new THREE.RawShaderMaterial( { 296 | uniforms:{ 297 | lineWidth: { type: 'f', value: this.lineWidth }, 298 | map: { type: 't', value: this.map }, 299 | useMap: { type: 'f', value: this.useMap }, 300 | color: { type: 'c', value: this.color }, 301 | opacity: { type: 'f', value: this.opacity }, 302 | resolution: { type: 'v2', value: this.resolution }, 303 | sizeAttenuation: { type: 'f', value: this.sizeAttenuation }, 304 | near: { type: 'f', value: this.near }, 305 | far: { type: 'f', value: this.far }, 306 | dashArray: { type: 'v2', value: new THREE.Vector2( this.dashArray[ 0 ], this.dashArray[ 1 ] ) }, 307 | useDash: { type: 'f', value: this.useDash }, 308 | drawDepth: { type: 'f', value: 0 } 309 | }, 310 | vertexShader: vertexShaderSource.join( '\r\n' ), 311 | fragmentShader: fragmentShaderSource.join( '\r\n' ) 312 | }); 313 | 314 | delete parameters.lineWidth; 315 | delete parameters.map; 316 | delete parameters.useMap; 317 | delete parameters.color; 318 | delete parameters.opacity; 319 | delete parameters.resolution; 320 | delete parameters.sizeAttenuation; 321 | delete parameters.near; 322 | delete parameters.far; 323 | delete parameters.dashArray; 324 | 325 | material.type = 'MeshLineMaterial'; 326 | 327 | material.setValues( parameters ); 328 | 329 | return material; 330 | 331 | }; 332 | 333 | THREE.MeshLineMaterial.prototype = Object.create( THREE.Material.prototype ); 334 | THREE.MeshLineMaterial.prototype.constructor = THREE.MeshLineMaterial; 335 | 336 | THREE.MeshLineMaterial.prototype.copy = function ( source ) { 337 | 338 | THREE.Material.prototype.copy.call( this, source ); 339 | 340 | this.lineWidth = source.lineWidth; 341 | this.map = source.map; 342 | this.useMap = source.useMap; 343 | this.color.copy( source.color ); 344 | this.opacity = source.opacity; 345 | this.resolution.copy( source.resolution ); 346 | this.sizeAttenuation = source.sizeAttenuation; 347 | this.near = source.near; 348 | this.far = source.far; 349 | 350 | return this; 351 | 352 | }; 353 | -------------------------------------------------------------------------------- /js/isMobile.min.js: -------------------------------------------------------------------------------- 1 | !function(a){var b=/iPhone/i,c=/iPod/i,d=/iPad/i,e=/(?=.*\bAndroid\b)(?=.*\bMobile\b)/i,f=/Android/i,g=/(?=.*\bAndroid\b)(?=.*\bSD4930UR\b)/i,h=/(?=.*\bAndroid\b)(?=.*\b(?:KFOT|KFTT|KFJWI|KFJWA|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|KFARWI|KFASWI|KFSAWI|KFSAWA)\b)/i,i=/IEMobile/i,j=/(?=.*\bWindows\b)(?=.*\bARM\b)/i,k=/BlackBerry/i,l=/BB10/i,m=/Opera Mini/i,n=/(CriOS|Chrome)(?=.*\bMobile\b)/i,o=/(?=.*\bFirefox\b)(?=.*\bMobile\b)/i,p=new RegExp("(?:Nexus 7|BNTV250|Kindle Fire|Silk|GT-P1000)","i"),q=function(a,b){return a.test(b)},r=function(a){var r=a||navigator.userAgent,s=r.split("[FBAN");return"undefined"!=typeof s[1]&&(r=s[0]),this.apple={phone:q(b,r),ipod:q(c,r),tablet:!q(b,r)&&q(d,r),device:q(b,r)||q(c,r)||q(d,r)},this.amazon={phone:q(g,r),tablet:!q(g,r)&&q(h,r),device:q(g,r)||q(h,r)},this.android={phone:q(g,r)||q(e,r),tablet:!q(g,r)&&!q(e,r)&&(q(h,r)||q(f,r)),device:q(g,r)||q(h,r)||q(e,r)||q(f,r)},this.windows={phone:q(i,r),tablet:q(j,r),device:q(i,r)||q(j,r)},this.other={blackberry:q(k,r),blackberry10:q(l,r),opera:q(m,r),firefox:q(o,r),chrome:q(n,r),device:q(k,r)||q(l,r)||q(m,r)||q(o,r)||q(n,r)},this.seven_inch=q(p,r),this.any=this.apple.device||this.android.device||this.windows.device||this.other.device||this.seven_inch,this.phone=this.apple.phone||this.android.phone||this.windows.phone,this.tablet=this.apple.tablet||this.android.tablet||this.windows.tablet,"undefined"==typeof window?this:void 0},s=function(){var a=new r;return a.Class=r,a};"undefined"!=typeof module&&module.exports&&"undefined"==typeof window?module.exports=r:"undefined"!=typeof module&&module.exports&&"undefined"!=typeof window?module.exports=s():"function"==typeof define&&define.amd?define("isMobile",[],a.isMobile=s()):a.isMobile=s()}(this); 2 | -------------------------------------------------------------------------------- /matcap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/3dwebfest-2016/e738ab53c8ba7b55a1dcaddcbb3a0dff412fd4ed/matcap.jpg --------------------------------------------------------------------------------