ng2-3d-editor
Version:
Angular 2 3D Editor/viewer
717 lines (714 loc) • 958 kB
JavaScript
!function(e){function r(e,r,o){return 4===arguments.length?t.apply(this,arguments):void n(e,{declarative:!0,deps:r,declare:o})}function t(e,r,t,o){n(e,{declarative:!1,deps:r,executingRequire:t,execute:o})}function n(e,r){r.name=e,e in v||(v[e]=r),r.normalizedDeps=r.deps}function o(e,r){if(r[e.groupIndex]=r[e.groupIndex]||[],-1==g.call(r[e.groupIndex],e)){r[e.groupIndex].push(e);for(var t=0,n=e.normalizedDeps.length;n>t;t++){var a=e.normalizedDeps[t],u=v[a];if(u&&!u.evaluated){var d=e.groupIndex+(u.declarative!=e.declarative);if(void 0===u.groupIndex||u.groupIndex<d){if(void 0!==u.groupIndex&&(r[u.groupIndex].splice(g.call(r[u.groupIndex],u),1),0==r[u.groupIndex].length))throw new TypeError("Mixed dependency cycle detected");u.groupIndex=d}o(u,r)}}}}function a(e){var r=v[e];r.groupIndex=0;var t=[];o(r,t);for(var n=!!r.declarative==t.length%2,a=t.length-1;a>=0;a--){for(var u=t[a],i=0;i<u.length;i++){var s=u[i];n?d(s):l(s)}n=!n}}function u(e){return y[e]||(y[e]={name:e,dependencies:[],exports:{},importers:[]})}function d(r){if(!r.module){var t=r.module=u(r.name),n=r.module.exports,o=r.declare.call(e,function(e,r){if(t.locked=!0,"object"==typeof e)for(var o in e)n[o]=e[o];else n[e]=r;for(var a=0,u=t.importers.length;u>a;a++){var d=t.importers[a];if(!d.locked)for(var i=0;i<d.dependencies.length;++i)d.dependencies[i]===t&&d.setters[i](n)}return t.locked=!1,r},{id:r.name});t.setters=o.setters,t.execute=o.execute;for(var a=0,i=r.normalizedDeps.length;i>a;a++){var l,s=r.normalizedDeps[a],c=v[s],f=y[s];f?l=f.exports:c&&!c.declarative?l=c.esModule:c?(d(c),f=c.module,l=f.exports):l=p(s),f&&f.importers?(f.importers.push(t),t.dependencies.push(f)):t.dependencies.push(null),t.setters[a]&&t.setters[a](l)}}}function i(e){var r,t=v[e];if(t)t.declarative?f(e,[]):t.evaluated||l(t),r=t.module.exports;else if(r=p(e),!r)throw new Error("Unable to load dependency "+e+".");return(!t||t.declarative)&&r&&r.__useDefault?r["default"]:r}function l(r){if(!r.module){var t={},n=r.module={exports:t,id:r.name};if(!r.executingRequire)for(var o=0,a=r.normalizedDeps.length;a>o;o++){var u=r.normalizedDeps[o],d=v[u];d&&l(d)}r.evaluated=!0;var c=r.execute.call(e,function(e){for(var t=0,n=r.deps.length;n>t;t++)if(r.deps[t]==e)return i(r.normalizedDeps[t]);throw new TypeError("Module "+e+" not declared as a dependency.")},t,n);void 0!==typeof c&&(n.exports=c),t=n.exports,t&&t.__esModule?r.esModule=t:r.esModule=s(t)}}function s(r){var t={};if(("object"==typeof r||"function"==typeof r)&&r!==e)if(m)for(var n in r)"default"!==n&&c(t,r,n);else{var o=r&&r.hasOwnProperty;for(var n in r)"default"===n||o&&!r.hasOwnProperty(n)||(t[n]=r[n])}return t["default"]=r,x(t,"__useDefault",{value:!0}),t}function c(e,r,t){try{var n;(n=Object.getOwnPropertyDescriptor(r,t))&&x(e,t,n)}catch(o){return e[t]=r[t],!1}}function f(r,t){var n=v[r];if(n&&!n.evaluated&&n.declarative){t.push(r);for(var o=0,a=n.normalizedDeps.length;a>o;o++){var u=n.normalizedDeps[o];-1==g.call(t,u)&&(v[u]?f(u,t):p(u))}n.evaluated||(n.evaluated=!0,n.module.execute.call(e))}}function p(e){if(I[e])return I[e];if("@node/"==e.substr(0,6))return I[e]=s(D(e.substr(6)));var r=v[e];if(!r)throw"Module "+e+" not present.";return a(e),f(e,[]),v[e]=void 0,r.declarative&&x(r.module.exports,"__esModule",{value:!0}),I[e]=r.declarative?r.module.exports:r.esModule}var v={},g=Array.prototype.indexOf||function(e){for(var r=0,t=this.length;t>r;r++)if(this[r]===e)return r;return-1},m=!0;try{Object.getOwnPropertyDescriptor({a:0},"a")}catch(h){m=!1}var x;!function(){try{Object.defineProperty({},"a",{})&&(x=Object.defineProperty)}catch(e){x=function(e,r,t){try{e[r]=t.value||t.get.call(e)}catch(n){}}}}();var y={},D="undefined"!=typeof System&&System._nodeRequire||"undefined"!=typeof require&&require.resolve&&"undefined"!=typeof process&&require,I={"@empty":{}};return function(e,n,o,a){return function(u){u(function(u){for(var d={_nodeRequire:D,register:r,registerDynamic:t,get:p,set:function(e,r){I[e]=r},newModule:function(e){return e}},i=0;i<n.length;i++)(function(e,r){r&&r.__esModule?I[e]=r:I[e]=s(r)})(n[i],arguments[i]);a(d);var l=p(e[0]);if(e.length>1)for(var i=1;i<e.length;i++)p(e[i]);return o?l["default"]:l})}}}("undefined"!=typeof self?self:global)
(["1"], ["a","3"], true, function($__System) {
var require = this.require, exports = this.exports, module = this.module;
!function(e){function n(e,n){e=e.replace(l,"");var r=e.match(u),t=(r[1].split(",")[n]||"require").replace(s,""),i=p[t]||(p[t]=new RegExp(a+t+f,"g"));i.lastIndex=0;for(var o,c=[];o=i.exec(e);)c.push(o[2]||o[3]);return c}function r(e,n,t,o){if("object"==typeof e&&!(e instanceof Array))return r.apply(null,Array.prototype.splice.call(arguments,1,arguments.length-1));if("string"==typeof e&&"function"==typeof n&&(e=[e]),!(e instanceof Array)){if("string"==typeof e){var l=i.get(e);return l.__useDefault?l["default"]:l}throw new TypeError("Invalid require")}for(var a=[],f=0;f<e.length;f++)a.push(i["import"](e[f],o));Promise.all(a).then(function(e){n&&n.apply(null,e)},t)}function t(t,l,a){"string"!=typeof t&&(a=l,l=t,t=null),l instanceof Array||(a=l,l=["require","exports","module"].splice(0,a.length)),"function"!=typeof a&&(a=function(e){return function(){return e}}(a)),void 0===l[l.length-1]&&l.pop();var f,u,s;-1!=(f=o.call(l,"require"))&&(l.splice(f,1),t||(l=l.concat(n(a.toString(),f)))),-1!=(u=o.call(l,"exports"))&&l.splice(u,1),-1!=(s=o.call(l,"module"))&&l.splice(s,1);var p={name:t,deps:l,execute:function(n,t,o){for(var p=[],c=0;c<l.length;c++)p.push(n(l[c]));o.uri=o.id,o.config=function(){},-1!=s&&p.splice(s,0,o),-1!=u&&p.splice(u,0,t),-1!=f&&p.splice(f,0,function(e,t,l){return"string"==typeof e&&"function"!=typeof t?n(e):r.call(i,e,t,l,o.id)});var d=a.apply(-1==u?e:t,p);return"undefined"==typeof d&&o&&(d=o.exports),"undefined"!=typeof d?d:void 0}};if(t)c.anonDefine||c.isBundle?c.anonDefine&&c.anonDefine.name&&(c.anonDefine=null):c.anonDefine=p,c.isBundle=!0,i.registerDynamic(p.name,p.deps,!1,p.execute);else{if(c.anonDefine&&!c.anonDefine.name)throw new Error("Multiple anonymous defines in module "+t);c.anonDefine=p}}var i=$__System,o=Array.prototype.indexOf||function(e){for(var n=0,r=this.length;r>n;n++)if(this[n]===e)return n;return-1},l=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm,a="(?:^|[^$_a-zA-Z\\xA0-\\uFFFF.])",f="\\s*\\(\\s*(\"([^\"]+)\"|'([^']+)')\\s*\\)",u=/\(([^\)]*)\)/,s=/^\s+|\s+$/g,p={};t.amd={};var c={isBundle:!1,anonDefine:null};i.amdDefine=t,i.amdRequire=r}("undefined"!=typeof self?self:global);
$__System.registerDynamic("2", ["3", "4"], true, function ($__require, exports, module) {
"use strict";
var define,
global = this || self,
GLOBAL = global;
var __extends = this && this.__extends || function () {
var extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) {
d.__proto__ = b;
} || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
};
return function (d, b) {
extendStatics(d, b);
function __() {
this.constructor = d;
}
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
}();
var __decorate = this && this.__decorate || function (decorators, target, key, desc) {
var c = arguments.length,
r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = this && this.__metadata || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var core_1 = $__require("3");
var THREE = $__require("4");
var EditorControls = function (_super) {
__extends(EditorControls, _super);
function EditorControls(container, camera) {
var _this = _super.call(this) || this;
_this.panSpeed = 0.001;
_this.zoomSpeed = 0.001;
_this.rotationSpeed = 0.005;
_this.pointer = new THREE.Vector2();
_this.pointerOld = new THREE.Vector2();
_this.STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 };
_this.center = new THREE.Vector3();
_this.vector = new THREE.Vector3();
_this.spherical = new THREE.Spherical();
_this.changeEvent = { type: 'change' };
_this.normalMatrix = new THREE.Matrix3();
_this.state = _this.STATE.NONE;
_this.center = new THREE.Vector3();
_this.camera = camera;
_this.container = container;
_this.container.addEventListener('contextmenu', _this.contextmenu.bind(_this), false);
_this.container.addEventListener('mousedown', _this.onMouseDown.bind(_this), false);
_this.container.addEventListener('wheel', _this.onMouseWheel.bind(_this), false);
return _this;
}
EditorControls.prototype.onMouseDown = function (event) {
if (event.button === 0) {
this.state = this.STATE.ROTATE;
} else if (event.button === 1) {
this.state = this.STATE.ZOOM;
} else if (event.button === 2) {
this.state = this.STATE.PAN;
}
this.pointerOld.set(event.clientX, event.clientY);
this.container.addEventListener('mousemove', this.onMouseMove.bind(this), false);
this.container.addEventListener('mouseup', this.onMouseUp.bind(this), false);
this.container.addEventListener('mouseout', this.onMouseUp.bind(this), false);
this.container.addEventListener('dblclick', this.onMouseUp.bind(this), false);
};
EditorControls.prototype.onMouseMove = function (event) {
this.pointer.set(event.clientX, event.clientY);
var movementX = this.pointer.x - this.pointerOld.x;
var movementY = this.pointer.y - this.pointerOld.y;
if (this.state === this.STATE.ROTATE) {
this.rotate(new THREE.Vector3(-movementX * this.rotationSpeed, -movementY * this.rotationSpeed, 0));
} else if (this.state === this.STATE.ZOOM) {
this.zoom(new THREE.Vector3(0, 0, movementY));
} else if (this.state === this.STATE.PAN) {
this.pan(new THREE.Vector3(-movementX, movementY, 0));
}
this.pointerOld.set(event.clientX, event.clientY);
};
EditorControls.prototype.focus = function (target) {
var box = new THREE.Box3().setFromObject(target);
this.camera.lookAt(this.center.copy(box.getCenter()));
this.dispatchEvent(this.changeEvent);
};
EditorControls.prototype.dispatchEvent = function (event) {
_super.prototype.dispatchEvent.call(this, event);
};
EditorControls.prototype.pan = function (delta) {
var distance = this.camera.position.distanceTo(this.center);
delta.multiplyScalar(distance * this.panSpeed);
delta.applyMatrix3(this.normalMatrix.getNormalMatrix(this.camera.matrix));
this.camera.position.add(delta);
this.center.add(delta);
this.dispatchEvent(this.changeEvent);
};
EditorControls.prototype.zoom = function (delta) {
var distance = this.camera.position.distanceTo(this.center);
delta.multiplyScalar(distance * this.zoomSpeed);
if (delta.length() > distance) {
return;
}
delta.applyMatrix3(this.normalMatrix.getNormalMatrix(this.camera.matrix));
this.camera.position.add(delta);
this.dispatchEvent(this.changeEvent);
};
EditorControls.prototype.rotate = function (delta) {
this.vector.copy(this.camera.position).sub(this.center);
this.spherical.setFromVector3(this.vector);
this.spherical.theta += delta.x;
this.spherical.phi += delta.y;
this.spherical.makeSafe();
this.vector.setFromSpherical(this.spherical);
this.camera.position.copy(this.center).add(this.vector);
this.camera.lookAt(this.center);
this.dispatchEvent(this.changeEvent);
};
EditorControls.prototype.onMouseWheel = function (event) {
event.preventDefault();
this.zoom(new THREE.Vector3(0, 0, event.deltaY));
};
EditorControls.prototype.onMouseUp = function () {
this.container.removeEventListener('mousemove', this.onMouseMove.bind(this), false);
this.container.removeEventListener('mouseup', this.onMouseUp.bind(this), false);
this.container.removeEventListener('mouseout', this.onMouseUp.bind(this), false);
this.container.removeEventListener('dblclick', this.onMouseUp.bind(this), false);
this.state = this.STATE.NONE;
};
EditorControls.prototype.contextmenu = function (event) {
event.preventDefault();
};
return EditorControls;
}(THREE.EventDispatcher);
EditorControls = __decorate([core_1.Injectable(), __metadata("design:paramtypes", [Object, Object])], EditorControls);
exports.EditorControls = EditorControls;
return module.exports;
});
$__System.registerDynamic("5", ["4"], true, function ($__require, exports, module) {
/**
* @author Kyle-Larson https://github.com/Kyle-Larson
*
* Loader loads FBX file and generates Group representing FBX scene.
* Requires FBX file to be >= 7.0 and in ASCII format.
*
* Supports:
* Mesh Generation (Positional Data)
* Normal Data (Per Vertex Drawing Instance)
* UV Data (Per Vertex Drawing Instance)
* Skinning
* Animation
* - Separated Animations based on stacks.
* - Skeletal & Non-Skeletal Animations
*
* Needs Support:
* Indexed Buffers
* PreRotation support.
*/
"use strict";
var define,
global = this || self,
GLOBAL = global;
var THREE = $__require("4");
/**
* Generates a loader for loading FBX files from URL and parsing into
* a THREE.Group.
* @param {THREE.LoadingManager} manager - Loading Manager for loader to use.
*/
THREE.FBXLoader = function (manager) {
THREE.Loader.call(this);
this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager;
this.fileLoader = new THREE.FileLoader(this.manager);
this.textureLoader = new THREE.TextureLoader(this.manager);
};
Object.assign(THREE.FBXLoader.prototype, THREE.Loader.prototype);
THREE.FBXLoader.prototype.constructor = THREE.FBXLoader;
Object.assign(THREE.FBXLoader.prototype, {
/**
* Loads an ASCII FBX file from URL and parses into a THREE.Group.
* THREE.Group will have an animations property of AnimationClips
* of the different animations exported with the FBX.
* @param {string} url - URL of the FBX file.
* @param {function(THREE.Group):void} onLoad - Callback for when FBX file is loaded and parsed.
* @param {function(ProgressEvent):void} onProgress - Callback fired periodically when file is being retrieved from server.
* @param {function(Event):void} onError - Callback fired when error occurs (Currently only with retrieving file, not with parsing errors).
*/
load: function (url, onLoad, onProgress, onError) {
var self = this;
var resourceDirectory = url.split(/[\\\/]/);
resourceDirectory.pop();
resourceDirectory = resourceDirectory.join('/');
this.fileLoader.load(url, function (text) {
if (!isFbxFormatASCII(text)) {
console.error('FBXLoader: FBX Binary format not supported.');
self.manager.itemError(url);
return;
}
if (getFbxVersion(text) < 7000) {
console.error('FBXLoader: FBX version not supported for file at ' + url + ', FileVersion: ' + getFbxVersion(text));
self.manager.itemError(url);
return;
}
var scene = self.parse(text, resourceDirectory);
onLoad(scene);
}, onProgress, onError);
},
/**
* Parses an ASCII FBX file and returns a THREE.Group.
* THREE.Group will have an animations property of AnimationClips
* of the different animations within the FBX file.
* @param {string} FBXText - Contents of FBX file to parse.
* @param {string} resourceDirectory - Directory to load external assets (e.g. textures ) from.
* @returns {THREE.Group}
*/
parse: function (FBXText, resourceDirectory) {
var loader = this;
var FBXTree = new TextParser().parse(FBXText);
var connections = parseConnections(FBXTree);
var textures = parseTextures(FBXTree);
var materials = parseMaterials(FBXTree, textures, connections);
var deformerMap = parseDeformers(FBXTree, connections);
var geometryMap = parseGeometries(FBXTree, connections, deformerMap);
var sceneGraph = parseScene(FBXTree, connections, deformerMap, geometryMap, materials);
return sceneGraph;
/**
* @typedef {{value: number}} FBXValue
*/
/**
* @typedef {{value: {x: string, y: string, z: string}}} FBXVector3
*/
/**
* @typedef {{properties: {a: string}}} FBXArrayNode
*/
/**
* @typedef {{properties: {MappingInformationType: string, ReferenceInformationType: string }, subNodes: Object<string, FBXArrayNode>}} FBXMappedArrayNode
*/
/**
* @typedef {{id: number, name: string, properties: {FileName: string}}} FBXTextureNode
*/
/**
* @typedef {{id: number, attrName: string, properties: {ShadingModel: string, Diffuse: FBXVector3, Specular: FBXVector3, Shininess: FBXValue, Emissive: FBXVector3, EmissiveFactor: FBXValue, Opacity: FBXValue}}} FBXMaterialNode
*/
/**
* @typedef {{subNodes: {Indexes: FBXArrayNode, Weights: FBXArrayNode, Transform: FBXArrayNode, TransformLink: FBXArrayNode}, properties: { Mode: string }}} FBXSubDeformerNode
*/
/**
* @typedef {{id: number, attrName: string, attrType: string, subNodes: {Vertices: FBXArrayNode, PolygonVertexIndex: FBXArrayNode, LayerElementNormal: FBXMappedArrayNode[], LayerElementMaterial: FBXMappedArrayNode[], LayerElementUV: FBXMappedArrayNode[]}}} FBXGeometryNode
*/
/**
* @typedef {{id: number, attrName: string, attrType: string, properties: {Lcl_Translation: FBXValue, Lcl_Rotation: FBXValue, Lcl_Scaling: FBXValue}}} FBXModelNode
*/
/**
* Parses map of relationships between objects.
* @param {{Connections: { properties: { connections: [number, number, string][]}}}} FBXTree
* @returns {Map<number, {parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}>}
*/
function parseConnections(FBXTree) {
/**
* @type {Map<number, { parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}>}
*/
var connectionMap = new Map();
if ('Connections' in FBXTree) {
/**
* @type {[number, number, string][]}
*/
var connectionArray = FBXTree.Connections.properties.connections;
connectionArray.forEach(function (connection) {
if (!connectionMap.has(connection[0])) {
connectionMap.set(connection[0], {
parents: [],
children: []
});
}
var parentRelationship = { ID: connection[1], relationship: connection[2] };
connectionMap.get(connection[0]).parents.push(parentRelationship);
if (!connectionMap.has(connection[1])) {
connectionMap.set(connection[1], {
parents: [],
children: []
});
}
var childRelationship = { ID: connection[0], relationship: connection[2] };
connectionMap.get(connection[1]).children.push(childRelationship);
});
}
return connectionMap;
}
/**
* Parses map of textures referenced in FBXTree.
* @param {{Objects: {subNodes: {Texture: Object.<string, FBXTextureNode>}}}} FBXTree
* @returns {Map<number, THREE.Texture>}
*/
function parseTextures(FBXTree) {
/**
* @type {Map<number, THREE.Texture>}
*/
var textureMap = new Map();
if ('Texture' in FBXTree.Objects.subNodes) {
var textureNodes = FBXTree.Objects.subNodes.Texture;
for (var nodeID in textureNodes) {
var texture = parseTexture(textureNodes[nodeID]);
textureMap.set(parseInt(nodeID), texture);
}
}
return textureMap;
/**
* @param {textureNode} textureNode - Node to get texture information from.
* @returns {THREE.Texture}
*/
function parseTexture(textureNode) {
var FBX_ID = textureNode.id;
var name = textureNode.name;
var filePath = textureNode.properties.FileName;
var split = filePath.split(/[\\\/]/);
if (split.length > 0) {
var fileName = split[split.length - 1];
} else {
var fileName = filePath;
}
/**
* @type {THREE.Texture}
*/
var texture = loader.textureLoader.load(resourceDirectory + '/' + fileName);
texture.name = name;
texture.FBX_ID = FBX_ID;
return texture;
}
}
/**
* Parses map of Material information.
* @param {{Objects: {subNodes: {Material: Object.<number, FBXMaterialNode>}}}} FBXTree
* @param {Map<number, THREE.Texture>} textureMap
* @param {Map<number, {parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}>} connections
* @returns {Map<number, THREE.Material>}
*/
function parseMaterials(FBXTree, textureMap, connections) {
var materialMap = new Map();
if ('Material' in FBXTree.Objects.subNodes) {
var materialNodes = FBXTree.Objects.subNodes.Material;
for (var nodeID in materialNodes) {
var material = parseMaterial(materialNodes[nodeID], textureMap, connections);
materialMap.set(parseInt(nodeID), material);
}
}
return materialMap;
/**
* Takes information from Material node and returns a generated THREE.Material
* @param {FBXMaterialNode} materialNode
* @param {Map<number, THREE.Texture>} textureMap
* @param {Map<number, {parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}>} connections
* @returns {THREE.Material}
*/
function parseMaterial(materialNode, textureMap, connections) {
var FBX_ID = materialNode.id;
var name = materialNode.attrName;
var type = materialNode.properties.ShadingModel;
var children = connections.get(FBX_ID).children;
var parameters = parseParameters(materialNode.properties, textureMap, children);
var material;
switch (type) {
case 'phong':
material = new THREE.MeshPhongMaterial();
break;
case 'lambert':
material = new THREE.MeshLambertMaterial();
break;
default:
console.warn('No implementation given for material type ' + type + ' in FBXLoader.js. Defaulting to basic material');
material = new THREE.MeshBasicMaterial({ color: 0x3300ff });
break;
}
material.setValues(parameters);
material.name = name;
return material;
/**
* @typedef {{Diffuse: FBXVector3, Specular: FBXVector3, Shininess: FBXValue, Emissive: FBXVector3, EmissiveFactor: FBXValue, Opacity: FBXValue}} FBXMaterialProperties
*/
/**
* @typedef {{color: THREE.Color=, specular: THREE.Color=, shininess: number=, emissive: THREE.Color=, emissiveIntensity: number=, opacity: number=, transparent: boolean=, map: THREE.Texture=}} THREEMaterialParameterPack
*/
/**
* @param {FBXMaterialProperties} properties
* @param {Map<number, THREE.Texture>} textureMap
* @param {{ID: number, relationship: string}[]} childrenRelationships
* @returns {THREEMaterialParameterPack}
*/
function parseParameters(properties, textureMap, childrenRelationships) {
var parameters = {};
if (properties.Diffuse) {
parameters.color = parseColor(properties.Diffuse);
}
if (properties.Specular) {
parameters.specular = parseColor(properties.Specular);
}
if (properties.Shininess) {
parameters.shininess = properties.Shininess.value;
}
if (properties.Emissive) {
parameters.emissive = parseColor(properties.Emissive);
}
if (properties.EmissiveFactor) {
parameters.emissiveIntensity = properties.EmissiveFactor.value;
}
if (properties.Opacity) {
parameters.opacity = properties.Opacity.value;
}
if (parameters.opacity < 1.0) {
parameters.transparent = true;
}
childrenRelationships.forEach(function (relationship) {
var type = relationship.relationship;
switch (type) {
case " \"AmbientColor":
//TODO: Support AmbientColor textures
break;
case " \"DiffuseColor":
parameters.map = textureMap.get(relationship.ID);
break;
default:
console.warn('Unknown texture application of type ' + type + ', skipping texture');
break;
}
});
return parameters;
}
}
}
/**
* Generates map of Skeleton-like objects for use later when generating and binding skeletons.
* @param {{Objects: {subNodes: {Deformer: Object.<number, FBXSubDeformerNode>}}}} FBXTree
* @param {Map<number, {parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}>} connections
* @returns {Map<number, {map: Map<number, {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}>, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[], skeleton: THREE.Skeleton|null}>}
*/
function parseDeformers(FBXTree, connections) {
var skeletonMap = new Map();
if ('Deformer' in FBXTree.Objects.subNodes) {
var DeformerNodes = FBXTree.Objects.subNodes.Deformer;
for (var nodeID in DeformerNodes) {
var deformerNode = DeformerNodes[nodeID];
if (deformerNode.attrType === 'Skin') {
var conns = connections.get(parseInt(nodeID));
var skeleton = parseSkeleton(conns, DeformerNodes);
skeleton.FBX_ID = parseInt(nodeID);
skeletonMap.set(parseInt(nodeID), skeleton);
}
}
}
return skeletonMap;
/**
* Generates a "Skeleton Representation" of FBX nodes based on an FBX Skin Deformer's connections and an object containing SubDeformer nodes.
* @param {{parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}} connections
* @param {Object.<number, FBXSubDeformerNode>} DeformerNodes
* @returns {{map: Map<number, {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}>, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[], skeleton: THREE.Skeleton|null}}
*/
function parseSkeleton(connections, DeformerNodes) {
var subDeformers = new Map();
var subDeformerArray = [];
connections.children.forEach(function (child) {
var subDeformerNode = DeformerNodes[child.ID];
var subDeformer = {
FBX_ID: child.ID,
indices: parseIntArray(subDeformerNode.subNodes.Indexes.properties.a),
weights: parseFloatArray(subDeformerNode.subNodes.Weights.properties.a),
transform: parseMatrixArray(subDeformerNode.subNodes.Transform.properties.a),
transformLink: parseMatrixArray(subDeformerNode.subNodes.TransformLink.properties.a),
linkMode: subDeformerNode.properties.Mode
};
subDeformers.set(child.ID, subDeformer);
subDeformerArray.push(subDeformer);
});
return {
map: subDeformers,
array: subDeformerArray,
bones: []
};
}
}
/**
* Generates Buffer geometries from geometry information in FBXTree, and generates map of THREE.BufferGeometries
* @param {{Objects: {subNodes: {Geometry: Object.<number, FBXGeometryNode}}}} FBXTree
* @param {Map<number, {parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}>} connections
* @param {Map<number, {map: Map<number, {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}>, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[], skeleton: THREE.Skeleton|null}>} deformerMap
* @returns {Map<number, THREE.BufferGeometry>}
*/
function parseGeometries(FBXTree, connections, deformerMap) {
var geometryMap = new Map();
if ('Geometry' in FBXTree.Objects.subNodes) {
var geometryNodes = FBXTree.Objects.subNodes.Geometry;
for (var nodeID in geometryNodes) {
var relationships = connections.get(parseInt(nodeID));
var geo = parseGeometry(geometryNodes[nodeID], relationships, deformerMap);
geometryMap.set(parseInt(nodeID), geo);
}
}
return geometryMap;
/**
* Generates BufferGeometry from FBXGeometryNode.
* @param {FBXGeometryNode} geometryNode
* @param {{parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}} relationships
* @param {Map<number, {map: Map<number, {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}>, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[]}>} deformerMap
* @returns {THREE.BufferGeometry}
*/
function parseGeometry(geometryNode, relationships, deformerMap) {
if (geometryNode.attrType === 'Mesh') {
return parseMeshGeometry(geometryNode, relationships, deformerMap);
} else if (geometryNode.attrType === 'NurbsCurve') {
return parseNurbsGeometry(geometryNode, relationships);
}
/**
* Specialty function for parsing Mesh based Geometry Nodes.
* @param {FBXGeometryNode} geometryNode
* @param {{parents: {ID: number, relationship: string}[], children: {ID: number, relationship: string}[]}} relationships - Object representing relationships between specific geometry node and other nodes.
* @param {Map<number, {map: Map<number, {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}>, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[]}>} deformerMap - Map object of deformers and subDeformers by ID.
* @returns {THREE.BufferGeometry}
*/
function parseMeshGeometry(geometryNode, relationships, deformerMap) {
var FBX_ID = geometryNode.id;
var name = geometryNode.attrName;
for (var i = 0; i < relationships.children.length; ++i) {
if (deformerMap.has(relationships.children[i].ID)) {
var deformer = deformerMap.get(relationships.children[i].ID);
break;
}
}
var geometry = genGeometry(geometryNode, deformer);
return geometry;
/**
* @param {{map: Map<number, {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}>, array: {FBX_ID: number, indices: number[], weights: number[], transform: number[], transformLink: number[], linkMode: string}[]}} deformer - Skeleton representation for geometry instance.
* @returns {THREE.BufferGeometry}
*/
function genGeometry(geometryNode, deformer) {
var geometry = new Geometry();
//First, each index is going to be its own vertex.
var vertexBuffer = parseFloatArray(geometryNode.subNodes.Vertices.properties.a);
var indexBuffer = parseIntArray(geometryNode.subNodes.PolygonVertexIndex.properties.a);
if ('LayerElementNormal' in geometryNode.subNodes) {
var normalInfo = getNormals(geometryNode);
}
if ('LayerElementUV' in geometryNode.subNodes) {
var uvInfo = getUVs(geometryNode);
}
if ('LayerElementMaterial' in geometryNode.subNodes) {
var materialInfo = getMaterials(geometryNode);
}
var faceVertexBuffer = [];
var polygonIndex = 0;
for (var polygonVertexIndex = 0; polygonVertexIndex < indexBuffer.length; ++polygonVertexIndex) {
var endOfFace;
var vertexIndex = indexBuffer[polygonVertexIndex];
if (indexBuffer[polygonVertexIndex] < 0) {
vertexIndex = vertexIndex ^ -1;
indexBuffer[polygonVertexIndex] = vertexIndex;
endOfFace = true;
}
var vertex = new Vertex();
var weightIndices = [];
var weights = [];
vertex.position.fromArray(vertexBuffer, vertexIndex * 3);
// If we have a deformer for this geometry, get the skinIndex and skinWeights for this object.
// They are stored as vertex indices on each deformer, and we need them as deformer indices
// for each vertex.
if (deformer) {
for (var j = 0; j < deformer.array.length; ++j) {
var index = deformer.array[j].indices.findIndex(function (index) {
return index === indexBuffer[polygonVertexIndex];
});
if (index !== -1) {
weights.push(deformer.array[j].weights[index]);
weightIndices.push(j);
}
}
if (weights.length > 4) {
console.warn('FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.');
var WIndex = [0, 0, 0, 0];
var Weight = [0, 0, 0, 0];
for (var polygonVertexIndex = 0; polygonVertexIndex < weights.length; ++polygonVertexIndex) {
var currentWeight = weights[polygonVertexIndex];
var currentIndex = weightIndices[polygonVertexIndex];
for (var j = 0; j < Weight.length; ++j) {
if (currentWeight > Weight[j]) {
var tmp = Weight[j];
Weight[j] = currentWeight;
currentWeight = tmp;
tmp = WIndex[j];
WIndex[j] = currentIndex;
currentIndex = tmp;
}
}
}
weightIndices = WIndex;
weights = Weight;
}
for (var i = weights.length; i < 4; i++) {
weights[i] = 0;
weightIndices[i] = 0;
}
vertex.skinWeights.fromArray(weights);
vertex.skinIndices.fromArray(weightIndices);
}
if (normalInfo) {
vertex.normal.fromArray(getData(polygonVertexIndex, polygonIndex, vertexIndex, normalInfo));
}
if (uvInfo) {
vertex.uv.fromArray(getData(polygonVertexIndex, polygonIndex, vertexIndex, uvInfo));
}
//Add vertex to face buffer.
faceVertexBuffer.push(vertex);
// If index was negative to start with, we have finished this individual face
// and can generate the face data to the geometry.
if (endOfFace) {
var face = new Face();
var materials = getData(polygonVertexIndex, polygonIndex, vertexIndex, materialInfo);
face.genTrianglesFromVertices(faceVertexBuffer);
face.materialIndex = materials[0];
geometry.faces.push(face);
faceVertexBuffer = [];
polygonIndex++;
endOfFace = false;
}
}
/**
* @type {{vertexBuffer: number[], normalBuffer: number[], uvBuffer: number[], skinIndexBuffer: number[], skinWeightBuffer: number[], materialIndexBuffer: number[]}}
*/
var bufferInfo = geometry.flattenToBuffers();
var geo = new THREE.BufferGeometry();
geo.name = geometryNode.name;
geo.addAttribute('position', new THREE.BufferAttribute(new Float32Array(bufferInfo.vertexBuffer), 3));
if (bufferInfo.normalBuffer.length > 0) {
geo.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(bufferInfo.normalBuffer), 3));
}
if (bufferInfo.uvBuffer.length > 0) {
geo.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(bufferInfo.uvBuffer), 2));
}
if (deformer) {
geo.addAttribute('skinIndex', new THREE.BufferAttribute(new Float32Array(bufferInfo.skinIndexBuffer), 4));
geo.addAttribute('skinWeight', new THREE.BufferAttribute(new Float32Array(bufferInfo.skinWeightBuffer), 4));
geo.FBX_Deformer = deformer;
}
// Convert the material indices of each vertex into rendering groups on the geometry.
var prevMaterialIndex = bufferInfo.materialIndexBuffer[0];
var startIndex = 0;
for (var materialBufferIndex = 0; materialBufferIndex < bufferInfo.materialIndexBuffer.length; ++materialBufferIndex) {
if (bufferInfo.materialIndexBuffer[materialBufferIndex] !== prevMaterialIndex) {
geo.addGroup(startIndex, materialBufferIndex - startIndex, prevMaterialIndex);
startIndex = materialBufferIndex;
prevMaterialIndex = bufferInfo.materialIndexBuffer[materialBufferIndex];
}
}
return geo;
/**
* Parses normal information for geometry.
* @param {FBXGeometryNode} geometryNode
* @returns {{dataSize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}}
*/
function getNormals(geometryNode) {
var NormalNode = geometryNode.subNodes.LayerElementNormal[0];
var mappingType = NormalNode.properties.MappingInformationType;
var referenceType = NormalNode.properties.ReferenceInformationType;
var buffer = parseFloatArray(NormalNode.subNodes.Normals.properties.a);
var indexBuffer = [];
if (referenceType === 'IndexToDirect') {
indexBuffer = parseIntArray(NormalNode.subNodes.NormalIndex.properties.a);
}
return {
dataSize: 3,
buffer: buffer,
indices: indexBuffer,
mappingType: mappingType,
referenceType: referenceType
};
}
/**
* Parses UV information for geometry.
* @param {FBXGeometryNode} geometryNode
* @returns {{dataSize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}}
*/
function getUVs(geometryNode) {
var UVNode = geometryNode.subNodes.LayerElementUV[0];
var mappingType = UVNode.properties.MappingInformationType;
var referenceType = UVNode.properties.ReferenceInformationType;
var buffer = parseFloatArray(UVNode.subNodes.UV.properties.a);
var indexBuffer = [];
if (referenceType === 'IndexToDirect') {
indexBuffer = parseIntArray(UVNode.subNodes.UVIndex.properties.a);
}
return {
dataSize: 2,
buffer: buffer,
indices: indexBuffer,
mappingType: mappingType,
referenceType: referenceType
};
}
/**
* Parses material application information for geometry.
* @param {FBXGeometryNode}
* @returns {{dataSize: number, buffer: number[], indices: number[], mappingType: string, referenceType: string}}
*/
function getMaterials(geometryNode) {
var MaterialNode = geometryNode.subNodes.LayerElementMaterial[0];
var mappingType = MaterialNode.properties.MappingInformationType;
var referenceType = MaterialNode.properties.ReferenceInformationType;
var materialIndexBuffer = parseIntArray(MaterialNode.subNodes.Materials.properties.a);
// Since materials are stored as indices, there's a bit of a mismatch between FBX and what
// we expect. So we create an intermediate buffer that points to the index in the buffer,
// for conforming with the other functions we've written for other data.
var materialIndices = [];
materialIndexBuffer.forEach(function (materialIndex, index) {
m