plugins/threebox/loaders/ColladaLoader.js

/**
 * @author mrdoob / http://mrdoob.com/
 * @author Mugen87 / https://github.com/Mugen87
 */


const ColladaLoader = function (manager) {

    this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

};

ColladaLoader.prototype = {

    constructor: THREE.ColladaLoader,

    crossOrigin: 'Anonymous',

    load: function (url, onLoad, onProgress, onError) {

        var scope = this;

        var path = scope.path === undefined ? THREE.Loader.prototype.extractUrlBase(url) : scope.path;

        var loader = new THREE.FileLoader(scope.manager);
        loader.load(url, function (text) {

            onLoad(scope.parse(text, path));

        }, onProgress, onError);

    },

    setPath: function (value) {

        this.path = value;

    },

    options: {

        set convertUpAxis(value) {

            console.warn('THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.');

        }

    },

    setCrossOrigin: function (value) {

        this.crossOrigin = value;

    },

    parse: function (text, path) {

        function getElementsByTagName(xml, name) {

            // Non recursive xml.getElementsByTagName() ...

            var array = [];
            var childNodes = xml.childNodes;

            for (var i = 0, l = childNodes.length; i < l; i++) {

                var child = childNodes[i];

                if (child.nodeName === name) {

                    array.push(child);

                }

            }

            return array;

        }

        function parseStrings(text) {

            if (text.length === 0) return [];

            var parts = text.trim().split(/\s+/);
            var array = new Array(parts.length);

            for (var i = 0, l = parts.length; i < l; i++) {

                array[i] = parts[i];

            }

            return array;

        }

        function parseFloats(text) {

            if (text.length === 0) return [];

            var parts = text.trim().split(/\s+/);
            var array = new Array(parts.length);

            for (var i = 0, l = parts.length; i < l; i++) {

                array[i] = parseFloat(parts[i]);

            }

            return array;

        }

        function parseInts(text) {

            if (text.length === 0) return [];

            var parts = text.trim().split(/\s+/);
            var array = new Array(parts.length);

            for (var i = 0, l = parts.length; i < l; i++) {

                array[i] = parseInt(parts[i]);

            }

            return array;

        }

        function parseId(text) {

            return text.substring(1);

        }

        function generateId() {

            return 'three_default_' + ( count++ );

        }

        function isEmpty(object) {

            return Object.keys(object).length === 0;

        }

        // asset

        function parseAsset(xml) {

            return {
                unit: parseAssetUnit(getElementsByTagName(xml, 'unit')[0]),
                upAxis: parseAssetUpAxis(getElementsByTagName(xml, 'up_axis')[0])
            };

        }

        function parseAssetUnit(xml) {

            return xml !== undefined ? parseFloat(xml.getAttribute('meter')) : 1;

        }

        function parseAssetUpAxis(xml) {

            return xml !== undefined ? xml.textContent : 'Y_UP';

        }

        // library

        function parseLibrary(xml, libraryName, nodeName, parser) {

            var library = getElementsByTagName(xml, libraryName)[0];

            if (library !== undefined) {

                var elements = getElementsByTagName(library, nodeName);

                for (var i = 0; i < elements.length; i++) {

                    parser(elements[i]);

                }

            }

        }

        function buildLibrary(data, builder) {

            for (var name in data) {

                var object = data[name];
                object.build = builder(data[name]);

            }

        }

        // get

        function getBuild(data, builder) {

            if (data.build !== undefined) return data.build;

            data.build = builder(data);

            return data.build;

        }

        // animation

        function parseAnimation(xml) {

            var data = {
                sources: {},
                samplers: {},
                channels: {}
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                var id;

                switch (child.nodeName) {
                    case 'source':
                        id = child.getAttribute('id');
                        data.sources[id] = parseSource(child);
                        break;

                    case 'sampler':
                        id = child.getAttribute('id');
                        data.samplers[id] = parseAnimationSampler(child);
                        break;

                    case 'channel':
                        id = child.getAttribute('target');
                        data.channels[id] = parseAnimationChannel(child);
                        break;

                    default:
                        console.log(child);
                }

            }

            library.animations[xml.getAttribute('id')] = data;

        }

        function parseAnimationSampler(xml) {

            var data = {
                inputs: {}
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'input':
                        var id = parseId(child.getAttribute('source'));
                        var semantic = child.getAttribute('semantic');
                        data.inputs[semantic] = id;
                        break;
                }

            }

            return data;

        }

        function parseAnimationChannel(xml) {

            var data = {};

            var target = xml.getAttribute('target');

            // parsing SID Addressing Syntax

            var parts = target.split('/');

            var id = parts.shift();
            var sid = parts.shift();

            // check selection syntax

            var arraySyntax = ( sid.indexOf('(') !== -1 );
            var memberSyntax = ( sid.indexOf('.') !== -1 );

            if (memberSyntax) {

                //  member selection access

                parts = sid.split('.');
                sid = parts.shift();
                data.member = parts.shift();

            } else if (arraySyntax) {

                // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices.

                var indices = sid.split('(');
                sid = indices.shift();

                for (var i = 0; i < indices.length; i++) {

                    indices[i] = parseInt(indices[i].replace(/\)/, ''));

                }

                data.indices = indices;

            }

            data.id = id;
            data.sid = sid;

            data.arraySyntax = arraySyntax;
            data.memberSyntax = memberSyntax;

            data.sampler = parseId(xml.getAttribute('source'));

            return data;

        }

        function buildAnimation(data) {

            var tracks = [];

            var channels = data.channels;
            var samplers = data.samplers;
            var sources = data.sources;

            for (var target in channels) {

                if (channels.hasOwnProperty(target)) {

                    var channel = channels[target];
                    var sampler = samplers[channel.sampler];

                    var inputId = sampler.inputs.INPUT;
                    var outputId = sampler.inputs.OUTPUT;

                    var inputSource = sources[inputId];
                    var outputSource = sources[outputId];

                    var animation = buildAnimationChannel(channel, inputSource, outputSource);

                    createKeyframeTracks(animation, tracks);

                }

            }

            return tracks;

        }

        function getAnimation(id) {

            return getBuild(library.animations[id], buildAnimation);

        }

        function buildAnimationChannel(channel, inputSource, outputSource) {

            var node = library.nodes[channel.id];
            var object3D = getNode(node.id);

            var transform = node.transforms[channel.sid];
            var defaultMatrix = node.matrix.clone().transpose();

            var time, stride;
            var i, il, j, jl;

            var data = {};

            // the collada spec allows the animation of data in various ways.
            // depending on the transform type (matrix, translate, rotate, scale), we execute different logic

            switch (transform) {
                case 'matrix':

                    for (i = 0, il = inputSource.array.length; i < il; i++) {

                        time = inputSource.array[i];
                        stride = i * outputSource.stride;

                        if (data[time] === undefined) data[time] = {};

                        if (channel.arraySyntax === true) {

                            var value = outputSource.array[stride];
                            var index = channel.indices[0] + 4 * channel.indices[1];

                            data[time][index] = value;

                        } else {

                            for (j = 0, jl = outputSource.stride; j < jl; j++) {

                                data[time][j] = outputSource.array[stride + j];

                            }

                        }

                    }

                    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;

                case 'scale':
                    console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform);
                    break;
            }

            var keyframes = prepareAnimationData(data, defaultMatrix);

            var animation = {
                name: object3D.uuid,
                keyframes: keyframes
            };

            return animation;

        }

        function prepareAnimationData(data, defaultMatrix) {

            var keyframes = [];

            // transfer data into a sortable array

            for (var time in data) {

                keyframes.push({time: parseFloat(time), value: data[time]});

            }

            // ensure keyframes are sorted by time

            keyframes.sort(ascending);

            // now we clean up all animation data, so we can use them for keyframe tracks

            for (var i = 0; i < 16; i++) {

                transformAnimationData(keyframes, i, defaultMatrix.elements[i]);

            }

            return keyframes;

            // array sort function

            function ascending(a, b) {

                return a.time - b.time;

            }

        }

        var position = new THREE.Vector3();
        var scale = new THREE.Vector3();
        var quaternion = new THREE.Quaternion();

        function createKeyframeTracks(animation, tracks) {

            var keyframes = animation.keyframes;
            var name = animation.name;

            var times = [];
            var positionData = [];
            var quaternionData = [];
            var scaleData = [];

            for (var i = 0, l = keyframes.length; i < l; i++) {

                var keyframe = keyframes[i];

                var time = keyframe.time;
                var value = keyframe.value;

                matrix.fromArray(value).transpose();
                matrix.decompose(position, quaternion, scale);

                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);

            }

            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));

            return tracks;

        }

        function transformAnimationData(keyframes, property, defaultValue) {

            var keyframe;

            var empty = true;
            var i, l;

            // check, if values of a property are missing in our keyframes

            for (i = 0, l = keyframes.length; i < l; i++) {

                keyframe = keyframes[i];

                if (keyframe.value[property] === undefined) {

                    keyframe.value[property] = null; // mark as missing

                } else {

                    empty = false;

                }

            }

            if (empty === true) {

                // no values at all, so we set a default value

                for (i = 0, l = keyframes.length; i < l; i++) {

                    keyframe = keyframes[i];

                    keyframe.value[property] = defaultValue;

                }

            } else {

                // filling gaps

                createMissingKeyframes(keyframes, property);

            }

        }

        function createMissingKeyframes(keyframes, property) {

            var prev, next;

            for (var i = 0, l = keyframes.length; i < l; i++) {

                var keyframe = keyframes[i];

                if (keyframe.value[property] === null) {

                    prev = getPrev(keyframes, i, property);
                    next = getNext(keyframes, i, property);

                    if (prev === null) {

                        keyframe.value[property] = next.value[property];
                        continue;

                    }

                    if (next === null) {

                        keyframe.value[property] = prev.value[property];
                        continue;

                    }

                    interpolate(keyframe, prev, next, property);

                }

            }

        }

        function getPrev(keyframes, i, property) {

            while (i >= 0) {

                var keyframe = keyframes[i];

                if (keyframe.value[property] !== null) return keyframe;

                i--;

            }

            return null;

        }

        function getNext(keyframes, i, property) {

            while (i < keyframes.length) {

                var keyframe = keyframes[i];

                if (keyframe.value[property] !== null) return keyframe;

                i++;

            }

            return null;

        }

        function interpolate(key, prev, next, property) {

            if (( next.time - prev.time ) === 0) {

                key.value[property] = prev.value[property];
                return;

            }

            key.value[property] = ( ( key.time - prev.time ) * ( next.value[property] - prev.value[property] ) / ( next.time - prev.time ) ) + prev.value[property];

        }

        // animation clips

        function parseAnimationClip(xml) {

            var data = {
                name: xml.getAttribute('id') || 'default',
                start: parseFloat(xml.getAttribute('start') || 0),
                end: parseFloat(xml.getAttribute('end') || 0),
                animations: []
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'instance_animation':
                        data.animations.push(parseId(child.getAttribute('url')));
                        break;
                }

            }

            library.clips[xml.getAttribute('id')] = data;

        }

        function buildAnimationClip(data) {

            var tracks = [];

            var name = data.name;
            var duration = ( data.end - data.start ) || -1;
            var animations = data.animations;

            for (var i = 0, il = animations.length; i < il; i++) {

                var animationTracks = getAnimation(animations[i]);

                for (var j = 0, jl = animationTracks.length; j < jl; j++) {

                    tracks.push(animationTracks[j]);

                }

            }

            return new THREE.AnimationClip(name, duration, tracks);

        }

        function getAnimationClip(id) {

            return getBuild(library.clips[id], buildAnimationClip);

        }

        // controller

        function parseController(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'skin':
                        // there is exactly one skin per controller
                        data.id = parseId(child.getAttribute('source'));
                        data.skin = parseSkin(child);
                        break;

                    case 'morph':
                        data.id = parseId(child.getAttribute('source'));
                        console.warn('THREE.ColladaLoader: Morph target animation not supported yet.');
                        break;
                }

            }

            library.controllers[xml.getAttribute('id')] = data;

        }

        function parseSkin(xml) {

            var data = {
                sources: {}
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'bind_shape_matrix':
                        data.bindShapeMatrix = parseFloats(child.textContent);
                        break;

                    case 'source':
                        var id = child.getAttribute('id');
                        data.sources[id] = parseSource(child);
                        break;

                    case 'joints':
                        data.joints = parseJoints(child);
                        break;

                    case 'vertex_weights':
                        data.vertexWeights = parseVertexWeights(child);
                        break;
                }

            }

            return data;

        }

        function parseJoints(xml) {

            var data = {
                inputs: {}
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'input':
                        var semantic = child.getAttribute('semantic');
                        var id = parseId(child.getAttribute('source'));
                        data.inputs[semantic] = id;
                        break;
                }

            }

            return data;

        }

        function parseVertexWeights(xml) {

            var data = {
                inputs: {}
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    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;

                    case 'v':
                        data.v = parseInts(child.textContent);
                        break;
                }

            }

            return data;

        }

        function buildController(data) {

            var build = {
                id: data.id
            };

            var geometry = library.geometries[build.id];

            if (data.skin !== undefined) {

                build.skin = buildSkin(data.skin);

                // we enhance the 'sources' property of the corresponding geometry with our skin data

                geometry.sources.skinIndices = build.skin.indices;
                geometry.sources.skinWeights = build.skin.weights;

            }

            return build;

        }

        function buildSkin(data) {

            var BONE_LIMIT = 4;

            var build = {
                joints: [], // this must be an array to preserve the joint order
                indices: {
                    array: [],
                    stride: BONE_LIMIT
                },
                weights: {
                    array: [],
                    stride: BONE_LIMIT
                }
            };

            var sources = data.sources;
            var vertexWeights = data.vertexWeights;

            var vcount = vertexWeights.vcount;
            var v = vertexWeights.v;
            var jointOffset = vertexWeights.inputs.JOINT.offset;
            var weightOffset = vertexWeights.inputs.WEIGHT.offset;

            var jointSource = data.sources[data.joints.inputs.JOINT];
            var inverseSource = data.sources[data.joints.inputs.INV_BIND_MATRIX];

            var weights = sources[vertexWeights.inputs.WEIGHT.id].array;
            var stride = 0;

            var i, j, l;

            // procces skin data for each vertex

            for (i = 0, l = vcount.length; i < l; i++) {

                var jointCount = vcount[i]; // this is the amount of joints that affect a single vertex
                var vertexSkinData = [];

                for (j = 0; j < jointCount; j++) {

                    var skinIndex = v[stride + jointOffset];
                    var weightId = v[stride + weightOffset];
                    var skinWeight = weights[weightId];

                    vertexSkinData.push({index: skinIndex, weight: skinWeight});

                    stride += 2;

                }

                // we sort the joints in descending order based on the weights.
                // this ensures, we only procced the most important joints of the vertex

                vertexSkinData.sort(descending);

                // 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

                for (j = 0; j < BONE_LIMIT; j++) {

                    var d = vertexSkinData[j];

                    if (d !== undefined) {

                        build.indices.array.push(d.index);
                        build.weights.array.push(d.weight);

                    } else {

                        build.indices.array.push(0);
                        build.weights.array.push(0);

                    }

                }

            }

            // setup bind matrix

            build.bindMatrix = new THREE.Matrix4().fromArray(data.bindShapeMatrix).transpose();

            // process bones and inverse bind matrix data

            for (i = 0, l = jointSource.array.length; i < l; i++) {

                var name = jointSource.array[i];
                var boneInverse = new THREE.Matrix4().fromArray(inverseSource.array, i * inverseSource.stride).transpose();

                build.joints.push({name: name, boneInverse: boneInverse});

            }

            return build;

            // array sort function

            function descending(a, b) {

                return b.weight - a.weight;

            }

        }

        function getController(id) {

            return getBuild(library.controllers[id], buildController);

        }

        // image

        function parseImage(xml) {

            var data = {
                init_from: getElementsByTagName(xml, 'init_from')[0].textContent
            };

            library.images[xml.getAttribute('id')] = data;

        }

        function buildImage(data) {

            if (data.build !== undefined) return data.build;

            return data.init_from;

        }

        function getImage(id) {

            return getBuild(library.images[id], buildImage);

        }

        // effect

        function parseEffect(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'profile_COMMON':
                        data.profile = parseEffectProfileCOMMON(child);
                        break;
                }

            }

            library.effects[xml.getAttribute('id')] = data;

        }

        function parseEffectProfileCOMMON(xml) {

            var data = {
                surfaces: {},
                samplers: {}
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'newparam':
                        parseEffectNewparam(child, data);
                        break;

                    case 'technique':
                        data.technique = parseEffectTechnique(child);
                        break;
                }

            }

            return data;

        }

        function parseEffectNewparam(xml, data) {

            var sid = xml.getAttribute('sid');

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'surface':
                        data.surfaces[sid] = parseEffectSurface(child);
                        break;

                    case 'sampler2D':
                        data.samplers[sid] = parseEffectSampler(child);
                        break;
                }

            }

        }

        function parseEffectSurface(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'init_from':
                        data.init_from = child.textContent;
                        break;
                }

            }

            return data;

        }

        function parseEffectSampler(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'source':
                        data.source = child.textContent;
                        break;
                }

            }

            return data;

        }

        function parseEffectTechnique(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'constant':
                    case 'lambert':
                    case 'blinn':
                    case 'phong':
                        data.type = child.nodeName;
                        data.parameters = parseEffectParameters(child);
                        break;
                }

            }

            return data;

        }

        function parseEffectParameters(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'emission':
                    case 'diffuse':
                    case 'specular':
                    case 'shininess':
                    case 'transparent':
                    case 'transparency':
                        data[child.nodeName] = parseEffectParameter(child);
                        break;
                }

            }

            return data;

        }

        function parseEffectParameter(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'color':
                        data[child.nodeName] = parseFloats(child.textContent);
                        break;

                    case 'float':
                        data[child.nodeName] = parseFloat(child.textContent);
                        break;

                    case 'texture':
                        data[child.nodeName] = {
                            id: child.getAttribute('texture'),
                            extra: parseEffectParameterTexture(child)
                        };
                        break;
                }

            }

            return data;

        }

        function parseEffectParameterTexture(xml) {

            var data = {
                technique: {}
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'extra':
                        parseEffectParameterTextureExtra(child, data);
                        break;
                }

            }

            return data;

        }

        function parseEffectParameterTextureExtra(xml, data) {

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'technique':
                        parseEffectParameterTextureExtraTechnique(child, data);
                        break;
                }

            }

        }

        function parseEffectParameterTextureExtraTechnique(xml, data) {

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'repeatU':
                    case 'repeatV':
                    case 'offsetU':
                    case 'offsetV':
                        data.technique[child.nodeName] = parseFloat(child.textContent);
                        break;

                    case 'wrapU':
                    case 'wrapV':

                        // some files have values for wrapU/wrapV which become NaN via parseInt

                        if (child.textContent.toUpperCase() === 'TRUE') {

                            data.technique[child.nodeName] = 1;

                        } else if (child.textContent.toUpperCase() === 'FALSE') {

                            data.technique[child.nodeName] = 0;

                        } else {

                            data.technique[child.nodeName] = parseInt(child.textContent);

                        }

                        break;
                }

            }

        }

        function buildEffect(data) {

            return data;

        }

        function getEffect(id) {

            return getBuild(library.effects[id], buildEffect);

        }

        // material

        function parseMaterial(xml) {

            var data = {
                name: xml.getAttribute('name')
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'instance_effect':
                        data.url = parseId(child.getAttribute('url'));
                        break;
                }

            }

            library.materials[xml.getAttribute('id')] = data;

        }

        function buildMaterial(data) {

            var effect = getEffect(data.url);
            var technique = effect.profile.technique;

            var material;

            switch (technique.type) {
                case 'phong':
                case 'blinn':
                    material = new THREE.MeshPhongMaterial();
                    break;

                case 'lambert':
                    material = new THREE.MeshLambertMaterial();
                    break;

                default:
                    material = new THREE.MeshBasicMaterial();
                    break;
            }

            material.name = data.name;

            function getTexture(textureObject) {

                var sampler = effect.profile.samplers[textureObject.id];

                if (sampler !== undefined) {

                    var surface = effect.profile.surfaces[sampler.source];

                    var texture = textureLoader.load(getImage(surface.init_from));

                    var extra = textureObject.extra;

                    if (extra !== undefined && extra.technique !== undefined && isEmpty(extra.technique) === false) {

                        var technique = extra.technique;

                        texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
                        texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;

                        texture.offset.set(technique.offsetU || 0, technique.offsetV || 0);
                        texture.repeat.set(technique.repeatU || 1, technique.repeatV || 1);

                    } else {

                        texture.wrapS = THREE.RepeatWrapping;
                        texture.wrapT = THREE.RepeatWrapping;

                    }

                    return texture;

                }

                console.error('THREE.ColladaLoader: Undefined sampler', textureObject.id);

                return null;

            }

            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 'shininess':
                        if (parameter.float && material.shininess)
                            material.shininess = parameter.float;
                        break;
                    case 'emission':
                        if (parameter.color && material.emissive)
                            material.emissive.fromArray(parameter.color);
                        break;
                    case 'transparent':
                        // if ( parameter.texture ) material.alphaMap = getTexture( parameter.texture );
                        material.transparent = true;
                        break;
                    case 'transparency':
                        if (parameter.float !== undefined) material.opacity = parameter.float;
                        material.transparent = true;
                        break;
                }

            }

            return material;

        }

        function getMaterial(id) {

            return getBuild(library.materials[id], buildMaterial);

        }

        // camera

        function parseCamera(xml) {

            var data = {
                name: xml.getAttribute('name')
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'optics':
                        data.optics = parseCameraOptics(child);
                        break;
                }

            }

            library.cameras[xml.getAttribute('id')] = data;

        }

        function parseCameraOptics(xml) {

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                switch (child.nodeName) {
                    case 'technique_common':
                        return parseCameraTechnique(child);
                }

            }

            return {};

        }

        function parseCameraTechnique(xml) {

            var data = {};

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                switch (child.nodeName) {
                    case 'perspective':
                    case 'orthographic':

                        data.technique = child.nodeName;
                        data.parameters = parseCameraParameters(child);

                        break;
                }

            }

            return data;

        }

        function parseCameraParameters(xml) {

            var data = {};

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                switch (child.nodeName) {
                    case 'xfov':
                    case 'yfov':
                    case 'xmag':
                    case 'ymag':
                    case 'znear':
                    case 'zfar':
                    case 'aspect_ratio':
                        data[child.nodeName] = parseFloat(child.textContent);
                        break;
                }

            }

            return data;

        }

        function buildCamera(data) {

            var camera;

            switch (data.optics.technique) {
                case 'perspective':
                    camera = new THREE.PerspectiveCamera(
                        data.optics.parameters.yfov,
                        data.optics.parameters.aspect_ratio,
                        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;

                    xmag = ( xmag === undefined ) ? ( ymag * aspectRatio ) : xmag;
                    ymag = ( ymag === undefined ) ? ( xmag / aspectRatio ) : ymag;

                    xmag *= 0.5;
                    ymag *= 0.5;

                    camera = new THREE.OrthographicCamera(
                        -xmag, xmag, ymag, -ymag, // left, right, top, bottom
                        data.optics.parameters.znear,
                        data.optics.parameters.zfar
                    );
                    break;

                default:
                    camera = new THREE.PerspectiveCamera();
                    break;
            }

            camera.name = data.name;

            return camera;

        }

        function getCamera(id) {

            return getBuild(library.cameras[id], buildCamera);

        }

        // light

        function parseLight(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'technique_common':
                        data = parseLightTechnique(child);
                        break;
                }

            }

            library.lights[xml.getAttribute('id')] = data;

        }

        function parseLightTechnique(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'directional':
                    case 'point':
                    case 'spot':
                    case 'ambient':

                        data.technique = child.nodeName;
                        data.parameters = parseLightParameters(child);
                }

            }

            return data;

        }

        function parseLightParameters(xml) {

            var data = {};

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'color':
                        var array = parseFloats(child.textContent);
                        data.color = new THREE.Color().fromArray(array);
                        break;

                    case 'falloff_angle':
                        data.falloffAngle = parseFloat(child.textContent);
                        break;

                    case 'quadratic_attenuation':
                        var f = parseFloat(child.textContent);
                        data.distance = f ? Math.sqrt(1 / f) : 0;
                        break;
                }

            }

            return data;

        }

        function buildLight(data) {

            var light;

            switch (data.technique) {
                case 'directional':
                    light = new THREE.DirectionalLight();
                    break;

                case 'point':
                    light = new THREE.PointLight();
                    break;

                case 'spot':
                    light = new THREE.SpotLight();
                    break;

                case 'ambient':
                    light = new THREE.AmbientLight();
                    break;
            }

            if (data.parameters.color) light.color.copy(data.parameters.color);
            if (data.parameters.distance) light.distance = data.parameters.distance;

            return light;

        }

        function getLight(id) {

            return getBuild(library.lights[id], buildLight);

        }

        // geometry

        function parseGeometry(xml) {

            var data = {
                name: xml.getAttribute('name'),
                sources: {},
                vertices: {},
                primitives: []
            };

            var mesh = getElementsByTagName(xml, 'mesh')[0];

            // 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;

            for (var i = 0; i < mesh.childNodes.length; i++) {

                var child = mesh.childNodes[i];

                if (child.nodeType !== 1) continue;

                var id = child.getAttribute('id');

                switch (child.nodeName) {
                    case 'source':
                        data.sources[id] = parseSource(child);
                        break;

                    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;

                    case 'lines':
                    case 'linestrips':
                    case 'polylist':
                    case 'triangles':
                        data.primitives.push(parseGeometryPrimitive(child));
                        break;

                    default:
                        console.log(child);
                }

            }

            library.geometries[xml.getAttribute('id')] = data;

        }

        function parseSource(xml) {

            var data = {
                array: [],
                stride: 3
            };

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'float_array':
                        data.array = parseFloats(child.textContent);
                        break;

                    case 'Name_array':
                        data.array = parseStrings(child.textContent);
                        break;

                    case 'technique_common':
                        var accessor = getElementsByTagName(child, 'accessor')[0];

                        if (accessor !== undefined) {

                            data.stride = parseInt(accessor.getAttribute('stride'));

                        }
                        break;
                }

            }

            return data;

        }

        function parseGeometryVertices(xml) {

            var data = {};

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                data[child.getAttribute('semantic')] = parseId(child.getAttribute('source'));

            }

            return data;

        }

        function parseGeometryPrimitive(xml) {

            var primitive = {
                type: xml.nodeName,
                material: xml.getAttribute('material'),
                count: parseInt(xml.getAttribute('count')),
                inputs: {},
                stride: 0
            };

            for (var i = 0, l = xml.childNodes.length; i < l; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'input':
                        var id = parseId(child.getAttribute('source'));
                        var semantic = child.getAttribute('semantic');
                        var offset = parseInt(child.getAttribute('offset'));
                        primitive.inputs[semantic] = {id: id, offset: offset};
                        primitive.stride = Math.max(primitive.stride, offset + 1);
                        break;

                    case 'vcount':
                        primitive.vcount = parseInts(child.textContent);
                        break;

                    case 'p':
                        primitive.p = parseInts(child.textContent);
                        break;
                }

            }

            return primitive;

        }

        function groupPrimitives(primitives) {

            var build = {};

            for (var i = 0; i < primitives.length; i++) {

                var primitive = primitives[i];

                if (build[primitive.type] === undefined) build[primitive.type] = [];

                build[primitive.type].push(primitive);

            }

            return build;

        }

        function buildGeometry(data) {

            var build = {};

            var sources = data.sources;
            var vertices = data.vertices;
            var primitives = data.primitives;

            if (primitives.length === 0) return {};

            // our goal is to create one buffer geoemtry for a single type of primitives
            // first, we group all primitives by their type

            var groupedPrimitives = groupPrimitives(primitives);

            for (var type in groupedPrimitives) {

                // second, we create for each type of primitives (polylist,triangles or lines) a buffer geometry

                build[type] = buildGeometryType(groupedPrimitives[type], sources, vertices);

            }

            return build;

        }

        function buildGeometryType(primitives, sources, vertices) {

            var build = {};

            var position = {array: [], stride: 0};
            var normal = {array: [], stride: 0};
            var uv = {array: [], stride: 0};
            var color = {array: [], stride: 0};

            var skinIndex = {array: [], stride: 4};
            var skinWeight = {array: [], stride: 4};

            var geometry = new THREE.BufferGeometry();

            var materialKeys = [];

            var start = 0, count = 0;

            for (var p = 0; p < primitives.length; p++) {

                var primitive = primitives[p];
                var inputs = primitive.inputs;
                var triangleCount = 1;

                if (primitive.vcount && primitive.vcount[0] === 4) {

                    triangleCount = 2; // one quad -> two triangles

                }

                // groups

                if (primitive.type === 'lines' || primitive.type === 'linestrips') {

                    count = primitive.count * 2;

                } else {

                    count = primitive.count * 3 * triangleCount;

                }

                geometry.addGroup(start, count, p);
                start += count;

                // material

                if (primitive.material) {

                    materialKeys.push(primitive.material);

                }

                // geometry data

                for (var name in inputs) {

                    var input = inputs[name];

                    switch (name) {
                        case 'VERTEX':
                            for (var key in vertices) {

                                var id = vertices[key];

                                switch (key) {
                                    case 'POSITION':
                                        buildGeometryData(primitive, sources[id], input.offset, position.array);
                                        position.stride = sources[id].stride;

                                        if (sources.skinWeights && sources.skinIndices) {

                                            buildGeometryData(primitive, sources.skinIndices, input.offset, skinIndex.array);
                                            buildGeometryData(primitive, sources.skinWeights, input.offset, skinWeight.array);

                                        }
                                        break;

                                    case 'NORMAL':
                                        buildGeometryData(primitive, sources[id], input.offset, normal.array);
                                        normal.stride = sources[id].stride;
                                        break;

                                    case 'COLOR':
                                        buildGeometryData(primitive, sources[id], input.offset, color.array);
                                        color.stride = sources[id].stride;
                                        break;

                                    case 'TEXCOORD':
                                        buildGeometryData(primitive, sources[id], input.offset, uv.array);
                                        uv.stride = sources[id].stride;
                                        break;

                                    default:
                                        console.warn('THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key);
                                }

                            }
                            break;

                        case 'NORMAL':
                            buildGeometryData(primitive, sources[input.id], input.offset, normal.array);
                            normal.stride = sources[input.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[input.id], input.offset, uv.array);
                            uv.stride = sources[input.id].stride;
                            break;
                    }

                }

            }

            // build geometry

            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 (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));

            build.data = geometry;
            build.type = primitives[0].type;
            build.materialKeys = materialKeys;

            return build;

        }

        function buildGeometryData(primitive, source, offset, array) {

            var indices = primitive.p;
            var stride = primitive.stride;
            var vcount = primitive.vcount;

            function pushVector(i) {

                var index = indices[i + offset] * sourceStride;
                var length = index + sourceStride;

                for (; index < length; index++) {

                    array.push(sourceArray[index]);

                }

            }

            var maxcount = 0;

            var sourceArray = source.array;
            var sourceStride = source.stride;

            if (primitive.vcount !== undefined) {

                var index = 0;

                for (var i = 0, l = vcount.length; i < l; i++) {

                    var count = vcount[i];

                    if (count === 4) {

                        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(d);
                        pushVector(b);
                        pushVector(c);
                        pushVector(d);

                    } else if (count === 3) {

                        var a = index + stride * 0;
                        var b = index + stride * 1;
                        var c = index + stride * 2;

                        pushVector(a);
                        pushVector(b);
                        pushVector(c);

                    } else {

                        maxcount = Math.max(maxcount, count);

                    }

                    index += stride * count;

                }

                if (maxcount > 0) {

                    console.log('THREE.ColladaLoader: Geometry has faces with more than 4 vertices.');

                }

            } else {

                for (var i = 0, l = indices.length; i < l; i += stride) {

                    pushVector(i);

                }

            }

        }

        function getGeometry(id) {

            return getBuild(library.geometries[id], buildGeometry);

        }

        // kinematics

        function parseKinematicsModel(xml) {

            var data = {
                name: xml.getAttribute('name') || '',
                joints: {},
                links: []
            };

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'technique_common':
                        parseKinematicsTechniqueCommon(child, data);
                        break;
                }

            }

            library.kinematicsModels[xml.getAttribute('id')] = data;

        }

        function buildKinematicsModel(data) {

            if (data.build !== undefined) return data.build;

            return data;

        }

        function getKinematicsModel(id) {

            return getBuild(library.kinematicsModels[id], buildKinematicsModel);

        }

        function parseKinematicsTechniqueCommon(xml, data) {

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'joint':
                        data.joints[child.getAttribute('sid')] = parseKinematicsJoint(child);
                        break;

                    case 'link':
                        data.links.push(parseKinematicsLink(child));
                        break;
                }

            }

        }

        function parseKinematicsJoint(xml) {

            var data;

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'prismatic':
                    case 'revolute':
                        data = parseKinematicsJointParameter(child);
                        break;
                }

            }

            return data;

        }

        function parseKinematicsJointParameter(xml, data) {

            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
            };

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    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];

                        data.limits.max = parseFloat(max.textContent);
                        data.limits.min = parseFloat(min.textContent);
                        break;
                }

            }

            // if min is equal to or greater than max, consider the joint static

            if (data.limits.min >= data.limits.max) {

                data.static = true;

            }

            // calculate middle position

            data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0;

            return data;

        }

        function parseKinematicsLink(xml) {

            var data = {
                sid: xml.getAttribute('sid'),
                name: xml.getAttribute('name') || '',
                attachments: [],
                transforms: []
            };

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'attachment_full':
                        data.attachments.push(parseKinematicsAttachment(child));
                        break;

                    case 'matrix':
                    case 'translate':
                    case 'rotate':
                        data.transforms.push(parseKinematicsTransform(child));
                        break;
                }

            }

            return data;

        }

        function parseKinematicsAttachment(xml) {

            var data = {
                joint: xml.getAttribute('joint').split('/').pop(),
                transforms: [],
                links: []
            };

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'link':
                        data.links.push(parseKinematicsLink(child));
                        break;

                    case 'matrix':
                    case 'translate':
                    case 'rotate':
                        data.transforms.push(parseKinematicsTransform(child));
                        break;
                }

            }

            return data;

        }

        function parseKinematicsTransform(xml) {

            var data = {
                type: xml.nodeName
            };

            var array = parseFloats(xml.textContent);

            switch (data.type) {
                case 'matrix':
                    data.obj = new THREE.Matrix4();
                    data.obj.fromArray(array).transpose();
                    break;

                case 'translate':
                    data.obj = new THREE.Vector3();
                    data.obj.fromArray(array);
                    break;

                case 'rotate':
                    data.obj = new THREE.Vector3();
                    data.obj.fromArray(array);
                    data.angle = THREE.Math.degToRad(array[3]);
                    break;
            }

            return data;

        }

        function parseKinematicsScene(xml) {

            var data = {
                bindJointAxis: []
            };

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    case 'bind_joint_axis':
                        data.bindJointAxis.push(parseKinematicsBindJointAxis(child));
                        break;
                }

            }

            library.kinematicsScenes[parseId(xml.getAttribute('url'))] = data;

        }

        function parseKinematicsBindJointAxis(xml) {

            var data = {
                target: xml.getAttribute('target').split('/').pop()
            };

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                if (child.nodeType !== 1) continue;

                switch (child.nodeName) {
                    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;
                }

            }

            return data;

        }

        function buildKinematicsScene(data) {

            if (data.build !== undefined) return data.build;

            return data;

        }

        function getKinematicsScene(id) {

            return getBuild(library.kinematicsScenes[id], buildKinematicsScene);

        }

        function setupKinematics() {

            var kinematicsModelId = Object.keys(library.kinematicsModels)[0];
            var kinematicsSceneId = Object.keys(library.kinematicsScenes)[0];
            var visualSceneId = Object.keys(library.visualScenes)[0];

            if (kinematicsModelId === undefined || kinematicsSceneId === undefined) return;

            var kinematicsModel = getKinematicsModel(kinematicsModelId);
            var kinematicsScene = getKinematicsScene(kinematicsSceneId);
            var visualScene = getVisualScene(visualSceneId);

            var bindJointAxis = kinematicsScene.bindJointAxis;
            var jointMap = {};

            for (var i = 0, l = bindJointAxis.length; i < l; i++) {

                var axis = bindJointAxis[i];

                // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix'

                var targetElement = collada.querySelector('[sid="' + axis.target + '"]');

                if (targetElement) {

                    // get the parent of the transfrom element

                    var parentVisualElement = targetElement.parentElement;

                    // connect the joint of the kinematics model with the element in the visual scene

                    connect(axis.jointIndex, parentVisualElement);

                }

            }

            function connect(jointIndex, visualElement) {

                var visualElementName = visualElement.getAttribute('name');
                var joint = kinematicsModel.joints[jointIndex];

                visualScene.traverse(function (object) {

                    if (object.name === visualElementName) {

                        jointMap[jointIndex] = {
                            object: object,
                            transforms: buildTransformList(visualElement),
                            joint: joint,
                            position: joint.zeroPosition
                        };

                    }

                });

            }

            var m0 = new THREE.Matrix4();

            kinematics = {

                joints: kinematicsModel && kinematicsModel.joints,

                getJointValue: function (jointIndex) {

                    var jointData = jointMap[jointIndex];

                    if (jointData) {

                        return jointData.position;

                    } else {

                        console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.');

                    }

                },

                setJointValue: function (jointIndex, value) {

                    var jointData = jointMap[jointIndex];

                    if (jointData) {

                        var joint = jointData.joint;

                        if (value > joint.limits.max || value < joint.limits.min) {

                            console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').');

                        } else if (joint.static) {

                            console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' is static.');

                        } else {

                            var object = jointData.object;
                            var axis = joint.axis;
                            var transforms = jointData.transforms;

                            matrix.identity();

                            // each update, we have to apply all transforms in the correct order

                            for (var i = 0; i < transforms.length; i++) {

                                var transform = transforms[i];

                                // if there is a connection of the transform node with a joint, apply the joint value

                                if (transform.sid && transform.sid.indexOf(jointIndex) !== -1) {

                                    switch (joint.type) {
                                        case 'revolute':
                                            matrix.multiply(m0.makeRotationAxis(axis, THREE.Math.degToRad(value)));
                                            break;

                                        case 'prismatic':
                                            matrix.multiply(m0.makeTranslation(axis.x * value, axis.y * value, axis.z * value));
                                            break;

                                        default:
                                            console.warn('THREE.ColladaLoader: Unknown joint type: ' + joint.type);
                                            break;
                                    }

                                } else {

                                    switch (transform.type) {
                                        case 'matrix':
                                            matrix.multiply(transform.obj);
                                            break;

                                        case 'translate':
                                            matrix.multiply(m0.makeTranslation(transform.obj.x, transform.obj.y, transform.obj.z));
                                            break;

                                        case 'scale':
                                            matrix.scale(transform.obj);
                                            break;

                                        case 'rotate':
                                            matrix.multiply(m0.makeRotationAxis(transform.obj, transform.angle));
                                            break;
                                    }

                                }

                            }

                            object.matrix.copy(matrix);
                            object.matrix.decompose(object.position, object.quaternion, object.scale);

                            jointMap[jointIndex].position = value;

                        }

                    } else {

                        console.log('THREE.ColladaLoader: ' + jointIndex + ' does not exist.');

                    }

                }

            };

        }

        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.Math.degToRad(array[3]);
                        transforms.push({
                            sid: child.getAttribute('sid'),
                            type: child.nodeName,
                            obj: vector,
                            angle: angle
                        });
                        break;
                }

            }

            return transforms;

        }

        // nodes

        function prepareNodes(xml) {

            var elements = xml.getElementsByTagName('node');

            // ensure all node elements have id attributes

            for (var i = 0; i < elements.length; i++) {

                var element = elements[i];

                if (element.hasAttribute('id') === false) {

                    element.setAttribute('id', generateId());

                }

            }

        }

        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.Math.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);
                }

            }

            library.nodes[data.id] = data;

            return data;

        }

        function parseNodeInstance(xml) {

            var data = {
                id: parseId(xml.getAttribute('url')),
                materials: {},
                skeletons: []
            };

            for (var i = 0; i < xml.childNodes.length; i++) {

                var child = xml.childNodes[i];

                switch (child.nodeName) {
                    case 'bind_material':
                        var instances = child.getElementsByTagName('instance_material');

                        for (var j = 0; j < instances.length; j++) {

                            var instance = instances[j];
                            var symbol = instance.getAttribute('symbol');
                            var target = instance.getAttribute('target');

                            data.materials[symbol] = parseId(target);

                        }

                        break;

                    case 'skeleton':
                        data.skeletons.push(parseId(child.textContent));
                        break;

                    default:
                        break;
                }

            }

            return data;

        }

        function buildSkeleton(skeletons, joints) {

            var boneData = [];
            var sortedBoneData = [];

            var i, j, data;

            // 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++) {

                var skeleton = skeletons[i];
                var root = getNode(skeleton);

                // setup bone data for a single bone hierarchy

                buildBoneHierarchy(root, joints, boneData);

            }

            // sort bone data (the order is defined in the corresponding controller)

            for (i = 0; i < joints.length; i++) {

                for (j = 0; j < boneData.length; j++) {

                    data = boneData[j];

                    if (data.bone.name === joints[i].name) {

                        sortedBoneData[i] = data;
                        data.processed = true;
                        break;

                    }

                }

            }

            // add unprocessed bone data at the end of the list

            for (i = 0; i < boneData.length; i++) {

                data = boneData[i];

                if (data.processed === false) {

                    sortedBoneData.push(data);
                    data.processed = true;

                }

            }

            // setup arrays for skeleton creation

            var bones = [];
            var boneInverses = [];

            for (i = 0; i < sortedBoneData.length; i++) {

                data = sortedBoneData[i];

                bones.push(data.bone);
                boneInverses.push(data.boneInverse);

            }

            return new THREE.Skeleton(bones, boneInverses);

        }

        function buildBoneHierarchy(root, joints, boneData) {

            // setup bone data from visual scene

            root.traverse(function (object) {

                if (object.isBone === true) {

                    var boneInverse;

                    // retrieve the boneInverse from the controller data

                    for (var i = 0; i < joints.length; i++) {

                        var joint = joints[i];

                        if (joint.name === object.name) {

                            boneInverse = joint.boneInverse;
                            break;

                        }

                    }

                    if (boneInverse === undefined) {

                        // 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.

                        boneInverse = new THREE.Matrix4();

                    }

                    boneData.push({bone: object, boneInverse: boneInverse, processed: false});

                }

            });

        }

        function buildNode(data) {

            var objects = [];

            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;

            // nodes

            for (var i = 0, l = nodes.length; i < l; i++) {

                objects.push(getNode(nodes[i]));

            }

            // instance cameras

            for (var i = 0, l = instanceCameras.length; i < l; i++) {

                objects.push(getCamera(instanceCameras[i]).clone());

            }

            // instance controllers

            for (var i = 0, l = instanceControllers.length; i < l; i++) {

                var instance = instanceControllers[i];
                var controller = getController(instance.id);
                var geometries = getGeometry(controller.id);
                var newObjects = buildObjects(geometries, instance.materials);

                var skeletons = instance.skeletons;
                var joints = controller.skin.joints;

                var skeleton = buildSkeleton(skeletons, joints);

                for (var j = 0, jl = newObjects.length; j < jl; j++) {

                    var object = newObjects[j];

                    if (object.isSkinnedMesh) {

                        object.bind(skeleton, controller.skin.bindMatrix);
                        object.normalizeSkinWeights();

                    }

                    objects.push(object);

                }

            }

            // instance lights

            for (var i = 0, l = instanceLights.length; i < l; i++) {

                objects.push(getLight(instanceLights[i]).clone());

            }

            // instance geometries

            for (var i = 0, l = instanceGeometries.length; i < l; i++) {

                var instance = instanceGeometries[i];

                // a single geometry instance in collada can lead to multiple object3Ds.
                // this is the case when primitives are combined like triangles and lines

                var geometries = getGeometry(instance.id);
                var newObjects = buildObjects(geometries, instance.materials);

                for (var j = 0, jl = newObjects.length; j < jl; j++) {

                    objects.push(newObjects[j]);

                }

            }

            // instance nodes

            for (var i = 0, l = instanceNodes.length; i < l; i++) {

                objects.push(getNode(instanceNodes[i]).clone());

            }

            var object;

            if (nodes.length === 0 && objects.length === 1) {

                object = objects[0];

            } else {

                object = ( type === 'JOINT' ) ? new THREE.Bone() : new THREE.Group();

                for (var i = 0; i < objects.length; i++) {

                    object.add(objects[i]);

                }

            }

            object.name = ( type === 'JOINT' ) ? data.sid : data.name;
            object.matrix.copy(matrix);
            object.matrix.decompose(object.position, object.quaternion, object.scale);

            return object;

        }

        function resolveMaterialBinding(keys, instanceMaterials) {

            var materials = [];

            for (var i = 0, l = keys.length; i < l; i++) {

                var id = instanceMaterials[keys[i]];
                materials.push(getMaterial(id));

            }

            return materials;

        }

        function buildObjects(geometries, instanceMaterials) {

            var objects = [];

            for (var type in geometries) {

                var geometry = geometries[type];

                var materials = resolveMaterialBinding(geometry.materialKeys, instanceMaterials);

                // handle case if no materials are defined

                if (materials.length === 0) {

                    if (type === 'lines' || type === 'linestrips') {

                        materials.push(new THREE.LineBasicMaterial());

                    } else {

                        materials.push(new THREE.MeshPhongMaterial());

                    }

                }

                // regard skinning

                var skinning = ( geometry.data.attributes.skinIndex !== undefined );

                if (skinning) {

                    for (var i = 0, l = materials.length; i < l; i++) {

                        materials[i].skinning = true;

                    }

                }

                // choose between a single or multi materials (material array)

                var material = ( materials.length === 1 ) ? materials[0] : materials;

                // now create a specific 3D object

                var object;

                switch (type) {
                    case 'lines':
                        object = new THREE.LineSegments(geometry.data, material);
                        break;

                    case 'linestrips':
                        object = new THREE.Line(geometry.data, material);
                        break;

                    case 'triangles':
                    case 'polylist':
                        if (skinning) {

                            object = new THREE.SkinnedMesh(geometry.data, material);

                        } else {

                            object = new THREE.Mesh(geometry.data, material);

                        }
                        break;
                }

                objects.push(object);

            }

            return objects;

        }

        function getNode(id) {

            return getBuild(library.nodes[id], buildNode);

        }

        // visual scenes

        function parseVisualScene(xml) {

            var data = {
                name: xml.getAttribute('name'),
                children: []
            };

            prepareNodes(xml);

            var elements = getElementsByTagName(xml, 'node');

            for (var i = 0; i < elements.length; i++) {

                data.children.push(parseNode(elements[i]));

            }

            library.visualScenes[xml.getAttribute('id')] = data;

        }

        function buildVisualScene(data) {

            var group = new THREE.Group();
            group.name = data.name;

            var children = data.children;

            for (var i = 0; i < children.length; i++) {

                var child = children[i];

                if (child.id === null) {

                    group.add(buildNode(child));

                } else {

                    // if there is an ID, let's try to get the finished build (e.g. joints are already build)

                    group.add(getNode(child.id));

                }

            }

            return group;

        }

        function getVisualScene(id) {

            return getBuild(library.visualScenes[id], buildVisualScene);

        }

        // scenes

        function parseScene(xml) {

            var instance = getElementsByTagName(xml, 'instance_visual_scene')[0];
            return getVisualScene(parseId(instance.getAttribute('url')));

        }

        function setupAnimations() {

            var clips = library.clips;

            if (isEmpty(clips) === true) {

                if (isEmpty(library.animations) === false) {

                    // if there are animations but no clips, we create a default clip for playback

                    var tracks = [];

                    for (var id in library.animations) {

                        var animationTracks = getAnimation(id);

                        for (var i = 0, l = animationTracks.length; i < l; i++) {

                            tracks.push(animationTracks[i]);

                        }

                    }

                    animations.push(new THREE.AnimationClip('default', -1, tracks));

                }

            } else {

                for (var id in clips) {

                    animations.push(getAnimationClip(id));

                }

            }

        }

        console.time('THREE.ColladaLoader');

        if (text.length === 0) {

            return {scene: new THREE.Scene()};

        }

        console.time('THREE.ColladaLoader: DOMParser');

        var xml = new DOMParser().parseFromString(text, 'application/xml');

        console.timeEnd('THREE.ColladaLoader: DOMParser');

        var collada = getElementsByTagName(xml, 'COLLADA')[0];

        // metadata

        var version = collada.getAttribute('version');
        console.log('THREE.ColladaLoader: File version', version);

        var asset = parseAsset(getElementsByTagName(collada, 'asset')[0]);
        var textureLoader = new THREE.TextureLoader(this.manager);
        textureLoader.setPath(path).setCrossOrigin(this.crossOrigin);

        //

        var animations = [];
        var kinematics = {};
        var count = 0;

        //

        var library = {
            animations: {},
            clips: {},
            controllers: {},
            images: {},
            effects: {},
            materials: {},
            cameras: {},
            lights: {},
            geometries: {},
            nodes: {},
            visualScenes: {},
            kinematicsModels: {},
            kinematicsScenes: {}
        };

        console.time('THREE.ColladaLoader: Parse');

        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, 'scene', 'instance_kinematics_scene', parseKinematicsScene);

        console.timeEnd('THREE.ColladaLoader: Parse');

        console.time('THREE.ColladaLoader: Build');

        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);

        console.timeEnd('THREE.ColladaLoader: Build');

        setupAnimations();
        setupKinematics();

        var scene = parseScene(getElementsByTagName(collada, 'scene')[0]);

        if (asset.upAxis === 'Z_UP') {

            scene.rotation.x = -Math.PI / 2;

        }

        scene.scale.multiplyScalar(asset.unit);

        console.timeEnd('THREE.ColladaLoader');

        return {
            animations: animations,
            kinematics: kinematics,
            library: library,
            scene: scene
        };

    }

};

module.exports = exports = ColladaLoader;