Created
September 13, 2019 09:51
-
-
Save BretCameron/ff96fee30da795b0b7224e2a13d411d9 to your computer and use it in GitHub Desktop.
A forked version of THREE.js's STLLoader.js, containing only the parse function
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
BufferAttribute, | |
BufferGeometry, | |
Float32BufferAttribute, | |
LoaderUtils, | |
Vector3 | |
} from "three"; | |
export default function parseSTL ( data ) { | |
function isBinary( data ) { | |
var expect, face_size, n_faces, reader; | |
reader = new DataView( data ); | |
face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 ); | |
n_faces = reader.getUint32( 80, true ); | |
expect = 80 + ( 32 / 8 ) + ( n_faces * face_size ); | |
if ( expect === reader.byteLength ) { | |
return true; | |
} | |
// An ASCII STL data must begin with 'solid ' as the first six bytes. | |
// However, ASCII STLs lacking the SPACE after the 'd' are known to be | |
// plentiful. So, check the first 5 bytes for 'solid'. | |
// Several encodings, such as UTF-8, precede the text with up to 5 bytes: | |
// https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding | |
// Search for "solid" to start anywhere after those prefixes. | |
// US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd' | |
var solid = [ 115, 111, 108, 105, 100 ]; | |
for ( var off = 0; off < 5; off ++ ) { | |
// If "solid" text is matched to the current offset, declare it to be an ASCII STL. | |
if ( matchDataViewAt( solid, reader, off ) ) return false; | |
} | |
// Couldn't find "solid" text at the beginning; it is binary STL. | |
return true; | |
} | |
function matchDataViewAt( query, reader, offset ) { | |
// Check if each byte in query matches the corresponding byte from the current offset | |
for ( var i = 0, il = query.length; i < il; i ++ ) { | |
if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false; | |
} | |
return true; | |
} | |
function parseBinary( data ) { | |
var reader = new DataView( data ); | |
var faces = reader.getUint32( 80, true ); | |
var r, g, b, hasColors = false, colors; | |
var defaultR, defaultG, defaultB, alpha; | |
// process STL header | |
// check for default color in header ("COLOR=rgba" sequence). | |
for ( var index = 0; index < 80 - 10; index ++ ) { | |
if ( ( reader.getUint32( index, false ) === 0x434F4C4F /*COLO*/ ) && | |
( reader.getUint8( index + 4 ) === 0x52 /*'R'*/ ) && | |
( reader.getUint8( index + 5 ) === 0x3D /*'='*/ ) ) { | |
hasColors = true; | |
colors = new Float32Array( faces * 3 * 3 ); | |
defaultR = reader.getUint8( index + 6 ) / 255; | |
defaultG = reader.getUint8( index + 7 ) / 255; | |
defaultB = reader.getUint8( index + 8 ) / 255; | |
alpha = reader.getUint8( index + 9 ) / 255; | |
} | |
} | |
var dataOffset = 84; | |
var faceLength = 12 * 4 + 2; | |
var geometry = new BufferGeometry(); | |
var vertices = new Float32Array( faces * 3 * 3 ); | |
var normals = new Float32Array( faces * 3 * 3 ); | |
for ( var face = 0; face < faces; face ++ ) { | |
var start = dataOffset + face * faceLength; | |
var normalX = reader.getFloat32( start, true ); | |
var normalY = reader.getFloat32( start + 4, true ); | |
var normalZ = reader.getFloat32( start + 8, true ); | |
if ( hasColors ) { | |
var packedColor = reader.getUint16( start + 48, true ); | |
if ( ( packedColor & 0x8000 ) === 0 ) { | |
// facet has its own unique color | |
r = ( packedColor & 0x1F ) / 31; | |
g = ( ( packedColor >> 5 ) & 0x1F ) / 31; | |
b = ( ( packedColor >> 10 ) & 0x1F ) / 31; | |
} else { | |
r = defaultR; | |
g = defaultG; | |
b = defaultB; | |
} | |
} | |
for ( var i = 1; i <= 3; i ++ ) { | |
var vertexstart = start + i * 12; | |
var componentIdx = ( face * 3 * 3 ) + ( ( i - 1 ) * 3 ); | |
vertices[ componentIdx ] = reader.getFloat32( vertexstart, true ); | |
vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true ); | |
vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true ); | |
normals[ componentIdx ] = normalX; | |
normals[ componentIdx + 1 ] = normalY; | |
normals[ componentIdx + 2 ] = normalZ; | |
if ( hasColors ) { | |
colors[ componentIdx ] = r; | |
colors[ componentIdx + 1 ] = g; | |
colors[ componentIdx + 2 ] = b; | |
} | |
} | |
} | |
geometry.addAttribute( 'position', new BufferAttribute( vertices, 3 ) ); | |
geometry.addAttribute( 'normal', new BufferAttribute( normals, 3 ) ); | |
if ( hasColors ) { | |
geometry.addAttribute( 'color', new BufferAttribute( colors, 3 ) ); | |
geometry.hasColors = true; | |
geometry.alpha = alpha; | |
} | |
return geometry; | |
} | |
function parseASCII( data ) { | |
var geometry = new BufferGeometry(); | |
var patternSolid = /solid([\s\S]*?)endsolid/g; | |
var patternFace = /facet([\s\S]*?)endfacet/g; | |
var faceCounter = 0; | |
var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source; | |
var patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' ); | |
var patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' ); | |
var vertices = []; | |
var normals = []; | |
var normal = new Vector3(); | |
var result; | |
var groupVertexes = []; | |
var groupCount = 0; | |
var startVertex = 0; | |
var endVertex = 0; | |
while ( ( result = patternSolid.exec( data ) ) !== null ) { | |
startVertex = endVertex; | |
var solid = result[ 0 ]; | |
while ( ( result = patternFace.exec( solid ) ) !== null ) { | |
var vertexCountPerFace = 0; | |
var normalCountPerFace = 0; | |
var text = result[ 0 ]; | |
while ( ( result = patternNormal.exec( text ) ) !== null ) { | |
normal.x = parseFloat( result[ 1 ] ); | |
normal.y = parseFloat( result[ 2 ] ); | |
normal.z = parseFloat( result[ 3 ] ); | |
normalCountPerFace ++; | |
} | |
while ( ( result = patternVertex.exec( text ) ) !== null ) { | |
vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) ); | |
normals.push( normal.x, normal.y, normal.z ); | |
vertexCountPerFace ++; | |
endVertex ++; | |
} | |
// every face have to own ONE valid normal | |
if ( normalCountPerFace !== 1 ) { | |
console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter ); | |
} | |
// each face have to own THREE valid vertices | |
if ( vertexCountPerFace !== 3 ) { | |
console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter ); | |
} | |
faceCounter ++; | |
} | |
groupVertexes.push( { startVertex: startVertex, endVertex: endVertex } ); | |
groupCount ++; | |
} | |
geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); | |
geometry.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); | |
if ( groupCount > 0 ) { | |
for ( var i = 0; i < groupVertexes.length; i ++ ) { | |
geometry.addGroup( groupVertexes[ i ].startVertex, groupVertexes[ i ].endVertex, i ); | |
} | |
} | |
return geometry; | |
} | |
function ensureString( buffer ) { | |
if ( typeof buffer !== 'string' ) { | |
return LoaderUtils.decodeText( new Uint8Array( buffer ) ); | |
} | |
return buffer; | |
} | |
function ensureBinary( buffer ) { | |
if ( typeof buffer === 'string' ) { | |
var array_buffer = new Uint8Array( buffer.length ); | |
for ( var i = 0; i < buffer.length; i ++ ) { | |
array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian | |
} | |
return array_buffer.buffer || array_buffer; | |
} else { | |
return buffer; | |
} | |
} | |
// start | |
var binData = ensureBinary( data ); | |
return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment