Accueil / npm / three@0.186.1 / examples / jsm / exporters / DRACOExporter.js

DRACOExporter.js

import { Color, ColorManagement, SRGBColorSpace } from 'three';

/* global DracoEncoderModule */

/**
 * An exporter to compress geometry with the Draco library.
 *
 * [Draco](https://google.github.io/draco/) is an open source library for compressing and
 * decompressing 3D meshes and point clouds. Compressed geometry can be significantly smaller,
 * at the cost of additional decoding time on the client device.
 *
 * Standalone Draco files have a `.drc` extension, and contain vertex positions,
 * normals, colors, and other attributes. Draco files *do not* contain materials,
 * textures, animation, or node hierarchies – to use these features, embed Draco geometry
 * inside of a glTF file. A normal glTF file can be converted to a Draco-compressed glTF file
 * using [glTF-Pipeline](https://github.com/AnalyticalGraphicsInc/gltf-pipeline).
 *
 * The exporter requires the Draco encoder to be loaded as a global script in advance:
 *
 * ```html
 * <script src="https://cdn.jsdelivr.net/gh/google/draco@1.5.7/javascript/draco_encoder.js"></script>
 * ```
 *
 * ```js
 * const exporter = new DRACOExporter();
 * const data = await exporter.parseAsync( mesh, options );
 * ```
 *
 * @three_import import { DRACOExporter } from 'three/addons/exporters/DRACOExporter.js';
 */
class DRACOExporter {

	/**
	 * Parses the given mesh or point cloud and generates the Draco output.
	 *
	 * @async
	 * @param {(Mesh|Points)} object - The mesh or point cloud to export.
	 * @param {DRACOExporter~Options} options - The export options.
	 * @return {Promise<Int8Array>} A Promise that resolves with the exported Draco.
	 */
	async parseAsync( object, options = {} ) {

		options = Object.assign( {
			decodeSpeed: 5,
			encodeSpeed: 5,
			encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING,
			quantization: [ 16, 8, 8, 8, 8 ],
			exportUvs: true,
			exportNormals: true,
			exportColor: false,
		}, options );

		if ( typeof DracoEncoderModule === 'undefined' ) {

			throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' );

		}

		const geometry = object.geometry;

		let dracoEncoder = DracoEncoderModule();

		// older encoder builds expose the module synchronously, newer builds return a promise

		if ( dracoEncoder.Encoder === undefined ) dracoEncoder = await dracoEncoder;

		const encoder = new dracoEncoder.Encoder();
		let builder;
		let dracoObject;

		if ( object.isMesh === true ) {

			builder = new dracoEncoder.MeshBuilder();
			dracoObject = new dracoEncoder.Mesh();

			const vertices = geometry.getAttribute( 'position' );
			builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );

			const faces = geometry.getIndex();

			if ( faces !== null ) {

				builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array );

			} else {

				const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count );

				for ( let i = 0; i < faces.length; i ++ ) {

					faces[ i ] = i;

				}

				builder.AddFacesToMesh( dracoObject, vertices.count, faces );

			}

			if ( options.exportNormals === true ) {

				const normals = geometry.getAttribute( 'normal' );

				if ( normals !== undefined ) {

					builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array );

				}

			}

			if ( options.exportUvs === true ) {

				const uvs = geometry.getAttribute( 'uv' );

				if ( uvs !== undefined ) {

					builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array );

				}

			}

			if ( options.exportColor === true ) {

				const colors = geometry.getAttribute( 'color' );

				if ( colors !== undefined ) {

					const array = createVertexColorSRGBArray( colors );

					builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array );

				}

			}

		} else if ( object.isPoints === true ) {

			builder = new dracoEncoder.PointCloudBuilder();
			dracoObject = new dracoEncoder.PointCloud();

			const vertices = geometry.getAttribute( 'position' );
			builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );

			if ( options.exportColor === true ) {

				const colors = geometry.getAttribute( 'color' );

				if ( colors !== undefined ) {

					const array = createVertexColorSRGBArray( colors );

					builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array );

				}

			}

		} else {

			throw new Error( 'THREE.DRACOExporter: Unsupported object type.' );

		}

		//Compress using draco encoder

		const encodedData = new dracoEncoder.DracoInt8Array();

		//Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression).

		const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5;
		const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5;

		encoder.SetSpeedOptions( encodeSpeed, decodeSpeed );

		// Sets the desired encoding method for a given geometry.

		if ( options.encoderMethod !== undefined ) {

			encoder.SetEncodingMethod( options.encoderMethod );

		}

		// Sets the quantization (number of bits used to represent) compression options for a named attribute.
		// The attribute values will be quantized in a box defined by the maximum extent of the attribute values.
		if ( options.quantization !== undefined ) {

			for ( let i = 0; i < 5; i ++ ) {

				if ( options.quantization[ i ] !== undefined ) {

					encoder.SetAttributeQuantization( i, options.quantization[ i ] );

				}

			}

		}

		let length;

		if ( object.isMesh === true ) {

			length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData );

		} else {

			length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData );

		}

		dracoEncoder.destroy( dracoObject );

		if ( length === 0 ) {

			throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' );

		}

		//Copy encoded data to buffer.
		const outputData = new Int8Array( new ArrayBuffer( length ) );

		for ( let i = 0; i < length; i ++ ) {

			outputData[ i ] = encodedData.GetValue( i );

		}

		dracoEncoder.destroy( encodedData );
		dracoEncoder.destroy( encoder );
		dracoEncoder.destroy( builder );

		return outputData;

	}

	/**
	 * @deprecated Use {@link DRACOExporter#parseAsync} instead.
	 */
	parse() {

		throw new Error( 'THREE.DRACOExporter: parse() has been replaced by parseAsync().' );

	}

}

function createVertexColorSRGBArray( attribute ) {

	// While .drc files do not specify colorspace, the only 'official' tooling
	// is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected
	// for .drc files, but note that Draco buffers embedded in glTF files will
	// be Linear-sRGB instead.

	const _color = new Color();

	const count = attribute.count;
	const itemSize = attribute.itemSize;
	const array = new Float32Array( count * itemSize );

	for ( let i = 0, il = count; i < il; i ++ ) {

		_color.fromBufferAttribute( attribute, i );

		ColorManagement.workingToColorSpace( _color, SRGBColorSpace );

		array[ i * itemSize ] = _color.r;
		array[ i * itemSize + 1 ] = _color.g;
		array[ i * itemSize + 2 ] = _color.b;

		if ( itemSize === 4 ) {

			array[ i * itemSize + 3 ] = attribute.getW( i );

		}

	}

	return array;

}

// Encoder methods

/**
 * Edgebreaker encoding.
 *
 * @static
 * @constant
 * @type {number}
 * @default 1
 */
DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1;

/**
 * Sequential encoding.
 *
 * @static
 * @constant
 * @type {number}
 * @default 0
 */
DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0;

// Geometry type

DRACOExporter.POINT_CLOUD = 0;
DRACOExporter.TRIANGULAR_MESH = 1;

// Attribute type

DRACOExporter.INVALID = - 1;
DRACOExporter.POSITION = 0;
DRACOExporter.NORMAL = 1;
DRACOExporter.COLOR = 2;
DRACOExporter.TEX_COORD = 3;
DRACOExporter.GENERIC = 4;

/**
 * Export options of `DRACOExporter`.
 *
 * @typedef {Object} DRACOExporter~Options
 * @property {number} [decodeSpeed=5] - Indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality).
 * @property {number} [encodeSpeed=5] - Indicates how to tune the encoder parameters (0 gives better speed but worst quality).
 * @property {number} [encoderMethod=1] - Either sequential (very little compression) or Edgebreaker. Edgebreaker traverses the triangles of the mesh in a deterministic, spiral-like way which provides most of the benefits of this data format.
 * @property {Array<number>} [quantization=[ 16, 8, 8, 8, 8 ]] - Indicates the precision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC).
 * @property {boolean} [exportUvs=true] - Whether to export UVs or not.
 * @property {boolean} [exportNormals=true] - Whether to export normals or not.
 * @property {boolean} [exportColor=false] - Whether to export colors or not.
 **/

export { DRACOExporter };

Exemple