const THREE = RK; THREE.Vector3 = THREE.Vec3; /* ─ │ └ ├ ┌ ┬ ─ single item ─ two items ─ two items last, children ┬ root ['', '', '┬ '] [true, true] ├── first child ['', '├─', '─ '] [false, false] ├─┬ child parent ['', '├─', '┬ '] [false, true] │ ├── first child ['│ ', '├─', '─ '] [false, false] │ └─┬ child parent ['│ ', '└─', '┬ '] [true, true] │ ├── first child ['│ ', '├─', '─ '] [false, false] │ └── last child ['│ ', '└─', '─ '] [true, false] └── last child ['', '└─', '─ '] [true, false] parent is last child ? ' ' : '│ ' nSibling part 1: ├─ lastSibling part 1: └─ hasChildren part 2: ┬ noChildren part 2: ─ */ window.console.tree = (obj, prop, toString, forEach) => { if (!toString) toString = x => x === null ? 'null' : x === undefined ? 'undefined' : x.toString() if (!prop) { prop = obj.children ? 'children' : obj.childNodes ? 'childNodes' : null } const output = [] const walk = (obj, part0, lastSibling) => { let children = obj[prop] if (children && !Array.isArray(children)) children = Array.from(children) const len = children && children.length || 0 const part1 = lastSibling ? '└─' : '├─' const part2 = len ? '┬ ' : '─ ' output.push(part0 + part1 + part2 + toString(obj)) if (forEach) forEach(obj, part0 + part1 + part2) children.forEach((child, i) => { walk(child, part0 + (lastSibling ? ' ' : '│ '), i === len - 1) }) } walk(obj, ' ', true) return output.join('\n') } window.console.tree.withObject = (obj, prop, toString) => window.console.tree(obj, prop, toString, (x, y) => console.log(y, x)) && undefined window.console.tree.groups = (obj, prop) => { if (!prop) { prop = obj.children ? 'children' : obj.childNodes ? 'childNodes' : null } const walk = (obj) => { let children = obj[prop] if (children && !Array.isArray(children)) children = Array.from(children) if (children && children.length) { console.group(obj) children.forEach(walk) console.groupEnd() } else { console.log(obj) } } walk(obj) } window.walk = function(root) { /* const walk = (obj, i, tree, more) => { const indent = tree + (more ? ' ├' : ' └') //"".padRight(i * 2, ' ') console.log(indent + obj.toString()) if (obj && obj.children) obj.children.forEach((x, y) => walk(x, i + 1, tree + (y < obj.children.length - 1 ? ' │' : ' '), y < obj.children.length - 1)) } */ const walk = (obj, part0, lastSibling) => { const part1 = lastSibling ? '└─' : '├─' const part2 = obj.children.length ? '┬ ' : '─ ' console.log(part0 + part1 + part2 + obj.toString()) obj.children.forEach((child, i) => { walk(child, part0 + (lastSibling ? ' ' : '│ '), i === obj.children.length - 1) }) } walk(root, ' ', true) } Object.keys(RK).forEach(key => { const Type = RK[key] if (Type && Type.prototype && !Type.prototype.hasOwnProperty('toString')) { Type.prototype.toString = function() { const description = [key, this.name, this.id].filter(x => x === 0 || x).join(' ') return `[${description}]` } } }) /** * @author fernandojsg / http://fernandojsg.com * @author Don McCurdy / https://www.donmccurdy.com * @author Takahiro / https://github.com/takahirox */ //------------------------------------------------------------------------------ // Constants //------------------------------------------------------------------------------ var WEBGL_CONSTANTS = { POINTS: 0x0000, LINES: 0x0001, LINE_LOOP: 0x0002, LINE_STRIP: 0x0003, TRIANGLES: 0x0004, TRIANGLE_STRIP: 0x0005, TRIANGLE_FAN: 0x0006, UNSIGNED_BYTE: 0x1401, UNSIGNED_SHORT: 0x1403, FLOAT: 0x1406, UNSIGNED_INT: 0x1405, ARRAY_BUFFER: 0x8892, ELEMENT_ARRAY_BUFFER: 0x8893, NEAREST: 0x2600, LINEAR: 0x2601, NEAREST_MIPMAP_NEAREST: 0x2700, LINEAR_MIPMAP_NEAREST: 0x2701, NEAREST_MIPMAP_LINEAR: 0x2702, LINEAR_MIPMAP_LINEAR: 0x2703 }; var THREE_TO_WEBGL = { // @TODO Replace with computed property name [THREE.*] when available on es6 1003: WEBGL_CONSTANTS.NEAREST, 1004: WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST, 1005: WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR, 1006: WEBGL_CONSTANTS.LINEAR, 1007: WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST, 1008: WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR }; var PATH_PROPERTIES = { scale: 'scale', position: 'translation', quaternion: 'rotation', morphTargetInfluences: 'weights' }; //------------------------------------------------------------------------------ // GLTF Exporter //------------------------------------------------------------------------------ THREE.GLTFExporter = function () {}; THREE.GLTFExporter.prototype = { constructor: THREE.GLTFExporter, /** * Parse scenes and generate GLTF output * @param {THREE.Scene or [THREE.Scenes]} input THREE.Scene or Array of THREE.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 }; 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: { version: "2.0", generator: "THREE.GLTFExporter" } }; var byteOffset = 0; var buffers = []; var pending = []; var nodeMap = new Map(); var skins = []; var extensionsUsed = {}; var cachedData = { attributes: new Map(), materials: new Map(), textures: new Map() }; var cachedCanvas; /** * 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; } var array = new Uint8Array( new ArrayBuffer( text.length ) ); for ( var i = 0, il = text.length; i < il; i ++ ) { var value = text.charCodeAt( i ); // Replacing multi-byte character with space(0x20). array[ i ] = value > 0xFF ? 0x20 : value; } return array.buffer; } /** * Get the min and max vectors from the given attribute * @param {THREE.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 = { min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ), max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY ) }; 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 THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height ); } /** * Checks if normal attribute values are normalized. * * @param {THREE.BufferAttribute} normal * @returns {Boolean} * */ function isNormalizedNormalAttribute( normal ) { if ( cachedData.attributes.has( normal ) ) { return false; } var v = new THREE.Vector3(); 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; } return true; } /** * Creates normalized normal buffer attribute. * * @param {THREE.BufferAttribute} normal * @returns {THREE.BufferAttribute} * */ function createNormalizedNormalAttribute( normal ) { if ( cachedData.attributes.has( normal ) ) { return cachedData.textures.get( normal ); } var attribute = normal.clone(); var v = new THREE.Vector3(); for ( var i = 0, il = attribute.count; i < il; i ++ ) { v.fromArray( attribute.array, i * 3 ); if ( v.x === 0 && v.y === 0 && v.z === 0 ) { // if values can't be normalized set (1, 0, 0) v.setX( 1.0 ); } else { v.normalize(); } v.toArray( attribute.array, i * 3 ); } cachedData.attributes.set( normal, attribute ); return attribute; } /** * 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 ) { return Math.ceil( bufferSize / 4 ) * 4; } /** * 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 ) { paddingByte = paddingByte || 0; var paddedLength = getPaddedBufferSize( arrayBuffer.byteLength ); if ( paddedLength !== arrayBuffer.byteLength ) { var array = new Uint8Array( paddedLength ); array.set( new Uint8Array( arrayBuffer ) ); if ( paddingByte !== 0 ) { for ( var i = arrayBuffer.byteLength; i < paddedLength; i ++ ) { array[ i ] = paddingByte; } } return array.buffer; } return arrayBuffer; } /** * Serializes a userData. * * @param {THREE.Object3D|THREE.Material} object * @returns {Object} */ function serializeUserData( object ) { try { return JSON.parse( JSON.stringify( object.userData ) ); } catch ( error ) { console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' + 'won\'t be serialized because of JSON.stringify error - ' + error.message ); return {}; } } /** * Process a buffer to append to the default one. * @param {ArrayBuffer} buffer * @return {Integer} */ function processBuffer( buffer ) { if ( ! outputJSON.buffers ) { outputJSON.buffers = [ { byteLength: 0 } ]; } // All buffers are merged before export. buffers.push( buffer ); return 0; } /** * Process and generate a BufferView * @param {THREE.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 ) { if ( ! outputJSON.bufferViews ) { outputJSON.bufferViews = []; } // Create a new dataview and dump the attribute's array into it var componentSize; if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { componentSize = 1; } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { componentSize = 2; } else { componentSize = 4; } var byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); var dataView = new DataView( new ArrayBuffer( byteLength ) ); var offset = 0; for ( var i = start; i < start + count; i ++ ) { 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 ]; if ( componentType === WEBGL_CONSTANTS.FLOAT ) { dataView.setFloat32( offset, value, true ); } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) { dataView.setUint32( offset, value, true ); } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { dataView.setUint16( offset, value, true ); } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { dataView.setUint8( offset, value ); } offset += componentSize; } } var gltfBufferView = { buffer: processBuffer( dataView.buffer ), byteOffset: byteOffset, byteLength: byteLength }; if ( target !== undefined ) gltfBufferView.target = target; if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { // Only define byteStride for vertex attributes. gltfBufferView.byteStride = attribute.itemSize * componentSize; } byteOffset += byteLength; outputJSON.bufferViews.push( gltfBufferView ); // @TODO Merge bufferViews where possible. var output = { id: outputJSON.bufferViews.length - 1, byteLength: 0 }; return output; } /** * Process and generate a BufferView from an image Blob. * @param {Blob} blob * @return {Promise} */ function processBufferViewImage( blob ) { if ( ! outputJSON.bufferViews ) { outputJSON.bufferViews = []; } return new Promise( function ( resolve ) { var reader = new window.FileReader(); reader.readAsArrayBuffer( blob ); reader.onloadend = function () { var buffer = getPaddedArrayBuffer( reader.result ); var bufferView = { buffer: processBuffer( buffer ), byteOffset: byteOffset, byteLength: buffer.byteLength }; byteOffset += buffer.byteLength; outputJSON.bufferViews.push( bufferView ); resolve( outputJSON.bufferViews.length - 1 ); }; } ); } /** * Process attribute to generate an accessor * @param {THREE.BufferAttribute} attribute Attribute to process * @param {THREE.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 ) { var types = { 1: 'SCALAR', 2: 'VEC2', 3: 'VEC3', 4: 'VEC4', 16: 'MAT4' }; var componentType; // Detect the component type of the attribute array (float, uint or ushort) if ( attribute.array.constructor === Float32Array ) { componentType = WEBGL_CONSTANTS.FLOAT; } else if ( attribute.array.constructor === Uint32Array ) { componentType = WEBGL_CONSTANTS.UNSIGNED_INT; } else if ( attribute.array.constructor === Uint16Array ) { componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT; } else if ( attribute.array.constructor === Uint8Array ) { componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE; } else { throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); } 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 ) { var end = start + count; var end2 = geometry.drawRange.count === Infinity ? attribute.count : geometry.drawRange.start + geometry.drawRange.count; start = Math.max( start, geometry.drawRange.start ); count = Math.min( end, end2 ) - start; if ( count < 0 ) count = 0; } // Skip creating an accessor if the attribute doesn't have data to export if ( count === 0 ) { return null; } var minMax = getMinMax( attribute, start, count ); var bufferViewTarget; // If geometry isn't provided, don't infer the target usage of the bufferView. For // animation samplers, target must not be set. if ( geometry !== undefined ) { bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER; } var bufferView = processBufferView( attribute, componentType, start, count, bufferViewTarget ); var gltfAccessor = { bufferView: bufferView.id, byteOffset: bufferView.byteOffset, componentType: componentType, count: count, max: minMax.max, min: minMax.min, type: types[ attribute.itemSize ] }; if ( ! outputJSON.accessors ) { outputJSON.accessors = []; } outputJSON.accessors.push( gltfAccessor ); return outputJSON.accessors.length - 1; } /** * Process image * @param {Texture} map Texture to process * @return {Integer} Index of the processed texture in the "images" array */ function processImage( map ) { // @TODO Cache if ( ! outputJSON.images ) { outputJSON.images = []; } var mimeType = map.format === THREE.RGBAFormat ? 'image/png' : 'image/jpeg'; var gltfImage = { mimeType: mimeType }; if ( options.embedImages ) { var canvas = cachedCanvas = cachedCanvas || document.createElement( 'canvas' ); canvas.width = map.image.width; canvas.height = map.image.height; if ( options.forcePowerOfTwoTextures && ! isPowerOfTwo( map.image ) ) { console.warn( 'GLTFExporter: Resized non-power-of-two image.', map.image ); canvas.width = THREE.Math.floorPowerOfTwo( canvas.width ); canvas.height = THREE.Math.floorPowerOfTwo( canvas.height ); } var ctx = canvas.getContext( '2d' ); if ( map.flipY === true ) { ctx.translate( 0, canvas.height ); ctx.scale( 1, - 1 ); } ctx.drawImage( map.image, 0, 0, canvas.width, canvas.height ); if ( options.binary === true ) { pending.push( new Promise( function ( resolve ) { canvas.toBlob( function ( blob ) { processBufferViewImage( blob ).then( function ( bufferViewIndex ) { gltfImage.bufferView = bufferViewIndex; resolve(); } ); }, mimeType ); } ) ); } else { gltfImage.uri = canvas.toDataURL( mimeType ); } } else { gltfImage.uri = map.image.src; } outputJSON.images.push( gltfImage ); return outputJSON.images.length - 1; } /** * Process sampler * @param {Texture} map Texture to process * @return {Integer} Index of the processed texture in the "samplers" array */ function processSampler( map ) { if ( ! outputJSON.samplers ) { outputJSON.samplers = []; } 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 ] }; outputJSON.samplers.push( gltfSampler ); return outputJSON.samplers.length - 1; } /** * 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 ) ) { return cachedData.textures.get( map ); } if ( ! outputJSON.textures ) { outputJSON.textures = []; } var gltfTexture = { sampler: processSampler( map ), source: processImage( map ) }; outputJSON.textures.push( gltfTexture ); var index = outputJSON.textures.length - 1; cachedData.textures.set( map, index ); return index; } /** * Process material * @param {THREE.Material} material Material to process * @return {Integer} Index of the processed material in the "materials" array */ function processMaterial( material ) { if ( cachedData.materials.has( material ) ) { return cachedData.materials.get( material ); } if ( ! outputJSON.materials ) { outputJSON.materials = []; } if ( material.isShaderMaterial ) { console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' ); return null; } // @QUESTION Should we avoid including any attribute that has the default value? var gltfMaterial = { pbrMetallicRoughness: {} }; if ( material.isMeshBasicMaterial ) { gltfMaterial.extensions = { KHR_materials_unlit: {} }; extensionsUsed[ 'KHR_materials_unlit' ] = true; } else if ( ! material.isMeshStandardMaterial ) { console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' ); } // pbrMetallicRoughness.baseColorFactor var color = material.color.toArray().concat( [ material.opacity ] ); if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) { gltfMaterial.pbrMetallicRoughness.baseColorFactor = color; } if ( material.isMeshStandardMaterial ) { gltfMaterial.pbrMetallicRoughness.metallicFactor = material.metalness; gltfMaterial.pbrMetallicRoughness.roughnessFactor = material.roughness; } else if ( material.isMeshBasicMaterial ) { gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.0; gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.9; } else { gltfMaterial.pbrMetallicRoughness.metallicFactor = 0.5; gltfMaterial.pbrMetallicRoughness.roughnessFactor = 0.5; } // pbrMetallicRoughness.metallicRoughnessTexture if ( material.metalnessMap || material.roughnessMap ) { if ( material.metalnessMap === material.roughnessMap ) { gltfMaterial.pbrMetallicRoughness.metallicRoughnessTexture = { index: processTexture( material.metalnessMap ) }; } else { console.warn( 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.' ); } } // pbrMetallicRoughness.baseColorTexture if ( material.map ) { gltfMaterial.pbrMetallicRoughness.baseColorTexture = { index: processTexture( material.map ) }; } if ( material.isMeshBasicMaterial || material.isLineBasicMaterial || material.isPointsMaterial ) { } else { // emissiveFactor var emissive = material.emissive.clone().multiplyScalar( material.emissiveIntensity ).toArray(); if ( ! equalArray( emissive, [ 0, 0, 0 ] ) ) { gltfMaterial.emissiveFactor = emissive; } // emissiveTexture if ( material.emissiveMap ) { gltfMaterial.emissiveTexture = { index: processTexture( material.emissiveMap ) }; } } // normalTexture if ( material.normalMap ) { gltfMaterial.normalTexture = { index: processTexture( material.normalMap ) }; if ( material.normalScale.x !== - 1 ) { if ( material.normalScale.x !== material.normalScale.y ) { console.warn( 'THREE.GLTFExporter: Normal scale components are different, ignoring Y and exporting X.' ); } gltfMaterial.normalTexture.scale = material.normalScale.x; } } // occlusionTexture if ( material.aoMap ) { gltfMaterial.occlusionTexture = { index: processTexture( material.aoMap ) }; if ( material.aoMapIntensity !== 1.0 ) { gltfMaterial.occlusionTexture.strength = material.aoMapIntensity; } } // alphaMode if ( material.transparent || material.alphaTest > 0.0 ) { gltfMaterial.alphaMode = material.opacity < 1.0 ? 'BLEND' : 'MASK'; // 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; } } // doubleSided if ( material.side === THREE.DoubleSide ) { gltfMaterial.doubleSided = true; } if ( material.name !== '' ) { gltfMaterial.name = material.name; } if ( Object.keys( material.userData ).length > 0 ) { gltfMaterial.extras = serializeUserData( material ); } outputJSON.materials.push( gltfMaterial ); var index = outputJSON.materials.length - 1; cachedData.materials.set( material, index ); return index; } /** * Process mesh * @param {THREE.Mesh} mesh Mesh to process * @return {Integer} Index of the processed mesh in the "meshes" array */ function processMesh( mesh ) { var geometry = mesh.geometry; var mode; // Use the correct mode if ( mesh.isLineSegments ) { mode = WEBGL_CONSTANTS.LINES; } else if ( mesh.isLineLoop ) { mode = WEBGL_CONSTANTS.LINE_LOOP; } else if ( mesh.isLine ) { mode = WEBGL_CONSTANTS.LINE_STRIP; } else if ( mesh.isPoints ) { mode = WEBGL_CONSTANTS.POINTS; } else { if ( ! geometry.isBufferGeometry ) { var geometryTemp = new THREE.BufferGeometry(); geometryTemp.fromGeometry( geometry ); geometry = geometryTemp; } if ( mesh.drawMode === THREE.TriangleFanDrawMode ) { console.warn( 'GLTFExporter: TriangleFanDrawMode and wireframe incompatible.' ); mode = WEBGL_CONSTANTS.TRIANGLE_FAN; } else if ( mesh.drawMode === THREE.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 = []; // 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 ) ) { console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' ); geometry.addAttribute( 'normal', createNormalizedNormalAttribute( originalNormal ) ); } // @QUESTION Detect if .vertexColors = THREE.VertexColors? // For every attribute create an accessor for ( var attributeName in geometry.attributes ) { var attribute = geometry.attributes[ attributeName ]; attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase(); // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. var array = attribute.array; if ( attributeName === 'JOINTS_0' && ! ( array instanceof Uint16Array ) && ! ( array instanceof Uint8Array ) ) { console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); attribute = new THREE.BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); } if ( attributeName.substr( 0, 5 ) !== 'MORPH' ) { var accessor = processAccessor( attribute, geometry ); if ( accessor !== null ) { attributes[ attributeName ] = accessor; } } } if ( originalNormal !== undefined ) geometry.addAttribute( 'normal', originalNormal ); // Skip if no exportable attributes found if ( Object.keys( attributes ).length === 0 ) { return null; } // Morph targets if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) { var weights = []; var targetNames = []; var reverseDictionary = {}; if ( mesh.morphTargetDictionary !== undefined ) { 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 attributeName in geometry.morphAttributes ) { // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. // Three.js doesn't support TANGENT yet. if ( attributeName !== 'position' && attributeName !== 'normal' ) { if ( ! warned ) { console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' ); warned = true; } continue; } var attribute = geometry.morphAttributes[ attributeName ][ i ]; // 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 ]; // Clones attribute not to override var relativeAttribute = attribute.clone(); 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 ) ); } target[ attributeName.toUpperCase() ] = processAccessor( relativeAttribute, geometry ); } targets.push( target ); weights.push( mesh.morphTargetInfluences[ i ] ); if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] ); } gltfMesh.weights = weights; if ( targetNames.length > 0 ) { gltfMesh.extras = {}; gltfMesh.extras.targetNames = targetNames; } } var extras = ( Object.keys( geometry.userData || [] ).length > 0 ) ? serializeUserData( geometry ) : undefined; var forceIndices = options.forceIndices; var isMultiMaterial = Array.isArray( mesh.material ); if ( isMultiMaterial && mesh.geometry.groups.length === 0 ) return null; if ( ! forceIndices && geometry.index === null && isMultiMaterial ) { // temporal workaround. console.warn( 'THREE.GLTFExporter: Creating index for non-indexed multi-material mesh.' ); forceIndices = true; } var didForceIndices = false; if ( geometry.index === null && forceIndices ) { var indices = []; for ( var i = 0, il = geometry.attributes.position.count; i < il; i ++ ) { indices[ i ] = i; } geometry.setIndex( indices ); didForceIndices = true; } var materials = isMultiMaterial ? mesh.material : [ mesh.material ]; var groups = isMultiMaterial ? mesh.geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; for ( var i = 0, il = groups.length; i < il; i ++ ) { var primitive = { mode: mode, attributes: attributes, }; if ( extras ) primitive.extras = extras; if ( targets.length > 0 ) primitive.targets = targets; if ( geometry.index !== null ) { primitive.indices = processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count ); } var material = processMaterial( materials[ groups[ i ].materialIndex ] ); if ( material !== null ) { primitive.material = material; } primitives.push( primitive ); } if ( didForceIndices ) { geometry.setIndex( null ); } gltfMesh.primitives = primitives; if ( ! outputJSON.meshes ) { outputJSON.meshes = []; } outputJSON.meshes.push( gltfMesh ); return outputJSON.meshes.length - 1; } /** * Process camera * @param {THREE.Camera} camera Camera to process * @return {Integer} Index of the processed mesh in the "camera" array */ function processCamera( camera ) { if ( ! outputJSON.cameras ) { outputJSON.cameras = []; } var isOrtho = camera.isOrthographicCamera; var gltfCamera = { type: isOrtho ? 'orthographic' : 'perspective' }; if ( isOrtho ) { gltfCamera.orthographic = { xmag: camera.right * 2, ymag: camera.top * 2, zfar: camera.far <= 0 ? 0.001 : camera.far, znear: camera.near < 0 ? 0 : camera.near }; } else { gltfCamera.perspective = { aspectRatio: camera.aspect, yfov: THREE.Math.degToRad( camera.fov ) / camera.aspect, zfar: camera.far <= 0 ? 0.001 : camera.far, znear: camera.near < 0 ? 0 : camera.near }; } if ( camera.name !== '' ) { gltfCamera.name = camera.type; } outputJSON.cameras.push( gltfCamera ); return outputJSON.cameras.length - 1; } /** * Creates glTF animation entry from AnimationClip object. * * Status: * - Only properties listed in PATH_PROPERTIES may be animated. * * @param {THREE.AnimationClip} clip * @param {THREE.Object3D} root * @return {number} */ function processAnimation( clip, root ) { if ( ! outputJSON.animations ) { outputJSON.animations = []; } var channels = []; var samplers = []; for ( var i = 0; i < clip.tracks.length; ++ i ) { var track = clip.tracks[ i ]; var trackBinding = THREE.PropertyBinding.parseTrackName( track.name ); var trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName ); var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ]; if ( trackBinding.objectName === 'bones' ) { if ( trackNode.isSkinnedMesh === true ) { trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex ); } else { trackNode = undefined; } } if ( ! trackNode || ! trackProperty ) { console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); return null; } var inputItemSize = 1; var outputItemSize = track.values.length / track.times.length; if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) { outputItemSize /= trackNode.morphTargetInfluences.length; } var interpolation; // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE // Detecting glTF cubic spline interpolant by checking factory method's special property // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return // valid value from .getInterpolation(). if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) { interpolation = 'CUBICSPLINE'; // itemSize of CUBICSPLINE keyframe is 9 // (VEC3 * 3: inTangent, splineVertex, and outTangent) // but needs to be stored as VEC3 so dividing by 3 here. outputItemSize /= 3; } else if ( track.getInterpolation() === THREE.InterpolateDiscrete ) { interpolation = 'STEP'; } else { interpolation = 'LINEAR'; } samplers.push( { input: processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ), output: processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ), interpolation: interpolation } ); channels.push( { sampler: samplers.length - 1, target: { node: nodeMap.get( trackNode ), path: trackProperty } } ); } outputJSON.animations.push( { name: clip.name || 'clip_' + outputJSON.animations.length, samplers: samplers, channels: channels } ); return outputJSON.animations.length - 1; } function processSkin( object ) { var node = outputJSON.nodes[ nodeMap.get( object ) ]; var skeleton = object.skeleton; var rootJoint = object.skeleton.bones[ 0 ]; if ( rootJoint === undefined ) return null; var joints = []; var inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 ); for ( var i = 0; i < skeleton.bones.length; ++ i ) { joints.push( nodeMap.get( skeleton.bones[ i ] ) ); skeleton.boneInverses[ i ].toArray( inverseBindMatrices, i * 16 ); } if ( outputJSON.skins === undefined ) { outputJSON.skins = []; } outputJSON.skins.push( { inverseBindMatrices: processAccessor( new THREE.BufferAttribute( inverseBindMatrices, 16 ) ), joints: joints, skeleton: nodeMap.get( rootJoint ) } ); var skinIndex = node.skin = outputJSON.skins.length - 1; return skinIndex; } /** * Process Object3D node * @param {THREE.Object3D} node Object3D to processNode * @return {Integer} Index of the node in the nodes list */ function processNode( object ) { if ( object.isLight ) { console.warn( 'GLTFExporter: Unsupported node type:', object.constructor.name ); return null; } if ( ! outputJSON.nodes ) { outputJSON.nodes = []; } var gltfNode = {}; if ( options.trs ) { var rotation = object.quaternion.toArray(); var position = object.position.toArray(); var scale = object.scale.toArray(); if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) { gltfNode.rotation = rotation; } if ( ! equalArray( position, [ 0, 0, 0 ] ) ) { gltfNode.translation = position; } if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) { gltfNode.scale = scale; } } 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.matrix = object.matrix.elements; } } // 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.userData && Object.keys( object.userData ).length > 0 ) { gltfNode.extras = serializeUserData( object ); } if ( object.isMesh || object.isLine || object.isPoints ) { var mesh = processMesh( object ); if ( mesh !== null ) { gltfNode.mesh = mesh; } } else if ( object.isCamera ) { gltfNode.camera = processCamera( object ); } if ( object.isSkinnedMesh ) { skins.push( object ); } if ( object.children.length > 0 ) { var children = []; for ( var i = 0, l = object.children.length; i < l; i ++ ) { var child = object.children[ i ]; if ( child.visible || options.onlyVisible === false ) { var node = processNode( child ); if ( node !== null ) { children.push( node ); } } } if ( children.length > 0 ) { gltfNode.children = children; } } outputJSON.nodes.push( gltfNode ); var nodeIndex = outputJSON.nodes.length - 1; nodeMap.set( object, nodeIndex ); return nodeIndex; } /** * Process Scene * @param {THREE.Scene} node Scene to process */ function processScene( scene ) { if ( ! outputJSON.scenes ) { outputJSON.scenes = []; outputJSON.scene = 0; } var gltfScene = { nodes: [] }; if ( scene.name !== '' ) { gltfScene.name = scene.name; } outputJSON.scenes.push( gltfScene ); var nodes = []; for ( var i = 0, l = scene.children.length; i < l; i ++ ) { var child = scene.children[ i ]; if ( child.visible || options.onlyVisible === false ) { var node = processNode( child ); if ( node !== null ) { nodes.push( node ); } } } if ( nodes.length > 0 ) { gltfScene.nodes = nodes; } } /** * Creates a THREE.Scene to hold a list of objects and parse it * @param {Array} objects List of objects to process */ function processObjects( objects ) { var scene = new THREE.Scene(); scene.name = 'AuxScene'; 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 ] ); } processScene( scene ); } function processInput( input ) { input = input instanceof Array ? input : [ input ]; var objectsWithoutScene = []; for ( var i = 0; i < input.length; i ++ ) { if ( input[ i ] instanceof THREE.Scene ) { processScene( input[ i ] ); } else { objectsWithoutScene.push( input[ i ] ); } } if ( objectsWithoutScene.length > 0 ) { processObjects( objectsWithoutScene ); } for ( var i = 0; i < skins.length; ++ i ) { processSkin( skins[ i ] ); } for ( var i = 0; i < options.animations.length; ++ i ) { processAnimation( options.animations[ i ], input[ 0 ] ); } } processInput( input ); Promise.all( pending ).then( function () { // Merge buffers. var blob = new Blob( buffers, { type: 'application/octet-stream' } ); // 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(); if ( options.binary === true ) { // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification var GLB_HEADER_BYTES = 12; var GLB_HEADER_MAGIC = 0x46546C67; var GLB_VERSION = 2; var GLB_CHUNK_PREFIX_BYTES = 8; var GLB_CHUNK_TYPE_JSON = 0x4E4F534A; var GLB_CHUNK_TYPE_BIN = 0x004E4942; 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 () { var base64data = reader.result; outputJSON.buffers[ 0 ].uri = base64data; onDone( outputJSON ); }; } } else { onDone( outputJSON ); } } ); } }; THREE.STLExporter = function () {}; THREE.STLExporter.prototype = { constructor: THREE.STLExporter, parse: ( function () { var vector = new THREE.Vec3(); var normalMatrixWorld = new THREE.Matrix3(); return function parse( scene, options ) { if ( options === undefined ) options = {}; var binary = options.binary !== undefined ? options.binary : false; // var objects = []; var triangles = 0; scene.traverse( function ( object ) { if ( object.isMesh ) { if (object.pose && object.skeleton && typeof(object.skeleton.pose) == 'function') { console.log('Posing object', object) object.pose(); } console.log(object) var geometry = object.geometry; if ( geometry.isBufferGeometry ) { geometry = new THREE.Geometry().fromBufferGeometry( geometry ); } if ( geometry.isGeometry ) { triangles += geometry.faces.length; objects.push( { geometry: geometry, matrixWorld: object.matrixWorld, boneMatrices: object.skeleton && object.skeleton.boneMatrices } ); } } } ); if ( true ) { var output = ''; output += 'solid exported\n'; for ( var i = 0, il = objects.length; i < il; i ++ ) { var object = objects[ i ]; var vertices = object.geometry.vertices; var faces = object.geometry.faces; var matrixWorld = object.matrixWorld; var boneMatrices = object.boneMatrices; normalMatrixWorld.getNormalMatrix( matrixWorld ); for ( var j = 0, jl = faces.length; j < jl; j ++ ) { var face = faces[ j ]; vector.copy( face.normal )// .applyMatrix3( normalMatrixWorld ).normalize(); output += '\tfacet normal ' + vector.x + ' ' + vector.y + ' ' + vector.z + '\n'; output += '\t\touter loop\n'; var indices = [ face.a, face.b, face.c ]; for ( var k = 0; k < 3; k ++ ) { vector.copy( vertices[ indices[ k ] ] ).applyMatrix4( matrixWorld ); output += '\t\t\tvertex ' + vector.x + ' ' + vector.y + ' ' + vector.z + '\n'; } output += '\t\tendloop\n'; output += '\tendfacet\n'; } } output += 'endsolid exported\n'; return output; } }; }() ) }; THREE.Baker = function () {}; THREE.Baker.prototype = { constructor: THREE.Baker, parse: ( function () { var vector = new THREE.Vec3(); var normalMatrixWorld = new THREE.Matrix3(); return function parse( scene, options ) { if ( options === undefined ) options = {}; var binary = options.binary !== undefined ? options.binary : false; // var objects = []; var triangles = 0; scene.traverse( function ( object ) { if ( object.isMesh ) { if (object.pose && object.skeleton && typeof(object.skeleton.pose) == 'function') { console.log('Posing object', object) object.pose(); } console.log(object) var og = object.geometry; var geometry = object.geometry; /* FROM */ function checkBufferGeometryIntersection( object, positions, uvs, a, b, c ) { var inverseMatrix = new THREE.Matrix4(); var ray = new THREE.Ray(); var sphere = new THREE.Sphere(); var vA = new THREE.Vec3(); var vB = new THREE.Vec3(); var vC = new THREE.Vec3(); var tempA = new THREE.Vec3(); var tempB = new THREE.Vec3(); var tempC = new THREE.Vec3(); var uvA = new THREE.Vec2(); var uvB = new THREE.Vec2(); var uvC = new THREE.Vec2(); var barycoord = new THREE.Vec3(); var intersectionPoint = new THREE.Vec3(); var intersectionPointWorld = new THREE.Vec3(); vA.fromArray( positions, a * 3 ); vB.fromArray( positions, b * 3 ); vC.fromArray( positions, c * 3 ); if ( object.boneTransform ) { vA = object.boneTransform( vA, a ); vB = object.boneTransform( vB, b ); vC = object.boneTransform( vC, c ); } } var uvs, intersection; if ( geometry instanceof THREE.BufferGeometry ) { debugger; console.log('BUFFER') var a, b, c; var index = geometry.index; var attributes = geometry.attributes; var positions = attributes.position.array; if ( attributes.uv !== undefined ) { uvs = attributes.uv.array; } if ( index !== null ) { var indices = index.array; for ( var i = 0, l = indices.length; i < l; i += 3 ) { a = indices[ i ]; b = indices[ i + 1 ]; c = indices[ i + 2 ]; intersection = checkBufferGeometryIntersection( this, positions, uvs, a, b, c ); if ( intersection ) { intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indices buffer semantics intersects.push( intersection ); } } } else { for ( var i = 0, l = positions.length; i < l; i += 9 ) { a = i / 3; b = a + 1; c = a + 2; intersection = checkBufferGeometryIntersection( this, positions, uvs, a, b, c ); if ( intersection ) { intersection.index = a; // triangle number in positions buffer semantics intersects.push( intersection ); } } } } else if ( geometry instanceof THREE.Geometry ) { console.log('GEOMETRY') var fvA, fvB, fvC; //var isFaceMaterial = material instanceof THREE.MultiMaterial; //var materials = isFaceMaterial === true ? material.materials : null; var vertices = geometry.vertices; var faces = geometry.faces; var faceVertexUvs = geometry.faceVertexUvs[ 0 ]; if ( faceVertexUvs.length > 0 ) uvs = faceVertexUvs; for ( var f = 0, fl = faces.length; f < fl; f ++ ) { var face = faces[ f ]; //var faceMaterial = isFaceMaterial === true ? materials[ face.materialIndex ] : material; //if ( faceMaterial === undefined ) continue; fvA = vertices[ face.a ]; fvB = vertices[ face.b ]; fvC = vertices[ face.c ]; /* if ( faceMaterial.morphTargets === true ) { var morphTargets = geometry.morphTargets; var morphInfluences = this.morphTargetInfluences; vA.set( 0, 0, 0 ); vB.set( 0, 0, 0 ); vC.set( 0, 0, 0 ); for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { var influence = morphInfluences[ t ]; if ( influence === 0 ) continue; var targets = morphTargets[ t ].vertices; vA.addScaledVector( tempA.subVectors( targets[ face.a ], fvA ), influence ); vB.addScaledVector( tempB.subVectors( targets[ face.b ], fvB ), influence ); vC.addScaledVector( tempC.subVectors( targets[ face.c ], fvC ), influence ); } vA.add( fvA ); vB.add( fvB ); vC.add( fvC ); fvA = vA; fvB = vB; fvC = vC; } */ if ( this.boneTransform ) { console.log('YES') fvA = this.boneTransform( fvA, face.a ); fvB = this.boneTransform( fvB, face.b ); fvC = this.boneTransform( fvC, face.c ); } } } else { console.log(geometry && geometry.constructor.name || 'Non-Geo') } /* END FROM*/ if ( geometry.isBufferGeometry ) { geometry = new THREE.Geometry().fromBufferGeometry( geometry ); } /* if ( geometry.isGeometry ) { if (object.skeleton) { const allBones = object.skeleton.bones; const position = Array.from(og.attributes.position.array); const skinWeight = Array.from(og.attributes.skinWeight.array); const skinIndex = Array.from(og.attributes.skinIndex.array); // "loop each vert" geometry.vertices.forEach((v, i) => { const p = i * 3; const f = i * 4; var pos = new THREE.Vec3(position[p], position[p + 1], position[p + 2]); var weights = skinWeight.slice(i * 4, i * 4 + 4); var totalWeight = weights.reduce((a, b) => a + b); var indices = skinIndex.slice(i * 4, i * 4 + 4); var bones = indices.map((a, b) => allBones[a].matrix); // "then over each bone that affects that vert" var applied = bones.map((bone, i) => pos.clone().applyMatrix4( // "weighted if you have bone weights" bone.clone().multiplyScalar(weights[i] / totalWeight) ) ) // "accumulate that" var final = applied.reduce((a, b) => a.add(b), new THREE.Vec3(0, 0, 0)) final.multiplyScalar(1/4) //bones.forEach((bone, i) => pos.applyMatrix4(bone.clone().multiplyScalar(weights[i]))) v.set(final.x, final.y, final.z) }) /* geometry.vertices.forEach(v => { object.skeleton.bones.forEach(b => { window.xx = [v, b] v.applyMatrix4(b.matrixWorld.clone().multiplyScalar(1/object.skeleton.bones.length)) }) }) * / }; object.geometry = geometry; triangles += geometry.faces.length; objects.push( { geometry: geometry, matrixWorld: object.matrixWorld, boneMatrices: object.skeleton && object.skeleton.boneMatrices } ); } */ } } ); }; }() ) }; THREE.SkinnedMesh.prototype.boneTransform = ( function() { var clone = new THREE.Vec3(), result = new THREE.Vec3(), skinIndices = new THREE.Vec4(), skinWeights = new THREE.Vec4(); var temp = new THREE.Vec3(), tempMatrix = new THREE.Matrix4(), properties = [ 'x', 'y', 'z', 'w' ]; return function( vertex, index ) { if ( this.geometry instanceof THREE.BufferGeometry ) { var index4 = index * 4; skinIndices.fromArray( this.geometry.attributes.skinIndex.array, index4 ); skinWeights.fromArray( this.geometry.attributes.skinWeight.array, index4 ); } else if ( this.geometry instanceof THREE.Geometry ) { skinIndices.copy( this.geometry.skinIndices[ index ] ); skinWeights.copy( this.geometry.skinWeights[ index ] ); } var clone = vertex.clone().applyMatrix4( this.bindMatrix ); result.set( 0, 0, 0 ); for ( var i = 0; i < 4; i ++ ) { var skinWeight = skinWeights[ properties[ i ] ]; if ( skinWeight != 0 ) { var boneIndex = skinIndices[ properties[ i ] ]; tempMatrix.multiplyMatrices( this.skeleton.bones[ boneIndex ].matrixWorld, this.skeleton.boneInverses[ boneIndex ] ); result.add( temp.copy( clone ).applyMatrix4( tempMatrix ).multiplyScalar( skinWeight ) ); } } return clone.copy( result.applyMatrix4( this.bindMatrixInverse ) ); }; } )(); // Export scripts window.code = {} for (let pkg of Object.values(window.FuseBox.packages)) { for (let moduleName in pkg.f) { window.code[moduleName] = pkg.f[moduleName].fn.toString() } } console.log('All code saved to `window.code`. `exportCode()` to save it') window.exportCode = () => { window.console.save(window.code, 'heroforge.json') } window.bake = () => { const baker = new THREE.Baker(); baker.parse(CK.characters[0].characterDisplay.threeObj); console.log('Baked'); } window.save = (filename) => { const exporter = new THREE.GLTFExporter(); exporter.parse(CK.characters[0].characterDisplay.threeObj, data => { window.data = data; console.log('done') if (filename) console.save(JSON.stringify(window.data), `${filename}.gltf`) }) } // Your code here...