STLLoader.js
1 /** 2 * @author aleeper / http://adamleeper.com/ 3 * @author mrdoob / http://mrdoob.com/ 4 * @author gero3 / https://github.com/gero3 5 * 6 * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. 7 * 8 * Supports both binary and ASCII encoded files, with automatic detection of type. 9 * 10 * Limitations: 11 * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). 12 * There is perhaps some question as to how valid it is to always assume little-endian-ness. 13 * ASCII decoding assumes file is UTF-8. Seems to work for the examples... 14 * 15 * Usage: 16 * var loader = new THREE.STLLoader(); 17 * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { 18 * scene.add( new THREE.Mesh( geometry ) ); 19 * }); 20 * 21 * For binary STLs geometry might contain colors for vertices. To use it: 22 * // use the same code to load STL as above 23 * if (geometry.hasColors) { 24 * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors }); 25 * } else { .... } 26 * var mesh = new THREE.Mesh( geometry, material ); 27 */ 28 29 30 THREE.STLLoader = function ( manager ) { 31 32 this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 33 34 }; 35 36 THREE.STLLoader.prototype = { 37 38 constructor: THREE.STLLoader, 39 40 load: function ( url, onLoad, onProgress, onError ) { 41 42 var scope = this; 43 44 var loader = new THREE.XHRLoader( scope.manager ); 45 loader.setCrossOrigin( this.crossOrigin ); 46 loader.setResponseType('arraybuffer'); 47 loader.load( url, function ( text ) { 48 49 onLoad( scope.parse( text ) ); 50 51 }, onProgress, onError ); 52 53 }, 54 55 parse: function ( data ) { 56 57 var isBinary = function () { 58 59 var expect, face_size, n_faces, reader; 60 reader = new DataView( binData ); 61 face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8); 62 n_faces = reader.getUint32(80, true); 63 expect = 80 + (32 / 8) + (n_faces * face_size); 64 65 if ( expect === reader.byteLength ) { 66 67 return true; 68 69 } 70 71 // some binary files will have different size from expected, 72 // checking characters higher than ASCII to confirm is binary 73 var fileLength = reader.byteLength; 74 for ( var index = 0; index < fileLength; index ++ ) { 75 76 if ( reader.getUint8(index, false) > 127 ) { 77 78 return true; 79 80 } 81 82 } 83 84 return false; 85 86 }; 87 88 var binData = this.ensureBinary( data ); 89 90 return isBinary() 91 ? this.parseBinary( binData ) 92 : 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 = new Float32Array( faces * 3 * 3); 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 var dataOffset = 84; 124 var faceLength = 12 * 4 + 2; 125 126 var offset = 0; 127 128 var geometry = new THREE.BufferGeometry(); 129 130 var vertices = new Float32Array( faces * 3 * 3 ); 131 var normals = new Float32Array( faces * 3 * 3 ); 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) { // facet has its own unique color 145 146 r = (packedColor & 0x1F) / 31; 147 g = ((packedColor >> 5) & 0x1F) / 31; 148 b = ((packedColor >> 10) & 0x1F) / 31; 149 } else { 150 151 r = defaultR; 152 g = defaultG; 153 b = defaultB; 154 } 155 } 156 157 for ( var i = 1; i <= 3; i ++ ) { 158 159 var vertexstart = start + i * 12; 160 161 vertices[ offset ] = reader.getFloat32( vertexstart, true ); 162 vertices[ offset + 1 ] = reader.getFloat32( vertexstart + 4, true ); 163 vertices[ offset + 2 ] = reader.getFloat32( vertexstart + 8, true ); 164 165 normals[ offset ] = normalX; 166 normals[ offset + 1 ] = normalY; 167 normals[ offset + 2 ] = normalZ; 168 169 if (hasColors) { 170 colors[ offset ] = r; 171 colors[ offset + 1 ] = g; 172 colors[ offset + 2 ] = b; 173 } 174 175 offset += 3; 176 177 } 178 179 } 180 181 geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); 182 geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); 183 184 if (hasColors) { 185 geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) ); 186 geometry.hasColors = true; 187 geometry.alpha = alpha; 188 } 189 190 return geometry; 191 192 }, 193 194 parseASCII: function ( data ) { 195 196 var geometry, length, normal, patternFace, patternNormal, patternVertex, result, text; 197 geometry = new THREE.Geometry(); 198 patternFace = /facet([\s\S]*?)endfacet/g; 199 200 while ( ( result = patternFace.exec( data ) ) !== null ) { 201 202 text = result[0]; 203 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; 204 205 while ( ( result = patternNormal.exec( text ) ) !== null ) { 206 207 normal = new THREE.Vector3( parseFloat( result[ 1 ] ), parseFloat( result[ 3 ] ), parseFloat( result[ 5 ] ) ); 208 209 } 210 211 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; 212 213 while ( ( result = patternVertex.exec( text ) ) !== null ) { 214 215 geometry.vertices.push( new THREE.Vector3( parseFloat( result[ 1 ] ), parseFloat( result[ 3 ] ), parseFloat( result[ 5 ] ) ) ); 216 217 } 218 219 length = geometry.vertices.length; 220 221 geometry.faces.push( new THREE.Face3( length - 3, length - 2, length - 1, normal ) ); 222 223 } 224 225 geometry.computeBoundingBox(); 226 geometry.computeBoundingSphere(); 227 228 return geometry; 229 230 }, 231 232 ensureString: function ( buf ) { 233 234 if (typeof buf !== "string") { 235 var array_buffer = new Uint8Array(buf); 236 var str = ''; 237 for (var i = 0; i < buf.byteLength; i ++) { 238 str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian 239 } 240 return str; 241 } else { 242 return buf; 243 } 244 245 }, 246 247 ensureBinary: function ( buf ) { 248 249 if (typeof buf === "string") { 250 var array_buffer = new Uint8Array(buf.length); 251 for (var i = 0; i < buf.length; i ++) { 252 array_buffer[i] = buf.charCodeAt(i) & 0xff; // implicitly assumes little-endian 253 } 254 return array_buffer.buffer || array_buffer; 255 } else { 256 return buf; 257 } 258 259 } 260 261 }; 262 263 if ( typeof DataView === 'undefined') { 264 265 DataView = function(buffer, byteOffset, byteLength) { 266 267 this.buffer = buffer; 268 this.byteOffset = byteOffset || 0; 269 this.byteLength = byteLength || buffer.byteLength || buffer.length; 270 this._isString = typeof buffer === "string"; 271 272 } 273 274 DataView.prototype = { 275 276 _getCharCodes:function(buffer,start,length) { 277 start = start || 0; 278 length = length || buffer.length; 279 var end = start + length; 280 var codes = []; 281 for (var i = start; i < end; i ++) { 282 codes.push(buffer.charCodeAt(i) & 0xff); 283 } 284 return codes; 285 }, 286 287 _getBytes: function (length, byteOffset, littleEndian) { 288 289 var result; 290 291 // Handle the lack of endianness 292 if (littleEndian === undefined) { 293 294 littleEndian = this._littleEndian; 295 296 } 297 298 // Handle the lack of byteOffset 299 if (byteOffset === undefined) { 300 301 byteOffset = this.byteOffset; 302 303 } else { 304 305 byteOffset = this.byteOffset + byteOffset; 306 307 } 308 309 if (length === undefined) { 310 311 length = this.byteLength - byteOffset; 312 313 } 314 315 // Error Checking 316 if (typeof byteOffset !== 'number') { 317 318 throw new TypeError('DataView byteOffset is not a number'); 319 320 } 321 322 if (length < 0 || byteOffset + length > this.byteLength) { 323 324 throw new Error('DataView length or (byteOffset+length) value is out of bounds'); 325 326 } 327 328 if (this.isString) { 329 330 result = this._getCharCodes(this.buffer, byteOffset, byteOffset + length); 331 332 } else { 333 334 result = this.buffer.slice(byteOffset, byteOffset + length); 335 336 } 337 338 if (!littleEndian && length > 1) { 339 340 if (!(result instanceof Array)) { 341 342 result = Array.prototype.slice.call(result); 343 344 } 345 346 result.reverse(); 347 } 348 349 return result; 350 351 }, 352 353 // Compatibility functions on a String Buffer 354 355 getFloat64: function (byteOffset, littleEndian) { 356 357 var b = this._getBytes(8, byteOffset, littleEndian), 358 359 sign = 1 - (2 * (b[7] >> 7)), 360 exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1), 361 362 // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead 363 mantissa = ((b[6] & 0x0f) * Math.pow(2, 48)) + (b[5] * Math.pow(2, 40)) + (b[4] * Math.pow(2, 32)) + 364 (b[3] * Math.pow(2, 24)) + (b[2] * Math.pow(2, 16)) + (b[1] * Math.pow(2, 8)) + b[0]; 365 366 if (exponent === 1024) { 367 if (mantissa !== 0) { 368 return NaN; 369 } else { 370 return sign * Infinity; 371 } 372 } 373 374 if (exponent === -1023) { // Denormalized 375 return sign * mantissa * Math.pow(2, -1022 - 52); 376 } 377 378 return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent); 379 380 }, 381 382 getFloat32: function (byteOffset, littleEndian) { 383 384 var b = this._getBytes(4, byteOffset, littleEndian), 385 386 sign = 1 - (2 * (b[3] >> 7)), 387 exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127, 388 mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0]; 389 390 if (exponent === 128) { 391 if (mantissa !== 0) { 392 return NaN; 393 } else { 394 return sign * Infinity; 395 } 396 } 397 398 if (exponent === -127) { // Denormalized 399 return sign * mantissa * Math.pow(2, -126 - 23); 400 } 401 402 return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent); 403 }, 404 405 getInt32: function (byteOffset, littleEndian) { 406 var b = this._getBytes(4, byteOffset, littleEndian); 407 return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; 408 }, 409 410 getUint32: function (byteOffset, littleEndian) { 411 return this.getInt32(byteOffset, littleEndian) >>> 0; 412 }, 413 414 getInt16: function (byteOffset, littleEndian) { 415 return (this.getUint16(byteOffset, littleEndian) << 16) >> 16; 416 }, 417 418 getUint16: function (byteOffset, littleEndian) { 419 var b = this._getBytes(2, byteOffset, littleEndian); 420 return (b[1] << 8) | b[0]; 421 }, 422 423 getInt8: function (byteOffset) { 424 return (this.getUint8(byteOffset) << 24) >> 24; 425 }, 426 427 getUint8: function (byteOffset) { 428 return this._getBytes(1, byteOffset)[0]; 429 } 430 431 }; 432 433 }