From 62f5f4661cc047c6334ff152f4a92f40ae87dc46 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Tue, 31 Oct 2023 15:56:49 +0100 Subject: [PATCH] update examples/bundle.js used in deployed castle example --- examples/bundle.js | 9874 ++++++++++++++++++++++---------------------- 1 file changed, 5037 insertions(+), 4837 deletions(-) diff --git a/examples/bundle.js b/examples/bundle.js index 17854e2b..bcb458ed 100644 --- a/examples/bundle.js +++ b/examples/bundle.js @@ -7,7 +7,7 @@ require('./src/misc'); require('./src/pathfinding'); require('./src/primitives'); -},{"./src/controls":12,"./src/loaders":21,"./src/misc":26,"./src/pathfinding":29,"./src/primitives":36}],2:[function(require,module,exports){ +},{"./src/controls":13,"./src/loaders":23,"./src/misc":28,"./src/pathfinding":31,"./src/primitives":38}],2:[function(require,module,exports){ 'use strict'; /** @@ -17,3569 +17,3569 @@ require('./src/primitives'); module.exports = THREE.ColladaLoader = function (manager) { - this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager; + this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager; }; THREE.ColladaLoader.prototype = { - constructor: THREE.ColladaLoader, + constructor: THREE.ColladaLoader, - crossOrigin: 'anonymous', + crossOrigin: 'anonymous', - load: function load(url, onLoad, onProgress, onError) { + load: function load(url, onLoad, onProgress, onError) { - var scope = this; + var scope = this; - var path = scope.path === undefined ? THREE.LoaderUtils.extractUrlBase(url) : scope.path; + var path = scope.path === undefined ? THREE.LoaderUtils.extractUrlBase(url) : scope.path; - var loader = new THREE.FileLoader(scope.manager); - loader.setPath(scope.path); - loader.load(url, function (text) { + var loader = new THREE.FileLoader(scope.manager); + loader.setPath(scope.path); + loader.load(url, function (text) { - onLoad(scope.parse(text, path)); - }, onProgress, onError); - }, - - setPath: function setPath(value) { - - this.path = value; - return this; - }, + onLoad(scope.parse(text, path)); + }, onProgress, onError); + }, - setResourcePath: function setResourcePath(value) { + setPath: function setPath(value) { - this.resourcePath = value; - return this; - }, + this.path = value; + return this; + }, - options: { + setResourcePath: function setResourcePath(value) { - set convertUpAxis(value) { + this.resourcePath = value; + return this; + }, - console.warn('THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.'); - } + options: { - }, + set convertUpAxis(value) { - setCrossOrigin: function setCrossOrigin(value) { + console.warn('THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.'); + } - this.crossOrigin = value; - return this; - }, + }, - parse: function parse(text, path) { + setCrossOrigin: function setCrossOrigin(value) { - function getElementsByTagName(xml, name) { + this.crossOrigin = value; + return this; + }, - // Non recursive xml.getElementsByTagName() ... + parse: function parse(text, path) { - var array = []; - var childNodes = xml.childNodes; + function getElementsByTagName(xml, name) { - for (var i = 0, l = childNodes.length; i < l; i++) { + // Non recursive xml.getElementsByTagName() ... - var child = childNodes[i]; + var array = []; + var childNodes = xml.childNodes; - if (child.nodeName === name) { + for (var i = 0, l = childNodes.length; i < l; i++) { - array.push(child); - } - } + var child = childNodes[i]; - return array; - } + if (child.nodeName === name) { - function parseStrings(text) { + array.push(child); + } + } - if (text.length === 0) return []; + return array; + } - var parts = text.trim().split(/\s+/); - var array = new Array(parts.length); + function parseStrings(text) { - for (var i = 0, l = parts.length; i < l; i++) { + if (text.length === 0) return []; - array[i] = parts[i]; - } + var parts = text.trim().split(/\s+/); + var array = new Array(parts.length); - return array; - } + for (var i = 0, l = parts.length; i < l; i++) { - function parseFloats(text) { + array[i] = parts[i]; + } - if (text.length === 0) return []; + return array; + } - var parts = text.trim().split(/\s+/); - var array = new Array(parts.length); + function parseFloats(text) { - for (var i = 0, l = parts.length; i < l; i++) { + if (text.length === 0) return []; - array[i] = parseFloat(parts[i]); - } + var parts = text.trim().split(/\s+/); + var array = new Array(parts.length); - return array; - } + for (var i = 0, l = parts.length; i < l; i++) { - function parseInts(text) { + array[i] = parseFloat(parts[i]); + } - if (text.length === 0) return []; + return array; + } - var parts = text.trim().split(/\s+/); - var array = new Array(parts.length); + function parseInts(text) { - for (var i = 0, l = parts.length; i < l; i++) { + if (text.length === 0) return []; - array[i] = parseInt(parts[i]); - } + var parts = text.trim().split(/\s+/); + var array = new Array(parts.length); - return array; - } + for (var i = 0, l = parts.length; i < l; i++) { - function parseId(text) { + array[i] = parseInt(parts[i]); + } - return text.substring(1); - } + return array; + } - function generateId() { + function parseId(text) { - return 'three_default_' + count++; - } + return text.substring(1); + } - function isEmpty(object) { + function generateId() { - return Object.keys(object).length === 0; - } + return 'three_default_' + count++; + } - // asset + function isEmpty(object) { - function parseAsset(xml) { + return Object.keys(object).length === 0; + } - return { - unit: parseAssetUnit(getElementsByTagName(xml, 'unit')[0]), - upAxis: parseAssetUpAxis(getElementsByTagName(xml, 'up_axis')[0]) - }; - } + // asset - function parseAssetUnit(xml) { + function parseAsset(xml) { - if (xml !== undefined && xml.hasAttribute('meter') === true) { + return { + unit: parseAssetUnit(getElementsByTagName(xml, 'unit')[0]), + upAxis: parseAssetUpAxis(getElementsByTagName(xml, 'up_axis')[0]) + }; + } - return parseFloat(xml.getAttribute('meter')); - } else { + function parseAssetUnit(xml) { - return 1; // default 1 meter - } - } + if (xml !== undefined && xml.hasAttribute('meter') === true) { - function parseAssetUpAxis(xml) { + return parseFloat(xml.getAttribute('meter')); + } else { - return xml !== undefined ? xml.textContent : 'Y_UP'; - } + return 1; // default 1 meter + } + } - // library + function parseAssetUpAxis(xml) { - function parseLibrary(xml, libraryName, nodeName, parser) { + return xml !== undefined ? xml.textContent : 'Y_UP'; + } - var library = getElementsByTagName(xml, libraryName)[0]; + // library - if (library !== undefined) { + function parseLibrary(xml, libraryName, nodeName, parser) { - var elements = getElementsByTagName(library, nodeName); + var library = getElementsByTagName(xml, libraryName)[0]; - for (var i = 0; i < elements.length; i++) { + if (library !== undefined) { - parser(elements[i]); - } - } - } + var elements = getElementsByTagName(library, nodeName); - function buildLibrary(data, builder) { + for (var i = 0; i < elements.length; i++) { - for (var name in data) { + parser(elements[i]); + } + } + } - var object = data[name]; - object.build = builder(data[name]); - } - } + function buildLibrary(data, builder) { - // get + for (var name in data) { - function getBuild(data, builder) { + var object = data[name]; + object.build = builder(data[name]); + } + } - if (data.build !== undefined) return data.build; + // get - data.build = builder(data); + function getBuild(data, builder) { - return data.build; - } + if (data.build !== undefined) return data.build; - // animation + data.build = builder(data); - function parseAnimation(xml) { + return data.build; + } - var data = { - sources: {}, - samplers: {}, - channels: {} - }; + // animation - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseAnimation(xml) { - var child = xml.childNodes[i]; + var data = { + sources: {}, + samplers: {}, + channels: {} + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - var id; + var child = xml.childNodes[i]; - switch (child.nodeName) { + if (child.nodeType !== 1) continue; - case 'source': - id = child.getAttribute('id'); - data.sources[id] = parseSource(child); - break; + var id; - case 'sampler': - id = child.getAttribute('id'); - data.samplers[id] = parseAnimationSampler(child); - break; + switch (child.nodeName) { - case 'channel': - id = child.getAttribute('target'); - data.channels[id] = parseAnimationChannel(child); - break; + case 'source': + id = child.getAttribute('id'); + data.sources[id] = parseSource(child); + break; - default: - console.log(child); + case 'sampler': + id = child.getAttribute('id'); + data.samplers[id] = parseAnimationSampler(child); + break; - } - } + case 'channel': + id = child.getAttribute('target'); + data.channels[id] = parseAnimationChannel(child); + break; - library.animations[xml.getAttribute('id')] = data; - } + default: + console.log(child); - function parseAnimationSampler(xml) { + } + } - var data = { - inputs: {} - }; + library.animations[xml.getAttribute('id')] = data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseAnimationSampler(xml) { - var child = xml.childNodes[i]; + var data = { + inputs: {} + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'input': - var id = parseId(child.getAttribute('source')); - var semantic = child.getAttribute('semantic'); - data.inputs[semantic] = id; - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'input': + var id = parseId(child.getAttribute('source')); + var semantic = child.getAttribute('semantic'); + data.inputs[semantic] = id; + break; - function parseAnimationChannel(xml) { + } + } - var data = {}; + return data; + } - var target = xml.getAttribute('target'); + function parseAnimationChannel(xml) { - // parsing SID Addressing Syntax + var data = {}; - var parts = target.split('/'); + var target = xml.getAttribute('target'); - var id = parts.shift(); - var sid = parts.shift(); + // parsing SID Addressing Syntax - // check selection syntax + var parts = target.split('/'); - var arraySyntax = sid.indexOf('(') !== -1; - var memberSyntax = sid.indexOf('.') !== -1; + var id = parts.shift(); + var sid = parts.shift(); - if (memberSyntax) { + // check selection syntax - // member selection access + var arraySyntax = sid.indexOf('(') !== -1; + var memberSyntax = sid.indexOf('.') !== -1; - parts = sid.split('.'); - sid = parts.shift(); - data.member = parts.shift(); - } else if (arraySyntax) { + if (memberSyntax) { - // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. + // member selection access - var indices = sid.split('('); - sid = indices.shift(); + parts = sid.split('.'); + sid = parts.shift(); + data.member = parts.shift(); + } else if (arraySyntax) { - for (var i = 0; i < indices.length; i++) { + // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. - indices[i] = parseInt(indices[i].replace(/\)/, '')); - } + var indices = sid.split('('); + sid = indices.shift(); - data.indices = indices; - } + for (var i = 0; i < indices.length; i++) { - data.id = id; - data.sid = sid; + indices[i] = parseInt(indices[i].replace(/\)/, '')); + } - data.arraySyntax = arraySyntax; - data.memberSyntax = memberSyntax; + data.indices = indices; + } - data.sampler = parseId(xml.getAttribute('source')); + data.id = id; + data.sid = sid; - return data; - } + data.arraySyntax = arraySyntax; + data.memberSyntax = memberSyntax; - function buildAnimation(data) { + data.sampler = parseId(xml.getAttribute('source')); - var tracks = []; + return data; + } - var channels = data.channels; - var samplers = data.samplers; - var sources = data.sources; + function buildAnimation(data) { - for (var target in channels) { + var tracks = []; - if (channels.hasOwnProperty(target)) { + var channels = data.channels; + var samplers = data.samplers; + var sources = data.sources; - var channel = channels[target]; - var sampler = samplers[channel.sampler]; + for (var target in channels) { - var inputId = sampler.inputs.INPUT; - var outputId = sampler.inputs.OUTPUT; + if (channels.hasOwnProperty(target)) { - var inputSource = sources[inputId]; - var outputSource = sources[outputId]; + var channel = channels[target]; + var sampler = samplers[channel.sampler]; - var animation = buildAnimationChannel(channel, inputSource, outputSource); + var inputId = sampler.inputs.INPUT; + var outputId = sampler.inputs.OUTPUT; - createKeyframeTracks(animation, tracks); - } - } + var inputSource = sources[inputId]; + var outputSource = sources[outputId]; - return tracks; - } + var animation = buildAnimationChannel(channel, inputSource, outputSource); - function getAnimation(id) { + createKeyframeTracks(animation, tracks); + } + } - return getBuild(library.animations[id], buildAnimation); - } + return tracks; + } - function buildAnimationChannel(channel, inputSource, outputSource) { + function getAnimation(id) { - var node = library.nodes[channel.id]; - var object3D = getNode(node.id); + return getBuild(library.animations[id], buildAnimation); + } - var transform = node.transforms[channel.sid]; - var defaultMatrix = node.matrix.clone().transpose(); + function buildAnimationChannel(channel, inputSource, outputSource) { - var time, stride; - var i, il, j, jl; + var node = library.nodes[channel.id]; + var object3D = getNode(node.id); - var data = {}; + var transform = node.transforms[channel.sid]; + var defaultMatrix = node.matrix.clone().transpose(); - // the collada spec allows the animation of data in various ways. - // depending on the transform type (matrix, translate, rotate, scale), we execute different logic + var time, stride; + var i, il, j, jl; - switch (transform) { + var data = {}; - case 'matrix': + // the collada spec allows the animation of data in various ways. + // depending on the transform type (matrix, translate, rotate, scale), we execute different logic - for (i = 0, il = inputSource.array.length; i < il; i++) { + switch (transform) { - time = inputSource.array[i]; - stride = i * outputSource.stride; + case 'matrix': - if (data[time] === undefined) data[time] = {}; + for (i = 0, il = inputSource.array.length; i < il; i++) { - if (channel.arraySyntax === true) { + time = inputSource.array[i]; + stride = i * outputSource.stride; - var value = outputSource.array[stride]; - var index = channel.indices[0] + 4 * channel.indices[1]; + if (data[time] === undefined) data[time] = {}; - data[time][index] = value; - } else { + if (channel.arraySyntax === true) { - for (j = 0, jl = outputSource.stride; j < jl; j++) { + var value = outputSource.array[stride]; + var index = channel.indices[0] + 4 * channel.indices[1]; - data[time][j] = outputSource.array[stride + j]; - } - } - } + data[time][index] = value; + } else { - break; + for (j = 0, jl = outputSource.stride; j < jl; j++) { - case 'translate': - console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); - break; + data[time][j] = outputSource.array[stride + j]; + } + } + } - case 'rotate': - console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); - break; + break; - case 'scale': - console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); - break; + case 'translate': + console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); + break; - } + case 'rotate': + console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); + break; - var keyframes = prepareAnimationData(data, defaultMatrix); + case 'scale': + console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); + break; - var animation = { - name: object3D.uuid, - keyframes: keyframes - }; + } - return animation; - } + var keyframes = prepareAnimationData(data, defaultMatrix); - function prepareAnimationData(data, defaultMatrix) { + var animation = { + name: object3D.uuid, + keyframes: keyframes + }; - var keyframes = []; + return animation; + } - // transfer data into a sortable array + function prepareAnimationData(data, defaultMatrix) { - for (var time in data) { + var keyframes = []; - keyframes.push({ time: parseFloat(time), value: data[time] }); - } + // transfer data into a sortable array - // ensure keyframes are sorted by time + for (var time in data) { - keyframes.sort(ascending); + keyframes.push({ time: parseFloat(time), value: data[time] }); + } - // now we clean up all animation data, so we can use them for keyframe tracks + // ensure keyframes are sorted by time - for (var i = 0; i < 16; i++) { + keyframes.sort(ascending); - transformAnimationData(keyframes, i, defaultMatrix.elements[i]); - } + // now we clean up all animation data, so we can use them for keyframe tracks - return keyframes; + for (var i = 0; i < 16; i++) { - // array sort function + transformAnimationData(keyframes, i, defaultMatrix.elements[i]); + } - function ascending(a, b) { + return keyframes; - return a.time - b.time; - } - } + // array sort function - var position = new THREE.Vector3(); - var scale = new THREE.Vector3(); - var quaternion = new THREE.Quaternion(); + function ascending(a, b) { - function createKeyframeTracks(animation, tracks) { + return a.time - b.time; + } + } - var keyframes = animation.keyframes; - var name = animation.name; + var position = new THREE.Vector3(); + var scale = new THREE.Vector3(); + var quaternion = new THREE.Quaternion(); - var times = []; - var positionData = []; - var quaternionData = []; - var scaleData = []; + function createKeyframeTracks(animation, tracks) { - for (var i = 0, l = keyframes.length; i < l; i++) { + var keyframes = animation.keyframes; + var name = animation.name; - var keyframe = keyframes[i]; + var times = []; + var positionData = []; + var quaternionData = []; + var scaleData = []; - var time = keyframe.time; - var value = keyframe.value; + for (var i = 0, l = keyframes.length; i < l; i++) { - matrix.fromArray(value).transpose(); - matrix.decompose(position, quaternion, scale); + var keyframe = keyframes[i]; - times.push(time); - positionData.push(position.x, position.y, position.z); - quaternionData.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w); - scaleData.push(scale.x, scale.y, scale.z); - } + var time = keyframe.time; + var value = keyframe.value; - if (positionData.length > 0) tracks.push(new THREE.VectorKeyframeTrack(name + '.position', times, positionData)); - if (quaternionData.length > 0) tracks.push(new THREE.QuaternionKeyframeTrack(name + '.quaternion', times, quaternionData)); - if (scaleData.length > 0) tracks.push(new THREE.VectorKeyframeTrack(name + '.scale', times, scaleData)); + matrix.fromArray(value).transpose(); + matrix.decompose(position, quaternion, scale); - return tracks; - } + times.push(time); + positionData.push(position.x, position.y, position.z); + quaternionData.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w); + scaleData.push(scale.x, scale.y, scale.z); + } - function transformAnimationData(keyframes, property, defaultValue) { + if (positionData.length > 0) tracks.push(new THREE.VectorKeyframeTrack(name + '.position', times, positionData)); + if (quaternionData.length > 0) tracks.push(new THREE.QuaternionKeyframeTrack(name + '.quaternion', times, quaternionData)); + if (scaleData.length > 0) tracks.push(new THREE.VectorKeyframeTrack(name + '.scale', times, scaleData)); - var keyframe; + return tracks; + } - var empty = true; - var i, l; + function transformAnimationData(keyframes, property, defaultValue) { - // check, if values of a property are missing in our keyframes + var keyframe; - for (i = 0, l = keyframes.length; i < l; i++) { + var empty = true; + var i, l; - keyframe = keyframes[i]; + // check, if values of a property are missing in our keyframes - if (keyframe.value[property] === undefined) { + for (i = 0, l = keyframes.length; i < l; i++) { - keyframe.value[property] = null; // mark as missing - } else { + keyframe = keyframes[i]; - empty = false; - } - } + if (keyframe.value[property] === undefined) { - if (empty === true) { + keyframe.value[property] = null; // mark as missing + } else { - // no values at all, so we set a default value + empty = false; + } + } - for (i = 0, l = keyframes.length; i < l; i++) { + if (empty === true) { - keyframe = keyframes[i]; + // no values at all, so we set a default value - keyframe.value[property] = defaultValue; - } - } else { + for (i = 0, l = keyframes.length; i < l; i++) { - // filling gaps + keyframe = keyframes[i]; - createMissingKeyframes(keyframes, property); - } - } + keyframe.value[property] = defaultValue; + } + } else { - function createMissingKeyframes(keyframes, property) { + // filling gaps - var prev, next; + createMissingKeyframes(keyframes, property); + } + } - for (var i = 0, l = keyframes.length; i < l; i++) { + function createMissingKeyframes(keyframes, property) { - var keyframe = keyframes[i]; + var prev, next; - if (keyframe.value[property] === null) { + for (var i = 0, l = keyframes.length; i < l; i++) { - prev = getPrev(keyframes, i, property); - next = getNext(keyframes, i, property); + var keyframe = keyframes[i]; - if (prev === null) { + if (keyframe.value[property] === null) { - keyframe.value[property] = next.value[property]; - continue; - } + prev = getPrev(keyframes, i, property); + next = getNext(keyframes, i, property); - if (next === null) { + if (prev === null) { - keyframe.value[property] = prev.value[property]; - continue; - } + keyframe.value[property] = next.value[property]; + continue; + } - interpolate(keyframe, prev, next, property); - } - } - } + if (next === null) { - function getPrev(keyframes, i, property) { + keyframe.value[property] = prev.value[property]; + continue; + } - while (i >= 0) { + interpolate(keyframe, prev, next, property); + } + } + } - var keyframe = keyframes[i]; + function getPrev(keyframes, i, property) { - if (keyframe.value[property] !== null) return keyframe; + while (i >= 0) { - i--; - } + var keyframe = keyframes[i]; - return null; - } + if (keyframe.value[property] !== null) return keyframe; - function getNext(keyframes, i, property) { + i--; + } - while (i < keyframes.length) { + return null; + } - var keyframe = keyframes[i]; + function getNext(keyframes, i, property) { - if (keyframe.value[property] !== null) return keyframe; + while (i < keyframes.length) { - i++; - } + var keyframe = keyframes[i]; - return null; - } + if (keyframe.value[property] !== null) return keyframe; - function interpolate(key, prev, next, property) { + i++; + } - if (next.time - prev.time === 0) { + return null; + } - key.value[property] = prev.value[property]; - return; - } + function interpolate(key, prev, next, property) { - key.value[property] = (key.time - prev.time) * (next.value[property] - prev.value[property]) / (next.time - prev.time) + prev.value[property]; - } + if (next.time - prev.time === 0) { - // animation clips + key.value[property] = prev.value[property]; + return; + } - function parseAnimationClip(xml) { + key.value[property] = (key.time - prev.time) * (next.value[property] - prev.value[property]) / (next.time - prev.time) + prev.value[property]; + } - var data = { - name: xml.getAttribute('id') || 'default', - start: parseFloat(xml.getAttribute('start') || 0), - end: parseFloat(xml.getAttribute('end') || 0), - animations: [] - }; + // animation clips - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseAnimationClip(xml) { - var child = xml.childNodes[i]; + var data = { + name: xml.getAttribute('id') || 'default', + start: parseFloat(xml.getAttribute('start') || 0), + end: parseFloat(xml.getAttribute('end') || 0), + animations: [] + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'instance_animation': - data.animations.push(parseId(child.getAttribute('url'))); - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - library.clips[xml.getAttribute('id')] = data; - } + case 'instance_animation': + data.animations.push(parseId(child.getAttribute('url'))); + break; - function buildAnimationClip(data) { + } + } - var tracks = []; + library.clips[xml.getAttribute('id')] = data; + } - var name = data.name; - var duration = data.end - data.start || -1; - var animations = data.animations; + function buildAnimationClip(data) { - for (var i = 0, il = animations.length; i < il; i++) { + var tracks = []; - var animationTracks = getAnimation(animations[i]); + var name = data.name; + var duration = data.end - data.start || -1; + var animations = data.animations; - for (var j = 0, jl = animationTracks.length; j < jl; j++) { + for (var i = 0, il = animations.length; i < il; i++) { - tracks.push(animationTracks[j]); - } - } + var animationTracks = getAnimation(animations[i]); - return new THREE.AnimationClip(name, duration, tracks); - } + for (var j = 0, jl = animationTracks.length; j < jl; j++) { - function getAnimationClip(id) { + tracks.push(animationTracks[j]); + } + } - return getBuild(library.clips[id], buildAnimationClip); - } + return new THREE.AnimationClip(name, duration, tracks); + } - // controller + function getAnimationClip(id) { - function parseController(xml) { + return getBuild(library.clips[id], buildAnimationClip); + } - var data = {}; + // controller - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseController(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'skin': - // there is exactly one skin per controller - data.id = parseId(child.getAttribute('source')); - data.skin = parseSkin(child); - break; + if (child.nodeType !== 1) continue; - case 'morph': - data.id = parseId(child.getAttribute('source')); - console.warn('THREE.ColladaLoader: Morph target animation not supported yet.'); - break; + switch (child.nodeName) { - } - } + case 'skin': + // there is exactly one skin per controller + data.id = parseId(child.getAttribute('source')); + data.skin = parseSkin(child); + break; - library.controllers[xml.getAttribute('id')] = data; - } + case 'morph': + data.id = parseId(child.getAttribute('source')); + console.warn('THREE.ColladaLoader: Morph target animation not supported yet.'); + break; - function parseSkin(xml) { + } + } - var data = { - sources: {} - }; + library.controllers[xml.getAttribute('id')] = data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseSkin(xml) { - var child = xml.childNodes[i]; + var data = { + sources: {} + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'bind_shape_matrix': - data.bindShapeMatrix = parseFloats(child.textContent); - break; + if (child.nodeType !== 1) continue; - case 'source': - var id = child.getAttribute('id'); - data.sources[id] = parseSource(child); - break; + switch (child.nodeName) { - case 'joints': - data.joints = parseJoints(child); - break; + case 'bind_shape_matrix': + data.bindShapeMatrix = parseFloats(child.textContent); + break; - case 'vertex_weights': - data.vertexWeights = parseVertexWeights(child); - break; + case 'source': + var id = child.getAttribute('id'); + data.sources[id] = parseSource(child); + break; - } - } + case 'joints': + data.joints = parseJoints(child); + break; - return data; - } + case 'vertex_weights': + data.vertexWeights = parseVertexWeights(child); + break; - function parseJoints(xml) { + } + } - var data = { - inputs: {} - }; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseJoints(xml) { - var child = xml.childNodes[i]; + var data = { + inputs: {} + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'input': - var semantic = child.getAttribute('semantic'); - var id = parseId(child.getAttribute('source')); - data.inputs[semantic] = id; - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'input': + var semantic = child.getAttribute('semantic'); + var id = parseId(child.getAttribute('source')); + data.inputs[semantic] = id; + break; - function parseVertexWeights(xml) { + } + } - var data = { - inputs: {} - }; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseVertexWeights(xml) { - var child = xml.childNodes[i]; + var data = { + inputs: {} + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'input': - var semantic = child.getAttribute('semantic'); - var id = parseId(child.getAttribute('source')); - var offset = parseInt(child.getAttribute('offset')); - data.inputs[semantic] = { id: id, offset: offset }; - break; + if (child.nodeType !== 1) continue; - case 'vcount': - data.vcount = parseInts(child.textContent); - break; + switch (child.nodeName) { - case 'v': - data.v = parseInts(child.textContent); - break; + case 'input': + var semantic = child.getAttribute('semantic'); + var id = parseId(child.getAttribute('source')); + var offset = parseInt(child.getAttribute('offset')); + data.inputs[semantic] = { id: id, offset: offset }; + break; - } - } + case 'vcount': + data.vcount = parseInts(child.textContent); + break; - return data; - } + case 'v': + data.v = parseInts(child.textContent); + break; - function buildController(data) { + } + } - var build = { - id: data.id - }; + return data; + } - var geometry = library.geometries[build.id]; + function buildController(data) { - if (data.skin !== undefined) { + var build = { + id: data.id + }; - build.skin = buildSkin(data.skin); + var geometry = library.geometries[build.id]; - // we enhance the 'sources' property of the corresponding geometry with our skin data + if (data.skin !== undefined) { - geometry.sources.skinIndices = build.skin.indices; - geometry.sources.skinWeights = build.skin.weights; - } + build.skin = buildSkin(data.skin); - return build; - } + // we enhance the 'sources' property of the corresponding geometry with our skin data - function buildSkin(data) { + geometry.sources.skinIndices = build.skin.indices; + geometry.sources.skinWeights = build.skin.weights; + } - var BONE_LIMIT = 4; + return build; + } - var build = { - joints: [], // this must be an array to preserve the joint order - indices: { - array: [], - stride: BONE_LIMIT - }, - weights: { - array: [], - stride: BONE_LIMIT - } - }; + function buildSkin(data) { - var sources = data.sources; - var vertexWeights = data.vertexWeights; + var BONE_LIMIT = 4; - var vcount = vertexWeights.vcount; - var v = vertexWeights.v; - var jointOffset = vertexWeights.inputs.JOINT.offset; - var weightOffset = vertexWeights.inputs.WEIGHT.offset; + var build = { + joints: [], // this must be an array to preserve the joint order + indices: { + array: [], + stride: BONE_LIMIT + }, + weights: { + array: [], + stride: BONE_LIMIT + } + }; - var jointSource = data.sources[data.joints.inputs.JOINT]; - var inverseSource = data.sources[data.joints.inputs.INV_BIND_MATRIX]; + var sources = data.sources; + var vertexWeights = data.vertexWeights; - var weights = sources[vertexWeights.inputs.WEIGHT.id].array; - var stride = 0; + var vcount = vertexWeights.vcount; + var v = vertexWeights.v; + var jointOffset = vertexWeights.inputs.JOINT.offset; + var weightOffset = vertexWeights.inputs.WEIGHT.offset; - var i, j, l; + var jointSource = data.sources[data.joints.inputs.JOINT]; + var inverseSource = data.sources[data.joints.inputs.INV_BIND_MATRIX]; - // procces skin data for each vertex + var weights = sources[vertexWeights.inputs.WEIGHT.id].array; + var stride = 0; - for (i = 0, l = vcount.length; i < l; i++) { + var i, j, l; - var jointCount = vcount[i]; // this is the amount of joints that affect a single vertex - var vertexSkinData = []; + // procces skin data for each vertex - for (j = 0; j < jointCount; j++) { + for (i = 0, l = vcount.length; i < l; i++) { - var skinIndex = v[stride + jointOffset]; - var weightId = v[stride + weightOffset]; - var skinWeight = weights[weightId]; + var jointCount = vcount[i]; // this is the amount of joints that affect a single vertex + var vertexSkinData = []; - vertexSkinData.push({ index: skinIndex, weight: skinWeight }); + for (j = 0; j < jointCount; j++) { - stride += 2; - } + var skinIndex = v[stride + jointOffset]; + var weightId = v[stride + weightOffset]; + var skinWeight = weights[weightId]; - // we sort the joints in descending order based on the weights. - // this ensures, we only procced the most important joints of the vertex + vertexSkinData.push({ index: skinIndex, weight: skinWeight }); - vertexSkinData.sort(descending); + stride += 2; + } - // now we provide for each vertex a set of four index and weight values. - // the order of the skin data matches the order of vertices + // we sort the joints in descending order based on the weights. + // this ensures, we only procced the most important joints of the vertex - for (j = 0; j < BONE_LIMIT; j++) { + vertexSkinData.sort(descending); - var d = vertexSkinData[j]; + // now we provide for each vertex a set of four index and weight values. + // the order of the skin data matches the order of vertices - if (d !== undefined) { + for (j = 0; j < BONE_LIMIT; j++) { - build.indices.array.push(d.index); - build.weights.array.push(d.weight); - } else { + var d = vertexSkinData[j]; - build.indices.array.push(0); - build.weights.array.push(0); - } - } - } + if (d !== undefined) { - // setup bind matrix + build.indices.array.push(d.index); + build.weights.array.push(d.weight); + } else { - if (data.bindShapeMatrix) { + build.indices.array.push(0); + build.weights.array.push(0); + } + } + } - build.bindMatrix = new THREE.Matrix4().fromArray(data.bindShapeMatrix).transpose(); - } else { + // setup bind matrix - build.bindMatrix = new THREE.Matrix4().identity(); - } + if (data.bindShapeMatrix) { - // process bones and inverse bind matrix data + build.bindMatrix = new THREE.Matrix4().fromArray(data.bindShapeMatrix).transpose(); + } else { - for (i = 0, l = jointSource.array.length; i < l; i++) { + build.bindMatrix = new THREE.Matrix4().identity(); + } - var name = jointSource.array[i]; - var boneInverse = new THREE.Matrix4().fromArray(inverseSource.array, i * inverseSource.stride).transpose(); + // process bones and inverse bind matrix data - build.joints.push({ name: name, boneInverse: boneInverse }); - } + for (i = 0, l = jointSource.array.length; i < l; i++) { - return build; + var name = jointSource.array[i]; + var boneInverse = new THREE.Matrix4().fromArray(inverseSource.array, i * inverseSource.stride).transpose(); - // array sort function + build.joints.push({ name: name, boneInverse: boneInverse }); + } - function descending(a, b) { + return build; - return b.weight - a.weight; - } - } + // array sort function - function getController(id) { + function descending(a, b) { - return getBuild(library.controllers[id], buildController); - } + return b.weight - a.weight; + } + } - // image + function getController(id) { - function parseImage(xml) { + return getBuild(library.controllers[id], buildController); + } - var data = { - init_from: getElementsByTagName(xml, 'init_from')[0].textContent - }; + // image - library.images[xml.getAttribute('id')] = data; - } + function parseImage(xml) { - function buildImage(data) { + var data = { + init_from: getElementsByTagName(xml, 'init_from')[0].textContent + }; - if (data.build !== undefined) return data.build; + library.images[xml.getAttribute('id')] = data; + } - return data.init_from; - } + function buildImage(data) { - function getImage(id) { + if (data.build !== undefined) return data.build; - var data = library.images[id]; + return data.init_from; + } - if (data !== undefined) { + function getImage(id) { - return getBuild(data, buildImage); - } + var data = library.images[id]; - console.warn('THREE.ColladaLoader: Couldn\'t find image with ID:', id); + if (data !== undefined) { - return null; - } + return getBuild(data, buildImage); + } - // effect + console.warn('THREE.ColladaLoader: Couldn\'t find image with ID:', id); - function parseEffect(xml) { + return null; + } - var data = {}; + // effect - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffect(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'profile_COMMON': - data.profile = parseEffectProfileCOMMON(child); - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - library.effects[xml.getAttribute('id')] = data; - } + case 'profile_COMMON': + data.profile = parseEffectProfileCOMMON(child); + break; - function parseEffectProfileCOMMON(xml) { + } + } - var data = { - surfaces: {}, - samplers: {} - }; + library.effects[xml.getAttribute('id')] = data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectProfileCOMMON(xml) { - var child = xml.childNodes[i]; + var data = { + surfaces: {}, + samplers: {} + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'newparam': - parseEffectNewparam(child, data); - break; + if (child.nodeType !== 1) continue; - case 'technique': - data.technique = parseEffectTechnique(child); - break; + switch (child.nodeName) { - case 'extra': - data.extra = parseEffectExtra(child); - break; + case 'newparam': + parseEffectNewparam(child, data); + break; - } - } + case 'technique': + data.technique = parseEffectTechnique(child); + break; - return data; - } + case 'extra': + data.extra = parseEffectExtra(child); + break; - function parseEffectNewparam(xml, data) { + } + } - var sid = xml.getAttribute('sid'); + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectNewparam(xml, data) { - var child = xml.childNodes[i]; + var sid = xml.getAttribute('sid'); - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'surface': - data.surfaces[sid] = parseEffectSurface(child); - break; + if (child.nodeType !== 1) continue; - case 'sampler2D': - data.samplers[sid] = parseEffectSampler(child); - break; + switch (child.nodeName) { - } - } - } + case 'surface': + data.surfaces[sid] = parseEffectSurface(child); + break; - function parseEffectSurface(xml) { + case 'sampler2D': + data.samplers[sid] = parseEffectSampler(child); + break; - var data = {}; + } + } + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectSurface(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'init_from': - data.init_from = child.textContent; - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'init_from': + data.init_from = child.textContent; + break; - function parseEffectSampler(xml) { + } + } - var data = {}; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectSampler(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'source': - data.source = child.textContent; - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'source': + data.source = child.textContent; + break; - function parseEffectTechnique(xml) { + } + } - var data = {}; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectTechnique(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'constant': - case 'lambert': - case 'blinn': - case 'phong': - data.type = child.nodeName; - data.parameters = parseEffectParameters(child); - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'constant': + case 'lambert': + case 'blinn': + case 'phong': + data.type = child.nodeName; + data.parameters = parseEffectParameters(child); + break; - function parseEffectParameters(xml) { + } + } - var data = {}; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectParameters(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'emission': - case 'diffuse': - case 'specular': - case 'bump': - case 'ambient': - case 'shininess': - case 'transparency': - data[child.nodeName] = parseEffectParameter(child); - break; - case 'transparent': - data[child.nodeName] = { - opaque: child.getAttribute('opaque'), - data: parseEffectParameter(child) - }; - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'emission': + case 'diffuse': + case 'specular': + case 'bump': + case 'ambient': + case 'shininess': + case 'transparency': + data[child.nodeName] = parseEffectParameter(child); + break; + case 'transparent': + data[child.nodeName] = { + opaque: child.getAttribute('opaque'), + data: parseEffectParameter(child) + }; + break; - function parseEffectParameter(xml) { + } + } - var data = {}; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectParameter(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'color': - data[child.nodeName] = parseFloats(child.textContent); - break; + if (child.nodeType !== 1) continue; - case 'float': - data[child.nodeName] = parseFloat(child.textContent); - break; + switch (child.nodeName) { - case 'texture': - data[child.nodeName] = { id: child.getAttribute('texture'), extra: parseEffectParameterTexture(child) }; - break; + case 'color': + data[child.nodeName] = parseFloats(child.textContent); + break; - } - } + case 'float': + data[child.nodeName] = parseFloat(child.textContent); + break; - return data; - } + case 'texture': + data[child.nodeName] = { id: child.getAttribute('texture'), extra: parseEffectParameterTexture(child) }; + break; - function parseEffectParameterTexture(xml) { + } + } - var data = { - technique: {} - }; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectParameterTexture(xml) { - var child = xml.childNodes[i]; + var data = { + technique: {} + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'extra': - parseEffectParameterTextureExtra(child, data); - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'extra': + parseEffectParameterTextureExtra(child, data); + break; - function parseEffectParameterTextureExtra(xml, data) { + } + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + return data; + } - var child = xml.childNodes[i]; + function parseEffectParameterTextureExtra(xml, data) { - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'technique': - parseEffectParameterTextureExtraTechnique(child, data); - break; + if (child.nodeType !== 1) continue; - } - } - } + switch (child.nodeName) { - function parseEffectParameterTextureExtraTechnique(xml, data) { + case 'technique': + parseEffectParameterTextureExtraTechnique(child, data); + break; - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + } + } + } - var child = xml.childNodes[i]; + function parseEffectParameterTextureExtraTechnique(xml, data) { - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'repeatU': - case 'repeatV': - case 'offsetU': - case 'offsetV': - data.technique[child.nodeName] = parseFloat(child.textContent); - break; + if (child.nodeType !== 1) continue; - case 'wrapU': - case 'wrapV': + switch (child.nodeName) { - // some files have values for wrapU/wrapV which become NaN via parseInt + case 'repeatU': + case 'repeatV': + case 'offsetU': + case 'offsetV': + data.technique[child.nodeName] = parseFloat(child.textContent); + break; - if (child.textContent.toUpperCase() === 'TRUE') { + case 'wrapU': + case 'wrapV': - data.technique[child.nodeName] = 1; - } else if (child.textContent.toUpperCase() === 'FALSE') { + // some files have values for wrapU/wrapV which become NaN via parseInt - data.technique[child.nodeName] = 0; - } else { + if (child.textContent.toUpperCase() === 'TRUE') { - data.technique[child.nodeName] = parseInt(child.textContent); - } + data.technique[child.nodeName] = 1; + } else if (child.textContent.toUpperCase() === 'FALSE') { - break; + data.technique[child.nodeName] = 0; + } else { - } - } + data.technique[child.nodeName] = parseInt(child.textContent); } - function parseEffectExtra(xml) { + break; - var data = {}; + } + } + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectExtra(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'technique': - data.technique = parseEffectExtraTechnique(child); - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'technique': + data.technique = parseEffectExtraTechnique(child); + break; - function parseEffectExtraTechnique(xml) { + } + } - var data = {}; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseEffectExtraTechnique(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'double_sided': - data[child.nodeName] = parseInt(child.textContent); - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'double_sided': + data[child.nodeName] = parseInt(child.textContent); + break; - function buildEffect(data) { + } + } - return data; - } + return data; + } - function getEffect(id) { + function buildEffect(data) { - return getBuild(library.effects[id], buildEffect); - } + return data; + } - // material + function getEffect(id) { - function parseMaterial(xml) { + return getBuild(library.effects[id], buildEffect); + } - var data = { - name: xml.getAttribute('name') - }; + // material - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseMaterial(xml) { - var child = xml.childNodes[i]; + var data = { + name: xml.getAttribute('name') + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'instance_effect': - data.url = parseId(child.getAttribute('url')); - break; + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - library.materials[xml.getAttribute('id')] = data; - } + case 'instance_effect': + data.url = parseId(child.getAttribute('url')); + break; - function getTextureLoader(image) { + } + } - var loader; + library.materials[xml.getAttribute('id')] = data; + } - var extension = image.slice((image.lastIndexOf('.') - 1 >>> 0) + 2); // http://www.jstips.co/en/javascript/get-file-extension/ - extension = extension.toLowerCase(); + function getTextureLoader(image) { - switch (extension) { + var loader; - case 'tga': - loader = tgaLoader; - break; + var extension = image.slice((image.lastIndexOf('.') - 1 >>> 0) + 2); // http://www.jstips.co/en/javascript/get-file-extension/ + extension = extension.toLowerCase(); - default: - loader = textureLoader; + switch (extension) { - } + case 'tga': + loader = tgaLoader; + break; - return loader; - } + default: + loader = textureLoader; - function buildMaterial(data) { + } - var effect = getEffect(data.url); - var technique = effect.profile.technique; - var extra = effect.profile.extra; + return loader; + } - var material; + function buildMaterial(data) { - switch (technique.type) { + var effect = getEffect(data.url); + var technique = effect.profile.technique; + var extra = effect.profile.extra; - case 'phong': - case 'blinn': - material = new THREE.MeshPhongMaterial(); - break; + var material; - case 'lambert': - material = new THREE.MeshLambertMaterial(); - break; + switch (technique.type) { - default: - material = new THREE.MeshBasicMaterial(); - break; + case 'phong': + case 'blinn': + material = new THREE.MeshPhongMaterial(); + break; - } + case 'lambert': + material = new THREE.MeshLambertMaterial(); + break; - material.name = data.name; + default: + material = new THREE.MeshBasicMaterial(); + break; - function getTexture(textureObject) { + } - var sampler = effect.profile.samplers[textureObject.id]; - var image = null; + material.name = data.name; - // get image + function getTexture(textureObject) { - if (sampler !== undefined) { + var sampler = effect.profile.samplers[textureObject.id]; + var image = null; - var surface = effect.profile.surfaces[sampler.source]; - image = getImage(surface.init_from); - } else { + // get image - console.warn('THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).'); - image = getImage(textureObject.id); - } + if (sampler !== undefined) { - // create texture if image is avaiable + var surface = effect.profile.surfaces[sampler.source]; + image = getImage(surface.init_from); + } else { - if (image !== null) { + console.warn('THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).'); + image = getImage(textureObject.id); + } - var loader = getTextureLoader(image); + // create texture if image is avaiable - if (loader !== undefined) { + if (image !== null) { - var texture = loader.load(image); + var loader = getTextureLoader(image); - var extra = textureObject.extra; + if (loader !== undefined) { - if (extra !== undefined && extra.technique !== undefined && isEmpty(extra.technique) === false) { + var texture = loader.load(image); - var technique = extra.technique; + var extra = textureObject.extra; - texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; - texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + if (extra !== undefined && extra.technique !== undefined && isEmpty(extra.technique) === false) { - texture.offset.set(technique.offsetU || 0, technique.offsetV || 0); - texture.repeat.set(technique.repeatU || 1, technique.repeatV || 1); - } else { + var technique = extra.technique; - texture.wrapS = THREE.RepeatWrapping; - texture.wrapT = THREE.RepeatWrapping; - } + texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; - return texture; - } else { + texture.offset.set(technique.offsetU || 0, technique.offsetV || 0); + texture.repeat.set(technique.repeatU || 1, technique.repeatV || 1); + } else { - console.warn('THREE.ColladaLoader: Loader for texture %s not found.', image); + texture.wrapS = THREE.RepeatWrapping; + texture.wrapT = THREE.RepeatWrapping; + } - return null; - } - } else { + return texture; + } else { - console.warn('THREE.ColladaLoader: Couldn\'t create texture with ID:', textureObject.id); + console.warn('THREE.ColladaLoader: Loader for texture %s not found.', image); - return null; - } - } + return null; + } + } else { - var parameters = technique.parameters; - - for (var key in parameters) { - - var parameter = parameters[key]; - - switch (key) { - - case 'diffuse': - if (parameter.color) material.color.fromArray(parameter.color); - if (parameter.texture) material.map = getTexture(parameter.texture); - break; - case 'specular': - if (parameter.color && material.specular) material.specular.fromArray(parameter.color); - if (parameter.texture) material.specularMap = getTexture(parameter.texture); - break; - case 'bump': - if (parameter.texture) material.normalMap = getTexture(parameter.texture); - break; - case 'ambient': - if (parameter.texture) material.lightMap = getTexture(parameter.texture); - break; - case 'shininess': - if (parameter.float && material.shininess) material.shininess = parameter.float; - break; - case 'emission': - if (parameter.color && material.emissive) material.emissive.fromArray(parameter.color); - if (parameter.texture) material.emissiveMap = getTexture(parameter.texture); - break; - - } - } + console.warn('THREE.ColladaLoader: Couldn\'t create texture with ID:', textureObject.id); - // + return null; + } + } - var transparent = parameters['transparent']; - var transparency = parameters['transparency']; + var parameters = technique.parameters; + + for (var key in parameters) { + + var parameter = parameters[key]; + + switch (key) { + + case 'diffuse': + if (parameter.color) material.color.fromArray(parameter.color); + if (parameter.texture) material.map = getTexture(parameter.texture); + break; + case 'specular': + if (parameter.color && material.specular) material.specular.fromArray(parameter.color); + if (parameter.texture) material.specularMap = getTexture(parameter.texture); + break; + case 'bump': + if (parameter.texture) material.normalMap = getTexture(parameter.texture); + break; + case 'ambient': + if (parameter.texture) material.lightMap = getTexture(parameter.texture); + break; + case 'shininess': + if (parameter.float && material.shininess) material.shininess = parameter.float; + break; + case 'emission': + if (parameter.color && material.emissive) material.emissive.fromArray(parameter.color); + if (parameter.texture) material.emissiveMap = getTexture(parameter.texture); + break; - // does not exist but + } + } - if (transparency === undefined && transparent) { + // - transparency = { - float: 1 - }; - } + var transparent = parameters['transparent']; + var transparency = parameters['transparency']; - // does not exist but + // does not exist but - if (transparent === undefined && transparency) { + if (transparency === undefined && transparent) { - transparent = { - opaque: 'A_ONE', - data: { - color: [1, 1, 1, 1] - } }; - } + transparency = { + float: 1 + }; + } - if (transparent && transparency) { + // does not exist but - // handle case if a texture exists but no color + if (transparent === undefined && transparency) { - if (transparent.data.texture) { + transparent = { + opaque: 'A_ONE', + data: { + color: [1, 1, 1, 1] + } }; + } - // we do not set an alpha map (see #13792) + if (transparent && transparency) { - material.transparent = true; - } else { + // handle case if a texture exists but no color - var color = transparent.data.color; + if (transparent.data.texture) { - switch (transparent.opaque) { + // we do not set an alpha map (see #13792) - case 'A_ONE': - material.opacity = color[3] * transparency.float; - break; - case 'RGB_ZERO': - material.opacity = 1 - color[0] * transparency.float; - break; - case 'A_ZERO': - material.opacity = 1 - color[3] * transparency.float; - break; - case 'RGB_ONE': - material.opacity = color[0] * transparency.float; - break; - default: - console.warn('THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque); + material.transparent = true; + } else { - } + var color = transparent.data.color; - if (material.opacity < 1) material.transparent = true; - } - } + switch (transparent.opaque) { - // + case 'A_ONE': + material.opacity = color[3] * transparency.float; + break; + case 'RGB_ZERO': + material.opacity = 1 - color[0] * transparency.float; + break; + case 'A_ZERO': + material.opacity = 1 - color[3] * transparency.float; + break; + case 'RGB_ONE': + material.opacity = color[0] * transparency.float; + break; + default: + console.warn('THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque); - if (extra !== undefined && extra.technique !== undefined && extra.technique.double_sided === 1) { + } - material.side = THREE.DoubleSide; - } + if (material.opacity < 1) material.transparent = true; + } + } - return material; - } + // - function getMaterial(id) { + if (extra !== undefined && extra.technique !== undefined && extra.technique.double_sided === 1) { - return getBuild(library.materials[id], buildMaterial); - } + material.side = THREE.DoubleSide; + } - // camera + return material; + } - function parseCamera(xml) { + function getMaterial(id) { - var data = { - name: xml.getAttribute('name') - }; + return getBuild(library.materials[id], buildMaterial); + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + // camera - var child = xml.childNodes[i]; + function parseCamera(xml) { - if (child.nodeType !== 1) continue; + var data = { + name: xml.getAttribute('name') + }; - switch (child.nodeName) { + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - case 'optics': - data.optics = parseCameraOptics(child); - break; + var child = xml.childNodes[i]; - } - } + if (child.nodeType !== 1) continue; - library.cameras[xml.getAttribute('id')] = data; - } + switch (child.nodeName) { - function parseCameraOptics(xml) { + case 'optics': + data.optics = parseCameraOptics(child); + break; - for (var i = 0; i < xml.childNodes.length; i++) { + } + } - var child = xml.childNodes[i]; + library.cameras[xml.getAttribute('id')] = data; + } - switch (child.nodeName) { + function parseCameraOptics(xml) { - case 'technique_common': - return parseCameraTechnique(child); + for (var i = 0; i < xml.childNodes.length; i++) { - } - } + var child = xml.childNodes[i]; - return {}; - } + switch (child.nodeName) { - function parseCameraTechnique(xml) { + case 'technique_common': + return parseCameraTechnique(child); - var data = {}; + } + } - for (var i = 0; i < xml.childNodes.length; i++) { + return {}; + } - var child = xml.childNodes[i]; + function parseCameraTechnique(xml) { - switch (child.nodeName) { + var data = {}; - case 'perspective': - case 'orthographic': + for (var i = 0; i < xml.childNodes.length; i++) { - data.technique = child.nodeName; - data.parameters = parseCameraParameters(child); + var child = xml.childNodes[i]; - break; + switch (child.nodeName) { - } - } + case 'perspective': + case 'orthographic': - return data; - } + data.technique = child.nodeName; + data.parameters = parseCameraParameters(child); - function parseCameraParameters(xml) { + break; - var data = {}; + } + } - for (var i = 0; i < xml.childNodes.length; i++) { + return data; + } - var child = xml.childNodes[i]; + function parseCameraParameters(xml) { - switch (child.nodeName) { + var data = {}; - case 'xfov': - case 'yfov': - case 'xmag': - case 'ymag': - case 'znear': - case 'zfar': - case 'aspect_ratio': - data[child.nodeName] = parseFloat(child.textContent); - break; + for (var i = 0; i < xml.childNodes.length; i++) { - } - } + var child = xml.childNodes[i]; - return data; - } + switch (child.nodeName) { - function buildCamera(data) { + case 'xfov': + case 'yfov': + case 'xmag': + case 'ymag': + case 'znear': + case 'zfar': + case 'aspect_ratio': + data[child.nodeName] = parseFloat(child.textContent); + break; - var camera; + } + } - switch (data.optics.technique) { + return data; + } - case 'perspective': - camera = new THREE.PerspectiveCamera(data.optics.parameters.yfov, data.optics.parameters.aspect_ratio, data.optics.parameters.znear, data.optics.parameters.zfar); - break; + function buildCamera(data) { - case 'orthographic': - var ymag = data.optics.parameters.ymag; - var xmag = data.optics.parameters.xmag; - var aspectRatio = data.optics.parameters.aspect_ratio; + var camera; - xmag = xmag === undefined ? ymag * aspectRatio : xmag; - ymag = ymag === undefined ? xmag / aspectRatio : ymag; + switch (data.optics.technique) { - xmag *= 0.5; - ymag *= 0.5; + case 'perspective': + camera = new THREE.PerspectiveCamera(data.optics.parameters.yfov, data.optics.parameters.aspect_ratio, data.optics.parameters.znear, data.optics.parameters.zfar); + break; - camera = new THREE.OrthographicCamera(-xmag, xmag, ymag, -ymag, // left, right, top, bottom - data.optics.parameters.znear, data.optics.parameters.zfar); - break; + case 'orthographic': + var ymag = data.optics.parameters.ymag; + var xmag = data.optics.parameters.xmag; + var aspectRatio = data.optics.parameters.aspect_ratio; - default: - camera = new THREE.PerspectiveCamera(); - break; + xmag = xmag === undefined ? ymag * aspectRatio : xmag; + ymag = ymag === undefined ? xmag / aspectRatio : ymag; - } + xmag *= 0.5; + ymag *= 0.5; - camera.name = data.name; + camera = new THREE.OrthographicCamera(-xmag, xmag, ymag, -ymag, // left, right, top, bottom + data.optics.parameters.znear, data.optics.parameters.zfar); + break; - return camera; - } + default: + camera = new THREE.PerspectiveCamera(); + break; - function getCamera(id) { + } - var data = library.cameras[id]; + camera.name = data.name; - if (data !== undefined) { + return camera; + } - return getBuild(data, buildCamera); - } + function getCamera(id) { - console.warn('THREE.ColladaLoader: Couldn\'t find camera with ID:', id); + var data = library.cameras[id]; - return null; - } + if (data !== undefined) { - // light + return getBuild(data, buildCamera); + } - function parseLight(xml) { + console.warn('THREE.ColladaLoader: Couldn\'t find camera with ID:', id); - var data = {}; + return null; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + // light - var child = xml.childNodes[i]; + function parseLight(xml) { - if (child.nodeType !== 1) continue; + var data = {}; - switch (child.nodeName) { + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - case 'technique_common': - data = parseLightTechnique(child); - break; + var child = xml.childNodes[i]; - } - } + if (child.nodeType !== 1) continue; - library.lights[xml.getAttribute('id')] = data; - } + switch (child.nodeName) { - function parseLightTechnique(xml) { + case 'technique_common': + data = parseLightTechnique(child); + break; - var data = {}; + } + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + library.lights[xml.getAttribute('id')] = data; + } - var child = xml.childNodes[i]; + function parseLightTechnique(xml) { - if (child.nodeType !== 1) continue; + var data = {}; - switch (child.nodeName) { + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - case 'directional': - case 'point': - case 'spot': - case 'ambient': + var child = xml.childNodes[i]; - data.technique = child.nodeName; - data.parameters = parseLightParameters(child); + if (child.nodeType !== 1) continue; - } - } + switch (child.nodeName) { - return data; - } + case 'directional': + case 'point': + case 'spot': + case 'ambient': - function parseLightParameters(xml) { + data.technique = child.nodeName; + data.parameters = parseLightParameters(child); - var data = {}; + } + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + return data; + } - var child = xml.childNodes[i]; + function parseLightParameters(xml) { - if (child.nodeType !== 1) continue; + var data = {}; - switch (child.nodeName) { + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - case 'color': - var array = parseFloats(child.textContent); - data.color = new THREE.Color().fromArray(array); - break; + var child = xml.childNodes[i]; - case 'falloff_angle': - data.falloffAngle = parseFloat(child.textContent); - break; + if (child.nodeType !== 1) continue; - case 'quadratic_attenuation': - var f = parseFloat(child.textContent); - data.distance = f ? Math.sqrt(1 / f) : 0; - break; + switch (child.nodeName) { - } - } + case 'color': + var array = parseFloats(child.textContent); + data.color = new THREE.Color().fromArray(array); + break; - return data; - } + case 'falloff_angle': + data.falloffAngle = parseFloat(child.textContent); + break; - function buildLight(data) { + case 'quadratic_attenuation': + var f = parseFloat(child.textContent); + data.distance = f ? Math.sqrt(1 / f) : 0; + break; - var light; + } + } - switch (data.technique) { + return data; + } - case 'directional': - light = new THREE.DirectionalLight(); - break; + function buildLight(data) { - case 'point': - light = new THREE.PointLight(); - break; + var light; - case 'spot': - light = new THREE.SpotLight(); - break; + switch (data.technique) { - case 'ambient': - light = new THREE.AmbientLight(); - break; + case 'directional': + light = new THREE.DirectionalLight(); + break; - } + case 'point': + light = new THREE.PointLight(); + break; - if (data.parameters.color) light.color.copy(data.parameters.color); - if (data.parameters.distance) light.distance = data.parameters.distance; + case 'spot': + light = new THREE.SpotLight(); + break; - return light; - } + case 'ambient': + light = new THREE.AmbientLight(); + break; - function getLight(id) { + } - var data = library.lights[id]; + if (data.parameters.color) light.color.copy(data.parameters.color); + if (data.parameters.distance) light.distance = data.parameters.distance; - if (data !== undefined) { + return light; + } - return getBuild(data, buildLight); - } + function getLight(id) { - console.warn('THREE.ColladaLoader: Couldn\'t find light with ID:', id); + var data = library.lights[id]; - return null; - } + if (data !== undefined) { - // geometry + return getBuild(data, buildLight); + } - function parseGeometry(xml) { + console.warn('THREE.ColladaLoader: Couldn\'t find light with ID:', id); - var data = { - name: xml.getAttribute('name'), - sources: {}, - vertices: {}, - primitives: [] - }; + return null; + } - var mesh = getElementsByTagName(xml, 'mesh')[0]; + // geometry - // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep - if (mesh === undefined) return; + function parseGeometry(xml) { - for (var i = 0; i < mesh.childNodes.length; i++) { + var data = { + name: xml.getAttribute('name'), + sources: {}, + vertices: {}, + primitives: [] + }; - var child = mesh.childNodes[i]; + var mesh = getElementsByTagName(xml, 'mesh')[0]; - if (child.nodeType !== 1) continue; + // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep + if (mesh === undefined) return; - var id = child.getAttribute('id'); + for (var i = 0; i < mesh.childNodes.length; i++) { - switch (child.nodeName) { + var child = mesh.childNodes[i]; - case 'source': - data.sources[id] = parseSource(child); - break; + if (child.nodeType !== 1) continue; - case 'vertices': - // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ]; - data.vertices = parseGeometryVertices(child); - break; + var id = child.getAttribute('id'); - case 'polygons': - console.warn('THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName); - break; + switch (child.nodeName) { - case 'lines': - case 'linestrips': - case 'polylist': - case 'triangles': - data.primitives.push(parseGeometryPrimitive(child)); - break; + case 'source': + data.sources[id] = parseSource(child); + break; - default: - console.log(child); + case 'vertices': + // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ]; + data.vertices = parseGeometryVertices(child); + break; - } - } + case 'polygons': + console.warn('THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName); + break; - library.geometries[xml.getAttribute('id')] = data; - } + case 'lines': + case 'linestrips': + case 'polylist': + case 'triangles': + data.primitives.push(parseGeometryPrimitive(child)); + break; - function parseSource(xml) { + default: + console.log(child); - var data = { - array: [], - stride: 3 - }; + } + } - for (var i = 0; i < xml.childNodes.length; i++) { + library.geometries[xml.getAttribute('id')] = data; + } - var child = xml.childNodes[i]; + function parseSource(xml) { - if (child.nodeType !== 1) continue; + var data = { + array: [], + stride: 3 + }; - switch (child.nodeName) { + for (var i = 0; i < xml.childNodes.length; i++) { - case 'float_array': - data.array = parseFloats(child.textContent); - break; + var child = xml.childNodes[i]; - case 'Name_array': - data.array = parseStrings(child.textContent); - break; + if (child.nodeType !== 1) continue; - case 'technique_common': - var accessor = getElementsByTagName(child, 'accessor')[0]; + switch (child.nodeName) { - if (accessor !== undefined) { + case 'float_array': + data.array = parseFloats(child.textContent); + break; - data.stride = parseInt(accessor.getAttribute('stride')); - } - break; + case 'Name_array': + data.array = parseStrings(child.textContent); + break; - } - } + case 'technique_common': + var accessor = getElementsByTagName(child, 'accessor')[0]; - return data; + if (accessor !== undefined) { + + data.stride = parseInt(accessor.getAttribute('stride')); } + break; - function parseGeometryVertices(xml) { + } + } - var data = {}; + return data; + } - for (var i = 0; i < xml.childNodes.length; i++) { + function parseGeometryVertices(xml) { - var child = xml.childNodes[i]; + var data = {}; - if (child.nodeType !== 1) continue; + for (var i = 0; i < xml.childNodes.length; i++) { - data[child.getAttribute('semantic')] = parseId(child.getAttribute('source')); - } + var child = xml.childNodes[i]; - return data; - } + if (child.nodeType !== 1) continue; - function parseGeometryPrimitive(xml) { + data[child.getAttribute('semantic')] = parseId(child.getAttribute('source')); + } - var primitive = { - type: xml.nodeName, - material: xml.getAttribute('material'), - count: parseInt(xml.getAttribute('count')), - inputs: {}, - stride: 0, - hasUV: false - }; + return data; + } - for (var i = 0, l = xml.childNodes.length; i < l; i++) { + function parseGeometryPrimitive(xml) { - var child = xml.childNodes[i]; + var primitive = { + type: xml.nodeName, + material: xml.getAttribute('material'), + count: parseInt(xml.getAttribute('count')), + inputs: {}, + stride: 0, + hasUV: false + }; - if (child.nodeType !== 1) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { - switch (child.nodeName) { + var child = xml.childNodes[i]; - case 'input': - var id = parseId(child.getAttribute('source')); - var semantic = child.getAttribute('semantic'); - var offset = parseInt(child.getAttribute('offset')); - var set = parseInt(child.getAttribute('set')); - var inputname = set > 0 ? semantic + set : semantic; - primitive.inputs[inputname] = { id: id, offset: offset }; - primitive.stride = Math.max(primitive.stride, offset + 1); - if (semantic === 'TEXCOORD') primitive.hasUV = true; - break; + if (child.nodeType !== 1) continue; - case 'vcount': - primitive.vcount = parseInts(child.textContent); - break; + switch (child.nodeName) { - case 'p': - primitive.p = parseInts(child.textContent); - break; + case 'input': + var id = parseId(child.getAttribute('source')); + var semantic = child.getAttribute('semantic'); + var offset = parseInt(child.getAttribute('offset')); + var set = parseInt(child.getAttribute('set')); + var inputname = set > 0 ? semantic + set : semantic; + primitive.inputs[inputname] = { id: id, offset: offset }; + primitive.stride = Math.max(primitive.stride, offset + 1); + if (semantic === 'TEXCOORD') primitive.hasUV = true; + break; - } - } + case 'vcount': + primitive.vcount = parseInts(child.textContent); + break; - return primitive; - } + case 'p': + primitive.p = parseInts(child.textContent); + break; - function groupPrimitives(primitives) { + } + } - var build = {}; + return primitive; + } - for (var i = 0; i < primitives.length; i++) { + function groupPrimitives(primitives) { - var primitive = primitives[i]; + var build = {}; - if (build[primitive.type] === undefined) build[primitive.type] = []; + for (var i = 0; i < primitives.length; i++) { - build[primitive.type].push(primitive); - } + var primitive = primitives[i]; - return build; - } + if (build[primitive.type] === undefined) build[primitive.type] = []; - function checkUVCoordinates(primitives) { + build[primitive.type].push(primitive); + } - var count = 0; + return build; + } - for (var i = 0, l = primitives.length; i < l; i++) { + function checkUVCoordinates(primitives) { - var primitive = primitives[i]; + var count = 0; - if (primitive.hasUV === true) { + for (var i = 0, l = primitives.length; i < l; i++) { - count++; - } - } + var primitive = primitives[i]; - if (count > 0 && count < primitives.length) { + if (primitive.hasUV === true) { - primitives.uvsNeedsFix = true; - } - } + count++; + } + } - function buildGeometry(data) { + if (count > 0 && count < primitives.length) { - var build = {}; + primitives.uvsNeedsFix = true; + } + } - var sources = data.sources; - var vertices = data.vertices; - var primitives = data.primitives; + function buildGeometry(data) { - if (primitives.length === 0) return {}; + var build = {}; - // our goal is to create one buffer geometry for a single type of primitives - // first, we group all primitives by their type + var sources = data.sources; + var vertices = data.vertices; + var primitives = data.primitives; - var groupedPrimitives = groupPrimitives(primitives); + if (primitives.length === 0) return {}; - for (var type in groupedPrimitives) { + // our goal is to create one buffer geometry for a single type of primitives + // first, we group all primitives by their type - var primitiveType = groupedPrimitives[type]; + var groupedPrimitives = groupPrimitives(primitives); - // second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines) + for (var type in groupedPrimitives) { - checkUVCoordinates(primitiveType); + var primitiveType = groupedPrimitives[type]; - // third, create a buffer geometry for each type of primitives + // second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines) - build[type] = buildGeometryType(primitiveType, sources, vertices); - } + checkUVCoordinates(primitiveType); - return build; - } + // third, create a buffer geometry for each type of primitives - function buildGeometryType(primitives, sources, vertices) { + build[type] = buildGeometryType(primitiveType, sources, vertices); + } - var build = {}; + return build; + } - var position = { array: [], stride: 0 }; - var normal = { array: [], stride: 0 }; - var uv = { array: [], stride: 0 }; - var uv2 = { array: [], stride: 0 }; - var color = { array: [], stride: 0 }; + function buildGeometryType(primitives, sources, vertices) { - var skinIndex = { array: [], stride: 4 }; - var skinWeight = { array: [], stride: 4 }; + var build = {}; - var geometry = new THREE.BufferGeometry(); + var position = { array: [], stride: 0 }; + var normal = { array: [], stride: 0 }; + var uv = { array: [], stride: 0 }; + var uv2 = { array: [], stride: 0 }; + var color = { array: [], stride: 0 }; - var materialKeys = []; + var skinIndex = { array: [], stride: 4 }; + var skinWeight = { array: [], stride: 4 }; - var start = 0; + var geometry = new THREE.BufferGeometry(); - for (var p = 0; p < primitives.length; p++) { + var materialKeys = []; - var primitive = primitives[p]; - var inputs = primitive.inputs; + var start = 0; - // groups + for (var p = 0; p < primitives.length; p++) { - var count = 0; + var primitive = primitives[p]; + var inputs = primitive.inputs; - switch (primitive.type) { + // groups - case 'lines': - case 'linestrips': - count = primitive.count * 2; - break; + var count = 0; - case 'triangles': - count = primitive.count * 3; - break; + switch (primitive.type) { - case 'polylist': + case 'lines': + case 'linestrips': + count = primitive.count * 2; + break; - for (var g = 0; g < primitive.count; g++) { + case 'triangles': + count = primitive.count * 3; + break; - var vc = primitive.vcount[g]; + case 'polylist': - switch (vc) { + for (var g = 0; g < primitive.count; g++) { - case 3: - count += 3; // single triangle - break; + var vc = primitive.vcount[g]; - case 4: - count += 6; // quad, subdivided into two triangles - break; + switch (vc) { - default: - count += (vc - 2) * 3; // polylist with more than four vertices - break; + case 3: + count += 3; // single triangle + break; - } - } + case 4: + count += 6; // quad, subdivided into two triangles + break; - break; + default: + count += (vc - 2) * 3; // polylist with more than four vertices + break; - default: - console.warn('THREE.ColladaLoader: Unknow primitive type:', primitive.type); - - } - - geometry.addGroup(start, count, p); - start += count; - - // material + } + } - if (primitive.material) { + break; - materialKeys.push(primitive.material); - } + default: + console.warn('THREE.ColladaLoader: Unknow primitive type:', primitive.type); - // geometry data + } - for (var name in inputs) { + geometry.addGroup(start, count, p); + start += count; - var input = inputs[name]; + // material - switch (name) { + if (primitive.material) { - case 'VERTEX': - for (var key in vertices) { + materialKeys.push(primitive.material); + } - var id = vertices[key]; + // geometry data - switch (key) { + for (var name in inputs) { - case 'POSITION': - var prevLength = position.array.length; - buildGeometryData(primitive, sources[id], input.offset, position.array); - position.stride = sources[id].stride; + var input = inputs[name]; - if (sources.skinWeights && sources.skinIndices) { + switch (name) { - buildGeometryData(primitive, sources.skinIndices, input.offset, skinIndex.array); - buildGeometryData(primitive, sources.skinWeights, input.offset, skinWeight.array); - } + case 'VERTEX': + for (var key in vertices) { - // see #3803 + var id = vertices[key]; - if (primitive.hasUV === false && primitives.uvsNeedsFix === true) { + switch (key) { - var count = (position.array.length - prevLength) / position.stride; + case 'POSITION': + var prevLength = position.array.length; + buildGeometryData(primitive, sources[id], input.offset, position.array); + position.stride = sources[id].stride; - for (var i = 0; i < count; i++) { + if (sources.skinWeights && sources.skinIndices) { - // fill missing uv coordinates + buildGeometryData(primitive, sources.skinIndices, input.offset, skinIndex.array); + buildGeometryData(primitive, sources.skinWeights, input.offset, skinWeight.array); + } - uv.array.push(0, 0); - } - } - break; + // see #3803 - case 'NORMAL': - buildGeometryData(primitive, sources[id], input.offset, normal.array); - normal.stride = sources[id].stride; - break; + if (primitive.hasUV === false && primitives.uvsNeedsFix === true) { - case 'COLOR': - buildGeometryData(primitive, sources[id], input.offset, color.array); - color.stride = sources[id].stride; - break; + var count = (position.array.length - prevLength) / position.stride; - case 'TEXCOORD': - buildGeometryData(primitive, sources[id], input.offset, uv.array); - uv.stride = sources[id].stride; - break; + for (var i = 0; i < count; i++) { - case 'TEXCOORD1': - buildGeometryData(primitive, sources[id], input.offset, uv2.array); - uv.stride = sources[id].stride; - break; + // fill missing uv coordinates - default: - console.warn('THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key); + uv.array.push(0, 0); + } + } + break; - } - } - break; + case 'NORMAL': + buildGeometryData(primitive, sources[id], input.offset, normal.array); + normal.stride = sources[id].stride; + break; - case 'NORMAL': - buildGeometryData(primitive, sources[input.id], input.offset, normal.array); - normal.stride = sources[input.id].stride; - break; + case 'COLOR': + buildGeometryData(primitive, sources[id], input.offset, color.array); + color.stride = sources[id].stride; + break; - case 'COLOR': - buildGeometryData(primitive, sources[input.id], input.offset, color.array); - color.stride = sources[input.id].stride; - break; + case 'TEXCOORD': + buildGeometryData(primitive, sources[id], input.offset, uv.array); + uv.stride = sources[id].stride; + break; - case 'TEXCOORD': - buildGeometryData(primitive, sources[input.id], input.offset, uv.array); - uv.stride = sources[input.id].stride; - break; + case 'TEXCOORD1': + buildGeometryData(primitive, sources[id], input.offset, uv2.array); + uv.stride = sources[id].stride; + break; - case 'TEXCOORD1': - buildGeometryData(primitive, sources[input.id], input.offset, uv2.array); - uv2.stride = sources[input.id].stride; - break; + default: + console.warn('THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key); - } - } - } + } + } + break; - // build geometry + case 'NORMAL': + buildGeometryData(primitive, sources[input.id], input.offset, normal.array); + normal.stride = sources[input.id].stride; + break; - if (position.array.length > 0) geometry.addAttribute('position', new THREE.Float32BufferAttribute(position.array, position.stride)); - if (normal.array.length > 0) geometry.addAttribute('normal', new THREE.Float32BufferAttribute(normal.array, normal.stride)); - if (color.array.length > 0) geometry.addAttribute('color', new THREE.Float32BufferAttribute(color.array, color.stride)); - if (uv.array.length > 0) geometry.addAttribute('uv', new THREE.Float32BufferAttribute(uv.array, uv.stride)); - if (uv2.array.length > 0) geometry.addAttribute('uv2', new THREE.Float32BufferAttribute(uv2.array, uv2.stride)); + case 'COLOR': + buildGeometryData(primitive, sources[input.id], input.offset, color.array); + color.stride = sources[input.id].stride; + break; - if (skinIndex.array.length > 0) geometry.addAttribute('skinIndex', new THREE.Float32BufferAttribute(skinIndex.array, skinIndex.stride)); - if (skinWeight.array.length > 0) geometry.addAttribute('skinWeight', new THREE.Float32BufferAttribute(skinWeight.array, skinWeight.stride)); + case 'TEXCOORD': + buildGeometryData(primitive, sources[input.id], input.offset, uv.array); + uv.stride = sources[input.id].stride; + break; - build.data = geometry; - build.type = primitives[0].type; - build.materialKeys = materialKeys; + case 'TEXCOORD1': + buildGeometryData(primitive, sources[input.id], input.offset, uv2.array); + uv2.stride = sources[input.id].stride; + break; - return build; - } + } + } + } - function buildGeometryData(primitive, source, offset, array) { + // build geometry - var indices = primitive.p; - var stride = primitive.stride; - var vcount = primitive.vcount; + if (position.array.length > 0) geometry.addAttribute('position', new THREE.Float32BufferAttribute(position.array, position.stride)); + if (normal.array.length > 0) geometry.addAttribute('normal', new THREE.Float32BufferAttribute(normal.array, normal.stride)); + if (color.array.length > 0) geometry.addAttribute('color', new THREE.Float32BufferAttribute(color.array, color.stride)); + if (uv.array.length > 0) geometry.addAttribute('uv', new THREE.Float32BufferAttribute(uv.array, uv.stride)); + if (uv2.array.length > 0) geometry.addAttribute('uv2', new THREE.Float32BufferAttribute(uv2.array, uv2.stride)); - function pushVector(i) { + if (skinIndex.array.length > 0) geometry.addAttribute('skinIndex', new THREE.Float32BufferAttribute(skinIndex.array, skinIndex.stride)); + if (skinWeight.array.length > 0) geometry.addAttribute('skinWeight', new THREE.Float32BufferAttribute(skinWeight.array, skinWeight.stride)); - var index = indices[i + offset] * sourceStride; - var length = index + sourceStride; + build.data = geometry; + build.type = primitives[0].type; + build.materialKeys = materialKeys; - for (; index < length; index++) { + return build; + } - array.push(sourceArray[index]); - } - } + function buildGeometryData(primitive, source, offset, array) { - var sourceArray = source.array; - var sourceStride = source.stride; + var indices = primitive.p; + var stride = primitive.stride; + var vcount = primitive.vcount; - if (primitive.vcount !== undefined) { + function pushVector(i) { - var index = 0; + var index = indices[i + offset] * sourceStride; + var length = index + sourceStride; - for (var i = 0, l = vcount.length; i < l; i++) { + for (; index < length; index++) { - var count = vcount[i]; + array.push(sourceArray[index]); + } + } - if (count === 4) { + var sourceArray = source.array; + var sourceStride = source.stride; - var a = index + stride * 0; - var b = index + stride * 1; - var c = index + stride * 2; - var d = index + stride * 3; + if (primitive.vcount !== undefined) { - pushVector(a);pushVector(b);pushVector(d); - pushVector(b);pushVector(c);pushVector(d); - } else if (count === 3) { + var index = 0; - var a = index + stride * 0; - var b = index + stride * 1; - var c = index + stride * 2; + for (var i = 0, l = vcount.length; i < l; i++) { - pushVector(a);pushVector(b);pushVector(c); - } else if (count > 4) { + var count = vcount[i]; - for (var k = 1, kl = count - 2; k <= kl; k++) { + if (count === 4) { - var a = index + stride * 0; - var b = index + stride * k; - var c = index + stride * (k + 1); + var a = index + stride * 0; + var b = index + stride * 1; + var c = index + stride * 2; + var d = index + stride * 3; - pushVector(a);pushVector(b);pushVector(c); - } - } + pushVector(a);pushVector(b);pushVector(d); + pushVector(b);pushVector(c);pushVector(d); + } else if (count === 3) { - index += stride * count; - } - } else { + var a = index + stride * 0; + var b = index + stride * 1; + var c = index + stride * 2; - for (var i = 0, l = indices.length; i < l; i += stride) { + pushVector(a);pushVector(b);pushVector(c); + } else if (count > 4) { - pushVector(i); - } - } - } + for (var k = 1, kl = count - 2; k <= kl; k++) { - function getGeometry(id) { + var a = index + stride * 0; + var b = index + stride * k; + var c = index + stride * (k + 1); - return getBuild(library.geometries[id], buildGeometry); + pushVector(a);pushVector(b);pushVector(c); } + } - // kinematics + index += stride * count; + } + } else { - function parseKinematicsModel(xml) { + for (var i = 0, l = indices.length; i < l; i += stride) { - var data = { - name: xml.getAttribute('name') || '', - joints: {}, - links: [] - }; + pushVector(i); + } + } + } - for (var i = 0; i < xml.childNodes.length; i++) { + function getGeometry(id) { - var child = xml.childNodes[i]; + return getBuild(library.geometries[id], buildGeometry); + } - if (child.nodeType !== 1) continue; + // kinematics - switch (child.nodeName) { + function parseKinematicsModel(xml) { - case 'technique_common': - parseKinematicsTechniqueCommon(child, data); - break; + var data = { + name: xml.getAttribute('name') || '', + joints: {}, + links: [] + }; - } - } + for (var i = 0; i < xml.childNodes.length; i++) { - library.kinematicsModels[xml.getAttribute('id')] = data; - } + var child = xml.childNodes[i]; - function buildKinematicsModel(data) { + if (child.nodeType !== 1) continue; - if (data.build !== undefined) return data.build; + switch (child.nodeName) { - return data; - } + case 'technique_common': + parseKinematicsTechniqueCommon(child, data); + break; - function getKinematicsModel(id) { + } + } - return getBuild(library.kinematicsModels[id], buildKinematicsModel); - } + library.kinematicsModels[xml.getAttribute('id')] = data; + } - function parseKinematicsTechniqueCommon(xml, data) { + function buildKinematicsModel(data) { - for (var i = 0; i < xml.childNodes.length; i++) { + if (data.build !== undefined) return data.build; - var child = xml.childNodes[i]; + return data; + } - if (child.nodeType !== 1) continue; + function getKinematicsModel(id) { - switch (child.nodeName) { + return getBuild(library.kinematicsModels[id], buildKinematicsModel); + } - case 'joint': - data.joints[child.getAttribute('sid')] = parseKinematicsJoint(child); - break; + function parseKinematicsTechniqueCommon(xml, data) { - case 'link': - data.links.push(parseKinematicsLink(child)); - break; + for (var i = 0; i < xml.childNodes.length; i++) { - } - } - } + var child = xml.childNodes[i]; - function parseKinematicsJoint(xml) { + if (child.nodeType !== 1) continue; - var data; + switch (child.nodeName) { - for (var i = 0; i < xml.childNodes.length; i++) { + case 'joint': + data.joints[child.getAttribute('sid')] = parseKinematicsJoint(child); + break; - var child = xml.childNodes[i]; + case 'link': + data.links.push(parseKinematicsLink(child)); + break; - if (child.nodeType !== 1) continue; + } + } + } - switch (child.nodeName) { + function parseKinematicsJoint(xml) { - case 'prismatic': - case 'revolute': - data = parseKinematicsJointParameter(child); - break; + var data; - } - } + for (var i = 0; i < xml.childNodes.length; i++) { - return data; - } + var child = xml.childNodes[i]; - function parseKinematicsJointParameter(xml, data) { + if (child.nodeType !== 1) continue; - var data = { - sid: xml.getAttribute('sid'), - name: xml.getAttribute('name') || '', - axis: new THREE.Vector3(), - limits: { - min: 0, - max: 0 - }, - type: xml.nodeName, - static: false, - zeroPosition: 0, - middlePosition: 0 - }; + switch (child.nodeName) { - for (var i = 0; i < xml.childNodes.length; i++) { + case 'prismatic': + case 'revolute': + data = parseKinematicsJointParameter(child); + break; - var child = xml.childNodes[i]; + } + } - if (child.nodeType !== 1) continue; + return data; + } - switch (child.nodeName) { + function parseKinematicsJointParameter(xml, data) { - case 'axis': - var array = parseFloats(child.textContent); - data.axis.fromArray(array); - break; - case 'limits': - var max = child.getElementsByTagName('max')[0]; - var min = child.getElementsByTagName('min')[0]; + var data = { + sid: xml.getAttribute('sid'), + name: xml.getAttribute('name') || '', + axis: new THREE.Vector3(), + limits: { + min: 0, + max: 0 + }, + type: xml.nodeName, + static: false, + zeroPosition: 0, + middlePosition: 0 + }; - data.limits.max = parseFloat(max.textContent); - data.limits.min = parseFloat(min.textContent); - break; + for (var i = 0; i < xml.childNodes.length; i++) { - } - } + var child = xml.childNodes[i]; - // if min is equal to or greater than max, consider the joint static + if (child.nodeType !== 1) continue; - if (data.limits.min >= data.limits.max) { + switch (child.nodeName) { - data.static = true; - } + case 'axis': + var array = parseFloats(child.textContent); + data.axis.fromArray(array); + break; + case 'limits': + var max = child.getElementsByTagName('max')[0]; + var min = child.getElementsByTagName('min')[0]; - // calculate middle position + data.limits.max = parseFloat(max.textContent); + data.limits.min = parseFloat(min.textContent); + break; - data.middlePosition = (data.limits.min + data.limits.max) / 2.0; + } + } - return data; - } + // if min is equal to or greater than max, consider the joint static - function parseKinematicsLink(xml) { + if (data.limits.min >= data.limits.max) { - var data = { - sid: xml.getAttribute('sid'), - name: xml.getAttribute('name') || '', - attachments: [], - transforms: [] - }; + data.static = true; + } - for (var i = 0; i < xml.childNodes.length; i++) { + // calculate middle position - var child = xml.childNodes[i]; + data.middlePosition = (data.limits.min + data.limits.max) / 2.0; - if (child.nodeType !== 1) continue; + return data; + } - switch (child.nodeName) { + function parseKinematicsLink(xml) { - case 'attachment_full': - data.attachments.push(parseKinematicsAttachment(child)); - break; + var data = { + sid: xml.getAttribute('sid'), + name: xml.getAttribute('name') || '', + attachments: [], + transforms: [] + }; - case 'matrix': - case 'translate': - case 'rotate': - data.transforms.push(parseKinematicsTransform(child)); - break; + for (var i = 0; i < xml.childNodes.length; i++) { - } - } + var child = xml.childNodes[i]; - return data; - } + if (child.nodeType !== 1) continue; - function parseKinematicsAttachment(xml) { + switch (child.nodeName) { - var data = { - joint: xml.getAttribute('joint').split('/').pop(), - transforms: [], - links: [] - }; + case 'attachment_full': + data.attachments.push(parseKinematicsAttachment(child)); + break; - for (var i = 0; i < xml.childNodes.length; i++) { + case 'matrix': + case 'translate': + case 'rotate': + data.transforms.push(parseKinematicsTransform(child)); + break; - var child = xml.childNodes[i]; + } + } - if (child.nodeType !== 1) continue; + return data; + } - switch (child.nodeName) { + function parseKinematicsAttachment(xml) { - case 'link': - data.links.push(parseKinematicsLink(child)); - break; + var data = { + joint: xml.getAttribute('joint').split('/').pop(), + transforms: [], + links: [] + }; - case 'matrix': - case 'translate': - case 'rotate': - data.transforms.push(parseKinematicsTransform(child)); - break; + for (var i = 0; i < xml.childNodes.length; i++) { - } - } + var child = xml.childNodes[i]; - return data; - } + if (child.nodeType !== 1) continue; - function parseKinematicsTransform(xml) { + switch (child.nodeName) { - var data = { - type: xml.nodeName - }; + case 'link': + data.links.push(parseKinematicsLink(child)); + break; - var array = parseFloats(xml.textContent); + case 'matrix': + case 'translate': + case 'rotate': + data.transforms.push(parseKinematicsTransform(child)); + break; - switch (data.type) { + } + } - case 'matrix': - data.obj = new THREE.Matrix4(); - data.obj.fromArray(array).transpose(); - break; + return data; + } - case 'translate': - data.obj = new THREE.Vector3(); - data.obj.fromArray(array); - break; + function parseKinematicsTransform(xml) { - case 'rotate': - data.obj = new THREE.Vector3(); - data.obj.fromArray(array); - data.angle = THREE.MathUtils.degToRad(array[3]); - break; + var data = { + type: xml.nodeName + }; - } + var array = parseFloats(xml.textContent); - return data; - } + switch (data.type) { - // physics + case 'matrix': + data.obj = new THREE.Matrix4(); + data.obj.fromArray(array).transpose(); + break; - function parsePhysicsModel(xml) { + case 'translate': + data.obj = new THREE.Vector3(); + data.obj.fromArray(array); + break; - var data = { - name: xml.getAttribute('name') || '', - rigidBodies: {} - }; + case 'rotate': + data.obj = new THREE.Vector3(); + data.obj.fromArray(array); + data.angle = THREE.MathUtils.degToRad(array[3]); + break; - for (var i = 0; i < xml.childNodes.length; i++) { + } - var child = xml.childNodes[i]; + return data; + } - if (child.nodeType !== 1) continue; + // physics - switch (child.nodeName) { + function parsePhysicsModel(xml) { - case 'rigid_body': - data.rigidBodies[child.getAttribute('name')] = {}; - parsePhysicsRigidBody(child, data.rigidBodies[child.getAttribute('name')]); - break; + var data = { + name: xml.getAttribute('name') || '', + rigidBodies: {} + }; - } - } + for (var i = 0; i < xml.childNodes.length; i++) { - library.physicsModels[xml.getAttribute('id')] = data; - } + var child = xml.childNodes[i]; - function parsePhysicsRigidBody(xml, data) { + if (child.nodeType !== 1) continue; - for (var i = 0; i < xml.childNodes.length; i++) { + switch (child.nodeName) { - var child = xml.childNodes[i]; + case 'rigid_body': + data.rigidBodies[child.getAttribute('name')] = {}; + parsePhysicsRigidBody(child, data.rigidBodies[child.getAttribute('name')]); + break; - if (child.nodeType !== 1) continue; + } + } - switch (child.nodeName) { + library.physicsModels[xml.getAttribute('id')] = data; + } - case 'technique_common': - parsePhysicsTechniqueCommon(child, data); - break; + function parsePhysicsRigidBody(xml, data) { - } - } - } + for (var i = 0; i < xml.childNodes.length; i++) { - function parsePhysicsTechniqueCommon(xml, data) { + var child = xml.childNodes[i]; - for (var i = 0; i < xml.childNodes.length; i++) { + if (child.nodeType !== 1) continue; - var child = xml.childNodes[i]; + switch (child.nodeName) { - if (child.nodeType !== 1) continue; + case 'technique_common': + parsePhysicsTechniqueCommon(child, data); + break; - switch (child.nodeName) { + } + } + } - case 'inertia': - data.inertia = parseFloats(child.textContent); - break; + function parsePhysicsTechniqueCommon(xml, data) { - case 'mass': - data.mass = parseFloats(child.textContent)[0]; - break; + for (var i = 0; i < xml.childNodes.length; i++) { - } - } - } + var child = xml.childNodes[i]; - // scene + if (child.nodeType !== 1) continue; - function parseKinematicsScene(xml) { + switch (child.nodeName) { - var data = { - bindJointAxis: [] - }; + case 'inertia': + data.inertia = parseFloats(child.textContent); + break; - for (var i = 0; i < xml.childNodes.length; i++) { + case 'mass': + data.mass = parseFloats(child.textContent)[0]; + break; - var child = xml.childNodes[i]; + } + } + } - if (child.nodeType !== 1) continue; + // scene - switch (child.nodeName) { + function parseKinematicsScene(xml) { - case 'bind_joint_axis': - data.bindJointAxis.push(parseKinematicsBindJointAxis(child)); - break; + var data = { + bindJointAxis: [] + }; - } - } + for (var i = 0; i < xml.childNodes.length; i++) { - library.kinematicsScenes[parseId(xml.getAttribute('url'))] = data; - } + var child = xml.childNodes[i]; - function parseKinematicsBindJointAxis(xml) { + if (child.nodeType !== 1) continue; - var data = { - target: xml.getAttribute('target').split('/').pop() - }; + switch (child.nodeName) { - for (var i = 0; i < xml.childNodes.length; i++) { + case 'bind_joint_axis': + data.bindJointAxis.push(parseKinematicsBindJointAxis(child)); + break; - var child = xml.childNodes[i]; + } + } - if (child.nodeType !== 1) continue; + library.kinematicsScenes[parseId(xml.getAttribute('url'))] = data; + } - switch (child.nodeName) { + function parseKinematicsBindJointAxis(xml) { - case 'axis': - var param = child.getElementsByTagName('param')[0]; - data.axis = param.textContent; - var tmpJointIndex = data.axis.split('inst_').pop().split('axis')[0]; - data.jointIndex = tmpJointIndex.substr(0, tmpJointIndex.length - 1); - break; + var data = { + target: xml.getAttribute('target').split('/').pop() + }; - } - } + for (var i = 0; i < xml.childNodes.length; i++) { - return data; - } + var child = xml.childNodes[i]; - function buildKinematicsScene(data) { + if (child.nodeType !== 1) continue; - if (data.build !== undefined) return data.build; + switch (child.nodeName) { - return data; - } + case 'axis': + var param = child.getElementsByTagName('param')[0]; + data.axis = param.textContent; + var tmpJointIndex = data.axis.split('inst_').pop().split('axis')[0]; + data.jointIndex = tmpJointIndex.substr(0, tmpJointIndex.length - 1); + break; - function getKinematicsScene(id) { + } + } - return getBuild(library.kinematicsScenes[id], buildKinematicsScene); - } + return data; + } - function setupKinematics() { + function buildKinematicsScene(data) { - var kinematicsModelId = Object.keys(library.kinematicsModels)[0]; - var kinematicsSceneId = Object.keys(library.kinematicsScenes)[0]; - var visualSceneId = Object.keys(library.visualScenes)[0]; + if (data.build !== undefined) return data.build; - if (kinematicsModelId === undefined || kinematicsSceneId === undefined) return; + return data; + } - var kinematicsModel = getKinematicsModel(kinematicsModelId); - var kinematicsScene = getKinematicsScene(kinematicsSceneId); - var visualScene = getVisualScene(visualSceneId); + function getKinematicsScene(id) { - var bindJointAxis = kinematicsScene.bindJointAxis; - var jointMap = {}; + return getBuild(library.kinematicsScenes[id], buildKinematicsScene); + } - for (var i = 0, l = bindJointAxis.length; i < l; i++) { + function setupKinematics() { - var axis = bindJointAxis[i]; + var kinematicsModelId = Object.keys(library.kinematicsModels)[0]; + var kinematicsSceneId = Object.keys(library.kinematicsScenes)[0]; + var visualSceneId = Object.keys(library.visualScenes)[0]; - // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix' + if (kinematicsModelId === undefined || kinematicsSceneId === undefined) return; - var targetElement = collada.querySelector('[sid="' + axis.target + '"]'); + var kinematicsModel = getKinematicsModel(kinematicsModelId); + var kinematicsScene = getKinematicsScene(kinematicsSceneId); + var visualScene = getVisualScene(visualSceneId); - if (targetElement) { + var bindJointAxis = kinematicsScene.bindJointAxis; + var jointMap = {}; - // get the parent of the transfrom element + for (var i = 0, l = bindJointAxis.length; i < l; i++) { - var parentVisualElement = targetElement.parentElement; + var axis = bindJointAxis[i]; - // connect the joint of the kinematics model with the element in the visual scene + // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix' - connect(axis.jointIndex, parentVisualElement); - } - } + var targetElement = collada.querySelector('[sid="' + axis.target + '"]'); - function connect(jointIndex, visualElement) { + if (targetElement) { - var visualElementName = visualElement.getAttribute('name'); - var joint = kinematicsModel.joints[jointIndex]; + // get the parent of the transfrom element - visualScene.traverse(function (object) { + var parentVisualElement = targetElement.parentElement; - if (object.name === visualElementName) { + // connect the joint of the kinematics model with the element in the visual scene - jointMap[jointIndex] = { - object: object, - transforms: buildTransformList(visualElement), - joint: joint, - position: joint.zeroPosition - }; - } - }); - } + connect(axis.jointIndex, parentVisualElement); + } + } - var m0 = new THREE.Matrix4(); + function connect(jointIndex, visualElement) { - kinematics = { + var visualElementName = visualElement.getAttribute('name'); + var joint = kinematicsModel.joints[jointIndex]; - joints: kinematicsModel && kinematicsModel.joints, + visualScene.traverse(function (object) { - getJointValue: function getJointValue(jointIndex) { + if (object.name === visualElementName) { - var jointData = jointMap[jointIndex]; + jointMap[jointIndex] = { + object: object, + transforms: buildTransformList(visualElement), + joint: joint, + position: joint.zeroPosition + }; + } + }); + } - if (jointData) { + var m0 = new THREE.Matrix4(); - return jointData.position; - } else { + kinematics = { - console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.'); - } - }, + joints: kinematicsModel && kinematicsModel.joints, - setJointValue: function setJointValue(jointIndex, value) { + getJointValue: function getJointValue(jointIndex) { - var jointData = jointMap[jointIndex]; + var jointData = jointMap[jointIndex]; - if (jointData) { + if (jointData) { - var joint = jointData.joint; + return jointData.position; + } else { - if (value > joint.limits.max || value < joint.limits.min) { + console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.'); + } + }, - console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').'); - } else if (joint.static) { + setJointValue: function setJointValue(jointIndex, value) { - console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' is static.'); - } else { + var jointData = jointMap[jointIndex]; - var object = jointData.object; - var axis = joint.axis; - var transforms = jointData.transforms; + if (jointData) { - matrix.identity(); + var joint = jointData.joint; - // each update, we have to apply all transforms in the correct order + if (value > joint.limits.max || value < joint.limits.min) { - for (var i = 0; i < transforms.length; i++) { + console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').'); + } else if (joint.static) { - var transform = transforms[i]; + console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' is static.'); + } else { - // if there is a connection of the transform node with a joint, apply the joint value + var object = jointData.object; + var axis = joint.axis; + var transforms = jointData.transforms; - if (transform.sid && transform.sid.indexOf(jointIndex) !== -1) { + matrix.identity(); - switch (joint.type) { + // each update, we have to apply all transforms in the correct order - case 'revolute': - matrix.multiply(m0.makeRotationAxis(axis, THREE.MathUtils.degToRad(value))); - break; + for (var i = 0; i < transforms.length; i++) { - case 'prismatic': - matrix.multiply(m0.makeTranslation(axis.x * value, axis.y * value, axis.z * value)); - break; + var transform = transforms[i]; - default: - console.warn('THREE.ColladaLoader: Unknown joint type: ' + joint.type); - break; + // if there is a connection of the transform node with a joint, apply the joint value - } - } else { + if (transform.sid && transform.sid.indexOf(jointIndex) !== -1) { - switch (transform.type) { + switch (joint.type) { - case 'matrix': - matrix.multiply(transform.obj); - break; + case 'revolute': + matrix.multiply(m0.makeRotationAxis(axis, THREE.MathUtils.degToRad(value))); + break; - case 'translate': - matrix.multiply(m0.makeTranslation(transform.obj.x, transform.obj.y, transform.obj.z)); - break; + case 'prismatic': + matrix.multiply(m0.makeTranslation(axis.x * value, axis.y * value, axis.z * value)); + break; - case 'scale': - matrix.scale(transform.obj); - break; + default: + console.warn('THREE.ColladaLoader: Unknown joint type: ' + joint.type); + break; - case 'rotate': - matrix.multiply(m0.makeRotationAxis(transform.obj, transform.angle)); - break; + } + } else { - } - } - } + switch (transform.type) { - object.matrix.copy(matrix); - object.matrix.decompose(object.position, object.quaternion, object.scale); + case 'matrix': + matrix.multiply(transform.obj); + break; - jointMap[jointIndex].position = value; - } - } else { + case 'translate': + matrix.multiply(m0.makeTranslation(transform.obj.x, transform.obj.y, transform.obj.z)); + break; - console.log('THREE.ColladaLoader: ' + jointIndex + ' does not exist.'); - } - } + case 'scale': + matrix.scale(transform.obj); + break; - }; - } + case 'rotate': + matrix.multiply(m0.makeRotationAxis(transform.obj, transform.angle)); + break; - function buildTransformList(node) { - - var transforms = []; - - var xml = collada.querySelector('[id="' + node.id + '"]'); - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'matrix': - var array = parseFloats(child.textContent); - var matrix = new THREE.Matrix4().fromArray(array).transpose(); - transforms.push({ - sid: child.getAttribute('sid'), - type: child.nodeName, - obj: matrix - }); - break; - - case 'translate': - case 'scale': - var array = parseFloats(child.textContent); - var vector = new THREE.Vector3().fromArray(array); - transforms.push({ - sid: child.getAttribute('sid'), - type: child.nodeName, - obj: vector - }); - break; - - case 'rotate': - var array = parseFloats(child.textContent); - var vector = new THREE.Vector3().fromArray(array); - var angle = THREE.MathUtils.degToRad(array[3]); - transforms.push({ - sid: child.getAttribute('sid'), - type: child.nodeName, - obj: vector, - angle: angle - }); - break; - - } } + } + } + + object.matrix.copy(matrix); + object.matrix.decompose(object.position, object.quaternion, object.scale); - return transforms; + jointMap[jointIndex].position = value; } + } else { - // nodes + console.log('THREE.ColladaLoader: ' + jointIndex + ' does not exist.'); + } + } - function prepareNodes(xml) { + }; + } - var elements = xml.getElementsByTagName('node'); + function buildTransformList(node) { + + var transforms = []; + + var xml = collada.querySelector('[id="' + node.id + '"]'); + + for (var i = 0; i < xml.childNodes.length; i++) { + + var child = xml.childNodes[i]; + + if (child.nodeType !== 1) continue; + + switch (child.nodeName) { + + case 'matrix': + var array = parseFloats(child.textContent); + var matrix = new THREE.Matrix4().fromArray(array).transpose(); + transforms.push({ + sid: child.getAttribute('sid'), + type: child.nodeName, + obj: matrix + }); + break; + + case 'translate': + case 'scale': + var array = parseFloats(child.textContent); + var vector = new THREE.Vector3().fromArray(array); + transforms.push({ + sid: child.getAttribute('sid'), + type: child.nodeName, + obj: vector + }); + break; + + case 'rotate': + var array = parseFloats(child.textContent); + var vector = new THREE.Vector3().fromArray(array); + var angle = THREE.MathUtils.degToRad(array[3]); + transforms.push({ + sid: child.getAttribute('sid'), + type: child.nodeName, + obj: vector, + angle: angle + }); + break; - // ensure all node elements have id attributes + } + } - for (var i = 0; i < elements.length; i++) { + return transforms; + } - var element = elements[i]; + // nodes - if (element.hasAttribute('id') === false) { + function prepareNodes(xml) { - element.setAttribute('id', generateId()); - } - } - } + var elements = xml.getElementsByTagName('node'); - var matrix = new THREE.Matrix4(); - var vector = new THREE.Vector3(); - - function parseNode(xml) { - - var data = { - name: xml.getAttribute('name') || '', - type: xml.getAttribute('type'), - id: xml.getAttribute('id'), - sid: xml.getAttribute('sid'), - matrix: new THREE.Matrix4(), - nodes: [], - instanceCameras: [], - instanceControllers: [], - instanceLights: [], - instanceGeometries: [], - instanceNodes: [], - transforms: {} - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'node': - data.nodes.push(child.getAttribute('id')); - parseNode(child); - break; - - case 'instance_camera': - data.instanceCameras.push(parseId(child.getAttribute('url'))); - break; - - case 'instance_controller': - data.instanceControllers.push(parseNodeInstance(child)); - break; - - case 'instance_light': - data.instanceLights.push(parseId(child.getAttribute('url'))); - break; - - case 'instance_geometry': - data.instanceGeometries.push(parseNodeInstance(child)); - break; - - case 'instance_node': - data.instanceNodes.push(parseId(child.getAttribute('url'))); - break; - - case 'matrix': - var array = parseFloats(child.textContent); - data.matrix.multiply(matrix.fromArray(array).transpose()); - data.transforms[child.getAttribute('sid')] = child.nodeName; - break; - - case 'translate': - var array = parseFloats(child.textContent); - vector.fromArray(array); - data.matrix.multiply(matrix.makeTranslation(vector.x, vector.y, vector.z)); - data.transforms[child.getAttribute('sid')] = child.nodeName; - break; - - case 'rotate': - var array = parseFloats(child.textContent); - var angle = THREE.MathUtils.degToRad(array[3]); - data.matrix.multiply(matrix.makeRotationAxis(vector.fromArray(array), angle)); - data.transforms[child.getAttribute('sid')] = child.nodeName; - break; - - case 'scale': - var array = parseFloats(child.textContent); - data.matrix.scale(vector.fromArray(array)); - data.transforms[child.getAttribute('sid')] = child.nodeName; - break; - - case 'extra': - break; - - default: - console.log(child); - - } - } + // ensure all node elements have id attributes - if (hasNode(data.id)) { + for (var i = 0; i < elements.length; i++) { - console.warn('THREE.ColladaLoader: There is already a node with ID %s. Exclude current node from further processing.', data.id); - } else { + var element = elements[i]; - library.nodes[data.id] = data; - } + if (element.hasAttribute('id') === false) { - return data; - } + element.setAttribute('id', generateId()); + } + } + } - function parseNodeInstance(xml) { + var matrix = new THREE.Matrix4(); + var vector = new THREE.Vector3(); + + function parseNode(xml) { + + var data = { + name: xml.getAttribute('name') || '', + type: xml.getAttribute('type'), + id: xml.getAttribute('id'), + sid: xml.getAttribute('sid'), + matrix: new THREE.Matrix4(), + nodes: [], + instanceCameras: [], + instanceControllers: [], + instanceLights: [], + instanceGeometries: [], + instanceNodes: [], + transforms: {} + }; + + for (var i = 0; i < xml.childNodes.length; i++) { + + var child = xml.childNodes[i]; + + if (child.nodeType !== 1) continue; + + switch (child.nodeName) { + + case 'node': + data.nodes.push(child.getAttribute('id')); + parseNode(child); + break; + + case 'instance_camera': + data.instanceCameras.push(parseId(child.getAttribute('url'))); + break; + + case 'instance_controller': + data.instanceControllers.push(parseNodeInstance(child)); + break; + + case 'instance_light': + data.instanceLights.push(parseId(child.getAttribute('url'))); + break; + + case 'instance_geometry': + data.instanceGeometries.push(parseNodeInstance(child)); + break; + + case 'instance_node': + data.instanceNodes.push(parseId(child.getAttribute('url'))); + break; + + case 'matrix': + var array = parseFloats(child.textContent); + data.matrix.multiply(matrix.fromArray(array).transpose()); + data.transforms[child.getAttribute('sid')] = child.nodeName; + break; + + case 'translate': + var array = parseFloats(child.textContent); + vector.fromArray(array); + data.matrix.multiply(matrix.makeTranslation(vector.x, vector.y, vector.z)); + data.transforms[child.getAttribute('sid')] = child.nodeName; + break; + + case 'rotate': + var array = parseFloats(child.textContent); + var angle = THREE.MathUtils.degToRad(array[3]); + data.matrix.multiply(matrix.makeRotationAxis(vector.fromArray(array), angle)); + data.transforms[child.getAttribute('sid')] = child.nodeName; + break; + + case 'scale': + var array = parseFloats(child.textContent); + data.matrix.scale(vector.fromArray(array)); + data.transforms[child.getAttribute('sid')] = child.nodeName; + break; + + case 'extra': + break; + + default: + console.log(child); - var data = { - id: parseId(xml.getAttribute('url')), - materials: {}, - skeletons: [] - }; + } + } - for (var i = 0; i < xml.childNodes.length; i++) { + if (hasNode(data.id)) { - var child = xml.childNodes[i]; + console.warn('THREE.ColladaLoader: There is already a node with ID %s. Exclude current node from further processing.', data.id); + } else { - switch (child.nodeName) { + library.nodes[data.id] = data; + } - case 'bind_material': - var instances = child.getElementsByTagName('instance_material'); + return data; + } - for (var j = 0; j < instances.length; j++) { + function parseNodeInstance(xml) { - var instance = instances[j]; - var symbol = instance.getAttribute('symbol'); - var target = instance.getAttribute('target'); + var data = { + id: parseId(xml.getAttribute('url')), + materials: {}, + skeletons: [] + }; - data.materials[symbol] = parseId(target); - } + for (var i = 0; i < xml.childNodes.length; i++) { - break; + var child = xml.childNodes[i]; - case 'skeleton': - data.skeletons.push(parseId(child.textContent)); - break; + switch (child.nodeName) { - default: - break; + case 'bind_material': + var instances = child.getElementsByTagName('instance_material'); - } - } + for (var j = 0; j < instances.length; j++) { - return data; + var instance = instances[j]; + var symbol = instance.getAttribute('symbol'); + var target = instance.getAttribute('target'); + + data.materials[symbol] = parseId(target); } - function buildSkeleton(skeletons, joints) { + break; - var boneData = []; - var sortedBoneData = []; + case 'skeleton': + data.skeletons.push(parseId(child.textContent)); + break; - var i, j, data; + default: + break; - // a skeleton can have multiple root bones. collada expresses this - // situtation with multiple "skeleton" tags per controller instance + } + } - for (i = 0; i < skeletons.length; i++) { + return data; + } - var skeleton = skeletons[i]; + function buildSkeleton(skeletons, joints) { - var root; + var boneData = []; + var sortedBoneData = []; - if (hasNode(skeleton)) { + var i, j, data; - root = getNode(skeleton); - buildBoneHierarchy(root, joints, boneData); - } else if (hasVisualScene(skeleton)) { + // a skeleton can have multiple root bones. collada expresses this + // situtation with multiple "skeleton" tags per controller instance - // handle case where the skeleton refers to the visual scene (#13335) + for (i = 0; i < skeletons.length; i++) { - var visualScene = library.visualScenes[skeleton]; - var children = visualScene.children; + var skeleton = skeletons[i]; - for (var j = 0; j < children.length; j++) { + var root; - var child = children[j]; + if (hasNode(skeleton)) { - if (child.type === 'JOINT') { + root = getNode(skeleton); + buildBoneHierarchy(root, joints, boneData); + } else if (hasVisualScene(skeleton)) { - var root = getNode(child.id); - buildBoneHierarchy(root, joints, boneData); - } - } - } else { + // handle case where the skeleton refers to the visual scene (#13335) - console.error('THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton); - } - } + var visualScene = library.visualScenes[skeleton]; + var children = visualScene.children; - // sort bone data (the order is defined in the corresponding controller) + for (var j = 0; j < children.length; j++) { - for (i = 0; i < joints.length; i++) { + var child = children[j]; - for (j = 0; j < boneData.length; j++) { + if (child.type === 'JOINT') { - data = boneData[j]; + var root = getNode(child.id); + buildBoneHierarchy(root, joints, boneData); + } + } + } else { - if (data.bone.name === joints[i].name) { + console.error('THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton); + } + } - sortedBoneData[i] = data; - data.processed = true; - break; - } - } - } + // sort bone data (the order is defined in the corresponding controller) - // add unprocessed bone data at the end of the list + for (i = 0; i < joints.length; i++) { - for (i = 0; i < boneData.length; i++) { + for (j = 0; j < boneData.length; j++) { - data = boneData[i]; + data = boneData[j]; - if (data.processed === false) { + if (data.bone.name === joints[i].name) { - sortedBoneData.push(data); - data.processed = true; - } - } + sortedBoneData[i] = data; + data.processed = true; + break; + } + } + } - // setup arrays for skeleton creation + // add unprocessed bone data at the end of the list - var bones = []; - var boneInverses = []; + for (i = 0; i < boneData.length; i++) { - for (i = 0; i < sortedBoneData.length; i++) { + data = boneData[i]; - data = sortedBoneData[i]; + if (data.processed === false) { - bones.push(data.bone); - boneInverses.push(data.boneInverse); - } + sortedBoneData.push(data); + data.processed = true; + } + } - return new THREE.Skeleton(bones, boneInverses); - } + // setup arrays for skeleton creation + + var bones = []; + var boneInverses = []; - function buildBoneHierarchy(root, joints, boneData) { + for (i = 0; i < sortedBoneData.length; i++) { - // setup bone data from visual scene + data = sortedBoneData[i]; - root.traverse(function (object) { + bones.push(data.bone); + boneInverses.push(data.boneInverse); + } - if (object.isBone === true) { + return new THREE.Skeleton(bones, boneInverses); + } - var boneInverse; + function buildBoneHierarchy(root, joints, boneData) { - // retrieve the boneInverse from the controller data + // setup bone data from visual scene - for (var i = 0; i < joints.length; i++) { + root.traverse(function (object) { - var joint = joints[i]; + if (object.isBone === true) { - if (joint.name === object.name) { + var boneInverse; - boneInverse = joint.boneInverse; - break; - } - } + // retrieve the boneInverse from the controller data - if (boneInverse === undefined) { + for (var i = 0; i < joints.length; i++) { - // Unfortunately, there can be joints in the visual scene that are not part of the - // corresponding controller. In this case, we have to create a dummy boneInverse matrix - // for the respective bone. This bone won't affect any vertices, because there are no skin indices - // and weights defined for it. But we still have to add the bone to the sorted bone list in order to - // ensure a correct animation of the model. + var joint = joints[i]; - boneInverse = new THREE.Matrix4(); - } + if (joint.name === object.name) { - boneData.push({ bone: object, boneInverse: boneInverse, processed: false }); - } - }); + boneInverse = joint.boneInverse; + break; } + } - function buildNode(data) { + if (boneInverse === undefined) { - var objects = []; + // Unfortunately, there can be joints in the visual scene that are not part of the + // corresponding controller. In this case, we have to create a dummy boneInverse matrix + // for the respective bone. This bone won't affect any vertices, because there are no skin indices + // and weights defined for it. But we still have to add the bone to the sorted bone list in order to + // ensure a correct animation of the model. - var matrix = data.matrix; - var nodes = data.nodes; - var type = data.type; - var instanceCameras = data.instanceCameras; - var instanceControllers = data.instanceControllers; - var instanceLights = data.instanceLights; - var instanceGeometries = data.instanceGeometries; - var instanceNodes = data.instanceNodes; + boneInverse = new THREE.Matrix4(); + } - // nodes + boneData.push({ bone: object, boneInverse: boneInverse, processed: false }); + } + }); + } - for (var i = 0, l = nodes.length; i < l; i++) { + function buildNode(data) { - objects.push(getNode(nodes[i])); - } + var objects = []; - // instance cameras + var matrix = data.matrix; + var nodes = data.nodes; + var type = data.type; + var instanceCameras = data.instanceCameras; + var instanceControllers = data.instanceControllers; + var instanceLights = data.instanceLights; + var instanceGeometries = data.instanceGeometries; + var instanceNodes = data.instanceNodes; - for (var i = 0, l = instanceCameras.length; i < l; i++) { + // nodes - var instanceCamera = getCamera(instanceCameras[i]); + for (var i = 0, l = nodes.length; i < l; i++) { - if (instanceCamera !== null) { + objects.push(getNode(nodes[i])); + } - objects.push(instanceCamera.clone()); - } - } + // instance cameras - // instance controllers + for (var i = 0, l = instanceCameras.length; i < l; i++) { - for (var i = 0, l = instanceControllers.length; i < l; i++) { + var instanceCamera = getCamera(instanceCameras[i]); - var instance = instanceControllers[i]; - var controller = getController(instance.id); - var geometries = getGeometry(controller.id); - var newObjects = buildObjects(geometries, instance.materials); + if (instanceCamera !== null) { - var skeletons = instance.skeletons; - var joints = controller.skin.joints; + objects.push(instanceCamera.clone()); + } + } - var skeleton = buildSkeleton(skeletons, joints); + // instance controllers - for (var j = 0, jl = newObjects.length; j < jl; j++) { + for (var i = 0, l = instanceControllers.length; i < l; i++) { - var object = newObjects[j]; + var instance = instanceControllers[i]; + var controller = getController(instance.id); + var geometries = getGeometry(controller.id); + var newObjects = buildObjects(geometries, instance.materials); - if (object.isSkinnedMesh) { + var skeletons = instance.skeletons; + var joints = controller.skin.joints; - object.bind(skeleton, controller.skin.bindMatrix); - object.normalizeSkinWeights(); - } + var skeleton = buildSkeleton(skeletons, joints); - objects.push(object); - } - } + for (var j = 0, jl = newObjects.length; j < jl; j++) { - // instance lights + var object = newObjects[j]; - for (var i = 0, l = instanceLights.length; i < l; i++) { + if (object.isSkinnedMesh) { - var instanceLight = getLight(instanceLights[i]); + object.bind(skeleton, controller.skin.bindMatrix); + object.normalizeSkinWeights(); + } - if (instanceLight !== null) { + objects.push(object); + } + } - objects.push(instanceLight.clone()); - } - } + // instance lights - // instance geometries + for (var i = 0, l = instanceLights.length; i < l; i++) { - for (var i = 0, l = instanceGeometries.length; i < l; i++) { + var instanceLight = getLight(instanceLights[i]); - var instance = instanceGeometries[i]; + if (instanceLight !== null) { - // a single geometry instance in collada can lead to multiple object3Ds. - // this is the case when primitives are combined like triangles and lines + objects.push(instanceLight.clone()); + } + } - var geometries = getGeometry(instance.id); - var newObjects = buildObjects(geometries, instance.materials); + // instance geometries - for (var j = 0, jl = newObjects.length; j < jl; j++) { + for (var i = 0, l = instanceGeometries.length; i < l; i++) { - objects.push(newObjects[j]); - } - } + var instance = instanceGeometries[i]; - // instance nodes + // a single geometry instance in collada can lead to multiple object3Ds. + // this is the case when primitives are combined like triangles and lines - for (var i = 0, l = instanceNodes.length; i < l; i++) { + var geometries = getGeometry(instance.id); + var newObjects = buildObjects(geometries, instance.materials); - objects.push(getNode(instanceNodes[i]).clone()); - } + for (var j = 0, jl = newObjects.length; j < jl; j++) { - var object; + objects.push(newObjects[j]); + } + } - if (nodes.length === 0 && objects.length === 1) { + // instance nodes - object = objects[0]; - } else { + for (var i = 0, l = instanceNodes.length; i < l; i++) { - object = type === 'JOINT' ? new THREE.Bone() : new THREE.Group(); + objects.push(getNode(instanceNodes[i]).clone()); + } - for (var i = 0; i < objects.length; i++) { + var object; - object.add(objects[i]); - } - } + if (nodes.length === 0 && objects.length === 1) { - if (object.name === '') { + object = objects[0]; + } else { - object.name = type === 'JOINT' ? data.sid : data.name; - } + object = type === 'JOINT' ? new THREE.Bone() : new THREE.Group(); - object.matrix.copy(matrix); - object.matrix.decompose(object.position, object.quaternion, object.scale); + for (var i = 0; i < objects.length; i++) { - return object; - } + object.add(objects[i]); + } + } - var fallbackMaterial = new THREE.MeshBasicMaterial({ color: 0xff00ff }); + if (object.name === '') { - function resolveMaterialBinding(keys, instanceMaterials) { + object.name = type === 'JOINT' ? data.sid : data.name; + } - var materials = []; + object.matrix.copy(matrix); + object.matrix.decompose(object.position, object.quaternion, object.scale); - for (var i = 0, l = keys.length; i < l; i++) { + return object; + } - var id = instanceMaterials[keys[i]]; + var fallbackMaterial = new THREE.MeshBasicMaterial({ color: 0xff00ff }); - if (id === undefined) { + function resolveMaterialBinding(keys, instanceMaterials) { - console.warn('THREE.ColladaLoader: Material with key %s not found. Apply fallback material.', keys[i]); - materials.push(fallbackMaterial); - } else { + var materials = []; - materials.push(getMaterial(id)); - } - } + for (var i = 0, l = keys.length; i < l; i++) { - return materials; - } + var id = instanceMaterials[keys[i]]; + + if (id === undefined) { - function buildObjects(geometries, instanceMaterials) { + console.warn('THREE.ColladaLoader: Material with key %s not found. Apply fallback material.', keys[i]); + materials.push(fallbackMaterial); + } else { - var objects = []; + materials.push(getMaterial(id)); + } + } - for (var type in geometries) { + return materials; + } - var geometry = geometries[type]; + function buildObjects(geometries, instanceMaterials) { - var materials = resolveMaterialBinding(geometry.materialKeys, instanceMaterials); + var objects = []; - // handle case if no materials are defined + for (var type in geometries) { - if (materials.length === 0) { + var geometry = geometries[type]; - if (type === 'lines' || type === 'linestrips') { + var materials = resolveMaterialBinding(geometry.materialKeys, instanceMaterials); - materials.push(new THREE.LineBasicMaterial()); - } else { + // handle case if no materials are defined - materials.push(new THREE.MeshPhongMaterial()); - } - } + if (materials.length === 0) { - // regard skinning + if (type === 'lines' || type === 'linestrips') { - var skinning = geometry.data.attributes.skinIndex !== undefined; + materials.push(new THREE.LineBasicMaterial()); + } else { - if (skinning) { + materials.push(new THREE.MeshPhongMaterial()); + } + } - for (var i = 0, l = materials.length; i < l; i++) { + // regard skinning - materials[i].skinning = true; - } - } + var skinning = geometry.data.attributes.skinIndex !== undefined; - // choose between a single or multi materials (material array) + if (skinning) { - var material = materials.length === 1 ? materials[0] : materials; + for (var i = 0, l = materials.length; i < l; i++) { - // now create a specific 3D object + materials[i].skinning = true; + } + } - var object; + // choose between a single or multi materials (material array) - switch (type) { + var material = materials.length === 1 ? materials[0] : materials; - case 'lines': - object = new THREE.LineSegments(geometry.data, material); - break; + // now create a specific 3D object - case 'linestrips': - object = new THREE.Line(geometry.data, material); - break; + var object; - case 'triangles': - case 'polylist': - if (skinning) { + switch (type) { - object = new THREE.SkinnedMesh(geometry.data, material); - } else { + case 'lines': + object = new THREE.LineSegments(geometry.data, material); + break; - object = new THREE.Mesh(geometry.data, material); - } - break; + case 'linestrips': + object = new THREE.Line(geometry.data, material); + break; - } + case 'triangles': + case 'polylist': + if (skinning) { - objects.push(object); - } + object = new THREE.SkinnedMesh(geometry.data, material); + } else { - return objects; + object = new THREE.Mesh(geometry.data, material); } + break; - function hasNode(id) { + } - return library.nodes[id] !== undefined; - } + objects.push(object); + } - function getNode(id) { + return objects; + } - return getBuild(library.nodes[id], buildNode); - } + function hasNode(id) { - // visual scenes + return library.nodes[id] !== undefined; + } - function parseVisualScene(xml) { + function getNode(id) { - var data = { - name: xml.getAttribute('name'), - children: [] - }; + return getBuild(library.nodes[id], buildNode); + } - prepareNodes(xml); + // visual scenes - var elements = getElementsByTagName(xml, 'node'); + function parseVisualScene(xml) { - for (var i = 0; i < elements.length; i++) { + var data = { + name: xml.getAttribute('name'), + children: [] + }; - data.children.push(parseNode(elements[i])); - } + prepareNodes(xml); - library.visualScenes[xml.getAttribute('id')] = data; - } + var elements = getElementsByTagName(xml, 'node'); - function buildVisualScene(data) { + for (var i = 0; i < elements.length; i++) { - var group = new THREE.Group(); - group.name = data.name; + data.children.push(parseNode(elements[i])); + } - var children = data.children; + library.visualScenes[xml.getAttribute('id')] = data; + } - for (var i = 0; i < children.length; i++) { + function buildVisualScene(data) { - var child = children[i]; + var group = new THREE.Group(); + group.name = data.name; - group.add(getNode(child.id)); - } + var children = data.children; - return group; - } + for (var i = 0; i < children.length; i++) { - function hasVisualScene(id) { + var child = children[i]; - return library.visualScenes[id] !== undefined; - } + group.add(getNode(child.id)); + } - function getVisualScene(id) { + return group; + } - return getBuild(library.visualScenes[id], buildVisualScene); - } + function hasVisualScene(id) { - // scenes + return library.visualScenes[id] !== undefined; + } - function parseScene(xml) { + function getVisualScene(id) { - var instance = getElementsByTagName(xml, 'instance_visual_scene')[0]; - return getVisualScene(parseId(instance.getAttribute('url'))); - } + return getBuild(library.visualScenes[id], buildVisualScene); + } - function setupAnimations() { + // scenes - var clips = library.clips; + function parseScene(xml) { - if (isEmpty(clips) === true) { + var instance = getElementsByTagName(xml, 'instance_visual_scene')[0]; + return getVisualScene(parseId(instance.getAttribute('url'))); + } - if (isEmpty(library.animations) === false) { + function setupAnimations() { - // if there are animations but no clips, we create a default clip for playback + var clips = library.clips; - var tracks = []; + if (isEmpty(clips) === true) { - for (var id in library.animations) { + if (isEmpty(library.animations) === false) { - var animationTracks = getAnimation(id); + // if there are animations but no clips, we create a default clip for playback - for (var i = 0, l = animationTracks.length; i < l; i++) { + var tracks = []; - tracks.push(animationTracks[i]); - } - } + for (var id in library.animations) { - animations.push(new THREE.AnimationClip('default', -1, tracks)); - } - } else { + var animationTracks = getAnimation(id); - for (var id in clips) { + for (var i = 0, l = animationTracks.length; i < l; i++) { - animations.push(getAnimationClip(id)); - } - } + tracks.push(animationTracks[i]); } + } + + animations.push(new THREE.AnimationClip('default', -1, tracks)); + } + } else { - if (text.length === 0) { + for (var id in clips) { - return { scene: new THREE.Scene() }; - } + animations.push(getAnimationClip(id)); + } + } + } - var xml = new DOMParser().parseFromString(text, 'application/xml'); + if (text.length === 0) { - var collada = getElementsByTagName(xml, 'COLLADA')[0]; + return { scene: new THREE.Scene() }; + } - // metadata + var xml = new DOMParser().parseFromString(text, 'application/xml'); - var version = collada.getAttribute('version'); - console.log('THREE.ColladaLoader: File version', version); + var collada = getElementsByTagName(xml, 'COLLADA')[0]; - var asset = parseAsset(getElementsByTagName(collada, 'asset')[0]); - var textureLoader = new THREE.TextureLoader(this.manager); - textureLoader.setPath(this.resourcePath || path).setCrossOrigin(this.crossOrigin); + // metadata - var tgaLoader; + var version = collada.getAttribute('version'); + console.log('THREE.ColladaLoader: File version', version); - if (THREE.TGALoader) { + var asset = parseAsset(getElementsByTagName(collada, 'asset')[0]); + var textureLoader = new THREE.TextureLoader(this.manager); + textureLoader.setPath(this.resourcePath || path).setCrossOrigin(this.crossOrigin); - tgaLoader = new THREE.TGALoader(this.manager); - tgaLoader.setPath(this.resourcePath || path); - } + var tgaLoader; - // - - var animations = []; - var kinematics = {}; - var count = 0; - - // - - var library = { - animations: {}, - clips: {}, - controllers: {}, - images: {}, - effects: {}, - materials: {}, - cameras: {}, - lights: {}, - geometries: {}, - nodes: {}, - visualScenes: {}, - kinematicsModels: {}, - physicsModels: {}, - kinematicsScenes: {} - }; + if (THREE.TGALoader) { - parseLibrary(collada, 'library_animations', 'animation', parseAnimation); - parseLibrary(collada, 'library_animation_clips', 'animation_clip', parseAnimationClip); - parseLibrary(collada, 'library_controllers', 'controller', parseController); - parseLibrary(collada, 'library_images', 'image', parseImage); - parseLibrary(collada, 'library_effects', 'effect', parseEffect); - parseLibrary(collada, 'library_materials', 'material', parseMaterial); - parseLibrary(collada, 'library_cameras', 'camera', parseCamera); - parseLibrary(collada, 'library_lights', 'light', parseLight); - parseLibrary(collada, 'library_geometries', 'geometry', parseGeometry); - parseLibrary(collada, 'library_nodes', 'node', parseNode); - parseLibrary(collada, 'library_visual_scenes', 'visual_scene', parseVisualScene); - parseLibrary(collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel); - parseLibrary(collada, 'library_physics_models', 'physics_model', parsePhysicsModel); - parseLibrary(collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene); - - buildLibrary(library.animations, buildAnimation); - buildLibrary(library.clips, buildAnimationClip); - buildLibrary(library.controllers, buildController); - buildLibrary(library.images, buildImage); - buildLibrary(library.effects, buildEffect); - buildLibrary(library.materials, buildMaterial); - buildLibrary(library.cameras, buildCamera); - buildLibrary(library.lights, buildLight); - buildLibrary(library.geometries, buildGeometry); - buildLibrary(library.visualScenes, buildVisualScene); - - setupAnimations(); - setupKinematics(); - - var scene = parseScene(getElementsByTagName(collada, 'scene')[0]); - - if (asset.upAxis === 'Z_UP') { - - scene.quaternion.setFromEuler(new THREE.Euler(-Math.PI / 2, 0, 0)); - } + tgaLoader = new THREE.TGALoader(this.manager); + tgaLoader.setPath(this.resourcePath || path); + } - scene.scale.multiplyScalar(asset.unit); + // + + var animations = []; + var kinematics = {}; + var count = 0; + + // + + var library = { + animations: {}, + clips: {}, + controllers: {}, + images: {}, + effects: {}, + materials: {}, + cameras: {}, + lights: {}, + geometries: {}, + nodes: {}, + visualScenes: {}, + kinematicsModels: {}, + physicsModels: {}, + kinematicsScenes: {} + }; - return { - animations: animations, - kinematics: kinematics, - library: library, - scene: scene - }; - } + parseLibrary(collada, 'library_animations', 'animation', parseAnimation); + parseLibrary(collada, 'library_animation_clips', 'animation_clip', parseAnimationClip); + parseLibrary(collada, 'library_controllers', 'controller', parseController); + parseLibrary(collada, 'library_images', 'image', parseImage); + parseLibrary(collada, 'library_effects', 'effect', parseEffect); + parseLibrary(collada, 'library_materials', 'material', parseMaterial); + parseLibrary(collada, 'library_cameras', 'camera', parseCamera); + parseLibrary(collada, 'library_lights', 'light', parseLight); + parseLibrary(collada, 'library_geometries', 'geometry', parseGeometry); + parseLibrary(collada, 'library_nodes', 'node', parseNode); + parseLibrary(collada, 'library_visual_scenes', 'visual_scene', parseVisualScene); + parseLibrary(collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel); + parseLibrary(collada, 'library_physics_models', 'physics_model', parsePhysicsModel); + parseLibrary(collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene); + + buildLibrary(library.animations, buildAnimation); + buildLibrary(library.clips, buildAnimationClip); + buildLibrary(library.controllers, buildController); + buildLibrary(library.images, buildImage); + buildLibrary(library.effects, buildEffect); + buildLibrary(library.materials, buildMaterial); + buildLibrary(library.cameras, buildCamera); + buildLibrary(library.lights, buildLight); + buildLibrary(library.geometries, buildGeometry); + buildLibrary(library.visualScenes, buildVisualScene); + + setupAnimations(); + setupKinematics(); + + var scene = parseScene(getElementsByTagName(collada, 'scene')[0]); + + if (asset.upAxis === 'Z_UP') { + + scene.quaternion.setFromEuler(new THREE.Euler(-Math.PI / 2, 0, 0)); + } + + scene.scale.multiplyScalar(asset.unit); + + return { + animations: animations, + kinematics: kinematics, + library: library, + scene: scene + }; + } }; @@ -3589,9 +3589,9 @@ THREE.ColladaLoader.prototype = { var _typeof2 = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol" ? function (obj) { - return typeof obj === "undefined" ? "undefined" : _typeof2(obj); + return typeof obj === "undefined" ? "undefined" : _typeof2(obj); } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === "undefined" ? "undefined" : _typeof2(obj); + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === "undefined" ? "undefined" : _typeof2(obj); }; /** @@ -3616,3567 +3616,3567 @@ var _typeof = typeof Symbol === "function" && _typeof2(Symbol.iterator) === "sym module.exports = THREE.FBXLoader = function () { - var fbxTree; - var connections; - var sceneGraph; - - function FBXLoader(manager) { - - this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager; - } - - FBXLoader.prototype = { - - constructor: FBXLoader, - - crossOrigin: 'anonymous', - - load: function load(url, onLoad, onProgress, onError) { - var self = this; + var fbxTree; + var connections; + var sceneGraph; - var resourceDirectory = THREE.LoaderUtils.extractUrlBase(url); + function FBXLoader(manager) { - var loader = new THREE.FileLoader(this.manager); - loader.setResponseType('arraybuffer'); - loader.load(url, function (buffer) { + this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager; + } - try { + FBXLoader.prototype = { - var scene = self.parse(buffer, resourceDirectory); - onLoad(scene); - } catch (error) { + constructor: FBXLoader, - setTimeout(function () { + crossOrigin: 'anonymous', - if (onError) onError(error); + load: function load(url, onLoad, onProgress, onError) { + var self = this; - self.manager.itemError(url); - }, 0); - } - }, onProgress, onError); - }, + var resourceDirectory = THREE.LoaderUtils.extractUrlBase(url); - setCrossOrigin: function setCrossOrigin(value) { + var loader = new THREE.FileLoader(this.manager); + loader.setResponseType('arraybuffer'); + loader.load(url, function (buffer) { - this.crossOrigin = value; - return this; - }, + try { - parse: function parse(FBXBuffer, resourceDirectory) { + var scene = self.parse(buffer, resourceDirectory); + onLoad(scene); + } catch (error) { - if (isFbxFormatBinary(FBXBuffer)) { + setTimeout(function () { - fbxTree = new BinaryParser().parse(FBXBuffer); - } else { + if (onError) onError(error); - var FBXText = convertArrayBufferToString(FBXBuffer); + self.manager.itemError(url); + }, 0); + } + }, onProgress, onError); + }, - if (!isFbxFormatASCII(FBXText)) { + setCrossOrigin: function setCrossOrigin(value) { - throw new Error('THREE.FBXLoader: Unknown format.'); - } + this.crossOrigin = value; + return this; + }, - if (getFbxVersion(FBXText) < 7000) { + parse: function parse(FBXBuffer, resourceDirectory) { - throw new Error('THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion(FBXText)); - } + if (isFbxFormatBinary(FBXBuffer)) { - fbxTree = new TextParser().parse(FBXText); - } + fbxTree = new BinaryParser().parse(FBXBuffer); + } else { - //console.log( FBXTree ); + var FBXText = convertArrayBufferToString(FBXBuffer); - var textureLoader = new THREE.TextureLoader(this.manager).setPath(resourceDirectory).setCrossOrigin(this.crossOrigin); + if (!isFbxFormatASCII(FBXText)) { - return new FBXTreeParser(textureLoader).parse(fbxTree); - } + throw new Error('THREE.FBXLoader: Unknown format.'); + } - }; + if (getFbxVersion(FBXText) < 7000) { - // Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group - function FBXTreeParser(textureLoader) { + throw new Error('THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion(FBXText)); + } - this.textureLoader = textureLoader; + fbxTree = new TextParser().parse(FBXText); } - 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; - }, + //console.log( FBXTree ); - // 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 textureLoader = new THREE.TextureLoader(this.manager).setPath(resourceDirectory).setCrossOrigin(this.crossOrigin); - 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)) { + return new FBXTreeParser(textureLoader).parse(fbxTree); + } - connectionMap.set(fromID, { - parents: [], - children: [] - }); - } + }; - var parentRelationship = { ID: toID, relationship: relationship }; - connectionMap.get(fromID).parents.push(parentRelationship); + // Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group + function FBXTreeParser(textureLoader) { - if (!connectionMap.has(toID)) { + this.textureLoader = textureLoader; + } - connectionMap.set(toID, { - parents: [], - children: [] - }); - } + FBXTreeParser.prototype = { - var childRelationship = { ID: fromID, relationship: relationship }; - connectionMap.get(toID).children.push(childRelationship); - }); - } + constructor: FBXTreeParser, - return connectionMap; - }, + parse: function parse() { - // Parse FBXTree.Objects.Video for embedded image data - // These images are connected to textures in FBXTree.Objects.Textures - // via FBXTree.Connections. - parseImages: function parseImages() { + connections = this.parseConnections(); - var images = {}; - var blobs = {}; + var images = this.parseImages(); + var textures = this.parseTextures(images); + var materials = this.parseMaterials(textures); + var deformers = this.parseDeformers(); + var geometryMap = new GeometryParser().parse(deformers); - if ('Video' in fbxTree.Objects) { + this.parseScene(deformers, geometryMap, materials); - var videoNodes = fbxTree.Objects.Video; + return sceneGraph; + }, - for (var nodeID in videoNodes) { + // 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 videoNode = videoNodes[nodeID]; + var connectionMap = new Map(); - var id = parseInt(nodeID); + if ('Connections' in fbxTree) { - images[id] = videoNode.RelativeFilename || videoNode.Filename; + var rawConnections = fbxTree.Connections.connections; - // raw image data is in videoNode.Content - if ('Content' in videoNode) { + rawConnections.forEach(function (rawConnection) { - var arrayBufferContent = videoNode.Content instanceof ArrayBuffer && videoNode.Content.byteLength > 0; - var base64Content = typeof videoNode.Content === 'string' && videoNode.Content !== ''; + var fromID = rawConnection[0]; + var toID = rawConnection[1]; + var relationship = rawConnection[2]; - if (arrayBufferContent || base64Content) { + if (!connectionMap.has(fromID)) { - var image = this.parseImage(videoNodes[nodeID]); + connectionMap.set(fromID, { + parents: [], + children: [] + }); + } - blobs[videoNode.RelativeFilename || videoNode.Filename] = image; - } - } - } - } + var parentRelationship = { ID: toID, relationship: relationship }; + connectionMap.get(fromID).parents.push(parentRelationship); - for (var id in images) { + if (!connectionMap.has(toID)) { - var filename = images[id]; + connectionMap.set(toID, { + parents: [], + children: [] + }); + } - if (blobs[filename] !== undefined) images[id] = blobs[filename];else images[id] = images[id].split('\\').pop(); - } + var childRelationship = { ID: fromID, relationship: relationship }; + connectionMap.get(toID).children.push(childRelationship); + }); + } - return images; - }, + return connectionMap; + }, - // Parse embedded image data in FBXTree.Video.Content - parseImage: function parseImage(videoNode) { + // 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 content = videoNode.Content; - var fileName = videoNode.RelativeFilename || videoNode.Filename; - var extension = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase(); + var images = {}; + var blobs = {}; - var type; + if ('Video' in fbxTree.Objects) { - switch (extension) { + var videoNodes = fbxTree.Objects.Video; - case 'bmp': + for (var nodeID in videoNodes) { - type = 'image/bmp'; - break; + var videoNode = videoNodes[nodeID]; - case 'jpg': - case 'jpeg': + var id = parseInt(nodeID); - type = 'image/jpeg'; - break; + images[id] = videoNode.RelativeFilename || videoNode.Filename; - case 'png': + // raw image data is in videoNode.Content + if ('Content' in videoNode) { - type = 'image/png'; - break; + var arrayBufferContent = videoNode.Content instanceof ArrayBuffer && videoNode.Content.byteLength > 0; + var base64Content = typeof videoNode.Content === 'string' && videoNode.Content !== ''; - case 'tif': + if (arrayBufferContent || base64Content) { - type = 'image/tiff'; - break; + var image = this.parseImage(videoNodes[nodeID]); - case 'tga': + blobs[videoNode.RelativeFilename || videoNode.Filename] = image; + } + } + } + } - if (typeof THREE.TGALoader !== 'function') { + for (var id in images) { - console.warn('FBXLoader: THREE.TGALoader is required to load TGA textures'); - return; - } else { + var filename = images[id]; - if (THREE.Loader.Handlers.get('.tga') === null) { + if (blobs[filename] !== undefined) images[id] = blobs[filename];else images[id] = images[id].split('\\').pop(); + } - THREE.Loader.Handlers.add(/\.tga$/i, new THREE.TGALoader()); - } + return images; + }, - type = 'image/tga'; - break; - } + // Parse embedded image data in FBXTree.Video.Content + parseImage: function parseImage(videoNode) { - default: + var content = videoNode.Content; + var fileName = videoNode.RelativeFilename || videoNode.Filename; + var extension = fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase(); - console.warn('FBXLoader: Image type "' + extension + '" is not supported.'); - return; + var type; - } + switch (extension) { - if (typeof content === 'string') { - // ASCII format + case 'bmp': - return 'data:' + type + ';base64,' + content; - } else { - // Binary Format + type = 'image/bmp'; + break; - var array = new Uint8Array(content); - return window.URL.createObjectURL(new Blob([array], { type: type })); - } - }, + case 'jpg': + case 'jpeg': - // 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) { + type = 'image/jpeg'; + break; - var textureMap = new Map(); + case 'png': - if ('Texture' in fbxTree.Objects) { + type = 'image/png'; + break; - var textureNodes = fbxTree.Objects.Texture; - for (var nodeID in textureNodes) { + case 'tif': - var texture = this.parseTexture(textureNodes[nodeID], images); - textureMap.set(parseInt(nodeID), texture); - } - } + type = 'image/tiff'; + break; - return textureMap; - }, + case 'tga': - // Parse individual node in FBXTree.Objects.Texture - parseTexture: function parseTexture(textureNode, images) { + if (typeof THREE.TGALoader !== 'function') { - var texture = this.loadTexture(textureNode, images); + console.warn('FBXLoader: THREE.TGALoader is required to load TGA textures'); + return; + } else { - texture.ID = textureNode.id; + if (THREE.Loader.Handlers.get('.tga') === null) { - texture.name = textureNode.attrName; + THREE.Loader.Handlers.add(/\.tga$/i, new THREE.TGALoader()); + } - var wrapModeU = textureNode.WrapModeU; - var wrapModeV = textureNode.WrapModeV; + type = 'image/tga'; + break; + } - var valueU = wrapModeU !== undefined ? wrapModeU.value : 0; - var valueV = wrapModeV !== undefined ? wrapModeV.value : 0; + default: - // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a - // 0: repeat(default), 1: clamp + console.warn('FBXLoader: Image type "' + extension + '" is not supported.'); + return; - texture.wrapS = valueU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; - texture.wrapT = valueV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + } - if ('Scaling' in textureNode) { + if (typeof content === 'string') { + // ASCII format - var values = textureNode.Scaling.value; + return 'data:' + type + ';base64,' + content; + } else { + // Binary Format - texture.repeat.x = values[0]; - texture.repeat.y = values[1]; - } + var array = new Uint8Array(content); + return window.URL.createObjectURL(new Blob([array], { type: type })); + } + }, - return texture; - }, + // 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) { - // load a texture specified as a blob or data URI, or via an external URL using THREE.TextureLoader - loadTexture: function loadTexture(textureNode, images) { + var textureMap = new Map(); - var fileName; + if ('Texture' in fbxTree.Objects) { - var currentPath = this.textureLoader.path; + var textureNodes = fbxTree.Objects.Texture; + for (var nodeID in textureNodes) { - var children = connections.get(textureNode.id).children; + var texture = this.parseTexture(textureNodes[nodeID], images); + textureMap.set(parseInt(nodeID), texture); + } + } - if (children !== undefined && children.length > 0 && images[children[0].ID] !== undefined) { + return textureMap; + }, - fileName = images[children[0].ID]; + // Parse individual node in FBXTree.Objects.Texture + parseTexture: function parseTexture(textureNode, images) { - if (fileName.indexOf('blob:') === 0 || fileName.indexOf('data:') === 0) { + var texture = this.loadTexture(textureNode, images); - this.textureLoader.setPath(undefined); - } - } + texture.ID = textureNode.id; - var texture; + texture.name = textureNode.attrName; - var extension = textureNode.FileName.slice(-3).toLowerCase(); + var wrapModeU = textureNode.WrapModeU; + var wrapModeV = textureNode.WrapModeV; - if (extension === 'tga') { + var valueU = wrapModeU !== undefined ? wrapModeU.value : 0; + var valueV = wrapModeV !== undefined ? wrapModeV.value : 0; - var loader = THREE.Loader.Handlers.get('.tga'); + // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a + // 0: repeat(default), 1: clamp - if (loader === null) { + texture.wrapS = valueU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + texture.wrapT = valueV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; - console.warn('FBXLoader: TGALoader not found, creating empty placeholder texture for', fileName); - texture = new THREE.Texture(); - } else { + if ('Scaling' in textureNode) { - texture = loader.load(fileName); - } - } else if (extension === 'psd') { + var values = textureNode.Scaling.value; - console.warn('FBXLoader: PSD textures are not supported, creating empty placeholder texture for', fileName); - texture = new THREE.Texture(); - } else { + texture.repeat.x = values[0]; + texture.repeat.y = values[1]; + } - texture = this.textureLoader.load(fileName); - } + return texture; + }, - this.textureLoader.setPath(currentPath); + // load a texture specified as a blob or data URI, or via an external URL using THREE.TextureLoader + loadTexture: function loadTexture(textureNode, images) { - return texture; - }, + var fileName; - // Parse nodes in FBXTree.Objects.Material - parseMaterials: function parseMaterials(textureMap) { + var currentPath = this.textureLoader.path; - var materialMap = new Map(); + var children = connections.get(textureNode.id).children; - if ('Material' in fbxTree.Objects) { + if (children !== undefined && children.length > 0 && images[children[0].ID] !== undefined) { - var materialNodes = fbxTree.Objects.Material; + fileName = images[children[0].ID]; - for (var nodeID in materialNodes) { + if (fileName.indexOf('blob:') === 0 || fileName.indexOf('data:') === 0) { - var material = this.parseMaterial(materialNodes[nodeID], textureMap); + this.textureLoader.setPath(undefined); + } + } - if (material !== null) materialMap.set(parseInt(nodeID), material); - } - } + var texture; - return materialMap; - }, + var extension = textureNode.FileName.slice(-3).toLowerCase(); - // 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) { + if (extension === 'tga') { - var ID = materialNode.id; - var name = materialNode.attrName; - var type = materialNode.ShadingModel; + var loader = THREE.Loader.Handlers.get('.tga'); - // Case where FBX wraps shading model in property object. - if ((typeof type === 'undefined' ? 'undefined' : _typeof(type)) === 'object') { + if (loader === null) { - type = type.value; - } + console.warn('FBXLoader: TGALoader not found, creating empty placeholder texture for', fileName); + texture = new THREE.Texture(); + } else { - // Ignore unused materials which don't have any connections. - if (!connections.has(ID)) return null; + texture = loader.load(fileName); + } + } else if (extension === 'psd') { - var parameters = this.parseParameters(materialNode, textureMap, ID); + console.warn('FBXLoader: PSD textures are not supported, creating empty placeholder texture for', fileName); + texture = new THREE.Texture(); + } else { - var material; + texture = this.textureLoader.load(fileName); + } - switch (type.toLowerCase()) { + this.textureLoader.setPath(currentPath); - 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({ color: 0x3300ff }); - break; + return texture; + }, - } + // Parse nodes in FBXTree.Objects.Material + parseMaterials: function parseMaterials(textureMap) { - material.setValues(parameters); - material.name = name; + var materialMap = new Map(); - return material; - }, + if ('Material' in fbxTree.Objects) { - // 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 materialNodes = fbxTree.Objects.Material; - var parameters = {}; + for (var nodeID in materialNodes) { - if (materialNode.BumpFactor) { + var material = this.parseMaterial(materialNodes[nodeID], textureMap); - parameters.bumpScale = materialNode.BumpFactor.value; - } - if (materialNode.Diffuse) { + if (material !== null) materialMap.set(parseInt(nodeID), material); + } + } - parameters.color = new THREE.Color().fromArray(materialNode.Diffuse.value); - } else if (materialNode.DiffuseColor && materialNode.DiffuseColor.type === 'Color') { + return materialMap; + }, - // The blender exporter exports diffuse here instead of in materialNode.Diffuse - parameters.color = new THREE.Color().fromArray(materialNode.DiffuseColor.value); - } - if (materialNode.DisplacementFactor) { + // 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) { - parameters.displacementScale = materialNode.DisplacementFactor.value; - } - if (materialNode.Emissive) { + var ID = materialNode.id; + var name = materialNode.attrName; + var type = materialNode.ShadingModel; - parameters.emissive = new THREE.Color().fromArray(materialNode.Emissive.value); - } else if (materialNode.EmissiveColor && materialNode.EmissiveColor.type === 'Color') { + // Case where FBX wraps shading model in property object. + if ((typeof type === 'undefined' ? 'undefined' : _typeof(type)) === 'object') { - // The blender exporter exports emissive color here instead of in materialNode.Emissive - parameters.emissive = new THREE.Color().fromArray(materialNode.EmissiveColor.value); - } - if (materialNode.EmissiveFactor) { + type = type.value; + } - parameters.emissiveIntensity = parseFloat(materialNode.EmissiveFactor.value); - } - if (materialNode.Opacity) { + // Ignore unused materials which don't have any connections. + if (!connections.has(ID)) return null; - parameters.opacity = parseFloat(materialNode.Opacity.value); - } - if (parameters.opacity < 1.0) { + var parameters = this.parseParameters(materialNode, textureMap, ID); - parameters.transparent = true; - } - if (materialNode.ReflectionFactor) { + var material; - parameters.reflectivity = materialNode.ReflectionFactor.value; - } - if (materialNode.Shininess) { + switch (type.toLowerCase()) { - parameters.shininess = materialNode.Shininess.value; - } - if (materialNode.Specular) { + 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({ color: 0x3300ff }); + break; - 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); - } + material.setValues(parameters); + material.name = name; - var self = this; - connections.get(ID).children.forEach(function (child) { + return material; + }, - var type = child.relationship; + // 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) { - switch (type) { + var parameters = {}; - case 'Bump': - parameters.bumpMap = self.getTexture(textureMap, child.ID); - break; + if (materialNode.BumpFactor) { - case 'DiffuseColor': - parameters.map = self.getTexture(textureMap, child.ID); - break; + parameters.bumpScale = materialNode.BumpFactor.value; + } + if (materialNode.Diffuse) { - case 'DisplacementColor': - parameters.displacementMap = self.getTexture(textureMap, child.ID); - break; + parameters.color = new THREE.Color().fromArray(materialNode.Diffuse.value); + } else if (materialNode.DiffuseColor && materialNode.DiffuseColor.type === 'Color') { - case 'EmissiveColor': - parameters.emissiveMap = self.getTexture(textureMap, child.ID); - break; + // The blender exporter exports diffuse here instead of in materialNode.Diffuse + parameters.color = new THREE.Color().fromArray(materialNode.DiffuseColor.value); + } + if (materialNode.DisplacementFactor) { - case 'NormalMap': - parameters.normalMap = self.getTexture(textureMap, child.ID); - break; + parameters.displacementScale = materialNode.DisplacementFactor.value; + } + if (materialNode.Emissive) { - case 'ReflectionColor': - parameters.envMap = self.getTexture(textureMap, child.ID); - parameters.envMap.mapping = THREE.EquirectangularReflectionMapping; - break; + parameters.emissive = new THREE.Color().fromArray(materialNode.Emissive.value); + } else if (materialNode.EmissiveColor && materialNode.EmissiveColor.type === 'Color') { - case 'SpecularColor': - parameters.specularMap = self.getTexture(textureMap, child.ID); - break; + // The blender exporter exports emissive color here instead of in materialNode.Emissive + parameters.emissive = new THREE.Color().fromArray(materialNode.EmissiveColor.value); + } + if (materialNode.EmissiveFactor) { - case 'TransparentColor': - parameters.alphaMap = self.getTexture(textureMap, child.ID); - parameters.transparent = true; - break; + parameters.emissiveIntensity = parseFloat(materialNode.EmissiveFactor.value); + } + if (materialNode.Opacity) { - 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; + parameters.opacity = parseFloat(materialNode.Opacity.value); + } + if (parameters.opacity < 1.0) { - } - }); + parameters.transparent = true; + } + if (materialNode.ReflectionFactor) { - return parameters; - }, + parameters.reflectivity = materialNode.ReflectionFactor.value; + } + if (materialNode.Shininess) { - // get a texture from the textureMap for use by a material. - getTexture: function getTexture(textureMap, id) { + parameters.shininess = materialNode.Shininess.value; + } + if (materialNode.Specular) { - // 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) { + parameters.specular = new THREE.Color().fromArray(materialNode.Specular.value); + } else if (materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color') { - console.warn('THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.'); - id = connections.get(id).children[0].ID; - } + // The blender exporter exports specular color here instead of in materialNode.Specular + parameters.specular = new THREE.Color().fromArray(materialNode.SpecularColor.value); + } - return textureMap.get(id); - }, + var self = this; + connections.get(ID).children.forEach(function (child) { - // 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 type = child.relationship; - var skeletons = {}; - var morphTargets = {}; + switch (type) { - if ('Deformer' in fbxTree.Objects) { + case 'Bump': + parameters.bumpMap = self.getTexture(textureMap, child.ID); + break; - var DeformerNodes = fbxTree.Objects.Deformer; + case 'DiffuseColor': + parameters.map = self.getTexture(textureMap, child.ID); + break; - for (var nodeID in DeformerNodes) { + case 'DisplacementColor': + parameters.displacementMap = self.getTexture(textureMap, child.ID); + break; - var deformerNode = DeformerNodes[nodeID]; + case 'EmissiveColor': + parameters.emissiveMap = self.getTexture(textureMap, child.ID); + break; - var relationships = connections.get(parseInt(nodeID)); + case 'NormalMap': + parameters.normalMap = self.getTexture(textureMap, child.ID); + break; - if (deformerNode.attrType === 'Skin') { + case 'ReflectionColor': + parameters.envMap = self.getTexture(textureMap, child.ID); + parameters.envMap.mapping = THREE.EquirectangularReflectionMapping; + break; - var skeleton = this.parseSkeleton(relationships, DeformerNodes); - skeleton.ID = nodeID; + case 'SpecularColor': + parameters.specularMap = self.getTexture(textureMap, child.ID); + break; - 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; + case 'TransparentColor': + parameters.alphaMap = self.getTexture(textureMap, child.ID); + parameters.transparent = true; + break; - skeletons[nodeID] = skeleton; - } else if (deformerNode.attrType === 'BlendShape') { + 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; - var morphTarget = { - id: nodeID - }; + } + }); - morphTarget.rawTargets = this.parseMorphTargets(relationships, DeformerNodes); - morphTarget.id = nodeID; + return parameters; + }, - if (relationships.parents.length > 1) console.warn('THREE.FBXLoader: morph target attached to more than one geometry is not supported.'); + // get a texture from the textureMap for use by a material. + getTexture: function getTexture(textureMap, id) { - morphTargets[nodeID] = morphTarget; - } - } - } + // 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) { - return { + console.warn('THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.'); + id = connections.get(id).children[0].ID; + } - skeletons: skeletons, - morphTargets: morphTargets + 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() { - // 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 skeletons = {}; + var morphTargets = {}; - var rawBones = []; + if ('Deformer' in fbxTree.Objects) { - relationships.children.forEach(function (child) { + var DeformerNodes = fbxTree.Objects.Deformer; - var boneNode = deformerNodes[child.ID]; + for (var nodeID in DeformerNodes) { - if (boneNode.attrType !== 'Cluster') return; + var deformerNode = DeformerNodes[nodeID]; - var rawBone = { + var relationships = connections.get(parseInt(nodeID)); - ID: child.ID, - indices: [], - weights: [], - transform: new THREE.Matrix4().fromArray(boneNode.Transform.a), - transformLink: new THREE.Matrix4().fromArray(boneNode.TransformLink.a), - linkMode: boneNode.Mode + if (deformerNode.attrType === 'Skin') { - }; + var skeleton = this.parseSkeleton(relationships, DeformerNodes); + skeleton.ID = nodeID; - if ('Indexes' in boneNode) { + 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; - rawBone.indices = boneNode.Indexes.a; - rawBone.weights = boneNode.Weights.a; - } + skeletons[nodeID] = skeleton; + } else if (deformerNode.attrType === 'BlendShape') { - rawBones.push(rawBone); - }); + var morphTarget = { + id: nodeID + }; - return { + morphTarget.rawTargets = this.parseMorphTargets(relationships, DeformerNodes); + morphTarget.id = nodeID; - rawBones: rawBones, - bones: [] + if (relationships.parents.length > 1) console.warn('THREE.FBXLoader: morph target attached to more than one geometry is not supported.'); - }; - }, + morphTargets[nodeID] = morphTarget; + } + } + } - // The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel" - parseMorphTargets: function parseMorphTargets(relationships, deformerNodes) { + return { - var rawMorphTargets = []; + skeletons: skeletons, + morphTargets: morphTargets - for (var i = 0; i < relationships.children.length; i++) { + }; + }, - if (i === 8) { + // 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) { - console.warn('FBXLoader: maximum of 8 morph targets supported. Ignoring additional targets.'); + var rawBones = []; - break; - } + relationships.children.forEach(function (child) { - var child = relationships.children[i]; + var boneNode = deformerNodes[child.ID]; - var morphTargetNode = deformerNodes[child.ID]; + if (boneNode.attrType !== 'Cluster') return; - var rawMorphTarget = { + var rawBone = { - name: morphTargetNode.attrName, - initialWeight: morphTargetNode.DeformPercent, - id: morphTargetNode.id, - fullWeights: morphTargetNode.FullWeights.a + ID: child.ID, + indices: [], + weights: [], + transform: new THREE.Matrix4().fromArray(boneNode.Transform.a), + transformLink: new THREE.Matrix4().fromArray(boneNode.TransformLink.a), + linkMode: boneNode.Mode - }; + }; - if (morphTargetNode.attrType !== 'BlendShapeChannel') return; + if ('Indexes' in boneNode) { - var targetRelationships = connections.get(parseInt(child.ID)); + rawBone.indices = boneNode.Indexes.a; + rawBone.weights = boneNode.Weights.a; + } - targetRelationships.children.forEach(function (child) { + rawBones.push(rawBone); + }); - if (child.relationship === undefined) rawMorphTarget.geoID = child.ID; - }); + return { - rawMorphTargets.push(rawMorphTarget); - } + rawBones: rawBones, + bones: [] - return rawMorphTargets; - }, + }; + }, - // create the main THREE.Group() to be returned by the loader - parseScene: function parseScene(deformers, geometryMap, materialMap) { + // The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel" + parseMorphTargets: function parseMorphTargets(relationships, deformerNodes) { - sceneGraph = new THREE.Group(); + var rawMorphTargets = []; - var modelMap = this.parseModels(deformers.skeletons, geometryMap, materialMap); + for (var i = 0; i < relationships.children.length; i++) { - var modelNodes = fbxTree.Objects.Model; + if (i === 8) { - var self = this; - modelMap.forEach(function (model) { + console.warn('FBXLoader: maximum of 8 morph targets supported. Ignoring additional targets.'); - var modelNode = modelNodes[model.ID]; - self.setLookAtProperties(model, modelNode); + break; + } - var parentConnections = connections.get(model.ID).parents; + var child = relationships.children[i]; - parentConnections.forEach(function (connection) { + var morphTargetNode = deformerNodes[child.ID]; - var parent = modelMap.get(connection.ID); - if (parent !== undefined) parent.add(model); - }); + var rawMorphTarget = { - if (model.parent === null) { + name: morphTargetNode.attrName, + initialWeight: morphTargetNode.DeformPercent, + id: morphTargetNode.id, + fullWeights: morphTargetNode.FullWeights.a - sceneGraph.add(model); - } - }); + }; - this.bindSkeleton(deformers.skeletons, geometryMap, modelMap); + if (morphTargetNode.attrType !== 'BlendShapeChannel') return; - this.createAmbientLight(); + var targetRelationships = connections.get(parseInt(child.ID)); - this.setupMorphMaterials(); + targetRelationships.children.forEach(function (child) { - var animations = new AnimationParser().parse(); + if (child.relationship === undefined) rawMorphTarget.geoID = child.ID; + }); - // if all the models where already combined in a single group, just return that - if (sceneGraph.children.length === 1 && sceneGraph.children[0].isGroup) { + rawMorphTargets.push(rawMorphTarget); + } - sceneGraph.children[0].animations = animations; - sceneGraph = sceneGraph.children[0]; - } + return rawMorphTargets; + }, - sceneGraph.animations = animations; - }, + // create the main THREE.Group() to be returned by the loader + parseScene: function parseScene(deformers, geometryMap, materialMap) { - // parse nodes in FBXTree.Objects.Model - parseModels: function parseModels(skeletons, geometryMap, materialMap) { + sceneGraph = new THREE.Group(); - var modelMap = new Map(); - var modelNodes = fbxTree.Objects.Model; + var modelMap = this.parseModels(deformers.skeletons, geometryMap, materialMap); - for (var nodeID in modelNodes) { + var modelNodes = fbxTree.Objects.Model; - var id = parseInt(nodeID); - var node = modelNodes[nodeID]; - var relationships = connections.get(id); + var self = this; + modelMap.forEach(function (model) { - var model = this.buildSkeleton(relationships, skeletons, id, node.attrName); + var modelNode = modelNodes[model.ID]; + self.setLookAtProperties(model, modelNode); - if (!model) { + var parentConnections = connections.get(model.ID).parents; - switch (node.attrType) { + parentConnections.forEach(function (connection) { - 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': // usually associated with a Bone, however if a Bone was not created we'll make a Group instead - case 'Null': - default: - model = new THREE.Group(); - break; + var parent = modelMap.get(connection.ID); + if (parent !== undefined) parent.add(model); + }); - } + if (model.parent === null) { - model.name = THREE.PropertyBinding.sanitizeNodeName(node.attrName); - model.ID = id; - } + sceneGraph.add(model); + } + }); - this.setModelTransforms(model, node); - modelMap.set(id, model); - } + this.bindSkeleton(deformers.skeletons, geometryMap, modelMap); - return modelMap; - }, + this.createAmbientLight(); - buildSkeleton: function buildSkeleton(relationships, skeletons, id, name) { + this.setupMorphMaterials(); - var bone = null; + var animations = new AnimationParser().parse(); - relationships.parents.forEach(function (parent) { + // if all the models where already combined in a single group, just return that + if (sceneGraph.children.length === 1 && sceneGraph.children[0].isGroup) { - for (var ID in skeletons) { + sceneGraph.children[0].animations = animations; + sceneGraph = sceneGraph.children[0]; + } - var skeleton = skeletons[ID]; + sceneGraph.animations = animations; + }, - skeleton.rawBones.forEach(function (rawBone, i) { + // parse nodes in FBXTree.Objects.Model + parseModels: function parseModels(skeletons, geometryMap, materialMap) { - if (rawBone.ID === parent.ID) { + var modelMap = new Map(); + var modelNodes = fbxTree.Objects.Model; - var subBone = bone; - bone = new THREE.Bone(); - bone.matrixWorld.copy(rawBone.transformLink); + for (var nodeID in modelNodes) { - // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id - bone.name = THREE.PropertyBinding.sanitizeNodeName(name); - bone.ID = id; + var id = parseInt(nodeID); + var node = modelNodes[nodeID]; + var relationships = connections.get(id); - skeleton.bones[i] = bone; + var model = this.buildSkeleton(relationships, skeletons, id, node.attrName); - // 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) { + if (!model) { - bone.add(subBone); - } - } - }); - } - }); + switch (node.attrType) { - return bone; - }, + 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': // usually associated with a Bone, however if a Bone was not created we'll make a Group instead + case 'Null': + default: + model = new THREE.Group(); + break; - // create a THREE.PerspectiveCamera or THREE.OrthographicCamera - createCamera: function createCamera(relationships) { + } - var model; - var cameraAttribute; + model.name = THREE.PropertyBinding.sanitizeNodeName(node.attrName); + model.ID = id; + } - relationships.children.forEach(function (child) { + this.setModelTransforms(model, node); + modelMap.set(id, model); + } - var attr = fbxTree.Objects.NodeAttribute[child.ID]; + return modelMap; + }, - if (attr !== undefined) { + buildSkeleton: function buildSkeleton(relationships, skeletons, id, name) { - cameraAttribute = attr; - } - }); + var bone = null; - if (cameraAttribute === undefined) { + relationships.parents.forEach(function (parent) { - model = new THREE.Object3D(); - } else { + for (var ID in skeletons) { - var type = 0; - if (cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1) { + var skeleton = skeletons[ID]; - type = 1; - } + skeleton.rawBones.forEach(function (rawBone, i) { - var nearClippingPlane = 1; - if (cameraAttribute.NearPlane !== undefined) { + if (rawBone.ID === parent.ID) { - nearClippingPlane = cameraAttribute.NearPlane.value / 1000; - } + var subBone = bone; + bone = new THREE.Bone(); + bone.matrixWorld.copy(rawBone.transformLink); - var farClippingPlane = 1000; - if (cameraAttribute.FarPlane !== undefined) { + // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id + bone.name = THREE.PropertyBinding.sanitizeNodeName(name); + bone.ID = id; - farClippingPlane = cameraAttribute.FarPlane.value / 1000; - } + skeleton.bones[i] = bone; - var width = window.innerWidth; - var height = window.innerHeight; + // 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) { - if (cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined) { + bone.add(subBone); + } + } + }); + } + }); - width = cameraAttribute.AspectWidth.value; - height = cameraAttribute.AspectHeight.value; - } + return bone; + }, - var aspect = width / height; + // create a THREE.PerspectiveCamera or THREE.OrthographicCamera + createCamera: function createCamera(relationships) { - var fov = 45; - if (cameraAttribute.FieldOfView !== undefined) { + var model; + var cameraAttribute; - fov = cameraAttribute.FieldOfView.value; - } + relationships.children.forEach(function (child) { - var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null; + var attr = fbxTree.Objects.NodeAttribute[child.ID]; - switch (type) { + if (attr !== undefined) { - case 0: - // Perspective - model = new THREE.PerspectiveCamera(fov, aspect, nearClippingPlane, farClippingPlane); - if (focalLength !== null) model.setFocalLength(focalLength); - break; + cameraAttribute = attr; + } + }); - case 1: - // Orthographic - model = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, nearClippingPlane, farClippingPlane); - break; + if (cameraAttribute === undefined) { - default: - console.warn('THREE.FBXLoader: Unknown camera type ' + type + '.'); - model = new THREE.Object3D(); - break; + model = new THREE.Object3D(); + } else { - } - } + var type = 0; + if (cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1) { - return model; - }, + type = 1; + } - // Create a THREE.DirectionalLight, THREE.PointLight or THREE.SpotLight - createLight: function createLight(relationships) { + var nearClippingPlane = 1; + if (cameraAttribute.NearPlane !== undefined) { - var model; - var lightAttribute; + nearClippingPlane = cameraAttribute.NearPlane.value / 1000; + } - relationships.children.forEach(function (child) { + var farClippingPlane = 1000; + if (cameraAttribute.FarPlane !== undefined) { - var attr = fbxTree.Objects.NodeAttribute[child.ID]; + farClippingPlane = cameraAttribute.FarPlane.value / 1000; + } - if (attr !== undefined) { + var width = window.innerWidth; + var height = window.innerHeight; - lightAttribute = attr; - } - }); + if (cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined) { - if (lightAttribute === undefined) { + width = cameraAttribute.AspectWidth.value; + height = cameraAttribute.AspectHeight.value; + } - model = new THREE.Object3D(); - } else { + var aspect = width / height; - var type; + var fov = 45; + if (cameraAttribute.FieldOfView !== undefined) { - // LightType can be undefined for Point lights - if (lightAttribute.LightType === undefined) { + fov = cameraAttribute.FieldOfView.value; + } - type = 0; - } else { + var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null; - type = lightAttribute.LightType.value; - } + switch (type) { - var color = 0xffffff; + case 0: + // Perspective + model = new THREE.PerspectiveCamera(fov, aspect, nearClippingPlane, farClippingPlane); + if (focalLength !== null) model.setFocalLength(focalLength); + break; - if (lightAttribute.Color !== undefined) { + case 1: + // Orthographic + model = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, nearClippingPlane, farClippingPlane); + break; - color = new THREE.Color().fromArray(lightAttribute.Color.value); - } + default: + console.warn('THREE.FBXLoader: Unknown camera type ' + type + '.'); + model = new THREE.Object3D(); + break; - var intensity = lightAttribute.Intensity === undefined ? 1 : lightAttribute.Intensity.value / 100; + } + } - // light disabled - if (lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0) { + return model; + }, - intensity = 0; - } + // Create a THREE.DirectionalLight, THREE.PointLight or THREE.SpotLight + createLight: function createLight(relationships) { - var distance = 0; - if (lightAttribute.FarAttenuationEnd !== undefined) { + var model; + var lightAttribute; - if (lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0) { + relationships.children.forEach(function (child) { - distance = 0; - } else { + var attr = fbxTree.Objects.NodeAttribute[child.ID]; - distance = lightAttribute.FarAttenuationEnd.value; - } - } + if (attr !== undefined) { - // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd? - var decay = 1; + lightAttribute = attr; + } + }); - switch (type) { + if (lightAttribute === undefined) { - case 0: - // Point - model = new THREE.PointLight(color, intensity, distance, decay); - break; + model = new THREE.Object3D(); + } else { - case 1: - // Directional - model = new THREE.DirectionalLight(color, intensity); - break; + var type; - case 2: - // Spot - var angle = Math.PI / 3; + // LightType can be undefined for Point lights + if (lightAttribute.LightType === undefined) { - if (lightAttribute.InnerAngle !== undefined) { + type = 0; + } else { - angle = THREE.MathUtils.degToRad(lightAttribute.InnerAngle.value); - } + type = lightAttribute.LightType.value; + } - var penumbra = 0; - if (lightAttribute.OuterAngle !== undefined) { + var color = 0xffffff; - // 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); - } + if (lightAttribute.Color !== undefined) { - model = new THREE.SpotLight(color, intensity, distance, angle, penumbra, decay); - break; + color = new THREE.Color().fromArray(lightAttribute.Color.value); + } - default: - console.warn('THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a THREE.PointLight.'); - model = new THREE.PointLight(color, intensity); - break; + var intensity = lightAttribute.Intensity === undefined ? 1 : lightAttribute.Intensity.value / 100; - } + // light disabled + if (lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0) { - if (lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1) { + intensity = 0; + } - model.castShadow = true; - } - } + var distance = 0; + if (lightAttribute.FarAttenuationEnd !== undefined) { - return model; - }, + if (lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0) { - createMesh: function createMesh(relationships, geometryMap, materialMap) { + distance = 0; + } else { - var model; - var geometry = null; - var material = null; - var materials = []; + distance = lightAttribute.FarAttenuationEnd.value; + } + } - // get geometry and materials(s) from connections - relationships.children.forEach(function (child) { + // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd? + var decay = 1; - if (geometryMap.has(child.ID)) { + switch (type) { - geometry = geometryMap.get(child.ID); - } + case 0: + // Point + model = new THREE.PointLight(color, intensity, distance, decay); + break; - if (materialMap.has(child.ID)) { + case 1: + // Directional + model = new THREE.DirectionalLight(color, intensity); + break; - materials.push(materialMap.get(child.ID)); - } - }); + case 2: + // Spot + var angle = Math.PI / 3; - if (materials.length > 1) { + if (lightAttribute.InnerAngle !== undefined) { - material = materials; - } else if (materials.length > 0) { + angle = THREE.MathUtils.degToRad(lightAttribute.InnerAngle.value); + } - material = materials[0]; - } else { + var penumbra = 0; + if (lightAttribute.OuterAngle !== undefined) { - material = new THREE.MeshPhongMaterial({ color: 0xcccccc }); - materials.push(material); - } + // 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); + } - if ('color' in geometry.attributes) { + model = new THREE.SpotLight(color, intensity, distance, angle, penumbra, decay); + break; - materials.forEach(function (material) { + default: + console.warn('THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a THREE.PointLight.'); + model = new THREE.PointLight(color, intensity); + break; - material.vertexColors = THREE.VertexColors; - }); - } + } - if (geometry.FBX_Deformer) { + if (lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1) { - materials.forEach(function (material) { + model.castShadow = true; + } + } - material.skinning = true; - }); + return model; + }, - model = new THREE.SkinnedMesh(geometry, material); - } else { + createMesh: function createMesh(relationships, geometryMap, materialMap) { - model = new THREE.Mesh(geometry, material); - } + var model; + var geometry = null; + var material = null; + var materials = []; - return model; - }, + // get geometry and materials(s) from connections + relationships.children.forEach(function (child) { - createCurve: function createCurve(relationships, geometryMap) { + if (geometryMap.has(child.ID)) { - var geometry = relationships.children.reduce(function (geo, child) { + geometry = geometryMap.get(child.ID); + } - if (geometryMap.has(child.ID)) geo = geometryMap.get(child.ID); + if (materialMap.has(child.ID)) { - return geo; - }, null); + materials.push(materialMap.get(child.ID)); + } + }); - // 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); - }, + if (materials.length > 1) { - // parse the model node for transform details and apply them to the model - setModelTransforms: function setModelTransforms(model, modelNode) { + material = materials; + } else if (materials.length > 0) { - var transformData = {}; + material = materials[0]; + } else { - if ('RotationOrder' in modelNode) transformData.eulerOrder = parseInt(modelNode.RotationOrder.value); - if ('Lcl_Translation' in modelNode) transformData.translation = modelNode.Lcl_Translation.value; - if ('RotationOffset' in modelNode) transformData.rotationOffset = modelNode.RotationOffset.value; - if ('Lcl_Rotation' in modelNode) transformData.rotation = modelNode.Lcl_Rotation.value; - if ('PreRotation' in modelNode) transformData.preRotation = modelNode.PreRotation.value; - if ('PostRotation' in modelNode) transformData.postRotation = modelNode.PostRotation.value; - if ('Lcl_Scaling' in modelNode) transformData.scale = modelNode.Lcl_Scaling.value; + material = new THREE.MeshPhongMaterial({ color: 0xcccccc }); + materials.push(material); + } - var transform = generateTransform(transformData); + if ('color' in geometry.attributes) { - model.applyMatrix(transform); - }, + materials.forEach(function (material) { - setLookAtProperties: function setLookAtProperties(model, modelNode) { + material.vertexColors = THREE.VertexColors; + }); + } - if ('LookAtProperty' in modelNode) { + if (geometry.FBX_Deformer) { - var children = connections.get(model.ID).children; + materials.forEach(function (material) { - children.forEach(function (child) { + material.skinning = true; + }); - if (child.relationship === 'LookAtProperty') { + model = new THREE.SkinnedMesh(geometry, material); + } else { - var lookAtTarget = fbxTree.Objects.Model[child.ID]; + model = new THREE.Mesh(geometry, material); + } - if ('Lcl_Translation' in lookAtTarget) { + return model; + }, - var pos = lookAtTarget.Lcl_Translation.value; + createCurve: function createCurve(relationships, geometryMap) { - // DirectionalLight, SpotLight - if (model.target !== undefined) { + var geometry = relationships.children.reduce(function (geo, child) { - model.target.position.fromArray(pos); - sceneGraph.add(model.target); - } else { - // Cameras and other Object3Ds + if (geometryMap.has(child.ID)) geo = geometryMap.get(child.ID); - model.lookAt(new THREE.Vector3().fromArray(pos)); - } - } - } - }); - } - }, + return geo; + }, null); - bindSkeleton: function bindSkeleton(skeletons, geometryMap, modelMap) { + // 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); + }, - var bindMatrices = this.parsePoseNodes(); + // parse the model node for transform details and apply them to the model + setModelTransforms: function setModelTransforms(model, modelNode) { - for (var ID in skeletons) { + var transformData = {}; - var skeleton = skeletons[ID]; + if ('RotationOrder' in modelNode) transformData.eulerOrder = parseInt(modelNode.RotationOrder.value); + if ('Lcl_Translation' in modelNode) transformData.translation = modelNode.Lcl_Translation.value; + if ('RotationOffset' in modelNode) transformData.rotationOffset = modelNode.RotationOffset.value; + if ('Lcl_Rotation' in modelNode) transformData.rotation = modelNode.Lcl_Rotation.value; + if ('PreRotation' in modelNode) transformData.preRotation = modelNode.PreRotation.value; + if ('PostRotation' in modelNode) transformData.postRotation = modelNode.PostRotation.value; + if ('Lcl_Scaling' in modelNode) transformData.scale = modelNode.Lcl_Scaling.value; - var parents = connections.get(parseInt(skeleton.ID)).parents; + var transform = generateTransform(transformData); - parents.forEach(function (parent) { + model.applyMatrix(transform); + }, - if (geometryMap.has(parent.ID)) { + setLookAtProperties: function setLookAtProperties(model, modelNode) { - var geoID = parent.ID; - var geoRelationships = connections.get(geoID); + if ('LookAtProperty' in modelNode) { - geoRelationships.parents.forEach(function (geoConnParent) { + var children = connections.get(model.ID).children; - if (modelMap.has(geoConnParent.ID)) { + children.forEach(function (child) { - var model = modelMap.get(geoConnParent.ID); + if (child.relationship === 'LookAtProperty') { - model.bind(new THREE.Skeleton(skeleton.bones), bindMatrices[geoConnParent.ID]); - } - }); - } - }); - } - }, + var lookAtTarget = fbxTree.Objects.Model[child.ID]; - parsePoseNodes: function parsePoseNodes() { + if ('Lcl_Translation' in lookAtTarget) { - var bindMatrices = {}; + var pos = lookAtTarget.Lcl_Translation.value; - if ('Pose' in fbxTree.Objects) { + // DirectionalLight, SpotLight + if (model.target !== undefined) { - var BindPoseNode = fbxTree.Objects.Pose; + model.target.position.fromArray(pos); + sceneGraph.add(model.target); + } else { + // Cameras and other Object3Ds - for (var nodeID in BindPoseNode) { + model.lookAt(new THREE.Vector3().fromArray(pos)); + } + } + } + }); + } + }, - if (BindPoseNode[nodeID].attrType === 'BindPose') { + bindSkeleton: function bindSkeleton(skeletons, geometryMap, modelMap) { - var poseNodes = BindPoseNode[nodeID].PoseNode; + var bindMatrices = this.parsePoseNodes(); - if (Array.isArray(poseNodes)) { + for (var ID in skeletons) { - poseNodes.forEach(function (poseNode) { + var skeleton = skeletons[ID]; - bindMatrices[poseNode.Node] = new THREE.Matrix4().fromArray(poseNode.Matrix.a); - }); - } else { + var parents = connections.get(parseInt(skeleton.ID)).parents; - bindMatrices[poseNodes.Node] = new THREE.Matrix4().fromArray(poseNodes.Matrix.a); - } - } - } - } + parents.forEach(function (parent) { - return bindMatrices; - }, + if (geometryMap.has(parent.ID)) { - // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light - createAmbientLight: function createAmbientLight() { + var geoID = parent.ID; + var geoRelationships = connections.get(geoID); - if ('GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings) { + geoRelationships.parents.forEach(function (geoConnParent) { - var ambientColor = fbxTree.GlobalSettings.AmbientColor.value; - var r = ambientColor[0]; - var g = ambientColor[1]; - var b = ambientColor[2]; + if (modelMap.has(geoConnParent.ID)) { - if (r !== 0 || g !== 0 || b !== 0) { + var model = modelMap.get(geoConnParent.ID); - var color = new THREE.Color(r, g, b); - sceneGraph.add(new THREE.AmbientLight(color, 1)); - } - } - }, + model.bind(new THREE.Skeleton(skeleton.bones), bindMatrices[geoConnParent.ID]); + } + }); + } + }); + } + }, - setupMorphMaterials: function setupMorphMaterials() { + parsePoseNodes: function parsePoseNodes() { - sceneGraph.traverse(function (child) { + var bindMatrices = {}; - if (child.isMesh) { + if ('Pose' in fbxTree.Objects) { - if (child.geometry.morphAttributes.position || child.geometry.morphAttributes.normal) { + var BindPoseNode = fbxTree.Objects.Pose; - var uuid = child.uuid; - var matUuid = child.material.uuid; + for (var nodeID in BindPoseNode) { - // if a geometry has morph targets, it cannot share the material with other geometries - var sharedMat = false; + if (BindPoseNode[nodeID].attrType === 'BindPose') { - sceneGraph.traverse(function (child) { + var poseNodes = BindPoseNode[nodeID].PoseNode; - if (child.isMesh) { + if (Array.isArray(poseNodes)) { - if (child.material.uuid === matUuid && child.uuid !== uuid) sharedMat = true; - } - }); + poseNodes.forEach(function (poseNode) { - if (sharedMat === true) child.material = child.material.clone(); + bindMatrices[poseNode.Node] = new THREE.Matrix4().fromArray(poseNode.Matrix.a); + }); + } else { - child.material.morphTargets = true; - } - } - }); + bindMatrices[poseNodes.Node] = new THREE.Matrix4().fromArray(poseNodes.Matrix.a); } + } + } + } - }; - - // 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; + return bindMatrices; + }, - } - }, + // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light + createAmbientLight: function createAmbientLight() { - // Parse single node mesh geometry in FBXTree.Objects.Geometry - parseMeshGeometry: function parseMeshGeometry(relationships, geoNode, deformers) { + if ('GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings) { - var skeletons = deformers.skeletons; - var morphTargets = deformers.morphTargets; + var ambientColor = fbxTree.GlobalSettings.AmbientColor.value; + var r = ambientColor[0]; + var g = ambientColor[1]; + var b = ambientColor[2]; - var modelNodes = relationships.parents.map(function (parent) { + if (r !== 0 || g !== 0 || b !== 0) { - return fbxTree.Objects.Model[parent.ID]; - }); + var color = new THREE.Color(r, g, b); + sceneGraph.add(new THREE.AmbientLight(color, 1)); + } + } + }, - // don't create geometry if it is not associated with any models - if (modelNodes.length === 0) return; + setupMorphMaterials: function setupMorphMaterials() { - var skeleton = relationships.children.reduce(function (skeleton, child) { + sceneGraph.traverse(function (child) { - if (skeletons[child.ID] !== undefined) skeleton = skeletons[child.ID]; + if (child.isMesh) { - return skeleton; - }, null); + if (child.geometry.morphAttributes.position || child.geometry.morphAttributes.normal) { - var morphTarget = relationships.children.reduce(function (morphTarget, child) { + var uuid = child.uuid; + var matUuid = child.material.uuid; - if (morphTargets[child.ID] !== undefined) morphTarget = morphTargets[child.ID]; + // if a geometry has morph targets, it cannot share the material with other geometries + var sharedMat = false; - return morphTarget; - }, null); + sceneGraph.traverse(function (child) { - // TODO: if there is more than one model associated with the geometry, AND the models have - // different geometric transforms, then this will cause problems - // if ( modelNodes.length > 1 ) { } + if (child.isMesh) { - // For now just assume one model and get the preRotations from that - var modelNode = modelNodes[0]; + if (child.material.uuid === matUuid && child.uuid !== uuid) sharedMat = true; + } + }); - var transformData = {}; + if (sharedMat === true) child.material = child.material.clone(); - if ('RotationOrder' in modelNode) transformData.eulerOrder = modelNode.RotationOrder.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; + child.material.morphTargets = true; + } + } + }); + } - var transform = generateTransform(transformData); + }; - return this.genGeometry(geoNode, skeleton, morphTarget, transform); - }, + // parse Geometry data from FBXTree and return map of BufferGeometries + function GeometryParser() {} - // Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry - genGeometry: function genGeometry(geoNode, skeleton, morphTarget, preTransform) { + GeometryParser.prototype = { - var geo = new THREE.BufferGeometry(); - if (geoNode.attrName) geo.name = geoNode.attrName; + constructor: GeometryParser, - var geoInfo = this.parseGeoNode(geoNode, skeleton); - var buffers = this.genBuffers(geoInfo); + // Parse nodes in FBXTree.Objects.Geometry + parse: function parse(deformers) { - var positionAttribute = new THREE.Float32BufferAttribute(buffers.vertex, 3); + var geometryMap = new Map(); - preTransform.applyToBufferAttribute(positionAttribute); + if ('Geometry' in fbxTree.Objects) { - geo.addAttribute('position', positionAttribute); + var geoNodes = fbxTree.Objects.Geometry; - if (buffers.colors.length > 0) { + for (var nodeID in geoNodes) { - geo.addAttribute('color', new THREE.Float32BufferAttribute(buffers.colors, 3)); - } + var relationships = connections.get(parseInt(nodeID)); + var geo = this.parseGeometry(relationships, geoNodes[nodeID], deformers); - if (skeleton) { + geometryMap.set(parseInt(nodeID), geo); + } + } - geo.addAttribute('skinIndex', new THREE.Uint16BufferAttribute(buffers.weightsIndices, 4)); + return geometryMap; + }, - geo.addAttribute('skinWeight', new THREE.Float32BufferAttribute(buffers.vertexWeights, 4)); + // Parse single node in FBXTree.Objects.Geometry + parseGeometry: function parseGeometry(relationships, geoNode, deformers) { - // used later to bind the skeleton to the model - geo.FBX_Deformer = skeleton; - } + switch (geoNode.attrType) { - if (buffers.normal.length > 0) { + case 'Mesh': + return this.parseMeshGeometry(relationships, geoNode, deformers); + break; - var normalAttribute = new THREE.Float32BufferAttribute(buffers.normal, 3); + case 'NurbsCurve': + return this.parseNurbsGeometry(geoNode); + break; - var normalMatrix = new THREE.Matrix3().getNormalMatrix(preTransform); - normalMatrix.applyToBufferAttribute(normalAttribute); + } + }, - geo.addAttribute('normal', normalAttribute); - } + // Parse single node mesh geometry in FBXTree.Objects.Geometry + parseMeshGeometry: function parseMeshGeometry(relationships, geoNode, deformers) { - buffers.uvs.forEach(function (uvBuffer, i) { + var skeletons = deformers.skeletons; + var morphTargets = deformers.morphTargets; - // subsequent uv buffers are called 'uv1', 'uv2', ... - var name = 'uv' + (i + 1).toString(); + var modelNodes = relationships.parents.map(function (parent) { - // the first uv buffer is just called 'uv' - if (i === 0) { + return fbxTree.Objects.Model[parent.ID]; + }); - name = 'uv'; - } + // don't create geometry if it is not associated with any models + if (modelNodes.length === 0) return; - geo.addAttribute(name, new THREE.Float32BufferAttribute(buffers.uvs[i], 2)); - }); + var skeleton = relationships.children.reduce(function (skeleton, child) { - if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { + if (skeletons[child.ID] !== undefined) skeleton = skeletons[child.ID]; - // Convert the material indices of each vertex into rendering groups on the geometry. - var prevMaterialIndex = buffers.materialIndex[0]; - var startIndex = 0; + return skeleton; + }, null); - buffers.materialIndex.forEach(function (currentIndex, i) { + var morphTarget = relationships.children.reduce(function (morphTarget, child) { - if (currentIndex !== prevMaterialIndex) { + if (morphTargets[child.ID] !== undefined) morphTarget = morphTargets[child.ID]; - geo.addGroup(startIndex, i - startIndex, prevMaterialIndex); + return morphTarget; + }, null); - prevMaterialIndex = currentIndex; - startIndex = i; - } - }); + // TODO: if there is more than one model associated with the geometry, AND the models have + // different geometric transforms, then this will cause problems + // if ( modelNodes.length > 1 ) { } - // the loop above doesn't add the last group, do that here. - if (geo.groups.length > 0) { + // For now just assume one model and get the preRotations from that + var modelNode = modelNodes[0]; - var lastGroup = geo.groups[geo.groups.length - 1]; - var lastIndex = lastGroup.start + lastGroup.count; + var transformData = {}; - if (lastIndex !== buffers.materialIndex.length) { + if ('RotationOrder' in modelNode) transformData.eulerOrder = modelNode.RotationOrder.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; - geo.addGroup(lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex); - } - } + var transform = generateTransform(transformData); - // case where there are multiple materials but the whole geometry is only - // using one of them - if (geo.groups.length === 0) { + return this.genGeometry(geoNode, skeleton, morphTarget, transform); + }, - geo.addGroup(0, buffers.materialIndex.length, buffers.materialIndex[0]); - } - } + // Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry + genGeometry: function genGeometry(geoNode, skeleton, morphTarget, preTransform) { - this.addMorphTargets(geo, geoNode, morphTarget, preTransform); + var geo = new THREE.BufferGeometry(); + if (geoNode.attrName) geo.name = geoNode.attrName; - return geo; - }, + var geoInfo = this.parseGeoNode(geoNode, skeleton); + var buffers = this.genBuffers(geoInfo); - parseGeoNode: function parseGeoNode(geoNode, skeleton) { + var positionAttribute = new THREE.Float32BufferAttribute(buffers.vertex, 3); - var geoInfo = {}; + preTransform.applyToBufferAttribute(positionAttribute); - geoInfo.vertexPositions = geoNode.Vertices !== undefined ? geoNode.Vertices.a : []; - geoInfo.vertexIndices = geoNode.PolygonVertexIndex !== undefined ? geoNode.PolygonVertexIndex.a : []; + geo.addAttribute('position', positionAttribute); - if (geoNode.LayerElementColor) { + if (buffers.colors.length > 0) { - geoInfo.color = this.parseVertexColors(geoNode.LayerElementColor[0]); - } + geo.addAttribute('color', new THREE.Float32BufferAttribute(buffers.colors, 3)); + } - if (geoNode.LayerElementMaterial) { + if (skeleton) { - geoInfo.material = this.parseMaterialIndices(geoNode.LayerElementMaterial[0]); - } + geo.addAttribute('skinIndex', new THREE.Uint16BufferAttribute(buffers.weightsIndices, 4)); - if (geoNode.LayerElementNormal) { + geo.addAttribute('skinWeight', new THREE.Float32BufferAttribute(buffers.vertexWeights, 4)); - geoInfo.normal = this.parseNormals(geoNode.LayerElementNormal[0]); - } + // used later to bind the skeleton to the model + geo.FBX_Deformer = skeleton; + } - if (geoNode.LayerElementUV) { + if (buffers.normal.length > 0) { - geoInfo.uv = []; + var normalAttribute = new THREE.Float32BufferAttribute(buffers.normal, 3); - var i = 0; - while (geoNode.LayerElementUV[i]) { + var normalMatrix = new THREE.Matrix3().getNormalMatrix(preTransform); + normalMatrix.applyToBufferAttribute(normalAttribute); - geoInfo.uv.push(this.parseUVs(geoNode.LayerElementUV[i])); - i++; - } - } + geo.addAttribute('normal', normalAttribute); + } - geoInfo.weightTable = {}; + buffers.uvs.forEach(function (uvBuffer, i) { - if (skeleton !== null) { + // subsequent uv buffers are called 'uv1', 'uv2', ... + var name = 'uv' + (i + 1).toString(); - geoInfo.skeleton = skeleton; + // the first uv buffer is just called 'uv' + if (i === 0) { - skeleton.rawBones.forEach(function (rawBone, i) { + name = 'uv'; + } - // loop over the bone's vertex indices and weights - rawBone.indices.forEach(function (index, j) { + geo.addAttribute(name, new THREE.Float32BufferAttribute(buffers.uvs[i], 2)); + }); - if (geoInfo.weightTable[index] === undefined) geoInfo.weightTable[index] = []; + if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { - geoInfo.weightTable[index].push({ + // Convert the material indices of each vertex into rendering groups on the geometry. + var prevMaterialIndex = buffers.materialIndex[0]; + var startIndex = 0; - id: i, - weight: rawBone.weights[j] + buffers.materialIndex.forEach(function (currentIndex, i) { - }); - }); - }); - } + if (currentIndex !== prevMaterialIndex) { - return geoInfo; - }, + geo.addGroup(startIndex, i - startIndex, prevMaterialIndex); - genBuffers: function genBuffers(geoInfo) { + prevMaterialIndex = currentIndex; + startIndex = i; + } + }); - var buffers = { - vertex: [], - normal: [], - colors: [], - uvs: [], - materialIndex: [], - vertexWeights: [], - weightsIndices: [] - }; + // the loop above doesn't add the last group, do that here. + if (geo.groups.length > 0) { - var polygonIndex = 0; - var faceLength = 0; - var displayedWeightsWarning = false; + var lastGroup = geo.groups[geo.groups.length - 1]; + var lastIndex = lastGroup.start + lastGroup.count; - // these will hold data for a single face - var facePositionIndexes = []; - var faceNormals = []; - var faceColors = []; - var faceUVs = []; - var faceWeights = []; - var faceWeightIndices = []; + if (lastIndex !== buffers.materialIndex.length) { - var self = this; - geoInfo.vertexIndices.forEach(function (vertexIndex, polygonVertexIndex) { + geo.addGroup(lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex); + } + } - var endOfFace = false; + // case where there are multiple materials but the whole geometry is only + // using one of them + if (geo.groups.length === 0) { - // 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) { + geo.addGroup(0, buffers.materialIndex.length, buffers.materialIndex[0]); + } + } - vertexIndex = vertexIndex ^ -1; // equivalent to ( x * -1 ) - 1 - endOfFace = true; - } + this.addMorphTargets(geo, geoNode, morphTarget, preTransform); - var weightIndices = []; - var weights = []; + return geo; + }, - facePositionIndexes.push(vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2); + parseGeoNode: function parseGeoNode(geoNode, skeleton) { - if (geoInfo.color) { + var geoInfo = {}; - var data = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color); + geoInfo.vertexPositions = geoNode.Vertices !== undefined ? geoNode.Vertices.a : []; + geoInfo.vertexIndices = geoNode.PolygonVertexIndex !== undefined ? geoNode.PolygonVertexIndex.a : []; - faceColors.push(data[0], data[1], data[2]); - } + if (geoNode.LayerElementColor) { - if (geoInfo.skeleton) { + geoInfo.color = this.parseVertexColors(geoNode.LayerElementColor[0]); + } - if (geoInfo.weightTable[vertexIndex] !== undefined) { + if (geoNode.LayerElementMaterial) { - geoInfo.weightTable[vertexIndex].forEach(function (wt) { + geoInfo.material = this.parseMaterialIndices(geoNode.LayerElementMaterial[0]); + } - weights.push(wt.weight); - weightIndices.push(wt.id); - }); - } + if (geoNode.LayerElementNormal) { - if (weights.length > 4) { + geoInfo.normal = this.parseNormals(geoNode.LayerElementNormal[0]); + } - if (!displayedWeightsWarning) { + if (geoNode.LayerElementUV) { - console.warn('THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.'); - displayedWeightsWarning = true; - } + geoInfo.uv = []; - var wIndex = [0, 0, 0, 0]; - var Weight = [0, 0, 0, 0]; + var i = 0; + while (geoNode.LayerElementUV[i]) { - weights.forEach(function (weight, weightIndex) { + geoInfo.uv.push(this.parseUVs(geoNode.LayerElementUV[i])); + i++; + } + } - var currentWeight = weight; - var currentIndex = weightIndices[weightIndex]; + geoInfo.weightTable = {}; - Weight.forEach(function (comparedWeight, comparedWeightIndex, comparedWeightArray) { + if (skeleton !== null) { - if (currentWeight > comparedWeight) { + geoInfo.skeleton = skeleton; - comparedWeightArray[comparedWeightIndex] = currentWeight; - currentWeight = comparedWeight; + skeleton.rawBones.forEach(function (rawBone, i) { - var tmp = wIndex[comparedWeightIndex]; - wIndex[comparedWeightIndex] = currentIndex; - currentIndex = tmp; - } - }); - }); + // loop over the bone's vertex indices and weights + rawBone.indices.forEach(function (index, j) { - weightIndices = wIndex; - weights = Weight; - } + if (geoInfo.weightTable[index] === undefined) geoInfo.weightTable[index] = []; - // if the weight array is shorter than 4 pad with 0s - while (weights.length < 4) { + geoInfo.weightTable[index].push({ - weights.push(0); - weightIndices.push(0); - } + id: i, + weight: rawBone.weights[j] - for (var i = 0; i < 4; ++i) { + }); + }); + }); + } - faceWeights.push(weights[i]); - faceWeightIndices.push(weightIndices[i]); - } - } + return geoInfo; + }, - if (geoInfo.normal) { + genBuffers: function genBuffers(geoInfo) { - var data = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal); + var buffers = { + vertex: [], + normal: [], + colors: [], + uvs: [], + materialIndex: [], + vertexWeights: [], + weightsIndices: [] + }; - faceNormals.push(data[0], data[1], data[2]); - } + var polygonIndex = 0; + var faceLength = 0; + var displayedWeightsWarning = false; - if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { + // these will hold data for a single face + var facePositionIndexes = []; + var faceNormals = []; + var faceColors = []; + var faceUVs = []; + var faceWeights = []; + var faceWeightIndices = []; - var materialIndex = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material)[0]; - } + var self = this; + geoInfo.vertexIndices.forEach(function (vertexIndex, polygonVertexIndex) { - if (geoInfo.uv) { + var endOfFace = false; - geoInfo.uv.forEach(function (uv, i) { + // 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) { - var data = getData(polygonVertexIndex, polygonIndex, vertexIndex, uv); + vertexIndex = vertexIndex ^ -1; // equivalent to ( x * -1 ) - 1 + endOfFace = true; + } - if (faceUVs[i] === undefined) { + var weightIndices = []; + var weights = []; - faceUVs[i] = []; - } + facePositionIndexes.push(vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2); - faceUVs[i].push(data[0]); - faceUVs[i].push(data[1]); - }); - } + if (geoInfo.color) { - faceLength++; + var data = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color); - if (endOfFace) { + faceColors.push(data[0], data[1], data[2]); + } - self.genFace(buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength); + if (geoInfo.skeleton) { - polygonIndex++; - faceLength = 0; + if (geoInfo.weightTable[vertexIndex] !== undefined) { - // reset arrays for the next face - facePositionIndexes = []; - faceNormals = []; - faceColors = []; - faceUVs = []; - faceWeights = []; - faceWeightIndices = []; - } - }); + geoInfo.weightTable[vertexIndex].forEach(function (wt) { - return buffers; - }, + weights.push(wt.weight); + weightIndices.push(wt.id); + }); + } - // Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris - genFace: function genFace(buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength) { + if (weights.length > 4) { - for (var i = 2; i < faceLength; i++) { + if (!displayedWeightsWarning) { - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[0]]); - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[1]]); - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[2]]); + console.warn('THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.'); + displayedWeightsWarning = true; + } - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3]]); - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3 + 1]]); - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3 + 2]]); + var wIndex = [0, 0, 0, 0]; + var Weight = [0, 0, 0, 0]; - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3]]); - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3 + 1]]); - buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3 + 2]]); + weights.forEach(function (weight, weightIndex) { - if (geoInfo.skeleton) { + var currentWeight = weight; + var currentIndex = weightIndices[weightIndex]; - buffers.vertexWeights.push(faceWeights[0]); - buffers.vertexWeights.push(faceWeights[1]); - buffers.vertexWeights.push(faceWeights[2]); - buffers.vertexWeights.push(faceWeights[3]); + Weight.forEach(function (comparedWeight, comparedWeightIndex, comparedWeightArray) { - buffers.vertexWeights.push(faceWeights[(i - 1) * 4]); - buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 1]); - buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 2]); - buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 3]); + if (currentWeight > comparedWeight) { - buffers.vertexWeights.push(faceWeights[i * 4]); - buffers.vertexWeights.push(faceWeights[i * 4 + 1]); - buffers.vertexWeights.push(faceWeights[i * 4 + 2]); - buffers.vertexWeights.push(faceWeights[i * 4 + 3]); + comparedWeightArray[comparedWeightIndex] = currentWeight; + currentWeight = comparedWeight; - buffers.weightsIndices.push(faceWeightIndices[0]); - buffers.weightsIndices.push(faceWeightIndices[1]); - buffers.weightsIndices.push(faceWeightIndices[2]); - buffers.weightsIndices.push(faceWeightIndices[3]); + var tmp = wIndex[comparedWeightIndex]; + wIndex[comparedWeightIndex] = currentIndex; + currentIndex = tmp; + } + }); + }); - buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4]); - buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 1]); - buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 2]); - buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 3]); + weightIndices = wIndex; + weights = Weight; + } - buffers.weightsIndices.push(faceWeightIndices[i * 4]); - buffers.weightsIndices.push(faceWeightIndices[i * 4 + 1]); - buffers.weightsIndices.push(faceWeightIndices[i * 4 + 2]); - buffers.weightsIndices.push(faceWeightIndices[i * 4 + 3]); - } + // if the weight array is shorter than 4 pad with 0s + while (weights.length < 4) { - if (geoInfo.color) { + weights.push(0); + weightIndices.push(0); + } - buffers.colors.push(faceColors[0]); - buffers.colors.push(faceColors[1]); - buffers.colors.push(faceColors[2]); + for (var i = 0; i < 4; ++i) { - buffers.colors.push(faceColors[(i - 1) * 3]); - buffers.colors.push(faceColors[(i - 1) * 3 + 1]); - buffers.colors.push(faceColors[(i - 1) * 3 + 2]); + faceWeights.push(weights[i]); + faceWeightIndices.push(weightIndices[i]); + } + } - buffers.colors.push(faceColors[i * 3]); - buffers.colors.push(faceColors[i * 3 + 1]); - buffers.colors.push(faceColors[i * 3 + 2]); - } + if (geoInfo.normal) { - if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { + var data = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal); - buffers.materialIndex.push(materialIndex); - buffers.materialIndex.push(materialIndex); - buffers.materialIndex.push(materialIndex); - } + faceNormals.push(data[0], data[1], data[2]); + } - if (geoInfo.normal) { + if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { - buffers.normal.push(faceNormals[0]); - buffers.normal.push(faceNormals[1]); - buffers.normal.push(faceNormals[2]); + var materialIndex = getData(polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material)[0]; + } - buffers.normal.push(faceNormals[(i - 1) * 3]); - buffers.normal.push(faceNormals[(i - 1) * 3 + 1]); - buffers.normal.push(faceNormals[(i - 1) * 3 + 2]); + if (geoInfo.uv) { - buffers.normal.push(faceNormals[i * 3]); - buffers.normal.push(faceNormals[i * 3 + 1]); - buffers.normal.push(faceNormals[i * 3 + 2]); - } + geoInfo.uv.forEach(function (uv, i) { - if (geoInfo.uv) { + var data = getData(polygonVertexIndex, polygonIndex, vertexIndex, uv); - geoInfo.uv.forEach(function (uv, j) { + if (faceUVs[i] === undefined) { - if (buffers.uvs[j] === undefined) buffers.uvs[j] = []; + faceUVs[i] = []; + } - buffers.uvs[j].push(faceUVs[j][0]); - buffers.uvs[j].push(faceUVs[j][1]); + faceUVs[i].push(data[0]); + faceUVs[i].push(data[1]); + }); + } - buffers.uvs[j].push(faceUVs[j][(i - 1) * 2]); - buffers.uvs[j].push(faceUVs[j][(i - 1) * 2 + 1]); + faceLength++; - buffers.uvs[j].push(faceUVs[j][i * 2]); - buffers.uvs[j].push(faceUVs[j][i * 2 + 1]); - }); - } - } - }, + if (endOfFace) { - addMorphTargets: function addMorphTargets(parentGeo, parentGeoNode, morphTarget, preTransform) { + self.genFace(buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength); - if (morphTarget === null) return; + polygonIndex++; + faceLength = 0; - parentGeo.morphAttributes.position = []; - parentGeo.morphAttributes.normal = []; + // reset arrays for the next face + facePositionIndexes = []; + faceNormals = []; + faceColors = []; + faceUVs = []; + faceWeights = []; + faceWeightIndices = []; + } + }); - var self = this; - morphTarget.rawTargets.forEach(function (rawTarget) { + return buffers; + }, - var morphGeoNode = fbxTree.Objects.Geometry[rawTarget.geoID]; + // Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris + genFace: function genFace(buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength) { - if (morphGeoNode !== undefined) { + for (var i = 2; i < faceLength; i++) { - self.genMorphGeometry(parentGeo, parentGeoNode, morphGeoNode, preTransform); - } - }); - }, + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[0]]); + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[1]]); + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[2]]); - // a morph geometry node is similar to a standard node, and the node is also contained - // in FBXTree.Objects.Geometry, however it can only have attributes for position, normal - // and a special attribute Index defining which vertices of the original geometry are affected - // Normal and position attributes only have data for the vertices that are affected by the morph - genMorphGeometry: function genMorphGeometry(parentGeo, parentGeoNode, morphGeoNode, preTransform) { + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3]]); + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3 + 1]]); + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[(i - 1) * 3 + 2]]); - var morphGeo = new THREE.BufferGeometry(); - if (morphGeoNode.attrName) morphGeo.name = morphGeoNode.attrName; + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3]]); + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3 + 1]]); + buffers.vertex.push(geoInfo.vertexPositions[facePositionIndexes[i * 3 + 2]]); - var vertexIndices = parentGeoNode.PolygonVertexIndex !== undefined ? parentGeoNode.PolygonVertexIndex.a : []; + if (geoInfo.skeleton) { - // make a copy of the parent's vertex positions - var vertexPositions = parentGeoNode.Vertices !== undefined ? parentGeoNode.Vertices.a.slice() : []; + buffers.vertexWeights.push(faceWeights[0]); + buffers.vertexWeights.push(faceWeights[1]); + buffers.vertexWeights.push(faceWeights[2]); + buffers.vertexWeights.push(faceWeights[3]); - var morphPositions = morphGeoNode.Vertices !== undefined ? morphGeoNode.Vertices.a : []; - var indices = morphGeoNode.Indexes !== undefined ? morphGeoNode.Indexes.a : []; + buffers.vertexWeights.push(faceWeights[(i - 1) * 4]); + buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 1]); + buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 2]); + buffers.vertexWeights.push(faceWeights[(i - 1) * 4 + 3]); - for (var i = 0; i < indices.length; i++) { + buffers.vertexWeights.push(faceWeights[i * 4]); + buffers.vertexWeights.push(faceWeights[i * 4 + 1]); + buffers.vertexWeights.push(faceWeights[i * 4 + 2]); + buffers.vertexWeights.push(faceWeights[i * 4 + 3]); - var morphIndex = indices[i] * 3; + buffers.weightsIndices.push(faceWeightIndices[0]); + buffers.weightsIndices.push(faceWeightIndices[1]); + buffers.weightsIndices.push(faceWeightIndices[2]); + buffers.weightsIndices.push(faceWeightIndices[3]); - // FBX format uses blend shapes rather than morph targets. This can be converted - // by additively combining the blend shape positions with the original geometry's positions - vertexPositions[morphIndex] += morphPositions[i * 3]; - vertexPositions[morphIndex + 1] += morphPositions[i * 3 + 1]; - vertexPositions[morphIndex + 2] += morphPositions[i * 3 + 2]; - } + buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4]); + buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 1]); + buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 2]); + buffers.weightsIndices.push(faceWeightIndices[(i - 1) * 4 + 3]); - // TODO: add morph normal support - var morphGeoInfo = { - vertexIndices: vertexIndices, - vertexPositions: vertexPositions - }; + buffers.weightsIndices.push(faceWeightIndices[i * 4]); + buffers.weightsIndices.push(faceWeightIndices[i * 4 + 1]); + buffers.weightsIndices.push(faceWeightIndices[i * 4 + 2]); + buffers.weightsIndices.push(faceWeightIndices[i * 4 + 3]); + } - var morphBuffers = this.genBuffers(morphGeoInfo); + if (geoInfo.color) { - var positionAttribute = new THREE.Float32BufferAttribute(morphBuffers.vertex, 3); - positionAttribute.name = morphGeoNode.attrName; + buffers.colors.push(faceColors[0]); + buffers.colors.push(faceColors[1]); + buffers.colors.push(faceColors[2]); - preTransform.applyToBufferAttribute(positionAttribute); + buffers.colors.push(faceColors[(i - 1) * 3]); + buffers.colors.push(faceColors[(i - 1) * 3 + 1]); + buffers.colors.push(faceColors[(i - 1) * 3 + 2]); - parentGeo.morphAttributes.position.push(positionAttribute); - }, + buffers.colors.push(faceColors[i * 3]); + buffers.colors.push(faceColors[i * 3 + 1]); + buffers.colors.push(faceColors[i * 3 + 2]); + } - // Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists - parseNormals: function parseNormals(NormalNode) { + if (geoInfo.material && geoInfo.material.mappingType !== 'AllSame') { - var mappingType = NormalNode.MappingInformationType; - var referenceType = NormalNode.ReferenceInformationType; - var buffer = NormalNode.Normals.a; - var indexBuffer = []; - if (referenceType === 'IndexToDirect') { + buffers.materialIndex.push(materialIndex); + buffers.materialIndex.push(materialIndex); + buffers.materialIndex.push(materialIndex); + } - if ('NormalIndex' in NormalNode) { + if (geoInfo.normal) { - indexBuffer = NormalNode.NormalIndex.a; - } else if ('NormalsIndex' in NormalNode) { + buffers.normal.push(faceNormals[0]); + buffers.normal.push(faceNormals[1]); + buffers.normal.push(faceNormals[2]); - indexBuffer = NormalNode.NormalsIndex.a; - } - } + buffers.normal.push(faceNormals[(i - 1) * 3]); + buffers.normal.push(faceNormals[(i - 1) * 3 + 1]); + buffers.normal.push(faceNormals[(i - 1) * 3 + 2]); - return { - dataSize: 3, - buffer: buffer, - indices: indexBuffer, - mappingType: mappingType, - referenceType: referenceType - }; - }, - - // Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists - parseUVs: function parseUVs(UVNode) { - - var mappingType = UVNode.MappingInformationType; - var referenceType = UVNode.ReferenceInformationType; - var buffer = UVNode.UV.a; - var indexBuffer = []; - if (referenceType === 'IndexToDirect') { - - indexBuffer = UVNode.UVIndex.a; - } - - return { - dataSize: 2, - buffer: buffer, - indices: indexBuffer, - mappingType: mappingType, - referenceType: referenceType - }; - }, - - // Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists - parseVertexColors: function parseVertexColors(ColorNode) { - - var mappingType = ColorNode.MappingInformationType; - var referenceType = ColorNode.ReferenceInformationType; - var buffer = ColorNode.Colors.a; - var indexBuffer = []; - if (referenceType === 'IndexToDirect') { - - indexBuffer = ColorNode.ColorIndex.a; - } - - return { - dataSize: 4, - buffer: buffer, - indices: indexBuffer, - mappingType: mappingType, - referenceType: referenceType - }; - }, + buffers.normal.push(faceNormals[i * 3]); + buffers.normal.push(faceNormals[i * 3 + 1]); + buffers.normal.push(faceNormals[i * 3 + 2]); + } - // Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists - parseMaterialIndices: function parseMaterialIndices(MaterialNode) { + if (geoInfo.uv) { - var mappingType = MaterialNode.MappingInformationType; - var referenceType = MaterialNode.ReferenceInformationType; + geoInfo.uv.forEach(function (uv, j) { - if (mappingType === 'NoMappingInformation') { + if (buffers.uvs[j] === undefined) buffers.uvs[j] = []; - return { - dataSize: 1, - buffer: [0], - indices: [0], - mappingType: 'AllSame', - referenceType: referenceType - }; - } + buffers.uvs[j].push(faceUVs[j][0]); + buffers.uvs[j].push(faceUVs[j][1]); - var materialIndexBuffer = MaterialNode.Materials.a; + buffers.uvs[j].push(faceUVs[j][(i - 1) * 2]); + buffers.uvs[j].push(faceUVs[j][(i - 1) * 2 + 1]); - // 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 = []; + buffers.uvs[j].push(faceUVs[j][i * 2]); + buffers.uvs[j].push(faceUVs[j][i * 2 + 1]); + }); + } + } + }, - for (var i = 0; i < materialIndexBuffer.length; ++i) { + addMorphTargets: function addMorphTargets(parentGeo, parentGeoNode, morphTarget, preTransform) { - materialIndices.push(i); - } + if (morphTarget === null) return; - return { - dataSize: 1, - buffer: materialIndexBuffer, - indices: materialIndices, - mappingType: mappingType, - referenceType: referenceType - }; - }, + parentGeo.morphAttributes.position = []; + parentGeo.morphAttributes.normal = []; - // Generate a NurbGeometry from a node in FBXTree.Objects.Geometry - parseNurbsGeometry: function parseNurbsGeometry(geoNode) { + var self = this; + morphTarget.rawTargets.forEach(function (rawTarget) { - if (THREE.NURBSCurve === undefined) { + var morphGeoNode = fbxTree.Objects.Geometry[rawTarget.geoID]; - console.error('THREE.FBXLoader: The loader relies on THREE.NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.'); - return new THREE.BufferGeometry(); - } + if (morphGeoNode !== undefined) { - var order = parseInt(geoNode.Order); + self.genMorphGeometry(parentGeo, parentGeoNode, morphGeoNode, preTransform); + } + }); + }, - if (isNaN(order)) { + // a morph geometry node is similar to a standard node, and the node is also contained + // in FBXTree.Objects.Geometry, however it can only have attributes for position, normal + // and a special attribute Index defining which vertices of the original geometry are affected + // Normal and position attributes only have data for the vertices that are affected by the morph + genMorphGeometry: function genMorphGeometry(parentGeo, parentGeoNode, morphGeoNode, preTransform) { - console.error('THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id); - return new THREE.BufferGeometry(); - } + var morphGeo = new THREE.BufferGeometry(); + if (morphGeoNode.attrName) morphGeo.name = morphGeoNode.attrName; - var degree = order - 1; + var vertexIndices = parentGeoNode.PolygonVertexIndex !== undefined ? parentGeoNode.PolygonVertexIndex.a : []; - var knots = geoNode.KnotVector.a; - var controlPoints = []; - var pointsValues = geoNode.Points.a; + // make a copy of the parent's vertex positions + var vertexPositions = parentGeoNode.Vertices !== undefined ? parentGeoNode.Vertices.a.slice() : []; - for (var i = 0, l = pointsValues.length; i < l; i += 4) { + var morphPositions = morphGeoNode.Vertices !== undefined ? morphGeoNode.Vertices.a : []; + var indices = morphGeoNode.Indexes !== undefined ? morphGeoNode.Indexes.a : []; - controlPoints.push(new THREE.Vector4().fromArray(pointsValues, i)); - } + for (var i = 0; i < indices.length; i++) { - var startKnot, endKnot; + var morphIndex = indices[i] * 3; - if (geoNode.Form === 'Closed') { + // FBX format uses blend shapes rather than morph targets. This can be converted + // by additively combining the blend shape positions with the original geometry's positions + vertexPositions[morphIndex] += morphPositions[i * 3]; + vertexPositions[morphIndex + 1] += morphPositions[i * 3 + 1]; + vertexPositions[morphIndex + 2] += morphPositions[i * 3 + 2]; + } - controlPoints.push(controlPoints[0]); - } else if (geoNode.Form === 'Periodic') { + // TODO: add morph normal support + var morphGeoInfo = { + vertexIndices: vertexIndices, + vertexPositions: vertexPositions + }; - startKnot = degree; - endKnot = knots.length - 1 - startKnot; + var morphBuffers = this.genBuffers(morphGeoInfo); - for (var i = 0; i < degree; ++i) { + var positionAttribute = new THREE.Float32BufferAttribute(morphBuffers.vertex, 3); + positionAttribute.name = morphGeoNode.attrName; - controlPoints.push(controlPoints[i]); - } - } + preTransform.applyToBufferAttribute(positionAttribute); - var curve = new THREE.NURBSCurve(degree, knots, controlPoints, startKnot, endKnot); - var vertices = curve.getPoints(controlPoints.length * 7); + parentGeo.morphAttributes.position.push(positionAttribute); + }, - var positions = new Float32Array(vertices.length * 3); + // Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists + parseNormals: function parseNormals(NormalNode) { - vertices.forEach(function (vertex, i) { + var mappingType = NormalNode.MappingInformationType; + var referenceType = NormalNode.ReferenceInformationType; + var buffer = NormalNode.Normals.a; + var indexBuffer = []; + if (referenceType === 'IndexToDirect') { - vertex.toArray(positions, i * 3); - }); + if ('NormalIndex' in NormalNode) { - var geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3)); + indexBuffer = NormalNode.NormalIndex.a; + } else if ('NormalsIndex' in NormalNode) { - return geometry; - } + indexBuffer = NormalNode.NormalsIndex.a; + } + } + return { + dataSize: 3, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType }; + }, - // parse animation data from FBXTree - function AnimationParser() {} - - AnimationParser.prototype = { - - constructor: AnimationParser, - - // take raw animation clips and turn them into three.js animation clips - parse: function parse() { - - var animationClips = []; - - var rawClips = this.parseClips(); + // Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists + parseUVs: function parseUVs(UVNode) { - if (rawClips === undefined) return animationClips; + var mappingType = UVNode.MappingInformationType; + var referenceType = UVNode.ReferenceInformationType; + var buffer = UVNode.UV.a; + var indexBuffer = []; + if (referenceType === 'IndexToDirect') { - for (var key in rawClips) { + indexBuffer = UVNode.UVIndex.a; + } - var rawClip = rawClips[key]; + return { + dataSize: 2, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType + }; + }, - var clip = this.addClip(rawClip); + // Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists + parseVertexColors: function parseVertexColors(ColorNode) { - animationClips.push(clip); - } + var mappingType = ColorNode.MappingInformationType; + var referenceType = ColorNode.ReferenceInformationType; + var buffer = ColorNode.Colors.a; + var indexBuffer = []; + if (referenceType === 'IndexToDirect') { - return animationClips; - }, + indexBuffer = ColorNode.ColorIndex.a; + } - parseClips: function parseClips() { + return { + dataSize: 4, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType + }; + }, - // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve, - // if this is undefined we can safely assume there are no animations - if (fbxTree.Objects.AnimationCurve === undefined) return undefined; + // Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists + parseMaterialIndices: function parseMaterialIndices(MaterialNode) { - var curveNodesMap = this.parseAnimationCurveNodes(); + var mappingType = MaterialNode.MappingInformationType; + var referenceType = MaterialNode.ReferenceInformationType; - this.parseAnimationCurves(curveNodesMap); + if (mappingType === 'NoMappingInformation') { - var layersMap = this.parseAnimationLayers(curveNodesMap); - var rawClips = this.parseAnimStacks(layersMap); + return { + dataSize: 1, + buffer: [0], + indices: [0], + mappingType: 'AllSame', + referenceType: referenceType + }; + } - return rawClips; - }, + var materialIndexBuffer = MaterialNode.Materials.a; - // parse nodes in FBXTree.Objects.AnimationCurveNode - // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation ) - // and is referenced by an AnimationLayer - parseAnimationCurveNodes: function parseAnimationCurveNodes() { + // 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 = []; - var rawCurveNodes = fbxTree.Objects.AnimationCurveNode; + for (var i = 0; i < materialIndexBuffer.length; ++i) { - var curveNodesMap = new Map(); + materialIndices.push(i); + } - for (var nodeID in rawCurveNodes) { + return { + dataSize: 1, + buffer: materialIndexBuffer, + indices: materialIndices, + mappingType: mappingType, + referenceType: referenceType + }; + }, - var rawCurveNode = rawCurveNodes[nodeID]; + // Generate a NurbGeometry from a node in FBXTree.Objects.Geometry + parseNurbsGeometry: function parseNurbsGeometry(geoNode) { - if (rawCurveNode.attrName.match(/S|R|T|DeformPercent/) !== null) { + if (THREE.NURBSCurve === undefined) { - var curveNode = { + console.error('THREE.FBXLoader: The loader relies on THREE.NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.'); + return new THREE.BufferGeometry(); + } - id: rawCurveNode.id, - attr: rawCurveNode.attrName, - curves: {} + var order = parseInt(geoNode.Order); - }; + if (isNaN(order)) { - curveNodesMap.set(curveNode.id, curveNode); - } - } + console.error('THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id); + return new THREE.BufferGeometry(); + } - return curveNodesMap; - }, + var degree = order - 1; - // parse nodes in FBXTree.Objects.AnimationCurve and connect them up to - // previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated - // axis ( e.g. times and values of x rotation) - parseAnimationCurves: function parseAnimationCurves(curveNodesMap) { + var knots = geoNode.KnotVector.a; + var controlPoints = []; + var pointsValues = geoNode.Points.a; - var rawCurves = fbxTree.Objects.AnimationCurve; + for (var i = 0, l = pointsValues.length; i < l; i += 4) { - // TODO: Many values are identical up to roundoff error, but won't be optimised - // e.g. position times: [0, 0.4, 0. 8] - // position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809] - // clearly, this should be optimised to - // times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809] - // this shows up in nearly every FBX file, and generally time array is length > 100 + controlPoints.push(new THREE.Vector4().fromArray(pointsValues, i)); + } - for (var nodeID in rawCurves) { + var startKnot, endKnot; - var animationCurve = { + if (geoNode.Form === 'Closed') { - id: rawCurves[nodeID].id, - times: rawCurves[nodeID].KeyTime.a.map(convertFBXTimeToSeconds), - values: rawCurves[nodeID].KeyValueFloat.a + controlPoints.push(controlPoints[0]); + } else if (geoNode.Form === 'Periodic') { - }; + startKnot = degree; + endKnot = knots.length - 1 - startKnot; - var relationships = connections.get(animationCurve.id); + for (var i = 0; i < degree; ++i) { - if (relationships !== undefined) { + controlPoints.push(controlPoints[i]); + } + } - var animationCurveID = relationships.parents[0].ID; - var animationCurveRelationship = relationships.parents[0].relationship; + var curve = new THREE.NURBSCurve(degree, knots, controlPoints, startKnot, endKnot); + var vertices = curve.getPoints(controlPoints.length * 7); - if (animationCurveRelationship.match(/X/)) { + var positions = new Float32Array(vertices.length * 3); - curveNodesMap.get(animationCurveID).curves['x'] = animationCurve; - } else if (animationCurveRelationship.match(/Y/)) { + vertices.forEach(function (vertex, i) { - curveNodesMap.get(animationCurveID).curves['y'] = animationCurve; - } else if (animationCurveRelationship.match(/Z/)) { + vertex.toArray(positions, i * 3); + }); - curveNodesMap.get(animationCurveID).curves['z'] = animationCurve; - } else if (animationCurveRelationship.match(/d|DeformPercent/) && curveNodesMap.has(animationCurveID)) { + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3)); - curveNodesMap.get(animationCurveID).curves['morph'] = animationCurve; - } - } - } - }, + return geometry; + } - // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references - // to various AnimationCurveNodes and is referenced by an AnimationStack node - // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack - parseAnimationLayers: function parseAnimationLayers(curveNodesMap) { + }; - var rawLayers = fbxTree.Objects.AnimationLayer; + // parse animation data from FBXTree + function AnimationParser() {} - var layersMap = new Map(); + AnimationParser.prototype = { - for (var nodeID in rawLayers) { + constructor: AnimationParser, - var layerCurveNodes = []; + // take raw animation clips and turn them into three.js animation clips + parse: function parse() { - var connection = connections.get(parseInt(nodeID)); + var animationClips = []; - if (connection !== undefined) { + var rawClips = this.parseClips(); - // all the animationCurveNodes used in the layer - var children = connection.children; + if (rawClips === undefined) return animationClips; - var self = this; - children.forEach(function (child, i) { + for (var key in rawClips) { - if (curveNodesMap.has(child.ID)) { + var rawClip = rawClips[key]; - var curveNode = curveNodesMap.get(child.ID); + var clip = this.addClip(rawClip); - // check that the curves are defined for at least one axis, otherwise ignore the curveNode - if (curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined) { + animationClips.push(clip); + } - if (layerCurveNodes[i] === undefined) { + return animationClips; + }, - var modelID; + parseClips: function parseClips() { - connections.get(child.ID).parents.forEach(function (parent) { + // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve, + // if this is undefined we can safely assume there are no animations + if (fbxTree.Objects.AnimationCurve === undefined) return undefined; - if (parent.relationship !== undefined) modelID = parent.ID; - }); + var curveNodesMap = this.parseAnimationCurveNodes(); - var rawModel = fbxTree.Objects.Model[modelID.toString()]; + this.parseAnimationCurves(curveNodesMap); - var node = { + var layersMap = this.parseAnimationLayers(curveNodesMap); + var rawClips = this.parseAnimStacks(layersMap); - modelName: THREE.PropertyBinding.sanitizeNodeName(rawModel.attrName), - initialPosition: [0, 0, 0], - initialRotation: [0, 0, 0], - initialScale: [1, 1, 1], - transform: self.getModelAnimTransform(rawModel) + return rawClips; + }, - }; + // parse nodes in FBXTree.Objects.AnimationCurveNode + // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation ) + // and is referenced by an AnimationLayer + parseAnimationCurveNodes: function parseAnimationCurveNodes() { - // if the animated model is pre rotated, we'll have to apply the pre rotations to every - // animation value as well - if ('PreRotation' in rawModel) node.preRotations = rawModel.PreRotation.value; - if ('PostRotation' in rawModel) node.postRotations = rawModel.PostRotation.value; + var rawCurveNodes = fbxTree.Objects.AnimationCurveNode; - layerCurveNodes[i] = node; - } + var curveNodesMap = new Map(); - layerCurveNodes[i][curveNode.attr] = curveNode; - } else if (curveNode.curves.morph !== undefined) { + for (var nodeID in rawCurveNodes) { - if (layerCurveNodes[i] === undefined) { + var rawCurveNode = rawCurveNodes[nodeID]; - var deformerID; + if (rawCurveNode.attrName.match(/S|R|T|DeformPercent/) !== null) { - connections.get(child.ID).parents.forEach(function (parent) { + var curveNode = { - if (parent.relationship !== undefined) deformerID = parent.ID; - }); + id: rawCurveNode.id, + attr: rawCurveNode.attrName, + curves: {} - var morpherID = connections.get(deformerID).parents[0].ID; - var geoID = connections.get(morpherID).parents[0].ID; + }; - // assuming geometry is not used in more than one model - var modelID = connections.get(geoID).parents[0].ID; + curveNodesMap.set(curveNode.id, curveNode); + } + } - var rawModel = fbxTree.Objects.Model[modelID]; + return curveNodesMap; + }, - var node = { + // parse nodes in FBXTree.Objects.AnimationCurve and connect them up to + // previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated + // axis ( e.g. times and values of x rotation) + parseAnimationCurves: function parseAnimationCurves(curveNodesMap) { - modelName: THREE.PropertyBinding.sanitizeNodeName(rawModel.attrName), - morphName: fbxTree.Objects.Deformer[deformerID].attrName + var rawCurves = fbxTree.Objects.AnimationCurve; - }; + // TODO: Many values are identical up to roundoff error, but won't be optimised + // e.g. position times: [0, 0.4, 0. 8] + // position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809] + // clearly, this should be optimised to + // times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809] + // this shows up in nearly every FBX file, and generally time array is length > 100 - layerCurveNodes[i] = node; - } + for (var nodeID in rawCurves) { - layerCurveNodes[i][curveNode.attr] = curveNode; - } - } - }); + var animationCurve = { - layersMap.set(parseInt(nodeID), layerCurveNodes); - } - } + id: rawCurves[nodeID].id, + times: rawCurves[nodeID].KeyTime.a.map(convertFBXTimeToSeconds), + values: rawCurves[nodeID].KeyValueFloat.a - return layersMap; - }, + }; - getModelAnimTransform: function getModelAnimTransform(modelNode) { + var relationships = connections.get(animationCurve.id); - var transformData = {}; + if (relationships !== undefined) { - if ('RotationOrder' in modelNode) transformData.eulerOrder = parseInt(modelNode.RotationOrder.value); + var animationCurveID = relationships.parents[0].ID; + var animationCurveRelationship = relationships.parents[0].relationship; - if ('Lcl_Translation' in modelNode) transformData.translation = modelNode.Lcl_Translation.value; - if ('RotationOffset' in modelNode) transformData.rotationOffset = modelNode.RotationOffset.value; + if (animationCurveRelationship.match(/X/)) { - if ('Lcl_Rotation' in modelNode) transformData.rotation = modelNode.Lcl_Rotation.value; - if ('PreRotation' in modelNode) transformData.preRotation = modelNode.PreRotation.value; + curveNodesMap.get(animationCurveID).curves['x'] = animationCurve; + } else if (animationCurveRelationship.match(/Y/)) { - if ('PostRotation' in modelNode) transformData.postRotation = modelNode.PostRotation.value; + curveNodesMap.get(animationCurveID).curves['y'] = animationCurve; + } else if (animationCurveRelationship.match(/Z/)) { - if ('Lcl_Scaling' in modelNode) transformData.scale = modelNode.Lcl_Scaling.value; + curveNodesMap.get(animationCurveID).curves['z'] = animationCurve; + } else if (animationCurveRelationship.match(/d|DeformPercent/) && curveNodesMap.has(animationCurveID)) { - return generateTransform(transformData); - }, + curveNodesMap.get(animationCurveID).curves['morph'] = animationCurve; + } + } + } + }, - // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation - // hierarchy. Each Stack node will be used to create a THREE.AnimationClip - parseAnimStacks: function parseAnimStacks(layersMap) { + // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references + // to various AnimationCurveNodes and is referenced by an AnimationStack node + // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack + parseAnimationLayers: function parseAnimationLayers(curveNodesMap) { - var rawStacks = fbxTree.Objects.AnimationStack; + var rawLayers = fbxTree.Objects.AnimationLayer; - // connect the stacks (clips) up to the layers - var rawClips = {}; + var layersMap = new Map(); - for (var nodeID in rawStacks) { + for (var nodeID in rawLayers) { - var children = connections.get(parseInt(nodeID)).children; + var layerCurveNodes = []; - if (children.length > 1) { + var connection = connections.get(parseInt(nodeID)); - // it seems like stacks will always be associated with a single layer. But just in case there are files - // where there are multiple layers per stack, we'll display a warning - console.warn('THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.'); - } + if (connection !== undefined) { - var layer = layersMap.get(children[0].ID); + // all the animationCurveNodes used in the layer + var children = connection.children; - rawClips[nodeID] = { + var self = this; + children.forEach(function (child, i) { - name: rawStacks[nodeID].attrName, - layer: layer + if (curveNodesMap.has(child.ID)) { - }; - } + var curveNode = curveNodesMap.get(child.ID); - return rawClips; - }, + // check that the curves are defined for at least one axis, otherwise ignore the curveNode + if (curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined) { - addClip: function addClip(rawClip) { + if (layerCurveNodes[i] === undefined) { - var tracks = []; + var modelID; - var self = this; - rawClip.layer.forEach(function (rawTracks) { + connections.get(child.ID).parents.forEach(function (parent) { - tracks = tracks.concat(self.generateTracks(rawTracks)); + if (parent.relationship !== undefined) modelID = parent.ID; }); - return new THREE.AnimationClip(rawClip.name, -1, tracks); - }, - - generateTracks: function generateTracks(rawTracks) { - - var tracks = []; - - var initialPosition = new THREE.Vector3(); - var initialRotation = new THREE.Quaternion(); - var initialScale = new THREE.Vector3(); - - if (rawTracks.transform) rawTracks.transform.decompose(initialPosition, initialRotation, initialScale); - - initialPosition = initialPosition.toArray(); - initialRotation = new THREE.Euler().setFromQuaternion(initialRotation).toArray(); // todo: euler order - initialScale = initialScale.toArray(); - - if (rawTracks.T !== undefined && Object.keys(rawTracks.T.curves).length > 0) { - - var positionTrack = this.generateVectorTrack(rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position'); - if (positionTrack !== undefined) tracks.push(positionTrack); - } - - if (rawTracks.R !== undefined && Object.keys(rawTracks.R.curves).length > 0) { - - var rotationTrack = this.generateRotationTrack(rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotations, rawTracks.postRotations); - if (rotationTrack !== undefined) tracks.push(rotationTrack); - } - - if (rawTracks.S !== undefined && Object.keys(rawTracks.S.curves).length > 0) { + var rawModel = fbxTree.Objects.Model[modelID.toString()]; - var scaleTrack = this.generateVectorTrack(rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale'); - if (scaleTrack !== undefined) tracks.push(scaleTrack); - } + var node = { - if (rawTracks.DeformPercent !== undefined) { + modelName: THREE.PropertyBinding.sanitizeNodeName(rawModel.attrName), + initialPosition: [0, 0, 0], + initialRotation: [0, 0, 0], + initialScale: [1, 1, 1], + transform: self.getModelAnimTransform(rawModel) - var morphTrack = this.generateMorphTrack(rawTracks); - if (morphTrack !== undefined) tracks.push(morphTrack); - } - - return tracks; - }, - - generateVectorTrack: function generateVectorTrack(modelName, curves, initialValue, type) { - - var times = this.getTimesForAllAxes(curves); - var values = this.getKeyframeTrackValues(times, curves, initialValue); - - return new THREE.VectorKeyframeTrack(modelName + '.' + type, times, values); - }, - - generateRotationTrack: function generateRotationTrack(modelName, curves, initialValue, preRotations, postRotations) { - - if (curves.x !== undefined) { - - this.interpolateRotations(curves.x); - curves.x.values = curves.x.values.map(THREE.MathUtils.degToRad); - } - if (curves.y !== undefined) { - - this.interpolateRotations(curves.y); - curves.y.values = curves.y.values.map(THREE.MathUtils.degToRad); - } - if (curves.z !== undefined) { - - this.interpolateRotations(curves.z); - curves.z.values = curves.z.values.map(THREE.MathUtils.degToRad); - } - - var times = this.getTimesForAllAxes(curves); - var values = this.getKeyframeTrackValues(times, curves, initialValue); - - if (preRotations !== undefined) { - - preRotations = preRotations.map(THREE.MathUtils.degToRad); - preRotations.push('ZYX'); - - preRotations = new THREE.Euler().fromArray(preRotations); - preRotations = new THREE.Quaternion().setFromEuler(preRotations); - } - - if (postRotations !== undefined) { - - postRotations = postRotations.map(THREE.MathUtils.degToRad); - postRotations.push('ZYX'); - - postRotations = new THREE.Euler().fromArray(postRotations); - postRotations = new THREE.Quaternion().setFromEuler(postRotations).inverse(); - } - - var quaternion = new THREE.Quaternion(); - var euler = new THREE.Euler(); - - var quaternionValues = []; - - for (var i = 0; i < values.length; i += 3) { - - euler.set(values[i], values[i + 1], values[i + 2], 'ZYX'); + }; - quaternion.setFromEuler(euler); + // if the animated model is pre rotated, we'll have to apply the pre rotations to every + // animation value as well + if ('PreRotation' in rawModel) node.preRotations = rawModel.PreRotation.value; + if ('PostRotation' in rawModel) node.postRotations = rawModel.PostRotation.value; - if (preRotations !== undefined) quaternion.premultiply(preRotations); - if (postRotations !== undefined) quaternion.multiply(postRotations); + layerCurveNodes[i] = node; + } - quaternion.toArray(quaternionValues, i / 3 * 4); - } + layerCurveNodes[i][curveNode.attr] = curveNode; + } else if (curveNode.curves.morph !== undefined) { - return new THREE.QuaternionKeyframeTrack(modelName + '.quaternion', times, quaternionValues); - }, + if (layerCurveNodes[i] === undefined) { - generateMorphTrack: function generateMorphTrack(rawTracks) { + var deformerID; - var curves = rawTracks.DeformPercent.curves.morph; - var values = curves.values.map(function (val) { + connections.get(child.ID).parents.forEach(function (parent) { - return val / 100; + if (parent.relationship !== undefined) deformerID = parent.ID; }); - var morphNum = sceneGraph.getObjectByName(rawTracks.modelName).morphTargetDictionary[rawTracks.morphName]; + var morpherID = connections.get(deformerID).parents[0].ID; + var geoID = connections.get(morpherID).parents[0].ID; - return new THREE.NumberKeyframeTrack(rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values); - }, + // assuming geometry is not used in more than one model + var modelID = connections.get(geoID).parents[0].ID; - // For all animated objects, times are defined separately for each axis - // Here we'll combine the times into one sorted array without duplicates - getTimesForAllAxes: function getTimesForAllAxes(curves) { + var rawModel = fbxTree.Objects.Model[modelID]; - var times = []; + var node = { - // first join together the times for each axis, if defined - if (curves.x !== undefined) times = times.concat(curves.x.times); - if (curves.y !== undefined) times = times.concat(curves.y.times); - if (curves.z !== undefined) times = times.concat(curves.z.times); + modelName: THREE.PropertyBinding.sanitizeNodeName(rawModel.attrName), + morphName: fbxTree.Objects.Deformer[deformerID].attrName - // then sort them and remove duplicates - times = times.sort(function (a, b) { + }; - return a - b; - }).filter(function (elem, index, array) { + layerCurveNodes[i] = node; + } - return array.indexOf(elem) == index; - }); + layerCurveNodes[i][curveNode.attr] = curveNode; + } + } + }); - return times; - }, + layersMap.set(parseInt(nodeID), layerCurveNodes); + } + } - getKeyframeTrackValues: function getKeyframeTrackValues(times, curves, initialValue) { + return layersMap; + }, - var prevValue = initialValue; + getModelAnimTransform: function getModelAnimTransform(modelNode) { - var values = []; + var transformData = {}; - var xIndex = -1; - var yIndex = -1; - var zIndex = -1; + if ('RotationOrder' in modelNode) transformData.eulerOrder = parseInt(modelNode.RotationOrder.value); - times.forEach(function (time) { + if ('Lcl_Translation' in modelNode) transformData.translation = modelNode.Lcl_Translation.value; + if ('RotationOffset' in modelNode) transformData.rotationOffset = modelNode.RotationOffset.value; - if (curves.x) xIndex = curves.x.times.indexOf(time); - if (curves.y) yIndex = curves.y.times.indexOf(time); - if (curves.z) zIndex = curves.z.times.indexOf(time); + if ('Lcl_Rotation' in modelNode) transformData.rotation = modelNode.Lcl_Rotation.value; + if ('PreRotation' in modelNode) transformData.preRotation = modelNode.PreRotation.value; - // if there is an x value defined for this frame, use that - if (xIndex !== -1) { + if ('PostRotation' in modelNode) transformData.postRotation = modelNode.PostRotation.value; - var xValue = curves.x.values[xIndex]; - values.push(xValue); - prevValue[0] = xValue; - } else { + if ('Lcl_Scaling' in modelNode) transformData.scale = modelNode.Lcl_Scaling.value; - // otherwise use the x value from the previous frame - values.push(prevValue[0]); - } + return generateTransform(transformData); + }, - if (yIndex !== -1) { + // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation + // hierarchy. Each Stack node will be used to create a THREE.AnimationClip + parseAnimStacks: function parseAnimStacks(layersMap) { - var yValue = curves.y.values[yIndex]; - values.push(yValue); - prevValue[1] = yValue; - } else { + var rawStacks = fbxTree.Objects.AnimationStack; - values.push(prevValue[1]); - } + // connect the stacks (clips) up to the layers + var rawClips = {}; - if (zIndex !== -1) { + for (var nodeID in rawStacks) { - var zValue = curves.z.values[zIndex]; - values.push(zValue); - prevValue[2] = zValue; - } else { + var children = connections.get(parseInt(nodeID)).children; - values.push(prevValue[2]); - } - }); + if (children.length > 1) { - return values; - }, + // it seems like stacks will always be associated with a single layer. But just in case there are files + // where there are multiple layers per stack, we'll display a warning + console.warn('THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.'); + } - // Rotations are defined as Euler angles which can have values of any size - // These will be converted to quaternions which don't support values greater than - // PI, so we'll interpolate large rotations - interpolateRotations: function interpolateRotations(curve) { + var layer = layersMap.get(children[0].ID); - for (var i = 1; i < curve.values.length; i++) { + rawClips[nodeID] = { - var initialValue = curve.values[i - 1]; - var valuesSpan = curve.values[i] - initialValue; + name: rawStacks[nodeID].attrName, + layer: layer - var absoluteSpan = Math.abs(valuesSpan); + }; + } - if (absoluteSpan >= 180) { + return rawClips; + }, - var numSubIntervals = absoluteSpan / 180; + addClip: function addClip(rawClip) { - var step = valuesSpan / numSubIntervals; - var nextValue = initialValue + step; + var tracks = []; - var initialTime = curve.times[i - 1]; - var timeSpan = curve.times[i] - initialTime; - var interval = timeSpan / numSubIntervals; - var nextTime = initialTime + interval; + var self = this; + rawClip.layer.forEach(function (rawTracks) { - var interpolatedTimes = []; - var interpolatedValues = []; + tracks = tracks.concat(self.generateTracks(rawTracks)); + }); - while (nextTime < curve.times[i]) { + return new THREE.AnimationClip(rawClip.name, -1, tracks); + }, - interpolatedTimes.push(nextTime); - nextTime += interval; + generateTracks: function generateTracks(rawTracks) { - interpolatedValues.push(nextValue); - nextValue += step; - } + var tracks = []; - curve.times = inject(curve.times, i, interpolatedTimes); - curve.values = inject(curve.values, i, interpolatedValues); - } - } - } + var initialPosition = new THREE.Vector3(); + var initialRotation = new THREE.Quaternion(); + var initialScale = new THREE.Vector3(); - }; + if (rawTracks.transform) rawTracks.transform.decompose(initialPosition, initialRotation, initialScale); - // parse an FBX file in ASCII format - function TextParser() {} + initialPosition = initialPosition.toArray(); + initialRotation = new THREE.Euler().setFromQuaternion(initialRotation).toArray(); // todo: euler order + initialScale = initialScale.toArray(); - TextParser.prototype = { + if (rawTracks.T !== undefined && Object.keys(rawTracks.T.curves).length > 0) { - constructor: TextParser, + var positionTrack = this.generateVectorTrack(rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position'); + if (positionTrack !== undefined) tracks.push(positionTrack); + } - getPrevNode: function getPrevNode() { + if (rawTracks.R !== undefined && Object.keys(rawTracks.R.curves).length > 0) { - return this.nodeStack[this.currentIndent - 2]; - }, + var rotationTrack = this.generateRotationTrack(rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotations, rawTracks.postRotations); + if (rotationTrack !== undefined) tracks.push(rotationTrack); + } - getCurrentNode: function getCurrentNode() { + if (rawTracks.S !== undefined && Object.keys(rawTracks.S.curves).length > 0) { - return this.nodeStack[this.currentIndent - 1]; - }, + var scaleTrack = this.generateVectorTrack(rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale'); + if (scaleTrack !== undefined) tracks.push(scaleTrack); + } - getCurrentProp: function getCurrentProp() { + if (rawTracks.DeformPercent !== undefined) { - return this.currentProp; - }, + var morphTrack = this.generateMorphTrack(rawTracks); + if (morphTrack !== undefined) tracks.push(morphTrack); + } - pushStack: function pushStack(node) { + return tracks; + }, - this.nodeStack.push(node); - this.currentIndent += 1; - }, + generateVectorTrack: function generateVectorTrack(modelName, curves, initialValue, type) { - popStack: function popStack() { + var times = this.getTimesForAllAxes(curves); + var values = this.getKeyframeTrackValues(times, curves, initialValue); - this.nodeStack.pop(); - this.currentIndent -= 1; - }, + return new THREE.VectorKeyframeTrack(modelName + '.' + type, times, values); + }, - setCurrentProp: function setCurrentProp(val, name) { + generateRotationTrack: function generateRotationTrack(modelName, curves, initialValue, preRotations, postRotations) { - this.currentProp = val; - this.currentPropName = name; - }, + if (curves.x !== undefined) { - parse: function parse(text) { + this.interpolateRotations(curves.x); + curves.x.values = curves.x.values.map(THREE.MathUtils.degToRad); + } + if (curves.y !== undefined) { - this.currentIndent = 0; - console.log("FBXTree: ", FBXTree); - this.allNodes = new FBXTree(); - this.nodeStack = []; - this.currentProp = []; - this.currentPropName = ''; + this.interpolateRotations(curves.y); + curves.y.values = curves.y.values.map(THREE.MathUtils.degToRad); + } + if (curves.z !== undefined) { - var self = this; + this.interpolateRotations(curves.z); + curves.z.values = curves.z.values.map(THREE.MathUtils.degToRad); + } - var split = text.split(/[\r\n]+/); + var times = this.getTimesForAllAxes(curves); + var values = this.getKeyframeTrackValues(times, curves, initialValue); - split.forEach(function (line, i) { + if (preRotations !== undefined) { - var matchComment = line.match(/^[\s\t]*;/); - var matchEmpty = line.match(/^[\s\t]*$/); + preRotations = preRotations.map(THREE.MathUtils.degToRad); + preRotations.push('ZYX'); - if (matchComment || matchEmpty) return; + preRotations = new THREE.Euler().fromArray(preRotations); + preRotations = new THREE.Quaternion().setFromEuler(preRotations); + } - var matchBeginning = line.match('^\\t{' + self.currentIndent + '}(\\w+):(.*){', ''); - var matchProperty = line.match('^\\t{' + self.currentIndent + '}(\\w+):[\\s\\t\\r\\n](.*)'); - var matchEnd = line.match('^\\t{' + (self.currentIndent - 1) + '}}'); + if (postRotations !== undefined) { - if (matchBeginning) { + postRotations = postRotations.map(THREE.MathUtils.degToRad); + postRotations.push('ZYX'); - self.parseNodeBegin(line, matchBeginning); - } else if (matchProperty) { + postRotations = new THREE.Euler().fromArray(postRotations); + postRotations = new THREE.Quaternion().setFromEuler(postRotations).inverse(); + } - self.parseNodeProperty(line, matchProperty, split[++i]); - } else if (matchEnd) { + var quaternion = new THREE.Quaternion(); + var euler = new THREE.Euler(); - self.popStack(); - } else if (line.match(/^[^\s\t}]/)) { + var quaternionValues = []; - // large arrays are split over multiple lines terminated with a ',' character - // if this is encountered the line needs to be joined to the previous line - self.parseNodePropertyContinued(line); - } - }); + for (var i = 0; i < values.length; i += 3) { - return this.allNodes; - }, + euler.set(values[i], values[i + 1], values[i + 2], 'ZYX'); - parseNodeBegin: function parseNodeBegin(line, property) { + quaternion.setFromEuler(euler); - var nodeName = property[1].trim().replace(/^"/, '').replace(/"$/, ''); + if (preRotations !== undefined) quaternion.premultiply(preRotations); + if (postRotations !== undefined) quaternion.multiply(postRotations); - var nodeAttrs = property[2].split(',').map(function (attr) { + quaternion.toArray(quaternionValues, i / 3 * 4); + } - return attr.trim().replace(/^"/, '').replace(/"$/, ''); - }); + return new THREE.QuaternionKeyframeTrack(modelName + '.quaternion', times, quaternionValues); + }, - var node = { name: nodeName }; - var attrs = this.parseNodeAttr(nodeAttrs); + generateMorphTrack: function generateMorphTrack(rawTracks) { - var currentNode = this.getCurrentNode(); + var curves = rawTracks.DeformPercent.curves.morph; + var values = curves.values.map(function (val) { - // a top node - if (this.currentIndent === 0) { + return val / 100; + }); - this.allNodes.add(nodeName, node); - } else { - // a subnode + var morphNum = sceneGraph.getObjectByName(rawTracks.modelName).morphTargetDictionary[rawTracks.morphName]; - // if the subnode already exists, append it - if (nodeName in currentNode) { + return new THREE.NumberKeyframeTrack(rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values); + }, - // special case Pose needs PoseNodes as an array - if (nodeName === 'PoseNode') { + // For all animated objects, times are defined separately for each axis + // Here we'll combine the times into one sorted array without duplicates + getTimesForAllAxes: function getTimesForAllAxes(curves) { - currentNode.PoseNode.push(node); - } else if (currentNode[nodeName].id !== undefined) { + var times = []; - currentNode[nodeName] = {}; - currentNode[nodeName][currentNode[nodeName].id] = currentNode[nodeName]; - } + // first join together the times for each axis, if defined + if (curves.x !== undefined) times = times.concat(curves.x.times); + if (curves.y !== undefined) times = times.concat(curves.y.times); + if (curves.z !== undefined) times = times.concat(curves.z.times); - if (attrs.id !== '') currentNode[nodeName][attrs.id] = node; - } else if (typeof attrs.id === 'number') { + // then sort them and remove duplicates + times = times.sort(function (a, b) { - currentNode[nodeName] = {}; - currentNode[nodeName][attrs.id] = node; - } else if (nodeName !== 'Properties70') { + return a - b; + }).filter(function (elem, index, array) { - if (nodeName === 'PoseNode') currentNode[nodeName] = [node];else currentNode[nodeName] = node; - } - } + return array.indexOf(elem) == index; + }); - if (typeof attrs.id === 'number') node.id = attrs.id; - if (attrs.name !== '') node.attrName = attrs.name; - if (attrs.type !== '') node.attrType = attrs.type; + return times; + }, - this.pushStack(node); - }, + getKeyframeTrackValues: function getKeyframeTrackValues(times, curves, initialValue) { - parseNodeAttr: function parseNodeAttr(attrs) { + var prevValue = initialValue; - var id = attrs[0]; + var values = []; - if (attrs[0] !== '') { + var xIndex = -1; + var yIndex = -1; + var zIndex = -1; - id = parseInt(attrs[0]); + times.forEach(function (time) { - if (isNaN(id)) { + if (curves.x) xIndex = curves.x.times.indexOf(time); + if (curves.y) yIndex = curves.y.times.indexOf(time); + if (curves.z) zIndex = curves.z.times.indexOf(time); - id = attrs[0]; - } - } + // if there is an x value defined for this frame, use that + if (xIndex !== -1) { - var name = '', - type = ''; + var xValue = curves.x.values[xIndex]; + values.push(xValue); + prevValue[0] = xValue; + } else { - if (attrs.length > 1) { + // otherwise use the x value from the previous frame + values.push(prevValue[0]); + } - name = attrs[1].replace(/^(\w+)::/, ''); - type = attrs[2]; - } + if (yIndex !== -1) { - return { id: id, name: name, type: type }; - }, + var yValue = curves.y.values[yIndex]; + values.push(yValue); + prevValue[1] = yValue; + } else { - parseNodeProperty: function parseNodeProperty(line, property, contentLine) { + values.push(prevValue[1]); + } - var propName = property[1].replace(/^"/, '').replace(/"$/, '').trim(); - var propValue = property[2].replace(/^"/, '').replace(/"$/, '').trim(); + if (zIndex !== -1) { - // for special case: base64 image data follows "Content: ," line - // Content: , - // "/9j/4RDaRXhpZgAATU0A..." - if (propName === 'Content' && propValue === ',') { + var zValue = curves.z.values[zIndex]; + values.push(zValue); + prevValue[2] = zValue; + } else { - propValue = contentLine.replace(/"/g, '').replace(/,$/, '').trim(); - } + values.push(prevValue[2]); + } + }); - var currentNode = this.getCurrentNode(); - var parentName = currentNode.name; + return values; + }, - if (parentName === 'Properties70') { + // Rotations are defined as Euler angles which can have values of any size + // These will be converted to quaternions which don't support values greater than + // PI, so we'll interpolate large rotations + interpolateRotations: function interpolateRotations(curve) { - this.parseNodeSpecialProperty(line, propName, propValue); - return; - } + for (var i = 1; i < curve.values.length; i++) { - // Connections - if (propName === 'C') { + var initialValue = curve.values[i - 1]; + var valuesSpan = curve.values[i] - initialValue; - var connProps = propValue.split(',').slice(1); - var from = parseInt(connProps[0]); - var to = parseInt(connProps[1]); + var absoluteSpan = Math.abs(valuesSpan); - var rest = propValue.split(',').slice(3); + if (absoluteSpan >= 180) { - rest = rest.map(function (elem) { + var numSubIntervals = absoluteSpan / 180; - return elem.trim().replace(/^"/, ''); - }); + var step = valuesSpan / numSubIntervals; + var nextValue = initialValue + step; - propName = 'connections'; - propValue = [from, to]; - append(propValue, rest); + var initialTime = curve.times[i - 1]; + var timeSpan = curve.times[i] - initialTime; + var interval = timeSpan / numSubIntervals; + var nextTime = initialTime + interval; - if (currentNode[propName] === undefined) { + var interpolatedTimes = []; + var interpolatedValues = []; - currentNode[propName] = []; - } - } + while (nextTime < curve.times[i]) { - // Node - if (propName === 'Node') currentNode.id = propValue; + interpolatedTimes.push(nextTime); + nextTime += interval; - // connections - if (propName in currentNode && Array.isArray(currentNode[propName])) { + interpolatedValues.push(nextValue); + nextValue += step; + } - currentNode[propName].push(propValue); - } else { + curve.times = inject(curve.times, i, interpolatedTimes); + curve.values = inject(curve.values, i, interpolatedValues); + } + } + } - if (propName !== 'a') currentNode[propName] = propValue;else currentNode.a = propValue; - } + }; - this.setCurrentProp(currentNode, propName); + // parse an FBX file in ASCII format + function TextParser() {} - // convert string to array, unless it ends in ',' in which case more will be added to it - if (propName === 'a' && propValue.slice(-1) !== ',') { + TextParser.prototype = { - currentNode.a = parseNumberArray(propValue); - } - }, + constructor: TextParser, - parseNodePropertyContinued: function parseNodePropertyContinued(line) { + getPrevNode: function getPrevNode() { - var currentNode = this.getCurrentNode(); + return this.nodeStack[this.currentIndent - 2]; + }, - currentNode.a += line; + getCurrentNode: function getCurrentNode() { - // if the line doesn't end in ',' we have reached the end of the property value - // so convert the string to an array - if (line.slice(-1) !== ',') { + return this.nodeStack[this.currentIndent - 1]; + }, - currentNode.a = parseNumberArray(currentNode.a); - } - }, + getCurrentProp: function getCurrentProp() { - // parse "Property70" - parseNodeSpecialProperty: function parseNodeSpecialProperty(line, propName, propValue) { + return this.currentProp; + }, - // split this - // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 - // into array like below - // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ] - var props = propValue.split('",').map(function (prop) { + pushStack: function pushStack(node) { - return prop.trim().replace(/^\"/, '').replace(/\s/, '_'); - }); + this.nodeStack.push(node); + this.currentIndent += 1; + }, - var innerPropName = props[0]; - var innerPropType1 = props[1]; - var innerPropType2 = props[2]; - var innerPropFlag = props[3]; - var innerPropValue = props[4]; - - // cast values where needed, otherwise leave as strings - switch (innerPropType1) { - - case 'int': - case 'enum': - case 'bool': - case 'ULongLong': - case 'double': - case 'Number': - case 'FieldOfView': - innerPropValue = parseFloat(innerPropValue); - break; - - case 'Color': - case 'ColorRGB': - case 'Vector3D': - case 'Lcl_Translation': - case 'Lcl_Rotation': - case 'Lcl_Scaling': - innerPropValue = parseNumberArray(innerPropValue); - break; - - } - - // CAUTION: these props must append to parent's parent - this.getPrevNode()[innerPropName] = { - - 'type': innerPropType1, - 'type2': innerPropType2, - 'flag': innerPropFlag, - 'value': innerPropValue + popStack: function popStack() { - }; + this.nodeStack.pop(); + this.currentIndent -= 1; + }, - this.setCurrentProp(this.getPrevNode(), innerPropName); - } + setCurrentProp: function setCurrentProp(val, name) { - }; + this.currentProp = val; + this.currentPropName = name; + }, - // Parse an FBX file in Binary format - function BinaryParser() {} + parse: function parse(text) { - BinaryParser.prototype = { + this.currentIndent = 0; + console.log("FBXTree: ", FBXTree); + this.allNodes = new FBXTree(); + this.nodeStack = []; + this.currentProp = []; + this.currentPropName = ''; - constructor: BinaryParser, + var self = this; - parse: function parse(buffer) { + var split = text.split(/[\r\n]+/); - var reader = new BinaryReader(buffer); - reader.skip(23); // skip magic 23 bytes + split.forEach(function (line, i) { - var version = reader.getUint32(); + var matchComment = line.match(/^[\s\t]*;/); + var matchEmpty = line.match(/^[\s\t]*$/); - console.log('THREE.FBXLoader: FBX binary version: ' + version); + if (matchComment || matchEmpty) return; - var allNodes = new FBXTree(); + var matchBeginning = line.match('^\\t{' + self.currentIndent + '}(\\w+):(.*){', ''); + var matchProperty = line.match('^\\t{' + self.currentIndent + '}(\\w+):[\\s\\t\\r\\n](.*)'); + var matchEnd = line.match('^\\t{' + (self.currentIndent - 1) + '}}'); - while (!this.endOfContent(reader)) { + if (matchBeginning) { - var node = this.parseNode(reader, version); - if (node !== null) allNodes.add(node.name, node); - } + self.parseNodeBegin(line, matchBeginning); + } else if (matchProperty) { - return allNodes; - }, + self.parseNodeProperty(line, matchProperty, split[++i]); + } else if (matchEnd) { - // Check if reader has reached the end of content. - endOfContent: function endOfContent(reader) { + self.popStack(); + } else if (line.match(/^[^\s\t}]/)) { - // footer size: 160bytes + 16-byte alignment padding - // - 16bytes: magic - // - padding til 16-byte alignment (at least 1byte?) - // (seems like some exporters embed fixed 15 or 16bytes?) - // - 4bytes: magic - // - 4bytes: version - // - 120bytes: zero - // - 16bytes: magic - if (reader.size() % 16 === 0) { + // large arrays are split over multiple lines terminated with a ',' character + // if this is encountered the line needs to be joined to the previous line + self.parseNodePropertyContinued(line); + } + }); - return (reader.getOffset() + 160 + 16 & ~0xf) >= reader.size(); - } else { + return this.allNodes; + }, - return reader.getOffset() + 160 + 16 >= reader.size(); - } - }, + parseNodeBegin: function parseNodeBegin(line, property) { - // recursively parse nodes until the end of the file is reached - parseNode: function parseNode(reader, version) { + var nodeName = property[1].trim().replace(/^"/, '').replace(/"$/, ''); - var node = {}; + var nodeAttrs = property[2].split(',').map(function (attr) { - // The first three data sizes depends on version. - var endOffset = version >= 7500 ? reader.getUint64() : reader.getUint32(); - var numProperties = version >= 7500 ? reader.getUint64() : reader.getUint32(); + return attr.trim().replace(/^"/, '').replace(/"$/, ''); + }); - // note: do not remove this even if you get a linter warning as it moves the buffer forward - var propertyListLen = version >= 7500 ? reader.getUint64() : reader.getUint32(); + var node = { name: nodeName }; + var attrs = this.parseNodeAttr(nodeAttrs); - var nameLen = reader.getUint8(); - var name = reader.getString(nameLen); + var currentNode = this.getCurrentNode(); - // Regards this node as NULL-record if endOffset is zero - if (endOffset === 0) return null; + // a top node + if (this.currentIndent === 0) { - var propertyList = []; + this.allNodes.add(nodeName, node); + } else { + // a subnode - for (var i = 0; i < numProperties; i++) { + // if the subnode already exists, append it + if (nodeName in currentNode) { - propertyList.push(this.parseProperty(reader)); - } + // special case Pose needs PoseNodes as an array + if (nodeName === 'PoseNode') { - // Regards the first three elements in propertyList as id, attrName, and attrType - var id = propertyList.length > 0 ? propertyList[0] : ''; - var attrName = propertyList.length > 1 ? propertyList[1] : ''; - var attrType = propertyList.length > 2 ? propertyList[2] : ''; + currentNode.PoseNode.push(node); + } else if (currentNode[nodeName].id !== undefined) { - // check if this node represents just a single property - // like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]} - node.singleProperty = numProperties === 1 && reader.getOffset() === endOffset ? true : false; + currentNode[nodeName] = {}; + currentNode[nodeName][currentNode[nodeName].id] = currentNode[nodeName]; + } - while (endOffset > reader.getOffset()) { + if (attrs.id !== '') currentNode[nodeName][attrs.id] = node; + } else if (typeof attrs.id === 'number') { - var subNode = this.parseNode(reader, version); + currentNode[nodeName] = {}; + currentNode[nodeName][attrs.id] = node; + } else if (nodeName !== 'Properties70') { - if (subNode !== null) this.parseSubNode(name, node, subNode); - } + if (nodeName === 'PoseNode') currentNode[nodeName] = [node];else currentNode[nodeName] = node; + } + } - node.propertyList = propertyList; // raw property list used by parent + if (typeof attrs.id === 'number') node.id = attrs.id; + if (attrs.name !== '') node.attrName = attrs.name; + if (attrs.type !== '') node.attrType = attrs.type; - if (typeof id === 'number') node.id = id; - if (attrName !== '') node.attrName = attrName; - if (attrType !== '') node.attrType = attrType; - if (name !== '') node.name = name; + this.pushStack(node); + }, - return node; - }, + parseNodeAttr: function parseNodeAttr(attrs) { - parseSubNode: function parseSubNode(name, node, subNode) { + var id = attrs[0]; - // special case: child node is single property - if (subNode.singleProperty === true) { + if (attrs[0] !== '') { - var value = subNode.propertyList[0]; + id = parseInt(attrs[0]); - if (Array.isArray(value)) { + if (isNaN(id)) { - node[subNode.name] = subNode; + id = attrs[0]; + } + } - subNode.a = value; - } else { + var name = '', + type = ''; - node[subNode.name] = value; - } - } else if (name === 'Connections' && subNode.name === 'C') { + if (attrs.length > 1) { - var array = []; + name = attrs[1].replace(/^(\w+)::/, ''); + type = attrs[2]; + } - subNode.propertyList.forEach(function (property, i) { + return { id: id, name: name, type: type }; + }, - // first Connection is FBX type (OO, OP, etc.). We'll discard these - if (i !== 0) array.push(property); - }); + parseNodeProperty: function parseNodeProperty(line, property, contentLine) { - if (node.connections === undefined) { + var propName = property[1].replace(/^"/, '').replace(/"$/, '').trim(); + var propValue = property[2].replace(/^"/, '').replace(/"$/, '').trim(); - node.connections = []; - } + // for special case: base64 image data follows "Content: ," line + // Content: , + // "/9j/4RDaRXhpZgAATU0A..." + if (propName === 'Content' && propValue === ',') { - node.connections.push(array); - } else if (subNode.name === 'Properties70') { + propValue = contentLine.replace(/"/g, '').replace(/,$/, '').trim(); + } - var keys = Object.keys(subNode); + var currentNode = this.getCurrentNode(); + var parentName = currentNode.name; - keys.forEach(function (key) { + if (parentName === 'Properties70') { - node[key] = subNode[key]; - }); - } else if (name === 'Properties70' && subNode.name === 'P') { + this.parseNodeSpecialProperty(line, propName, propValue); + return; + } - var innerPropName = subNode.propertyList[0]; - var innerPropType1 = subNode.propertyList[1]; - var innerPropType2 = subNode.propertyList[2]; - var innerPropFlag = subNode.propertyList[3]; - var innerPropValue; + // Connections + if (propName === 'C') { - if (innerPropName.indexOf('Lcl ') === 0) innerPropName = innerPropName.replace('Lcl ', 'Lcl_'); - if (innerPropType1.indexOf('Lcl ') === 0) innerPropType1 = innerPropType1.replace('Lcl ', 'Lcl_'); + var connProps = propValue.split(',').slice(1); + var from = parseInt(connProps[0]); + var to = parseInt(connProps[1]); - if (innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf('Lcl_') === 0) { + var rest = propValue.split(',').slice(3); - innerPropValue = [subNode.propertyList[4], subNode.propertyList[5], subNode.propertyList[6]]; - } else { + rest = rest.map(function (elem) { - innerPropValue = subNode.propertyList[4]; - } + return elem.trim().replace(/^"/, ''); + }); - // this will be copied to parent, see above - node[innerPropName] = { + propName = 'connections'; + propValue = [from, to]; + append(propValue, rest); - 'type': innerPropType1, - 'type2': innerPropType2, - 'flag': innerPropFlag, - 'value': innerPropValue + if (currentNode[propName] === undefined) { - }; - } else if (node[subNode.name] === undefined) { + currentNode[propName] = []; + } + } - if (typeof subNode.id === 'number') { + // Node + if (propName === 'Node') currentNode.id = propValue; - node[subNode.name] = {}; - node[subNode.name][subNode.id] = subNode; - } else { + // connections + if (propName in currentNode && Array.isArray(currentNode[propName])) { - node[subNode.name] = subNode; - } - } else { + currentNode[propName].push(propValue); + } else { - if (subNode.name === 'PoseNode') { + if (propName !== 'a') currentNode[propName] = propValue;else currentNode.a = propValue; + } - if (!Array.isArray(node[subNode.name])) { + this.setCurrentProp(currentNode, propName); - node[subNode.name] = [node[subNode.name]]; - } + // convert string to array, unless it ends in ',' in which case more will be added to it + if (propName === 'a' && propValue.slice(-1) !== ',') { - node[subNode.name].push(subNode); - } else if (node[subNode.name][subNode.id] === undefined) { + currentNode.a = parseNumberArray(propValue); + } + }, - node[subNode.name][subNode.id] = subNode; - } - } - }, + parseNodePropertyContinued: function parseNodePropertyContinued(line) { - parseProperty: function parseProperty(reader) { + var currentNode = this.getCurrentNode(); - var type = reader.getString(1); + currentNode.a += line; - switch (type) { + // if the line doesn't end in ',' we have reached the end of the property value + // so convert the string to an array + if (line.slice(-1) !== ',') { - case 'C': - return reader.getBoolean(); + currentNode.a = parseNumberArray(currentNode.a); + } + }, - case 'D': - return reader.getFloat64(); + // parse "Property70" + parseNodeSpecialProperty: function parseNodeSpecialProperty(line, propName, propValue) { - case 'F': - return reader.getFloat32(); + // split this + // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + // into array like below + // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ] + var props = propValue.split('",').map(function (prop) { - case 'I': - return reader.getInt32(); + return prop.trim().replace(/^\"/, '').replace(/\s/, '_'); + }); - case 'L': - return reader.getInt64(); + var innerPropName = props[0]; + var innerPropType1 = props[1]; + var innerPropType2 = props[2]; + var innerPropFlag = props[3]; + var innerPropValue = props[4]; - case 'R': - var length = reader.getUint32(); - return reader.getArrayBuffer(length); + // cast values where needed, otherwise leave as strings + switch (innerPropType1) { - case 'S': - var length = reader.getUint32(); - return reader.getString(length); + case 'int': + case 'enum': + case 'bool': + case 'ULongLong': + case 'double': + case 'Number': + case 'FieldOfView': + innerPropValue = parseFloat(innerPropValue); + break; - case 'Y': - return reader.getInt16(); + case 'Color': + case 'ColorRGB': + case 'Vector3D': + case 'Lcl_Translation': + case 'Lcl_Rotation': + case 'Lcl_Scaling': + innerPropValue = parseNumberArray(innerPropValue); + break; - case 'b': - case 'c': - case 'd': - case 'f': - case 'i': - case 'l': + } - var arrayLength = reader.getUint32(); - var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed - var compressedLength = reader.getUint32(); + // CAUTION: these props must append to parent's parent + this.getPrevNode()[innerPropName] = { - if (encoding === 0) { + 'type': innerPropType1, + 'type2': innerPropType2, + 'flag': innerPropFlag, + 'value': innerPropValue - switch (type) { + }; - case 'b': - case 'c': - return reader.getBooleanArray(arrayLength); + this.setCurrentProp(this.getPrevNode(), innerPropName); + } - case 'd': - return reader.getFloat64Array(arrayLength); + }; - case 'f': - return reader.getFloat32Array(arrayLength); + // Parse an FBX file in Binary format + function BinaryParser() {} - case 'i': - return reader.getInt32Array(arrayLength); + BinaryParser.prototype = { - case 'l': - return reader.getInt64Array(arrayLength); + constructor: BinaryParser, - } - } + parse: function parse(buffer) { - if (typeof Zlib === 'undefined') { + var reader = new BinaryReader(buffer); + reader.skip(23); // skip magic 23 bytes - console.error('THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js'); - } + var version = reader.getUint32(); - var inflate = new Zlib.Inflate(new Uint8Array(reader.getArrayBuffer(compressedLength))); // eslint-disable-line no-undef - var reader2 = new BinaryReader(inflate.decompress().buffer); + console.log('THREE.FBXLoader: FBX binary version: ' + version); - switch (type) { + var allNodes = new FBXTree(); - case 'b': - case 'c': - return reader2.getBooleanArray(arrayLength); + while (!this.endOfContent(reader)) { - case 'd': - return reader2.getFloat64Array(arrayLength); + var node = this.parseNode(reader, version); + if (node !== null) allNodes.add(node.name, node); + } - case 'f': - return reader2.getFloat32Array(arrayLength); + return allNodes; + }, - case 'i': - return reader2.getInt32Array(arrayLength); + // Check if reader has reached the end of content. + endOfContent: function endOfContent(reader) { - case 'l': - return reader2.getInt64Array(arrayLength); + // footer size: 160bytes + 16-byte alignment padding + // - 16bytes: magic + // - padding til 16-byte alignment (at least 1byte?) + // (seems like some exporters embed fixed 15 or 16bytes?) + // - 4bytes: magic + // - 4bytes: version + // - 120bytes: zero + // - 16bytes: magic + if (reader.size() % 16 === 0) { - } + return (reader.getOffset() + 160 + 16 & ~0xf) >= reader.size(); + } else { - default: - throw new Error('THREE.FBXLoader: Unknown property type ' + type); + return reader.getOffset() + 160 + 16 >= reader.size(); + } + }, - } - } + // recursively parse nodes until the end of the file is reached + parseNode: function parseNode(reader, version) { - }; + var node = {}; - function BinaryReader(buffer, littleEndian) { + // The first three data sizes depends on version. + var endOffset = version >= 7500 ? reader.getUint64() : reader.getUint32(); + var numProperties = version >= 7500 ? reader.getUint64() : reader.getUint32(); - this.dv = new DataView(buffer); - this.offset = 0; - this.littleEndian = littleEndian !== undefined ? littleEndian : true; - } + // note: do not remove this even if you get a linter warning as it moves the buffer forward + var propertyListLen = version >= 7500 ? reader.getUint64() : reader.getUint32(); - BinaryReader.prototype = { + var nameLen = reader.getUint8(); + var name = reader.getString(nameLen); - constructor: BinaryReader, + // Regards this node as NULL-record if endOffset is zero + if (endOffset === 0) return null; - getOffset: function getOffset() { + var propertyList = []; - return this.offset; - }, + for (var i = 0; i < numProperties; i++) { - size: function size() { + propertyList.push(this.parseProperty(reader)); + } - return this.dv.buffer.byteLength; - }, + // Regards the first three elements in propertyList as id, attrName, and attrType + var id = propertyList.length > 0 ? propertyList[0] : ''; + var attrName = propertyList.length > 1 ? propertyList[1] : ''; + var attrType = propertyList.length > 2 ? propertyList[2] : ''; - skip: function skip(length) { + // check if this node represents just a single property + // like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]} + node.singleProperty = numProperties === 1 && reader.getOffset() === endOffset ? true : false; - this.offset += length; - }, + while (endOffset > reader.getOffset()) { - // seems like true/false representation depends on exporter. - // true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54) - // then sees LSB. - getBoolean: function getBoolean() { + var subNode = this.parseNode(reader, version); - return (this.getUint8() & 1) === 1; - }, + if (subNode !== null) this.parseSubNode(name, node, subNode); + } - getBooleanArray: function getBooleanArray(size) { + node.propertyList = propertyList; // raw property list used by parent - var a = []; + if (typeof id === 'number') node.id = id; + if (attrName !== '') node.attrName = attrName; + if (attrType !== '') node.attrType = attrType; + if (name !== '') node.name = name; - for (var i = 0; i < size; i++) { + return node; + }, - a.push(this.getBoolean()); - } + parseSubNode: function parseSubNode(name, node, subNode) { - return a; - }, + // special case: child node is single property + if (subNode.singleProperty === true) { - getUint8: function getUint8() { + var value = subNode.propertyList[0]; - var value = this.dv.getUint8(this.offset); - this.offset += 1; - return value; - }, + if (Array.isArray(value)) { - getInt16: function getInt16() { + node[subNode.name] = subNode; - var value = this.dv.getInt16(this.offset, this.littleEndian); - this.offset += 2; - return value; - }, + subNode.a = value; + } else { - getInt32: function getInt32() { + node[subNode.name] = value; + } + } else if (name === 'Connections' && subNode.name === 'C') { - var value = this.dv.getInt32(this.offset, this.littleEndian); - this.offset += 4; - return value; - }, + var array = []; - getInt32Array: function getInt32Array(size) { + subNode.propertyList.forEach(function (property, i) { - var a = []; + // first Connection is FBX type (OO, OP, etc.). We'll discard these + if (i !== 0) array.push(property); + }); - for (var i = 0; i < size; i++) { + if (node.connections === undefined) { - a.push(this.getInt32()); - } + node.connections = []; + } - return a; - }, + node.connections.push(array); + } else if (subNode.name === 'Properties70') { - getUint32: function getUint32() { + var keys = Object.keys(subNode); - var value = this.dv.getUint32(this.offset, this.littleEndian); - this.offset += 4; - return value; - }, + keys.forEach(function (key) { - // JavaScript doesn't support 64-bit integer so calculate this here - // 1 << 32 will return 1 so using multiply operation instead here. - // There's a possibility that this method returns wrong value if the value - // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER. - // TODO: safely handle 64-bit integer - getInt64: function getInt64() { + node[key] = subNode[key]; + }); + } else if (name === 'Properties70' && subNode.name === 'P') { - var low, high; + var innerPropName = subNode.propertyList[0]; + var innerPropType1 = subNode.propertyList[1]; + var innerPropType2 = subNode.propertyList[2]; + var innerPropFlag = subNode.propertyList[3]; + var innerPropValue; - if (this.littleEndian) { + if (innerPropName.indexOf('Lcl ') === 0) innerPropName = innerPropName.replace('Lcl ', 'Lcl_'); + if (innerPropType1.indexOf('Lcl ') === 0) innerPropType1 = innerPropType1.replace('Lcl ', 'Lcl_'); - low = this.getUint32(); - high = this.getUint32(); - } else { + if (innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf('Lcl_') === 0) { - high = this.getUint32(); - low = this.getUint32(); - } + innerPropValue = [subNode.propertyList[4], subNode.propertyList[5], subNode.propertyList[6]]; + } else { - // calculate negative value - if (high & 0x80000000) { + innerPropValue = subNode.propertyList[4]; + } - high = ~high & 0xFFFFFFFF; - low = ~low & 0xFFFFFFFF; + // this will be copied to parent, see above + node[innerPropName] = { - if (low === 0xFFFFFFFF) high = high + 1 & 0xFFFFFFFF; + 'type': innerPropType1, + 'type2': innerPropType2, + 'flag': innerPropFlag, + 'value': innerPropValue - low = low + 1 & 0xFFFFFFFF; + }; + } else if (node[subNode.name] === undefined) { - return -(high * 0x100000000 + low); - } + if (typeof subNode.id === 'number') { - return high * 0x100000000 + low; - }, + node[subNode.name] = {}; + node[subNode.name][subNode.id] = subNode; + } else { - getInt64Array: function getInt64Array(size) { + node[subNode.name] = subNode; + } + } else { - var a = []; + if (subNode.name === 'PoseNode') { - for (var i = 0; i < size; i++) { + if (!Array.isArray(node[subNode.name])) { - a.push(this.getInt64()); - } + node[subNode.name] = [node[subNode.name]]; + } - return a; - }, + node[subNode.name].push(subNode); + } else if (node[subNode.name][subNode.id] === undefined) { - // Note: see getInt64() comment - getUint64: function getUint64() { + node[subNode.name][subNode.id] = subNode; + } + } + }, - var low, high; + parseProperty: function parseProperty(reader) { - if (this.littleEndian) { + var type = reader.getString(1); - low = this.getUint32(); - high = this.getUint32(); - } else { + switch (type) { - high = this.getUint32(); - low = this.getUint32(); - } + case 'C': + return reader.getBoolean(); - return high * 0x100000000 + low; - }, + case 'D': + return reader.getFloat64(); - getFloat32: function getFloat32() { + case 'F': + return reader.getFloat32(); - var value = this.dv.getFloat32(this.offset, this.littleEndian); - this.offset += 4; - return value; - }, + case 'I': + return reader.getInt32(); - getFloat32Array: function getFloat32Array(size) { + case 'L': + return reader.getInt64(); - var a = []; + case 'R': + var length = reader.getUint32(); + return reader.getArrayBuffer(length); - for (var i = 0; i < size; i++) { + case 'S': + var length = reader.getUint32(); + return reader.getString(length); - a.push(this.getFloat32()); - } + case 'Y': + return reader.getInt16(); - return a; - }, + case 'b': + case 'c': + case 'd': + case 'f': + case 'i': + case 'l': - getFloat64: function getFloat64() { + var arrayLength = reader.getUint32(); + var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed + var compressedLength = reader.getUint32(); - var value = this.dv.getFloat64(this.offset, this.littleEndian); - this.offset += 8; - return value; - }, + if (encoding === 0) { - getFloat64Array: function getFloat64Array(size) { + switch (type) { - var a = []; + case 'b': + case 'c': + return reader.getBooleanArray(arrayLength); - for (var i = 0; i < size; i++) { + case 'd': + return reader.getFloat64Array(arrayLength); - a.push(this.getFloat64()); - } + case 'f': + return reader.getFloat32Array(arrayLength); - return a; - }, + case 'i': + return reader.getInt32Array(arrayLength); - getArrayBuffer: function getArrayBuffer(size) { + case 'l': + return reader.getInt64Array(arrayLength); - var value = this.dv.buffer.slice(this.offset, this.offset + size); - this.offset += size; - return value; - }, + } + } - getString: function getString(size) { + if (typeof Zlib === 'undefined') { - // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead - var a = []; + console.error('THREE.FBXLoader: External library Inflate.min.js required, obtain or import from https://github.com/imaya/zlib.js'); + } - for (var i = 0; i < size; i++) { + var inflate = new Zlib.Inflate(new Uint8Array(reader.getArrayBuffer(compressedLength))); // eslint-disable-line no-undef + var reader2 = new BinaryReader(inflate.decompress().buffer); - a[i] = this.getUint8(); - } + switch (type) { - var nullByte = a.indexOf(0); - if (nullByte >= 0) a = a.slice(0, nullByte); + case 'b': + case 'c': + return reader2.getBooleanArray(arrayLength); - return THREE.LoaderUtils.decodeText(new Uint8Array(a)); - } + case 'd': + return reader2.getFloat64Array(arrayLength); - }; + case 'f': + return reader2.getFloat32Array(arrayLength); - // FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format) - // and BinaryParser( FBX Binary format) - function FBXTree() {} + case 'i': + return reader2.getInt32Array(arrayLength); - FBXTree.prototype = { + case 'l': + return reader2.getInt64Array(arrayLength); - constructor: FBXTree, + } - add: function add(key, val) { + default: + throw new Error('THREE.FBXLoader: Unknown property type ' + type); - this[key] = val; - } + } + } - }; + }; - // ************** UTILITY FUNCTIONS ************** + function BinaryReader(buffer, littleEndian) { - function isFbxFormatBinary(buffer) { + this.dv = new DataView(buffer); + this.offset = 0; + this.littleEndian = littleEndian !== undefined ? littleEndian : true; + } - var CORRECT = 'Kaydara FBX Binary \0'; + BinaryReader.prototype = { - return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString(buffer, 0, CORRECT.length); - } + constructor: BinaryReader, - function isFbxFormatASCII(text) { + getOffset: function getOffset() { - var CORRECT = ['K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\']; + return this.offset; + }, - var cursor = 0; + size: function size() { - function read(offset) { + return this.dv.buffer.byteLength; + }, - var result = text[offset - 1]; - text = text.slice(cursor + offset); - cursor++; - return result; - } + skip: function skip(length) { - for (var i = 0; i < CORRECT.length; ++i) { + this.offset += length; + }, - var num = read(1); - if (num === CORRECT[i]) { + // seems like true/false representation depends on exporter. + // true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54) + // then sees LSB. + getBoolean: function getBoolean() { - return false; - } - } + return (this.getUint8() & 1) === 1; + }, - return true; - } + getBooleanArray: function getBooleanArray(size) { - function getFbxVersion(text) { + var a = []; - var versionRegExp = /FBXVersion: (\d+)/; - var match = text.match(versionRegExp); - if (match) { + for (var i = 0; i < size; i++) { - var version = parseInt(match[1]); - return version; - } - throw new Error('THREE.FBXLoader: Cannot find the version number for the file given.'); + a.push(this.getBoolean()); } - // Converts FBX ticks into real time seconds. - function convertFBXTimeToSeconds(time) { + return a; + }, - return time / 46186158000; - } + getUint8: function getUint8() { - var dataArray = []; + var value = this.dv.getUint8(this.offset); + this.offset += 1; + return value; + }, - // extracts the data from the correct position in the FBX array based on indexing type - function getData(polygonVertexIndex, polygonIndex, vertexIndex, infoObject) { + getInt16: function getInt16() { - var index; + var value = this.dv.getInt16(this.offset, this.littleEndian); + this.offset += 2; + return value; + }, - switch (infoObject.mappingType) { + getInt32: function getInt32() { - case 'ByPolygonVertex': - index = polygonVertexIndex; - break; - case 'ByPolygon': - index = polygonIndex; - break; - case 'ByVertice': - index = vertexIndex; - break; - case 'AllSame': - index = infoObject.indices[0]; - break; - default: - console.warn('THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType); + var value = this.dv.getInt32(this.offset, this.littleEndian); + this.offset += 4; + return value; + }, - } + getInt32Array: function getInt32Array(size) { - if (infoObject.referenceType === 'IndexToDirect') index = infoObject.indices[index]; + var a = []; - var from = index * infoObject.dataSize; - var to = from + infoObject.dataSize; + for (var i = 0; i < size; i++) { - return slice(dataArray, infoObject.buffer, from, to); + a.push(this.getInt32()); } - var tempMat = new THREE.Matrix4(); - var tempEuler = new THREE.Euler(); - var tempVec = new THREE.Vector3(); - var translation = new THREE.Vector3(); - var rotation = new THREE.Matrix4(); + return a; + }, - // generate transformation from FBX transform data - // ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm - // transformData = { - // eulerOrder: int, - // translation: [], - // rotationOffset: [], - // preRotation - // rotation - // postRotation - // scale - // } - // all entries are optional - function generateTransform(transformData) { + getUint32: function getUint32() { - var transform = new THREE.Matrix4(); - translation.set(0, 0, 0); - rotation.identity(); + var value = this.dv.getUint32(this.offset, this.littleEndian); + this.offset += 4; + return value; + }, - var order = transformData.eulerOrder ? getEulerOrder(transformData.eulerOrder) : getEulerOrder(0); + // JavaScript doesn't support 64-bit integer so calculate this here + // 1 << 32 will return 1 so using multiply operation instead here. + // There's a possibility that this method returns wrong value if the value + // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER. + // TODO: safely handle 64-bit integer + getInt64: function getInt64() { - if (transformData.translation) translation.fromArray(transformData.translation); - if (transformData.rotationOffset) translation.add(tempVec.fromArray(transformData.rotationOffset)); + var low, high; - if (transformData.rotation) { + if (this.littleEndian) { - var array = transformData.rotation.map(THREE.MathUtils.degToRad); - array.push(order); - rotation.makeRotationFromEuler(tempEuler.fromArray(array)); - } + low = this.getUint32(); + high = this.getUint32(); + } else { + + high = this.getUint32(); + low = this.getUint32(); + } - if (transformData.preRotation) { + // calculate negative value + if (high & 0x80000000) { - var array = transformData.preRotation.map(THREE.MathUtils.degToRad); - array.push(order); - tempMat.makeRotationFromEuler(tempEuler.fromArray(array)); + high = ~high & 0xFFFFFFFF; + low = ~low & 0xFFFFFFFF; - rotation.premultiply(tempMat); - } + if (low === 0xFFFFFFFF) high = high + 1 & 0xFFFFFFFF; - if (transformData.postRotation) { + low = low + 1 & 0xFFFFFFFF; - var array = transformData.postRotation.map(THREE.MathUtils.degToRad); - array.push(order); - tempMat.makeRotationFromEuler(tempEuler.fromArray(array)); + return -(high * 0x100000000 + low); + } - tempMat.getInverse(tempMat); + return high * 0x100000000 + low; + }, - rotation.multiply(tempMat); - } + getInt64Array: function getInt64Array(size) { - if (transformData.scale) transform.scale(tempVec.fromArray(transformData.scale)); + var a = []; - transform.setPosition(translation); - transform.multiply(rotation); + for (var i = 0; i < size; i++) { - return transform; + a.push(this.getInt64()); } - // Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order - // ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html - function getEulerOrder(order) { + return a; + }, + + // Note: see getInt64() comment + getUint64: function getUint64() { - var enums = ['ZYX', // -> XYZ extrinsic - 'YZX', // -> XZY extrinsic - 'XZY', // -> YZX extrinsic - 'ZXY', // -> YXZ extrinsic - 'YXZ', // -> ZXY extrinsic - 'XYZ']; + var low, high; - if (order === 6) { + if (this.littleEndian) { - console.warn('THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.'); - return enums[0]; - } + low = this.getUint32(); + high = this.getUint32(); + } else { - return enums[order]; + high = this.getUint32(); + low = this.getUint32(); } - // Parses comma separated list of numbers and returns them an array. - // Used internally by the TextParser - function parseNumberArray(value) { + return high * 0x100000000 + low; + }, - var array = value.split(',').map(function (val) { + getFloat32: function getFloat32() { - return parseFloat(val); - }); + var value = this.dv.getFloat32(this.offset, this.littleEndian); + this.offset += 4; + return value; + }, - return array; - } + getFloat32Array: function getFloat32Array(size) { - function convertArrayBufferToString(buffer, from, to) { + var a = []; - if (from === undefined) from = 0; - if (to === undefined) to = buffer.byteLength; + for (var i = 0; i < size; i++) { - return THREE.LoaderUtils.decodeText(new Uint8Array(buffer, from, to)); + a.push(this.getFloat32()); } - function append(a, b) { + return a; + }, + + getFloat64: function getFloat64() { + + var value = this.dv.getFloat64(this.offset, this.littleEndian); + this.offset += 8; + return value; + }, - for (var i = 0, j = a.length, l = b.length; i < l; i++, j++) { + getFloat64Array: function getFloat64Array(size) { - a[j] = b[i]; - } + var a = []; + + for (var i = 0; i < size; i++) { + + a.push(this.getFloat64()); } - function slice(a, b, from, to) { + return a; + }, - for (var i = from, j = 0; i < to; i++, j++) { + getArrayBuffer: function getArrayBuffer(size) { - a[j] = b[i]; - } + var value = this.dv.buffer.slice(this.offset, this.offset + size); + this.offset += size; + return value; + }, + + getString: function getString(size) { + + // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead + var a = []; + + for (var i = 0; i < size; i++) { - return a; + a[i] = this.getUint8(); } - // inject array a2 into array a1 at index - function inject(a1, index, a2) { + var nullByte = a.indexOf(0); + if (nullByte >= 0) a = a.slice(0, nullByte); + + return THREE.LoaderUtils.decodeText(new Uint8Array(a)); + } + + }; + + // FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format) + // and BinaryParser( FBX Binary format) + function FBXTree() {} + + FBXTree.prototype = { + + constructor: FBXTree, + + add: function add(key, val) { + + this[key] = val; + } + + }; + + // ************** UTILITY FUNCTIONS ************** + + function isFbxFormatBinary(buffer) { + + var CORRECT = 'Kaydara FBX Binary \0'; + + return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString(buffer, 0, CORRECT.length); + } + + function isFbxFormatASCII(text) { + + var CORRECT = ['K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\']; + + var cursor = 0; + + function read(offset) { - return a1.slice(0, index).concat(a2).concat(a1.slice(index)); + var result = text[offset - 1]; + text = text.slice(cursor + offset); + cursor++; + return result; + } + + for (var i = 0; i < CORRECT.length; ++i) { + + var num = read(1); + if (num === CORRECT[i]) { + + return false; } + } + + return true; + } + + function getFbxVersion(text) { + + var versionRegExp = /FBXVersion: (\d+)/; + var match = text.match(versionRegExp); + if (match) { + + var version = parseInt(match[1]); + return version; + } + throw new Error('THREE.FBXLoader: Cannot find the version number for the file given.'); + } + + // Converts FBX ticks into real time seconds. + function convertFBXTimeToSeconds(time) { + + return time / 46186158000; + } + + var dataArray = []; + + // extracts the data from the correct position in the FBX array based on indexing type + function getData(polygonVertexIndex, polygonIndex, vertexIndex, infoObject) { + + var index; + + switch (infoObject.mappingType) { + + case 'ByPolygonVertex': + index = polygonVertexIndex; + break; + case 'ByPolygon': + index = polygonIndex; + break; + case 'ByVertice': + index = vertexIndex; + break; + case 'AllSame': + index = infoObject.indices[0]; + break; + default: + console.warn('THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType); + + } + + if (infoObject.referenceType === 'IndexToDirect') index = infoObject.indices[index]; + + var from = index * infoObject.dataSize; + var to = from + infoObject.dataSize; + + return slice(dataArray, infoObject.buffer, from, to); + } + + var tempMat = new THREE.Matrix4(); + var tempEuler = new THREE.Euler(); + var tempVec = new THREE.Vector3(); + var translation = new THREE.Vector3(); + var rotation = new THREE.Matrix4(); + + // generate transformation from FBX transform data + // ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm + // transformData = { + // eulerOrder: int, + // translation: [], + // rotationOffset: [], + // preRotation + // rotation + // postRotation + // scale + // } + // all entries are optional + function generateTransform(transformData) { + + var transform = new THREE.Matrix4(); + translation.set(0, 0, 0); + rotation.identity(); + + var order = transformData.eulerOrder ? getEulerOrder(transformData.eulerOrder) : getEulerOrder(0); + + if (transformData.translation) translation.fromArray(transformData.translation); + if (transformData.rotationOffset) translation.add(tempVec.fromArray(transformData.rotationOffset)); + + if (transformData.rotation) { + + var array = transformData.rotation.map(THREE.MathUtils.degToRad); + array.push(order); + rotation.makeRotationFromEuler(tempEuler.fromArray(array)); + } + + if (transformData.preRotation) { + + var array = transformData.preRotation.map(THREE.MathUtils.degToRad); + array.push(order); + tempMat.makeRotationFromEuler(tempEuler.fromArray(array)); + + rotation.premultiply(tempMat); + } + + if (transformData.postRotation) { + + var array = transformData.postRotation.map(THREE.MathUtils.degToRad); + array.push(order); + tempMat.makeRotationFromEuler(tempEuler.fromArray(array)); - return FBXLoader; + tempMat.getInverse(tempMat); + + rotation.multiply(tempMat); + } + + if (transformData.scale) transform.scale(tempVec.fromArray(transformData.scale)); + + transform.setPosition(translation); + transform.multiply(rotation); + + return transform; + } + + // Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order + // ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html + function getEulerOrder(order) { + + var enums = ['ZYX', // -> XYZ extrinsic + 'YZX', // -> XZY extrinsic + 'XZY', // -> YZX extrinsic + 'ZXY', // -> YXZ extrinsic + 'YXZ', // -> ZXY extrinsic + 'XYZ']; + + if (order === 6) { + + console.warn('THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.'); + return enums[0]; + } + + return enums[order]; + } + + // Parses comma separated list of numbers and returns them an array. + // Used internally by the TextParser + function parseNumberArray(value) { + + var array = value.split(',').map(function (val) { + + return parseFloat(val); + }); + + return array; + } + + function convertArrayBufferToString(buffer, from, to) { + + if (from === undefined) from = 0; + if (to === undefined) to = buffer.byteLength; + + return THREE.LoaderUtils.decodeText(new Uint8Array(buffer, from, to)); + } + + function append(a, b) { + + for (var i = 0, j = a.length, l = b.length; i < l; i++, j++) { + + a[j] = b[i]; + } + } + + function slice(a, b, from, to) { + + for (var i = from, j = 0; i < to; i++, j++) { + + a[j] = b[i]; + } + + return a; + } + + // inject array a2 into array a1 at index + function inject(a1, index, a2) { + + return a1.slice(0, index).concat(a2).concat(a1.slice(index)); + } + + return FBXLoader; }(); },{}],4:[function(require,module,exports){ @@ -8045,10 +8045,12 @@ module.exports = fetchScript; })(window); },{}],8:[function(require,module,exports){ +!function(t,i){"object"==typeof exports&&"object"==typeof module?module.exports=i():"function"==typeof define&&define.amd?define("nipplejs",[],i):"object"==typeof exports?exports.nipplejs=i():t.nipplejs=i()}(window,(function(){return function(t){var i={};function e(o){if(i[o])return i[o].exports;var n=i[o]={i:o,l:!1,exports:{}};return t[o].call(n.exports,n,n.exports,e),n.l=!0,n.exports}return e.m=t,e.c=i,e.d=function(t,i,o){e.o(t,i)||Object.defineProperty(t,i,{enumerable:!0,get:o})},e.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},e.t=function(t,i){if(1&i&&(t=e(t)),8&i)return t;if(4&i&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(e.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&i&&"string"!=typeof t)for(var n in t)e.d(o,n,function(i){return t[i]}.bind(null,n));return o},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,i){return Object.prototype.hasOwnProperty.call(t,i)},e.p="",e(e.s=0)}([function(t,i,e){"use strict";e.r(i);var o,n=function(t,i){var e=i.x-t.x,o=i.y-t.y;return Math.sqrt(e*e+o*o)},s=function(t){return t*(Math.PI/180)},r=function(t){return t*(180/Math.PI)},d=new Map,a=function(t){d.has(t)&&clearTimeout(d.get(t)),d.set(t,setTimeout(t,100))},p=function(t,i,e){for(var o,n=i.split(/[ ,]+/g),s=0;s=0&&this._handlers_[t].splice(this._handlers_[t].indexOf(i),1),this},_.prototype.trigger=function(t,i){var e,o=this,n=t.split(/[ ,]+/g);o._handlers_=o._handlers_||{};for(var s=0;ss&&n<3*s&&!t.lockX?i="up":n>-s&&n<=s&&!t.lockY?i="left":n>3*-s&&n<=-s&&!t.lockX?i="down":t.lockY||(i="right"),t.lockY||(e=n>-r&&n0?"up":"down"),t.force>this.options.threshold){var d,a={};for(d in this.direction)this.direction.hasOwnProperty(d)&&(a[d]=this.direction[d]);var p={};for(d in this.direction={x:e,y:o,angle:i},t.direction=this.direction,a)a[d]===this.direction[d]&&(p[d]=!0);if(p.x&&p.y&&p.angle)return t;p.x&&p.y||this.trigger("plain",t),p.x||this.trigger("plain:"+e,t),p.y||this.trigger("plain:"+o,t),p.angle||this.trigger("dir dir:"+i,t)}else this.resetDirection();return t};var P=k;function E(t,i){this.nipples=[],this.idles=[],this.actives=[],this.ids=[],this.pressureIntervals={},this.manager=t,this.id=E.id,E.id+=1,this.defaults={zone:document.body,multitouch:!1,maxNumberOfNipples:10,mode:"dynamic",position:{top:0,left:0},catchDistance:200,size:100,threshold:.1,color:"white",fadeTime:250,dataOnly:!1,restJoystick:!0,restOpacity:.5,lockX:!1,lockY:!1,shape:"circle",dynamicPage:!1,follow:!1},this.config(i),"static"!==this.options.mode&&"semi"!==this.options.mode||(this.options.multitouch=!1),this.options.multitouch||(this.options.maxNumberOfNipples=1);var e=getComputedStyle(this.options.zone.parentElement);return e&&"flex"===e.display&&(this.parentIsFlex=!0),this.updateBox(),this.prepareNipples(),this.bindings(),this.begin(),this.nipples}E.prototype=new T,E.constructor=E,E.id=0,E.prototype.prepareNipples=function(){var t=this.nipples;t.on=this.on.bind(this),t.off=this.off.bind(this),t.options=this.options,t.destroy=this.destroy.bind(this),t.ids=this.ids,t.id=this.id,t.processOnMove=this.processOnMove.bind(this),t.processOnEnd=this.processOnEnd.bind(this),t.get=function(i){if(void 0===i)return t[0];for(var e=0,o=t.length;e