瀏覽代碼

Add modulrized STLLoader.

haafoo 6 年之前
父節點
當前提交
ac325c657d
共有 4 個文件被更改,包括 628 次插入0 次删除
  1. 1 0
      examples/files.js
  2. 354 0
      examples/jsm/loaders/STLLoader.js
  3. 272 0
      examples/webgl_loader_stl_module.html
  4. 1 0
      utils/modularize.js

+ 1 - 0
examples/files.js

@@ -125,6 +125,7 @@ var files = {
 		"webgl_loader_sea3d_skinning",
 		"webgl_loader_sea3d_skinning",
 		"webgl_loader_sea3d_sound",
 		"webgl_loader_sea3d_sound",
 		"webgl_loader_stl",
 		"webgl_loader_stl",
+		"webgl_loader_stl_module",
 		"webgl_loader_svg",
 		"webgl_loader_svg",
 		"webgl_loader_texture_dds",
 		"webgl_loader_texture_dds",
 		"webgl_loader_texture_exr",
 		"webgl_loader_texture_exr",

+ 354 - 0
examples/jsm/loaders/STLLoader.js

@@ -0,0 +1,354 @@
+/**
+ * @author aleeper / http://adamleeper.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author gero3 / https://github.com/gero3
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
+ *
+ * Supports both binary and ASCII encoded files, with automatic detection of type.
+ *
+ * The loader returns a non-indexed buffer geometry.
+ *
+ * Limitations:
+ *  Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL).
+ *  There is perhaps some question as to how valid it is to always assume little-endian-ness.
+ *  ASCII decoding assumes file is UTF-8.
+ *
+ * Usage:
+ *  var loader = new STLLoader();
+ *  loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
+ *    scene.add( new Mesh( geometry ) );
+ *  });
+ *
+ * For binary STLs geometry might contain colors for vertices. To use it:
+ *  // use the same code to load STL as above
+ *  if (geometry.hasColors) {
+ *    material = new MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: VertexColors });
+ *  } else { .... }
+ *  var mesh = new Mesh( geometry, material );
+ */
+
+import {
+	BufferAttribute,
+	BufferGeometry,
+	DefaultLoadingManager,
+	FileLoader,
+	Float32BufferAttribute,
+	LoaderUtils,
+	Vector3
+} from "../../../build/three.module.js";
+
+
+var STLLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+STLLoader.prototype = {
+
+	constructor: STLLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( scope.path );
+		loader.setResponseType( 'arraybuffer' );
+		loader.load( url, function ( text ) {
+
+			try {
+
+				onLoad( scope.parse( text ) );
+
+			} catch ( exception ) {
+
+				if ( onError ) {
+
+					onError( exception );
+
+				}
+
+			}
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	parse: function ( 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 = [];
+
+					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 = [];
+			var normals = [];
+
+			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;
+
+					vertices.push( reader.getFloat32( vertexstart, true ) );
+					vertices.push( reader.getFloat32( vertexstart + 4, true ) );
+					vertices.push( reader.getFloat32( vertexstart + 8, true ) );
+
+					normals.push( normalX, normalY, normalZ );
+
+					if ( hasColors ) {
+
+						colors.push( r, g, b );
+
+					}
+
+				}
+
+			}
+
+			geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( vertices ), 3 ) );
+			geometry.addAttribute( 'normal', new BufferAttribute( new Float32Array( normals ), 3 ) );
+
+			if ( hasColors ) {
+
+				geometry.addAttribute( 'color', new BufferAttribute( new Float32Array( colors ), 3 ) );
+				geometry.hasColors = true;
+				geometry.alpha = alpha;
+
+			}
+
+			return geometry;
+
+		}
+
+		function parseASCII( data ) {
+
+			var geometry = new BufferGeometry();
+			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;
+
+			while ( ( result = patternFace.exec( data ) ) !== 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 ++;
+
+				}
+
+				// 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 ++;
+
+			}
+
+			geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+			geometry.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+
+			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 ) );
+
+	}
+
+};
+
+export { STLLoader };

+ 272 - 0
examples/webgl_loader_stl_module.html

@@ -0,0 +1,272 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - STL</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #000000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				z-index: 100;
+				display:block;
+
+			}
+
+			a { color: skyblue }
+			.button { background:#999; color:#eee; padding:0.2em 0.5em; cursor:pointer }
+			.highlight { background:orange; color:#fff; }
+
+			span {
+				display: inline-block;
+				width: 60px;
+				text-align: center;
+			}
+
+		</style>
+	</head>
+	<body>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> -
+			STL loader test by <a href="https://github.com/aleeper" target="_blank" rel="noopener">aleeper</a>. PR2 head from <a href="http://www.ros.org/wiki/pr2_description">www.ros.org</a>
+		</div>
+
+		<script src="js/WebGL.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<script type='module'>
+			import {
+				PerspectiveCamera,
+				Scene,
+				WebGLRenderer,
+				Mesh,
+				HemisphereLight,
+				DirectionalLight,
+				Vector3,
+				Color,
+				VertexColors,
+				Fog,
+				PlaneBufferGeometry,
+				MeshPhongMaterial
+			} from '../build/three.module.js';
+
+			import { STLLoader } from './jsm/loaders/STLLoader.js';
+
+
+			if ( WEBGL.isWebGLAvailable() === false ) {
+
+				document.body.appendChild( WEBGL.getWebGLErrorMessage() );
+
+			}
+
+			var container, stats;
+
+			var camera, cameraTarget, scene, renderer;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new PerspectiveCamera( 35, window.innerWidth / window.innerHeight, 1, 15 );
+				camera.position.set( 3, 0.15, 3 );
+
+				cameraTarget = new Vector3( 0, - 0.25, 0 );
+
+				scene = new Scene();
+				scene.background = new Color( 0x72645b );
+				scene.fog = new Fog( 0x72645b, 2, 15 );
+
+
+				// Ground
+
+				var plane = new Mesh(
+					new PlaneBufferGeometry( 40, 40 ),
+					new MeshPhongMaterial( { color: 0x999999, specular: 0x101010 } )
+				);
+				plane.rotation.x = - Math.PI / 2;
+				plane.position.y = - 0.5;
+				scene.add( plane );
+
+				plane.receiveShadow = true;
+
+
+				// ASCII file
+
+				var loader = new STLLoader();
+				loader.load( './models/stl/ascii/slotted_disk.stl', function ( geometry ) {
+
+					var material = new MeshPhongMaterial( { color: 0xff5533, specular: 0x111111, shininess: 200 } );
+					var mesh = new Mesh( geometry, material );
+
+					mesh.position.set( 0, - 0.25, 0.6 );
+					mesh.rotation.set( 0, - Math.PI / 2, 0 );
+					mesh.scale.set( 0.5, 0.5, 0.5 );
+
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
+
+					scene.add( mesh );
+
+				} );
+
+
+				// Binary files
+
+				var material = new MeshPhongMaterial( { color: 0xAAAAAA, specular: 0x111111, shininess: 200 } );
+
+				loader.load( './models/stl/binary/pr2_head_pan.stl', function ( geometry ) {
+
+					var mesh = new Mesh( geometry, material );
+
+					mesh.position.set( 0, - 0.37, - 0.6 );
+					mesh.rotation.set( - Math.PI / 2, 0, 0 );
+					mesh.scale.set( 2, 2, 2 );
+
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
+
+					scene.add( mesh );
+
+				} );
+
+				loader.load( './models/stl/binary/pr2_head_tilt.stl', function ( geometry ) {
+
+					var mesh = new Mesh( geometry, material );
+
+					mesh.position.set( 0.136, - 0.37, - 0.6 );
+					mesh.rotation.set( - Math.PI / 2, 0.3, 0 );
+					mesh.scale.set( 2, 2, 2 );
+
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
+
+					scene.add( mesh );
+
+				} );
+
+				// Colored binary STL
+				loader.load( './models/stl/binary/colored.stl', function ( geometry ) {
+
+					var meshMaterial = material;
+					if ( geometry.hasColors ) {
+
+						meshMaterial = new MeshPhongMaterial( { opacity: geometry.alpha, vertexColors: VertexColors } );
+
+					}
+
+					var mesh = new Mesh( geometry, meshMaterial );
+
+					mesh.position.set( 0.5, 0.2, 0 );
+					mesh.rotation.set( - Math.PI / 2, Math.PI / 2, 0 );
+					mesh.scale.set( 0.3, 0.3, 0.3 );
+
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
+
+					scene.add( mesh );
+
+				} );
+
+
+				// Lights
+
+				scene.add( new HemisphereLight( 0x443333, 0x111122 ) );
+
+				addShadowedLight( 1, 1, 1, 0xffffff, 1.35 );
+				addShadowedLight( 0.5, 1, - 1, 0xffaa00, 1 );
+				// renderer
+
+				renderer = new WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				renderer.gammaInput = true;
+				renderer.gammaOutput = true;
+
+				renderer.shadowMap.enabled = true;
+
+				container.appendChild( renderer.domElement );
+
+				// stats
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function addShadowedLight( x, y, z, color, intensity ) {
+
+				var directionalLight = new DirectionalLight( color, intensity );
+				directionalLight.position.set( x, y, z );
+				scene.add( directionalLight );
+
+				directionalLight.castShadow = true;
+
+				var d = 1;
+				directionalLight.shadow.camera.left = - d;
+				directionalLight.shadow.camera.right = d;
+				directionalLight.shadow.camera.top = d;
+				directionalLight.shadow.camera.bottom = - d;
+
+				directionalLight.shadow.camera.near = 1;
+				directionalLight.shadow.camera.far = 4;
+
+				directionalLight.shadow.mapSize.width = 1024;
+				directionalLight.shadow.mapSize.height = 1024;
+
+				directionalLight.shadow.bias = - 0.002;
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				var timer = Date.now() * 0.0005;
+
+				camera.position.x = Math.cos( timer ) * 3;
+				camera.position.z = Math.sin( timer ) * 3;
+
+				camera.lookAt( cameraTarget );
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 0
utils/modularize.js

@@ -23,6 +23,7 @@ var files = [
 	{ path: 'loaders/GLTFLoader.js', ignoreList: [ 'NoSide', 'Matrix2', 'DDSLoader' ] },
 	{ path: 'loaders/GLTFLoader.js', ignoreList: [ 'NoSide', 'Matrix2', 'DDSLoader' ] },
 	{ path: 'loaders/OBJLoader.js', ignoreList: [] },
 	{ path: 'loaders/OBJLoader.js', ignoreList: [] },
 	{ path: 'loaders/MTLLoader.js', ignoreList: [] },
 	{ path: 'loaders/MTLLoader.js', ignoreList: [] },
+	{ path: 'loaders/STLLoader.js', ignoreList: [] },
 
 
 	{ path: 'pmrem/PMREMCubeUVPacker.js', ignoreList: [] },
 	{ path: 'pmrem/PMREMCubeUVPacker.js', ignoreList: [] },
 	{ path: 'pmrem/PMREMGenerator.js', ignoreList: [] },
 	{ path: 'pmrem/PMREMGenerator.js', ignoreList: [] },