/** * @author yamahigashi https://github.com/yamahigashi * * This loader loads FBX file in *ASCII and version 7 format*. * * Support * - mesh * - skinning * - normal / uv * * Not Support * - material * - texture * - morph */ (function () { THREE.FBXLoader = function (showStatus, manager) { THREE.Loader.call(this, showStatus); this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager; this.textureLoader = null; this.textureBasePath = null; }; THREE.FBXLoader.prototype = Object.create(THREE.Loader.prototype); THREE.FBXLoader.prototype.constructor = THREE.FBXLoader; THREE.FBXLoader.prototype.load = function (url, onLoad, onProgress, onError) { var scope = this; var loader = new THREE.XHRLoader(scope.manager); // loader.setCrossOrigin( this.crossOrigin ); loader.load(url, function (text) { if (!scope.isFbxFormatASCII(text)) { console.warn('FBXLoader: !!! FBX Binary format not supported !!!'); } else if (!scope.isFbxVersionSupported(text)) { console.warn('FBXLoader: !!! FBX Version below 7 not supported !!!'); } else { scope.textureBasePath = scope.extractUrlBase(url); onLoad(scope.parse(text)); } }, onProgress, onError); }; THREE.FBXLoader.prototype.setCrossOrigin = function (value) { this.crossOrigin = value; }; THREE.FBXLoader.prototype.isFbxFormatASCII = function (body) { CORRECT = ['K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\']; var cursor = 0; var read = function (offset) { var result = body[ offset - 1 ]; body = body.slice(cursor + offset); cursor++; return result; }; for (var i = 0; i < CORRECT.length; ++i) { num = read(1); if (num == CORRECT[ i ]) { return false; } } return true; }; THREE.FBXLoader.prototype.isFbxVersionSupported = function (body) { var versionExp = /FBXVersion: (\d+)/; match = body.match(versionExp); if (match) { var version = parseInt(match[ 1 ]); console.log('FBXLoader: FBX version ' + version); return version >= 7000; } return false; }; THREE.FBXLoader.prototype.parse = function (text) { var scope = this; console.time('FBXLoader'); console.time('FBXLoader: TextParser'); var nodes = new FBXParser().parse(text); console.timeEnd('FBXLoader: TextParser'); console.time('FBXLoader: ObjectParser'); scope.hierarchy = (new Bones()).parseHierarchy(nodes); scope.weights = (new Weights()).parse(nodes, scope.hierarchy); scope.animations = (new Animation()).parse(nodes, scope.hierarchy); scope.textures = (new Textures()).parse(nodes, scope.hierarchy); console.timeEnd('FBXLoader: ObjectParser'); console.time('FBXLoader: GeometryParser'); geometries = this.parseGeometries(nodes); console.timeEnd('FBXLoader: GeometryParser'); var container = new THREE.Group(); for (var i = 0; i < geometries.length; ++i) { if (geometries[ i ] === undefined) { continue; } container.add(geometries[ i ]); //wireframe = new THREE.WireframeHelper( geometries[i], 0x00ff00 ); //container.add( wireframe ); //vnh = new THREE.VertexNormalsHelper( geometries[i], 0.6 ); //container.add( vnh ); //skh = new THREE.SkeletonHelper( geometries[i] ); //container.add( skh ); // container.add( new THREE.BoxHelper( geometries[i] ) ); } console.timeEnd('FBXLoader'); return container; }; THREE.FBXLoader.prototype.parseGeometries = function (node) { // has not geo, return [] if (!('Geometry' in node.Objects.subNodes)) { return []; } // has many var geoCount = 0; for (var geo in node.Objects.subNodes.Geometry) { if (geo.match(/^\d+$/)) { geoCount++; } } var res = []; if (geoCount > 0) { for (geo in node.Objects.subNodes.Geometry) { if (node.Objects.subNodes.Geometry[ geo ].attrType === 'Mesh') { res.push(this.parseGeometry(node.Objects.subNodes.Geometry[ geo ], node)); } } } else { res.push(this.parseGeometry(node.Objects.subNodes.Geometry, node)); } return res; }; THREE.FBXLoader.prototype.parseGeometry = function (node, nodes) { geo = (new Geometry()).parse(node); geo.addBones(this.hierarchy.hierarchy); //* var geometry = new THREE.BufferGeometry(); geometry.name = geo.name; geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(geo.vertices), 3)); if (geo.normals !== undefined && geo.normals.length > 0) { geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(geo.normals), 3)); } if (geo.uvs !== undefined && geo.uvs.length > 0) { geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(geo.uvs), 2)); } if (geo.indices !== undefined && geo.indices.length > 65535) { geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(geo.indices), 1)); } else if (geo.indices !== undefined) { geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(geo.indices), 1)); } geometry.verticesNeedUpdate = true; geometry.computeBoundingSphere(); geometry.computeBoundingBox(); // TODO: texture & material support var texture; var texs = this.textures.getById(nodes.searchConnectionParent(geo.id)); if (texs !== undefined && texs.length > 0) { if (this.textureLoader === null) { this.textureLoader = new THREE.TextureLoader(); } texture = this.textureLoader.load(this.textureBasePath + '/' + texs[ 0 ].fileName); } var material; if (texture !== undefined) { material = new THREE.MeshBasicMaterial({map: texture}); } else { material = new THREE.MeshBasicMaterial({color: 0x3300ff}); } geometry = new THREE.Geometry().fromBufferGeometry(geometry); geometry.bones = geo.bones; geometry.skinIndices = this.weights.skinIndices; geometry.skinWeights = this.weights.skinWeights; var mesh = null; if (geo.bones === undefined || geo.skins === undefined || this.animations === undefined || this.animations.length === 0) { mesh = new THREE.Mesh(geometry, material); var aConnections = nodes.Connections.properties.connections; var aModels = nodes.Objects.subNodes.Model; // console.log(nodes); for (var i = 0; i < aConnections.length; i++) { if (aConnections[i][0] === node.id) { // associo il nome della mesh pescandolo tramite la struttura connections nell'array di models mesh.name = aModels[aConnections[i][1]].attrName; if (aModels[aConnections[i][1]].properties.Lcl_Translation) { //console.log(aModels[aConnections[i][1]].properties.Lcl_Translation); var aCoordinate = aModels[aConnections[i][1]].properties.Lcl_Translation.value.split(","); // console.log(parseFloat(aModels[aConnections[i][1]].properties.Lcl_Translation.value)); for (var i = 0; i < aCoordinate.length; i++) { aCoordinate[i] = parseFloat(aCoordinate[i]); } //INVERTED AXIS Y UP TO Z UP mesh.position.x = aCoordinate[0]; mesh.position.y = aCoordinate[1]; mesh.position.z = aCoordinate[2]; } else { // console.log(aModels[aConnections[i][1]].properties.GeometricTranslation.value); } break; } } //------------------------------------------ } else { material.skinning = true; mesh = new THREE.SkinnedMesh(geometry, material); this.addAnimation(mesh, this.weights.matrices, this.animations); } return mesh; }; THREE.FBXLoader.prototype.addAnimation = function (mesh, matrices, animations) { var animationdata = {"name": 'animationtest', "fps": 30, "length": animations.length, "hierarchy": []}; for (var i = 0; i < mesh.geometry.bones.length; ++i) { var name = mesh.geometry.bones[ i ].name; name = name.replace(/.*:/, ''); animationdata.hierarchy.push({parent: mesh.geometry.bones[ i ].parent, name: name, keys: []}); } var hasCurve = function (animNode, attr) { if (animNode === undefined) { return false; } var attrNode; switch (attr) { case 'S': if (animNode.S === undefined) { return false; } attrNode = animNode.S; break; case 'R': if (animNode.R === undefined) { return false; } attrNode = animNode.R; break; case 'T': if (animNode.T === undefined) { return false; } attrNode = animNode.T; break; } if (attrNode.curves.x === undefined) { return false; } if (attrNode.curves.y === undefined) { return false; } if (attrNode.curves.z === undefined) { return false; } return true; }; var hasKeyOnFrame = function (attrNode, frame) { var x = isKeyExistOnFrame(attrNode.curves.x, frame); var y = isKeyExistOnFrame(attrNode.curves.y, frame); var z = isKeyExistOnFrame(attrNode.curves.z, frame); return x && y && z; }; var isKeyExistOnFrame = function (curve, frame) { var value = curve.values[ frame ]; return value !== undefined; }; var genKey = function (animNode, bone) { // key initialize with its bone's bind pose at first var key = {}; key.time = frame / animations.fps; // TODO: key.pos = bone.pos; key.rot = bone.rotq; key.scl = bone.scl; if (animNode === undefined) { return key; } try { if (hasCurve(animNode, 'T') && hasKeyOnFrame(animNode.T, frame)) { var pos = new THREE.Vector3( animNode.T.curves.x.values[ frame ], animNode.T.curves.y.values[ frame ], animNode.T.curves.z.values[ frame ]); key.pos = [pos.x, pos.y, pos.z]; } else { delete key.pos; } if (hasCurve(animNode, 'R') && hasKeyOnFrame(animNode.R, frame)) { var rx = degToRad(animNode.R.curves.x.values[ frame ]); var ry = degToRad(animNode.R.curves.y.values[ frame ]); var rz = degToRad(animNode.R.curves.z.values[ frame ]); var eul = new THREE.Vector3(rx, ry, rz); var rot = quatFromVec(eul.x, eul.y, eul.z); key.rot = [rot.x, rot.y, rot.z, rot.w]; } else { delete key.rot; } if (hasCurve(animNode, 'S') && hasKeyOnFrame(animNode.S, frame)) { var scl = new THREE.Vector3( animNode.S.curves.x.values[ frame ], animNode.S.curves.y.values[ frame ], animNode.S.curves.z.values[ frame ]); key.scl = [scl.x, scl.y, scl.z]; } else { delete key.scl; } } catch (e) { // curve is not full plotted console.log(bone); console.log(e); } return key; }; var bones = mesh.geometry.bones; for (frame = 0; frame < animations.frames; frame++) { for (i = 0; i < bones.length; i++) { var bone = bones[ i ]; var animNode = animations.curves[ i ]; for (var j = 0; j < animationdata.hierarchy.length; j++) { if (animationdata.hierarchy[ j ].name === bone.name) { animationdata.hierarchy[ j ].keys.push(genKey(animNode, bone)); } } } } if (mesh.geometry.animations === undefined) { mesh.geometry.animations = []; } mesh.geometry.animations.push(THREE.AnimationClip.parseAnimation(animationdata, mesh.geometry.bones)); }; THREE.FBXLoader.prototype.parseMaterials = function (node) { // has not mat, return [] if (!('Material' in node.subNodes)) { return []; } // has many var matCount = 0; for (var mat in node.subNodes.Materials) { if (mat.match(/^\d+$/)) { matCount++; } } var res = []; if (matCount > 0) { for (mat in node.subNodes.Material) { res.push(parseMaterial(node.subNodes.Material[ mat ])); } } else { res.push(parseMaterial(node.subNodes.Material)); } return res; }; // TODO THREE.FBXLoader.prototype.parseMaterial = function (node) { }; THREE.FBXLoader.prototype.loadFile = function (url, onLoad, onProgress, onError, responseType) { var loader = new THREE.XHRLoader(this.manager); loader.setResponseType(responseType); var request = loader.load(url, function (result) { onLoad(result); }, onProgress, onError); return request; }; THREE.FBXLoader.prototype.loadFileAsBuffer = function (url, onload, onProgress, onError) { this.loadFile(url, onLoad, onProgress, onError, 'arraybuffer'); }; THREE.FBXLoader.prototype.loadFileAsText = function (url, onLoad, onProgress, onError) { this.loadFile(url, onLoad, onProgress, onError, 'text'); }; /* ----------------------------------------------------------------- */ function FBXNodes() {} FBXNodes.prototype.add = function (key, val) { this[ key ] = val; }; FBXNodes.prototype.searchConnectionParent = function (id) { if (this.__cache_search_connection_parent === undefined) { this.__cache_search_connection_parent = []; } if (this.__cache_search_connection_parent[ id ] !== undefined) { return this.__cache_search_connection_parent[ id ]; } else { this.__cache_search_connection_parent[ id ] = []; } var conns = this.Connections.properties.connections; var results = []; for (var i = 0; i < conns.length; ++i) { if (conns[ i ][ 0 ] == id) { // 0 means scene root var res = conns[ i ][ 1 ] === 0 ? -1 : conns[ i ][ 1 ]; results.push(res); } } if (results.length > 0) { this.__cache_search_connection_parent[ id ] = this.__cache_search_connection_parent[ id ].concat(results); return results; } else { this.__cache_search_connection_parent[ id ] = [-1]; return [-1]; } }; FBXNodes.prototype.searchConnectionChildren = function (id) { if (this.__cache_search_connection_children === undefined) { this.__cache_search_connection_children = []; } if (this.__cache_search_connection_children[ id ] !== undefined) { return this.__cache_search_connection_children[ id ]; } else { this.__cache_search_connection_children[ id ] = []; } var conns = this.Connections.properties.connections; var res = []; for (var i = 0; i < conns.length; ++i) { if (conns[ i ][ 1 ] == id) { // 0 means scene root res.push(conns[ i ][ 0 ] === 0 ? -1 : conns[ i ][ 0 ]); // there may more than one kid, then search to the end } } if (res.length > 0) { this.__cache_search_connection_children[ id ] = this.__cache_search_connection_children[ id ].concat(res); return res; } else { this.__cache_search_connection_children[ id ] = [-1]; return [-1]; } }; FBXNodes.prototype.searchConnectionType = function (id, to) { var key = id + ',' + to; // TODO: to hash if (this.__cache_search_connection_type === undefined) { this.__cache_search_connection_type = ''; } if (this.__cache_search_connection_type[ key ] !== undefined) { return this.__cache_search_connection_type[ key ]; } else { this.__cache_search_connection_type[ key ] = ''; } var conns = this.Connections.properties.connections; //bbb for (var i = 0; i < conns.length; ++i) { if (conns[ i ][ 0 ] == id && conns[ i ][ 1 ] == to) { // 0 means scene root this.__cache_search_connection_type[ key ] = conns[ i ][ 2 ]; return conns[ i ][ 2 ]; } } this.__cache_search_connection_type[ id ] = null; return null; }; function FBXParser() {} FBXParser.prototype = { // constructor: FBXParser, // ------------ node stack manipulations ---------------------------------- getPrevNode: function () { return this.nodeStack[ this.currentIndent - 2 ]; }, getCurrentNode: function () { return this.nodeStack[ this.currentIndent - 1 ]; }, getCurrentProp: function () { return this.currentProp; }, pushStack: function (node) { this.nodeStack.push(node); this.currentIndent += 1; }, popStack: function () { this.nodeStack.pop(); this.currentIndent -= 1; }, setCurrentProp: function (val, name) { this.currentProp = val; this.currentPropName = name; }, // ----------parse --------------------------------------------------- parse: function (text) { this.currentIndent = 0; this.allNodes = new FBXNodes(); this.nodeStack = []; this.currentProp = []; this.currentPropName = ''; var split = text.split("\n"); for (var line in split) { var l = split[ line ]; // short cut if (l.match(/^[\s\t]*;/)) { continue; } // skip comment line if (l.match(/^[\s\t]*$/)) { continue; } // skip empty line // beginning of node var beginningOfNodeExp = new RegExp("^\\t{" + this.currentIndent + "}(\\w+):(.*){", ''); match = l.match(beginningOfNodeExp); if (match) { var nodeName = match[ 1 ].trim().replace(/^"/, '').replace(/"$/, ""); var nodeAttrs = match[ 2 ].split(',').map(function (element) { return element.trim().replace(/^"/, '').replace(/"$/, ''); }); this.parseNodeBegin(l, nodeName, nodeAttrs || null); continue; } // node's property var propExp = new RegExp("^\\t{" + (this.currentIndent) + "}(\\w+):[\\s\\t\\r\\n](.*)"); match = l.match(propExp); if (match) { var propName = match[ 1 ].replace(/^"/, '').replace(/"$/, "").trim(); var propValue = match[ 2 ].replace(/^"/, '').replace(/"$/, "").trim(); this.parseNodeProperty(l, propName, propValue); continue; } // end of node var endOfNodeExp = new RegExp("^\\t{" + (this.currentIndent - 1) + "}}"); if (l.match(endOfNodeExp)) { this.nodeEnd(); continue; } // for special case, // // Vertices: *8670 { // a: 0.0356229953467846,13.9599733352661,-0.399196773.....(snip) // -0.0612030513584614,13.960485458374,-0.409748703241348,-0.10..... // 0.12490539252758,13.7450733184814,-0.454119384288788,0.09272..... // 0.0836158767342567,13.5432004928589,-0.435397416353226,0.028..... // // these case the lines must contiue with previous line if (l.match(/^[^\s\t}]/)) { this.parseNodePropertyContinued(l); } } return this.allNodes; }, parseNodeBegin: function (line, nodeName, nodeAttrs) { // var nodeName = match[1]; var node = {'name': nodeName, properties: {}, 'subNodes': {}}; var attrs = this.parseNodeAttr(nodeAttrs); var currentNode = this.getCurrentNode(); // a top node if (this.currentIndent === 0) { this.allNodes.add(nodeName, node); } else { // a subnode // already exists subnode, then append it if (nodeName in currentNode.subNodes) { var tmp = currentNode.subNodes[ nodeName ]; // console.log( "duped entry found\nkey: " + nodeName + "\nvalue: " + propValue ); if (this.isFlattenNode(currentNode.subNodes[ nodeName ])) { if (attrs.id === '') { currentNode.subNodes[ nodeName ] = []; currentNode.subNodes[ nodeName ].push(tmp); } else { currentNode.subNodes[ nodeName ] = {}; currentNode.subNodes[ nodeName ][ tmp.id ] = tmp; } } if (attrs.id === '') { currentNode.subNodes[ nodeName ].push(node); } else { currentNode.subNodes[ nodeName ][ attrs.id ] = node; } } else { currentNode.subNodes[ nodeName ] = node; } } // for this ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ // NodeAttribute: 1001463072, "NodeAttribute::", "LimbNode" { if (nodeAttrs) { node.id = attrs.id; node.attrName = attrs.name; node.attrType = attrs.type; } this.pushStack(node); }, parseNodeAttr: function (attrs) { var id = attrs[ 0 ]; if (attrs[ 0 ] !== "") { id = parseInt(attrs[ 0 ]); if (isNaN(id)) { // PolygonVertexIndex: *16380 { id = attrs[ 0 ]; } } var name; var type; if (attrs.length > 1) { name = attrs[ 1 ].replace(/^(\w+)::/, ''); type = attrs[ 2 ]; } return {id: id, name: name || '', type: type || ''}; }, parseNodeProperty: function (line, propName, propValue) { var currentNode = this.getCurrentNode(); var parentName = currentNode.name; // special case parent node's is like "Properties70" // these chilren nodes must treat with careful if (parentName !== undefined) { var propMatch = parentName.match(/Properties(\d)+/); if (propMatch) { this.parseNodeSpecialProperty(line, propName, propValue); return; } } // special case Connections if (propName == 'C') { var connProps = propValue.split(',').slice(1); var from = parseInt(connProps[ 0 ]); var to = parseInt(connProps[ 1 ]); var rest = propValue.split(',').slice(3); propName = 'connections'; propValue = [from, to]; propValue = propValue.concat(rest); if (currentNode.properties[ propName ] === undefined) { currentNode.properties[ propName ] = []; } } // special case Connections if (propName == 'Node') { var id = parseInt(propValue); currentNode.properties.id = id; currentNode.id = id; } // already exists in properties, then append this if (propName in currentNode.properties) { // console.log( "duped entry found\nkey: " + propName + "\nvalue: " + propValue ); if (Array.isArray(currentNode.properties[ propName ])) { currentNode.properties[ propName ].push(propValue); } else { currentNode.properties[ propName ] += propValue; } } else { // console.log( propName + ": " + propValue ); if (Array.isArray(currentNode.properties[ propName ])) { currentNode.properties[ propName ].push(propValue); } else { currentNode.properties[ propName ] = propValue; } } this.setCurrentProp(currentNode.properties, propName); }, // TODO: parseNodePropertyContinued: function (line) { this.currentProp[ this.currentPropName ] += line; }, parseNodeSpecialProperty: function (line, propName, propValue) { // split this // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 // into array like below // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ] var props = propValue.split('",').map(function (element) { return element.trim().replace(/^\"/, '').replace(/\s/, '_'); }); var innerPropName = props[ 0 ]; var innerPropType1 = props[ 1 ]; var innerPropType2 = props[ 2 ]; var innerPropFlag = props[ 3 ]; var innerPropValue = props[ 4 ]; /* if ( innerPropValue === undefined ) { innerPropValue = props[3]; } */ // cast value in its type switch (innerPropType1) { case "int": innerPropValue = parseInt(innerPropValue); break; case "double": innerPropValue = parseFloat(innerPropValue); break; case "ColorRGB": case "Vector3D": var tmp = innerPropValue.split(','); innerPropValue = new THREE.Vector3(tmp[ 0 ], tmp[ 1 ], tmp[ 2 ]); break; } // CAUTION: these props must append to parent's parent this.getPrevNode().properties[ innerPropName ] = { 'type': innerPropType1, 'type2': innerPropType2, 'flag': innerPropFlag, 'value': innerPropValue }; this.setCurrentProp(this.getPrevNode().properties, innerPropName); }, nodeEnd: function (line) { this.popStack(); }, /* ---------------------------------------------------------------- */ /* util */ isFlattenNode: function (node) { return ('subNodes' in node && 'properties' in node) ? true : false; } }; function FBXAnalyzer() {} FBXAnalyzer.prototype = { }; // generate skinIndices, skinWeights // @skinIndices: per vertex data, this represents the bone indexes affects that vertex // @skinWeights: per vertex data, this represents the Weight Values affects that vertex // @matrices: per `bones` data function Weights() { this.skinIndices = []; this.skinWeights = []; this.matrices = []; } Weights.prototype.parseCluster = function (node, id, entry) { var _p = node.searchConnectionParent(id); var _indices = toInt(entry.subNodes.Indexes.properties.a.split(',')); var _weights = toFloat(entry.subNodes.Weights.properties.a.split(',')); var _transform = toMat44(toFloat(entry.subNodes.Transform.properties.a.split(','))); var _link = toMat44(toFloat(entry.subNodes.TransformLink.properties.a.split(','))); return { 'parent': _p, 'id': parseInt(id), 'indices': _indices, 'weights': _weights, 'transform': _transform, 'transformlink': _link, 'linkMode': entry.properties.Mode }; }; Weights.prototype.parse = function (node, bones) { this.skinIndices = []; this.skinWeights = []; this.matrices = []; var deformers = node.Objects.subNodes.Deformer; var clusters = {}; for (var id in deformers) { if (deformers[ id ].attrType === 'Cluster') { if (!('Indexes' in deformers[ id ].subNodes)) { continue; } //clusters.push( this.parseCluster( node, id, deformers[id] ) ); var cluster = this.parseCluster(node, id, deformers[ id ]); var boneId = node.searchConnectionChildren(cluster.id)[ 0 ]; clusters[ boneId ] = cluster; } } // this clusters is per Bone data, thus we make this into per vertex data var weights = []; var hi = bones.hierarchy; for (var b = 0; b < hi.length; ++b) { var bid = hi[ b ].internalId; if (clusters[ bid ] === undefined) { //console.log( bid ); this.matrices.push(new THREE.Matrix4()); continue; } var clst = clusters[ bid ]; // store transform matrix per bones this.matrices.push(clst.transform); //this.matrices.push( clst.transformlink ); for (var v = 0; v < clst.indices.length; ++v) { if (weights[ clst.indices[ v ] ] === undefined) { weights[ clst.indices[ v ] ] = {}; weights[ clst.indices[ v ] ].joint = []; weights[ clst.indices[ v ] ].weight = []; } // indices var affect = node.searchConnectionChildren(clst.id); if (affect.length > 1) { console.warn("FBXLoader: node " + clst.id + " have many weight kids: " + affect); } weights[ clst.indices[ v ] ].joint.push(bones.getBoneIdfromInternalId(node, affect[ 0 ])); // weight value weights[ clst.indices[ v ] ].weight.push(clst.weights[ v ]); } } // normalize the skin weights // TODO - this might be a good place to choose greatest 4 weights for (var i = 0; i < weights.length; i++) { var indicies = new THREE.Vector4( weights[ i ].joint[ 0 ] ? weights[ i ].joint[ 0 ] : 0, weights[ i ].joint[ 1 ] ? weights[ i ].joint[ 1 ] : 0, weights[ i ].joint[ 2 ] ? weights[ i ].joint[ 2 ] : 0, weights[ i ].joint[ 3 ] ? weights[ i ].joint[ 3 ] : 0); var weight = new THREE.Vector4( weights[ i ].weight[ 0 ] ? weights[ i ].weight[ 0 ] : 0, weights[ i ].weight[ 1 ] ? weights[ i ].weight[ 1 ] : 0, weights[ i ].weight[ 2 ] ? weights[ i ].weight[ 2 ] : 0, weights[ i ].weight[ 3 ] ? weights[ i ].weight[ 3 ] : 0); this.skinIndices.push(indicies); this.skinWeights.push(weight); } //console.log( this ); return this; }; function Bones() { // returns bones hierarchy tree. // [ // { // "parent": id, // "name": name, // "pos": pos, // "rotq": quat // }, // ... // {}, // ... // ] // /* sample response "bones" : [ {"parent":-1, "name":"Fbx01", "pos":[-0.002, 98.739, 1.6e-05], "rotq":[0, 0, 0, 1]}, {"parent":0, "name":"Fbx01_Pelvis", "pos":[0.00015963, 0, 7.33107e-08], "rotq":[0, 0, 0, 1]}, {"parent":1, "name":"Fbx01_Spine", "pos":[6.577e-06, 10.216, 0.0106811], "rotq":[0, 0, 0, 1]}, {"parent":2, "name":"Fbx01_R_Thigh", "pos":[14.6537, -10.216, -0.00918758], "rotq":[0, 0, 0, 1]}, {"parent":3, "name":"Fbx01_R_Calf", "pos":[-3.70047, -42.9681, -7.78158], "rotq":[0, 0, 0, 1]}, {"parent":4, "name":"Fbx01_R_Foot", "pos":[-2.0696, -46.0488, 9.42052], "rotq":[0, 0, 0, 1]}, {"parent":5, "name":"Fbx01_R_Toe0", "pos":[-0.0234785, -9.46233, -15.3187], "rotq":[0, 0, 0, 1]}, {"parent":2, "name":"Fbx01_L_Thigh", "pos":[-14.6537, -10.216, -0.00918314], "rotq":[0, 0, 0, 1]}, {"parent":7, "name":"Fbx01_L_Calf", "pos":[3.70037, -42.968, -7.78155], "rotq":[0, 0, 0, 1]}, {"parent":8, "name":"Fbx01_L_Foot", "pos":[2.06954, -46.0488, 9.42052], "rotq":[0, 0, 0, 1]}, {"parent":9, "name":"Fbx01_L_Toe0", "pos":[0.0234566, -9.46235, -15.3187], "rotq":[0, 0, 0, 1]}, {"parent":2, "name":"Fbx01_Spine1", "pos":[-2.97523e-05, 11.5892, -9.81027e-05], "rotq":[0, 0, 0, 1]}, {"parent":11, "name":"Fbx01_Spine2", "pos":[-2.91292e-05, 11.4685, 8.27126e-05], "rotq":[0, 0, 0, 1]}, {"parent":12, "name":"Fbx01_Spine3", "pos":[-4.48857e-05, 11.5783, 8.35108e-05], "rotq":[0, 0, 0, 1]}, {"parent":13, "name":"Fbx01_Neck", "pos":[1.22987e-05, 11.5582, -0.0044775], "rotq":[0, 0, 0, 1]}, {"parent":14, "name":"Fbx01_Head", "pos":[-3.50709e-05, 6.62915, -0.00523254], "rotq":[0, 0, 0, 1]}, {"parent":15, "name":"Fbx01_R_Eye", "pos":[3.31681, 12.739, -10.5267], "rotq":[0, 0, 0, 1]}, {"parent":15, "name":"Fbx01_L_Eye", "pos":[-3.32038, 12.7391, -10.5267], "rotq":[0, 0, 0, 1]}, {"parent":15, "name":"Jaw", "pos":[-0.0017738, 7.43481, -4.08114], "rotq":[0, 0, 0, 1]}, {"parent":14, "name":"Fbx01_R_Clavicle", "pos":[3.10919, 2.46577, -0.0115284], "rotq":[0, 0, 0, 1]}, {"parent":19, "name":"Fbx01_R_UpperArm", "pos":[16.014, 4.57764e-05, 3.10405], "rotq":[0, 0, 0, 1]}, {"parent":20, "name":"Fbx01_R_Forearm", "pos":[22.7068, -1.66322, -2.13803], "rotq":[0, 0, 0, 1]}, {"parent":21, "name":"Fbx01_R_Hand", "pos":[25.5881, -0.80249, -6.37307], "rotq":[0, 0, 0, 1]}, ... {"parent":27, "name":"Fbx01_R_Finger32", "pos":[2.15572, -0.548737, -0.539604], "rotq":[0, 0, 0, 1]}, {"parent":22, "name":"Fbx01_R_Finger2", "pos":[9.79318, 0.132553, -2.97845], "rotq":[0, 0, 0, 1]}, {"parent":29, "name":"Fbx01_R_Finger21", "pos":[2.74037, 0.0483093, -0.650531], "rotq":[0, 0, 0, 1]}, {"parent":55, "name":"Fbx01_L_Finger02", "pos":[-1.65308, -1.43208, -1.82885], "rotq":[0, 0, 0, 1]} ] */ this.hierarchy = []; } Bones.prototype.parseHierarchy = function (node) { var objects = node.Objects; var models = objects.subNodes.Model; var bones = []; for (var id in models) { if (models[ id ].attrType === undefined) { continue; } bones.push(models[ id ]); } this.hierarchy = []; for (var i = 0; i < bones.length; ++i) { var bone = bones[ i ]; var p = node.searchConnectionParent(bone.id)[ 0 ]; var t = [0.0, 0.0, 0.0]; var r = [0.0, 0.0, 0.0, 1.0]; var s = [1.0, 1.0, 1.0]; if ('Lcl_Translation' in bone.properties) { t = toFloat(bone.properties.Lcl_Translation.value.split(',')); } if ('Lcl_Rotation' in bone.properties) { r = toRad(toFloat(bone.properties.Lcl_Rotation.value.split(','))); var q = new THREE.Quaternion(); q.setFromEuler(new THREE.Euler(r[ 0 ], r[ 1 ], r[ 2 ], 'ZYX')); r = [q.x, q.y, q.z, q.w]; } if ('Lcl_Scaling' in bone.properties) { s = toFloat(bone.properties.Lcl_Scaling.value.split(',')); } // replace unsafe character var name = bone.attrName; name = name.replace(/:/, ''); name = name.replace(/_/, ''); name = name.replace(/-/, ''); this.hierarchy.push({"parent": p, "name": name, "pos": t, "rotq": r, "scl": s, "internalId": bone.id}); } this.reindexParentId(); this.restoreBindPose(node); return this; }; Bones.prototype.reindexParentId = function () { for (var h = 0; h < this.hierarchy.length; h++) { for (var ii = 0; ii < this.hierarchy.length; ++ii) { if (this.hierarchy[ h ].parent == this.hierarchy[ ii ].internalId) { this.hierarchy[ h ].parent = ii; break; } } } }; Bones.prototype.restoreBindPose = function (node) { var bindPoseNode = node.Objects.subNodes.Pose; if (bindPoseNode === undefined) { return; } var poseNode = bindPoseNode.subNodes.PoseNode; var localMatrices = {}; // store local matrices, modified later( initialy world space ) var worldMatrices = {}; // store world matrices for (var i = 0; i < poseNode.length; ++i) { var rawMatLcl = toMat44(poseNode[ i ].subNodes.Matrix.properties.a.split(',')); var rawMatWrd = toMat44(poseNode[ i ].subNodes.Matrix.properties.a.split(',')); localMatrices[ poseNode[ i ].id ] = rawMatLcl; worldMatrices[ poseNode[ i ].id ] = rawMatWrd; } for (var h = 0; h < this.hierarchy.length; ++h) { var bone = this.hierarchy[ h ]; var inId = bone.internalId; if (worldMatrices[ inId ] === undefined) { // has no bind pose node, possibly be mesh // console.log( bone ); continue; } var t = new THREE.Vector3(0, 0, 0); var r = new THREE.Quaternion(); var s = new THREE.Vector3(1, 1, 1); var parentId; var parentNodes = node.searchConnectionParent(inId); for (var pn = 0; pn < parentNodes.length; ++pn) { if (this.isBoneNode(parentNodes[ pn ])) { parentId = parentNodes[ pn ]; break; } } if (parentId !== undefined && localMatrices[ parentId ] !== undefined) { // convert world space matrix into local space var inv = new THREE.Matrix4(); inv.getInverse(worldMatrices[ parentId ]); inv.multiply(localMatrices[ inId ]); localMatrices[ inId ] = inv; } else { //console.log( bone ); } localMatrices[ inId ].decompose(t, r, s); bone.pos = [t.x, t.y, t.z]; bone.rotq = [r.x, r.y, r.z, r.w]; bone.scl = [s.x, s.y, s.z]; } }; Bones.prototype.searchRealId = function (internalId) { for (var h = 0; h < this.hierarchy.length; h++) { if (internalId == this.hierarchy[ h ].internalId) { return h; } } // console.warn( 'FBXLoader: notfound internalId in bones: ' + internalId); return -1; }; Bones.prototype.getByInternalId = function (internalId) { for (var h = 0; h < this.hierarchy.length; h++) { if (internalId == this.hierarchy[ h ].internalId) { return this.hierarchy[ h ]; } } return null; }; Bones.prototype.isBoneNode = function (id) { for (var i = 0; i < this.hierarchy.length; ++i) { if (id === this.hierarchy[ i ].internalId) { return true; } } return false; }; Bones.prototype.getBoneIdfromInternalId = function (node, id) { if (node.__cache_get_boneid_from_internalid === undefined) { node.__cache_get_boneid_from_internalid = []; } if (node.__cache_get_boneid_from_internalid[ id ] !== undefined) { return node.__cache_get_boneid_from_internalid[ id ]; } for (var i = 0; i < this.hierarchy.length; ++i) { if (this.hierarchy[ i ].internalId == id) { var res = i; node.__cache_get_boneid_from_internalid[ id ] = i; return i; } } // console.warn( 'FBXLoader: bone internalId(' + id + ') not found in bone hierarchy' ); return -1; }; function Geometry() { this.node = null; this.name = null; this.id = null; this.vertices = []; this.indices = []; this.normals = []; this.uvs = []; this.bones = []; this.skins = null; } Geometry.prototype.parse = function (geoNode) { this.node = geoNode; this.name = geoNode.attrName; this.id = geoNode.id; this.vertices = this.getVertices(); if (this.vertices === undefined) { console.log('FBXLoader: Geometry.parse(): pass' + this.node.id); return; } this.indices = this.getPolygonVertexIndices(); this.uvs = (new UV()).parse(this.node, this); this.normals = (new Normal()).parse(this.node, this); if (this.getPolygonTopologyMax() > 3) { this.indices = this.convertPolyIndicesToTri( this.indices, this.getPolygonTopologyArray()); } return this; }; Geometry.prototype.getVertices = function () { if (this.node.__cache_vertices) { return this.node.__cache_vertices; } if (this.node.subNodes.Vertices === undefined) { console.warn('this.node: ' + this.node.attrName + "(" + this.node.id + ") does not have Vertices"); this.node.__cache_vertices = undefined; return null; } var rawTextVert = this.node.subNodes.Vertices.properties.a; var vertices = rawTextVert.split(',').map(function (element) { return parseFloat(element); }); this.node.__cache_vertices = vertices; return this.node.__cache_vertices; }; Geometry.prototype.getPolygonVertexIndices = function () { if (this.node.__cache_indices && this.node.__cache_poly_topology_max) { return this.node.__cache_indices; } if (this.node.subNodes === undefined) { console.error('this.node.subNodes undefined'); console.log(this.node); return; } if (this.node.subNodes.PolygonVertexIndex === undefined) { console.warn('this.node: ' + this.node.attrName + "(" + this.node.id + ") does not have PolygonVertexIndex "); this.node.__cache_indices = undefined; return; } var rawTextIndices = this.node.subNodes.PolygonVertexIndex.properties.a; var indices = rawTextIndices.split(','); var currentTopo = 1; var topologyN = null; var topologyArr = []; // The indices that make up the polygon are in order and a negative index // means that it’s the last index of the polygon. That index needs // to be made positive and then you have to subtract 1 from it! for (var i = 0; i < indices.length; ++i) { var tmpI = parseInt(indices[ i ]); // found n if (tmpI < 0) { if (currentTopo > topologyN) { topologyN = currentTopo; } indices[ i ] = tmpI ^ -1; topologyArr.push(currentTopo); currentTopo = 1; } else { indices[ i ] = tmpI; currentTopo++; } } if (topologyN === null) { console.warn("FBXLoader: topology N not found: " + this.node.attrName); console.warn(this.node); topologyN = 3; } this.node.__cache_poly_topology_max = topologyN; this.node.__cache_poly_topology_arr = topologyArr; this.node.__cache_indices = indices; return this.node.__cache_indices; }; Geometry.prototype.getPolygonTopologyMax = function () { if (this.node.__cache_indices && this.node.__cache_poly_topology_max) { return this.node.__cache_poly_topology_max; } this.getPolygonVertexIndices(this.node); return this.node.__cache_poly_topology_max; }; Geometry.prototype.getPolygonTopologyArray = function () { if (this.node.__cache_indices && this.node.__cache_poly_topology_max) { return this.node.__cache_poly_topology_arr; } this.getPolygonVertexIndices(this.node); return this.node.__cache_poly_topology_arr; }; // a - d // | | // b - c // // [( a, b, c, d ) ........... // [( a, b, c ), (a, c, d ).... Geometry.prototype.convertPolyIndicesToTri = function (indices, strides) { var res = []; var i = 0; var tmp = []; var currentPolyNum = 0; var currentStride = 0; while (i < indices.length) { currentStride = strides[ currentPolyNum ]; // CAUTIN: NG over 6gon for (var j = 0; j <= (currentStride - 3); j++) { res.push(indices[ i ]); res.push(indices[ i + (currentStride - 2 - j) ]); res.push(indices[ i + (currentStride - 1 - j) ]); } currentPolyNum++; i += currentStride; } return res; }; Geometry.prototype.addBones = function (bones) { this.bones = bones; }; function UV() { this.uv = null; this.map = null; this.ref = null; this.node = null; this.index = null; } UV.prototype.getUV = function (node) { if (this.node && this.uv && this.map && this.ref) { return this.uv; } else { return this._parseText(node); } }; UV.prototype.getMap = function (node) { if (this.node && this.uv && this.map && this.ref) { return this.map; } else { this._parseText(node); return this.map; } }; UV.prototype.getRef = function (node) { if (this.node && this.uv && this.map && this.ref) { return this.ref; } else { this._parseText(node); return this.ref; } }; UV.prototype.getIndex = function (node) { if (this.node && this.uv && this.map && this.ref) { return this.index; } else { this._parseText(node); return this.index; } }; UV.prototype.getNode = function (topnode) { if (this.node !== null) { return this.node; } this.node = topnode.subNodes.LayerElementUV; return this.node; }; UV.prototype._parseText = function (node) { var uvNode = this.getNode(node); if (uvNode === undefined) { // console.log( node.attrName + "(" + node.id + ")" + " has no LayerElementUV." ); return []; } var count = 0; var x = ''; for (var n in uvNode) { if (n.match(/^\d+$/)) { count++; x = n; } } if (count > 0) { console.warn('multi uv not supported'); uvNode = uvNode[ n ]; } var uvIndex = uvNode.subNodes.UVIndex.properties.a; var uvs = uvNode.subNodes.UV.properties.a; var uvMap = uvNode.properties.MappingInformationType; var uvRef = uvNode.properties.ReferenceInformationType; this.uv = toFloat(uvs.split(',')); this.index = toInt(uvIndex.split(',')); this.map = uvMap; // TODO: normalize notation shaking... FOR BLENDER this.ref = uvRef; return this.uv; }; UV.prototype.parse = function (node, geo) { this.uvNode = this.getNode(node); this.uv = this.getUV(node); var mappingType = this.getMap(node); var refType = this.getRef(node); var indices = this.getIndex(node); var strides = geo.getPolygonTopologyArray(); // it means that there is a normal for every vertex of every polygon of the model. // For example, if the models has 8 vertices that make up four quads, then there // will be 16 normals (one normal * 4 polygons * 4 vertices of the polygon). Note // that generally a game engine needs the vertices to have only one normal defined. // So, if you find a vertex has more tha one normal, you can either ignore the normals // you find after the first, or calculate the mean from all of them (normal smoothing). //if ( mappingType == "ByPolygonVertex" ){ switch (mappingType) { case "ByPolygonVertex": switch (refType) { // Direct // The this.uv are in order. case "Direct": this.uv = this.parseUV_ByPolygonVertex_Direct(this.uv, indices, strides, 2); break; // IndexToDirect // The order of the this.uv is given by the uvsIndex property. case "IndexToDirect": this.uv = this.parseUV_ByPolygonVertex_IndexToDirect(this.uv, indices); break; } // convert from by polygon(vert) data into by verts data this.uv = mapByPolygonVertexToByVertex(this.uv, geo.getPolygonVertexIndices(node), 2); break; case "ByPolygon": switch (refType) { // Direct // The this.uv are in order. case "Direct": this.uv = this.parseUV_ByPolygon_Direct(); break; // IndexToDirect // The order of the this.uv is given by the uvsIndex property. case "IndexToDirect": this.uv = this.parseUV_ByPolygon_IndexToDirect(); break; } break; } return this.uv; }; UV.prototype.parseUV_ByPolygonVertex_Direct = function (node, indices, strides, itemSize) { return parse_Data_ByPolygonVertex_Direct(node, indices, strides, itemSize); }; UV.prototype.parseUV_ByPolygonVertex_IndexToDirect = function (node, indices) { return parse_Data_ByPolygonVertex_IndexToDirect(node, indices, 2); }; UV.prototype.parseUV_ByPolygon_Direct = function (node) { console.warn("not implemented"); return node; }; UV.prototype.parseUV_ByPolygon_IndexToDirect = function (node) { console.warn("not implemented"); return node; }; UV.prototype.parseUV_ByVertex_Direct = function (node) { console.warn("not implemented"); return node; }; function Normal() { this.normal = null; this.map = null; this.ref = null; this.node = null; this.index = null; } Normal.prototype.getNormal = function (node) { if (this.node && this.normal && this.map && this.ref) { return this.normal; } else { this._parseText(node); return this.normal; } }; // mappingType: possible variant // ByPolygon // ByPolygonVertex // ByVertex (or also ByVertice, as the Blender exporter writes) // ByEdge // AllSame // var mappingType = node.properties.MappingInformationType; Normal.prototype.getMap = function (node) { if (this.node && this.normal && this.map && this.ref) { return this.map; } else { this._parseText(node); return this.map; } }; // refType: possible variants // Direct // IndexToDirect (or Index for older versions) // var refType = node.properties.ReferenceInformationType; Normal.prototype.getRef = function (node) { if (this.node && this.normal && this.map && this.ref) { return this.ref; } else { this._parseText(node); return this.ref; } }; Normal.prototype.getNode = function (node) { if (this.node) { return this.node; } this.node = node.subNodes.LayerElementNormal; return this.node; }; Normal.prototype._parseText = function (node) { var normalNode = this.getNode(node); if (normalNode === undefined) { console.warn('node: ' + node.attrName + "(" + node.id + ") does not have LayerElementNormal"); return; } var mappingType = normalNode.properties.MappingInformationType; var refType = normalNode.properties.ReferenceInformationType; var rawTextNormals = normalNode.subNodes.Normals.properties.a; this.normal = toFloat(rawTextNormals.split(',')); // TODO: normalize notation shaking, vertex / vertice... blender... this.map = mappingType; this.ref = refType; }; Normal.prototype.parse = function (topnode, geo) { var normals = this.getNormal(topnode); var normalNode = this.getNode(topnode); var mappingType = this.getMap(topnode); var refType = this.getRef(topnode); var indices = geo.getPolygonVertexIndices(topnode); var strides = geo.getPolygonTopologyArray(topnode); // it means that there is a normal for every vertex of every polygon of the model. // For example, if the models has 8 vertices that make up four quads, then there // will be 16 normals (one normal * 4 polygons * 4 vertices of the polygon). Note // that generally a game engine needs the vertices to have only one normal defined. // So, if you find a vertex has more tha one normal, you can either ignore the normals // you find after the first, or calculate the mean from all of them (normal smoothing). //if ( mappingType == "ByPolygonVertex" ){ switch (mappingType) { case "ByPolygonVertex": switch (refType) { // Direct // The normals are in order. case "Direct": normals = this.parseNormal_ByPolygonVertex_Direct(normals, indices, strides, 3); break; // IndexToDirect // The order of the normals is given by the NormalsIndex property. case "IndexToDirect": normals = this.parseNormal_ByPolygonVertex_IndexToDirect(); break; } break; case "ByPolygon": switch (refType) { // Direct // The normals are in order. case "Direct": normals = this.parseNormal_ByPolygon_Direct(); break; // IndexToDirect // The order of the normals is given by the NormalsIndex property. case "IndexToDirect": normals = this.parseNormal_ByPolygon_IndexToDirect(); break; } break; } return normals; }; Normal.prototype.parseNormal_ByPolygonVertex_Direct = function (node, indices, strides, itemSize) { return parse_Data_ByPolygonVertex_Direct(node, indices, strides, itemSize); }; Normal.prototype.parseNormal_ByPolygonVertex_IndexToDirect = function (node) { console.warn("not implemented"); return node; }; Normal.prototype.parseNormal_ByPolygon_Direct = function (node) { console.warn("not implemented"); return node; }; Normal.prototype.parseNormal_ByPolygon_IndexToDirect = function (node) { console.warn("not implemented"); return node; }; Normal.prototype.parseNormal_ByVertex_Direct = function (node) { console.warn("not implemented"); return node; }; function AnimationCurve() { this.version = null; this.id = null; this.internalId = null; this.times = null; this.values = null; this.attrFlag = null; // tangeant this.attrData = null; // slope, weight } AnimationCurve.prototype.fromNode = function (curveNode) { this.id = curveNode.id; this.internalId = curveNode.id; this.times = curveNode.subNodes.KeyTime.properties.a; this.values = curveNode.subNodes.KeyValueFloat.properties.a; this.attrFlag = curveNode.subNodes.KeyAttrFlags.properties.a; this.attrData = curveNode.subNodes.KeyAttrDataFloat.properties.a; this.times = toFloat(this.times.split(',')); this.values = toFloat(this.values.split(',')); this.attrData = toFloat(this.attrData.split(',')); this.attrFlag = toInt(this.attrFlag.split(',')); this.times = this.times.map(function (element) { return FBXTimeToSeconds(element); }); return this; }; AnimationCurve.prototype.getLength = function () { return this.times[ this.times.length - 1 ]; }; function AnimationNode() { this.id = null; this.attr = null; // S, R, T this.attrX = false; this.attrY = false; this.attrZ = false; this.internalId = null; this.containerInternalId = null; // bone, null etc Id this.containerBoneId = null; // bone, null etc Id this.curveIdx = null; // AnimationCurve's indices this.curves = []; // AnimationCurve refs } AnimationNode.prototype.fromNode = function (allNodes, node, bones) { this.id = node.id; this.attr = node.attrName; this.internalId = node.id; if (this.attr.match(/S|R|T/)) { for (var attrKey in node.properties) { if (attrKey.match(/X/)) { this.attrX = true; } if (attrKey.match(/Y/)) { this.attrY = true; } if (attrKey.match(/Z/)) { this.attrZ = true; } } } else { // may be deform percent nodes return null; } this.containerIndices = allNodes.searchConnectionParent(this.id); this.curveIdx = allNodes.searchConnectionChildren(this.id); for (var i = this.containerIndices.length - 1; i >= 0; --i) { var boneId = bones.searchRealId(this.containerIndices[ i ]); if (boneId >= 0) { this.containerBoneId = boneId; this.containerId = this.containerIndices [ i ]; } if (boneId >= 0) { break; } } // this.containerBoneId = bones.searchRealId( this.containerIndices ); return this; }; AnimationNode.prototype.setCurve = function (curve) { this.curves.push(curve); }; function Animation() { this.curves = {}; this.length = 0.0; this.fps = 30.0; this.frames = 0.0; } Animation.prototype.parse = function (node, bones) { var rawNodes = node.Objects.subNodes.AnimationCurveNode; var rawCurves = node.Objects.subNodes.AnimationCurve; // first: expand AnimationCurveNode into curve nodes var curveNodes = []; for (var key in rawNodes) { if (key.match(/\d+/)) { var a = (new AnimationNode()).fromNode(node, rawNodes[ key ], bones); curveNodes.push(a); } } // second: gen dict, mapped by internalId var tmp = {}; for (var i = 0; i < curveNodes.length; ++i) { if (curveNodes[ i ] === null) { continue; } tmp[ curveNodes[ i ].id ] = curveNodes[ i ]; } // third: insert curves into the dict var ac = []; var max = 0.0; for (key in rawCurves) { if (key.match(/\d+/)) { var c = (new AnimationCurve()).fromNode(rawCurves[ key ]); ac.push(c); max = c.getLength() ? c.getLength() : max; var parentId = node.searchConnectionParent(c.id)[ 0 ]; var axis = node.searchConnectionType(c.id, parentId); if (axis.match(/X/)) { axis = 'x'; } if (axis.match(/Y/)) { axis = 'y'; } if (axis.match(/Z/)) { axis = 'z'; } tmp[ parentId ].curves[ axis ] = c; } } // forth: for (var t in tmp) { var id = tmp[ t ].containerBoneId; if (this.curves[ id ] === undefined) { this.curves[ id ] = {}; } this.curves[ id ][ tmp[ t ].attr ] = tmp[ t ]; } this.length = max; this.frames = this.length * this.fps; return this; }; function Textures() { this.textures = []; this.perGeoMap = {}; } Textures.prototype.add = function (tex) { if (this.textures === undefined) { this.textures = []; } this.textures.push(tex); for (var i = 0; i < tex.parentIds.length; ++i) { if (this.perGeoMap[ tex.parentIds[ i ] ] === undefined) { this.perGeoMap[ tex.parentIds[ i ] ] = []; } this.perGeoMap[ tex.parentIds[ i ] ].push(this.textures[ this.textures.length - 1 ]); } }; Textures.prototype.parse = function (node, bones) { var rawNodes = node.Objects.subNodes.Texture; for (var n in rawNodes) { var tex = (new Texture()).parse(rawNodes[ n ], node); this.add(tex); } return this; }; Textures.prototype.getById = function (id) { return this.perGeoMap[ id ]; }; function Texture() { this.fileName = ""; this.name = ""; this.id = null; this.parentIds = []; } Texture.prototype.parse = function (node, nodes) { this.id = node.id; this.name = node.attrName; this.fileName = this.parseFileName(node.properties.FileName); this.parentIds = this.searchParents(this.id, nodes); return this; }; // TODO: support directory Texture.prototype.parseFileName = function (fname) { if (fname === undefined) { return ""; } // ignore directory structure, flatten path var splitted = fname.split(/[\\\/]/); if (splitted.length > 0) { return splitted[ splitted.length - 1 ]; } else { return fname; } }; Texture.prototype.searchParents = function (id, nodes) { var p = nodes.searchConnectionParent(id); return p; }; /* --------------------------------------------------------------------- */ /* --------------------------------------------------------------------- */ /* --------------------------------------------------------------------- */ /* --------------------------------------------------------------------- */ function loadTextureImage(texture, url) { var loader = new THREE.ImageLoader(); loader.load(url, function (image) { }); loader.load(url, function (image) { texture.image = image; texture.needUpdate = true; console.log('tex load done'); }, // Function called when download progresses function (xhr) { console.log((xhr.loaded / xhr.total * 100) + '% loaded'); }, // Function called when download errors function (xhr) { console.log('An error happened'); } ); } // LayerElementUV: 0 { // Version: 101 // Name: "Texture_Projection" // MappingInformationType: "ByPolygonVertex" // ReferenceInformationType: "IndexToDirect" // UV: *1746 { // UVIndex: *7068 { // // The order of the uvs is given by the UVIndex property. function parse_Data_ByPolygonVertex_IndexToDirect(node, indices, itemSize) { var res = []; for (var i = 0; i < indices.length; ++i) { for (var j = 0; j < itemSize; ++j) { res.push(node[ (indices[ i ] * itemSize) + j ]); } } return res; } // what want: normal per vertex, order vertice // i have: normal per polygon // i have: indice per polygon parse_Data_ByPolygonVertex_Direct = function (node, indices, strides, itemSize) { // *21204 > 3573 // Geometry: 690680816, "Geometry::", "Mesh" { // Vertices: *3573 { // PolygonVertexIndex: *7068 { var tmp = []; var currentIndex = 0; // first: sort to per vertex for (var i = 0; i < indices.length; ++i) { tmp[ indices[ i ] ] = []; // TODO: duped entry? blend or something? for (var s = 0; s < itemSize; ++s) { tmp[ indices[ i ] ][ s ] = node[ currentIndex + s ]; } currentIndex += itemSize; } // second: expand x,y,z into serial array var res = []; for (var jj = 0; jj < tmp.length; ++jj) { if (tmp[ jj ] === undefined) { continue; } for (var t = 0; t < itemSize; ++t) { if (tmp[ jj ][ t ] === undefined) { continue; } res.push(tmp[ jj ][ t ]); } } return res; }; // convert from by polygon(vert) data into by verts data function mapByPolygonVertexToByVertex(data, indices, stride) { var tmp = {}; var res = []; var max = 0; for (var i = 0; i < indices.length; ++i) { if (indices[ i ] in tmp) { continue; } tmp[ indices[ i ] ] = {}; for (var j = 0; j < stride; ++j) { tmp[ indices[ i ] ][ j ] = data[ i * stride + j ]; } max = max < indices[ i ] ? indices[ i ] : max; } try { for (i = 0; i <= max; i++) { for (var s = 0; s < stride; s++) { res.push(tmp[ i ][ s ]); } } } catch (e) { //console.log( max ); //console.log( tmp ); //console.log( i ); //console.log( e ); } return res; } // AUTODESK uses broken clock. i guess var FBXTimeToSeconds = function (adskTime) { return adskTime / 46186158000; }; degToRad = function (degrees) { return degrees * Math.PI / 180; }; radToDeg = function (radians) { return radians * 180 / Math.PI; }; quatFromVec = function (x, y, z) { var euler = new THREE.Euler(x, y, z, 'ZYX'); var quat = new THREE.Quaternion(); quat.setFromEuler(euler); return quat; }; // extend Array.prototype ? ....uuuh toInt = function (arr) { return arr.map(function (element) { return parseInt(element); }); }; toFloat = function (arr) { return arr.map(function (element) { return parseFloat(element); }); }; toRad = function (arr) { return arr.map(function (element) { return degToRad(element); }); }; toMat44 = function (arr) { var mat = new THREE.Matrix4(); mat.set( arr[ 0 ], arr[ 4 ], arr[ 8 ], arr[ 12 ], arr[ 1 ], arr[ 5 ], arr[ 9 ], arr[ 13 ], arr[ 2 ], arr[ 6 ], arr[ 10 ], arr[ 14 ], arr[ 3 ], arr[ 7 ], arr[ 11 ], arr[ 15 ] ); /* mat.set( arr[ 0], arr[ 1], arr[ 2], arr[ 3], arr[ 4], arr[ 5], arr[ 6], arr[ 7], arr[ 8], arr[ 9], arr[10], arr[11], arr[12], arr[13], arr[14], arr[15] ); // */ return mat; }; })();