|
1 /** |
|
2 * @author aleeper / http://adamleeper.com/ |
|
3 * @author mrdoob / http://mrdoob.com/ |
|
4 * @author gero3 / https://github.com/gero3 |
|
5 * @author Mugen87 / https://github.com/Mugen87 |
|
6 * |
|
7 * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. |
|
8 * |
|
9 * Supports both binary and ASCII encoded files, with automatic detection of type. |
|
10 * |
|
11 * The loader returns a non-indexed buffer geometry. |
|
12 * |
|
13 * Limitations: |
|
14 * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). |
|
15 * There is perhaps some question as to how valid it is to always assume little-endian-ness. |
|
16 * ASCII decoding assumes file is UTF-8. |
|
17 * |
|
18 * Usage: |
|
19 * var loader = new THREE.STLLoader(); |
|
20 * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { |
|
21 * scene.add( new THREE.Mesh( geometry ) ); |
|
22 * }); |
|
23 * |
|
24 * For binary STLs geometry might contain colors for vertices. To use it: |
|
25 * // use the same code to load STL as above |
|
26 * if (geometry.hasColors) { |
|
27 * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors }); |
|
28 * } else { .... } |
|
29 * var mesh = new THREE.Mesh( geometry, material ); |
|
30 */ |
|
31 |
|
32 |
|
33 THREE.STLLoader = function ( manager ) { |
|
34 |
|
35 this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; |
|
36 |
|
37 }; |
|
38 |
|
39 THREE.STLLoader.prototype = { |
|
40 |
|
41 constructor: THREE.STLLoader, |
|
42 |
|
43 load: function ( url, onLoad, onProgress, onError ) { |
|
44 |
|
45 var scope = this; |
|
46 |
|
47 var loader = new THREE.FileLoader( scope.manager ); |
|
48 loader.setResponseType( 'arraybuffer' ); |
|
49 loader.load( url, function ( text ) { |
|
50 |
|
51 onLoad( scope.parse( text ) ); |
|
52 |
|
53 }, onProgress, onError ); |
|
54 |
|
55 }, |
|
56 |
|
57 parse: function ( data ) { |
|
58 |
|
59 var isBinary = function () { |
|
60 |
|
61 var expect, face_size, n_faces, reader; |
|
62 reader = new DataView( binData ); |
|
63 face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 ); |
|
64 n_faces = reader.getUint32( 80, true ); |
|
65 expect = 80 + ( 32 / 8 ) + ( n_faces * face_size ); |
|
66 |
|
67 if ( expect === reader.byteLength ) { |
|
68 |
|
69 return true; |
|
70 |
|
71 } |
|
72 |
|
73 // some binary files will have different size from expected, |
|
74 // checking characters higher than ASCII to confirm is binary |
|
75 var fileLength = reader.byteLength; |
|
76 for ( var index = 0; index < fileLength; index ++ ) { |
|
77 |
|
78 if ( reader.getUint8( index, false ) > 127 ) { |
|
79 |
|
80 return true; |
|
81 |
|
82 } |
|
83 |
|
84 } |
|
85 |
|
86 return false; |
|
87 |
|
88 }; |
|
89 |
|
90 var binData = this.ensureBinary( data ); |
|
91 |
|
92 return isBinary() ? this.parseBinary( binData ) : this.parseASCII( this.ensureString( data ) ); |
|
93 |
|
94 }, |
|
95 |
|
96 parseBinary: function ( data ) { |
|
97 |
|
98 var reader = new DataView( data ); |
|
99 var faces = reader.getUint32( 80, true ); |
|
100 |
|
101 var r, g, b, hasColors = false, colors; |
|
102 var defaultR, defaultG, defaultB, alpha; |
|
103 |
|
104 // process STL header |
|
105 // check for default color in header ("COLOR=rgba" sequence). |
|
106 |
|
107 for ( var index = 0; index < 80 - 10; index ++ ) { |
|
108 |
|
109 if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) && |
|
110 ( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) && |
|
111 ( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) { |
|
112 |
|
113 hasColors = true; |
|
114 colors = []; |
|
115 |
|
116 defaultR = reader.getUint8( index + 6 ) / 255; |
|
117 defaultG = reader.getUint8( index + 7 ) / 255; |
|
118 defaultB = reader.getUint8( index + 8 ) / 255; |
|
119 alpha = reader.getUint8( index + 9 ) / 255; |
|
120 |
|
121 } |
|
122 |
|
123 } |
|
124 |
|
125 var dataOffset = 84; |
|
126 var faceLength = 12 * 4 + 2; |
|
127 |
|
128 var geometry = new THREE.BufferGeometry(); |
|
129 |
|
130 var vertices = []; |
|
131 var normals = []; |
|
132 |
|
133 for ( var face = 0; face < faces; face ++ ) { |
|
134 |
|
135 var start = dataOffset + face * faceLength; |
|
136 var normalX = reader.getFloat32( start, true ); |
|
137 var normalY = reader.getFloat32( start + 4, true ); |
|
138 var normalZ = reader.getFloat32( start + 8, true ); |
|
139 |
|
140 if ( hasColors ) { |
|
141 |
|
142 var packedColor = reader.getUint16( start + 48, true ); |
|
143 |
|
144 if ( ( packedColor & 0x8000 ) === 0 ) { |
|
145 |
|
146 // facet has its own unique color |
|
147 |
|
148 r = ( packedColor & 0x1F ) / 31; |
|
149 g = ( ( packedColor >> 5 ) & 0x1F ) / 31; |
|
150 b = ( ( packedColor >> 10 ) & 0x1F ) / 31; |
|
151 |
|
152 } else { |
|
153 |
|
154 r = defaultR; |
|
155 g = defaultG; |
|
156 b = defaultB; |
|
157 |
|
158 } |
|
159 |
|
160 } |
|
161 |
|
162 for ( var i = 1; i <= 3; i ++ ) { |
|
163 |
|
164 var vertexstart = start + i * 12; |
|
165 |
|
166 vertices.push( reader.getFloat32( vertexstart, true ) ); |
|
167 vertices.push( reader.getFloat32( vertexstart + 4, true ) ); |
|
168 vertices.push( reader.getFloat32( vertexstart + 8, true ) ); |
|
169 |
|
170 normals.push( normalX, normalY, normalZ ); |
|
171 |
|
172 if ( hasColors ) { |
|
173 |
|
174 colors.push( r, g, b ); |
|
175 |
|
176 } |
|
177 |
|
178 } |
|
179 |
|
180 } |
|
181 |
|
182 geometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( vertices ), 3 ) ); |
|
183 geometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( normals ), 3 ) ); |
|
184 |
|
185 if ( hasColors ) { |
|
186 |
|
187 geometry.addAttribute( 'color', new THREE.BufferAttribute( new Float32Array( colors ), 3 ) ); |
|
188 geometry.hasColors = true; |
|
189 geometry.alpha = alpha; |
|
190 |
|
191 } |
|
192 |
|
193 return geometry; |
|
194 |
|
195 }, |
|
196 |
|
197 parseASCII: function ( data ) { |
|
198 |
|
199 var geometry, length, patternFace, patternNormal, patternVertex, result, text; |
|
200 geometry = new THREE.BufferGeometry(); |
|
201 patternFace = /facet([\s\S]*?)endfacet/g; |
|
202 |
|
203 var vertices = []; |
|
204 var normals = []; |
|
205 |
|
206 var normal = new THREE.Vector3(); |
|
207 |
|
208 while ( ( result = patternFace.exec( data ) ) !== null ) { |
|
209 |
|
210 text = result[ 0 ]; |
|
211 patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g; |
|
212 |
|
213 while ( ( result = patternNormal.exec( text ) ) !== null ) { |
|
214 |
|
215 normal.x = parseFloat( result[ 1 ] ); |
|
216 normal.y = parseFloat( result[ 3 ] ); |
|
217 normal.z = parseFloat( result[ 5 ] ); |
|
218 |
|
219 } |
|
220 |
|
221 patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g; |
|
222 |
|
223 while ( ( result = patternVertex.exec( text ) ) !== null ) { |
|
224 |
|
225 vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 3 ] ), parseFloat( result[ 5 ] ) ); |
|
226 normals.push( normal.x, normal.y, normal.z ); |
|
227 |
|
228 } |
|
229 |
|
230 } |
|
231 |
|
232 geometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( vertices ), 3 ) ); |
|
233 geometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( normals ), 3 ) ); |
|
234 |
|
235 return geometry; |
|
236 |
|
237 }, |
|
238 |
|
239 ensureString: function ( buf ) { |
|
240 |
|
241 if ( typeof buf !== "string" ) { |
|
242 |
|
243 var array_buffer = new Uint8Array( buf ); |
|
244 var strArray = []; |
|
245 for ( var i = 0; i < buf.byteLength; i ++ ) { |
|
246 |
|
247 strArray.push(String.fromCharCode( array_buffer[ i ] )); // implicitly assumes little-endian |
|
248 |
|
249 } |
|
250 return strArray.join(''); |
|
251 |
|
252 } else { |
|
253 |
|
254 return buf; |
|
255 |
|
256 } |
|
257 |
|
258 }, |
|
259 |
|
260 ensureBinary: function ( buf ) { |
|
261 |
|
262 if ( typeof buf === "string" ) { |
|
263 |
|
264 var array_buffer = new Uint8Array( buf.length ); |
|
265 for ( var i = 0; i < buf.length; i ++ ) { |
|
266 |
|
267 array_buffer[ i ] = buf.charCodeAt( i ) & 0xff; // implicitly assumes little-endian |
|
268 |
|
269 } |
|
270 return array_buffer.buffer || array_buffer; |
|
271 |
|
272 } else { |
|
273 |
|
274 return buf; |
|
275 |
|
276 } |
|
277 |
|
278 } |
|
279 |
|
280 }; |