UNPKG

ng2-3d-editor

Version:
717 lines (714 loc) 958 kB
!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