UNPKG

three

Version:

JavaScript 3D library

280 lines (198 loc) • 8.52 kB
import { FileLoader, Loader } from 'three/webgpu'; import { MaterialXDocument } from './materialx/MaterialXDocument.js'; import { MaterialXLog } from './materialx/MaterialXLog.js'; import { isZipBuffer, readMtlxArchive, createArchiveResolver } from './materialx/MaterialXArchive.js'; const _textDecoder = new TextDecoder(); function getResourcePath( loaderPath, url ) { if ( loaderPath ) return loaderPath; const index = url.lastIndexOf( '/' ); return index === - 1 ? '' : url.slice( 0, index + 1 ); } /** * A loader for the MaterialX format. * * The node materials loaded with this loader can only be used with {@link WebGPURenderer}. * Besides plain `.mtlx` documents, the loader accepts `.mtlx.zip` archives that bundle a * document with its textures. * * ```js * const loader = new MaterialXLoader().setPath( SAMPLE_PATH ); * const { materials } = await loader.loadAsync( 'standard_surface_brass_tiled.mtlx' ); * ``` * * @augments Loader * @three_import import { MaterialXLoader } from 'three/addons/loaders/MaterialXLoader.js'; */ class MaterialXLoader extends Loader { /** * Constructs a new MaterialX loader. * * @param {LoadingManager} [manager] - The loading manager. */ constructor( manager ) { super( manager ); /** * Releases the resources of the last loaded archive, if any. * * @private * @type {?Function} */ this.archiveDisposer = null; } /** * Frees the resources of the last loaded archive. * * @return {MaterialXLoader} A reference to this loader. */ dispose() { if ( this.archiveDisposer ) { this.archiveDisposer(); this.archiveDisposer = null; } return this; } /** * Starts loading from the given URL and passes the loaded MaterialX asset * to the `onLoad()` callback. * * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. * @param {function(Object)} onLoad - Executed when the loading process has been finished. Receives the parse result, see {@link MaterialXLoader#parse}. * @param {onProgressCallback} onProgress - Executed while the loading is in progress. * @param {onErrorCallback} onError - Executed when errors occur. * @param {Object} [options] - Parse options, see {@link MaterialXLoader#parse}. * @return {MaterialXLoader} A reference to this loader. */ load( url, onLoad, onProgress, onError, options = {} ) { const _onError = ( e ) => { if ( onError ) { onError( e ); } else { console.error( e ); } this.manager.itemError( url ); this.manager.itemEnd( url ); }; // Keep the LoadingManager active until parsing and dependent resources // have finished, matching the lifecycle used by GLTFLoader. this.manager.itemStart( url ); new FileLoader( this.manager ) .setPriority( this.priority ) .setCache( this.cache ) .setPath( this.path ) .setRequestHeader( this.requestHeader ) .setWithCredentials( this.withCredentials ) .setResponseType( 'arraybuffer' ) .load( url, ( data ) => { try { const parsed = this._parseBuffer( data, url, options ); parsed.document.waitForResources().then( () => { parsed.result.errors = parsed.log.errors; parsed.result.warnings = parsed.log.warnings; this._throwOnErrors( parsed.log, options ); onLoad( parsed.result ); this.manager.itemEnd( url ); } ).catch( _onError ); } catch ( e ) { _onError( e ); } }, onProgress, _onError ); return this; } /** * Async version of {@link MaterialXLoader#load}. The progress callback can be * omitted and the parse options passed as the second argument instead. * * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. * @param {onProgressCallback|Object} [onProgress] - Executed while the loading is in progress, or the parse options. * @param {Object} [options] - Parse options, see {@link MaterialXLoader#parse}. * @return {Promise<Object>} A Promise that resolves with the parse result. */ loadAsync( url, onProgress, options = {} ) { if ( onProgress && typeof onProgress === 'object' ) { options = onProgress; onProgress = undefined; } return new Promise( ( resolve, reject ) => { this.load( url, resolve, onProgress, reject, options ); } ); } /** * Parses a raw MaterialX document or a `.mtlx.zip` archive and returns the resulting materials. * * @param {ArrayBuffer|Uint8Array|string} data - The MaterialX document or archive. * @param {string} [url=''] - The URL the data was loaded from, used to resolve relative resource paths. * @param {Object} [options] - Parse options, see {@link MaterialXLoader#parse}. * @return {Object} The parse result, see {@link MaterialXLoader#parse}. */ parseBuffer( data, url = '', options = {} ) { const parsed = this._parseBuffer( data, url, options ); this._throwOnErrors( parsed.log, options ); return parsed.result; } _parseBuffer( data, url = '', options = {} ) { this.dispose(); let text; let archiveResolver = null; if ( data && ( isZipBuffer( data ) || /\.mtlx\.zip$/i.test( url ) ) ) { const archive = readMtlxArchive( data ); text = archive.text; const resolver = createArchiveResolver( archive.files ); archiveResolver = resolver.resolve; this.archiveDisposer = resolver.dispose; } else if ( typeof data === 'string' ) { text = data; } else if ( data instanceof Uint8Array ) { text = _textDecoder.decode( data ); } else { text = _textDecoder.decode( new Uint8Array( data ) ); } return this._parse( text, { ...options, archiveResolver, path: options.path || getResourcePath( this.path, url ) } ); } /** * Parses the given MaterialX document and returns the resulting materials * together with the translation log. * * @param {string} text - The raw MaterialX data as a string. * @param {Object} [options] - Parse options. * @param {string} [options.path] - The base path for resolving resources like textures. Defaults to the loader's path. * @param {string} [options.materialName] - Only translate the material with this name. Defaults to all materials. * @param {string} [options.uvSpace='bottom-left'] - The UV space of the document's textures, `'bottom-left'` or `'top-left'`. * @param {Function} [options.interfaceValidator] - Validates node interfaces, see `createStrictInterfaceValidator()` in `MaterialXInterfaceValidation.js`. * @param {function(Object, ?string): ?Node} [options.nodeResolver] - Builds the TSL node of a MaterialX node before the built-in node library. Called with the node and the requested output (`null` for the default output); returns the node, or `null` to leave it to the loader. For channel outputs such as `outx`, the loader takes the channel of the returned node. * @param {boolean} [options.throwOnErrors=true] - Whether translation errors throw or are only reported in the log. * @return {{materials: Object<string,NodeMaterial>, log: Array<Object>, errors: Array<Object>, warnings: Array<Object>}} The materials keyed by name and the translation log. */ parse( text, options = {} ) { const parsed = this._parse( text, options ); this._throwOnErrors( parsed.log, options ); return parsed.result; } _parse( text, options = {} ) { const log = new MaterialXLog(); const document = new MaterialXDocument( this.manager, options.path || this.path, log, options.archiveResolver || null, options.uvSpace ); document.imageLoader.setPriority( this.priority ); document.imageLoader.setCache( this.cache ); document.textureLoader.setPriority( this.priority ); document.textureLoader.setCache( this.cache ); document.imageLoader.setCrossOrigin( this.crossOrigin ); document.textureLoader.setCrossOrigin( this.crossOrigin ); document.textureLoader.setRequestHeader( this.requestHeader ); document.textureLoader.setWithCredentials( this.withCredentials ); const result = document.parse( text, options.materialName || null, { interfaceValidator: options.interfaceValidator, nodeResolver: options.nodeResolver, } ); return { document, log, result }; } _throwOnErrors( log, options ) { if ( options.throwOnErrors !== false && log.errors.length > 0 ) { const details = log.errors.map( ( error ) => error.message ).join( ' ' ); throw new Error( `THREE.MaterialXLoader: MaterialX translation failed with ${log.errors.length} error(s). ${details}` ); } } } export { MaterialXLoader };