├── LICENSE ├── README.md ├── demo ├── assets │ ├── LeePerrySmith.obj │ ├── stroke.png │ ├── worldHigh.svg │ └── worldLow.svg ├── birds.html ├── css │ └── main.css ├── graph.html ├── index.html ├── js │ ├── Bird.js │ ├── ImprovedNoise.js │ ├── Maf.js │ ├── OBJLoader.js │ ├── OrbitControls.js │ ├── THREE.ConstantSpline.js │ ├── dat.gui.min.js │ ├── main-graph.js │ ├── main-shape.js │ ├── main-spinner.js │ ├── main-svg.js │ ├── main.js │ ├── pathseg.js │ └── three.min.js ├── shape.html ├── spinner.html └── svg.html ├── package.json ├── screenshots ├── birds.jpg ├── demo.jpg ├── graph.jpg ├── shape.jpg ├── spinner.jpg └── svg.jpg └── src └── THREE.MeshLine.js /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 | # MeshLine 2 | Mesh replacement for ```THREE.Line``` 3 | 4 | Instead of using GL_LINE, it uses a strip of triangles billboarded. Some examples: 5 | 6 | [![Demo](screenshots/demo.jpg)](http://spite.github.io/THREE.MeshLine/demo/index.html) 7 | [![Graph](screenshots/graph.jpg)](http://spite.github.io/THREE.MeshLine/demo/graph.html) 8 | [![Spinner](screenshots/spinner.jpg)](http://spite.github.io/THREE.MeshLine/demo/spinner.html) 9 | [![SVG](screenshots/svg.jpg)](http://spite.github.io/THREE.MeshLine/demo/svg.html) 10 | [![Shape](screenshots/shape.jpg)](http://spite.github.io/THREE.MeshLine/demo/shape.html) 11 | [![Shape](screenshots/birds.jpg)](http://spite.github.io/THREE.MeshLine/demo/birds.html) 12 | 13 | * [Demo](http://spite.github.io/THREE.MeshLine/demo/index.html): play with the different settings of materials 14 | * [Graph](http://spite.github.io/THREE.MeshLine/demo/graph.html): example of using ```MeshLine``` to plot graphs 15 | * [Spinner](http://spite.github.io/THREE.MeshLine/demo/spinner.html): example of dynamic ```MeshLine``` with texture 16 | * [SVG](http://spite.github.io/THREE.MeshLine/demo/svg.html): example of ```MeshLine``` rendering SVG Paths 17 | * [Shape](http://spite.github.io/THREE.MeshLine/demo/shape.html): example of ```MeshLine``` created from a mesh 18 | * [Birds](http://spite.github.io/THREE.MeshLine/demo/birds.html): example of ```MeshLine.advance()``` by @caramelcode (Jared Sprague) and @mwcz (Michael Clayton) 19 | 20 | ### How to use #### 21 | 22 | * Include script 23 | * Create an array of 3D coordinates 24 | * Create a MeshLine and assign the points 25 | * Create a MeshLineMaterial 26 | * Use MeshLine and MeshLineMaterial to create a THREE.Mesh 27 | 28 | #### Include the script #### 29 | 30 | Include script after THREE is included 31 | ```js 32 | 33 | ``` 34 | or use npm to install it 35 | ``` 36 | npm i three.meshline 37 | ``` 38 | and include it in your code (don't forget to require three.js) 39 | ```js 40 | const THREE = require('three'); 41 | const MeshLine = require('three.meshline').MeshLine; 42 | const MeshLineMaterial = require('three.meshline').MeshLineMaterial; 43 | const MeshLineRaycast = require('three.meshline').MeshLineRaycast; 44 | ``` 45 | or 46 | ```js 47 | import * as THREE from 'three'; 48 | import { MeshLine, MeshLineMaterial, MeshLineRaycast } from 'three.meshline'; 49 | ``` 50 | 51 | ##### Create an array of 3D coordinates ##### 52 | 53 | First, create the list of numbers that will define the 3D points for the line. 54 | 55 | ```js 56 | const points = []; 57 | for (let j = 0; j < Math.PI; j += (2 * Math.PI) / 100) { 58 | points.push(Math.cos(j), Math.sin(j), 0); 59 | } 60 | ``` 61 | 62 | ```MeshLine``` also accepts a ```BufferGeometry``` looking up the vertices in it. 63 | 64 | ```js 65 | const points = []; 66 | for (let j = 0; j < Math.PI; j += 2 * Math.PI / 100) { 67 | points.push(new THREE.Vector3(Math.cos(j), Math.sin(j), 0)); 68 | } 69 | const geometry = new THREE.BufferGeometry().setFromPoints(points); 70 | const line = new MeshLine(); 71 | line.setGeometry(geometry); 72 | ``` 73 | 74 | ##### Create a MeshLine and assign the points ##### 75 | 76 | Once you have that, you can create a new `MeshLine`, and call `.setPoints()` passing the list of points. 77 | 78 | ```js 79 | const line = new MeshLine(); 80 | line.setPoints(points); 81 | ``` 82 | 83 | Note: `.setPoints` accepts a second parameter, which is a function to define the width in each point along the line. By default that value is 1, making the line width 1 \* lineWidth in the material. 84 | 85 | ```js 86 | // p is a decimal percentage of the number of points 87 | // ie. point 200 of 250 points, p = 0.8 88 | line.setPoints(geometry, p => 2); // makes width 2 * lineWidth 89 | line.setPoints(geometry, p => 1 - p); // makes width taper 90 | line.setPoints(geometry, p => 2 + Math.sin(50 * p)); // makes width sinusoidal 91 | ``` 92 | 93 | ##### Create a MeshLineMaterial ##### 94 | 95 | A ```MeshLine``` needs a ```MeshLineMaterial```: 96 | 97 | ```js 98 | const material = new MeshLineMaterial(OPTIONS); 99 | ``` 100 | 101 | By default it's a white material of width 1 unit. 102 | 103 | ```MeshLineMaterial``` has several attributes to control the appereance of the ```MeshLine```: 104 | 105 | * ```map``` - a ```THREE.Texture``` to paint along the line (requires ```useMap``` set to true) 106 | * ```useMap``` - tells the material to use ```map``` (0 - solid color, 1 use texture) 107 | * ```alphaMap``` - a ```THREE.Texture``` to use as alpha along the line (requires ```useAlphaMap``` set to true) 108 | * ```useAlphaMap``` - tells the material to use ```alphaMap``` (0 - no alpha, 1 modulate alpha) 109 | * ```repeat``` - THREE.Vector2 to define the texture tiling (applies to map and alphaMap - MIGHT CHANGE IN THE FUTURE) 110 | * ```color``` - ```THREE.Color``` to paint the line width, or tint the texture with 111 | * ```opacity``` - alpha value from 0 to 1 (requires ```transparent``` set to ```true```) 112 | * ```alphaTest``` - cutoff value from 0 to 1 113 | * ```dashArray``` - the length and space between dashes. (0 - no dash) 114 | * ```dashOffset``` - defines the location where the dash will begin. Ideal to animate the line. 115 | * ```dashRatio``` - defines the ratio between that is visible or not (0 - more visible, 1 - more invisible). 116 | * ```resolution``` - ```THREE.Vector2``` specifying the canvas size (REQUIRED) 117 | * ```sizeAttenuation``` - makes the line width constant regardless distance (1 unit is 1px on screen) (0 - attenuate, 1 - don't attenuate) 118 | * ```lineWidth``` - float defining width (if ```sizeAttenuation``` is true, it's world units; else is screen pixels) 119 | 120 | If you're rendering transparent lines or using a texture with alpha map, you should set ```depthTest``` to ```false```, ```transparent``` to ```true``` and ```blending``` to an appropriate blending mode, or use ```alphaTest```. 121 | 122 | ##### Use MeshLine and MeshLineMaterial to create a THREE.Mesh ##### 123 | 124 | Finally, we create a mesh and add it to the scene: 125 | 126 | ```js 127 | const mesh = new THREE.Mesh(line, material); 128 | scene.add(mesh); 129 | ``` 130 | 131 | You can optionally add raycast support with the following. 132 | 133 | ```js 134 | mesh.raycast = MeshLineRaycast; 135 | ``` 136 | 137 | ### Declarative use ### 138 | 139 | THREE.meshline can be used declaritively. This is how it would look like in [react-three-fiber](https://github.com/drcmda/react-three-fiber). You can try it live [here](https://codesandbox.io/s/react-three-fiber-three.meshline-example-vl221). 140 | 141 |

142 | 143 | 144 |

145 | 146 | ```jsx 147 | import { extend, Canvas } from 'react-three-fiber' 148 | import { MeshLine, MeshLineMaterial, MeshLineRaycast } from 'three.meshline' 149 | 150 | extend({ MeshLine, MeshLineMaterial }) 151 | 152 | function Line({ points, width, color }) { 153 | return ( 154 | 155 | 156 | 157 | 166 | 167 | 168 | ) 169 | } 170 | ``` 171 | 172 | Dynamic line widths can be set along each point using the `widthCallback` prop. 173 | ```jsx 174 | pointWidth * Math.random()} /> 175 | ``` 176 | 177 | ### TODO ### 178 | 179 | * Better miters 180 | * Proper sizes 181 | 182 | ### Support ### 183 | 184 | Tested successfully on 185 | 186 | * Chrome OSX, Windows, Android 187 | * Firefox OSX, Windows, Anroid 188 | * Safari OSX, iOS 189 | * Internet Explorer 11 (SVG and Shape demo won't work because they use Promises) 190 | * Opera OSX, Windows 191 | 192 | ### References ### 193 | 194 | * [Drawing lines is hard](http://mattdesl.svbtle.com/drawing-lines-is-hard) 195 | * [WebGL rendering of solid trails](http://codeflow.org/entries/2012/aug/05/webgl-rendering-of-solid-trails/) 196 | * [Drawing Antialiased Lines with OpenGL](https://www.mapbox.com/blog/drawing-antialiased-lines/) 197 | 198 | #### License #### 199 | 200 | MIT licensed 201 | 202 | Copyright (C) 2015-2016 Jaume Sanchez Elias, http://www.clicktorelease.com 203 | -------------------------------------------------------------------------------- /demo/assets/stroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/THREE.MeshLine/b49ae600898a88bdb3486a3674d5887ca1b61ff6/demo/assets/stroke.png -------------------------------------------------------------------------------- /demo/birds.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | THREE.MeshLine - THREE.MeshLine advance() Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

THREE.MeshLine - advance() Demo

16 |

Efficient and fast line updating. Move your mouse around.

17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 479 | 480 | 481 | 482 | -------------------------------------------------------------------------------- /demo/css/main.css: -------------------------------------------------------------------------------- 1 | *{ box-sizing: border-box; margin :0; padding: 0;} 2 | body{ overflow: hidden; font-family: Lato, sans-serif; width: 100%; height: 100% ; background-color: #dedede; color: #202020; padding: 20px; text-shadow: 0 1px 0 rgba(255,255,255,.5);} 3 | #container{ position: absolute; left: 0; top: 0; right: 0; bottom: 0; width: 100%; height: 100%; border: 1px solid transparent;} 4 | h1{ font-weight: 300; font-style: italic; font-size: 24px; margin-bottom: .5em;} 5 | #title{ position: absolute; left: 20px; top: 20px;} 6 | #directions{ position: absolute; left: 0; top: 50%; right: 0; text-align: center; margin-top: -2em; font-style: italic; font-size: 4vw; pointer-events: none; transition: opacity 250ms ease-out;} -------------------------------------------------------------------------------- /demo/graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | THREE.MeshLine - Graph example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

THREE.MeshLine - Graph example

18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | THREE.MeshLine - Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

THREE.MeshLine - Demo

18 |

Play with the different settings on the controls

19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /demo/js/Bird.js: -------------------------------------------------------------------------------- 1 | var Bird = function () { 2 | 3 | var scope = this; 4 | 5 | THREE.Geometry.call( this ); 6 | 7 | v( 5, 0, 0 ); 8 | v( - 5, - 2, 1 ); 9 | v( - 5, 0, 0 ); 10 | v( - 5, - 2, - 1 ); 11 | 12 | v( 0, 2, - 6 ); 13 | v( 0, 2, 6 ); 14 | v( 2, 0, 0 ); 15 | v( - 3, 0, 0 ); 16 | 17 | f3( 0, 2, 1 ); 18 | // f3( 0, 3, 2 ); 19 | 20 | f3( 4, 7, 6 ); 21 | f3( 5, 6, 7 ); 22 | 23 | this.computeFaceNormals(); 24 | 25 | function v( x, y, z ) { 26 | 27 | scope.vertices.push( new THREE.Vector3( x, y, z ) ); 28 | 29 | } 30 | 31 | function f3( a, b, c ) { 32 | 33 | scope.faces.push( new THREE.Face3( a, b, c ) ); 34 | 35 | } 36 | 37 | } 38 | 39 | Bird.prototype = Object.create( THREE.Geometry.prototype ); 40 | Bird.prototype.constructor = Bird; 41 | -------------------------------------------------------------------------------- /demo/js/ImprovedNoise.js: -------------------------------------------------------------------------------- 1 | // http://mrl.nyu.edu/~perlin/noise/ 2 | 3 | var ImprovedNoise = function () { 4 | 5 | var p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10, 6 | 23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87, 7 | 174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211, 8 | 133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208, 9 | 89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5, 10 | 202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119, 11 | 248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232, 12 | 178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249, 13 | 14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205, 14 | 93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180]; 15 | 16 | for (var i=0; i < 256 ; i++) { 17 | 18 | p[256+i] = p[i]; 19 | 20 | } 21 | 22 | function fade(t) { 23 | 24 | return t * t * t * (t * (t * 6 - 15) + 10); 25 | 26 | } 27 | 28 | function lerp(t, a, b) { 29 | 30 | return a + t * (b - a); 31 | 32 | } 33 | 34 | function grad(hash, x, y, z) { 35 | 36 | var h = hash & 15; 37 | var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; 38 | return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); 39 | 40 | } 41 | 42 | return { 43 | 44 | noise: function (x, y, z) { 45 | 46 | var floorX = Math.floor(x), floorY = Math.floor(y), floorZ = Math.floor(z); 47 | 48 | var X = floorX & 255, Y = floorY & 255, Z = floorZ & 255; 49 | 50 | x -= floorX; 51 | y -= floorY; 52 | z -= floorZ; 53 | 54 | var xMinus1 = x -1, yMinus1 = y - 1, zMinus1 = z - 1; 55 | 56 | var u = fade(x), v = fade(y), w = fade(z); 57 | 58 | var A = p[X]+Y, AA = p[A]+Z, AB = p[A+1]+Z, B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z; 59 | 60 | return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), 61 | grad(p[BA], xMinus1, y, z)), 62 | lerp(u, grad(p[AB], x, yMinus1, z), 63 | grad(p[BB], xMinus1, yMinus1, z))), 64 | lerp(v, lerp(u, grad(p[AA+1], x, y, zMinus1), 65 | grad(p[BA+1], xMinus1, y, z-1)), 66 | lerp(u, grad(p[AB+1], x, yMinus1, zMinus1), 67 | grad(p[BB+1], xMinus1, yMinus1, zMinus1)))); 68 | 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /demo/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 | Maf.almost = function( a, b ) { 262 | return ( Math.abs( a - b ) < .0001 ); 263 | } 264 | 265 | }()); -------------------------------------------------------------------------------- /demo/js/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | this.materials = null; 10 | 11 | this.regexp = { 12 | // v float float float 13 | vertex_pattern : /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 14 | // vn float float float 15 | normal_pattern : /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 16 | // vt float float 17 | uv_pattern : /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/, 18 | // f vertex vertex vertex 19 | face_vertex : /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/, 20 | // f vertex/uv vertex/uv vertex/uv 21 | face_vertex_uv : /^f\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+))?/, 22 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 23 | face_vertex_uv_normal : /^f\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)\s+(-?\d+)\/(-?\d+)\/(-?\d+)(?:\s+(-?\d+)\/(-?\d+)\/(-?\d+))?/, 24 | // f vertex//normal vertex//normal vertex//normal 25 | face_vertex_normal : /^f\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)\s+(-?\d+)\/\/(-?\d+)(?:\s+(-?\d+)\/\/(-?\d+))?/, 26 | // o object_name | g group_name 27 | object_pattern : /^[og]\s*(.+)?/, 28 | // s boolean 29 | smoothing_pattern : /^s\s+(\d+|on|off)/, 30 | // mtllib file_reference 31 | material_library_pattern : /^mtllib /, 32 | // usemtl material_name 33 | material_use_pattern : /^usemtl / 34 | }; 35 | 36 | }; 37 | 38 | THREE.OBJLoader.prototype = { 39 | 40 | constructor: THREE.OBJLoader, 41 | 42 | load: function ( url, onLoad, onProgress, onError ) { 43 | 44 | var scope = this; 45 | 46 | var loader = new THREE.FileLoader( scope.manager ); 47 | loader.setPath( this.path ); 48 | loader.load( url, function ( text ) { 49 | 50 | onLoad( scope.parse( text ) ); 51 | 52 | }, onProgress, onError ); 53 | 54 | }, 55 | 56 | setPath: function ( value ) { 57 | 58 | this.path = value; 59 | 60 | }, 61 | 62 | setMaterials: function ( materials ) { 63 | 64 | this.materials = materials; 65 | 66 | }, 67 | 68 | _createParserState : function () { 69 | 70 | var state = { 71 | objects : [], 72 | object : {}, 73 | 74 | vertices : [], 75 | normals : [], 76 | uvs : [], 77 | 78 | materialLibraries : [], 79 | 80 | startObject: function ( name, fromDeclaration ) { 81 | 82 | // If the current object (initial from reset) is not from a g/o declaration in the parsed 83 | // file. We need to use it for the first parsed g/o to keep things in sync. 84 | if ( this.object && this.object.fromDeclaration === false ) { 85 | 86 | this.object.name = name; 87 | this.object.fromDeclaration = ( fromDeclaration !== false ); 88 | return; 89 | 90 | } 91 | 92 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 93 | 94 | if ( this.object && typeof this.object._finalize === 'function' ) { 95 | 96 | this.object._finalize( true ); 97 | 98 | } 99 | 100 | this.object = { 101 | name : name || '', 102 | fromDeclaration : ( fromDeclaration !== false ), 103 | 104 | geometry : { 105 | vertices : [], 106 | normals : [], 107 | uvs : [] 108 | }, 109 | materials : [], 110 | smooth : true, 111 | 112 | startMaterial : function( name, libraries ) { 113 | 114 | var previous = this._finalize( false ); 115 | 116 | // New usemtl declaration overwrites an inherited material, except if faces were declared 117 | // after the material, then it must be preserved for proper MultiMaterial continuation. 118 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { 119 | 120 | this.materials.splice( previous.index, 1 ); 121 | 122 | } 123 | 124 | var material = { 125 | index : this.materials.length, 126 | name : name || '', 127 | mtllib : ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), 128 | smooth : ( previous !== undefined ? previous.smooth : this.smooth ), 129 | groupStart : ( previous !== undefined ? previous.groupEnd : 0 ), 130 | groupEnd : -1, 131 | groupCount : -1, 132 | inherited : false, 133 | 134 | clone : function( index ) { 135 | var cloned = { 136 | index : ( typeof index === 'number' ? index : this.index ), 137 | name : this.name, 138 | mtllib : this.mtllib, 139 | smooth : this.smooth, 140 | groupStart : 0, 141 | groupEnd : -1, 142 | groupCount : -1, 143 | inherited : false 144 | }; 145 | cloned.clone = this.clone.bind(cloned); 146 | return cloned; 147 | } 148 | }; 149 | 150 | this.materials.push( material ); 151 | 152 | return material; 153 | 154 | }, 155 | 156 | currentMaterial : function() { 157 | 158 | if ( this.materials.length > 0 ) { 159 | return this.materials[ this.materials.length - 1 ]; 160 | } 161 | 162 | return undefined; 163 | 164 | }, 165 | 166 | _finalize : function( end ) { 167 | 168 | var lastMultiMaterial = this.currentMaterial(); 169 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === -1 ) { 170 | 171 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 172 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 173 | lastMultiMaterial.inherited = false; 174 | 175 | } 176 | 177 | // Ignore objects tail materials if no face declarations followed them before a new o/g started. 178 | if ( end && this.materials.length > 1 ) { 179 | 180 | for ( var mi = this.materials.length - 1; mi >= 0; mi-- ) { 181 | if ( this.materials[mi].groupCount <= 0 ) { 182 | this.materials.splice( mi, 1 ); 183 | } 184 | } 185 | 186 | } 187 | 188 | // Guarantee at least one empty material, this makes the creation later more straight forward. 189 | if ( end && this.materials.length === 0 ) { 190 | 191 | this.materials.push({ 192 | name : '', 193 | smooth : this.smooth 194 | }); 195 | 196 | } 197 | 198 | return lastMultiMaterial; 199 | 200 | } 201 | }; 202 | 203 | // Inherit previous objects material. 204 | // Spec tells us that a declared material must be set to all objects until a new material is declared. 205 | // If a usemtl declaration is encountered while this new object is being parsed, it will 206 | // overwrite the inherited material. Exception being that there was already face declarations 207 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation. 208 | 209 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === "function" ) { 210 | 211 | var declared = previousMaterial.clone( 0 ); 212 | declared.inherited = true; 213 | this.object.materials.push( declared ); 214 | 215 | } 216 | 217 | this.objects.push( this.object ); 218 | 219 | }, 220 | 221 | finalize : function() { 222 | 223 | if ( this.object && typeof this.object._finalize === 'function' ) { 224 | 225 | this.object._finalize( true ); 226 | 227 | } 228 | 229 | }, 230 | 231 | parseVertexIndex: function ( value, len ) { 232 | 233 | var index = parseInt( value, 10 ); 234 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 235 | 236 | }, 237 | 238 | parseNormalIndex: function ( value, len ) { 239 | 240 | var index = parseInt( value, 10 ); 241 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 242 | 243 | }, 244 | 245 | parseUVIndex: function ( value, len ) { 246 | 247 | var index = parseInt( value, 10 ); 248 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; 249 | 250 | }, 251 | 252 | addVertex: function ( a, b, c ) { 253 | 254 | var src = this.vertices; 255 | var dst = this.object.geometry.vertices; 256 | 257 | dst.push( src[ a + 0 ] ); 258 | dst.push( src[ a + 1 ] ); 259 | dst.push( src[ a + 2 ] ); 260 | dst.push( src[ b + 0 ] ); 261 | dst.push( src[ b + 1 ] ); 262 | dst.push( src[ b + 2 ] ); 263 | dst.push( src[ c + 0 ] ); 264 | dst.push( src[ c + 1 ] ); 265 | dst.push( src[ c + 2 ] ); 266 | 267 | }, 268 | 269 | addVertexLine: function ( a ) { 270 | 271 | var src = this.vertices; 272 | var dst = this.object.geometry.vertices; 273 | 274 | dst.push( src[ a + 0 ] ); 275 | dst.push( src[ a + 1 ] ); 276 | dst.push( src[ a + 2 ] ); 277 | 278 | }, 279 | 280 | addNormal : function ( a, b, c ) { 281 | 282 | var src = this.normals; 283 | var dst = this.object.geometry.normals; 284 | 285 | dst.push( src[ a + 0 ] ); 286 | dst.push( src[ a + 1 ] ); 287 | dst.push( src[ a + 2 ] ); 288 | dst.push( src[ b + 0 ] ); 289 | dst.push( src[ b + 1 ] ); 290 | dst.push( src[ b + 2 ] ); 291 | dst.push( src[ c + 0 ] ); 292 | dst.push( src[ c + 1 ] ); 293 | dst.push( src[ c + 2 ] ); 294 | 295 | }, 296 | 297 | addUV: function ( a, b, c ) { 298 | 299 | var src = this.uvs; 300 | var dst = this.object.geometry.uvs; 301 | 302 | dst.push( src[ a + 0 ] ); 303 | dst.push( src[ a + 1 ] ); 304 | dst.push( src[ b + 0 ] ); 305 | dst.push( src[ b + 1 ] ); 306 | dst.push( src[ c + 0 ] ); 307 | dst.push( src[ c + 1 ] ); 308 | 309 | }, 310 | 311 | addUVLine: function ( a ) { 312 | 313 | var src = this.uvs; 314 | var dst = this.object.geometry.uvs; 315 | 316 | dst.push( src[ a + 0 ] ); 317 | dst.push( src[ a + 1 ] ); 318 | 319 | }, 320 | 321 | addFace: function ( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 322 | 323 | var vLen = this.vertices.length; 324 | 325 | var ia = this.parseVertexIndex( a, vLen ); 326 | var ib = this.parseVertexIndex( b, vLen ); 327 | var ic = this.parseVertexIndex( c, vLen ); 328 | var id; 329 | 330 | if ( d === undefined ) { 331 | 332 | this.addVertex( ia, ib, ic ); 333 | 334 | } else { 335 | 336 | id = this.parseVertexIndex( d, vLen ); 337 | 338 | this.addVertex( ia, ib, id ); 339 | this.addVertex( ib, ic, id ); 340 | 341 | } 342 | 343 | if ( ua !== undefined ) { 344 | 345 | var uvLen = this.uvs.length; 346 | 347 | ia = this.parseUVIndex( ua, uvLen ); 348 | ib = this.parseUVIndex( ub, uvLen ); 349 | ic = this.parseUVIndex( uc, uvLen ); 350 | 351 | if ( d === undefined ) { 352 | 353 | this.addUV( ia, ib, ic ); 354 | 355 | } else { 356 | 357 | id = this.parseUVIndex( ud, uvLen ); 358 | 359 | this.addUV( ia, ib, id ); 360 | this.addUV( ib, ic, id ); 361 | 362 | } 363 | 364 | } 365 | 366 | if ( na !== undefined ) { 367 | 368 | // Normals are many times the same. If so, skip function call and parseInt. 369 | var nLen = this.normals.length; 370 | ia = this.parseNormalIndex( na, nLen ); 371 | 372 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 373 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 374 | 375 | if ( d === undefined ) { 376 | 377 | this.addNormal( ia, ib, ic ); 378 | 379 | } else { 380 | 381 | id = this.parseNormalIndex( nd, nLen ); 382 | 383 | this.addNormal( ia, ib, id ); 384 | this.addNormal( ib, ic, id ); 385 | 386 | } 387 | 388 | } 389 | 390 | }, 391 | 392 | addLineGeometry: function ( vertices, uvs ) { 393 | 394 | this.object.geometry.type = 'Line'; 395 | 396 | var vLen = this.vertices.length; 397 | var uvLen = this.uvs.length; 398 | 399 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { 400 | 401 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); 402 | 403 | } 404 | 405 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { 406 | 407 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); 408 | 409 | } 410 | 411 | } 412 | 413 | }; 414 | 415 | state.startObject( '', false ); 416 | 417 | return state; 418 | 419 | }, 420 | 421 | parse: function ( text ) { 422 | 423 | console.time( 'OBJLoader' ); 424 | 425 | var state = this._createParserState(); 426 | 427 | if ( text.indexOf( '\r\n' ) !== - 1 ) { 428 | 429 | // This is faster than String.split with regex that splits on both 430 | text = text.replace( /\r\n/g, '\n' ); 431 | 432 | } 433 | 434 | if ( text.indexOf( '\\\n' ) !== - 1) { 435 | 436 | // join lines separated by a line continuation character (\) 437 | text = text.replace( /\\\n/g, '' ); 438 | 439 | } 440 | 441 | var lines = text.split( '\n' ); 442 | var line = '', lineFirstChar = '', lineSecondChar = ''; 443 | var lineLength = 0; 444 | var result = []; 445 | 446 | // Faster to just trim left side of the line. Use if available. 447 | var trimLeft = ( typeof ''.trimLeft === 'function' ); 448 | 449 | for ( var i = 0, l = lines.length; i < l; i ++ ) { 450 | 451 | line = lines[ i ]; 452 | 453 | line = trimLeft ? line.trimLeft() : line.trim(); 454 | 455 | lineLength = line.length; 456 | 457 | if ( lineLength === 0 ) continue; 458 | 459 | lineFirstChar = line.charAt( 0 ); 460 | 461 | // @todo invoke passed in handler if any 462 | if ( lineFirstChar === '#' ) continue; 463 | 464 | if ( lineFirstChar === 'v' ) { 465 | 466 | lineSecondChar = line.charAt( 1 ); 467 | 468 | if ( lineSecondChar === ' ' && ( result = this.regexp.vertex_pattern.exec( line ) ) !== null ) { 469 | 470 | // 0 1 2 3 471 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 472 | 473 | state.vertices.push( 474 | parseFloat( result[ 1 ] ), 475 | parseFloat( result[ 2 ] ), 476 | parseFloat( result[ 3 ] ) 477 | ); 478 | 479 | } else if ( lineSecondChar === 'n' && ( result = this.regexp.normal_pattern.exec( line ) ) !== null ) { 480 | 481 | // 0 1 2 3 482 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 483 | 484 | state.normals.push( 485 | parseFloat( result[ 1 ] ), 486 | parseFloat( result[ 2 ] ), 487 | parseFloat( result[ 3 ] ) 488 | ); 489 | 490 | } else if ( lineSecondChar === 't' && ( result = this.regexp.uv_pattern.exec( line ) ) !== null ) { 491 | 492 | // 0 1 2 493 | // ["vt 0.1 0.2", "0.1", "0.2"] 494 | 495 | state.uvs.push( 496 | parseFloat( result[ 1 ] ), 497 | parseFloat( result[ 2 ] ) 498 | ); 499 | 500 | } else { 501 | 502 | throw new Error( "Unexpected vertex/normal/uv line: '" + line + "'" ); 503 | 504 | } 505 | 506 | } else if ( lineFirstChar === "f" ) { 507 | 508 | if ( ( result = this.regexp.face_vertex_uv_normal.exec( line ) ) !== null ) { 509 | 510 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal 511 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 512 | // ["f 1/1/1 2/2/2 3/3/3", "1", "1", "1", "2", "2", "2", "3", "3", "3", undefined, undefined, undefined] 513 | 514 | state.addFace( 515 | result[ 1 ], result[ 4 ], result[ 7 ], result[ 10 ], 516 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 517 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 518 | ); 519 | 520 | } else if ( ( result = this.regexp.face_vertex_uv.exec( line ) ) !== null ) { 521 | 522 | // f vertex/uv vertex/uv vertex/uv 523 | // 0 1 2 3 4 5 6 7 8 524 | // ["f 1/1 2/2 3/3", "1", "1", "2", "2", "3", "3", undefined, undefined] 525 | 526 | state.addFace( 527 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 528 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 529 | ); 530 | 531 | } else if ( ( result = this.regexp.face_vertex_normal.exec( line ) ) !== null ) { 532 | 533 | // f vertex//normal vertex//normal vertex//normal 534 | // 0 1 2 3 4 5 6 7 8 535 | // ["f 1//1 2//2 3//3", "1", "1", "2", "2", "3", "3", undefined, undefined] 536 | 537 | state.addFace( 538 | result[ 1 ], result[ 3 ], result[ 5 ], result[ 7 ], 539 | undefined, undefined, undefined, undefined, 540 | result[ 2 ], result[ 4 ], result[ 6 ], result[ 8 ] 541 | ); 542 | 543 | } else if ( ( result = this.regexp.face_vertex.exec( line ) ) !== null ) { 544 | 545 | // f vertex vertex vertex 546 | // 0 1 2 3 4 547 | // ["f 1 2 3", "1", "2", "3", undefined] 548 | 549 | state.addFace( 550 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 551 | ); 552 | 553 | } else { 554 | 555 | throw new Error( "Unexpected face line: '" + line + "'" ); 556 | 557 | } 558 | 559 | } else if ( lineFirstChar === "l" ) { 560 | 561 | var lineParts = line.substring( 1 ).trim().split( " " ); 562 | var lineVertices = [], lineUVs = []; 563 | 564 | if ( line.indexOf( "/" ) === - 1 ) { 565 | 566 | lineVertices = lineParts; 567 | 568 | } else { 569 | 570 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { 571 | 572 | var parts = lineParts[ li ].split( "/" ); 573 | 574 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); 575 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); 576 | 577 | } 578 | 579 | } 580 | state.addLineGeometry( lineVertices, lineUVs ); 581 | 582 | } else if ( ( result = this.regexp.object_pattern.exec( line ) ) !== null ) { 583 | 584 | // o object_name 585 | // or 586 | // g group_name 587 | 588 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 589 | // var name = result[ 0 ].substr( 1 ).trim(); 590 | var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); 591 | 592 | state.startObject( name ); 593 | 594 | } else if ( this.regexp.material_use_pattern.test( line ) ) { 595 | 596 | // material 597 | 598 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); 599 | 600 | } else if ( this.regexp.material_library_pattern.test( line ) ) { 601 | 602 | // mtl file 603 | 604 | state.materialLibraries.push( line.substring( 7 ).trim() ); 605 | 606 | } else if ( ( result = this.regexp.smoothing_pattern.exec( line ) ) !== null ) { 607 | 608 | // smooth shading 609 | 610 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry, 611 | // but does not define a usemtl for each face set. 612 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups). 613 | // This requires some care to not create extra material on each smooth value for "normal" obj files. 614 | // where explicit usemtl defines geometry groups. 615 | // Example asset: examples/models/obj/cerberus/Cerberus.obj 616 | 617 | var value = result[ 1 ].trim().toLowerCase(); 618 | state.object.smooth = ( value === '1' || value === 'on' ); 619 | 620 | var material = state.object.currentMaterial(); 621 | if ( material ) { 622 | 623 | material.smooth = state.object.smooth; 624 | 625 | } 626 | 627 | } else { 628 | 629 | // Handle null terminated files without exception 630 | if ( line === '\0' ) continue; 631 | 632 | throw new Error( "Unexpected line: '" + line + "'" ); 633 | 634 | } 635 | 636 | } 637 | 638 | state.finalize(); 639 | 640 | var container = new THREE.Group(); 641 | container.materialLibraries = [].concat( state.materialLibraries ); 642 | 643 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 644 | 645 | var object = state.objects[ i ]; 646 | var geometry = object.geometry; 647 | var materials = object.materials; 648 | var isLine = ( geometry.type === 'Line' ); 649 | 650 | // Skip o/g line declarations that did not follow with any faces 651 | if ( geometry.vertices.length === 0 ) continue; 652 | 653 | var buffergeometry = new THREE.BufferGeometry(); 654 | 655 | buffergeometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 656 | 657 | if ( geometry.normals.length > 0 ) { 658 | 659 | buffergeometry.setAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 660 | 661 | } else { 662 | 663 | buffergeometry.computeVertexNormals(); 664 | 665 | } 666 | 667 | if ( geometry.uvs.length > 0 ) { 668 | 669 | buffergeometry.setAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 670 | 671 | } 672 | 673 | // Create materials 674 | 675 | var createdMaterials = []; 676 | 677 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 678 | 679 | var sourceMaterial = materials[mi]; 680 | var material = undefined; 681 | 682 | if ( this.materials !== null ) { 683 | 684 | material = this.materials.create( sourceMaterial.name ); 685 | 686 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. 687 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { 688 | 689 | var materialLine = new THREE.LineBasicMaterial(); 690 | materialLine.copy( material ); 691 | material = materialLine; 692 | 693 | } 694 | 695 | } 696 | 697 | if ( ! material ) { 698 | 699 | material = ( ! isLine ? new THREE.MeshPhongMaterial() : new THREE.LineBasicMaterial() ); 700 | material.name = sourceMaterial.name; 701 | 702 | } 703 | 704 | material.flatShading = sourceMaterial.smooth ? THREE.SmoothShading : THREE.FlatShading; 705 | 706 | createdMaterials.push(material); 707 | 708 | } 709 | 710 | // Create mesh 711 | 712 | var mesh; 713 | 714 | if ( createdMaterials.length > 1 ) { 715 | 716 | for ( var mi = 0, miLen = materials.length; mi < miLen ; mi++ ) { 717 | 718 | var sourceMaterial = materials[mi]; 719 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); 720 | 721 | } 722 | 723 | var multiMaterial = new THREE.MultiMaterial( createdMaterials ); 724 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, multiMaterial ) : new THREE.LineSegments( buffergeometry, multiMaterial ) ); 725 | 726 | } else { 727 | 728 | mesh = ( ! isLine ? new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ) : new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ) ); 729 | } 730 | 731 | mesh.name = object.name; 732 | 733 | container.add( mesh ); 734 | 735 | } 736 | 737 | console.timeEnd( 'OBJLoader' ); 738 | 739 | return container; 740 | 741 | } 742 | 743 | }; 744 | -------------------------------------------------------------------------------- /demo/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 | ( 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 | }() ); -------------------------------------------------------------------------------- /demo/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 | }; -------------------------------------------------------------------------------- /demo/js/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}(); 14 | dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}}, 15 | each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;bthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common); 29 | dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input, 30 | "blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b= 31 | this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); 32 | dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background= 33 | document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a= 34 | (this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 35 | dat.controllers.FunctionController=function(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object); 36 | this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 37 | dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 39 | dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); 40 | dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 41 | return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!= 42 | a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& 43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1; 44 | var b=1\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n', 71 | ".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", 72 | dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e= 73 | function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype, 74 | f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, 75 | dat.controllers.ColorController=function(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; 76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window, 77 | "mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1 78 | b&&(b=0);1e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); 79 | this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); 80 | this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style, 81 | {position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%", 82 | background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window, 83 | "mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1; 84 | c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255< 0 ) { 219 | 220 | var mesh = meshes[ id ]; 221 | var geo = mesh.geo; 222 | var g = mesh.g; 223 | 224 | var d = intersects[ 0 ].point.x; 225 | 226 | for( var j = 0; j < geo.length; j+= 3 ) { 227 | geo[ j ] = geo[ j + 3 ] * 1.001; 228 | geo[ j + 1 ] = geo[ j + 4 ] * 1.001; 229 | geo[ j + 2 ] = geo[ j + 5 ] * 1.001; 230 | } 231 | 232 | geo[ geo.length - 3 ] = d * Math.cos( angle ); 233 | geo[ geo.length - 2 ] = intersects[ 0 ].point.y; 234 | geo[ geo.length - 1 ] = d * Math.sin( angle ); 235 | 236 | g.setGeometry( geo ); 237 | 238 | } 239 | 240 | } 241 | 242 | function onWindowResize() { 243 | 244 | var w = container.clientWidth; 245 | var h = container.clientHeight; 246 | 247 | camera.aspect = w / h; 248 | camera.updateProjectionMatrix(); 249 | 250 | renderer.setSize( w, h ); 251 | 252 | resolution.set( w, h ); 253 | 254 | } 255 | 256 | var tmpVector = new THREE.Vector3(); 257 | 258 | function check() { 259 | 260 | for( var i in nMouse ) { checkIntersection( i ); } 261 | setTimeout( check, 20 ); 262 | 263 | } 264 | check(); 265 | 266 | function render() { 267 | 268 | requestAnimationFrame( render ); 269 | 270 | angle += .05; 271 | 272 | for( var i in meshes ) { 273 | var mesh = meshes[ i ]; 274 | mesh.rotation.y = angle; 275 | } 276 | 277 | /*for( var i in meshes ) { 278 | var geo = meshes[ i ].geo; 279 | for( var j = 0; j < geo.length; j+= 3 ) { 280 | geo[ j ] *= 1.01; 281 | geo[ j + 1 ] *= 1.01; 282 | geo[ j + 2 ] *= 1.01; 283 | } 284 | meshes[ i ].g.setGeometry( geo ); 285 | }*/ 286 | 287 | renderer.render( scene, camera ); 288 | 289 | } 290 | -------------------------------------------------------------------------------- /demo/js/main-svg.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var container = document.getElementById( 'container' ); 4 | 5 | var scene = new THREE.Scene(); 6 | var camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 10000 ); 7 | camera.position.set( 500, 0, 0 ); 8 | 9 | var renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true }); 10 | renderer.setSize( window.innerWidth, window.innerHeight ); 11 | renderer.setPixelRatio( window.devicePixelRatio ); 12 | container.appendChild( renderer.domElement ); 13 | 14 | var controls = new THREE.OrbitControls( camera, renderer.domElement ); 15 | var clock = new THREE.Clock(); 16 | 17 | var colors = [ 18 | 0xed6a5a, 19 | 0xf4f1bb, 20 | 0x9bc1bc, 21 | 0x5ca4a9, 22 | 0xe6ebe0, 23 | 0xf0b67f, 24 | 0xfe5f55, 25 | 0xd6d1b1, 26 | 0xc7efcf, 27 | 0xeef5db, 28 | 0x50514f, 29 | 0xf25f5c, 30 | 0xffe066, 31 | 0x247ba0, 32 | 0x70c1b3 33 | ]; 34 | 35 | var resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); 36 | var svg = new THREE.Object3D(); 37 | scene.add( svg ); 38 | 39 | init() 40 | render(); 41 | 42 | var material = new MeshLineMaterial( { 43 | map: null, 44 | useMap: false, 45 | color: new THREE.Color( colors[ 3 ] ), 46 | opacity: 1, 47 | resolution: resolution, 48 | sizeAttenuation: false, 49 | lineWidth: 1, 50 | depthWrite: false, 51 | depthTest: false, 52 | transparent: true 53 | }); 54 | 55 | var loader = new THREE.TextureLoader(); 56 | loader.load('assets/stroke.png', function(texture) { 57 | material.map = texture; 58 | }); 59 | 60 | 61 | function makeLine( geo ) { 62 | 63 | var g = new MeshLine(); 64 | g.setGeometry( geo ); 65 | 66 | var mesh = new THREE.Mesh( g.geometry, material ); 67 | mesh.position.z += 500; 68 | mesh.position.y += 300; 69 | mesh.rotation.y = -Math.PI / 2; 70 | mesh.rotation.z = Math.PI; 71 | scene.add( mesh ); 72 | 73 | return mesh; 74 | 75 | } 76 | 77 | function init() { 78 | 79 | readSVG().then( drawSVG ); 80 | 81 | } 82 | 83 | function readSVG() { 84 | 85 | return new Promise( function( resolve, reject ) { 86 | var ajax = new XMLHttpRequest(); 87 | ajax.open("GET", "assets/worldLow.svg", true); 88 | ajax.send(); 89 | ajax.addEventListener( 'load', function(e) { 90 | resolve( ajax.responseText ); 91 | } ); 92 | }); 93 | 94 | } 95 | 96 | function drawSVG( source ) { 97 | 98 | var lines = []; 99 | var parser = new DOMParser(); 100 | var doc = parser.parseFromString( source, "image/svg+xml"); 101 | 102 | var pathNodes = doc.querySelectorAll('path'); 103 | [].forEach.call( pathNodes, function( p ) { 104 | 105 | if( p instanceof SVGPathElement && p.pathSegList ) { 106 | 107 | var line = new THREE.Geometry(); 108 | var vertices = line.vertices; 109 | var x, y; 110 | var ox, oy; 111 | var px, py; 112 | 113 | var segments = p.pathSegList; 114 | for( var i = 0; i < segments.numberOfItems; i++ ) { 115 | 116 | var segment = segments.getItem( i ); 117 | 118 | var types = [ SVGPathSegMovetoAbs, SVGPathSegLinetoRel, SVGPathSegLinetoVerticalRel, SVGPathSegLinetoHorizontalRel, SVGPathSegLinetoHorizontalAbs, SVGPathSegLinetoVerticalAbs, SVGPathSegClosePath, SVGPathSegLinetoAbs ]; 119 | var found = false; 120 | types.forEach( function( t ) { 121 | if( segment instanceof t ) { 122 | found = true; 123 | } 124 | } ); 125 | if( !found ) { 126 | console.log( segment ); 127 | } 128 | 129 | if( segment instanceof SVGPathSegMovetoAbs ) { 130 | x = segment.x; 131 | y = segment.y; 132 | ox = x; 133 | oy = y; 134 | // add line; 135 | lines.push( line ); 136 | line = new THREE.Geometry(); 137 | line.vertices.push( new THREE.Vector3( x, y, 0 ) ); 138 | } 139 | if( segment instanceof SVGPathSegLinetoRel ) { 140 | x = px + segment.x; 141 | y = py + segment.y; 142 | line.vertices.push( new THREE.Vector3( x, y, 0 ) ); 143 | } 144 | if( segment instanceof SVGPathSegLinetoAbs ) { 145 | x = segment.x; 146 | y = segment.y; 147 | line.vertices.push( new THREE.Vector3( x, y, 0 ) ); 148 | } 149 | if( segment instanceof SVGPathSegLinetoVerticalRel ) { 150 | x = px; 151 | y = py + segment.y; 152 | line.vertices.push( new THREE.Vector3( x, y, 0 ) ); 153 | } 154 | if( segment instanceof SVGPathSegLinetoHorizontalRel ) { 155 | x = px + segment.x; 156 | y = py; 157 | line.vertices.push( new THREE.Vector3( x, y, 0 ) ); 158 | } 159 | if( segment instanceof SVGPathSegLinetoHorizontalAbs ) { 160 | x = segment.x; 161 | y = py; 162 | line.vertices.push( new THREE.Vector3( x, y, 0 ) ); 163 | } 164 | if( segment instanceof SVGPathSegLinetoVerticalAbs ) { 165 | x = px; 166 | y = segment.y; 167 | line.vertices.push( new THREE.Vector3( x, y, 0 ) ); 168 | } 169 | if( segment instanceof SVGPathSegClosePath ) { 170 | x = ox; 171 | y = oy; 172 | line.vertices.push( new THREE.Vector3( x, y, 0 ) ); 173 | // add line 174 | lines.push( line ); 175 | line = new THREE.Geometry(); 176 | } 177 | 178 | px = x; 179 | py = y; 180 | 181 | } 182 | 183 | } 184 | 185 | } ); 186 | 187 | lines.forEach( function( l ) { 188 | makeLine( l ); 189 | }) 190 | 191 | } 192 | 193 | function addLine( line ) { 194 | 195 | console.log( line ); 196 | 197 | } 198 | 199 | onWindowResize(); 200 | 201 | function onWindowResize() { 202 | 203 | var w = container.clientWidth; 204 | var h = container.clientHeight; 205 | 206 | camera.aspect = w / h; 207 | camera.updateProjectionMatrix(); 208 | 209 | renderer.setSize( w, h ); 210 | 211 | resolution.set( w, h ); 212 | 213 | } 214 | 215 | window.addEventListener( 'resize', onWindowResize ); 216 | 217 | function render() { 218 | 219 | requestAnimationFrame( render ); 220 | controls.update(); 221 | 222 | renderer.render( scene, camera ); 223 | 224 | } 225 | -------------------------------------------------------------------------------- /demo/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var container = document.getElementById( 'container' ); 4 | 5 | var scene = new THREE.Scene(); 6 | var camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, .1, 1000 ); 7 | camera.position.set( 0, 0, -10 ); 8 | 9 | var renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true }); 10 | renderer.setSize( window.innerWidth, window.innerHeight ); 11 | renderer.setPixelRatio( window.devicePixelRatio ); 12 | container.appendChild( renderer.domElement ); 13 | 14 | var controls = new THREE.OrbitControls( camera, renderer.domElement ); 15 | var clock = new THREE.Clock(); 16 | 17 | var lines = []; 18 | var resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); 19 | var strokeTexture; 20 | 21 | var Params = function() { 22 | this.curves = true; 23 | this.circles = false; 24 | this.amount = 100; 25 | this.lineWidth = 10; 26 | this.dashArray = 0.6; 27 | this.dashOffset = 0; 28 | this.dashRatio = 0.5; 29 | this.taper = 'parabolic'; 30 | this.strokes = false; 31 | this.sizeAttenuation = false; 32 | this.animateWidth = false; 33 | this.spread = false; 34 | this.autoRotate = true; 35 | this.autoUpdate = true; 36 | this.animateVisibility = false; 37 | this.animateDashOffset = false; 38 | this.update = function() { 39 | clearLines(); 40 | createLines(); 41 | } 42 | }; 43 | 44 | var params = new Params(); 45 | var gui = new dat.GUI(); 46 | 47 | window.addEventListener( 'load', function() { 48 | 49 | function update() { 50 | if( params.autoUpdate ) { 51 | clearLines(); 52 | createLines(); 53 | } 54 | } 55 | 56 | gui.add( params, 'curves' ).onChange( update ); 57 | gui.add( params, 'circles' ).onChange( update ); 58 | gui.add( params, 'amount', 1, 1000 ).onChange( update ); 59 | gui.add( params, 'lineWidth', 1, 20 ).onChange( update ); 60 | gui.add( params, 'dashArray', 0, 3 ).onChange( update ); 61 | gui.add( params, 'dashRatio', 0, 1 ).onChange( update ); 62 | gui.add( params, 'taper', [ 'none', 'linear', 'parabolic', 'wavy' ] ).onChange( update ); 63 | gui.add( params, 'strokes' ).onChange( update ); 64 | gui.add( params, 'sizeAttenuation' ).onChange( update ); 65 | gui.add( params, 'autoUpdate' ).onChange( update ); 66 | gui.add( params, 'update' ); 67 | gui.add( params, 'animateWidth' ); 68 | gui.add( params, 'spread' ); 69 | gui.add( params, 'autoRotate' ); 70 | gui.add( params, 'animateVisibility' ); 71 | gui.add( params, 'animateDashOffset' ); 72 | 73 | var loader = new THREE.TextureLoader(); 74 | loader.load( 'assets/stroke.png', function( texture ) { 75 | strokeTexture = texture; 76 | init() 77 | } ); 78 | 79 | } ); 80 | 81 | var TAU = 2 * Math.PI; 82 | var hexagonGeometry = new THREE.Geometry(); 83 | for( var j = 0; j < TAU - .1; j += TAU / 100 ) { 84 | var v = new THREE.Vector3(); 85 | v.set( Math.cos( j ), Math.sin( j ), 0 ); 86 | hexagonGeometry.vertices.push( v ); 87 | } 88 | hexagonGeometry.vertices.push( hexagonGeometry.vertices[ 0 ].clone() ); 89 | 90 | function createCurve() { 91 | 92 | var s = new THREE.ConstantSpline(); 93 | var rMin = 5; 94 | var rMax = 10; 95 | var origin = new THREE.Vector3( Maf.randomInRange( -rMin, rMin ), Maf.randomInRange( -rMin, rMin ), Maf.randomInRange( -rMin, rMin ) ); 96 | 97 | s.inc = .001; 98 | s.p0 = new THREE.Vector3( .5 - Math.random(), .5 - Math.random(), .5 - Math.random() ); 99 | s.p0.set( 0, 0, 0 ); 100 | s.p1 = s.p0.clone().add( new THREE.Vector3( .5 - Math.random(), .5 - Math.random(), .5 - Math.random() ) ); 101 | s.p2 = s.p1.clone().add( new THREE.Vector3( .5 - Math.random(), .5 - Math.random(), .5 - Math.random() ) ); 102 | s.p3 = s.p2.clone().add( new THREE.Vector3( .5 - Math.random(), .5 - Math.random(), .5 - Math.random() ) ); 103 | s.p0.multiplyScalar( rMin + Math.random() * rMax ); 104 | s.p1.multiplyScalar( rMin + Math.random() * rMax ); 105 | s.p2.multiplyScalar( rMin + Math.random() * rMax ); 106 | s.p3.multiplyScalar( rMin + Math.random() * rMax ); 107 | 108 | s.calculate(); 109 | var geometry = new THREE.Geometry(); 110 | s.calculateDistances(); 111 | //s.reticulate( { distancePerStep: .1 }); 112 | s.reticulate( { steps: 500 } ); 113 | var geometry = new THREE.Geometry(); 114 | 115 | for( var j = 0; j < s.lPoints.length - 1; j++ ) { 116 | geometry.vertices.push( s.lPoints[ j ].clone() ); 117 | } 118 | 119 | return geometry; 120 | 121 | } 122 | 123 | var colors = [ 124 | 0xed6a5a, 125 | 0xf4f1bb, 126 | 0x9bc1bc, 127 | 0x5ca4a9, 128 | 0xe6ebe0, 129 | 0xf0b67f, 130 | 0xfe5f55, 131 | 0xd6d1b1, 132 | 0xc7efcf, 133 | 0xeef5db, 134 | 0x50514f, 135 | 0xf25f5c, 136 | 0xffe066, 137 | 0x247ba0, 138 | 0x70c1b3 139 | ]; 140 | 141 | function clearLines() { 142 | 143 | lines.forEach( function( l ) { 144 | scene.remove( l ); 145 | } ); 146 | lines = []; 147 | 148 | } 149 | 150 | function makeLine( geo ) { 151 | 152 | var g = new MeshLine(); 153 | 154 | switch( params.taper ) { 155 | case 'none': g.setGeometry( geo ); break; 156 | case 'linear': g.setGeometry( geo, function( p ) { return 1 - p; } ); break; 157 | case 'parabolic': g.setGeometry( geo, function( p ) { return 1 * Maf.parabola( p, 1 )} ); break; 158 | case 'wavy': g.setGeometry( geo, function( p ) { return 2 + Math.sin( 50 * p ) } ); break; 159 | } 160 | 161 | var material = new MeshLineMaterial( { 162 | map: strokeTexture, 163 | useMap: params.strokes, 164 | color: new THREE.Color( colors[ ~~Maf.randomInRange( 0, colors.length ) ] ), 165 | opacity: 1,//params.strokes ? .5 : 1, 166 | dashArray: params.dashArray, 167 | dashOffset: params.dashOffset, 168 | dashRatio: params.dashRatio, 169 | resolution: resolution, 170 | sizeAttenuation: params.sizeAttenuation, 171 | lineWidth: params.lineWidth, 172 | depthWrite: false, 173 | depthTest: !params.strokes, 174 | alphaTest: params.strokes ? .5 : 0, 175 | transparent: true, 176 | side: THREE.DoubleSide 177 | }); 178 | var mesh = new THREE.Mesh( g.geometry, material ); 179 | if( params.spread || params.circles ) { 180 | var r = 50; 181 | mesh.position.set( Maf.randomInRange( -r, r ), Maf.randomInRange( -r, r ), Maf.randomInRange( -r, r ) ); 182 | var s = 10 + 10 * Math.random(); 183 | mesh.scale.set( s,s,s ); 184 | mesh.rotation.set( Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI ); 185 | } 186 | scene.add( mesh ); 187 | 188 | lines.push( mesh ); 189 | 190 | } 191 | 192 | function init() { 193 | 194 | createLines(); 195 | onWindowResize(); 196 | render(); 197 | 198 | } 199 | 200 | function createLine() { 201 | if( params.circles ) makeLine( hexagonGeometry ); 202 | if( params.curves ) makeLine( createCurve() ); 203 | //makeLine( makeVerticalLine() ); 204 | //makeLine( makeSquare() ); 205 | } 206 | 207 | function createLines() { 208 | for( var j = 0; j < params.amount; j++ ) { 209 | createLine(); 210 | } 211 | } 212 | 213 | function makeVerticalLine() { 214 | var g = new THREE.Geometry() 215 | var x = ( .5 - Math.random() ) * 100; 216 | g.vertices.push( new THREE.Vector3( x, -10, 0 ) ); 217 | g.vertices.push( new THREE.Vector3( x, 10, 0 ) ); 218 | return g; 219 | } 220 | 221 | function makeSquare() { 222 | var g = new THREE.Geometry() 223 | var x = ( .5 - Math.random() ) * 100; 224 | g.vertices.push( new THREE.Vector3( -1, -1, 0 ) ); 225 | g.vertices.push( new THREE.Vector3( 1, -1, 0 ) ); 226 | g.vertices.push( new THREE.Vector3( 1, 1, 0 ) ); 227 | g.vertices.push( new THREE.Vector3( -1, 1, 0 ) ); 228 | g.vertices.push( new THREE.Vector3( -1, -1, 0 ) ); 229 | return g; 230 | } 231 | 232 | function onWindowResize() { 233 | 234 | var w = container.clientWidth; 235 | var h = container.clientHeight; 236 | 237 | camera.aspect = w / h; 238 | camera.updateProjectionMatrix(); 239 | 240 | renderer.setSize( w, h ); 241 | 242 | resolution.set( w, h ); 243 | 244 | } 245 | 246 | window.addEventListener( 'resize', onWindowResize ); 247 | 248 | var tmpVector = new THREE.Vector3(); 249 | 250 | function render(time) { 251 | 252 | requestAnimationFrame( render ); 253 | controls.update(); 254 | 255 | var delta = clock.getDelta(); 256 | var t = clock.getElapsedTime(); 257 | lines.forEach( function( l, i ) { 258 | if( params.animateWidth ) l.material.uniforms.lineWidth.value = params.lineWidth * ( 1 + .5 * Math.sin( 5 * t + i ) ); 259 | if( params.autoRotate ) l.rotation.y += .125 * delta; 260 | l.material.uniforms.visibility.value= params.animateVisibility ? (time/3000) % 1.0 : 1.0; 261 | l.material.uniforms.dashOffset.value -= params.animateDashOffset ? 0.01 : 0; 262 | } ); 263 | 264 | renderer.render( scene, camera ); 265 | 266 | } 267 | -------------------------------------------------------------------------------- /demo/shape.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | THREE.MeshLine - Shape example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
    17 |

    THREE.MeshLine - Shape example

    18 |

    Loading and generating... This might take a while

    19 |
    20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /demo/spinner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spinner demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |

    Touch and drag to draw lines.
    Try multiple fingers on mobile!

    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /demo/svg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | THREE.MeshLine - SVG example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
    17 |

    THREE.MeshLine - SVG example

    18 |

    Lines with sizeAttenuation disabled

    19 |
    20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three.meshline", 3 | "version": "1.3.0", 4 | "description": "Mesh replacement for THREE.Line", 5 | "main": "src/THREE.MeshLine.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/spite/THREE.MeshLine.git" 12 | }, 13 | "keywords": [ 14 | "lines", 15 | "threejs", 16 | "mesh", 17 | "line" 18 | ], 19 | "files": [ 20 | "LICENSE", 21 | "History.md", 22 | "README.md", 23 | "src/THREE.MeshLine.js" 24 | ], 25 | "author": "Jaume Sanchez (https://www.clicktorelease.com)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/spite/THREE.MeshLine/issues" 29 | }, 30 | "homepage": "https://github.com/spite/THREE.MeshLine#readme" 31 | } -------------------------------------------------------------------------------- /screenshots/birds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/THREE.MeshLine/b49ae600898a88bdb3486a3674d5887ca1b61ff6/screenshots/birds.jpg -------------------------------------------------------------------------------- /screenshots/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/THREE.MeshLine/b49ae600898a88bdb3486a3674d5887ca1b61ff6/screenshots/demo.jpg -------------------------------------------------------------------------------- /screenshots/graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/THREE.MeshLine/b49ae600898a88bdb3486a3674d5887ca1b61ff6/screenshots/graph.jpg -------------------------------------------------------------------------------- /screenshots/shape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/THREE.MeshLine/b49ae600898a88bdb3486a3674d5887ca1b61ff6/screenshots/shape.jpg -------------------------------------------------------------------------------- /screenshots/spinner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/THREE.MeshLine/b49ae600898a88bdb3486a3674d5887ca1b61ff6/screenshots/spinner.jpg -------------------------------------------------------------------------------- /screenshots/svg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spite/THREE.MeshLine/b49ae600898a88bdb3486a3674d5887ca1b61ff6/screenshots/svg.jpg -------------------------------------------------------------------------------- /src/THREE.MeshLine.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | 'use strict' 3 | 4 | var root = this 5 | 6 | var has_require = typeof require !== 'undefined' 7 | 8 | var THREE = root.THREE || (has_require && require('three')) 9 | if (!THREE) throw new Error('MeshLine requires three.js') 10 | 11 | class MeshLine extends THREE.BufferGeometry { 12 | constructor() 13 | { 14 | super(); 15 | this.isMeshLine = true; 16 | this.type = 'MeshLine' 17 | 18 | this.positions = [] 19 | 20 | this.previous = [] 21 | this.next = [] 22 | this.side = [] 23 | this.width = [] 24 | this.indices_array = [] 25 | this.uvs = [] 26 | this.counters = [] 27 | this._points = [] 28 | this._geom = null 29 | 30 | this.widthCallback = null 31 | 32 | // Used to raycast 33 | this.matrixWorld = new THREE.Matrix4() 34 | 35 | Object.defineProperties(this, { 36 | // this is now a bufferGeometry 37 | // add getter to support previous api 38 | geometry: { 39 | enumerable: true, 40 | get: function() { 41 | return this 42 | }, 43 | }, 44 | geom: { 45 | enumerable: true, 46 | get: function() { 47 | return this._geom 48 | }, 49 | set: function(value) { 50 | this.setGeometry(value, this.widthCallback) 51 | }, 52 | }, 53 | // for declaritive architectures 54 | // to return the same value that sets the points 55 | // eg. this.points = points 56 | // console.log(this.points) -> points 57 | points: { 58 | enumerable: true, 59 | get: function() { 60 | return this._points 61 | }, 62 | set: function(value) { 63 | this.setPoints(value, this.widthCallback) 64 | }, 65 | }, 66 | }) 67 | } 68 | } 69 | 70 | MeshLine.prototype.setMatrixWorld = function(matrixWorld) { 71 | this.matrixWorld = matrixWorld 72 | } 73 | 74 | // setting via a geometry is rather superfluous 75 | // as you're creating a unecessary geometry just to throw away 76 | // but exists to support previous api 77 | MeshLine.prototype.setGeometry = function(g, c) { 78 | // as the input geometry are mutated we store them 79 | // for later retreival when necessary (declaritive architectures) 80 | this._geometry = g; 81 | this.setPoints(g.getAttribute("position").array, c); 82 | } 83 | 84 | MeshLine.prototype.setPoints = function(points, wcb) { 85 | if (!(points instanceof Float32Array) && !(points instanceof Array)) { 86 | console.error( 87 | "ERROR: The BufferArray of points is not instancied correctly." 88 | ); 89 | return; 90 | } 91 | // as the points are mutated we store them 92 | // for later retreival when necessary (declaritive architectures) 93 | this._points = points; 94 | this.widthCallback = wcb; 95 | this.positions = []; 96 | this.counters = []; 97 | if (points.length && points[0] instanceof THREE.Vector3) { 98 | // could transform Vector3 array into the array used below 99 | // but this approach will only loop through the array once 100 | // and is more performant 101 | for (var j = 0; j < points.length; j++) { 102 | var p = points[j]; 103 | var c = j / points.length; 104 | this.positions.push(p.x, p.y, p.z); 105 | this.positions.push(p.x, p.y, p.z); 106 | this.counters.push(c); 107 | this.counters.push(c); 108 | } 109 | } else { 110 | for (var j = 0; j < points.length; j += 3) { 111 | var c = j / points.length; 112 | this.positions.push(points[j], points[j + 1], points[j + 2]); 113 | this.positions.push(points[j], points[j + 1], points[j + 2]); 114 | this.counters.push(c); 115 | this.counters.push(c); 116 | } 117 | } 118 | this.process(); 119 | } 120 | 121 | function MeshLineRaycast(raycaster, intersects) { 122 | var inverseMatrix = new THREE.Matrix4() 123 | var ray = new THREE.Ray() 124 | var sphere = new THREE.Sphere() 125 | var interRay = new THREE.Vector3() 126 | var geometry = this.geometry 127 | // Checking boundingSphere distance to ray 128 | 129 | if (!geometry.boundingSphere) geometry.computeBoundingSphere() 130 | sphere.copy(geometry.boundingSphere) 131 | sphere.applyMatrix4(this.matrixWorld) 132 | 133 | if (raycaster.ray.intersectSphere(sphere, interRay) === false) { 134 | return 135 | } 136 | 137 | inverseMatrix.copy( this.matrixWorld ).invert(); 138 | ray.copy(raycaster.ray).applyMatrix4(inverseMatrix) 139 | 140 | var vStart = new THREE.Vector3() 141 | var vEnd = new THREE.Vector3() 142 | var interSegment = new THREE.Vector3() 143 | var step = this instanceof THREE.LineSegments ? 2 : 1 144 | var index = geometry.index 145 | var attributes = geometry.attributes 146 | 147 | if (index !== null) { 148 | var indices = index.array 149 | var positions = attributes.position.array 150 | var widths = attributes.width.array 151 | 152 | for (var i = 0, l = indices.length - 1; i < l; i += step) { 153 | var a = indices[i] 154 | var b = indices[i + 1] 155 | 156 | vStart.fromArray(positions, a * 3) 157 | vEnd.fromArray(positions, b * 3) 158 | var width = widths[Math.floor(i / 3)] !== undefined ? widths[Math.floor(i / 3)] : 1 159 | var precision = raycaster.params.Line.threshold + (this.material.lineWidth * width) / 2 160 | var precisionSq = precision * precision 161 | 162 | var distSq = ray.distanceSqToSegment(vStart, vEnd, interRay, interSegment) 163 | 164 | if (distSq > precisionSq) continue 165 | 166 | interRay.applyMatrix4(this.matrixWorld) //Move back to world space for distance calculation 167 | 168 | var distance = raycaster.ray.origin.distanceTo(interRay) 169 | 170 | if (distance < raycaster.near || distance > raycaster.far) continue 171 | 172 | intersects.push({ 173 | distance: distance, 174 | // What do we want? intersection point on the ray or on the segment?? 175 | // point: raycaster.ray.at( distance ), 176 | point: interSegment.clone().applyMatrix4(this.matrixWorld), 177 | index: i, 178 | face: null, 179 | faceIndex: null, 180 | object: this, 181 | }) 182 | // make event only fire once 183 | i = l 184 | } 185 | } 186 | } 187 | MeshLine.prototype.raycast = MeshLineRaycast 188 | MeshLine.prototype.compareV3 = function(a, b) { 189 | var aa = a * 6 190 | var ab = b * 6 191 | return ( 192 | this.positions[aa] === this.positions[ab] && 193 | this.positions[aa + 1] === this.positions[ab + 1] && 194 | this.positions[aa + 2] === this.positions[ab + 2] 195 | ) 196 | } 197 | 198 | MeshLine.prototype.copyV3 = function(a) { 199 | var aa = a * 6 200 | return [this.positions[aa], this.positions[aa + 1], this.positions[aa + 2]] 201 | } 202 | 203 | MeshLine.prototype.process = function() { 204 | var l = this.positions.length / 6 205 | 206 | this.previous = [] 207 | this.next = [] 208 | this.side = [] 209 | this.width = [] 210 | this.indices_array = [] 211 | this.uvs = [] 212 | 213 | var w 214 | 215 | var v 216 | // initial previous points 217 | if (this.compareV3(0, l - 1)) { 218 | v = this.copyV3(l - 2) 219 | } else { 220 | v = this.copyV3(0) 221 | } 222 | this.previous.push(v[0], v[1], v[2]) 223 | this.previous.push(v[0], v[1], v[2]) 224 | 225 | for (var j = 0; j < l; j++) { 226 | // sides 227 | this.side.push(1) 228 | this.side.push(-1) 229 | 230 | // widths 231 | if (this.widthCallback) w = this.widthCallback(j / (l - 1)) 232 | else w = 1 233 | this.width.push(w) 234 | this.width.push(w) 235 | 236 | // uvs 237 | this.uvs.push(j / (l - 1), 0) 238 | this.uvs.push(j / (l - 1), 1) 239 | 240 | if (j < l - 1) { 241 | // points previous to poisitions 242 | v = this.copyV3(j) 243 | this.previous.push(v[0], v[1], v[2]) 244 | this.previous.push(v[0], v[1], v[2]) 245 | 246 | // indices 247 | var n = j * 2 248 | this.indices_array.push(n, n + 1, n + 2) 249 | this.indices_array.push(n + 2, n + 1, n + 3) 250 | } 251 | if (j > 0) { 252 | // points after poisitions 253 | v = this.copyV3(j) 254 | this.next.push(v[0], v[1], v[2]) 255 | this.next.push(v[0], v[1], v[2]) 256 | } 257 | } 258 | 259 | // last next point 260 | if (this.compareV3(l - 1, 0)) { 261 | v = this.copyV3(1) 262 | } else { 263 | v = this.copyV3(l - 1) 264 | } 265 | this.next.push(v[0], v[1], v[2]) 266 | this.next.push(v[0], v[1], v[2]) 267 | 268 | // redefining the attribute seems to prevent range errors 269 | // if the user sets a differing number of vertices 270 | if (!this._attributes || this._attributes.position.count !== this.positions.length) { 271 | this._attributes = { 272 | position: new THREE.BufferAttribute(new Float32Array(this.positions), 3), 273 | previous: new THREE.BufferAttribute(new Float32Array(this.previous), 3), 274 | next: new THREE.BufferAttribute(new Float32Array(this.next), 3), 275 | side: new THREE.BufferAttribute(new Float32Array(this.side), 1), 276 | width: new THREE.BufferAttribute(new Float32Array(this.width), 1), 277 | uv: new THREE.BufferAttribute(new Float32Array(this.uvs), 2), 278 | index: new THREE.BufferAttribute(new Uint16Array(this.indices_array), 1), 279 | counters: new THREE.BufferAttribute(new Float32Array(this.counters), 1), 280 | } 281 | } else { 282 | this._attributes.position.copyArray(new Float32Array(this.positions)) 283 | this._attributes.position.needsUpdate = true 284 | this._attributes.previous.copyArray(new Float32Array(this.previous)) 285 | this._attributes.previous.needsUpdate = true 286 | this._attributes.next.copyArray(new Float32Array(this.next)) 287 | this._attributes.next.needsUpdate = true 288 | this._attributes.side.copyArray(new Float32Array(this.side)) 289 | this._attributes.side.needsUpdate = true 290 | this._attributes.width.copyArray(new Float32Array(this.width)) 291 | this._attributes.width.needsUpdate = true 292 | this._attributes.uv.copyArray(new Float32Array(this.uvs)) 293 | this._attributes.uv.needsUpdate = true 294 | this._attributes.index.copyArray(new Uint16Array(this.indices_array)) 295 | this._attributes.index.needsUpdate = true 296 | } 297 | 298 | this.setAttribute('position', this._attributes.position) 299 | this.setAttribute('previous', this._attributes.previous) 300 | this.setAttribute('next', this._attributes.next) 301 | this.setAttribute('side', this._attributes.side) 302 | this.setAttribute('width', this._attributes.width) 303 | this.setAttribute('uv', this._attributes.uv) 304 | this.setAttribute('counters', this._attributes.counters) 305 | 306 | this.setIndex(this._attributes.index) 307 | 308 | this.computeBoundingSphere() 309 | this.computeBoundingBox() 310 | } 311 | 312 | function memcpy(src, srcOffset, dst, dstOffset, length) { 313 | var i 314 | 315 | src = src.subarray || src.slice ? src : src.buffer 316 | dst = dst.subarray || dst.slice ? dst : dst.buffer 317 | 318 | src = srcOffset 319 | ? src.subarray 320 | ? src.subarray(srcOffset, length && srcOffset + length) 321 | : src.slice(srcOffset, length && srcOffset + length) 322 | : src 323 | 324 | if (dst.set) { 325 | dst.set(src, dstOffset) 326 | } else { 327 | for (i = 0; i < src.length; i++) { 328 | dst[i + dstOffset] = src[i] 329 | } 330 | } 331 | 332 | return dst 333 | } 334 | 335 | /** 336 | * Fast method to advance the line by one position. The oldest position is removed. 337 | * @param position 338 | */ 339 | MeshLine.prototype.advance = function(position) { 340 | var positions = this._attributes.position.array 341 | var previous = this._attributes.previous.array 342 | var next = this._attributes.next.array 343 | var l = positions.length 344 | 345 | // PREVIOUS 346 | memcpy(positions, 0, previous, 0, l) 347 | 348 | // POSITIONS 349 | memcpy(positions, 6, positions, 0, l - 6) 350 | 351 | positions[l - 6] = position.x 352 | positions[l - 5] = position.y 353 | positions[l - 4] = position.z 354 | positions[l - 3] = position.x 355 | positions[l - 2] = position.y 356 | positions[l - 1] = position.z 357 | 358 | // NEXT 359 | memcpy(positions, 6, next, 0, l - 6) 360 | 361 | next[l - 6] = position.x 362 | next[l - 5] = position.y 363 | next[l - 4] = position.z 364 | next[l - 3] = position.x 365 | next[l - 2] = position.y 366 | next[l - 1] = position.z 367 | 368 | this._attributes.position.needsUpdate = true 369 | this._attributes.previous.needsUpdate = true 370 | this._attributes.next.needsUpdate = true 371 | } 372 | 373 | THREE.ShaderChunk['meshline_vert'] = [ 374 | '', 375 | THREE.ShaderChunk.logdepthbuf_pars_vertex, 376 | THREE.ShaderChunk.fog_pars_vertex, 377 | '', 378 | 'attribute vec3 previous;', 379 | 'attribute vec3 next;', 380 | 'attribute float side;', 381 | 'attribute float width;', 382 | 'attribute float counters;', 383 | '', 384 | 'uniform vec2 resolution;', 385 | 'uniform float lineWidth;', 386 | 'uniform vec3 color;', 387 | 'uniform float opacity;', 388 | 'uniform float sizeAttenuation;', 389 | '', 390 | 'varying vec2 vUV;', 391 | 'varying vec4 vColor;', 392 | 'varying float vCounters;', 393 | '', 394 | 'vec2 fix( vec4 i, float aspect ) {', 395 | '', 396 | ' vec2 res = i.xy / i.w;', 397 | ' res.x *= aspect;', 398 | ' vCounters = counters;', 399 | ' return res;', 400 | '', 401 | '}', 402 | '', 403 | 'void main() {', 404 | '', 405 | ' float aspect = resolution.x / resolution.y;', 406 | '', 407 | ' vColor = vec4( color, opacity );', 408 | ' vUV = uv;', 409 | '', 410 | ' mat4 m = projectionMatrix * modelViewMatrix;', 411 | ' vec4 finalPosition = m * vec4( position, 1.0 );', 412 | ' vec4 prevPos = m * vec4( previous, 1.0 );', 413 | ' vec4 nextPos = m * vec4( next, 1.0 );', 414 | '', 415 | ' vec2 currentP = fix( finalPosition, aspect );', 416 | ' vec2 prevP = fix( prevPos, aspect );', 417 | ' vec2 nextP = fix( nextPos, aspect );', 418 | '', 419 | ' float w = lineWidth * width;', 420 | '', 421 | ' vec2 dir;', 422 | ' if( nextP == currentP ) dir = normalize( currentP - prevP );', 423 | ' else if( prevP == currentP ) dir = normalize( nextP - currentP );', 424 | ' else {', 425 | ' vec2 dir1 = normalize( currentP - prevP );', 426 | ' vec2 dir2 = normalize( nextP - currentP );', 427 | ' dir = normalize( dir1 + dir2 );', 428 | '', 429 | ' vec2 perp = vec2( -dir1.y, dir1.x );', 430 | ' vec2 miter = vec2( -dir.y, dir.x );', 431 | ' //w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );', 432 | '', 433 | ' }', 434 | '', 435 | ' //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;', 436 | ' vec4 normal = vec4( -dir.y, dir.x, 0., 1. );', 437 | ' normal.xy *= .5 * w;', 438 | ' normal *= projectionMatrix;', 439 | ' if( sizeAttenuation == 0. ) {', 440 | ' normal.xy *= finalPosition.w;', 441 | ' normal.xy /= ( vec4( resolution, 0., 1. ) * projectionMatrix ).xy;', 442 | ' }', 443 | '', 444 | ' finalPosition.xy += normal.xy * side;', 445 | '', 446 | ' gl_Position = finalPosition;', 447 | '', 448 | THREE.ShaderChunk.logdepthbuf_vertex, 449 | THREE.ShaderChunk.fog_vertex && ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 450 | THREE.ShaderChunk.fog_vertex, 451 | '}', 452 | ].join('\n') 453 | 454 | THREE.ShaderChunk['meshline_frag'] = [ 455 | '', 456 | THREE.ShaderChunk.fog_pars_fragment, 457 | THREE.ShaderChunk.logdepthbuf_pars_fragment, 458 | '', 459 | 'uniform sampler2D map;', 460 | 'uniform sampler2D alphaMap;', 461 | 'uniform float useMap;', 462 | 'uniform float useAlphaMap;', 463 | 'uniform float useDash;', 464 | 'uniform float dashArray;', 465 | 'uniform float dashOffset;', 466 | 'uniform float dashRatio;', 467 | 'uniform float visibility;', 468 | 'uniform float alphaTest;', 469 | 'uniform vec2 repeat;', 470 | '', 471 | 'varying vec2 vUV;', 472 | 'varying vec4 vColor;', 473 | 'varying float vCounters;', 474 | '', 475 | 'void main() {', 476 | '', 477 | THREE.ShaderChunk.logdepthbuf_fragment, 478 | '', 479 | ' vec4 c = vColor;', 480 | ' if( useMap == 1. ) c *= texture2D( map, vUV * repeat );', 481 | ' if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUV * repeat ).a;', 482 | ' if( c.a < alphaTest ) discard;', 483 | ' if( useDash == 1. ){', 484 | ' c.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio));', 485 | ' }', 486 | ' gl_FragColor = c;', 487 | ' gl_FragColor.a *= step(vCounters, visibility);', 488 | '', 489 | THREE.ShaderChunk.fog_fragment, 490 | '}', 491 | ].join('\n') 492 | 493 | class MeshLineMaterial extends THREE.ShaderMaterial { 494 | constructor(parameters) 495 | { 496 | super({ 497 | uniforms: Object.assign({}, THREE.UniformsLib.fog, { 498 | lineWidth: { value: 1 }, 499 | map: { value: null }, 500 | useMap: { value: 0 }, 501 | alphaMap: { value: null }, 502 | useAlphaMap: { value: 0 }, 503 | color: { value: new THREE.Color(0xffffff) }, 504 | opacity: { value: 1 }, 505 | resolution: { value: new THREE.Vector2(1, 1) }, 506 | sizeAttenuation: { value: 1 }, 507 | dashArray: { value: 0 }, 508 | dashOffset: { value: 0 }, 509 | dashRatio: { value: 0.5 }, 510 | useDash: { value: 0 }, 511 | visibility: { value: 1 }, 512 | alphaTest: { value: 0 }, 513 | repeat: { value: new THREE.Vector2(1, 1) }, 514 | }), 515 | 516 | vertexShader: THREE.ShaderChunk.meshline_vert, 517 | 518 | fragmentShader: THREE.ShaderChunk.meshline_frag, 519 | }); 520 | this.isMeshLineMaterial = true 521 | this.type = 'MeshLineMaterial' 522 | 523 | Object.defineProperties(this, { 524 | lineWidth: { 525 | enumerable: true, 526 | get: function() { 527 | return this.uniforms.lineWidth.value 528 | }, 529 | set: function(value) { 530 | this.uniforms.lineWidth.value = value 531 | }, 532 | }, 533 | map: { 534 | enumerable: true, 535 | get: function() { 536 | return this.uniforms.map.value 537 | }, 538 | set: function(value) { 539 | this.uniforms.map.value = value 540 | }, 541 | }, 542 | useMap: { 543 | enumerable: true, 544 | get: function() { 545 | return this.uniforms.useMap.value 546 | }, 547 | set: function(value) { 548 | this.uniforms.useMap.value = value 549 | }, 550 | }, 551 | alphaMap: { 552 | enumerable: true, 553 | get: function() { 554 | return this.uniforms.alphaMap.value 555 | }, 556 | set: function(value) { 557 | this.uniforms.alphaMap.value = value 558 | }, 559 | }, 560 | useAlphaMap: { 561 | enumerable: true, 562 | get: function() { 563 | return this.uniforms.useAlphaMap.value 564 | }, 565 | set: function(value) { 566 | this.uniforms.useAlphaMap.value = value 567 | }, 568 | }, 569 | color: { 570 | enumerable: true, 571 | get: function() { 572 | return this.uniforms.color.value 573 | }, 574 | set: function(value) { 575 | this.uniforms.color.value = value 576 | }, 577 | }, 578 | opacity: { 579 | enumerable: true, 580 | get: function() { 581 | return this.uniforms.opacity.value 582 | }, 583 | set: function(value) { 584 | this.uniforms.opacity.value = value 585 | }, 586 | }, 587 | resolution: { 588 | enumerable: true, 589 | get: function() { 590 | return this.uniforms.resolution.value 591 | }, 592 | set: function(value) { 593 | this.uniforms.resolution.value.copy(value) 594 | }, 595 | }, 596 | sizeAttenuation: { 597 | enumerable: true, 598 | get: function() { 599 | return this.uniforms.sizeAttenuation.value 600 | }, 601 | set: function(value) { 602 | this.uniforms.sizeAttenuation.value = value 603 | }, 604 | }, 605 | dashArray: { 606 | enumerable: true, 607 | get: function() { 608 | return this.uniforms.dashArray.value 609 | }, 610 | set: function(value) { 611 | this.uniforms.dashArray.value = value 612 | this.useDash = value !== 0 ? 1 : 0 613 | }, 614 | }, 615 | dashOffset: { 616 | enumerable: true, 617 | get: function() { 618 | return this.uniforms.dashOffset.value 619 | }, 620 | set: function(value) { 621 | this.uniforms.dashOffset.value = value 622 | }, 623 | }, 624 | dashRatio: { 625 | enumerable: true, 626 | get: function() { 627 | return this.uniforms.dashRatio.value 628 | }, 629 | set: function(value) { 630 | this.uniforms.dashRatio.value = value 631 | }, 632 | }, 633 | useDash: { 634 | enumerable: true, 635 | get: function() { 636 | return this.uniforms.useDash.value 637 | }, 638 | set: function(value) { 639 | this.uniforms.useDash.value = value 640 | }, 641 | }, 642 | visibility: { 643 | enumerable: true, 644 | get: function() { 645 | return this.uniforms.visibility.value 646 | }, 647 | set: function(value) { 648 | this.uniforms.visibility.value = value 649 | }, 650 | }, 651 | alphaTest: { 652 | enumerable: true, 653 | get: function() { 654 | return this.uniforms.alphaTest.value 655 | }, 656 | set: function(value) { 657 | this.uniforms.alphaTest.value = value 658 | }, 659 | }, 660 | repeat: { 661 | enumerable: true, 662 | get: function() { 663 | return this.uniforms.repeat.value 664 | }, 665 | set: function(value) { 666 | this.uniforms.repeat.value.copy(value) 667 | }, 668 | }, 669 | }) 670 | 671 | this.setValues(parameters) 672 | } 673 | } 674 | 675 | MeshLineMaterial.prototype.copy = function(source) { 676 | THREE.ShaderMaterial.prototype.copy.call(this, source) 677 | 678 | this.lineWidth = source.lineWidth 679 | this.map = source.map 680 | this.useMap = source.useMap 681 | this.alphaMap = source.alphaMap 682 | this.useAlphaMap = source.useAlphaMap 683 | this.color.copy(source.color) 684 | this.opacity = source.opacity 685 | this.resolution.copy(source.resolution) 686 | this.sizeAttenuation = source.sizeAttenuation 687 | this.dashArray.copy(source.dashArray) 688 | this.dashOffset.copy(source.dashOffset) 689 | this.dashRatio.copy(source.dashRatio) 690 | this.useDash = source.useDash 691 | this.visibility = source.visibility 692 | this.alphaTest = source.alphaTest 693 | this.repeat.copy(source.repeat) 694 | 695 | return this 696 | } 697 | 698 | if (typeof exports !== 'undefined') { 699 | if (typeof module !== 'undefined' && module.exports) { 700 | exports = module.exports = { 701 | MeshLine: MeshLine, 702 | MeshLineMaterial: MeshLineMaterial, 703 | MeshLineRaycast: MeshLineRaycast, 704 | } 705 | } 706 | exports.MeshLine = MeshLine 707 | exports.MeshLineMaterial = MeshLineMaterial 708 | exports.MeshLineRaycast = MeshLineRaycast 709 | } else { 710 | root.MeshLine = MeshLine 711 | root.MeshLineMaterial = MeshLineMaterial 712 | root.MeshLineRaycast = MeshLineRaycast 713 | } 714 | }.call(this)) 715 | --------------------------------------------------------------------------------