From 53493850abf1a8884fd7aecb0307de519184892f Mon Sep 17 00:00:00 2001 From: "Mr.doob" Date: Thu, 14 Mar 2019 20:23:25 -0700 Subject: [PATCH] JSM: Regenerated jsm files. --- examples/jsm/exporters/GLTFExporter.js | 2581 ++++++++++++------------ examples/jsm/loaders/MTLLoader.js | 602 ++++++ 2 files changed, 1895 insertions(+), 1288 deletions(-) create mode 100644 examples/jsm/loaders/MTLLoader.js diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 992a86e1a2..db1729dbfc 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -4,29 +4,6 @@ * @author Takahiro / https://github.com/takahirox */ -import { - NearestFilter, - NearestMipMapNearestFilter, - NearestMipMapLinearFilter, - LinearFilter, - LinearMipMapNearestFilter, - LinearMipMapLinearFilter, - ClampToEdgeWrapping, - RepeatWrapping, - MirroredRepeatWrapping, - Scene, - BufferAttribute, - BufferGeometry, - TriangleFanDrawMode, - TriangleStripDrawMode, - RGBAFormat, - DoubleSide, - PropertyBinding, - InterpolateDiscrete, - InterpolateLinear, - Vector3, -} from "../../../build/three.module.js"; - //------------------------------------------------------------------------------ // Constants //------------------------------------------------------------------------------ @@ -81,2149 +58,2177 @@ var PATH_PROPERTIES = { //------------------------------------------------------------------------------ // GLTF Exporter //------------------------------------------------------------------------------ -var GLTFExporter = ( function () { +import { + AnimationClip, + BufferAttribute, + BufferGeometry, + Camera, + ClampToEdgeWrapping, + DoubleSide, + Geometry, + InterpolateDiscrete, + LinearFilter, + LinearMipMapLinearFilter, + LinearMipMapNearestFilter, + Material, + Math, + Mesh, + MirroredRepeatWrapping, + NearestFilter, + NearestMipMapLinearFilter, + NearestMipMapNearestFilter, + Object3D, + PropertyBinding, + RGBAFormat, + RGBFormat, + RepeatWrapping, + Scene, + Scenes, + ShaderMaterial, + TriangleFanDrawMode, + TriangleStripDrawMode, + Vector3, + VertexColors +} from "../../../build/three.module.js"; - function GLTFExporter() { - } +var GLTFExporter = function () {}; - GLTFExporter.prototype = { +GLTFExporter.prototype = { - constructor: GLTFExporter, - /** - * Parse scenes and generate GLTF output - * @param {Scene or [Scenes]} input Scene or Array of Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - parse: function ( input, onDone, options ) { - - var DEFAULT_OPTIONS = { - binary: false, - trs: false, - onlyVisible: true, - truncateDrawRange: true, - embedImages: true, - animations: [], - forceIndices: false, - forcePowerOfTwoTextures: false - }; + constructor: GLTFExporter, - options = Object.assign( {}, DEFAULT_OPTIONS, options ); + /** + * Parse scenes and generate GLTF output + * @param {Scene or [Scenes]} input Scene or Array of Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options + */ + parse: function ( input, onDone, options ) { - if ( options.animations.length > 0 ) { + var DEFAULT_OPTIONS = { + binary: false, + trs: false, + onlyVisible: true, + truncateDrawRange: true, + embedImages: true, + animations: [], + forceIndices: false, + forcePowerOfTwoTextures: false + }; - // Only TRS properties, and not matrices, may be targeted by animation. - options.trs = true; + options = Object.assign( {}, DEFAULT_OPTIONS, options ); - } + if ( options.animations.length > 0 ) { + + // Only TRS properties, and not matrices, may be targeted by animation. + options.trs = true; - var outputJSON = { + } - asset: { + var outputJSON = { - version: "2.0", - generator: "GLTFExporter" + asset: { - } + version: "2.0", + generator: "GLTFExporter" - }; + } - var byteOffset = 0; - var buffers = []; - var pending = []; - var nodeMap = new Map(); - var skins = []; - var extensionsUsed = {}; - var cachedData = { - - meshes: new Map(), - attributes: new Map(), - attributesNormalized: new Map(), - materials: new Map(), - textures: new Map(), - images: new Map() + }; - }; + var byteOffset = 0; + var buffers = []; + var pending = []; + var nodeMap = new Map(); + var skins = []; + var extensionsUsed = {}; + var cachedData = { - var cachedCanvas; + meshes: new Map(), + attributes: new Map(), + attributesNormalized: new Map(), + materials: new Map(), + textures: new Map(), + images: new Map() - /** - * Compare two arrays - */ - /** - * Compare two arrays - * @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 ) { + var cachedCanvas; - return element === array2[ index ]; + /** + * Compare two arrays + */ + /** + * Compare two arrays + * @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. - * @param {string} text - * @return {ArrayBuffer} - */ - function stringToArrayBuffer( text ) { + } ); - if ( window.TextEncoder !== undefined ) { + } - return new TextEncoder().encode( text ).buffer; + /** + * Converts a string to an ArrayBuffer. + * @param {string} text + * @return {ArrayBuffer} + */ + function stringToArrayBuffer( text ) { - } + if ( window.TextEncoder !== undefined ) { - var array = new Uint8Array( new ArrayBuffer( text.length ) ); + return new TextEncoder().encode( text ).buffer; - for ( var i = 0, il = text.length; i < il; i ++ ) { + } - var value = text.charCodeAt( i ); + var array = new Uint8Array( new ArrayBuffer( text.length ) ); - // Replacing multi-byte character with space(0x20). - array[ i ] = value > 0xFF ? 0x20 : value; + for ( var i = 0, il = text.length; i < il; i ++ ) { - } + var value = text.charCodeAt( i ); - return array.buffer; + // Replacing multi-byte character with space(0x20). + array[ i ] = value > 0xFF ? 0x20 : value; } - /** - * Get the min and max vectors from the given attribute - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {Integer} start - * @param {Integer} count - * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) - */ - function getMinMax( attribute, start, count ) { + return array.buffer; - var output = { + } - min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), - max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) + /** + * Get the min and max vectors from the given attribute + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {Integer} start + * @param {Integer} count + * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) + */ + function getMinMax( attribute, start, count ) { - }; + var output = { - for ( var i = start; i < start + count; i ++ ) { + min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), + max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) - for ( var a = 0; a < attribute.itemSize; a ++ ) { + }; - var value = attribute.array[ i * attribute.itemSize + a ]; - output.min[ a ] = Math.min( output.min[ a ], value ); - output.max[ a ] = Math.max( output.max[ a ], value ); + for ( var i = start; i < start + count; i ++ ) { - } + for ( var a = 0; a < attribute.itemSize; a ++ ) { - } + var value = attribute.array[ i * attribute.itemSize + a ]; + output.min[ a ] = Math.min( output.min[ a ], value ); + output.max[ a ] = Math.max( output.max[ a ], value ); - return output; + } } - /** - * Checks if image size is POT. - * - * @param {Image} image The image to be checked. - * @returns {Boolean} Returns true if image size is POT. - * - */ - function isPowerOfTwo( image ) { + return output; - return Math.isPowerOfTwo( image.width ) && Math.isPowerOfTwo( image.height ); + } - } + /** + * Checks if image size is POT. + * + * @param {Image} image The image to be checked. + * @returns {Boolean} Returns true if image size is POT. + * + */ + function isPowerOfTwo( image ) { - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {Boolean} - * - */ - function isNormalizedNormalAttribute( normal ) { + return Math.isPowerOfTwo( image.width ) && Math.isPowerOfTwo( image.height ); - if ( cachedData.attributesNormalized.has( normal ) ) { + } - return false; + /** + * Checks if normal attribute values are normalized. + * + * @param {BufferAttribute} normal + * @returns {Boolean} + * + */ + function isNormalizedNormalAttribute( normal ) { - } + if ( cachedData.attributesNormalized.has( normal ) ) { - var v = new Vector3(); + return false; - for ( var i = 0, il = normal.count; i < il; i ++ ) { + } - // 0.0005 is from glTF-validator - if ( Math.abs( v.fromArray( normal.array, i * 3 ).length() - 1.0 ) > 0.0005 ) return false; + var v = new Vector3(); - } + for ( var i = 0, il = normal.count; i < il; i ++ ) { - return true; + // 0.0005 is from glTF-validator + if ( Math.abs( v.fromArray( normal.array, i * 3 ).length() - 1.0 ) > 0.0005 ) return false; } - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - function createNormalizedNormalAttribute( normal ) { + return true; - if ( cachedData.attributesNormalized.has( normal ) ) { + } - return cachedData.attributesNormalized.get( normal ); + /** + * Creates normalized normal buffer attribute. + * + * @param {BufferAttribute} normal + * @returns {BufferAttribute} + * + */ + function createNormalizedNormalAttribute( normal ) { - } + if ( cachedData.attributesNormalized.has( normal ) ) { - var attribute = normal.clone(); + return cachedData.attributesNormalized.get( normal ); - var v = new Vector3(); + } - for ( var i = 0, il = attribute.count; i < il; i ++ ) { + var attribute = normal.clone(); - v.fromArray( attribute.array, i * 3 ); + var v = new Vector3(); - if ( v.x === 0 && v.y === 0 && v.z === 0 ) { + for ( var i = 0, il = attribute.count; i < il; i ++ ) { - // if values can't be normalized set (1, 0, 0) - v.setX( 1.0 ); + v.fromArray( attribute.array, i * 3 ); - } else { + if ( v.x === 0 && v.y === 0 && v.z === 0 ) { - v.normalize(); + // if values can't be normalized set (1, 0, 0) + v.setX( 1.0 ); - } + } else { - v.toArray( attribute.array, i * 3 ); + v.normalize(); } - cachedData.attributesNormalized.set( normal, attribute ); - - return attribute; + v.toArray( attribute.array, i * 3 ); } - /** - * 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 - * - * @param {Integer} bufferSize The size the original buffer. - * @returns {Integer} new buffer size with required padding. - * - */ - function getPaddedBufferSize( bufferSize ) { + cachedData.attributesNormalized.set( normal, attribute ); - return Math.ceil( bufferSize / 4 ) * 4; + return attribute; - } + } - /** - * Returns a buffer aligned to 4-byte boundary. - * - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {Integer} paddingByte (Optional) - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer - */ - function getPaddedArrayBuffer( arrayBuffer, paddingByte ) { + /** + * 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 + * + * @param {Integer} bufferSize The size the original buffer. + * @returns {Integer} new buffer size with required padding. + * + */ + function getPaddedBufferSize( bufferSize ) { - paddingByte = paddingByte || 0; + return Math.ceil( bufferSize / 4 ) * 4; - var paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); + } - if ( paddedLength !== arrayBuffer.byteLength ) { + /** + * Returns a buffer aligned to 4-byte boundary. + * + * @param {ArrayBuffer} arrayBuffer Buffer to pad + * @param {Integer} paddingByte (Optional) + * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + */ + function getPaddedArrayBuffer( arrayBuffer, paddingByte ) { - var array = new Uint8Array( paddedLength ); - array.set( new Uint8Array( arrayBuffer ) ); + paddingByte = paddingByte || 0; - if ( paddingByte !== 0 ) { + var paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); - for ( var i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { + if ( paddedLength !== arrayBuffer.byteLength ) { - array[ i ] = paddingByte; + var array = new Uint8Array( paddedLength ); + array.set( new Uint8Array( arrayBuffer ) ); - } + if ( paddingByte !== 0 ) { - } + for ( var i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { - return array.buffer; + array[ i ] = paddingByte; + + } } - return arrayBuffer; + return array.buffer; } - /** - * Serializes a userData. - * - * @param {Object3D|Material} object - * @returns {Object} - */ - function serializeUserData( object ) { + return arrayBuffer; - try { + } - return JSON.parse( JSON.stringify( object.userData ) ); + /** + * Serializes a userData. + * + * @param {Object3D|Material} object + * @returns {Object} + */ + function serializeUserData( object ) { - } catch ( error ) { + try { - console.warn( 'GLTFExporter: userData of \'' + object.name + '\' ' + - 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + return JSON.parse( JSON.stringify( object.userData ) ); - return {}; + } catch ( error ) { - } + console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); + + return {}; } - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - */ - function applyTextureTransform( mapDef, texture ) { + } - var didTransform = false; - var transformDef = {}; + /** + * Applies a texture transform, if present, to the map definition. Requires + * the KHR_texture_transform extension. + */ + function applyTextureTransform( mapDef, texture ) { - if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) { + var didTransform = false; + var transformDef = {}; - transformDef.offset = texture.offset.toArray(); - didTransform = true; + 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.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 ( 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; - extensionsUsed[ 'KHR_texture_transform' ] = true; + if ( didTransform ) { - } + mapDef.extensions = mapDef.extensions || {}; + mapDef.extensions[ 'KHR_texture_transform' ] = transformDef; + extensionsUsed[ 'KHR_texture_transform' ] = true; } - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {Integer} - */ - function processBuffer( buffer ) { - - if ( ! outputJSON.buffers ) { - - outputJSON.buffers = [ { byteLength: 0 } ]; + } - } + /** + * Process a buffer to append to the default one. + * @param {ArrayBuffer} buffer + * @return {Integer} + */ + function processBuffer( buffer ) { - // All buffers are merged before export. - buffers.push( buffer ); + if ( ! outputJSON.buffers ) { - return 0; + outputJSON.buffers = [ { byteLength: 0 } ]; } - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} target (Optional) Target usage of the BufferView - * @return {Object} - */ - function processBufferView( attribute, componentType, start, count, target ) { + // All buffers are merged before export. + buffers.push( buffer ); - if ( ! outputJSON.bufferViews ) { + return 0; - outputJSON.bufferViews = []; + } - } + /** + * Process and generate a BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} target (Optional) Target usage of the BufferView + * @return {Object} + */ + function processBufferView( attribute, componentType, start, count, target ) { - // Create a new dataview and dump the attribute's array into it + if ( ! outputJSON.bufferViews ) { - var componentSize; + outputJSON.bufferViews = []; - if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + } - componentSize = 1; + // Create a new dataview and dump the attribute's array into it - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + var componentSize; - componentSize = 2; + if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - } else { + componentSize = 1; - componentSize = 4; + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - } + componentSize = 2; - var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); - var dataView = new DataView( new ArrayBuffer( byteLength ) ); - var offset = 0; + } else { - for ( var i = start; i < start + count; i ++ ) { + componentSize = 4; - for ( var a = 0; a < attribute.itemSize; a ++ ) { + } - // @TODO Fails on InterleavedBufferAttribute, and could probably be - // optimized for normal BufferAttribute. - var value = attribute.array[ i * attribute.itemSize + a ]; + var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); + var dataView = new DataView( new ArrayBuffer( byteLength ) ); + var offset = 0; - if ( componentType === WEBGL_CONSTANTS.FLOAT ) { + for ( var i = start; i < start + count; i ++ ) { - dataView.setFloat32( offset, value, true ); + for ( var a = 0; a < attribute.itemSize; a ++ ) { - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { + // @TODO Fails on InterleavedBufferAttribute, and could probably be + // optimized for normal BufferAttribute. + var value = attribute.array[ i * attribute.itemSize + a ]; - dataView.setUint32( offset, value, true ); + if ( componentType === WEBGL_CONSTANTS.FLOAT ) { - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + dataView.setFloat32( offset, value, true ); - dataView.setUint16( offset, value, true ); + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { - } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + dataView.setUint32( offset, value, true ); - dataView.setUint8( offset, value ); + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { - } + dataView.setUint16( offset, value, true ); - offset += componentSize; + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { - } - - } + dataView.setUint8( offset, value ); - var gltfBufferView = { + } - buffer: processBuffer( dataView.buffer ), - byteOffset: byteOffset, - byteLength: byteLength + offset += componentSize; - }; + } - if ( target !== undefined ) gltfBufferView.target = target; + } - if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + var gltfBufferView = { - // Only define byteStride for vertex attributes. - gltfBufferView.byteStride = attribute.itemSize * componentSize; + buffer: processBuffer( dataView.buffer ), + byteOffset: byteOffset, + byteLength: byteLength - } + }; - byteOffset += byteLength; + if ( target !== undefined ) gltfBufferView.target = target; - outputJSON.bufferViews.push( gltfBufferView ); + if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { - // @TODO Merge bufferViews where possible. - var output = { + // Only define byteStride for vertex attributes. + gltfBufferView.byteStride = attribute.itemSize * componentSize; - id: outputJSON.bufferViews.length - 1, - byteLength: 0 + } - }; + byteOffset += byteLength; - return output; + outputJSON.bufferViews.push( gltfBufferView ); - } + // @TODO Merge bufferViews where possible. + var output = { - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise} - */ - function processBufferViewImage( blob ) { + id: outputJSON.bufferViews.length - 1, + byteLength: 0 - if ( ! outputJSON.bufferViews ) { + }; - outputJSON.bufferViews = []; + return output; - } + } - return new Promise( function ( resolve ) { + /** + * Process and generate a BufferView from an image Blob. + * @param {Blob} blob + * @return {Promise} + */ + function processBufferViewImage( blob ) { - var reader = new window.FileReader(); - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + if ( ! outputJSON.bufferViews ) { - var buffer = getPaddedArrayBuffer( reader.result ); + outputJSON.bufferViews = []; - var bufferView = { - buffer: processBuffer( buffer ), - byteOffset: byteOffset, - byteLength: buffer.byteLength - }; + } - byteOffset += buffer.byteLength; + return new Promise( function ( resolve ) { - outputJSON.bufferViews.push( bufferView ); + var reader = new window.FileReader(); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { - resolve( outputJSON.bufferViews.length - 1 ); + var buffer = getPaddedArrayBuffer( reader.result ); + var bufferView = { + buffer: processBuffer( buffer ), + byteOffset: byteOffset, + byteLength: buffer.byteLength }; - } ); + byteOffset += buffer.byteLength; - } + outputJSON.bufferViews.push( bufferView ); - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {BufferGeometry} geometry (Optional) Geometry used for truncated draw range - * @param {Integer} start (Optional) - * @param {Integer} count (Optional) - * @return {Integer} Index of the processed accessor on the "accessors" array - */ - function processAccessor( attribute, geometry, start, count ) { + resolve( outputJSON.bufferViews.length - 1 ); - var types = { + }; - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 16: 'MAT4' + } ); - }; + } - var componentType; + /** + * Process attribute to generate an accessor + * @param {BufferAttribute} attribute Attribute to process + * @param {BufferGeometry} geometry (Optional) Geometry used for truncated draw range + * @param {Integer} start (Optional) + * @param {Integer} count (Optional) + * @return {Integer} Index of the processed accessor on the "accessors" array + */ + function processAccessor( attribute, geometry, start, count ) { - // Detect the component type of the attribute array (float, uint or ushort) - if ( attribute.array.constructor === Float32Array ) { + var types = { - componentType = WEBGL_CONSTANTS.FLOAT; + 1: 'SCALAR', + 2: 'VEC2', + 3: 'VEC3', + 4: 'VEC4', + 16: 'MAT4' - } else if ( attribute.array.constructor === Uint32Array ) { + }; - componentType = WEBGL_CONSTANTS.UNSIGNED_INT; + var componentType; - } else if ( attribute.array.constructor === Uint16Array ) { + // Detect the component type of the attribute array (float, uint or ushort) + if ( attribute.array.constructor === Float32Array ) { - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; + componentType = WEBGL_CONSTANTS.FLOAT; - } else if ( attribute.array.constructor === Uint8Array ) { + } else if ( attribute.array.constructor === Uint32Array ) { - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; + componentType = WEBGL_CONSTANTS.UNSIGNED_INT; - } else { + } else if ( attribute.array.constructor === Uint16Array ) { - throw new Error( 'GLTFExporter: Unsupported bufferAttribute component type.' ); + componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; - } + } else if ( attribute.array.constructor === Uint8Array ) { - if ( start === undefined ) start = 0; - if ( count === undefined ) count = attribute.count; + componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; - // @TODO Indexed buffer geometry with drawRange not supported yet - if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) { + } else { - var end = start + count; - var end2 = geometry.drawRange.count === Infinity - ? attribute.count - : geometry.drawRange.start + geometry.drawRange.count; + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); - start = Math.max( start, geometry.drawRange.start ); - count = Math.min( end, end2 ) - start; + } - if ( count < 0 ) count = 0; + if ( start === undefined ) start = 0; + if ( count === undefined ) count = attribute.count; - } + // @TODO Indexed buffer geometry with drawRange not supported yet + if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) { - // Skip creating an accessor if the attribute doesn't have data to export - if ( count === 0 ) { + var end = start + count; + var end2 = geometry.drawRange.count === Infinity + ? attribute.count + : geometry.drawRange.start + geometry.drawRange.count; - return null; + start = Math.max( start, geometry.drawRange.start ); + count = Math.min( end, end2 ) - start; - } + if ( count < 0 ) count = 0; - var minMax = getMinMax( attribute, start, count ); + } - var bufferViewTarget; + // Skip creating an accessor if the attribute doesn't have data to export + if ( count === 0 ) { - // 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 ) { + return null; - bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; + } - } + var minMax = getMinMax( attribute, start, count ); - var bufferView = processBufferView( attribute, componentType, start, count, bufferViewTarget ); + var bufferViewTarget; - var gltfAccessor = { + // 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 ) { - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[ attribute.itemSize ] + bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; - }; + } - if ( ! outputJSON.accessors ) { + var bufferView = processBufferView( attribute, componentType, start, count, bufferViewTarget ); - outputJSON.accessors = []; + var gltfAccessor = { - } + bufferView: bufferView.id, + byteOffset: bufferView.byteOffset, + componentType: componentType, + count: count, + max: minMax.max, + min: minMax.min, + type: types[ attribute.itemSize ] + + }; - outputJSON.accessors.push( gltfAccessor ); + if ( ! outputJSON.accessors ) { - return outputJSON.accessors.length - 1; + outputJSON.accessors = []; } - /** - * Process image - * @param {Image} image to process - * @param {Integer} format of the image (e.g. RGBFormat, RGBAFormat etc) - * @param {Boolean} flipY before writing out the image - * @return {Integer} Index of the processed texture in the "images" array - */ - function processImage( image, format, flipY ) { + outputJSON.accessors.push( gltfAccessor ); - if ( ! cachedData.images.has( image ) ) { + return outputJSON.accessors.length - 1; - cachedData.images.set( image, {} ); + } - } + /** + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (e.g. RGBFormat, RGBAFormat etc) + * @param {Boolean} flipY before writing out the image + * @return {Integer} Index of the processed texture in the "images" array + */ + function processImage( image, format, flipY ) { - var cachedImages = cachedData.images.get( image ); - var mimeType = format === RGBAFormat ? 'image/png' : 'image/jpeg'; - var key = mimeType + ":flipY/" + flipY.toString(); + if ( ! cachedData.images.has( image ) ) { - if ( cachedImages[ key ] !== undefined ) { + cachedData.images.set( image, {} ); - return cachedImages[ key ]; + } - } + var cachedImages = cachedData.images.get( image ); + var mimeType = format === RGBAFormat ? 'image/png' : 'image/jpeg'; + var key = mimeType + ":flipY/" + flipY.toString(); - if ( ! outputJSON.images ) { + if ( cachedImages[ key ] !== undefined ) { - outputJSON.images = []; + return cachedImages[ key ]; - } + } - var gltfImage = { mimeType: mimeType }; + if ( ! outputJSON.images ) { - if ( options.embedImages ) { + outputJSON.images = []; - var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' ); + } - canvas.width = image.width; - canvas.height = image.height; + var gltfImage = { mimeType: mimeType }; - if ( options.forcePowerOfTwoTextures && ! isPowerOfTwo( image ) ) { + if ( options.embedImages ) { - console.warn( 'GLTFExporter: Resized non-power-of-two image.', image ); + var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' ); - canvas.width = Math.floorPowerOfTwo( canvas.width ); - canvas.height = Math.floorPowerOfTwo( canvas.height ); + canvas.width = image.width; + canvas.height = image.height; - } + if ( options.forcePowerOfTwoTextures && ! isPowerOfTwo( image ) ) { - var ctx = canvas.getContext( '2d' ); + console.warn( 'GLTFExporter: Resized non-power-of-two image.', image ); - if ( flipY === true ) { + canvas.width = Math.floorPowerOfTwo( canvas.width ); + canvas.height = Math.floorPowerOfTwo( canvas.height ); - ctx.translate( 0, canvas.height ); - ctx.scale( 1, - 1 ); + } - } + var ctx = canvas.getContext( '2d' ); - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + if ( flipY === true ) { - if ( options.binary === true ) { + ctx.translate( 0, canvas.height ); + ctx.scale( 1, - 1 ); - pending.push( new Promise( function ( resolve ) { + } - canvas.toBlob( function ( blob ) { + ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); - processBufferViewImage( blob ).then( function ( bufferViewIndex ) { + if ( options.binary === true ) { - gltfImage.bufferView = bufferViewIndex; + pending.push( new Promise( function ( resolve ) { - resolve(); + canvas.toBlob( function ( blob ) { - } ); + processBufferViewImage( blob ).then( function ( bufferViewIndex ) { - }, mimeType ); + gltfImage.bufferView = bufferViewIndex; - } ) ); + resolve(); - } else { + } ); - gltfImage.uri = canvas.toDataURL( mimeType ); + }, mimeType ); - } + } ) ); } else { - gltfImage.uri = image.src; + gltfImage.uri = canvas.toDataURL( mimeType ); } - outputJSON.images.push( gltfImage ); - - var index = outputJSON.images.length - 1; - cachedImages[ key ] = index; + } else { - return index; + gltfImage.uri = image.src; } - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {Integer} Index of the processed texture in the "samplers" array - */ - function processSampler( map ) { + outputJSON.images.push( gltfImage ); - if ( ! outputJSON.samplers ) { + var index = outputJSON.images.length - 1; + cachedImages[ key ] = index; - outputJSON.samplers = []; + return index; - } + } - var gltfSampler = { + /** + * Process sampler + * @param {Texture} map Texture to process + * @return {Integer} Index of the processed texture in the "samplers" array + */ + function processSampler( map ) { - magFilter: THREE_TO_WEBGL[ map.magFilter ], - minFilter: THREE_TO_WEBGL[ map.minFilter ], - wrapS: THREE_TO_WEBGL[ map.wrapS ], - wrapT: THREE_TO_WEBGL[ map.wrapT ] + if ( ! outputJSON.samplers ) { - }; + outputJSON.samplers = []; - outputJSON.samplers.push( gltfSampler ); + } - return outputJSON.samplers.length - 1; + var gltfSampler = { - } + magFilter: THREE_TO_WEBGL[ map.magFilter ], + minFilter: THREE_TO_WEBGL[ map.minFilter ], + wrapS: THREE_TO_WEBGL[ map.wrapS ], + wrapT: THREE_TO_WEBGL[ map.wrapT ] - /** - * Process texture - * @param {Texture} map Map to process - * @return {Integer} Index of the processed texture in the "textures" array - */ - function processTexture( map ) { + }; - if ( cachedData.textures.has( map ) ) { + outputJSON.samplers.push( gltfSampler ); - return cachedData.textures.get( map ); + return outputJSON.samplers.length - 1; - } + } - if ( ! outputJSON.textures ) { + /** + * Process texture + * @param {Texture} map Map to process + * @return {Integer} Index of the processed texture in the "textures" array + */ + function processTexture( map ) { - outputJSON.textures = []; + if ( cachedData.textures.has( map ) ) { - } + return cachedData.textures.get( map ); - var gltfTexture = { + } - sampler: processSampler( map ), - source: processImage( map.image, map.format, map.flipY ) + if ( ! outputJSON.textures ) { - }; + outputJSON.textures = []; - outputJSON.textures.push( gltfTexture ); + } - var index = outputJSON.textures.length - 1; - cachedData.textures.set( map, index ); + var gltfTexture = { - return index; + sampler: processSampler( map ), + source: processImage( map.image, map.format, map.flipY ) - } + }; - /** - * Process material - * @param {Material} material Material to process - * @return {Integer} Index of the processed material in the "materials" array - */ - function processMaterial( material ) { + outputJSON.textures.push( gltfTexture ); - if ( cachedData.materials.has( material ) ) { + var index = outputJSON.textures.length - 1; + cachedData.textures.set( map, index ); - return cachedData.materials.get( material ); + return index; - } + } - if ( ! outputJSON.materials ) { + /** + * Process material + * @param {Material} material Material to process + * @return {Integer} Index of the processed material in the "materials" array + */ + function processMaterial( material ) { - outputJSON.materials = []; + if ( cachedData.materials.has( material ) ) { - } + return cachedData.materials.get( material ); - if ( material.isShaderMaterial ) { + } - console.warn( 'GLTFExporter: ShaderMaterial not supported.' ); - return null; + if ( ! outputJSON.materials ) { - } + outputJSON.materials = []; - // @QUESTION Should we avoid including any attribute that has the default value? - var gltfMaterial = { + } - pbrMetallicRoughness: {} + if ( material.isShaderMaterial ) { - }; + console.warn( 'GLTFExporter: ShaderMaterial not supported.' ); + return null; - if ( material.isMeshBasicMaterial ) { + } - gltfMaterial.extensions = { KHR_materials_unlit: {} }; + // @QUESTION Should we avoid including any attribute that has the default value? + var gltfMaterial = { - extensionsUsed[ 'KHR_materials_unlit' ] = true; + pbrMetallicRoughness: {} - } else if ( ! material.isMeshStandardMaterial ) { + }; - console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); + if ( material.isMeshBasicMaterial ) { - } + gltfMaterial.extensions = { KHR_materials_unlit: {} }; - // pbrMetallicRoughness.baseColorFactor - var color = material.color.toArray().concat( [ material.opacity ] ); + extensionsUsed[ 'KHR_materials_unlit' ] = true; - if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { + } else if ( ! material.isMeshStandardMaterial ) { - gltfMaterial.pbrMetallicRoughness.baseColorFactor = color; + console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); - } + } - if ( material.isMeshStandardMaterial ) { + // pbrMetallicRoughness.baseColorFactor + var color = material.color.toArray().concat( [ material.opacity ] ); - gltfMaterial.pbrMetallicRoughness.metallicFactor = material.metalness; - gltfMaterial.pbrMetallicRoughness.roughnessFactor = material.roughness; + if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { - } else if ( material.isMeshBasicMaterial ) { + gltfMaterial.pbrMetallicRoughness.baseColorFactor = color; - gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.0; - gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.9; + } - } else { + if ( material.isMeshStandardMaterial ) { - gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.5; - gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.5; + gltfMaterial.pbrMetallicRoughness.metallicFactor = material.metalness; + gltfMaterial.pbrMetallicRoughness.roughnessFactor = material.roughness; - } + } else if ( material.isMeshBasicMaterial ) { - // pbrMetallicRoughness.metallicRoughnessTexture - if ( material.metalnessMap || material.roughnessMap ) { + gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.0; + gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.9; - if ( material.metalnessMap === material.roughnessMap ) { + } else { - var metalRoughMapDef = { index: processTexture( material.metalnessMap ) }; - applyTextureTransform( metalRoughMapDef, material.metalnessMap ); - gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.5; + gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.5; - } else { + } - console.warn( 'GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' ); + // pbrMetallicRoughness.metallicRoughnessTexture + if ( material.metalnessMap || material.roughnessMap ) { - } + if ( material.metalnessMap === material.roughnessMap ) { - } + var metalRoughMapDef = { index: processTexture( material.metalnessMap ) }; + applyTextureTransform( metalRoughMapDef, material.metalnessMap ); + gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; - // pbrMetallicRoughness.baseColorTexture - if ( material.map ) { + } else { - var baseColorMapDef = { index: processTexture( material.map ) }; - applyTextureTransform( baseColorMapDef, material.map ); - gltfMaterial.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + console.warn( 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' ); } - if ( material.isMeshBasicMaterial || - material.isLineBasicMaterial || - material.isPointsMaterial ) { + } - } else { + // pbrMetallicRoughness.baseColorTexture + if ( material.map ) { - // emissiveFactor - var emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray(); + var baseColorMapDef = { index: processTexture( material.map ) }; + applyTextureTransform( baseColorMapDef, material.map ); + gltfMaterial.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; - if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) { + } - gltfMaterial.emissiveFactor = emissive; + if ( material.isMeshBasicMaterial || + material.isLineBasicMaterial || + material.isPointsMaterial ) { - } + } else { - // emissiveTexture - if ( material.emissiveMap ) { + // emissiveFactor + var emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray(); - var emissiveMapDef = { index: processTexture( material.emissiveMap ) }; - applyTextureTransform( emissiveMapDef, material.emissiveMap ); - gltfMaterial.emissiveTexture = emissiveMapDef; + if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) { - } + gltfMaterial.emissiveFactor = emissive; } - // normalTexture - if ( material.normalMap ) { + // emissiveTexture + if ( material.emissiveMap ) { + + var emissiveMapDef = { index: processTexture( material.emissiveMap ) }; + applyTextureTransform( emissiveMapDef, material.emissiveMap ); + gltfMaterial.emissiveTexture = emissiveMapDef; + + } - var normalMapDef = { index: processTexture( material.normalMap ) }; + } - if ( material.normalScale.x !== - 1 ) { + // normalTexture + if ( material.normalMap ) { - if ( material.normalScale.x !== material.normalScale.y ) { + var normalMapDef = { index: processTexture( material.normalMap ) }; - console.warn( 'GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' ); + if ( material.normalScale.x !== - 1 ) { - } + if ( material.normalScale.x !== material.normalScale.y ) { - normalMapDef.scale = material.normalScale.x; + console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' ); } - applyTextureTransform( normalMapDef, material.normalMap ); - - gltfMaterial.normalTexture = normalMapDef; + normalMapDef.scale = material.normalScale.x; } - // occlusionTexture - if ( material.aoMap ) { + applyTextureTransform( normalMapDef, material.normalMap ); - var occlusionMapDef = { - index: processTexture( material.aoMap ), - texCoord: 1 - }; + gltfMaterial.normalTexture = normalMapDef; - if ( material.aoMapIntensity !== 1.0 ) { + } - occlusionMapDef.strength = material.aoMapIntensity; + // occlusionTexture + if ( material.aoMap ) { - } + var occlusionMapDef = { + index: processTexture( material.aoMap ), + texCoord: 1 + }; - applyTextureTransform( occlusionMapDef, material.aoMap ); + if ( material.aoMapIntensity !== 1.0 ) { - gltfMaterial.occlusionTexture = occlusionMapDef; + occlusionMapDef.strength = material.aoMapIntensity; } - // alphaMode - if ( material.transparent || material.alphaTest > 0.0 ) { + applyTextureTransform( occlusionMapDef, material.aoMap ); - gltfMaterial.alphaMode = material.opacity < 1.0 ? 'BLEND' : 'MASK'; + gltfMaterial.occlusionTexture = occlusionMapDef; - // Write alphaCutoff if it's non-zero and different from the default (0.5). - if ( material.alphaTest > 0.0 && material.alphaTest !== 0.5 ) { - - gltfMaterial.alphaCutoff = material.alphaTest; + } - } + // alphaMode + if ( material.transparent || material.alphaTest > 0.0 ) { - } + gltfMaterial.alphaMode = material.opacity < 1.0 ? 'BLEND' : 'MASK'; - // doubleSided - if ( material.side === DoubleSide ) { + // Write alphaCutoff if it's non-zero and different from the default (0.5). + if ( material.alphaTest > 0.0 && material.alphaTest !== 0.5 ) { - gltfMaterial.doubleSided = true; + gltfMaterial.alphaCutoff = material.alphaTest; } - if ( material.name !== '' ) { + } - gltfMaterial.name = material.name; + // doubleSided + if ( material.side === DoubleSide ) { - } + gltfMaterial.doubleSided = true; - if ( Object.keys( material.userData ).length > 0 ) { + } - gltfMaterial.extras = serializeUserData( material ); + if ( material.name !== '' ) { - } + gltfMaterial.name = material.name; - outputJSON.materials.push( gltfMaterial ); + } - var index = outputJSON.materials.length - 1; - cachedData.materials.set( material, index ); + if ( Object.keys( material.userData ).length > 0 ) { - return index; + gltfMaterial.extras = serializeUserData( material ); } - /** - * Process mesh - * @param {Mesh} mesh Mesh to process - * @return {Integer} Index of the processed mesh in the "meshes" array - */ - function processMesh( mesh ) { + outputJSON.materials.push( gltfMaterial ); - var cacheKey = mesh.geometry.uuid + ':' + mesh.material.uuid; - if ( cachedData.meshes.has( cacheKey ) ) { + var index = outputJSON.materials.length - 1; + cachedData.materials.set( material, index ); - return cachedData.meshes.get( cacheKey ); + return index; - } + } - var geometry = mesh.geometry; + /** + * Process mesh + * @param {Mesh} mesh Mesh to process + * @return {Integer} Index of the processed mesh in the "meshes" array + */ + function processMesh( mesh ) { - var mode; + var cacheKey = mesh.geometry.uuid + ':' + mesh.material.uuid; + if ( cachedData.meshes.has( cacheKey ) ) { - // Use the correct mode - if ( mesh.isLineSegments ) { + return cachedData.meshes.get( cacheKey ); - mode = WEBGL_CONSTANTS.LINES; + } - } else if ( mesh.isLineLoop ) { + var geometry = mesh.geometry; - mode = WEBGL_CONSTANTS.LINE_LOOP; + var mode; - } else if ( mesh.isLine ) { + // Use the correct mode + if ( mesh.isLineSegments ) { - mode = WEBGL_CONSTANTS.LINE_STRIP; + mode = WEBGL_CONSTANTS.LINES; - } else if ( mesh.isPoints ) { + } else if ( mesh.isLineLoop ) { - mode = WEBGL_CONSTANTS.POINTS; + mode = WEBGL_CONSTANTS.LINE_LOOP; - } else { + } else if ( mesh.isLine ) { - if ( ! geometry.isBufferGeometry ) { + mode = WEBGL_CONSTANTS.LINE_STRIP; - console.warn( 'GLTFExporter: Exporting Geometry will increase file size. Use BufferGeometry instead.' ); + } else if ( mesh.isPoints ) { - var geometryTemp = new BufferGeometry(); - geometryTemp.fromGeometry( geometry ); - geometry = geometryTemp; + mode = WEBGL_CONSTANTS.POINTS; - } + } else { - if ( mesh.drawMode === TriangleFanDrawMode ) { + if ( ! geometry.isBufferGeometry ) { - console.warn( 'GLTFExporter: TriangleFanDrawMode and wireframe incompatible.' ); - mode = WEBGL_CONSTANTS.TRIANGLE_FAN; + console.warn( 'GLTFExporter: Exporting Geometry will increase file size. Use BufferGeometry instead.' ); - } else if ( mesh.drawMode === TriangleStripDrawMode ) { + var geometryTemp = new BufferGeometry(); + geometryTemp.fromGeometry( geometry ); + geometry = geometryTemp; - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINE_STRIP : WEBGL_CONSTANTS.TRIANGLE_STRIP; + } - } else { + if ( mesh.drawMode === TriangleFanDrawMode ) { - mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; + console.warn( 'GLTFExporter: TriangleFanDrawMode and wireframe incompatible.' ); + mode = WEBGL_CONSTANTS.TRIANGLE_FAN; - } + } else if ( mesh.drawMode === TriangleStripDrawMode ) { + + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINE_STRIP : WEBGL_CONSTANTS.TRIANGLE_STRIP; + + } else { + + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES; } - var gltfMesh = {}; + } - var attributes = {}; - var primitives = []; - var targets = []; + var gltfMesh = {}; - // Conversion between attributes names in threejs and gltf spec - var nameConversion = { + var attributes = {}; + var primitives = []; + var targets = []; - uv: 'TEXCOORD_0', - uv2: 'TEXCOORD_1', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0' + // Conversion between attributes names in threejs and gltf spec + var nameConversion = { - }; + uv: 'TEXCOORD_0', + uv2: 'TEXCOORD_1', + color: 'COLOR_0', + skinWeight: 'WEIGHTS_0', + skinIndex: 'JOINTS_0' - var originalNormal = geometry.getAttribute( 'normal' ); + }; - if ( originalNormal !== undefined && ! isNormalizedNormalAttribute( originalNormal ) ) { + var originalNormal = geometry.getAttribute( 'normal' ); - console.warn( 'GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); + if ( originalNormal !== undefined && ! isNormalizedNormalAttribute( originalNormal ) ) { - geometry.addAttribute( 'normal', createNormalizedNormalAttribute( originalNormal ) ); + console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); - } + geometry.addAttribute( 'normal', createNormalizedNormalAttribute( originalNormal ) ); - // @QUESTION Detect if .vertexColors = VertexColors? - // For every attribute create an accessor - var modifiedAttribute = null; - for ( var attributeName in geometry.attributes ) { + } - var attribute = geometry.attributes[ attributeName ]; - attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); - if (attributeName == 'PRIMITIVEID') - continue; + // @QUESTION Detect if .vertexColors = VertexColors? + // For every attribute create an accessor + var modifiedAttribute = null; + for ( var attributeName in geometry.attributes ) { - if ( cachedData.attributes.has( attribute ) ) { + var attribute = geometry.attributes[ attributeName ]; + attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); - attributes[ attributeName ] = cachedData.attributes.get( attribute ); - continue; + if ( cachedData.attributes.has( attribute ) ) { - } + attributes[ attributeName ] = cachedData.attributes.get( attribute ); + continue; - // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. - modifiedAttribute = null; - var array = attribute.array; - if ( attributeName === 'JOINTS_0' && - ! ( array instanceof Uint16Array ) && - ! ( array instanceof Uint8Array ) ) { + } - console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); - modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); + // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + modifiedAttribute = null; + var array = attribute.array; + if ( attributeName === 'JOINTS_0' && + ! ( array instanceof Uint16Array ) && + ! ( array instanceof Uint8Array ) ) { - } + console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); + modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); - if ( attributeName.substr( 0, 5 ) !== 'MORPH' ) { + } - var accessor = processAccessor( modifiedAttribute || attribute, geometry ); - if ( accessor !== null ) { + if ( attributeName.substr( 0, 5 ) !== 'MORPH' ) { - attributes[ attributeName ] = accessor; - cachedData.attributes.set( attribute, accessor ); + var accessor = processAccessor( modifiedAttribute || attribute, geometry ); + if ( accessor !== null ) { - } + attributes[ attributeName ] = accessor; + cachedData.attributes.set( attribute, accessor ); } } - if ( originalNormal !== undefined ) geometry.addAttribute( 'normal', originalNormal ); + } - // Skip if no exportable attributes found - if ( Object.keys( attributes ).length === 0 ) { + if ( originalNormal !== undefined ) geometry.addAttribute( 'normal', originalNormal ); - return null; + // Skip if no exportable attributes found + if ( Object.keys( attributes ).length === 0 ) { - } + return null; - // Morph targets - if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { + } - var weights = []; - var targetNames = []; - var reverseDictionary = {}; + // Morph targets + if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { - if ( mesh.morphTargetDictionary !== undefined ) { + var weights = []; + var targetNames = []; + var reverseDictionary = {}; - for ( var key in mesh.morphTargetDictionary ) { + if ( mesh.morphTargetDictionary !== undefined ) { - reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; + for ( var key in mesh.morphTargetDictionary ) { - } + reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key; } - for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - - var target = {}; + } - var warned = false; + for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) { - for ( var attributeName in geometry.morphAttributes ) { + var target = {}; - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. + var warned = false; - if ( attributeName !== 'position' && attributeName !== 'normal' ) { + for ( var attributeName in geometry.morphAttributes ) { - if ( ! warned ) { + // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. + // Three.js doesn't support TANGENT yet. - console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); - warned = true; + if ( attributeName !== 'position' && attributeName !== 'normal' ) { - } + if ( ! warned ) { - continue; + console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); + warned = true; } - var attribute = geometry.morphAttributes[ attributeName ][ i ]; - var gltfAttributeName = attributeName.toUpperCase(); + continue; - // 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 + } - var baseAttribute = geometry.attributes[ attributeName ]; + var attribute = geometry.morphAttributes[ attributeName ][ i ]; + var gltfAttributeName = attributeName.toUpperCase(); - if ( cachedData.attributes.has( attribute ) ) { + // 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 - target[ gltfAttributeName ] = cachedData.attributes.get( attribute ); - continue; + var baseAttribute = geometry.attributes[ attributeName ]; - } + if ( cachedData.attributes.has( attribute ) ) { - // Clones attribute not to override - var relativeAttribute = attribute.clone(); + target[ gltfAttributeName ] = cachedData.attributes.get( attribute ); + continue; - for ( var j = 0, jl = attribute.count; j < jl; j ++ ) { + } - relativeAttribute.setXYZ( - j, - attribute.getX( j ) - baseAttribute.getX( j ), - attribute.getY( j ) - baseAttribute.getY( j ), - attribute.getZ( j ) - baseAttribute.getZ( j ) - ); + // Clones attribute not to override + var relativeAttribute = attribute.clone(); - } + for ( var j = 0, jl = attribute.count; j < jl; j ++ ) { - target[ gltfAttributeName ] = processAccessor( relativeAttribute, geometry ); - cachedData.attributes.set( baseAttribute, target[ gltfAttributeName ] ); + relativeAttribute.setXYZ( + j, + attribute.getX( j ) - baseAttribute.getX( j ), + attribute.getY( j ) - baseAttribute.getY( j ), + attribute.getZ( j ) - baseAttribute.getZ( j ) + ); } - targets.push( target ); - - weights.push( mesh.morphTargetInfluences[ i ] ); - if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); + target[ gltfAttributeName ] = processAccessor( relativeAttribute, geometry ); + cachedData.attributes.set( baseAttribute, target[ gltfAttributeName ] ); } - gltfMesh.weights = weights; + targets.push( target ); - if ( targetNames.length > 0 ) { + weights.push( mesh.morphTargetInfluences[ i ] ); + if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); - gltfMesh.extras = {}; - gltfMesh.extras.targetNames = targetNames; + } - } + gltfMesh.weights = weights; - } + if ( targetNames.length > 0 ) { - var extras = ( Object.keys( geometry.userData ).length > 0 ) ? serializeUserData( geometry ) : undefined; + gltfMesh.extras = {}; + gltfMesh.extras.targetNames = targetNames; - var forceIndices = options.forceIndices; - var isMultiMaterial = Array.isArray( mesh.material ); + } - if ( isMultiMaterial && geometry.groups.length === 0 ) return null; + } - if ( ! forceIndices && geometry.index === null && isMultiMaterial ) { + var extras = ( Object.keys( geometry.userData ).length > 0 ) ? serializeUserData( geometry ) : undefined; - // temporal workaround. - console.warn( 'GLTFExporter: Creating index for non-indexed multi-material mesh.' ); - forceIndices = true; + var forceIndices = options.forceIndices; + var isMultiMaterial = Array.isArray( mesh.material ); - } + if ( isMultiMaterial && geometry.groups.length === 0 ) return null; - var didForceIndices = false; + if ( ! forceIndices && geometry.index === null && isMultiMaterial ) { - if ( geometry.index === null && forceIndices ) { + // temporal workaround. + console.warn( 'THREE.GLTFExporter: Creating index for non-indexed multi-material mesh.' ); + forceIndices = true; - var indices = []; + } - for ( var i = 0, il = geometry.attributes.position.count; i < il; i ++ ) { + var didForceIndices = false; - indices[ i ] = i; + if ( geometry.index === null && forceIndices ) { - } + var indices = []; - geometry.setIndex( indices ); + for ( var i = 0, il = geometry.attributes.position.count; i < il; i ++ ) { - didForceIndices = true; + indices[ i ] = i; } - var materials = isMultiMaterial ? mesh.material : [ mesh.material ]; - var groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; + geometry.setIndex( indices ); - for ( var i = 0, il = groups.length; i < il; i ++ ) { + didForceIndices = true; - var primitive = { - mode: mode, - attributes: attributes, - }; + } - if ( extras ) primitive.extras = extras; + var materials = isMultiMaterial ? mesh.material : [ mesh.material ]; + var groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; - if ( targets.length > 0 ) primitive.targets = targets; + for ( var i = 0, il = groups.length; i < il; i ++ ) { - if ( geometry.index !== null ) { + var primitive = { + mode: mode, + attributes: attributes, + }; - if ( cachedData.attributes.has( geometry.index ) ) { + if ( extras ) primitive.extras = extras; - primitive.indices = cachedData.attributes.get( geometry.index ); - } else { + if ( targets.length > 0 ) primitive.targets = targets; - primitive.indices = processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); - cachedData.attributes.set( geometry.index, primitive.indices ); - } - if (primitive.indices === null) - delete primitive.indices - } + if ( geometry.index !== null ) { - var material = processMaterial( materials[ groups[ i ].materialIndex ] ); + if ( cachedData.attributes.has( geometry.index ) ) { - if ( material !== null ) { + primitive.indices = cachedData.attributes.get( geometry.index ); - primitive.material = material; + } else { - } + primitive.indices = processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); + cachedData.attributes.set( geometry.index, primitive.indices ); - primitives.push( primitive ); + } } - if ( didForceIndices ) { + var material = processMaterial( materials[ groups[ i ].materialIndex ] ); + + if ( material !== null ) { - geometry.setIndex( null ); + primitive.material = material; } - gltfMesh.primitives = primitives; + primitives.push( primitive ); - if ( ! outputJSON.meshes ) { + } - outputJSON.meshes = []; + if ( didForceIndices ) { - } + geometry.setIndex( null ); + + } - outputJSON.meshes.push( gltfMesh ); + gltfMesh.primitives = primitives; - var index = outputJSON.meshes.length - 1; - cachedData.meshes.set( cacheKey, index ); + if ( ! outputJSON.meshes ) { - return index; + outputJSON.meshes = []; } - /** - * Process camera - * @param {Camera} camera Camera to process - * @return {Integer} Index of the processed mesh in the "camera" array - */ - function processCamera( camera ) { + outputJSON.meshes.push( gltfMesh ); - if ( ! outputJSON.cameras ) { + var index = outputJSON.meshes.length - 1; + cachedData.meshes.set( cacheKey, index ); - outputJSON.cameras = []; + return index; - } + } - var isOrtho = camera.isOrthographicCamera; + /** + * Process camera + * @param {Camera} camera Camera to process + * @return {Integer} Index of the processed mesh in the "camera" array + */ + function processCamera( camera ) { - var gltfCamera = { + if ( ! outputJSON.cameras ) { - type: isOrtho ? 'orthographic' : 'perspective' + outputJSON.cameras = []; - }; + } - if ( isOrtho ) { + var isOrtho = camera.isOrthographicCamera; - gltfCamera.orthographic = { + var gltfCamera = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near + type: isOrtho ? 'orthographic' : 'perspective' - }; + }; - } else { + if ( isOrtho ) { - gltfCamera.perspective = { + gltfCamera.orthographic = { - aspectRatio: camera.aspect, - yfov: Math.degToRad( camera.fov ), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near + xmag: camera.right * 2, + ymag: camera.top * 2, + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near - }; + }; - } + } else { - if ( camera.name !== '' ) { + gltfCamera.perspective = { - gltfCamera.name = camera.type; + aspectRatio: camera.aspect, + yfov: Math.degToRad( camera.fov ), + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near - } + }; - outputJSON.cameras.push( gltfCamera ); + } - return outputJSON.cameras.length - 1; + if ( camera.name !== '' ) { - } + gltfCamera.name = camera.type; - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {AnimationClip} clip - * @param {Object3D} root - * @return {number} - */ - function processAnimation( clip, root ) { + } - if ( ! outputJSON.animations ) { + outputJSON.cameras.push( gltfCamera ); - outputJSON.animations = []; + return outputJSON.cameras.length - 1; - } + } - clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); + /** + * Creates glTF animation entry from AnimationClip object. + * + * Status: + * - Only properties listed in PATH_PROPERTIES may be animated. + * + * @param {AnimationClip} clip + * @param {Object3D} root + * @return {number} + */ + function processAnimation( clip, root ) { - var tracks = clip.tracks; - var channels = []; - var samplers = []; + if ( ! outputJSON.animations ) { - for ( var i = 0; i < tracks.length; ++ i ) { + outputJSON.animations = []; - var track = tracks[ i ]; - var trackBinding = PropertyBinding.parseTrackName( track.name ); - var trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); - var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; + } - if ( trackBinding.objectName === 'bones' ) { + clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root ); - if ( trackNode.isSkinnedMesh === true ) { + var tracks = clip.tracks; + var channels = []; + var samplers = []; - trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); + for ( var i = 0; i < tracks.length; ++ i ) { - } else { + var track = tracks[ i ]; + var trackBinding = PropertyBinding.parseTrackName( track.name ); + var trackNode = PropertyBinding.findNode( root, trackBinding.nodeName ); + var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; - trackNode = undefined; + if ( trackBinding.objectName === 'bones' ) { - } + if ( trackNode.isSkinnedMesh === true ) { - } + trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); - if ( ! trackNode || ! trackProperty ) { + } else { - console.warn( 'GLTFExporter: Could not export animation track "%s".', track.name ); - return null; + trackNode = undefined; } - var inputItemSize = 1; - var outputItemSize = track.values.length / track.times.length; + } - if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { + if ( ! trackNode || ! trackProperty ) { - outputItemSize /= trackNode.morphTargetInfluences.length; + console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); + return null; - } + } - var interpolation; + var inputItemSize = 1; + var outputItemSize = track.values.length / track.times.length; - // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE + if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { - // 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 ) { + outputItemSize /= trackNode.morphTargetInfluences.length; - 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; + var interpolation; - } else if ( track.getInterpolation() === InterpolateDiscrete ) { + // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - interpolation = 'STEP'; + // 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 ) { - } else { + interpolation = 'CUBICSPLINE'; - interpolation = 'LINEAR'; + // 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 ) { - samplers.push( { + interpolation = 'STEP'; - input: processAccessor( new BufferAttribute( track.times, inputItemSize ) ), - output: processAccessor( new BufferAttribute( track.values, outputItemSize ) ), - interpolation: interpolation + } else { - } ); + interpolation = 'LINEAR'; - channels.push( { + } - sampler: samplers.length - 1, - target: { - node: nodeMap.get( trackNode ), - path: trackProperty - } + samplers.push( { - } ); + input: processAccessor( new BufferAttribute( track.times, inputItemSize ) ), + output: processAccessor( new BufferAttribute( track.values, outputItemSize ) ), + interpolation: interpolation - } + } ); - outputJSON.animations.push( { + channels.push( { - name: clip.name || 'clip_' + outputJSON.animations.length, - samplers: samplers, - channels: channels + sampler: samplers.length - 1, + target: { + node: nodeMap.get( trackNode ), + path: trackProperty + } } ); - return outputJSON.animations.length - 1; - } - function processSkin( object ) { - - var node = outputJSON.nodes[ nodeMap.get( object ) ]; + outputJSON.animations.push( { - var skeleton = object.skeleton; - var rootJoint = object.skeleton.bones[ 0 ]; + name: clip.name || 'clip_' + outputJSON.animations.length, + samplers: samplers, + channels: channels - if ( rootJoint === undefined ) return null; + } ); - var joints = []; - var inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); + return outputJSON.animations.length - 1; - for ( var i = 0; i < skeleton.bones.length; ++ i ) { + } - joints.push( nodeMap.get( skeleton.bones[ i ] ) ); + function processSkin( object ) { - skeleton.boneInverses[ i ].toArray( inverseBindMatrices, i * 16 ); + var node = outputJSON.nodes[ nodeMap.get( object ) ]; - } + var skeleton = object.skeleton; + var rootJoint = object.skeleton.bones[ 0 ]; - if ( outputJSON.skins === undefined ) { + if ( rootJoint === undefined ) return null; - outputJSON.skins = []; + var joints = []; + var inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); - } + for ( var i = 0; i < skeleton.bones.length; ++ i ) { - outputJSON.skins.push( { + joints.push( nodeMap.get( skeleton.bones[ i ] ) ); - inverseBindMatrices: processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), - joints: joints, - skeleton: nodeMap.get( rootJoint ) + skeleton.boneInverses[ i ].toArray( inverseBindMatrices, i * 16 ); - } ); + } - var skinIndex = node.skin = outputJSON.skins.length - 1; + if ( outputJSON.skins === undefined ) { - return skinIndex; + outputJSON.skins = []; } - function processLight( light ) { + outputJSON.skins.push( { - var lightDef = {}; + inverseBindMatrices: processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ), + joints: joints, + skeleton: nodeMap.get( rootJoint ) - if ( light.name ) lightDef.name = light.name; + } ); - lightDef.color = light.color.toArray(); + var skinIndex = node.skin = outputJSON.skins.length - 1; - lightDef.intensity = light.intensity; + return skinIndex; - if ( light.isDirectionalLight ) { + } - lightDef.type = 'directional'; + function processLight( light ) { - } else if ( light.isPointLight ) { + var lightDef = {}; - lightDef.type = 'point'; - if ( light.distance > 0 ) lightDef.range = light.distance; + if ( light.name ) lightDef.name = light.name; - } else if ( light.isSpotLight ) { + lightDef.color = light.color.toArray(); - lightDef.type = 'spot'; - if ( light.distance > 0 ) lightDef.range = light.distance; - lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; - lightDef.spot.outerConeAngle = light.angle; + lightDef.intensity = light.intensity; - } + if ( light.isDirectionalLight ) { - if ( light.decay !== undefined && light.decay !== 2 ) { + lightDef.type = 'directional'; - console.warn( 'GLTFExporter: Light decay may be lost. glTF is physically-based, ' - + 'and expects light.decay=2.' ); + } else if ( light.isPointLight ) { - } + lightDef.type = 'point'; + if ( light.distance > 0 ) lightDef.range = light.distance; - if ( light.target - && ( light.target.parent !== light - || light.target.position.x !== 0 - || light.target.position.y !== 0 - || light.target.position.z !== - 1 ) ) { + } else if ( light.isSpotLight ) { - console.warn( 'GLTFExporter: Light direction may be lost. For best results, ' - + 'make light.target a child of the light with position 0,0,-1.' ); + lightDef.type = 'spot'; + if ( light.distance > 0 ) lightDef.range = light.distance; + lightDef.spot = {}; + lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; + lightDef.spot.outerConeAngle = light.angle; - } + } - var lights = outputJSON.extensions[ 'KHR_lights_punctual' ].lights; - lights.push( lightDef ); - return lights.length - 1; + 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.' ); } - /** - * Process Object3D node - * @param {Object3D} node Object3D to processNode - * @return {Integer} Index of the node in the nodes list - */ - function processNode( object ) { + if ( light.target + && ( light.target.parent !== light + || light.target.position.x !== 0 + || light.target.position.y !== 0 + || light.target.position.z !== - 1 ) ) { - if ( ! outputJSON.nodes ) { + 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.' ); - outputJSON.nodes = []; + } - } + var lights = outputJSON.extensions[ 'KHR_lights_punctual' ].lights; + lights.push( lightDef ); + return lights.length - 1; - var gltfNode = {}; + } - if ( options.trs ) { + /** + * Process Object3D node + * @param {Object3D} node Object3D to processNode + * @return {Integer} Index of the node in the nodes list + */ + function processNode( object ) { - var rotation = object.quaternion.toArray(); - var position = object.position.toArray(); - var scale = object.scale.toArray(); + if ( ! outputJSON.nodes ) { - if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { + outputJSON.nodes = []; - gltfNode.rotation = rotation; + } - } + var gltfNode = {}; - if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { + if ( options.trs ) { - gltfNode.translation = position; + var rotation = object.quaternion.toArray(); + var position = object.position.toArray(); + var scale = object.scale.toArray(); - } + if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { - if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { + gltfNode.rotation = rotation; - gltfNode.scale = scale; + } - } + if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { - } else { + gltfNode.translation = position; - object.updateMatrix(); - if ( ! equalArray( object.matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ) ) { + } - gltfNode.matrix = object.matrix.elements; + if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { - } + gltfNode.scale = scale; } - // We don't export empty strings name because it represents no-name in Three.js. - if ( object.name !== '' ) { + } else { + + object.updateMatrix(); + if ( ! equalArray( object.matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ) ) { - gltfNode.name = String( object.name ); + gltfNode.matrix = object.matrix.elements; } - if ( object.userData && Object.keys( object.userData ).length > 0 ) { + } - gltfNode.extras = serializeUserData( object ); + // We don't export empty strings name because it represents no-name in Three.js. + if ( object.name !== '' ) { - } + gltfNode.name = String( object.name ); - if ( object.isMesh || object.isLine || object.isPoints ) { + } - var mesh = processMesh( object ); + if ( object.userData && Object.keys( object.userData ).length > 0 ) { - if ( mesh !== null ) { + gltfNode.extras = serializeUserData( object ); - gltfNode.mesh = mesh; + } - } + if ( object.isMesh || object.isLine || object.isPoints ) { - } else if ( object.isCamera ) { + var mesh = processMesh( object ); - gltfNode.camera = processCamera( object ); + if ( mesh !== null ) { - } else if ( object.isDirectionalLight || object.isPointLight || object.isSpotLight ) { + gltfNode.mesh = mesh; - if ( ! extensionsUsed[ 'KHR_lights_punctual' ] ) { + } - outputJSON.extensions = outputJSON.extensions || {}; - outputJSON.extensions[ 'KHR_lights_punctual' ] = { lights: [] }; - extensionsUsed[ 'KHR_lights_punctual' ] = true; + } else if ( object.isCamera ) { - } + gltfNode.camera = processCamera( object ); - gltfNode.extensions = gltfNode.extensions || {}; - gltfNode.extensions[ 'KHR_lights_punctual' ] = { light: processLight( object ) }; + } else if ( object.isDirectionalLight || object.isPointLight || object.isSpotLight ) { - } else if ( object.isLight ) { + if ( ! extensionsUsed[ 'KHR_lights_punctual' ] ) { - console.warn( `GLTFExporter: light ${object.name} of type ${object.constructor.name}: Only directional, point, and spot lights are supported.` ); - return null; + outputJSON.extensions = outputJSON.extensions || {}; + outputJSON.extensions[ 'KHR_lights_punctual' ] = { lights: [] }; + extensionsUsed[ 'KHR_lights_punctual' ] = true; } - if ( object.isSkinnedMesh ) { + gltfNode.extensions = gltfNode.extensions || {}; + gltfNode.extensions[ 'KHR_lights_punctual' ] = { light: processLight( object ) }; - skins.push( object ); + } else if ( object.isLight ) { - } + console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.' ); + return null; - if ( object.children.length > 0 ) { + } - var children = []; + if ( object.isSkinnedMesh ) { - for ( var i = 0, l = object.children.length; i < l; i ++ ) { + skins.push( object ); - var child = object.children[ i ]; + } - if ( child.visible || options.onlyVisible === false ) { + if ( object.children.length > 0 ) { - var node = processNode( child ); + var children = []; - if ( node !== null ) { + for ( var i = 0, l = object.children.length; i < l; i ++ ) { - children.push( node ); + var child = object.children[ i ]; - } + if ( child.visible || options.onlyVisible === false ) { - } + var node = processNode( child ); - } + if ( node !== null ) { - if ( children.length > 0 ) { + children.push( node ); - gltfNode.children = children; + } } - } - outputJSON.nodes.push( gltfNode ); + if ( children.length > 0 ) { + + gltfNode.children = children; - var nodeIndex = outputJSON.nodes.length - 1; - nodeMap.set( object, nodeIndex ); + } - return nodeIndex; } - /** - * Process Scene - * @param {Scene} node Scene to process - */ - function processScene( scene ) { + outputJSON.nodes.push( gltfNode ); - if ( ! outputJSON.scenes ) { + var nodeIndex = outputJSON.nodes.length - 1; + nodeMap.set( object, nodeIndex ); - outputJSON.scenes = []; - outputJSON.scene = 0; + return nodeIndex; - } + } - var gltfScene = { + /** + * Process Scene + * @param {Scene} node Scene to process + */ + function processScene( scene ) { - nodes: [] + if ( ! outputJSON.scenes ) { - }; + outputJSON.scenes = []; + outputJSON.scene = 0; - if ( scene.name !== '' ) { + } - gltfScene.name = scene.name; + var gltfScene = { - } + nodes: [] - if ( scene.userData && Object.keys( scene.userData ).length > 0 ) { + }; - gltfScene.extras = serializeUserData( scene ); + if ( scene.name !== '' ) { - } + gltfScene.name = scene.name; - outputJSON.scenes.push( gltfScene ); + } - var nodes = []; + if ( scene.userData && Object.keys( scene.userData ).length > 0 ) { - for ( var i = 0, l = scene.children.length; i < l; i ++ ) { + gltfScene.extras = serializeUserData( scene ); - var child = scene.children[ i ]; + } - if ( child.visible || options.onlyVisible === false ) { + outputJSON.scenes.push( gltfScene ); - var node = processNode( child ); + var nodes = []; - if ( node !== null ) { + for ( var i = 0, l = scene.children.length; i < l; i ++ ) { - nodes.push( node ); + var child = scene.children[ i ]; - } + if ( child.visible || options.onlyVisible === false ) { - } + var node = processNode( child ); - } + if ( node !== null ) { - if ( nodes.length > 0 ) { + nodes.push( node ); - gltfScene.nodes = nodes; + } } } - /** - * Creates a Scene to hold a list of objects and parse it - * @param {Array} objects List of objects to process - */ - function processObjects( objects ) { + if ( nodes.length > 0 ) { - var scene = new Scene(); - scene.name = 'AuxScene'; + gltfScene.nodes = nodes; - for ( var 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 ] ); + } - } + /** + * Creates a Scene to hold a list of objects and parse it + * @param {Array} objects List of objects to process + */ + function processObjects( objects ) { - processScene( scene ); + var scene = new Scene(); + scene.name = 'AuxScene'; - } + for ( var i = 0; i < objects.length; i ++ ) { - function processInput( input ) { + // 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 ] ); - input = input instanceof Array ? input : [ input ]; + } - var objectsWithoutScene = []; + processScene( scene ); - for ( var i = 0; i < input.length; i ++ ) { + } - if ( input[ i ] instanceof Scene ) { + function processInput( input ) { - processScene( input[ i ] ); + input = input instanceof Array ? input : [ input ]; - } else { + var objectsWithoutScene = []; - objectsWithoutScene.push( input[ i ] ); + for ( var i = 0; i < input.length; i ++ ) { - } + if ( input[ i ] instanceof Scene ) { - } + processScene( input[ i ] ); - if ( objectsWithoutScene.length > 0 ) { + } else { - processObjects( objectsWithoutScene ); + objectsWithoutScene.push( input[ i ] ); } - for ( var i = 0; i < skins.length; ++ i ) { + } - processSkin( skins[ i ] ); + if ( objectsWithoutScene.length > 0 ) { - } + processObjects( objectsWithoutScene ); - for ( var i = 0; i < options.animations.length; ++ i ) { + } - processAnimation( options.animations[ i ], input[ 0 ] ); + for ( var i = 0; i < skins.length; ++ i ) { - } + processSkin( skins[ i ] ); } - processInput( input ); - - Promise.all( pending ).then( function () { + for ( var i = 0; i < options.animations.length; ++ i ) { - // Merge buffers. - var blob = new Blob( buffers, { type: 'application/octet-stream' } ); + processAnimation( options.animations[ i ], input[ 0 ] ); - // Declare extensions. - var extensionsUsedList = Object.keys( extensionsUsed ); - if ( extensionsUsedList.length > 0 ) outputJSON.extensionsUsed = extensionsUsedList; - - if ( outputJSON.buffers && outputJSON.buffers.length > 0 ) { + } - // Update bytelength of the single buffer. - outputJSON.buffers[ 0 ].byteLength = blob.size; + } - var reader = new window.FileReader(); + processInput( input ); - if ( options.binary === true ) { + Promise.all( pending ).then( function () { - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + // Merge buffers. + var blob = new Blob( buffers, { type: 'application/octet-stream' } ); - var GLB_HEADER_BYTES = 12; - var GLB_HEADER_MAGIC = 0x46546C67; - var GLB_VERSION = 2; + // Declare extensions. + var extensionsUsedList = Object.keys( extensionsUsed ); + if ( extensionsUsedList.length > 0 ) outputJSON.extensionsUsed = extensionsUsedList; - var GLB_CHUNK_PREFIX_BYTES = 8; - var GLB_CHUNK_TYPE_JSON = 0x4E4F534A; - var GLB_CHUNK_TYPE_BIN = 0x004E4942; + if ( outputJSON.buffers && outputJSON.buffers.length > 0 ) { - reader.readAsArrayBuffer( blob ); - reader.onloadend = function () { + // Update bytelength of the single buffer. + outputJSON.buffers[ 0 ].byteLength = blob.size; - // Binary chunk. - var binaryChunk = getPaddedArrayBuffer( reader.result ); - var binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true ); - binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true ); + var reader = new window.FileReader(); - // JSON chunk. - var jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( outputJSON ) ), 0x20 ); - var jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) ); - jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true ); - jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true ); + if ( options.binary === true ) { - // GLB header. - var header = new ArrayBuffer( GLB_HEADER_BYTES ); - var headerView = new DataView( header ); - headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); - headerView.setUint32( 4, GLB_VERSION, true ); - var totalByteLength = GLB_HEADER_BYTES - + jsonChunkPrefix.byteLength + jsonChunk.byteLength - + binaryChunkPrefix.byteLength + binaryChunk.byteLength; - headerView.setUint32( 8, totalByteLength, true ); + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - var glbBlob = new Blob( [ - header, - jsonChunkPrefix, - jsonChunk, - binaryChunkPrefix, - binaryChunk - ], { type: 'application/octet-stream' } ); + var GLB_HEADER_BYTES = 12; + var GLB_HEADER_MAGIC = 0x46546C67; + var GLB_VERSION = 2; - var glbReader = new window.FileReader(); - glbReader.readAsArrayBuffer( glbBlob ); - glbReader.onloadend = function () { + var GLB_CHUNK_PREFIX_BYTES = 8; + var GLB_CHUNK_TYPE_JSON = 0x4E4F534A; + var GLB_CHUNK_TYPE_BIN = 0x004E4942; - onDone( glbReader.result ); + reader.readAsArrayBuffer( blob ); + reader.onloadend = function () { - }; + // Binary chunk. + var binaryChunk = getPaddedArrayBuffer( reader.result ); + var 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. + var jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( outputJSON ) ), 0x20 ); + var 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. + var header = new ArrayBuffer( GLB_HEADER_BYTES ); + var headerView = new DataView( header ); + headerView.setUint32( 0, GLB_HEADER_MAGIC, true ); + headerView.setUint32( 4, GLB_VERSION, true ); + var totalByteLength = GLB_HEADER_BYTES + + jsonChunkPrefix.byteLength + jsonChunk.byteLength + + binaryChunkPrefix.byteLength + binaryChunk.byteLength; + headerView.setUint32( 8, totalByteLength, true ); + + var glbBlob = new Blob( [ + header, + jsonChunkPrefix, + jsonChunk, + binaryChunkPrefix, + binaryChunk + ], { type: 'application/octet-stream' } ); + + var glbReader = new window.FileReader(); + glbReader.readAsArrayBuffer( glbBlob ); + glbReader.onloadend = function () { + + onDone( glbReader.result ); }; - } else { + }; - reader.readAsDataURL( blob ); - reader.onloadend = function () { + } else { - var base64data = reader.result; - outputJSON.buffers[ 0 ].uri = base64data; - onDone( outputJSON ); + reader.readAsDataURL( blob ); + reader.onloadend = function () { - }; + var base64data = reader.result; + outputJSON.buffers[ 0 ].uri = base64data; + onDone( outputJSON ); - } + }; - } else { + } - onDone( outputJSON ); + } else { - } + onDone( outputJSON ); - } ); + } - } + } ); - }; + } - GLTFExporter.Utils = { +}; - insertKeyframe: function ( track, time ) { +GLTFExporter.Utils = { - var tolerance = 0.001; // 1ms - var valueSize = track.getValueSize(); + insertKeyframe: function ( track, time ) { - var times = new track.TimeBufferType( track.times.length + 1 ); - var values = new track.ValueBufferType( track.values.length + valueSize ); - var interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); + var tolerance = 0.001; // 1ms + var valueSize = track.getValueSize(); - var index; + var times = new track.TimeBufferType( track.times.length + 1 ); + var values = new track.ValueBufferType( track.values.length + valueSize ); + var interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) ); - if ( track.times.length === 0 ) { + var index; - times[ 0 ] = time; + if ( track.times.length === 0 ) { - for ( var i = 0; i < valueSize; i ++ ) { + times[ 0 ] = time; - values[ i ] = 0; + for ( var i = 0; i < valueSize; i ++ ) { - } + values[ i ] = 0; - index = 0; + } - } else if ( time < track.times[ 0 ] ) { + index = 0; - if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; + } else if ( time < track.times[ 0 ] ) { - times[ 0 ] = time; - times.set( track.times, 1 ); + if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0; - values.set( interpolant.evaluate( time ), 0 ); - values.set( track.values, valueSize ); + times[ 0 ] = time; + times.set( track.times, 1 ); - index = 0; + values.set( interpolant.evaluate( time ), 0 ); + values.set( track.values, valueSize ); - } else if ( time > track.times[ track.times.length - 1 ] ) { + index = 0; - if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) { + } else if ( time > track.times[ track.times.length - 1 ] ) { - return 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 ); + times[ times.length - 1 ] = time; + times.set( track.times, 0 ); - index = times.length - 1; + values.set( track.values, 0 ); + values.set( interpolant.evaluate( time ), track.values.length ); - } else { + index = times.length - 1; - for ( var i = 0; i < track.times.length; i ++ ) { + } else { - if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; + for ( var i = 0; i < track.times.length; i ++ ) { - if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { + if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i; - times.set( track.times.slice( 0, i + 1 ), 0 ); - times[ i + 1 ] = time; - times.set( track.times.slice( i + 1 ), i + 2 ); + if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) { - 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 ); + times.set( track.times.slice( 0, i + 1 ), 0 ); + times[ i + 1 ] = time; + times.set( track.times.slice( i + 1 ), i + 2 ); - index = i + 1; + 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 ); - break; + index = i + 1; - } + break; } } - track.times = times; - track.values = values; + } - return index; + track.times = times; + track.values = values; - }, + return index; - mergeMorphTargetTracks: function ( clip, root ) { + }, - var tracks = []; - var mergedTracks = {}; - var sourceTracks = clip.tracks; + mergeMorphTargetTracks: function ( clip, root ) { - for ( var i = 0; i < sourceTracks.length; ++ i ) { + var tracks = []; + var mergedTracks = {}; + var sourceTracks = clip.tracks; - var sourceTrack = sourceTracks[ i ]; - var sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); - var sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); + for ( var i = 0; i < sourceTracks.length; ++ i ) { - if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) { + var sourceTrack = sourceTracks[ i ]; + var sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name ); + var sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName ); - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push( sourceTrack ); - continue; + 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( 'GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' ); + } - } + if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete + && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) { - console.warn( 'GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); + if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - sourceTrack = sourceTrack.clone(); - sourceTrack.setInterpolation( InterpolateLinear ); + // 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.' ); } - var targetCount = sourceTrackNode.morphTargetInfluences.length; - var targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; + console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' ); - if ( targetIndex === undefined ) { + sourceTrack = sourceTrack.clone(); + sourceTrack.setInterpolation( InterpolateLinear ); - throw new Error( 'GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - - } + } - var mergedTrack; + var targetCount = sourceTrackNode.morphTargetInfluences.length; + var targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ]; - // 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 ) { + if ( targetIndex === undefined ) { - mergedTrack = sourceTrack.clone(); + throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex ); - var values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); + } - for ( var j = 0; j < mergedTrack.times.length; j ++ ) { + var mergedTrack; - values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; + // 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(); - mergedTrack.name = '.morphTargetInfluences'; - mergedTrack.values = values; + var values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length ); - mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; - tracks.push( mergedTrack ); + for ( var j = 0; j < mergedTrack.times.length; j ++ ) { - continue; + values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ]; } - var mergedKeyframeIndex = 0; - var sourceKeyframeIndex = 0; - var sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); + mergedTrack.name = '.morphTargetInfluences'; + mergedTrack.values = values; - mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; + mergedTracks[ sourceTrackNode.uuid ] = mergedTrack; + tracks.push( mergedTrack ); - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for ( var j = 0; j < mergedTrack.times.length; j ++ ) { + continue; - mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); + } - } + var mergedKeyframeIndex = 0; + var sourceKeyframeIndex = 0; + var sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) ); - // 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 ( var j = 0; j < sourceTrack.times.length; j ++ ) { + mergedTrack = mergedTracks[ sourceTrackNode.uuid ]; - var keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); - mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for ( var j = 0; j < mergedTrack.times.length; j ++ ) { - } + mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] ); } - clip.tracks = tracks; + // 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 ( var j = 0; j < sourceTrack.times.length; j ++ ) { - return clip; + var keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] ); + mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ]; + + } } - }; - return GLTFExporter; -} )(); + clip.tracks = tracks; + + return clip; + + } + +}; export { GLTFExporter }; diff --git a/examples/jsm/loaders/MTLLoader.js b/examples/jsm/loaders/MTLLoader.js new file mode 100644 index 0000000000..5967e331ca --- /dev/null +++ b/examples/jsm/loaders/MTLLoader.js @@ -0,0 +1,602 @@ +/** + * Loads a Wavefront .mtl file specifying materials + * + * @author angelxuanchang + */ + +import { + BackSide, + ClampToEdgeWrapping, + Color, + DefaultLoadingManager, + DoubleSide, + FileLoader, + FrontSide, + Loader, + LoaderUtils, + MeshPhongMaterial, + MirroredRepeatWrapping, + RepeatWrapping, + TextureLoader, + Vector2 +} from "../../../build/three.module.js"; + +var MTLLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + +}; + +MTLLoader.prototype = { + + constructor: MTLLoader, + + /** + * Loads and parses a MTL asset from a URL. + * + * @param {String} url - URL to the MTL file. + * @param {Function} [onLoad] - Callback invoked with the loaded object. + * @param {Function} [onProgress] - Callback for download progress. + * @param {Function} [onError] - Callback for download errors. + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to load. + */ + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var path = ( this.path === undefined ) ? LoaderUtils.extractUrlBase( url ) : this.path; + + var loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( text, path ) ); + + }, onProgress, onError ); + + }, + + /** + * Set base path for resolving references. + * If set this path will be prepended to each loaded and found reference. + * + * @see setResourcePath + * @param {String} path + * @return {MTLLoader} + * + * @example + * mtlLoader.setPath( 'assets/obj/' ); + * mtlLoader.load( 'my.mtl', ... ); + */ + setPath: function ( path ) { + + this.path = path; + return this; + + }, + + /** + * Set base path for additional resources like textures. + * + * @see setPath + * @param {String} path + * @return {MTLLoader} + * + * @example + * mtlLoader.setPath( 'assets/obj/' ); + * mtlLoader.setResourcePath( 'assets/textures/' ); + * mtlLoader.load( 'my.mtl', ... ); + */ + setResourcePath: function ( path ) { + + this.resourcePath = path; + return this; + + }, + + setTexturePath: function ( path ) { + + console.warn( 'THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath().' ); + return this.setResourcePath( path ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setMaterialOptions: function ( value ) { + + this.materialOptions = value; + return this; + + }, + + /** + * Parses a MTL file. + * + * @param {String} text - Content of MTL file + * @return {MTLLoader.MaterialCreator} + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to parse. + */ + parse: function ( text, path ) { + + var lines = text.split( '\n' ); + var info = {}; + var delimiter_pattern = /\s+/; + var materialsInfo = {}; + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim(); + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + // Blank line or comment ignore + continue; + + } + + var pos = line.indexOf( ' ' ); + + var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; + key = key.toLowerCase(); + + var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; + value = value.trim(); + + if ( key === 'newmtl' ) { + + // New material + + info = { name: value }; + materialsInfo[ value ] = info; + + } else { + + if ( key === 'ka' || key === 'kd' || key === 'ks' || key ==='ke' ) { + + var ss = value.split( delimiter_pattern, 3 ); + info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; + + } else { + + info[ key ] = value; + + } + + } + + } + + var materialCreator = new MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions ); + materialCreator.setCrossOrigin( this.crossOrigin ); + materialCreator.setManager( this.manager ); + materialCreator.setMaterials( materialsInfo ); + return materialCreator; + + } + +}; + +/** + * Create a new THREE-MTLLoader.MaterialCreator + * @param baseUrl - Url relative to which textures are loaded + * @param options - Set of options on how to construct the materials + * side: Which side to apply the material + * FrontSide (default), BackSide, DoubleSide + * wrap: What type of wrapping to apply for textures + * RepeatWrapping (default), ClampToEdgeWrapping, MirroredRepeatWrapping + * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 + * Default: false, assumed to be already normalized + * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's + * Default: false + * @constructor + */ + +MTLLoader.MaterialCreator = function ( baseUrl, options ) { + + this.baseUrl = baseUrl || ''; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + this.side = ( this.options && this.options.side ) ? this.options.side : FrontSide; + this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : RepeatWrapping; + +}; + +MTLLoader.MaterialCreator.prototype = { + + constructor: MTLLoader.MaterialCreator, + + crossOrigin: 'anonymous', + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setManager: function ( value ) { + + this.manager = value; + + }, + + setMaterials: function ( materialsInfo ) { + + this.materialsInfo = this.convert( materialsInfo ); + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + }, + + convert: function ( materialsInfo ) { + + if ( ! this.options ) return materialsInfo; + + var converted = {}; + + for ( var mn in materialsInfo ) { + + // Convert materials info into normalized form based on options + + var mat = materialsInfo[ mn ]; + + var covmat = {}; + + converted[ mn ] = covmat; + + for ( var prop in mat ) { + + var save = true; + var value = mat[ prop ]; + var lprop = prop.toLowerCase(); + + switch ( lprop ) { + + case 'kd': + case 'ka': + case 'ks': + + // Diffuse color (color under white light) using RGB values + + if ( this.options && this.options.normalizeRGB ) { + + value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; + + } + + if ( this.options && this.options.ignoreZeroRGBs ) { + + if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) { + + // ignore + + save = false; + + } + + } + + break; + + default: + + break; + + } + + if ( save ) { + + covmat[ lprop ] = value; + + } + + } + + } + + return converted; + + }, + + preload: function () { + + for ( var mn in this.materialsInfo ) { + + this.create( mn ); + + } + + }, + + getIndex: function ( materialName ) { + + return this.nameLookup[ materialName ]; + + }, + + getAsArray: function () { + + var index = 0; + + for ( var mn in this.materialsInfo ) { + + this.materialsArray[ index ] = this.create( mn ); + this.nameLookup[ mn ] = index; + index ++; + + } + + return this.materialsArray; + + }, + + create: function ( materialName ) { + + if ( this.materials[ materialName ] === undefined ) { + + this.createMaterial_( materialName ); + + } + + return this.materials[ materialName ]; + + }, + + createMaterial_: function ( materialName ) { + + // Create material + + var scope = this; + var mat = this.materialsInfo[ materialName ]; + var params = { + + name: materialName, + side: this.side + + }; + + function resolveURL( baseUrl, url ) { + + if ( typeof url !== 'string' || url === '' ) + return ''; + + // Absolute URL + if ( /^https?:\/\//i.test( url ) ) return url; + + return baseUrl + url; + + } + + function setMapForType( mapType, value ) { + + if ( params[ mapType ] ) return; // Keep the first encountered texture + + var texParams = scope.getTextureParams( value, params ); + var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); + + map.repeat.copy( texParams.scale ); + map.offset.copy( texParams.offset ); + + map.wrapS = scope.wrap; + map.wrapT = scope.wrap; + + params[ mapType ] = map; + + } + + for ( var prop in mat ) { + + var value = mat[ prop ]; + var n; + + if ( value === '' ) continue; + + switch ( prop.toLowerCase() ) { + + // Ns is material specular exponent + + case 'kd': + + // Diffuse color (color under white light) using RGB values + + params.color = new Color().fromArray( value ); + + break; + + case 'ks': + + // Specular color (color when light is reflected from shiny surface) using RGB values + params.specular = new Color().fromArray( value ); + + break; + + case 'ke': + + // Emissive using RGB values + params.emissive = new Color().fromArray( value ); + + break; + + case 'map_kd': + + // Diffuse texture map + + setMapForType( "map", value ); + + break; + + case 'map_ks': + + // Specular map + + setMapForType( "specularMap", value ); + + break; + + case 'map_ke': + + // Emissive map + + setMapForType( "emissiveMap", value ); + + break; + + case 'norm': + + setMapForType( "normalMap", value ); + + break; + + case 'map_bump': + case 'bump': + + // Bump texture map + + setMapForType( "bumpMap", value ); + + break; + + case 'map_d': + + // Alpha map + + setMapForType( "alphaMap", value ); + params.transparent = true; + + break; + + case 'ns': + + // The specular exponent (defines the focus of the specular highlight) + // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. + + params.shininess = parseFloat( value ); + + break; + + case 'd': + n = parseFloat( value ); + + if ( n < 1 ) { + + params.opacity = n; + params.transparent = true; + + } + + break; + + case 'tr': + n = parseFloat( value ); + + if ( this.options && this.options.invertTrProperty ) n = 1 - n; + + if ( n > 0 ) { + + params.opacity = 1 - n; + params.transparent = true; + + } + + break; + + default: + break; + + } + + } + + this.materials[ materialName ] = new MeshPhongMaterial( params ); + return this.materials[ materialName ]; + + }, + + getTextureParams: function ( value, matParams ) { + + var texParams = { + + scale: new Vector2( 1, 1 ), + offset: new Vector2( 0, 0 ) + + }; + + var items = value.split( /\s+/ ); + var pos; + + pos = items.indexOf( '-bm' ); + + if ( pos >= 0 ) { + + matParams.bumpScale = parseFloat( items[ pos + 1 ] ); + items.splice( pos, 2 ); + + } + + pos = items.indexOf( '-s' ); + + if ( pos >= 0 ) { + + texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + pos = items.indexOf( '-o' ); + + if ( pos >= 0 ) { + + texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + texParams.url = items.join( ' ' ).trim(); + return texParams; + + }, + + loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { + + var texture; + var loader = Loader.Handlers.get( url ); + var manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager; + + if ( loader === null ) { + + loader = new TextureLoader( manager ); + + } + + if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); + texture = loader.load( url, onLoad, onProgress, onError ); + + if ( mapping !== undefined ) texture.mapping = mapping; + + return texture; + + } + +}; + +export { MTLLoader }; -- GitLab