bytev-charts
Version:
基于echarts和JavaScript及ES6封装的一个可以直接调用的图表组件库,内置主题设计,简单快捷,且支持用户自定义配置; npm 安装方式: npm install bytev-charts 若启动提示还需额外install插件,则运行 npm install @babel/runtime-corejs2 即可;
1,317 lines (1,122 loc) • 110 kB
JavaScript
import _typeof from "@babel/runtime-corejs2/helpers/typeof";
import "core-js/modules/es.array.for-each.js";
import "core-js/modules/es.object.to-string.js";
import "core-js/modules/web.dom-collections.for-each.js";
import "core-js/modules/es.array-buffer.constructor.js";
import "core-js/modules/es.array-buffer.slice.js";
import "core-js/modules/es.regexp.exec.js";
import "core-js/modules/es.string.split.js";
import "core-js/modules/es.array.slice.js";
import "core-js/modules/es.array.last-index-of.js";
import "core-js/modules/es.array.iterator.js";
import "core-js/modules/es.typed-array.uint8-array.js";
import "core-js/modules/es.typed-array.copy-within.js";
import "core-js/modules/es.typed-array.every.js";
import "core-js/modules/es.typed-array.fill.js";
import "core-js/modules/es.typed-array.filter.js";
import "core-js/modules/es.typed-array.find.js";
import "core-js/modules/es.typed-array.find-index.js";
import "core-js/modules/es.typed-array.for-each.js";
import "core-js/modules/es.typed-array.includes.js";
import "core-js/modules/es.typed-array.index-of.js";
import "core-js/modules/es.typed-array.iterator.js";
import "core-js/modules/es.typed-array.join.js";
import "core-js/modules/es.typed-array.last-index-of.js";
import "core-js/modules/es.typed-array.map.js";
import "core-js/modules/es.typed-array.reduce.js";
import "core-js/modules/es.typed-array.reduce-right.js";
import "core-js/modules/es.typed-array.reverse.js";
import "core-js/modules/es.typed-array.set.js";
import "core-js/modules/es.typed-array.slice.js";
import "core-js/modules/es.typed-array.some.js";
import "core-js/modules/es.typed-array.sort.js";
import "core-js/modules/es.typed-array.subarray.js";
import "core-js/modules/es.typed-array.to-locale-string.js";
import "core-js/modules/es.typed-array.to-string.js";
import "core-js/modules/es.string.iterator.js";
import "core-js/modules/web.dom-collections.iterator.js";
import "core-js/modules/web.url.js";
import "core-js/modules/web.url-search-params.js";
import "core-js/modules/es.function.name.js";
import "core-js/modules/es.string.repeat.js";
import "core-js/modules/es.array.index-of.js";
import "core-js/modules/es.array.filter.js";
import "core-js/modules/es.array.reduce.js";
import "core-js/modules/es.function.bind.js";
import "core-js/modules/es.array.map.js";
import "core-js/modules/es.date.to-string.js";
import "core-js/modules/es.regexp.to-string.js";
import "core-js/modules/es.typed-array.float32-array.js";
import "core-js/modules/es.promise.js";
import "core-js/modules/es.string.match.js";
import "core-js/modules/es.array.concat.js";
import "core-js/modules/es.array.sort.js";
import "core-js/modules/es.string.replace.js";
import "core-js/modules/es.string.trim.js";
import "core-js/modules/es.data-view.js";
import _Object$assign from "@babel/runtime-corejs2/core-js/object/assign";
import _Object$create from "@babel/runtime-corejs2/core-js/object/create";
import _Map from "@babel/runtime-corejs2/core-js/map";
import _parseInt from "@babel/runtime-corejs2/core-js/parse-int";
import _parseFloat from "@babel/runtime-corejs2/core-js/parse-float";
import _Array$isArray from "@babel/runtime-corejs2/core-js/array/is-array";
import _Object$keys from "@babel/runtime-corejs2/core-js/object/keys";
console.warn("THREE.FBXLoader: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation.");
/**
* Loader loads FBX file and generates Group representing FBX scene.
* Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format
* Versions lower than this may load but will probably have errors
*
* Needs Support:
* Morph normals / blend shape normals
*
* FBX format references:
* https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
* http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference)
*
* Binary format specification:
* https://code.blender.org/2013/08/fbx-binary-file-format-specification/
*/
THREE.FBXLoader = function () {
var fbxTree;
var connections;
var sceneGraph;
function FBXLoader(manager) {
THREE.Loader.call(this, manager);
}
FBXLoader.prototype = _Object$assign(_Object$create(THREE.Loader.prototype), {
constructor: FBXLoader,
load: function load(url, onLoad, onProgress, onError) {
var scope = this;
var path = scope.path === '' ? THREE.LoaderUtils.extractUrlBase(url) : scope.path;
var loader = new THREE.FileLoader(this.manager);
loader.setPath(scope.path);
loader.setResponseType('arraybuffer');
loader.setRequestHeader(scope.requestHeader);
loader.load(url, function (buffer) {
try {
onLoad(scope.parse(buffer, path));
} catch (e) {
if (onError) {
onError(e);
} else {
console.error(e);
}
scope.manager.itemError(url);
}
}, onProgress, onError);
},
parse: function parse(FBXBuffer, path) {
if (isFbxFormatBinary(FBXBuffer)) {
fbxTree = new BinaryParser().parse(FBXBuffer);
} else {
var FBXText = convertArrayBufferToString(FBXBuffer);
if (!isFbxFormatASCII(FBXText)) {
throw new Error('THREE.FBXLoader: Unknown format.');
}
if (getFbxVersion(FBXText) < 7000) {
throw new Error('THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion(FBXText));
}
fbxTree = new TextParser().parse(FBXText);
} // console.log( fbxTree );
var textureLoader = new THREE.TextureLoader(this.manager).setPath(this.resourcePath || path).setCrossOrigin(this.crossOrigin);
return new FBXTreeParser(textureLoader, this.manager).parse(fbxTree);
}
}); // Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group
function FBXTreeParser(textureLoader, manager) {
this.textureLoader = textureLoader;
this.manager = manager;
}
FBXTreeParser.prototype = {
constructor: FBXTreeParser,
parse: function parse() {
connections = this.parseConnections();
var images = this.parseImages();
var textures = this.parseTextures(images);
var materials = this.parseMaterials(textures);
var deformers = this.parseDeformers();
var geometryMap = new GeometryParser().parse(deformers);
this.parseScene(deformers, geometryMap, materials);
return sceneGraph;
},
// Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry )
// and details the connection type
parseConnections: function parseConnections() {
var connectionMap = new _Map();
if ('Connections' in fbxTree) {
var rawConnections = fbxTree.Connections.connections;
rawConnections.forEach(function (rawConnection) {
var fromID = rawConnection[0];
var toID = rawConnection[1];
var relationship = rawConnection[2];
if (!connectionMap.has(fromID)) {
connectionMap.set(fromID, {
parents: [],
children: []
});
}
var parentRelationship = {
ID: toID,
relationship: relationship
};
connectionMap.get(fromID).parents.push(parentRelationship);
if (!connectionMap.has(toID)) {
connectionMap.set(toID, {
parents: [],
children: []
});
}
var childRelationship = {
ID: fromID,
relationship: relationship
};
connectionMap.get(toID).children.push(childRelationship);
});
}
return connectionMap;
},
// Parse FBXTree.Objects.Video for embedded image data
// These images are connected to textures in FBXTree.Objects.Textures
// via FBXTree.Connections.
parseImages: function parseImages() {
var images = {};
var blobs = {};
if ('Video' in fbxTree.Objects) {
var videoNodes = fbxTree.Objects.Video;
for (var nodeID in videoNodes) {
var videoNode = videoNodes[nodeID];
var id = _parseInt(nodeID);
images[id] = videoNode.RelativeFilename || videoNode.Filename; // raw image data is in videoNode.Content
if ('Content' in videoNode) {
var arrayBufferContent = videoNode.Content instanceof ArrayBuffer && videoNode.Content.byteLength > 0;
var base64Content = typeof videoNode.Content === 'string' && videoNode.Content !== '';
if (arrayBufferContent || base64Content) {
var image = this.parseImage(videoNodes[nodeID]);
blobs[videoNode.RelativeFilename || videoNode.Filename] = image;
}
}
}
}
for (var id in images) {
var filename = images[id];
if (blobs[filename] !== undefined) images[id] = blobs[filename];else images[id] = images[id].split('\\').pop();
}
return images;
},
// Parse embedded image data in FBXTree.Video.Content
parseImage: function parseImage(videoNode) {
var content = videoNode.Content;
var fileName = videoNode.RelativeFilename || videoNode.Filename;
var extension = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase();
var type;
switch (extension) {
case 'bmp':
type = 'image/bmp';
break;
case 'jpg':
case 'jpeg':
type = 'image/jpeg';
break;
case 'png':
type = 'image/png';
break;
case 'tif':
type = 'image/tiff';
break;
case 'tga':
if (this.manager.getHandler('.tga') === null) {
console.warn('FBXLoader: TGA loader not found, skipping ', fileName);
}
type = 'image/tga';
break;
default:
console.warn('FBXLoader: Image type "' + extension + '" is not supported.');
return;
}
if (typeof content === 'string') {
// ASCII format
return 'data:' + type + ';base64,' + content;
} else {
// Binary Format
var array = new Uint8Array(content);
return window.URL.createObjectURL(new Blob([array], {
type: type
}));
}
},
// Parse nodes in FBXTree.Objects.Texture
// These contain details such as UV scaling, cropping, rotation etc and are connected
// to images in FBXTree.Objects.Video
parseTextures: function parseTextures(images) {
var textureMap = new _Map();
if ('Texture' in fbxTree.Objects) {
var textureNodes = fbxTree.Objects.Texture;
for (var nodeID in textureNodes) {
var texture = this.parseTexture(textureNodes[nodeID], images);
textureMap.set(_parseInt(nodeID), texture);
}
}
return textureMap;
},
// Parse individual node in FBXTree.Objects.Texture
parseTexture: function parseTexture(textureNode, images) {
var texture = this.loadTexture(textureNode, images);
texture.ID = textureNode.id;
texture.name = textureNode.attrName;
var wrapModeU = textureNode.WrapModeU;
var wrapModeV = textureNode.WrapModeV;
var valueU = wrapModeU !== undefined ? wrapModeU.value : 0;
var valueV = wrapModeV !== undefined ? wrapModeV.value : 0; // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a
// 0: repeat(default), 1: clamp
texture.wrapS = valueU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
texture.wrapT = valueV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
if ('Scaling' in textureNode) {
var values = textureNode.Scaling.value;
texture.repeat.x = values[0];
texture.repeat.y = values[1];
}
return texture;
},
// load a texture specified as a blob or data URI, or via an external URL using THREE.TextureLoader
loadTexture: function loadTexture(textureNode, images) {
var fileName;
var currentPath = this.textureLoader.path;
var children = connections.get(textureNode.id).children;
if (children !== undefined && children.length > 0 && images[children[0].ID] !== undefined) {
fileName = images[children[0].ID];
if (fileName.indexOf('blob:') === 0 || fileName.indexOf('data:') === 0) {
this.textureLoader.setPath(undefined);
}
}
var texture;
var extension = textureNode.FileName.slice(-3).toLowerCase();
if (extension === 'tga') {
var loader = this.manager.getHandler('.tga');
if (loader === null) {
console.warn('FBXLoader: TGA loader not found, creating placeholder texture for', textureNode.RelativeFilename);
texture = new THREE.Texture();
} else {
texture = loader.load(fileName);
}
} else if (extension === 'psd') {
console.warn('FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename);
texture = new THREE.Texture();
} else {
texture = this.textureLoader.load(fileName);
}
this.textureLoader.setPath(currentPath);
return texture;
},
// Parse nodes in FBXTree.Objects.Material
parseMaterials: function parseMaterials(textureMap) {
var materialMap = new _Map();
if ('Material' in fbxTree.Objects) {
var materialNodes = fbxTree.Objects.Material;
for (var nodeID in materialNodes) {
var material = this.parseMaterial(materialNodes[nodeID], textureMap);
if (material !== null) materialMap.set(_parseInt(nodeID), material);
}
}
return materialMap;
},
// Parse single node in FBXTree.Objects.Material
// Materials are connected to texture maps in FBXTree.Objects.Textures
// FBX format currently only supports Lambert and Phong shading models
parseMaterial: function parseMaterial(materialNode, textureMap) {
var ID = materialNode.id;
var name = materialNode.attrName;
var type = materialNode.ShadingModel; // Case where FBX wraps shading model in property object.
if (_typeof(type) === 'object') {
type = type.value;
} // Ignore unused materials which don't have any connections.
if (!connections.has(ID)) return null;
var parameters = this.parseParameters(materialNode, textureMap, ID);
var material;
switch (type.toLowerCase()) {
case 'phong':
material = new THREE.MeshPhongMaterial();
break;
case 'lambert':
material = new THREE.MeshLambertMaterial();
break;
default:
console.warn('THREE.FBXLoader: unknown material type "%s". Defaulting to MeshPhongMaterial.', type);
material = new THREE.MeshPhongMaterial();
break;
}
material.setValues(parameters);
material.name = name;
return material;
},
// Parse FBX material and return parameters suitable for a three.js material
// Also parse the texture map and return any textures associated with the material
parseParameters: function parseParameters(materialNode, textureMap, ID) {
var parameters = {};
if (materialNode.BumpFactor) {
parameters.bumpScale = materialNode.BumpFactor.value;
}
if (materialNode.Diffuse) {
parameters.color = new THREE.Color().fromArray(materialNode.Diffuse.value);
} else if (materialNode.DiffuseColor && materialNode.DiffuseColor.type === 'Color') {
// The blender exporter exports diffuse here instead of in materialNode.Diffuse
parameters.color = new THREE.Color().fromArray(materialNode.DiffuseColor.value);
}
if (materialNode.DisplacementFactor) {
parameters.displacementScale = materialNode.DisplacementFactor.value;
}
if (materialNode.Emissive) {
parameters.emissive = new THREE.Color().fromArray(materialNode.Emissive.value);
} else if (materialNode.EmissiveColor && materialNode.EmissiveColor.type === 'Color') {
// The blender exporter exports emissive color here instead of in materialNode.Emissive
parameters.emissive = new THREE.Color().fromArray(materialNode.EmissiveColor.value);
}
if (materialNode.EmissiveFactor) {
parameters.emissiveIntensity = _parseFloat(materialNode.EmissiveFactor.value);
}
if (materialNode.Opacity) {
parameters.opacity = _parseFloat(materialNode.Opacity.value);
}
if (parameters.opacity < 1.0) {
parameters.transparent = true;
}
if (materialNode.ReflectionFactor) {
parameters.reflectivity = materialNode.ReflectionFactor.value;
}
if (materialNode.Shininess) {
parameters.shininess = materialNode.Shininess.value;
}
if (materialNode.Specular) {
parameters.specular = new THREE.Color().fromArray(materialNode.Specular.value);
} else if (materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color') {
// The blender exporter exports specular color here instead of in materialNode.Specular
parameters.specular = new THREE.Color().fromArray(materialNode.SpecularColor.value);
}
var scope = this;
connections.get(ID).children.forEach(function (child) {
var type = child.relationship;
switch (type) {
case 'Bump':
parameters.bumpMap = scope.getTexture(textureMap, child.ID);
break;
case 'Maya|TEX_ao_map':
parameters.aoMap = scope.getTexture(textureMap, child.ID);
break;
case 'DiffuseColor':
case 'Maya|TEX_color_map':
parameters.map = scope.getTexture(textureMap, child.ID);
parameters.map.encoding = THREE.sRGBEncoding;
break;
case 'DisplacementColor':
parameters.displacementMap = scope.getTexture(textureMap, child.ID);
break;
case 'EmissiveColor':
parameters.emissiveMap = scope.getTexture(textureMap, child.ID);
parameters.emissiveMap.encoding = THREE.sRGBEncoding;
break;
case 'NormalMap':
case 'Maya|TEX_normal_map':
parameters.normalMap = scope.getTexture(textureMap, child.ID);
break;
case 'ReflectionColor':
parameters.envMap = scope.getTexture(textureMap, child.ID);
parameters.envMap.mapping = THREE.EquirectangularReflectionMapping;
parameters.envMap.encoding = THREE.sRGBEncoding;
break;
case 'SpecularColor':
parameters.specularMap = scope.getTexture(textureMap, child.ID);
parameters.specularMap.encoding = THREE.sRGBEncoding;
break;
case 'TransparentColor':
case 'TransparencyFactor':
parameters.alphaMap = scope.getTexture(textureMap, child.ID);
parameters.transparent = true;
break;
case 'AmbientColor':
case 'ShininessExponent': // AKA glossiness map
case 'SpecularFactor': // AKA specularLevel
case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor
default:
console.warn('THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type);
break;
}
});
return parameters;
},
// get a texture from the textureMap for use by a material.
getTexture: function getTexture(textureMap, id) {
// if the texture is a layered texture, just use the first layer and issue a warning
if ('LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture) {
console.warn('THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.');
id = connections.get(id).children[0].ID;
}
return textureMap.get(id);
},
// Parse nodes in FBXTree.Objects.Deformer
// Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here
// Generates map of Skeleton-like objects for use later when generating and binding skeletons.
parseDeformers: function parseDeformers() {
var skeletons = {};
var morphTargets = {};
if ('Deformer' in fbxTree.Objects) {
var DeformerNodes = fbxTree.Objects.Deformer;
for (var nodeID in DeformerNodes) {
var deformerNode = DeformerNodes[nodeID];
var relationships = connections.get(_parseInt(nodeID));
if (deformerNode.attrType === 'Skin') {
var skeleton = this.parseSkeleton(relationships, DeformerNodes);
skeleton.ID = nodeID;
if (relationships.parents.length > 1) console.warn('THREE.FBXLoader: skeleton attached to more than one geometry is not supported.');
skeleton.geometryID = relationships.parents[0].ID;
skeletons[nodeID] = skeleton;
} else if (deformerNode.attrType === 'BlendShape') {
var morphTarget = {
id: nodeID
};
morphTarget.rawTargets = this.parseMorphTargets(relationships, DeformerNodes);
morphTarget.id = nodeID;
if (relationships.parents.length > 1) console.warn('THREE.FBXLoader: morph target attached to more than one geometry is not supported.');
morphTargets[nodeID] = morphTarget;
}
}
}
return {
skeletons: skeletons,
morphTargets: morphTargets
};
},
// Parse single nodes in FBXTree.Objects.Deformer
// The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster'
// Each skin node represents a skeleton and each cluster node represents a bone
parseSkeleton: function parseSkeleton(relationships, deformerNodes) {
var rawBones = [];
relationships.children.forEach(function (child) {
var boneNode = deformerNodes[child.ID];
if (boneNode.attrType !== 'Cluster') return;
var rawBone = {
ID: child.ID,
indices: [],
weights: [],
transformLink: new THREE.Matrix4().fromArray(boneNode.TransformLink.a) // transform: new THREE.Matrix4().fromArray( boneNode.Transform.a ),
// linkMode: boneNode.Mode,
};
if ('Indexes' in boneNode) {
rawBone.indices = boneNode.Indexes.a;
rawBone.weights = boneNode.Weights.a;
}
rawBones.push(rawBone);
});
return {
rawBones: rawBones,
bones: []
};
},
// The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel"
parseMorphTargets: function parseMorphTargets(relationships, deformerNodes) {
var rawMorphTargets = [];
for (var i = 0; i < relationships.children.length; i++) {
var child = relationships.children[i];
var morphTargetNode = deformerNodes[child.ID];
var rawMorphTarget = {
name: morphTargetNode.attrName,
initialWeight: morphTargetNode.DeformPercent,
id: morphTargetNode.id,
fullWeights: morphTargetNode.FullWeights.a
};
if (morphTargetNode.attrType !== 'BlendShapeChannel') return;
rawMorphTarget.geoID = connections.get(_parseInt(child.ID)).children.filter(function (child) {
return child.relationship === undefined;
})[0].ID;
rawMorphTargets.push(rawMorphTarget);
}
return rawMorphTargets;
},
// create the main THREE.Group() to be returned by the loader
parseScene: function parseScene(deformers, geometryMap, materialMap) {
sceneGraph = new THREE.Group();
var modelMap = this.parseModels(deformers.skeletons, geometryMap, materialMap);
var modelNodes = fbxTree.Objects.Model;
var scope = this;
modelMap.forEach(function (model) {
var modelNode = modelNodes[model.ID];
scope.setLookAtProperties(model, modelNode);
var parentConnections = connections.get(model.ID).parents;
parentConnections.forEach(function (connection) {
var parent = modelMap.get(connection.ID);
if (parent !== undefined) parent.add(model);
});
if (model.parent === null) {
sceneGraph.add(model);
}
});
this.bindSkeleton(deformers.skeletons, geometryMap, modelMap);
this.createAmbientLight();
this.setupMorphMaterials();
sceneGraph.traverse(function (node) {
if (node.userData.transformData) {
if (node.parent) node.userData.transformData.parentMatrixWorld = node.parent.matrix;
var transform = generateTransform(node.userData.transformData);
node.applyMatrix4(transform);
}
});
var animations = new AnimationParser().parse(); // if all the models where already combined in a single group, just return that
if (sceneGraph.children.length === 1 && sceneGraph.children[0].isGroup) {
sceneGraph.children[0].animations = animations;
sceneGraph = sceneGraph.children[0];
}
sceneGraph.animations = animations;
},
// parse nodes in FBXTree.Objects.Model
parseModels: function parseModels(skeletons, geometryMap, materialMap) {
var modelMap = new _Map();
var modelNodes = fbxTree.Objects.Model;
for (var nodeID in modelNodes) {
var id = _parseInt(nodeID);
var node = modelNodes[nodeID];
var relationships = connections.get(id);
var model = this.buildSkeleton(relationships, skeletons, id, node.attrName);
if (!model) {
switch (node.attrType) {
case 'Camera':
model = this.createCamera(relationships);
break;
case 'Light':
model = this.createLight(relationships);
break;
case 'Mesh':
model = this.createMesh(relationships, geometryMap, materialMap);
break;
case 'NurbsCurve':
model = this.createCurve(relationships, geometryMap);
break;
case 'LimbNode':
case 'Root':
model = new THREE.Bone();
break;
case 'Null':
default:
model = new THREE.Group();
break;
}
model.name = node.attrName ? THREE.PropertyBinding.sanitizeNodeName(node.attrName) : '';
model.ID = id;
}
this.getTransformData(model, node);
modelMap.set(id, model);
}
return modelMap;
},
buildSkeleton: function buildSkeleton(relationships, skeletons, id, name) {
var bone = null;
relationships.parents.forEach(function (parent) {
for (var ID in skeletons) {
var skeleton = skeletons[ID];
skeleton.rawBones.forEach(function (rawBone, i) {
if (rawBone.ID === parent.ID) {
var subBone = bone;
bone = new THREE.Bone();
bone.matrixWorld.copy(rawBone.transformLink); // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id
bone.name = name ? THREE.PropertyBinding.sanitizeNodeName(name) : '';
bone.ID = id;
skeleton.bones[i] = bone; // In cases where a bone is shared between multiple meshes
// duplicate the bone here and and it as a child of the first bone
if (subBone !== null) {
bone.add(subBone);
}
}
});
}
});
return bone;
},
// create a THREE.PerspectiveCamera or THREE.OrthographicCamera
createCamera: function createCamera(relationships) {
var model;
var cameraAttribute;
relationships.children.forEach(function (child) {
var attr = fbxTree.Objects.NodeAttribute[child.ID];
if (attr !== undefined) {
cameraAttribute = attr;
}
});
if (cameraAttribute === undefined) {
model = new THREE.Object3D();
} else {
var type = 0;
if (cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1) {
type = 1;
}
var nearClippingPlane = 1;
if (cameraAttribute.NearPlane !== undefined) {
nearClippingPlane = cameraAttribute.NearPlane.value / 1000;
}
var farClippingPlane = 1000;
if (cameraAttribute.FarPlane !== undefined) {
farClippingPlane = cameraAttribute.FarPlane.value / 1000;
}
var width = window.innerWidth;
var height = window.innerHeight;
if (cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined) {
width = cameraAttribute.AspectWidth.value;
height = cameraAttribute.AspectHeight.value;
}
var aspect = width / height;
var fov = 45;
if (cameraAttribute.FieldOfView !== undefined) {
fov = cameraAttribute.FieldOfView.value;
}
var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null;
switch (type) {
case 0:
// Perspective
model = new THREE.PerspectiveCamera(fov, aspect, nearClippingPlane, farClippingPlane);
if (focalLength !== null) model.setFocalLength(focalLength);
break;
case 1:
// Orthographic
model = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, nearClippingPlane, farClippingPlane);
break;
default:
console.warn('THREE.FBXLoader: Unknown camera type ' + type + '.');
model = new THREE.Object3D();
break;
}
}
return model;
},
// Create a THREE.DirectionalLight, THREE.PointLight or THREE.SpotLight
createLight: function createLight(relationships) {
var model;
var lightAttribute;
relationships.children.forEach(function (child) {
var attr = fbxTree.Objects.NodeAttribute[child.ID];
if (attr !== undefined) {
lightAttribute = attr;
}
});
if (lightAttribute === undefined) {
model = new THREE.Object3D();
} else {
var type; // LightType can be undefined for Point lights
if (lightAttribute.LightType === undefined) {
type = 0;
} else {
type = lightAttribute.LightType.value;
}
var color = 0xffffff;
if (lightAttribute.Color !== undefined) {
color = new THREE.Color().fromArray(lightAttribute.Color.value);
}
var intensity = lightAttribute.Intensity === undefined ? 1 : lightAttribute.Intensity.value / 100; // light disabled
if (lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0) {
intensity = 0;
}
var distance = 0;
if (lightAttribute.FarAttenuationEnd !== undefined) {
if (lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0) {
distance = 0;
} else {
distance = lightAttribute.FarAttenuationEnd.value;
}
} // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd?
var decay = 1;
switch (type) {
case 0:
// Point
model = new THREE.PointLight(color, intensity, distance, decay);
break;
case 1:
// Directional
model = new THREE.DirectionalLight(color, intensity);
break;
case 2:
// Spot
var angle = Math.PI / 3;
if (lightAttribute.InnerAngle !== undefined) {
angle = THREE.MathUtils.degToRad(lightAttribute.InnerAngle.value);
}
var penumbra = 0;
if (lightAttribute.OuterAngle !== undefined) {
// TODO: this is not correct - FBX calculates outer and inner angle in degrees
// with OuterAngle > InnerAngle && OuterAngle <= Math.PI
// while three.js uses a penumbra between (0, 1) to attenuate the inner angle
penumbra = THREE.MathUtils.degToRad(lightAttribute.OuterAngle.value);
penumbra = Math.max(penumbra, 1);
}
model = new THREE.SpotLight(color, intensity, distance, angle, penumbra, decay);
break;
default:
console.warn('THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a THREE.PointLight.');
model = new THREE.PointLight(color, intensity);
break;
}
if (lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1) {
model.castShadow = true;
}
}
return model;
},
createMesh: function createMesh(relationships, geometryMap, materialMap) {
var model;
var geometry = null;
var material = null;
var materials = []; // get geometry and materials(s) from connections
relationships.children.forEach(function (child) {
if (geometryMap.has(child.ID)) {
geometry = geometryMap.get(child.ID);
}
if (materialMap.has(child.ID)) {
materials.push(materialMap.get(child.ID));
}
});
if (materials.length > 1) {
material = materials;
} else if (materials.length > 0) {
material = materials[0];
} else {
material = new THREE.MeshPhongMaterial({
color: 0xcccccc
});
materials.push(material);
}
if ('color' in geometry.attributes) {
materials.forEach(function (material) {
material.vertexColors = true;
});
}
if (geometry.FBX_Deformer) {
materials.forEach(function (material) {
material.skinning = true;
});
model = new THREE.SkinnedMesh(geometry, material);
model.normalizeSkinWeights();
} else {
model = new THREE.Mesh(geometry, material);
}
return model;
},
createCurve: function createCurve(relationships, geometryMap) {
var geometry = relationships.children.reduce(function (geo, child) {
if (geometryMap.has(child.ID)) geo = geometryMap.get(child.ID);
return geo;
}, null); // FBX does not list materials for Nurbs lines, so we'll just put our own in here.
var material = new THREE.LineBasicMaterial({
color: 0x3300ff,
linewidth: 1
});
return new THREE.Line(geometry, material);
},
// parse the model node for transform data
getTransformData: function getTransformData(model, modelNode) {
var transformData = {};
if ('InheritType' in modelNode) transformData.inheritType = _parseInt(modelNode.InheritType.value);
if ('RotationOrder' in modelNode) transformData.eulerOrder = getEulerOrder(modelNode.RotationOrder.value);else transformData.eulerOrder = 'ZYX';
if ('Lcl_Translation' in modelNode) transformData.translation = modelNode.Lcl_Translation.value;
if ('PreRotation' in modelNode) transformData.preRotation = modelNode.PreRotation.value;
if ('Lcl_Rotation' in modelNode) transformData.rotation = modelNode.Lcl_Rotation.value;
if ('PostRotation' in modelNode) transformData.postRotation = modelNode.PostRotation.value;
if ('Lcl_Scaling' in modelNode) transformData.scale = modelNode.Lcl_Scaling.value;
if ('ScalingOffset' in modelNode) transformData.scalingOffset = modelNode.ScalingOffset.value;
if ('ScalingPivot' in modelNode) transformData.scalingPivot = modelNode.ScalingPivot.value;
if ('RotationOffset' in modelNode) transformData.rotationOffset = modelNode.RotationOffset.value;
if ('RotationPivot' in modelNode) transformData.rotationPivot = modelNode.RotationPivot.value;
model.userData.transformData = transformData;
},
setLookAtProperties: function setLookAtProperties(model, modelNode) {
if ('LookAtProperty' in modelNode) {
var children = connections.get(model.ID).children;
children.forEach(function (child) {
if (child.relationship === 'LookAtProperty') {
var lookAtTarget = fbxTree.Objects.Model[child.ID];
if ('Lcl_Translation' in lookAtTarget) {
var pos = lookAtTarget.Lcl_Translation.value; // DirectionalLight, SpotLight
if (model.target !== undefined) {
model.target.position.fromArray(pos);
sceneGraph.add(model.target);
} else {
// Cameras and other Object3Ds
model.lookAt(new THREE.Vector3().fromArray(pos));
}
}
}
});
}
},
bindSkeleton: function bindSkeleton(skeletons, geometryMap, modelMap) {
var bindMatrices = this.parsePoseNodes();
for (var ID in skeletons) {
var skeleton = skeletons[ID];
var parents = connections.get(_parseInt(skeleton.ID)).parents;
parents.forEach(function (parent) {
if (geometryMap.has(parent.ID)) {
var geoID = parent.ID;
var geoRelationships = connections.get(geoID);
geoRelationships.parents.forEach(function (geoConnParent) {
if (modelMap.has(geoConnParent.ID)) {
var model = modelMap.get(geoConnParent.ID);
model.bind(new THREE.Skeleton(skeleton.bones), bindMatrices[geoConnParent.ID]);
}
});
}
});
}
},
parsePoseNodes: function parsePoseNodes() {
var bindMatrices = {};
if ('Pose' in fbxTree.Objects) {
var BindPoseNode = fbxTree.Objects.Pose;
for (var nodeID in BindPoseNode) {
if (BindPoseNode[nodeID].attrType === 'BindPose') {
var poseNodes = BindPoseNode[nodeID].PoseNode;
if (_Array$isArray(poseNodes)) {
poseNodes.forEach(function (poseNode) {
bindMatrices[poseNode.Node] = new THREE.Matrix4().fromArray(poseNode.Matrix.a);
});
} else {
bindMatrices[poseNodes.Node] = new THREE.Matrix4().fromArray(poseNodes.Matrix.a);
}
}
}
}
return bindMatrices;
},
// Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
createAmbientLight: function createAmbientLight() {
if ('GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings) {
var ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
var r = ambientColor[0];
var g = ambientColor[1];
var b = ambientColor[2];
if (r !== 0 || g !== 0 || b !== 0) {
var color = new THREE.Color(r, g, b);
sceneGraph.add(new THREE.AmbientLight(color, 1));
}
}
},
setupMorphMaterials: function setupMorphMaterials() {
var scope = this;
sceneGraph.traverse(function (child) {
if (child.isMesh) {
if (child.geometry.morphAttributes.position && child.geometry.morphAttributes.position.length) {
if (_Array$isArray(child.material)) {
child.material.forEach(function (material, i) {
scope.setupMorphMaterial(child, material, i);
});
} else {
scope.setupMorphMaterial(child, child.material);
}
}
}
});
},
setupMorphMaterial: function setupMorphMaterial(child, material, index) {
var uuid = child.uuid;
var matUuid = material.uuid; // if a geometry has morph targets, it cannot share the material with other geometries
var sharedMat = false;
sceneGraph.traverse(function (node) {
if (node.isMesh) {
if (_Array$isArray(node.material)) {
node.material.forEach(function (mat) {
if (mat.uuid === matUuid && node.uuid !== uuid) sharedMat = true;
});
} else if (node.material.uuid === matUuid && node.uuid !== uuid) sharedMat = true;
}
});
if (sharedMat === true) {
var clonedMat = material.clone();
clonedMat.morphTargets = true;
if (index === undefined) child.material = clonedMat;else child.material[index] = clonedMat;
} else material.morphTargets = true;
}
}; // parse Geometry data from FBXTree and return map of BufferGeometries
function GeometryParser() {}
GeometryParser.prototype = {
constructor: GeometryParser,
// Parse nodes in FBXTree.Objects.Geometry
parse: function parse(deformers) {
var geometryMap = new _Map();
if ('Geometry' in fbxTree.Objects) {
var geoNodes = fbxTree.Objects.Geometry;
for (var nodeID in geoNodes) {
var relationships = connections.get(_parseInt(nodeID));
var geo = this.parseGeometry(relationships, geoNodes[nodeID], deformers);
geometryMap.set(_parseInt(nodeID), geo);
}
}
return geometryMap;
},
// Parse single node in FBXTree.Objects.Geometry
parseGeometry: function parseGeometry(relationships, geoNode, deformers) {
switch (geoNode.attrType) {
case 'Mesh':
return this.parseMeshGeometry(relationships, geoNode, deformers);
break;
case 'NurbsCurve':
return this.parseNurbsGeometry(geoNode);
break;
}
},
// Parse single node mesh geometry in FBXTree.Objects.Geometry
parseMeshGeometry: function parseMeshGeometry(relationships, geoNode, deformers) {
var skeletons = deformers.skeletons;
var morphTargets = [];
var modelNodes = relationships.parents.map(function (parent) {
return fbxTree.Objects.Model[parent.ID];
}); // don't create geometry if it is not associated with any models
if (modelNodes.length === 0) return;
var skeleton = relationships.children.reduce(function (skeleton, child) {
if (skeletons[child.ID] !== undefined) skeleton = skeletons[child.ID];
return skeleton;
}, null);
relationships.children.forEach(function (child) {
if (deformers.morphTargets[child.ID] !== undefined) {
morphTargets.push(deformers.morphTargets[child.ID]);
}
}); // Assume one model and get the preRotation from that
// if there is more than one model associated with the geometry this may cause problems
var modelNode = modelNodes[0];
var transformData = {};
if ('RotationOrder' in modelNode) transformData.eulerOrder = getEulerOrder(modelNode.RotationOrder.value);
if ('InheritType' in modelNode) transformData.inheritType = _parseInt(modelNode.InheritType.value);
if ('GeometricTranslation' in modelNode) transformData.translation = modelNode.GeometricTranslation.value;
if ('GeometricRotation' in modelNode) transformData.rotation = modelNode.GeometricRotation.value;
if ('GeometricScaling' in modelNode) transformData.scale = modelNode.GeometricScaling.value;
var transform = generateTransform(transformData);
return this.genGeometry(geoNode, skeleton, morphTargets, transform);
},
// Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry
genGeometry: function genGeometry(geoNode, skeleton, morphTargets, preTransform) {
var geo = new THREE.BufferGeometry();
if (geoNode.attrName) geo.name = geoNode.attrName;
var geoInfo = this.parseGeoNode(geoNode, skeleton);
var buffers = this.genBuffers(geoInfo);
var positionAttribute = new THREE.Float32BufferAttribute(buffers.vertex, 3);
positionAttribute.applyMatrix4(preTransform);
geo.setAttribute('position', positionAttribute);
if (buffers.colors.length > 0) {
geo.setAttribute('color', new THREE.Float32BufferAttribute(buffers.colors, 3));
}
if (skeleton) {
geo.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(buffers.weightsIndices, 4));
geo.setAttribute('skinWeight', new THREE.Float32BufferAttribute(buffers.vertexWeights, 4)); // used later to bind the skeleton to the model
geo.FBX_Deformer = skeleton;
}
if (buffers.normal.length > 0) {
var normalMatrix = new THREE.Matrix3().getNormalMatrix(preTransform);
var normalAttribute = new THREE.Float32BufferAttribute(buffers.normal, 3);
normalAttribute.applyNormalMatrix(normalMatrix);
geo.setAttribute('normal', normalAttribute);
}
buffers.uvs.forEach(function (uvBuffer, i) {
// subsequent uv buffers are called 'uv1', 'uv2', ...
var name = 'uv' + (i + 1).toString(); // the first uv buffer is just called 'uv'
if (i === 0) {
name = 'uv';
}
geo.setAttribute(name, new THREE.Float32BufferAttribute(buffers.uvs[i], 2));
});
if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') {
// Convert the material indices of each vertex into rendering groups on the geometry.
var prevMaterialIndex = buffers.materialIndex[0];
var startIndex = 0;
buffers.materialIndex.forEach(function (currentIndex, i) {
if (currentIndex !== prevMaterialIndex) {
geo.addGroup(startIndex, i - startIndex, prevMaterialIndex);
prevMaterialIndex = currentIndex;
startIndex = i;
}
}); // the loop above doesn't add the last group, do that here.
if (geo.groups.length > 0) {
var lastGroup = geo.groups[geo.groups.length - 1];
var lastIndex = lastGroup.start + lastGroup.count;
if (lastIndex !== buffers.materialIndex.length) {
geo.addGroup(lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex);
}
} // case where there are multiple materials but the whole geometry is only
// using one of them
if (geo.groups.length === 0) {
geo.addGroup(0, buffers.materialIndex.length, buffers.materialIndex[0]);
}
}
this.addMorphTargets(geo, geoNode, morphTargets, preTransform);
return geo;
},
parseGeoNode: function parseGeoNode(geoNode, skeleton) {
var geoInfo = {};
geoInfo.vertexPositions = geoNode.Vertices !== undefined ? geoNode.Vertices.a : [];
geoInfo.vertexIndices = geoNode.PolygonVertexIndex !== undefined ? geoNode.PolygonVertexIndex.a : [];
if (geoNode.LayerElementColor) {
geoInfo.color = this.parseVertexColors(geoNode.LayerElementColor[0]);
}
if (geoNode.LayerElementMaterial) {
geoInfo.material = this.parseMaterialIndices(geoNode.LayerElementMaterial[0]);
}
if (geoNode.LayerElementNormal) {
geoInfo.normal = this.parseNormals(geoNode.LayerElementNormal[0]);
}
if (geoNode.LayerElementUV) {
geoInfo.uv = [];
var i = 0;
while (geoNode.LayerElementUV[i]) {
if (geoNode.LayerElementUV[i].UV) {
geoInfo.uv.push(this.parseUVs(geoNode.LayerElementUV[i]));
}
i++;
}
}
geoInfo.weightTable = {};
if (skeleton !== null) {
geoInfo.skeleton = skeleton;
skeleton.rawBones.forEach(function (rawBone, i) {
// loop over the bone's vertex indices and weights
rawBone.indices.forEach(function (index, j) {
if (geoInfo.weightTable[index] === undefined) geoInfo.weightTable[index] = [];
geoInfo.weightTable[index].push({
id: i,
weight: rawBone.weights[j]
});
});
});
}
return geoInfo;
},
genBuffers: function genBuffers(geoInfo) {
var buffers = {
vertex: [],
normal: [],
colors: [],
uvs: [],
materialIndex: [],
vertexWeights: [],
weightsIndices: []
};
var polygonIndex = 0;
var faceLength = 0;
var displayedWeightsWarning = false; // these will hold data for a single face
var facePositionIndexes = [];
var faceNormals = [];
var faceColors = [];
var faceUVs = [];
var faceWeights = [];
var faceWeightIndices = [];
var scope = this;
geoInfo.vertexIndices.forEach(function (vertexIndex, polygonVertexIndex) {
var endOfFace = false; // Face index and vertex index arrays are combined in a single array
// A cube with quad faces looks like this:
// PolygonVertexIndex: *24 {
// a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5
// }
// Negative numbers mark the end of a face - first face here is 0, 1, 3, -3
// to find index of last vertex bit shift the index: ^ - 1
if (vertexIndex < 0) {
vertexIndex = vertexIndex ^ -1; // equivalent to ( x * -1 ) - 1
endOfFace = true;
}
var weightIndices = [];
var weights = [];
facePositionIndexes.push(vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2);
if (geoInfo.color) {
var data = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color);
faceColors.push(data[0], data[1], data[2]);
}
if (geoInfo.skeleton) {
if (geoInfo.weightTable[vertexIndex] !=