瀏覽代碼

Merge pull request #16683 from soadzoor/jsm-DRACOLoader

JSM: Added module and TS file for DRACOLoader
Michael Herzog 6 年之前
父節點
當前提交
762b2ee330

+ 1 - 0
docs/manual/en/introduction/Import-via-modules.html

@@ -168,6 +168,7 @@
 						<li>BVHLoader</li>
 						<li>ColladaLoader</li>
 						<li>DDSLoader</li>
+						<li>DRACOLoader</li>
 						<li>EXRLoader</li>
 						<li>FBXLoader</li>
 						<li>GCodeLoader</li>

File diff suppressed because it is too large
+ 577 - 404
examples/js/loaders/DRACOLoader.js


+ 21 - 0
examples/jsm/loaders/DRACOLoader.d.ts

@@ -0,0 +1,21 @@
+import {
+  LoadingManager,
+  BufferGeometry,
+  TrianglesDrawModes
+} from '../../../src/Three';
+
+export class DRACOLoader {
+  constructor(manager?: LoadingManager);
+
+  static setDecoderPath(path: string): void;
+  static setDecoderConfig(config: object): void;
+  static getDecoderModule(): Promise<any>;
+  static releaseDecoderModule(): void;
+
+  load(url: string, onLoad: (geometry: BufferGeometry) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void): void;
+  setPath(path: string): DRACOLoader;
+  setVerbosity(level: number): DRACOLoader;
+  setDrawMode(drawMode: TrianglesDrawModes): DRACOLoader;
+  setSkipDequantization(attributeName: 'position', skip?: boolean): DRACOLoader;
+  isVersionSupported(version: number, callback: (isVersionSupported: boolean) => any): void;
+}

+ 741 - 0
examples/jsm/loaders/DRACOLoader.js

@@ -0,0 +1,741 @@
+/** Copyright 2016 The Draco Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+	BufferGeometry,
+	DefaultLoadingManager,
+	FileLoader,
+	Float32BufferAttribute,
+	Int16BufferAttribute,
+	Int32BufferAttribute,
+	Int8BufferAttribute,
+	TriangleStripDrawMode,
+	TrianglesDrawMode,
+	Uint16BufferAttribute,
+	Uint32BufferAttribute,
+	Uint8BufferAttribute
+} from "../../../build/three.module.js";
+"use strict";
+
+/**
+ * @param {THREE.LoadingManager} manager
+ */
+var DRACOLoader = function ( manager ) {
+
+	this.timeLoaded = 0;
+	this.manager = manager || DefaultLoadingManager;
+	this.materials = null;
+	this.verbosity = 0;
+	this.attributeOptions = {};
+	this.drawMode = TrianglesDrawMode;
+	// Native Draco attribute type to Three.JS attribute type.
+	this.nativeAttributeMap = {
+		position: "POSITION",
+		normal: "NORMAL",
+		color: "COLOR",
+		uv: "TEX_COORD"
+	};
+
+};
+
+DRACOLoader.prototype = {
+	constructor: DRACOLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( this.path );
+		loader.setResponseType( "arraybuffer" );
+		loader.load(
+			url,
+			function ( blob ) {
+
+				scope.decodeDracoFile( blob, onLoad );
+
+			},
+			onProgress,
+			onError
+		);
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	setVerbosity: function ( level ) {
+
+		this.verbosity = level;
+		return this;
+
+	},
+
+	/**
+	 *  Sets desired mode for generated geometry indices.
+	 *  Can be either:
+	 *      TrianglesDrawMode
+	 *      TriangleStripDrawMode
+	 */
+	setDrawMode: function ( drawMode ) {
+
+		this.drawMode = drawMode;
+		return this;
+
+	},
+
+	/**
+	 * Skips dequantization for a specific attribute.
+	 * |attributeName| is the js name of the given attribute type.
+	 * The only currently supported |attributeName| is 'position', more may be
+	 * added in future.
+	 */
+	setSkipDequantization: function ( attributeName, skip ) {
+
+		var skipDequantization = true;
+		if ( typeof skip !== "undefined" ) skipDequantization = skip;
+		this.getAttributeOptions(
+			attributeName
+		).skipDequantization = skipDequantization;
+		return this;
+
+	},
+
+	/**
+	 * Decompresses a Draco buffer. Names of attributes (for ID and type maps)
+	 * must be one of the supported three.js types, including: position, color,
+	 * normal, uv, uv2, skinIndex, skinWeight.
+	 *
+	 * @param {ArrayBuffer} rawBuffer
+	 * @param {Function} callback
+	 * @param {Object|undefined} attributeUniqueIdMap Provides a pre-defined ID
+	 *     for each attribute in the geometry to be decoded. If given,
+	 *     `attributeTypeMap` is required and `nativeAttributeMap` will be
+	 *     ignored.
+	 * @param {Object|undefined} attributeTypeMap Provides a predefined data
+	 *     type (as a typed array constructor) for each attribute in the
+	 *     geometry to be decoded.
+	 */
+	decodeDracoFile: function (
+		rawBuffer,
+		callback,
+		attributeUniqueIdMap,
+		attributeTypeMap
+	) {
+
+		var scope = this;
+		DRACOLoader.getDecoderModule().then( function ( module ) {
+
+			scope.decodeDracoFileInternal(
+				rawBuffer,
+				module.decoder,
+				callback,
+				attributeUniqueIdMap,
+				attributeTypeMap
+			);
+
+		} );
+
+	},
+
+	decodeDracoFileInternal: function (
+		rawBuffer,
+		dracoDecoder,
+		callback,
+		attributeUniqueIdMap,
+		attributeTypeMap
+	) {
+
+		/*
+		 * Here is how to use Draco Javascript decoder and get the geometry.
+		 */
+		var buffer = new dracoDecoder.DecoderBuffer();
+		buffer.Init( new Int8Array( rawBuffer ), rawBuffer.byteLength );
+		var decoder = new dracoDecoder.Decoder();
+
+		/*
+		 * Determine what type is this file: mesh or point cloud.
+		 */
+		var geometryType = decoder.GetEncodedGeometryType( buffer );
+		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+
+			if ( this.verbosity > 0 ) {
+
+				console.log( "Loaded a mesh." );
+
+			}
+
+		} else if ( geometryType == dracoDecoder.POINT_CLOUD ) {
+
+			if ( this.verbosity > 0 ) {
+
+				console.log( "Loaded a point cloud." );
+
+			}
+
+		} else {
+
+			var errorMsg = "DRACOLoader: Unknown geometry type.";
+			console.error( errorMsg );
+			throw new Error( errorMsg );
+
+		}
+		callback(
+			this.convertDracoGeometryTo3JS(
+				dracoDecoder,
+				decoder,
+				geometryType,
+				buffer,
+				attributeUniqueIdMap,
+				attributeTypeMap
+			)
+		);
+
+	},
+
+	addAttributeToGeometry: function (
+		dracoDecoder,
+		decoder,
+		dracoGeometry,
+		attributeName,
+		attributeType,
+		attribute,
+		geometry,
+		geometryBuffer
+	) {
+
+		if ( attribute.ptr === 0 ) {
+
+			var errorMsg = "DRACOLoader: No attribute " + attributeName;
+			console.error( errorMsg );
+			throw new Error( errorMsg );
+
+		}
+
+		var numComponents = attribute.num_components();
+		var numPoints = dracoGeometry.num_points();
+		var numValues = numPoints * numComponents;
+		var attributeData;
+		var TypedBufferAttribute;
+
+		switch ( attributeType ) {
+
+			case Float32Array:
+				attributeData = new dracoDecoder.DracoFloat32Array();
+				decoder.GetAttributeFloatForAllPoints(
+					dracoGeometry,
+					attribute,
+					attributeData
+				);
+				geometryBuffer[ attributeName ] = new Float32Array( numValues );
+				TypedBufferAttribute = Float32BufferAttribute;
+				break;
+
+			case Int8Array:
+				attributeData = new dracoDecoder.DracoInt8Array();
+				decoder.GetAttributeInt8ForAllPoints(
+					dracoGeometry,
+					attribute,
+					attributeData
+				);
+				geometryBuffer[ attributeName ] = new Int8Array( numValues );
+				TypedBufferAttribute = Int8BufferAttribute;
+				break;
+
+			case Int16Array:
+				attributeData = new dracoDecoder.DracoInt16Array();
+				decoder.GetAttributeInt16ForAllPoints(
+					dracoGeometry,
+					attribute,
+					attributeData
+				);
+				geometryBuffer[ attributeName ] = new Int16Array( numValues );
+				TypedBufferAttribute = Int16BufferAttribute;
+				break;
+
+			case Int32Array:
+				attributeData = new dracoDecoder.DracoInt32Array();
+				decoder.GetAttributeInt32ForAllPoints(
+					dracoGeometry,
+					attribute,
+					attributeData
+				);
+				geometryBuffer[ attributeName ] = new Int32Array( numValues );
+				TypedBufferAttribute = Int32BufferAttribute;
+				break;
+
+			case Uint8Array:
+				attributeData = new dracoDecoder.DracoUInt8Array();
+				decoder.GetAttributeUInt8ForAllPoints(
+					dracoGeometry,
+					attribute,
+					attributeData
+				);
+				geometryBuffer[ attributeName ] = new Uint8Array( numValues );
+				TypedBufferAttribute = Uint8BufferAttribute;
+				break;
+
+			case Uint16Array:
+				attributeData = new dracoDecoder.DracoUInt16Array();
+				decoder.GetAttributeUInt16ForAllPoints(
+					dracoGeometry,
+					attribute,
+					attributeData
+				);
+				geometryBuffer[ attributeName ] = new Uint16Array( numValues );
+				TypedBufferAttribute = Uint16BufferAttribute;
+				break;
+
+			case Uint32Array:
+				attributeData = new dracoDecoder.DracoUInt32Array();
+				decoder.GetAttributeUInt32ForAllPoints(
+					dracoGeometry,
+					attribute,
+					attributeData
+				);
+				geometryBuffer[ attributeName ] = new Uint32Array( numValues );
+				TypedBufferAttribute = Uint32BufferAttribute;
+				break;
+
+			default:
+				var errorMsg = "DRACOLoader: Unexpected attribute type.";
+				console.error( errorMsg );
+				throw new Error( errorMsg );
+
+		}
+
+		// Copy data from decoder.
+		for ( var i = 0; i < numValues; i ++ ) {
+
+			geometryBuffer[ attributeName ][ i ] = attributeData.GetValue( i );
+
+		}
+		// Add attribute to THREEJS geometry for rendering.
+		geometry.addAttribute(
+			attributeName,
+			new TypedBufferAttribute( geometryBuffer[ attributeName ], numComponents )
+		);
+		dracoDecoder.destroy( attributeData );
+
+	},
+
+	convertDracoGeometryTo3JS: function (
+		dracoDecoder,
+		decoder,
+		geometryType,
+		buffer,
+		attributeUniqueIdMap,
+		attributeTypeMap
+	) {
+
+		// TODO: Should not assume native Draco attribute IDs apply.
+		if ( this.getAttributeOptions( "position" ).skipDequantization === true ) {
+
+			decoder.SkipAttributeTransform( dracoDecoder.POSITION );
+
+		}
+		var dracoGeometry;
+		var decodingStatus;
+		var start_time = performance.now();
+		if ( geometryType === dracoDecoder.TRIANGULAR_MESH ) {
+
+			dracoGeometry = new dracoDecoder.Mesh();
+			decodingStatus = decoder.DecodeBufferToMesh( buffer, dracoGeometry );
+
+		} else {
+
+			dracoGeometry = new dracoDecoder.PointCloud();
+			decodingStatus = decoder.DecodeBufferToPointCloud( buffer, dracoGeometry );
+
+		}
+		if ( ! decodingStatus.ok() || dracoGeometry.ptr == 0 ) {
+
+			var errorMsg = "DRACOLoader: Decoding failed: ";
+			errorMsg += decodingStatus.error_msg();
+			console.error( errorMsg );
+			dracoDecoder.destroy( decoder );
+			dracoDecoder.destroy( dracoGeometry );
+			throw new Error( errorMsg );
+
+		}
+
+		var decode_end = performance.now();
+		dracoDecoder.destroy( buffer );
+		/*
+		 * Example on how to retrieve mesh and attributes.
+		 */
+		var numFaces;
+		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+
+			numFaces = dracoGeometry.num_faces();
+			if ( this.verbosity > 0 ) {
+
+				console.log( "Number of faces loaded: " + numFaces.toString() );
+
+			}
+
+		} else {
+
+			numFaces = 0;
+
+		}
+
+		var numPoints = dracoGeometry.num_points();
+		var numAttributes = dracoGeometry.num_attributes();
+		if ( this.verbosity > 0 ) {
+
+			console.log( "Number of points loaded: " + numPoints.toString() );
+			console.log( "Number of attributes loaded: " + numAttributes.toString() );
+
+		}
+
+		// Verify if there is position attribute.
+		// TODO: Should not assume native Draco attribute IDs apply.
+		var posAttId = decoder.GetAttributeId( dracoGeometry, dracoDecoder.POSITION );
+		if ( posAttId == - 1 ) {
+
+			var errorMsg = "DRACOLoader: No position attribute found.";
+			console.error( errorMsg );
+			dracoDecoder.destroy( decoder );
+			dracoDecoder.destroy( dracoGeometry );
+			throw new Error( errorMsg );
+
+		}
+		var posAttribute = decoder.GetAttribute( dracoGeometry, posAttId );
+
+		// Structure for converting to THREEJS geometry later.
+		var geometryBuffer = {};
+		// Import data to Three JS geometry.
+		var geometry = new BufferGeometry();
+
+		// Do not use both the native attribute map and a provided (e.g. glTF) map.
+		if ( attributeUniqueIdMap ) {
+
+			// Add attributes of user specified unique id. E.g. GLTF models.
+			for ( var attributeName in attributeUniqueIdMap ) {
+
+				var attributeType = attributeTypeMap[ attributeName ];
+				var attributeId = attributeUniqueIdMap[ attributeName ];
+				var attribute = decoder.GetAttributeByUniqueId(
+					dracoGeometry,
+					attributeId
+				);
+				this.addAttributeToGeometry(
+					dracoDecoder,
+					decoder,
+					dracoGeometry,
+					attributeName,
+					attributeType,
+					attribute,
+					geometry,
+					geometryBuffer
+				);
+
+			}
+
+		} else {
+
+			// Add native Draco attribute type to geometry.
+			for ( var attributeName in this.nativeAttributeMap ) {
+
+				var attId = decoder.GetAttributeId(
+					dracoGeometry,
+					dracoDecoder[ this.nativeAttributeMap[ attributeName ] ]
+				);
+				if ( attId !== - 1 ) {
+
+					if ( this.verbosity > 0 ) {
+
+						console.log( "Loaded " + attributeName + " attribute." );
+
+					}
+					var attribute = decoder.GetAttribute( dracoGeometry, attId );
+					this.addAttributeToGeometry(
+						dracoDecoder,
+						decoder,
+						dracoGeometry,
+						attributeName,
+						Float32Array,
+						attribute,
+						geometry,
+						geometryBuffer
+					);
+
+				}
+
+			}
+
+		}
+
+		// For mesh, we need to generate the faces.
+		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+
+			if ( this.drawMode === TriangleStripDrawMode ) {
+
+				var stripsArray = new dracoDecoder.DracoInt32Array();
+				var numStrips = decoder.GetTriangleStripsFromMesh(
+					dracoGeometry,
+					stripsArray
+				);
+				geometryBuffer.indices = new Uint32Array( stripsArray.size() );
+				for ( var i = 0; i < stripsArray.size(); ++ i ) {
+
+					geometryBuffer.indices[ i ] = stripsArray.GetValue( i );
+
+				}
+				dracoDecoder.destroy( stripsArray );
+
+			} else {
+
+				var numIndices = numFaces * 3;
+				geometryBuffer.indices = new Uint32Array( numIndices );
+				var ia = new dracoDecoder.DracoInt32Array();
+				for ( var i = 0; i < numFaces; ++ i ) {
+
+					decoder.GetFaceFromMesh( dracoGeometry, i, ia );
+					var index = i * 3;
+					geometryBuffer.indices[ index ] = ia.GetValue( 0 );
+					geometryBuffer.indices[ index + 1 ] = ia.GetValue( 1 );
+					geometryBuffer.indices[ index + 2 ] = ia.GetValue( 2 );
+
+				}
+				dracoDecoder.destroy( ia );
+
+			}
+
+		}
+
+		geometry.drawMode = this.drawMode;
+		if ( geometryType == dracoDecoder.TRIANGULAR_MESH ) {
+
+			geometry.setIndex(
+				new ( geometryBuffer.indices.length > 65535
+					? Uint32BufferAttribute
+					: Uint16BufferAttribute )( geometryBuffer.indices, 1 )
+			);
+
+		}
+
+		// TODO: Should not assume native Draco attribute IDs apply.
+		// TODO: Can other attribute types be quantized?
+		var posTransform = new dracoDecoder.AttributeQuantizationTransform();
+		if ( posTransform.InitFromAttribute( posAttribute ) ) {
+
+			// Quantized attribute. Store the quantization parameters into the
+			// js attribute.
+			geometry.attributes[ "position" ].isQuantized = true;
+			geometry.attributes[ "position" ].maxRange = posTransform.range();
+			geometry.attributes[
+				"position"
+			].numQuantizationBits = posTransform.quantization_bits();
+			geometry.attributes[ "position" ].minValues = new Float32Array( 3 );
+			for ( var i = 0; i < 3; ++ i ) {
+
+				geometry.attributes[ "position" ].minValues[ i ] = posTransform.min_value(
+					i
+				);
+
+			}
+
+		}
+		dracoDecoder.destroy( posTransform );
+		dracoDecoder.destroy( decoder );
+		dracoDecoder.destroy( dracoGeometry );
+
+		this.decode_time = decode_end - start_time;
+		this.import_time = performance.now() - decode_end;
+
+		if ( this.verbosity > 0 ) {
+
+			console.log( "Decode time: " + this.decode_time );
+			console.log( "Import time: " + this.import_time );
+
+		}
+		return geometry;
+
+	},
+
+	isVersionSupported: function ( version, callback ) {
+
+		DRACOLoader.getDecoderModule().then( function ( module ) {
+
+			callback( module.decoder.isVersionSupported( version ) );
+
+		} );
+
+	},
+
+	getAttributeOptions: function ( attributeName ) {
+
+		if ( typeof this.attributeOptions[ attributeName ] === "undefined" )
+			this.attributeOptions[ attributeName ] = {};
+		return this.attributeOptions[ attributeName ];
+
+	}
+};
+
+DRACOLoader.decoderPath = "./";
+DRACOLoader.decoderConfig = {};
+DRACOLoader.decoderModulePromise = null;
+
+/**
+ * Sets the base path for decoder source files.
+ * @param {string} path
+ */
+DRACOLoader.setDecoderPath = function ( path ) {
+
+	DRACOLoader.decoderPath = path;
+
+};
+
+/**
+ * Sets decoder configuration and releases singleton decoder module. Module
+ * will be recreated with the next decoding call.
+ * @param {Object} config
+ */
+DRACOLoader.setDecoderConfig = function ( config ) {
+
+	var wasmBinary = DRACOLoader.decoderConfig.wasmBinary;
+	DRACOLoader.decoderConfig = config || {};
+	DRACOLoader.releaseDecoderModule();
+
+	// Reuse WASM binary.
+	if ( wasmBinary ) DRACOLoader.decoderConfig.wasmBinary = wasmBinary;
+
+};
+
+/**
+ * Releases the singleton DracoDecoderModule instance. Module will be recreated
+ * with the next decoding call.
+ */
+DRACOLoader.releaseDecoderModule = function () {
+
+	DRACOLoader.decoderModulePromise = null;
+
+};
+
+/**
+ * Gets WebAssembly or asm.js singleton instance of DracoDecoderModule
+ * after testing for browser support. Returns Promise that resolves when
+ * module is available.
+ * @return {Promise<{decoder: DracoDecoderModule}>}
+ */
+DRACOLoader.getDecoderModule = function () {
+
+	var scope = this;
+	var path = DRACOLoader.decoderPath;
+	var config = DRACOLoader.decoderConfig;
+	var promise = DRACOLoader.decoderModulePromise;
+
+	if ( promise ) return promise;
+
+	// Load source files.
+	if ( typeof DracoDecoderModule !== "undefined" ) {
+
+		// Loaded externally.
+		promise = Promise.resolve();
+
+	} else if ( typeof WebAssembly !== "object" || config.type === "js" ) {
+
+		// Load with asm.js.
+		promise = DRACOLoader._loadScript( path + "draco_decoder.js" );
+
+	} else {
+
+		// Load with WebAssembly.
+		config.wasmBinaryFile = path + "draco_decoder.wasm";
+		promise = DRACOLoader._loadScript( path + "draco_wasm_wrapper.js" )
+			.then( function () {
+
+				return DRACOLoader._loadArrayBuffer( config.wasmBinaryFile );
+
+			} )
+			.then( function ( wasmBinary ) {
+
+				config.wasmBinary = wasmBinary;
+
+			} );
+
+	}
+
+	// Wait for source files, then create and return a decoder.
+	promise = promise.then( function () {
+
+		return new Promise( function ( resolve ) {
+
+			config.onModuleLoaded = function ( decoder ) {
+
+				scope.timeLoaded = performance.now();
+				// Module is Promise-like. Wrap before resolving to avoid loop.
+				resolve( { decoder: decoder } );
+
+			};
+			DracoDecoderModule( config );
+
+		} );
+
+	} );
+
+	DRACOLoader.decoderModulePromise = promise;
+	return promise;
+
+};
+
+/**
+ * @param {string} src
+ * @return {Promise}
+ */
+DRACOLoader._loadScript = function ( src ) {
+
+	var prevScript = document.getElementById( "decoder_script" );
+	if ( prevScript !== null ) {
+
+		prevScript.parentNode.removeChild( prevScript );
+
+	}
+	var head = document.getElementsByTagName( "head" )[ 0 ];
+	var script = document.createElement( "script" );
+	script.id = "decoder_script";
+	script.type = "text/javascript";
+	script.src = src;
+	return new Promise( function ( resolve ) {
+
+		script.onload = resolve;
+		head.appendChild( script );
+
+	} );
+
+};
+
+/**
+ * @param {string} src
+ * @return {Promise}
+ */
+DRACOLoader._loadArrayBuffer = function ( src ) {
+
+	var loader = new FileLoader();
+	loader.setResponseType( "arraybuffer" );
+	return new Promise( function ( resolve, reject ) {
+
+		loader.load( src, resolve, undefined, reject );
+
+	} );
+
+};
+
+export { DRACOLoader };

+ 1 - 0
utils/modularize.js

@@ -72,6 +72,7 @@ var files = [
 	{ path: 'loaders/BVHLoader.js', dependencies: [], ignoreList: [ 'Bones' ] },
 	{ path: 'loaders/ColladaLoader.js', dependencies: [ { name: 'TGALoader', path: 'loaders/TGALoader.js' } ], ignoreList: [] },
 	{ path: 'loaders/DDSLoader.js', dependencies: [], ignoreList: [] },
+	{ path: 'loaders/DRACOLoader.js', dependencies: [], ignoreList: [ 'LoadingManager' ] },
 	{ path: 'loaders/EXRLoader.js', dependencies: [], ignoreList: [] },
 	{ path: 'loaders/FBXLoader.js', dependencies: [ { name: 'TGALoader', path: 'loaders/TGALoader.js' }, { name: 'NURBSCurve', path: 'curves/NURBSCurve.js' } ], ignoreList: [] },
 	{ path: 'loaders/GCodeLoader.js', dependencies: [], ignoreList: [] },

Some files were not shown because too many files changed in this diff