diff options
Diffstat (limited to 'public/bluebey-studio/vendor/three/examples/jsm/exporters')
| -rw-r--r-- | public/bluebey-studio/vendor/three/examples/jsm/exporters/GLTFExporter.js | 3856 |
1 files changed, 0 insertions, 3856 deletions
diff --git a/public/bluebey-studio/vendor/three/examples/jsm/exporters/GLTFExporter.js b/public/bluebey-studio/vendor/three/examples/jsm/exporters/GLTFExporter.js deleted file mode 100644 index dc439d7..0000000 --- a/public/bluebey-studio/vendor/three/examples/jsm/exporters/GLTFExporter.js +++ /dev/null @@ -1,3856 +0,0 @@ -import { - BufferAttribute, - ClampToEdgeWrapping, - Color, - DoubleSide, - InterpolateDiscrete, - InterpolateLinear, - NoColorSpace, - LinearFilter, - LinearMipmapLinearFilter, - LinearMipmapNearestFilter, - MathUtils, - Matrix4, - MirroredRepeatWrapping, - NearestFilter, - NearestMipmapLinearFilter, - NearestMipmapNearestFilter, - PropertyBinding, - RGBAFormat, - RepeatWrapping, - Scene, - SRGBColorSpace, - TextureSource, - CompressedTexture, - Vector3, - Quaternion, - REVISION, - ImageUtils -} from 'three'; - -/** - * The KHR_mesh_quantization extension allows these extra attribute component types - * - * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes - */ -const KHR_mesh_quantization_ExtraAttrTypes = { - POSITION: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'unsigned byte normalized', - 'short', - 'short normalized', - 'unsigned short', - 'unsigned short normalized', - ], - NORMAL: [ - 'byte normalized', - 'short normalized', - ], - TANGENT: [ - 'byte normalized', - 'short normalized', - ], - TEXCOORD: [ - 'byte', - 'byte normalized', - 'unsigned byte', - 'short', - 'short normalized', - 'unsigned short', - ], -}; - -/** - * An exporter for `glTF` 2.0. - * - * glTF (GL Transmission Format) is an [open format specification](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0) - * for efficient delivery and loading of 3D content. Assets may be provided either in JSON (.gltf) - * or binary (.glb) format. External files store textures (.jpg, .png) and additional binary - * data (.bin). A glTF asset may deliver one or more scenes, including meshes, materials, - * textures, skins, skeletons, morph targets, animations, lights, and/or cameras. - * - * GLTFExporter supports the [glTF 2.0 extensions](https://github.com/KhronosGroup/glTF/tree/master/extensions/): - * - * - KHR_lights_punctual - * - KHR_materials_clearcoat - * - KHR_materials_dispersion - * - KHR_materials_emissive_strength - * - KHR_materials_ior - * - KHR_materials_iridescence - * - KHR_materials_specular - * - KHR_materials_sheen - * - KHR_materials_transmission - * - KHR_materials_unlit - * - KHR_materials_volume - * - KHR_mesh_quantization - * - KHR_texture_transform - * - EXT_materials_bump - * - EXT_mesh_gpu_instancing - * - EXT_texture_webp - * - * The following glTF 2.0 extension is supported by an external user plugin: - * - * - [KHR_materials_variants](https://github.com/takahirox/three-gltf-extensions) - * - * ```js - * const exporter = new GLTFExporter(); - * const data = await exporter.parseAsync( scene, options ); - * ``` - * - * @three_import import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js'; - */ -class GLTFExporter { - - /** - * Constructs a new glTF exporter. - */ - constructor() { - - /** - * A reference to a texture utils module. - * - * @type {?(WebGLTextureUtils|WebGPUTextureUtils)} - * @default null - */ - this.textureUtils = null; - - this.pluginCallbacks = []; - - this.register( function ( writer ) { - - return new GLTFLightExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsUnlitExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsTransmissionExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsVolumeExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsIorExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsSpecularExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsClearcoatExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsDispersionExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsIridescenceExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsSheenExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsAnisotropyExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsEmissiveStrengthExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMaterialsBumpExtension( writer ); - - } ); - - this.register( function ( writer ) { - - return new GLTFMeshGpuInstancing( writer ); - - } ); - - } - - /** - * Registers a plugin callback. This API is internally used to implement the various - * glTF extensions but can also used by third-party code to add additional logic - * to the exporter. - * - * @param {function(writer:GLTFWriter)} callback - The callback function to register. - * @return {GLTFExporter} A reference to this exporter. - */ - register( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { - - this.pluginCallbacks.push( callback ); - - } - - return this; - - } - - /** - * Unregisters a plugin callback. - * - * @param {Function} callback - The callback function to unregister. - * @return {GLTFExporter} A reference to this exporter. - */ - unregister( callback ) { - - if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { - - this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 ); - - } - - return this; - - } - - /** - * Sets the texture utils for this exporter. Only relevant when compressed textures have to be exported. - * - * Depending on whether you use {@link WebGLRenderer} or {@link WebGPURenderer}, you must inject the - * corresponding texture utils {@link module:WebGLTextureUtils} or {@link module:WebGPUTextureUtils}. - * - * @param {WebGLTextureUtils|WebGPUTextureUtils} utils - The texture utils. - * @return {GLTFExporter} A reference to this exporter. - */ - setTextureUtils( utils ) { - - this.textureUtils = utils; - - return this; - - } - - /** - * Parses the given scenes and generates the glTF output. - * - * @param {Scene|Array<Scene>} input - A scene or an array of scenes. - * @param {GLTFExporter~OnDone} onDone - A callback function that is executed when the export has finished. - * @param {GLTFExporter~OnError} onError - A callback function that is executed when an error happens. - * @param {GLTFExporter~Options} options - options - */ - parse( input, onDone, onError, options ) { - - const writer = new GLTFWriter(); - const plugins = []; - - for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) { - - plugins.push( this.pluginCallbacks[ i ]( writer ) ); - - } - - writer.setPlugins( plugins ); - writer.setTextureUtils( this.textureUtils ); - writer.writeAsync( input, onDone, options ).catch( onError ); - - } - - /** - * Async version of {@link GLTFExporter#parse}. - * - * @param {Scene|Array<Scene>} input - A scene or an array of scenes. - * @param {GLTFExporter~Options} options - options. - * @return {Promise<ArrayBuffer|string>} A Promise that resolved with the exported glTF data. - */ - parseAsync( input, options ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.parse( input, resolve, reject, options ); - - } ); - - } - -} - -//------------------------------------------------------------------------------ -// Constants -//------------------------------------------------------------------------------ - -const WEBGL_CONSTANTS = { - POINTS: 0x0000, - LINES: 0x0001, - LINE_LOOP: 0x0002, - LINE_STRIP: 0x0003, - TRIANGLES: 0x0004, - TRIANGLE_STRIP: 0x0005, - TRIANGLE_FAN: 0x0006, - - BYTE: 0x1400, - UNSIGNED_BYTE: 0x1401, - SHORT: 0x1402, - UNSIGNED_SHORT: 0x1403, - INT: 0x1404, - UNSIGNED_INT: 0x1405, - FLOAT: 0x1406, - - ARRAY_BUFFER: 0x8892, - ELEMENT_ARRAY_BUFFER: 0x8893, - - NEAREST: 0x2600, - LINEAR: 0x2601, - NEAREST_MIPMAP_NEAREST: 0x2700, - LINEAR_MIPMAP_NEAREST: 0x2701, - NEAREST_MIPMAP_LINEAR: 0x2702, - LINEAR_MIPMAP_LINEAR: 0x2703, - - CLAMP_TO_EDGE: 33071, - MIRRORED_REPEAT: 33648, - REPEAT: 10497 -}; - -const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization'; - -const THREE_TO_WEBGL = {}; - -THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST; -THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST; -THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR; -THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR; -THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST; -THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR; - -THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE; -THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT; -THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT; - -const PATH_PROPERTIES = { - scale: 'scale', - position: 'translation', - quaternion: 'rotation', - morphTargetInfluences: 'weights' -}; - -const DEFAULT_SPECULAR_COLOR = new Color(); - -// GLB constants -// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - -const GLB_HEADER_BYTES = 12; -const GLB_HEADER_MAGIC = 0x46546C67; -const GLB_VERSION = 2; - -const GLB_CHUNK_PREFIX_BYTES = 8; -const GLB_CHUNK_TYPE_JSON = 0x4E4F534A; -const GLB_CHUNK_TYPE_BIN = 0x004E4942; - -//------------------------------------------------------------------------------ -// Utility functions -//------------------------------------------------------------------------------ - -/** - * Compare two arrays - * - * @private - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {boolean} Returns true if both arrays are equal - */ -function equalArray( array1, array2 ) { - - return ( array1.length === array2.length ) && array1.every( function ( element, index ) { - - return element === array2[ index ]; - - } ); - -} - -/** - * Converts a string to an ArrayBuffer. - * - * @private - * @param {string} text - * @return {ArrayBuffer} - */ -function stringToArrayBuffer( text ) { - - return new TextEncoder().encode( text ).buffer; - -} - -/** - * Is identity matrix - * - * @private - * @param {Matrix4} matrix - * @returns {boolean} Returns true, if parameter is identity matrix - */ -function isIdentityMatrix( matrix ) { - - return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ); - -} - -/** - * Get the min and max vectors from the given attribute - * - * @private - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {number} start Start index - * @param {number} count Range to cover - * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) - */ -function getMinMax( attribute, start, count ) { - - const output = { - - min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), - max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) - - }; - - for ( let i = start; i < start + count; i ++ ) { - - for ( let a = 0; a < attribute.itemSize; a ++ ) { - - let value; - - if ( attribute.itemSize > 4 ) { - - // no support for interleaved data for itemSize > 4 - - value = attribute.array[ i * attribute.itemSize + a ]; - - } else { - - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); - - if ( attribute.normalized === true ) { - - value = MathUtils.normalize( value, attribute.array ); - - } - - } - - output.min[ a ] = Math.min( output.min[ a ], value ); - output.max[ a ] = Math.max( output.max[ a ], value ); - - } - - } - - return output; - -} - -/** - * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - * - * @private - * @param {number} bufferSize The size the original buffer. Should be an integer. - * @returns {number} new buffer size with required padding as an integer. - * - */ -function getPaddedBufferSize( bufferSize ) { - - return Math.ceil( bufferSize / 4 ) * 4; - -} - -/** - * Returns a buffer aligned to 4-byte boundary. - * - * @private - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {number} [paddingByte=0] Should be an integer - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer - */ -function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { - - const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); - - if ( paddedLength !== arrayBuffer.byteLength ) { - - const array = new Uint8Array( paddedLength ); - array.set( new Uint8Array( arrayBuffer ) ); - - if ( paddingByte !== 0 ) { - - for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { - - array[ i ] = paddingByte; - - } - - } - - return array.buffer; - - } - - return arrayBuffer; - -} - -function getCanvas() { - - if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) { - - return new OffscreenCanvas( 1, 1 ); - - } - - return document.createElement( 'canvas' ); - -} - -function getToBlobPromise( canvas, mimeType ) { - - if ( typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas ) { - - let quality; - - // Blink's implementation of convertToBlob seems to default to a quality level of 100% - // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. - if ( mimeType === 'image/jpeg' ) { - - quality = 0.92; - - } else if ( mimeType === 'image/webp' ) { - - quality = 0.8; - - } - - return canvas.convertToBlob( { - - type: mimeType, - quality: quality - - } ); - - } else { - - // HTMLCanvasElement code path - - return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) ); - - } - -} - -/** - * Writer - * - * @private - */ -class GLTFWriter { - - constructor() { - - this.plugins = []; - - this.options = {}; - this.pending = []; - this.buffers = []; - - this.byteOffset = 0; - this.buffers = []; - this.nodeMap = new Map(); - this.skins = []; - - this.extensionsUsed = {}; - this.extensionsRequired = {}; - - this.uids = new Map(); - this.uid = 0; - - this.json = { - asset: { - version: '2.0', - generator: 'THREE.GLTFExporter r' + REVISION - } - }; - - this.cache = { - meshes: new Map(), - attributes: new Map(), - attributesNormalized: new Map(), - materials: new Map(), - textures: new Map(), - images: new Map(), - normalMaps: new Map() - }; - - this.textureUtils = null; - - } - - setPlugins( plugins ) { - - this.plugins = plugins; - - } - - setTextureUtils( utils ) { - - this.textureUtils = utils; - - } - - /** - * Parse scenes and generate GLTF output - * - * @param {Scene|Array<Scene>} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - async writeAsync( input, onDone, options = {} ) { - - this.options = Object.assign( { - // default options - binary: false, - trs: false, - onlyVisible: true, - maxTextureSize: Infinity, - animations: [], - includeCustomExtensions: false, - copyright: null - }, options ); - - if ( this.options.animations.length > 0 ) { - - // Only TRS properties, and not matrices, may be targeted by animation. - this.options.trs = true; - - } - - await this.processInputAsync( input ); - - await Promise.all( this.pending ); - - const writer = this; - const buffers = writer.buffers; - const json = writer.json; - options = writer.options; - - const extensionsUsed = writer.extensionsUsed; - const extensionsRequired = writer.extensionsRequired; - - // Merge buffers. - const blob = new Blob( buffers, { type: 'application/octet-stream' } ); - - // Declare extensions. - const extensionsUsedList = Object.keys( extensionsUsed ); - const extensionsRequiredList = Object.keys( extensionsRequired ); - - if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList; - if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList; - - // Update bytelength of the single buffer. - if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; - - if ( options.copyright ) json.asset.copyright = options.copyright; - - if ( options.binary === true ) { - - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { - - // Binary chunk. - const binaryChunk = getPaddedArrayBuffer( reader.result ); - const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); - binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); - - // JSON chunk. - const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 ); - const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); - jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); - - // GLB header. - const header = new ArrayBuffer( GLB_HEADER_BYTES ); - const headerView = new DataView( header ); - headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); - headerView.setUint32( 4, GLB_VERSION, true ); - const totalByteLength = GLB_HEADER_BYTES - + jsonChunkPrefix.byteLength + jsonChunk.byteLength - + binaryChunkPrefix.byteLength + binaryChunk.byteLength; - headerView.setUint32( 8, totalByteLength, true ); - - const glbBlob = new Blob( [ - header, - jsonChunkPrefix, - jsonChunk, - binaryChunkPrefix, - binaryChunk - ], { type: 'application/octet-stream' } ); - - const glbReader = new FileReader(); - glbReader.readAsArrayBuffer( glbBlob ); - glbReader.onloadend = function () { - - onDone( glbReader.result ); - - }; - - }; - - } else { - - if ( json.buffers && json.buffers.length > 0 ) { - - const reader = new FileReader(); - reader.readAsDataURL( blob ); - reader.onloadend = function () { - - const base64data = reader.result; - json.buffers[ 0 ].uri = base64data; - onDone( json ); - - }; - - } else { - - onDone( json ); - - } - - } - - - } - - /** - * Serializes a userData. - * - * @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry|THREE.AnimationClip} object - * @param {Object} objectDef - */ - serializeUserData( object, objectDef ) { - - if ( Object.keys( object.userData ).length === 0 ) return; - - const options = this.options; - const extensionsUsed = this.extensionsUsed; - - try { - - const json = JSON.parse( JSON.stringify( object.userData ) ); - - if ( options.includeCustomExtensions && json.gltfExtensions ) { - - if ( objectDef.extensions === undefined ) objectDef.extensions = {}; - - for ( const extensionName in json.gltfExtensions ) { - - objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ]; - extensionsUsed[ extensionName ] = true; - - } - - delete json.gltfExtensions; - - } - - if ( Object.keys( json ).length > 0 ) objectDef.extras = json; - - } catch ( error ) { - - console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + - 'won\'t be serialized because of JSON.stringify error - ' + error.message ); - - } - - } - - /** - * Returns ids for buffer attributes. - * - * @param {Object} attribute - * @param {boolean} [isRelativeCopy=false] - * @return {number} An integer - */ - getUID( attribute, isRelativeCopy = false ) { - - if ( this.uids.has( attribute ) === false ) { - - const uids = new Map(); - - uids.set( true, this.uid ++ ); - uids.set( false, this.uid ++ ); - - this.uids.set( attribute, uids ); - - } - - const uids = this.uids.get( attribute ); - - return uids.get( isRelativeCopy ); - - } - - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {boolean} - */ - isNormalizedNormalAttribute( normal ) { - - const cache = this.cache; - - if ( cache.attributesNormalized.has( normal ) ) return false; - - const v = new Vector3(); - - for ( let i = 0, il = normal.count; i < il; i ++ ) { - - // 0.0005 is from glTF-validator - if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false; - - } - - return true; - - } - - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - createNormalizedNormalAttribute( normal ) { - - const cache = this.cache; - - if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal ); - - const attribute = normal.clone(); - const v = new Vector3(); - - for ( let i = 0, il = attribute.count; i < il; i ++ ) { - - v.fromBufferAttribute( attribute, i ); - - if ( v.x === 0 && v.y === 0 && v.z === 0 ) { - - // if values can't be normalized set (1, 0, 0) - v.setX( 1.0 ); - - } else { - - v.normalize(); - - } - - attribute.setXYZ( i, v.x, v.y, v.z ); - - } - - cache.attributesNormalized.set( normal, attribute ); - - return attribute; - - } - - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - * - * @param {Object} mapDef - * @param {THREE.Texture} texture - */ - applyTextureTransform( mapDef, texture ) { - - let didTransform = false; - const transformDef = {}; - - if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { - - transformDef.offset = texture.offset.toArray(); - didTransform = true; - - } - - if ( texture.rotation !== 0 ) { - - transformDef.rotation = texture.rotation; - didTransform = true; - - } - - if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) { - - transformDef.scale = texture.repeat.toArray(); - didTransform = true; - - } - - if ( didTransform ) { - - mapDef.extensions = mapDef.extensions || {}; - mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; - this.extensionsUsed[ 'KHR_texture_transform' ] = true; - - } - - } - - async buildMetalRoughTextureAsync( metalnessMap, roughnessMap ) { - - if ( metalnessMap === roughnessMap ) return metalnessMap; - - function getEncodingConversion( map ) { - - if ( map.colorSpace === SRGBColorSpace ) { - - return function SRGBToLinear( c ) { - - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - - }; - - } - - return function LinearToLinear( c ) { - - return c; - - }; - - } - - if ( metalnessMap instanceof CompressedTexture ) { - - metalnessMap = await this.decompressTextureAsync( metalnessMap ); - - } - - if ( roughnessMap instanceof CompressedTexture ) { - - roughnessMap = await this.decompressTextureAsync( roughnessMap ); - - } - - const metalness = metalnessMap ? metalnessMap.image : null; - const roughness = roughnessMap ? roughnessMap.image : null; - - const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 ); - const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 ); - - const canvas = getCanvas(); - canvas.width = width; - canvas.height = height; - - const context = canvas.getContext( '2d', { - willReadFrequently: true, - } ); - context.fillStyle = '#00ffff'; - context.fillRect( 0, 0, width, height ); - - const composite = context.getImageData( 0, 0, width, height ); - - if ( metalness ) { - - context.drawImage( metalness, 0, 0, width, height ); - - const convert = getEncodingConversion( metalnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; - - for ( let i = 2; i < data.length; i += 4 ) { - - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; - - } - - } - - if ( roughness ) { - - context.drawImage( roughness, 0, 0, width, height ); - - const convert = getEncodingConversion( roughnessMap ); - const data = context.getImageData( 0, 0, width, height ).data; - - for ( let i = 1; i < data.length; i += 4 ) { - - composite.data[ i ] = convert( data[ i ] / 256 ) * 256; - - } - - } - - context.putImageData( composite, 0, 0 ); - - // - - const reference = metalnessMap || roughnessMap; - - const texture = reference.clone(); - - texture.source = new TextureSource( canvas ); - texture.colorSpace = NoColorSpace; - texture.channel = ( metalnessMap || roughnessMap ).channel; - - if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) { - - console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' ); - - } - - console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); - - return texture; - - } - - - /** - * Builds a copy of the given normal map with the red and/or green channels - * inverted (`color = 255 - color`). This is used to bake the sign of - * `material.normalScale` and the tangent-space convention into the texture, - * since glTF only supports OpenGL-style normal maps with a univariate, - * positive scale. - * - * @param {THREE.Texture} normalMap The source normal map. - * @param {boolean} flipX Whether to invert the red channel (normal X). - * @param {boolean} flipY Whether to invert the green channel (normal Y). - * @return {Promise<THREE.Texture>} The derived normal map texture. - */ - async buildNormalMapTextureAsync( normalMap, flipX, flipY ) { - - if ( normalMap instanceof CompressedTexture ) { - - normalMap = await this.decompressTextureAsync( normalMap ); - - } - - const image = normalMap.image; - - const canvas = getCanvas(); - canvas.width = image.width; - canvas.height = image.height; - - const context = canvas.getContext( '2d', { - willReadFrequently: true, - } ); - - context.drawImage( image, 0, 0, canvas.width, canvas.height ); - - const imageData = context.getImageData( 0, 0, canvas.width, canvas.height ); - const data = imageData.data; - - for ( let i = 0; i < data.length; i += 4 ) { - - if ( flipX ) data[ i + 0 ] = 255 - data[ i + 0 ]; - if ( flipY ) data[ i + 1 ] = 255 - data[ i + 1 ]; - - } - - context.putImageData( imageData, 0, 0 ); - - const texture = normalMap.clone(); - texture.source = new TextureSource( canvas ); - - return texture; - - } - - async decompressTextureAsync( texture, maxTextureSize = Infinity ) { - - if ( this.textureUtils === null ) { - - throw new Error( 'THREE.GLTFExporter: setTextureUtils() must be called to process compressed textures.' ); - - } - - return await this.textureUtils.decompress( texture, maxTextureSize ); - - } - - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {0} - */ - processBuffer( buffer ) { - - const json = this.json; - const buffers = this.buffers; - - if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ]; - - // All buffers are merged before export. - buffers.push( buffer ); - - return 0; - - } - - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} [target] Target usage of the BufferView - * @return {Object} - */ - processBufferView( attribute, componentType, start, count, target ) { - - const json = this.json; - - if ( ! json.bufferViews ) json.bufferViews = []; - - // Create a new dataview and dump the attribute's array into it - - let componentSize; - - switch ( componentType ) { - - case WEBGL_CONSTANTS.BYTE: - case WEBGL_CONSTANTS.UNSIGNED_BYTE: - - componentSize = 1; - - break; - - case WEBGL_CONSTANTS.SHORT: - case WEBGL_CONSTANTS.UNSIGNED_SHORT: - - componentSize = 2; - - break; - - default: - - componentSize = 4; - - } - - let byteStride = attribute.itemSize * componentSize; - - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { - - // Each element of a vertex attribute MUST be aligned to 4-byte boundaries - // inside a bufferView - byteStride = Math.ceil( byteStride / 4 ) * 4; - - } - - const byteLength = getPaddedBufferSize( count * byteStride ); - const dataView = new DataView( new ArrayBuffer( byteLength ) ); - let offset = 0; - - for ( let i = start; i < start + count; i ++ ) { - - for ( let a = 0; a < attribute.itemSize; a ++ ) { - - let value; - - if ( attribute.itemSize > 4 ) { - - // no support for interleaved data for itemSize > 4 - - value = attribute.array[ i * attribute.itemSize + a ]; - - } else { - - if ( a === 0 ) value = attribute.getX( i ); - else if ( a === 1 ) value = attribute.getY( i ); - else if ( a === 2 ) value = attribute.getZ( i ); - else if ( a === 3 ) value = attribute.getW( i ); - - if ( attribute.normalized === true ) { - - value = MathUtils.normalize( value, attribute.array ); - - } - - } - - if ( componentType === WEBGL_CONSTANTS.FLOAT ) { - - dataView.setFloat32( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.INT ) { - - dataView.setInt32( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { - - dataView.setUint32( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.SHORT ) { - - dataView.setInt16( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - - dataView.setUint16( offset, value, true ); - - } else if ( componentType === WEBGL_CONSTANTS.BYTE ) { - - dataView.setInt8( offset, value ); - - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - - dataView.setUint8( offset, value ); - - } - - offset += componentSize; - - } - - if ( ( offset % byteStride ) !== 0 ) { - - offset += byteStride - ( offset % byteStride ); - - } - - } - - const bufferViewDef = { - - buffer: this.processBuffer( dataView.buffer ), - byteOffset: this.byteOffset, - byteLength: byteLength - - }; - - if ( target !== undefined ) bufferViewDef.target = target; - - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { - - // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = byteStride; - - } - - this.byteOffset += byteLength; - - json.bufferViews.push( bufferViewDef ); - - // @TODO Merge bufferViews where possible. - const output = { - - id: json.bufferViews.length - 1, - byteLength: 0 - - }; - - return output; - - } - - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise<number>} An integer - */ - processBufferViewImage( blob ) { - - const writer = this; - const json = writer.json; - - if ( ! json.bufferViews ) json.bufferViews = []; - - return new Promise( function ( resolve ) { - - const reader = new FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { - - const buffer = getPaddedArrayBuffer( reader.result ); - - const bufferViewDef = { - buffer: writer.processBuffer( buffer ), - byteOffset: writer.byteOffset, - byteLength: buffer.byteLength - }; - - writer.byteOffset += buffer.byteLength; - resolve( json.bufferViews.push( bufferViewDef ) - 1 ); - - }; - - } ); - - } - - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {?BufferGeometry} [geometry] Geometry used for truncated draw range - * @param {number} [start=0] - * @param {number} [count=Infinity] - * @return {?number} Index of the processed accessor on the "accessors" array - */ - processAccessor( attribute, geometry, start, count ) { - - const json = this.json; - - const types = { - - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 9: 'MAT3', - 16: 'MAT4' - - }; - - let componentType; - - // Detect the component type of the attribute array - if ( attribute.array.constructor === Float32Array ) { - - componentType = WEBGL_CONSTANTS.FLOAT; - - } else if ( attribute.array.constructor === Int32Array ) { - - componentType = WEBGL_CONSTANTS.INT; - - } else if ( attribute.array.constructor === Uint32Array ) { - - componentType = WEBGL_CONSTANTS.UNSIGNED_INT; - - } else if ( attribute.array.constructor === Int16Array ) { - - componentType = WEBGL_CONSTANTS.SHORT; - - } else if ( attribute.array.constructor === Uint16Array ) { - - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; - - } else if ( attribute.array.constructor === Int8Array ) { - - componentType = WEBGL_CONSTANTS.BYTE; - - } else if ( attribute.array.constructor === Uint8Array ) { - - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; - - } else { - - throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name ); - - } - - if ( start === undefined ) start = 0; - if ( count === undefined || count === Infinity ) count = attribute.count; - - // Skip creating an accessor if the attribute doesn't have data to export - if ( count === 0 ) return null; - - const minMax = getMinMax( attribute, start, count ); - let bufferViewTarget; - - // If geometry isn't provided, don't infer the target usage of the bufferView. For - // animation samplers, target must not be set. - if ( geometry !== undefined ) { - - bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; - - } - - const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget ); - - const accessorDef = { - - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[ attribute.itemSize ] - - }; - - if ( attribute.normalized === true ) accessorDef.normalized = true; - if ( ! json.accessors ) json.accessors = []; - - return json.accessors.push( accessorDef ) - 1; - - } - - /** - * Process image - * @param {Image} image to process - * @param {number} format Identifier of the format (RGBAFormat) - * @param {boolean} flipY before writing out the image - * @param {string} mimeType export format - * @return {number} Index of the processed texture in the "images" array - */ - processImage( image, format, flipY, mimeType = 'image/png' ) { - - if ( image !== null ) { - - const writer = this; - const cache = writer.cache; - const json = writer.json; - const options = writer.options; - const pending = writer.pending; - - if ( ! cache.images.has( image ) ) cache.images.set( image, {} ); - - const cachedImages = cache.images.get( image ); - - const key = mimeType + ':flipY/' + flipY.toString(); - - if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ]; - - if ( ! json.images ) json.images = []; - - const imageDef = { mimeType: mimeType }; - - const canvas = getCanvas(); - - canvas.width = Math.min( image.width, options.maxTextureSize ); - canvas.height = Math.min( image.height, options.maxTextureSize ); - - const ctx = canvas.getContext( '2d', { - willReadFrequently: true, - } ); - - if ( flipY === true ) { - - ctx.translate( 0, canvas.height ); - ctx.scale( 1, - 1 ); - - } - - if ( image.data !== undefined ) { // THREE.DataTexture - - if ( format !== RGBAFormat ) { - - console.error( 'GLTFExporter: Only RGBAFormat is supported.', format ); - - } - - if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) { - - console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image ); - - } - - const data = new Uint8ClampedArray( image.height * image.width * 4 ); - - for ( let i = 0; i < data.length; i += 4 ) { - - data[ i + 0 ] = image.data[ i + 0 ]; - data[ i + 1 ] = image.data[ i + 1 ]; - data[ i + 2 ] = image.data[ i + 2 ]; - data[ i + 3 ] = image.data[ i + 3 ]; - - } - - ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 ); - - } else { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || - ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ) { - - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); - - } else { - - throw new Error( 'THREE.GLTFExporter: Invalid image type. Use HTMLImageElement, HTMLCanvasElement, ImageBitmap or OffscreenCanvas.' ); - - } - - } - - if ( options.binary === true ) { - - pending.push( - - getToBlobPromise( canvas, mimeType ) - .then( blob => writer.processBufferViewImage( blob ) ) - .then( bufferViewIndex => { - - imageDef.bufferView = bufferViewIndex; - - } ) - - ); - - } else { - - imageDef.uri = ImageUtils.getDataURL( canvas, mimeType ); - - } - - const index = json.images.push( imageDef ) - 1; - cachedImages[ key ] = index; - return index; - - } else { - - throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' ); - - } - - } - - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {number} Index of the processed texture in the "samplers" array - */ - processSampler( map ) { - - const json = this.json; - - if ( ! json.samplers ) json.samplers = []; - - const samplerDef = { - magFilter: THREE_TO_WEBGL[ map.magFilter ], - minFilter: THREE_TO_WEBGL[ map.minFilter ], - wrapS: THREE_TO_WEBGL[ map.wrapS ], - wrapT: THREE_TO_WEBGL[ map.wrapT ] - }; - - return json.samplers.push( samplerDef ) - 1; - - } - - /** - * Process texture - * @param {Texture} map Map to process - * @return {Promise<number>} Index of the processed texture in the "textures" array - */ - async processTextureAsync( map ) { - - const writer = this; - const options = writer.options; - const cache = this.cache; - const json = this.json; - - if ( cache.textures.has( map ) ) return cache.textures.get( map ); - - if ( ! json.textures ) json.textures = []; - - // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture - if ( map instanceof CompressedTexture ) { - - map = await this.decompressTextureAsync( map, options.maxTextureSize ); - - } - - const mimeType = map.userData.mimeType; - - const imageIndex = this.processImage( map.image, map.format, map.flipY, mimeType ); - - const textureDef = { - sampler: this.processSampler( map ) - }; - - if ( mimeType === 'image/webp' ) { - - textureDef.extensions = textureDef.extensions || {}; - textureDef.extensions[ 'EXT_texture_webp' ] = { - source: imageIndex - }; - - this.extensionsUsed[ 'EXT_texture_webp' ] = true; - this.extensionsRequired[ 'EXT_texture_webp' ] = true; - - } else { - - textureDef.source = imageIndex; - - } - - if ( map.name ) textureDef.name = map.name; - - await this._invokeAllAsync( async function ( ext ) { - - ext.writeTexture && await ext.writeTexture( map, textureDef ); - - } ); - - const index = json.textures.push( textureDef ) - 1; - cache.textures.set( map, index ); - return index; - - } - - /** - * Process material - * @param {THREE.Material} material Material to process - * @param {THREE.BufferGeometry} [geometry] Geometry the material is used with. - * @return {Promise<?number>} Index of the processed material in the "materials" array - */ - async processMaterialAsync( material, geometry ) { - - const cache = this.cache; - const json = this.json; - - // Whether the geometry provides explicit tangents. The exported normal map depends on - // this, so it is part of the material cache key. - const hasTangent = geometry !== undefined && geometry.hasAttribute( 'tangent' ); - const cacheKey = material.normalMap ? material.uuid + ':' + hasTangent : material.uuid; - - if ( cache.materials.has( cacheKey ) ) return cache.materials.get( cacheKey ); - - if ( material.isShaderMaterial ) { - - console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); - return null; - - } - - if ( ! json.materials ) json.materials = []; - - // @QUESTION Should we avoid including any attribute that has the default value? - const materialDef = { pbrMetallicRoughness: {} }; - - if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) { - - console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); - - } - - // pbrMetallicRoughness.baseColorFactor - const color = material.color.toArray().concat( [ material.opacity ] ); - - if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { - - materialDef.pbrMetallicRoughness.baseColorFactor = color; - - } - - if ( material.isMeshStandardMaterial ) { - - materialDef.pbrMetallicRoughness.metallicFactor = material.metalness; - materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness; - - } else { - - materialDef.pbrMetallicRoughness.metallicFactor = 0; - materialDef.pbrMetallicRoughness.roughnessFactor = 1; - - } - - // pbrMetallicRoughness.metallicRoughnessTexture - if ( material.metalnessMap || material.roughnessMap ) { - - const metalRoughTexture = await this.buildMetalRoughTextureAsync( material.metalnessMap, material.roughnessMap ); - - const metalRoughMapDef = { - index: await this.processTextureAsync( metalRoughTexture ), - texCoord: metalRoughTexture.channel - }; - this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); - materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; - - } - - // pbrMetallicRoughness.baseColorTexture - if ( material.map ) { - - const baseColorMapDef = { - index: await this.processTextureAsync( material.map ), - texCoord: material.map.channel - }; - this.applyTextureTransform( baseColorMapDef, material.map ); - materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; - - } - - if ( material.emissive ) { - - const emissive = material.emissive; - const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b ); - - if ( maxEmissiveComponent > 0 ) { - - materialDef.emissiveFactor = material.emissive.toArray(); - - } - - // emissiveTexture - if ( material.emissiveMap ) { - - const emissiveMapDef = { - index: await this.processTextureAsync( material.emissiveMap ), - texCoord: material.emissiveMap.channel - }; - this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); - materialDef.emissiveTexture = emissiveMapDef; - - } - - } - - // normalTexture - if ( material.normalMap ) { - - const normalScale = material.normalScale; - - // glTF only supports OpenGL-style normal maps with a univariate, positive scale. - // A negative `normalScale` component is baked into the texture by inverting the - // corresponding channel. Meshes without explicit tangents use the opposite - // green-channel convention, so the green channel is inverted in that case too. - // - // The no-tangent green flip is the counterpart of GLTFLoader, which negates - // `normalScale.y` on import for the same case. - const flipX = normalScale.x < 0; - const flipY = hasTangent ? normalScale.y < 0 : normalScale.y > 0; - - let normalMap = material.normalMap; - - if ( flipX || flipY ) { - - if ( cache.normalMaps.has( material.normalMap ) === false ) cache.normalMaps.set( material.normalMap, {} ); - - const cachedVariants = cache.normalMaps.get( material.normalMap ); - const cacheKey = `${flipX}:${flipY}`; - - if ( cachedVariants[ cacheKey ] === undefined ) { - - cachedVariants[ cacheKey ] = await this.buildNormalMapTextureAsync( material.normalMap, flipX, flipY ); - - } - - normalMap = cachedVariants[ cacheKey ]; - - } - - const normalMapDef = { - index: await this.processTextureAsync( normalMap ), - texCoord: material.normalMap.channel - }; - - if ( Math.abs( normalScale.x ) !== 1 ) { - - // glTF normal scale is univariate. The magnitude of `x` is used; the sign of - // both components has already been baked into the texture above. - normalMapDef.scale = Math.abs( normalScale.x ); - - } - - this.applyTextureTransform( normalMapDef, material.normalMap ); - materialDef.normalTexture = normalMapDef; - - } - - // occlusionTexture - if ( material.aoMap ) { - - const occlusionMapDef = { - index: await this.processTextureAsync( material.aoMap ), - texCoord: material.aoMap.channel - }; - - if ( material.aoMapIntensity !== 1.0 ) { - - occlusionMapDef.strength = material.aoMapIntensity; - - } - - this.applyTextureTransform( occlusionMapDef, material.aoMap ); - materialDef.occlusionTexture = occlusionMapDef; - - } - - // alphaMode - if ( material.transparent ) { - - materialDef.alphaMode = 'BLEND'; - - } else { - - if ( material.alphaTest > 0.0 ) { - - materialDef.alphaMode = 'MASK'; - materialDef.alphaCutoff = material.alphaTest; - - } - - } - - // doubleSided - if ( material.side === DoubleSide ) materialDef.doubleSided = true; - if ( material.name !== '' ) materialDef.name = material.name; - - this.serializeUserData( material, materialDef ); - - await this._invokeAllAsync( async function ( ext ) { - - ext.writeMaterialAsync && await ext.writeMaterialAsync( material, materialDef ); - - } ); - - const index = json.materials.push( materialDef ) - 1; - cache.materials.set( cacheKey, index ); - return index; - - } - - /** - * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Promise<?number>} Index of the processed mesh in the "meshes" array - */ - async processMeshAsync( mesh ) { - - const cache = this.cache; - const json = this.json; - - const meshCacheKeyParts = [ mesh.geometry.uuid ]; - - if ( Array.isArray( mesh.material ) ) { - - for ( let i = 0, l = mesh.material.length; i < l; i ++ ) { - - meshCacheKeyParts.push( mesh.material[ i ].uuid ); - - } - - } else { - - meshCacheKeyParts.push( mesh.material.uuid ); - - } - - const meshCacheKey = meshCacheKeyParts.join( ':' ); - - if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey ); - - const geometry = mesh.geometry; - - let mode; - - // Use the correct mode - if ( mesh.isLineSegments ) { - - mode = WEBGL_CONSTANTS.LINES; - - } else if ( mesh.isLineLoop ) { - - mode = WEBGL_CONSTANTS.LINE_LOOP; - - } else if ( mesh.isLine ) { - - mode = WEBGL_CONSTANTS.LINE_STRIP; - - } else if ( mesh.isPoints ) { - - mode = WEBGL_CONSTANTS.POINTS; - - } else { - - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; - - } - - const meshDef = {}; - const attributes = {}; - const primitives = []; - const targets = []; - - // Conversion between attributes names in threejs and gltf spec - const nameConversion = { - uv: 'TEXCOORD_0', - uv1: 'TEXCOORD_1', - uv2: 'TEXCOORD_2', - uv3: 'TEXCOORD_3', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0' - }; - - const originalNormal = geometry.getAttribute( 'normal' ); - - if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) { - - console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); - - geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) ); - - } - - // @QUESTION Detect if .vertexColors = true? - // For every attribute create an accessor - let modifiedAttribute = null; - - for ( let attributeName in geometry.attributes ) { - - // Ignore morph target attributes, which are exported later. - if ( attributeName.slice( 0, 5 ) === 'morph' ) continue; - - const attribute = geometry.attributes[ attributeName ]; - attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); - - // Prefix all geometry attributes except the ones specifically - // listed in the spec; non-spec attributes are considered custom. - const validVertexAttributes = - /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/; - - if ( ! validVertexAttributes.test( attributeName ) && ! attributeName.startsWith( '_' ) ) attributeName = '_' + attributeName; - - if ( cache.attributes.has( this.getUID( attribute ) ) ) { - - attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) ); - continue; - - } - - // Enforce glTF vertex attribute requirements: - // - JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT - // - Only custom attributes may be INT or UNSIGNED_INT - modifiedAttribute = null; - const array = attribute.array; - - if ( attributeName === 'JOINTS_0' && - ! ( array instanceof Uint16Array ) && - ! ( array instanceof Uint8Array ) ) { - - console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); - modifiedAttribute = GLTFExporter.Utils.toTypedBufferAttribute( attribute, Uint16Array ); - - } else if ( ( array instanceof Uint32Array || array instanceof Int32Array ) && ! attributeName.startsWith( '_' ) ) { - - console.warn( `GLTFExporter: Attribute "${ attributeName }" converted to type FLOAT.` ); - modifiedAttribute = GLTFExporter.Utils.toTypedBufferAttribute( attribute, Float32Array ); - - } - - const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); - - if ( accessor !== null ) { - - if ( ! attributeName.startsWith( '_' ) ) { - - this.detectMeshQuantization( attributeName, attribute ); - - } - - attributes[ attributeName ] = accessor; - cache.attributes.set( this.getUID( attribute ), accessor ); - - } - - } - - if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal ); - - // Skip if no exportable attributes found - if ( Object.keys( attributes ).length === 0 ) return null; - - // Morph targets - if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { - - const weights = []; - const targetNames = []; - const reverseDictionary = {}; - - if ( mesh.morphTargetDictionary !== undefined ) { - - for ( const key in mesh.morphTargetDictionary ) { - - reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; - - } - - } - - for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - - const target = {}; - let warned = false; - - for ( const attributeName in geometry.morphAttributes ) { - - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. - - if ( attributeName !== 'position' && attributeName !== 'normal' ) { - - if ( ! warned ) { - - console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); - warned = true; - - } - - continue; - - } - - const attribute = geometry.morphAttributes[ attributeName ][ i ]; - const gltfAttributeName = attributeName.toUpperCase(); - - // Three.js morph attribute has absolute values while the one of glTF has relative values. - // - // glTF 2.0 Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets - - const baseAttribute = geometry.attributes[ attributeName ]; - - if ( cache.attributes.has( this.getUID( attribute, true ) ) ) { - - target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) ); - continue; - - } - - // Clones attribute not to override - const relativeAttribute = attribute.clone(); - - if ( ! geometry.morphTargetsRelative ) { - - for ( let j = 0, jl = attribute.count; j < jl; j ++ ) { - - for ( let a = 0; a < attribute.itemSize; a ++ ) { - - if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) ); - if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) ); - if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) ); - if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) ); - - } - - } - - } - - target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); - cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] ); - - } - - targets.push( target ); - - weights.push( mesh.morphTargetInfluences[ i ] ); - - if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); - - } - - meshDef.weights = weights; - - if ( targetNames.length > 0 ) { - - meshDef.extras = {}; - meshDef.extras.targetNames = targetNames; - - } - - } - - const isMultiMaterial = Array.isArray( mesh.material ); - - if ( isMultiMaterial && geometry.groups.length === 0 ) return null; - - let didForceIndices = false; - - if ( isMultiMaterial && geometry.index === null ) { - - const indices = []; - - for ( let i = 0, il = geometry.attributes.position.count; i < il; i ++ ) { - - indices[ i ] = i; - - } - - geometry.setIndex( indices ); - - didForceIndices = true; - - } - - const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const primitive = { - mode: mode, - attributes: attributes, - }; - - this.serializeUserData( geometry, primitive ); - - if ( targets.length > 0 ) primitive.targets = targets; - - if ( geometry.index !== null ) { - - let cacheKey = this.getUID( geometry.index ); - - if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) { - - cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count; - - } - - if ( cache.attributes.has( cacheKey ) ) { - - primitive.indices = cache.attributes.get( cacheKey ); - - } else { - - primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); - cache.attributes.set( cacheKey, primitive.indices ); - - } - - if ( primitive.indices === null ) delete primitive.indices; - - } - - const material = await this.processMaterialAsync( materials[ groups[ i ].materialIndex ], geometry ); - - if ( material !== null ) primitive.material = material; - - primitives.push( primitive ); - - } - - if ( didForceIndices === true ) { - - geometry.setIndex( null ); - - } - - meshDef.primitives = primitives; - - if ( ! json.meshes ) json.meshes = []; - - await this._invokeAllAsync( function ( ext ) { - - ext.writeMesh && ext.writeMesh( mesh, meshDef ); - - } ); - - const index = json.meshes.push( meshDef ) - 1; - cache.meshes.set( meshCacheKey, index ); - return index; - - } - - /** - * If a vertex attribute with a - * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) - * is used, it is checked whether it is a valid data type according to the - * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) - * extension. - * In this case the extension is automatically added to the list of used extensions. - * - * @param {string} attributeName - * @param {THREE.BufferAttribute} attribute - */ - detectMeshQuantization( attributeName, attribute ) { - - if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return; - - let attrType = undefined; - - switch ( attribute.array.constructor ) { - - case Int8Array: - - attrType = 'byte'; - - break; - - case Uint8Array: - - attrType = 'unsigned byte'; - - break; - - case Int16Array: - - attrType = 'short'; - - break; - - case Uint16Array: - - attrType = 'unsigned short'; - - break; - - default: - - return; - - } - - if ( attribute.normalized ) attrType += ' normalized'; - - const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ]; - - if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) { - - this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true; - this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true; - - } - - } - - /** - * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {number} Index of the processed mesh in the "camera" array - */ - processCamera( camera ) { - - const json = this.json; - - if ( ! json.cameras ) json.cameras = []; - - const isOrtho = camera.isOrthographicCamera; - - const cameraDef = { - type: isOrtho ? 'orthographic' : 'perspective' - }; - - if ( isOrtho ) { - - cameraDef.orthographic = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; - - } else { - - cameraDef.perspective = { - aspectRatio: camera.aspect, - yfov: MathUtils.degToRad( camera.fov ), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near - }; - - } - - // Question: Is saving "type" as name intentional? - if ( camera.name !== '' ) cameraDef.name = camera.type; - - return json.cameras.push( cameraDef ) - 1; - - } - - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {THREE.AnimationClip} clip - * @param {THREE.Object3D} root - * @return {?number} - */ - processAnimation( clip, root ) { - - const json = this.json; - const nodeMap = this.nodeMap; - - if ( ! json.animations ) json.animations = []; - - clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); - - const tracks = clip.tracks; - const channels = []; - const samplers = []; - - for ( let i = 0; i < tracks.length; ++ i ) { - - const track = tracks[ i ]; - const trackBinding = PropertyBinding.parseTrackName( track.name ); - let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); - const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - - if ( trackBinding.objectName === 'bones' ) { - - if ( trackNode.isSkinnedMesh === true ) { - - trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); - - } else { - - trackNode = undefined; - - } - - } - - if ( ! trackNode || ! trackProperty ) { - - console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); - continue; - - } - - const inputItemSize = 1; - let outputItemSize = track.values.length / track.times.length; - - if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { - - outputItemSize /= trackNode.morphTargetInfluences.length; - - } - - let interpolation; - - // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - - // Detecting glTF cubic spline interpolant by checking factory method's special property - // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return - // valid value from .getInterpolation(). - if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { - - interpolation = 'CUBICSPLINE'; - - // itemSize of CUBICSPLINE keyframe is 9 - // (VEC3 * 3: inTangent, splineVertex, and outTangent) - // but needs to be stored as VEC3 so dividing by 3 here. - outputItemSize /= 3; - - } else if ( track.getInterpolation() === InterpolateDiscrete ) { - - interpolation = 'STEP'; - - } else { - - interpolation = 'LINEAR'; - - } - - samplers.push( { - input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ), - output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ), - interpolation: interpolation - } ); - - channels.push( { - sampler: samplers.length - 1, - target: { - node: nodeMap.get( trackNode ), - path: trackProperty - } - } ); - - } - - const animationDef = { - name: clip.name || 'clip_' + json.animations.length, - samplers: samplers, - channels: channels - }; - - this.serializeUserData( clip, animationDef ); - - json.animations.push( animationDef ); - - return json.animations.length - 1; - - } - - /** - * @param {THREE.Object3D} object - * @return {?number} - */ - processSkin( object ) { - - const json = this.json; - const nodeMap = this.nodeMap; - - const node = json.nodes[ nodeMap.get( object ) ]; - - const skeleton = object.skeleton; - - if ( skeleton === undefined ) return null; - - const rootJoint = object.skeleton.bones[ 0 ]; - - if ( rootJoint === undefined ) return null; - - const joints = []; - const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); - const temporaryBoneInverse = new Matrix4(); - - for ( let i = 0; i < skeleton.bones.length; ++ i ) { - - joints.push( nodeMap.get( skeleton.bones[ i ] ) ); - temporaryBoneInverse.copy( skeleton.boneInverses[ i ] ); - temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 ); - - } - - if ( json.skins === undefined ) json.skins = []; - - json.skins.push( { - inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), - joints: joints, - skeleton: nodeMap.get( rootJoint ) - } ); - - const skinIndex = node.skin = json.skins.length - 1; - - return skinIndex; - - } - - /** - * Process Object3D node - * @param {THREE.Object3D} object Object3D to processNodeAsync - * @return {Promise<number>} Index of the node in the nodes list - */ - async processNodeAsync( object ) { - - const json = this.json; - const options = this.options; - const nodeMap = this.nodeMap; - - if ( ! json.nodes ) json.nodes = []; - - // Handle pivot by creating a container node - if ( object.pivot !== null ) { - - return await this._processNodeWithPivotAsync( object ); - - } - - const nodeDef = {}; - - if ( options.trs ) { - - const rotation = object.quaternion.toArray(); - const position = object.position.toArray(); - const scale = object.scale.toArray(); - - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - - nodeDef.rotation = rotation; - - } - - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - - nodeDef.translation = position; - - } - - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - - nodeDef.scale = scale; - - } - - } else { - - if ( object.matrixAutoUpdate ) { - - object.updateMatrix(); - - } - - if ( isIdentityMatrix( object.matrix ) === false ) { - - nodeDef.matrix = object.matrix.elements; - - } - - } - - // We don't export empty strings name because it represents no-name in Three.js. - if ( object.name !== '' ) nodeDef.name = String( object.name ); - - this.serializeUserData( object, nodeDef ); - - if ( object.isMesh || object.isLine || object.isPoints ) { - - const meshIndex = await this.processMeshAsync( object ); - - if ( meshIndex !== null ) nodeDef.mesh = meshIndex; - - } else if ( object.isCamera ) { - - nodeDef.camera = this.processCamera( object ); - - } - - if ( object.isSkinnedMesh ) this.skins.push( object ); - - const nodeIndex = json.nodes.push( nodeDef ) - 1; - nodeMap.set( object, nodeIndex ); - - if ( object.children.length > 0 ) { - - const children = []; - - for ( let i = 0, l = object.children.length; i < l; i ++ ) { - - const child = object.children[ i ]; - - if ( child.visible || options.onlyVisible === false ) { - - const childNodeIndex = await this.processNodeAsync( child ); - - if ( childNodeIndex !== null ) children.push( childNodeIndex ); - - } - - } - - if ( children.length > 0 ) nodeDef.children = children; - - } - - await this._invokeAllAsync( function ( ext ) { - - ext.writeNode && ext.writeNode( object, nodeDef ); - - } ); - - return nodeIndex; - - } - - /** - * Process Object3D node with pivot using container approach - * @param {THREE.Object3D} object Object3D with pivot - * @return {Promise<number>} Index of the container node - */ - async _processNodeWithPivotAsync( object ) { - - const json = this.json; - const options = this.options; - const nodeMap = this.nodeMap; - - const pivot = object.pivot; - - // Container node: holds position + pivot offset, rotation, scale - // Animations will target this node - const containerDef = {}; - - const rotation = object.quaternion.toArray(); - const position = [ - object.position.x + pivot.x, - object.position.y + pivot.y, - object.position.z + pivot.z - ]; - const scale = object.scale.toArray(); - - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - - containerDef.rotation = rotation; - - } - - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - - containerDef.translation = position; - - } - - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - - containerDef.scale = scale; - - } - - // Store pivot in extras for round-trip reconstruction - containerDef.extras = { pivot: pivot.toArray() }; - - if ( object.name !== '' ) containerDef.name = String( object.name ); - - this.serializeUserData( object, containerDef ); - - const containerIndex = json.nodes.push( containerDef ) - 1; - - // Map original object to container so animations target it - nodeMap.set( object, containerIndex ); - - // Child node: holds mesh with -pivot offset - const childDef = {}; - - const childPosition = [ - pivot.x, - pivot.y, - pivot.z ]; - - if ( ! equalArray( childPosition, [ 0, 0, 0 ] ) ) { - - childDef.translation = childPosition; - - } - - if ( object.isMesh || object.isLine || object.isPoints ) { - - const meshIndex = await this.processMeshAsync( object ); - - if ( meshIndex !== null ) childDef.mesh = meshIndex; - - } else if ( object.isCamera ) { - - childDef.camera = this.processCamera( object ); - - } - - if ( object.isSkinnedMesh ) this.skins.push( object ); - - const childIndex = json.nodes.push( childDef ) - 1; - - // Build children array for container - const containerChildren = [ childIndex ]; - - // Process object's children as children of the child node - if ( object.children.length > 0 ) { - - const grandchildren = []; - - for ( let i = 0, l = object.children.length; i < l; i ++ ) { - - const child = object.children[ i ]; - - if ( child.visible || options.onlyVisible === false ) { - - const childNodeIndex = await this.processNodeAsync( child ); - - if ( childNodeIndex !== null ) grandchildren.push( childNodeIndex ); - - } - - } - - if ( grandchildren.length > 0 ) childDef.children = grandchildren; - - } - - containerDef.children = containerChildren; - - await this._invokeAllAsync( function ( ext ) { - - ext.writeNode && ext.writeNode( object, containerDef ); - - } ); - - return containerIndex; - - } - - /** - * Process Scene - * @param {Scene} scene Scene to process - */ - async processSceneAsync( scene ) { - - const json = this.json; - const options = this.options; - - if ( ! json.scenes ) { - - json.scenes = []; - json.scene = 0; - - } - - const sceneDef = {}; - - if ( scene.name !== '' ) sceneDef.name = scene.name; - - json.scenes.push( sceneDef ); - - const nodes = []; - - for ( let i = 0, l = scene.children.length; i < l; i ++ ) { - - const child = scene.children[ i ]; - - if ( child.visible || options.onlyVisible === false ) { - - const nodeIndex = await this.processNodeAsync( child ); - - if ( nodeIndex !== null ) nodes.push( nodeIndex ); - - } - - } - - if ( nodes.length > 0 ) sceneDef.nodes = nodes; - - this.serializeUserData( scene, sceneDef ); - - } - - /** - * Creates a Scene to hold a list of objects and parse it - * @param {Array<THREE.Object3D>} objects List of objects to process - */ - async processObjectsAsync( objects ) { - - const scene = new Scene(); - scene.name = 'AuxScene'; - - for ( let i = 0; i < objects.length; i ++ ) { - - // We push directly to children instead of calling `add` to prevent - // modify the .parent and break its original scene and hierarchy - scene.children.push( objects[ i ] ); - - } - - await this.processSceneAsync( scene ); - - } - - /** - * @param {THREE.Object3D|Array<THREE.Object3D>} input - */ - async processInputAsync( input ) { - - const options = this.options; - - input = input instanceof Array ? input : [ input ]; - - await this._invokeAllAsync( function ( ext ) { - - ext.beforeParse && ext.beforeParse( input ); - - } ); - - const objectsWithoutScene = []; - - for ( let i = 0; i < input.length; i ++ ) { - - if ( input[ i ] instanceof Scene ) { - - await this.processSceneAsync( input[ i ] ); - - } else { - - objectsWithoutScene.push( input[ i ] ); - - } - - } - - if ( objectsWithoutScene.length > 0 ) { - - await this.processObjectsAsync( objectsWithoutScene ); - - } - - for ( let i = 0; i < this.skins.length; ++ i ) { - - this.processSkin( this.skins[ i ] ); - - } - - // animations - - if ( input.length === 1 ) { - - // default: single input, flat animations array - - for ( let i = 0; i < options.animations.length; ++ i ) { - - this.processAnimation( options.animations[ i ], input[ 0 ] ); - - } - - } else { - - // multi-input with multi-dimensional animations array - - for ( let i = 0; i < input.length; i ++ ) { - - const animations = options.animations[ i ] || []; - - for ( let j = 0; j < animations.length; ++ j ) { - - this.processAnimation( animations[ j ], input[ i ] ); - - } - - } - - } - - await this._invokeAllAsync( function ( ext ) { - - ext.afterParse && ext.afterParse( input ); - - } ); - - } - - async _invokeAllAsync( func ) { - - for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { - - await func( this.plugins[ i ] ); - - } - - } - -} - -/** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - * - * @private - */ -class GLTFLightExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_lights_punctual'; - - } - - writeNode( light, nodeDef ) { - - if ( ! light.isLight ) return; - - if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) { - - console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light ); - return; - - } - - const writer = this.writer; - const json = writer.json; - const extensionsUsed = writer.extensionsUsed; - - const lightDef = {}; - - if ( light.name ) lightDef.name = light.name; - - lightDef.color = light.color.toArray(); - - lightDef.intensity = light.intensity; - - if ( light.isDirectionalLight ) { - - lightDef.type = 'directional'; - - } else if ( light.isPointLight ) { - - lightDef.type = 'point'; - - if ( light.distance > 0 ) lightDef.range = light.distance; - - } else if ( light.isSpotLight ) { - - lightDef.type = 'spot'; - - if ( light.distance > 0 ) lightDef.range = light.distance; - - lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle; - lightDef.spot.outerConeAngle = light.angle; - - } - - if ( light.decay !== undefined && light.decay !== 2 ) { - - console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' - + 'and expects light.decay=2.' ); - - } - - if ( light.target - && ( light.target.parent !== light - || light.target.position.x !== 0 - || light.target.position.y !== 0 - || light.target.position.z !== - 1 ) ) { - - console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, ' - + 'make light.target a child of the light with position 0,0,-1.' ); - - } - - if ( ! extensionsUsed[ this.name ] ) { - - json.extensions = json.extensions || {}; - json.extensions[ this.name ] = { lights: [] }; - extensionsUsed[ this.name ] = true; - - } - - const lights = json.extensions[ this.name ].lights; - lights.push( lightDef ); - - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { light: lights.length - 1 }; - - } - -} - -/** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - * - * @private - */ -class GLTFMaterialsUnlitExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_unlit'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshBasicMaterial ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = {}; - - extensionsUsed[ this.name ] = true; - - materialDef.pbrMetallicRoughness.metallicFactor = 0.0; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.9; - - } - -} - -/** - * Clearcoat Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat - * - * @private - */ -class GLTFMaterialsClearcoatExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_clearcoat'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.clearcoatFactor = material.clearcoat; - - if ( material.clearcoatMap ) { - - const clearcoatMapDef = { - index: await writer.processTextureAsync( material.clearcoatMap ), - texCoord: material.clearcoatMap.channel - }; - writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); - extensionDef.clearcoatTexture = clearcoatMapDef; - - } - - extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; - - if ( material.clearcoatRoughnessMap ) { - - const clearcoatRoughnessMapDef = { - index: await writer.processTextureAsync( material.clearcoatRoughnessMap ), - texCoord: material.clearcoatRoughnessMap.channel - }; - writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); - extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; - - } - - if ( material.clearcoatNormalMap ) { - - const clearcoatNormalMapDef = { - index: await writer.processTextureAsync( material.clearcoatNormalMap ), - texCoord: material.clearcoatNormalMap.channel - }; - - if ( material.clearcoatNormalScale.x !== 1 ) clearcoatNormalMapDef.scale = material.clearcoatNormalScale.x; - - writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); - extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; - - } - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - - } - -} - -/** - * Materials dispersion Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_dispersion - * - * @private - */ -class GLTFMaterialsDispersionExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_dispersion'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.dispersion === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.dispersion = material.dispersion; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Iridescence Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence - * - * @private - */ -class GLTFMaterialsIridescenceExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_iridescence'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.iridescenceFactor = material.iridescence; - - if ( material.iridescenceMap ) { - - const iridescenceMapDef = { - index: await writer.processTextureAsync( material.iridescenceMap ), - texCoord: material.iridescenceMap.channel - }; - writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); - extensionDef.iridescenceTexture = iridescenceMapDef; - - } - - extensionDef.iridescenceIor = material.iridescenceIOR; - extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ]; - extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ]; - - if ( material.iridescenceThicknessMap ) { - - const iridescenceThicknessMapDef = { - index: await writer.processTextureAsync( material.iridescenceThicknessMap ), - texCoord: material.iridescenceThicknessMap.channel - }; - writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); - extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef; - - } - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - * - * @private - */ -class GLTFMaterialsTransmissionExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_transmission'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.transmissionFactor = material.transmission; - - if ( material.transmissionMap ) { - - const transmissionMapDef = { - index: await writer.processTextureAsync( material.transmissionMap ), - texCoord: material.transmissionMap.channel - }; - writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); - extensionDef.transmissionTexture = transmissionMapDef; - - } - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - * - * @private - */ -class GLTFMaterialsVolumeExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_volume'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.thicknessFactor = material.thickness; - - if ( material.thicknessMap ) { - - const thicknessMapDef = { - index: await writer.processTextureAsync( material.thicknessMap ), - texCoord: material.thicknessMap.channel - }; - writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); - extensionDef.thicknessTexture = thicknessMapDef; - - } - - if ( material.attenuationDistance !== Infinity ) { - - extensionDef.attenuationDistance = material.attenuationDistance; - - } - - extensionDef.attenuationColor = material.attenuationColor.toArray(); - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Materials ior Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior - * - * @private - */ -class GLTFMaterialsIorExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_ior'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.ior = material.ior; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Materials specular Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular - * - * @private - */ -class GLTFMaterialsSpecularExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_specular'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && - material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && - ! material.specularIntensityMap && ! material.specularColorMap ) ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - if ( material.specularIntensityMap ) { - - const specularIntensityMapDef = { - index: await writer.processTextureAsync( material.specularIntensityMap ), - texCoord: material.specularIntensityMap.channel - }; - writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); - extensionDef.specularTexture = specularIntensityMapDef; - - } - - if ( material.specularColorMap ) { - - const specularColorMapDef = { - index: await writer.processTextureAsync( material.specularColorMap ), - texCoord: material.specularColorMap.channel - }; - writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); - extensionDef.specularColorTexture = specularColorMapDef; - - } - - extensionDef.specularFactor = material.specularIntensity; - extensionDef.specularColorFactor = material.specularColor.toArray(); - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Sheen Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen - * - * @private - */ -class GLTFMaterialsSheenExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_sheen'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - if ( material.sheenRoughnessMap ) { - - const sheenRoughnessMapDef = { - index: await writer.processTextureAsync( material.sheenRoughnessMap ), - texCoord: material.sheenRoughnessMap.channel - }; - writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); - extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef; - - } - - if ( material.sheenColorMap ) { - - const sheenColorMapDef = { - index: await writer.processTextureAsync( material.sheenColorMap ), - texCoord: material.sheenColorMap.channel - }; - writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); - extensionDef.sheenColorTexture = sheenColorMapDef; - - } - - extensionDef.sheenRoughnessFactor = material.sheenRoughness; - extensionDef.sheenColorFactor = material.sheenColor.toArray(); - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Anisotropy Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy - * - * @private - */ -class GLTFMaterialsAnisotropyExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_anisotropy'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - if ( material.anisotropyMap ) { - - const anisotropyMapDef = { index: await writer.processTextureAsync( material.anisotropyMap ) }; - writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap ); - extensionDef.anisotropyTexture = anisotropyMapDef; - - } - - extensionDef.anisotropyStrength = material.anisotropy; - extensionDef.anisotropyRotation = material.anisotropyRotation; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * Materials Emissive Strength Extension - * - * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md - * - * @private - */ -class GLTFMaterialsEmissiveStrengthExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'KHR_materials_emissive_strength'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - extensionDef.emissiveStrength = material.emissiveIntensity; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - - -/** - * Materials bump Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump - * - * @private - */ -class GLTFMaterialsBumpExtension { - - constructor( writer ) { - - this.writer = writer; - this.name = 'EXT_materials_bump'; - - } - - async writeMaterialAsync( material, materialDef ) { - - if ( ! material.isMeshStandardMaterial || ( - material.bumpScale === 1 && - ! material.bumpMap ) ) return; - - const writer = this.writer; - const extensionsUsed = writer.extensionsUsed; - - const extensionDef = {}; - - if ( material.bumpMap ) { - - const bumpMapDef = { - index: await writer.processTextureAsync( material.bumpMap ), - texCoord: material.bumpMap.channel - }; - writer.applyTextureTransform( bumpMapDef, material.bumpMap ); - extensionDef.bumpTexture = bumpMapDef; - - } - - extensionDef.bumpFactor = material.bumpScale; - - materialDef.extensions = materialDef.extensions || {}; - materialDef.extensions[ this.name ] = extensionDef; - - extensionsUsed[ this.name ] = true; - - } - -} - -/** - * GPU Instancing Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing - * - * @private - */ -class GLTFMeshGpuInstancing { - - constructor( writer ) { - - this.writer = writer; - this.name = 'EXT_mesh_gpu_instancing'; - - } - - writeNode( object, nodeDef ) { - - if ( ! object.isInstancedMesh ) return; - - const writer = this.writer; - - const mesh = object; - - const translationAttr = new Float32Array( mesh.count * 3 ); - const rotationAttr = new Float32Array( mesh.count * 4 ); - const scaleAttr = new Float32Array( mesh.count * 3 ); - - const matrix = new Matrix4(); - const position = new Vector3(); - const quaternion = new Quaternion(); - const scale = new Vector3(); - - for ( let i = 0; i < mesh.count; i ++ ) { - - mesh.getMatrixAt( i, matrix ); - matrix.decompose( position, quaternion, scale ); - - position.toArray( translationAttr, i * 3 ); - quaternion.toArray( rotationAttr, i * 4 ); - scale.toArray( scaleAttr, i * 3 ); - - } - - const attributes = { - TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ), - ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ), - SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ), - }; - - if ( mesh.instanceColor ) - attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor ); - - nodeDef.extensions = nodeDef.extensions || {}; - nodeDef.extensions[ this.name ] = { attributes }; - - writer.extensionsUsed[ this.name ] = true; - writer.extensionsRequired[ this.name ] = true; - - } - -} - -/** - * Static utility functions - * - * @private - */ -GLTFExporter.Utils = { - - insertKeyframe: function ( track, time ) { - - const tolerance = 0.001; // 1ms - const valueSize = track.getValueSize(); - - const times = new track.TimeBufferType( track.times.length + 1 ); - const values = new track.ValueBufferType( track.values.length + valueSize ); - const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - - let index; - - if ( track.times.length === 0 ) { - - times[ 0 ] = time; - - for ( let i = 0; i < valueSize; i ++ ) { - - values[ i ] = 0; - - } - - index = 0; - - } else if ( time < track.times[ 0 ] ) { - - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - - times[ 0 ] = time; - times.set( track.times, 1 ); - - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); - - index = 0; - - } else if ( time > track.times[ track.times.length - 1 ] ) { - - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { - - return track.times.length - 1; - - } - - times[ times.length - 1 ] = time; - times.set( track.times, 0 ); - - values.set( track.values, 0 ); - values.set( interpolant.evaluate( time ), track.values.length ); - - index = times.length - 1; - - } else { - - for ( let i = 0; i < track.times.length; i ++ ) { - - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); - - values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 ); - values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize ); - values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize ); - - index = i + 1; - - break; - - } - - } - - } - - track.times = times; - track.values = values; - - return index; - - }, - - mergeMorphTargetTracks: function ( clip, root ) { - - const tracks = []; - const mergedTracks = {}; - const sourceTracks = clip.tracks; - - for ( let i = 0; i < sourceTracks.length; ++ i ) { - - let sourceTrack = sourceTracks[ i ]; - const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { - - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; - - } - - if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete - && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - - if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); - - } - - console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); - - } - - const targetCount = sourceTrackNode.morphTargetInfluences.length; - const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - - if ( targetIndex === undefined ) { - - throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - - } - - let mergedTrack; - - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) { - - mergedTrack = sourceTrack.clone(); - - const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; - - } - - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences'; - mergedTrack.values = values; - - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); - - continue; - - } - - const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( let j = 0; j < mergedTrack.times.length; j ++ ) { - - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); - - } - - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for ( let j = 0; j < sourceTrack.times.length; j ++ ) { - - const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; - - } - - } - - clip.tracks = tracks; - - return clip; - - }, - - toTypedBufferAttribute: function ( srcAttribute, TypedArray ) { - - const dstAttribute = new BufferAttribute( new TypedArray( srcAttribute.count * srcAttribute.itemSize ), srcAttribute.itemSize, false ); - - if ( ! srcAttribute.normalized && ! srcAttribute.isInterleavedBufferAttribute ) { - - dstAttribute.array.set( srcAttribute.array ); - - return dstAttribute; - - } - - for ( let i = 0, il = srcAttribute.count; i < il; i ++ ) { - - for ( let j = 0; j < srcAttribute.itemSize; j ++ ) { - - dstAttribute.setComponent( i, j, srcAttribute.getComponent( i, j ) ); - - } - - } - - return dstAttribute; - - } - -}; - -/** - * Export options of `GLTFExporter`. - * - * @typedef {Object} GLTFExporter~Options - * @property {boolean} [trs=false] - Export position, rotation and scale instead of matrix per node. - * @property {boolean} [onlyVisible=true] - Export only visible 3D objects. - * @property {boolean} [binary=false] - Export in binary (.glb) format, returning an ArrayBuffer. - * @property {number} [maxTextureSize=Infinity] - Restricts the image maximum size (both width and height) to the given value. - * @property {Array<AnimationClip>|Array<Array<AnimationClip>>} [animations=[]] - List of animations to be included in the export. When exporting a single 3D object or scene, this is a flat list of clips. - * When exporting an array of multiple scenes, this must be a nested array with one list of clips per scene, matched to the input by index. - * @property {boolean} [includeCustomExtensions=false] - Export custom glTF extensions defined on an object's `userData.gltfExtensions` property. - * @property {string} [copyright=null] - Export with a copyright notice embedded in the glTF. - **/ - -/** - * onDone callback of `GLTFExporter`. - * - * @callback GLTFExporter~OnDone - * @param {ArrayBuffer|string} result - The generated .gltf (JSON) or .glb (binary). - */ - -/** - * onError callback of `GLTFExporter`. - * - * @callback GLTFExporter~OnError - * @param {Error} error - The error object. - */ - -export { GLTFExporter }; |
