/**
 * @author Tim Knip / http://www.floorplanner.com/ / tim at floorplanner.com
 * @author Tony Parisi / http://www.tonyparisi.com/
 */

IV.THREE.ColladaLoader = function () {

    var COLLADA = null;
    var scene = null;
    var visualScene;
    var kinematicsModel;

    var readyCallbackFunc = null;

    var sources = {};
    var images = {};
    var animations = {};
    var controllers = {};
    var geometries = {};
    var materials = {};
    var effects = {};
    var cameras = {};
    var lights = {};

    var animData;
    var kinematics;
    var visualScenes;
    var kinematicsModels;
    var baseUrl;
    var morphs;
    var skins;

    var flip_uv = true;
    var preferredShading = IV.THREE.SmoothShading;

    var options = {
        // Force Geometry to always be centered at the local origin of the
        // containing Mesh.
        centerGeometry: false,

        // Axis conversion is done for geometries, animations, and controllers.
        // If we ever pull cameras or lights out of the COLLADA file, they'll
        // need extra work.
        convertUpAxis: false,

        subdivideFaces: true,

        upAxis: 'Y',

        // For reflective or refractive materials we'll use this cubemap
        defaultEnvMap: null

    };

    var colladaUnit = 1.0;
    var colladaUp = 'Y';
    var upConversion = null;

    function load ( url, readyCallback, progressCallback, failCallback ) {

        var length = 0;

        if ( document.implementation && document.implementation.createDocument ) {

            var request = new XMLHttpRequest();

            request.onreadystatechange = function() {

                if ( request.readyState === 4 ) {

                    if ( request.status === 0 || request.status === 200 ) {

                        if ( request.response ) {

                            readyCallbackFunc = readyCallback;
                            parse( request.response, undefined, url );

                        } else {

                            if ( failCallback ) {

                                failCallback( { type: 'error', url: url } );

                            } else {

                                console.error( "ColladaLoader: Empty or non-existing file (" + url + ")" );

                            }

                        }

                    }else{

                        if( failCallback ){

                            failCallback( { type: 'error', url: url } );

                        }else{

                            console.error( 'ColladaLoader: Couldn\'t load "' + url + '" (' + request.status + ')' );

                        }

                    }

                } else if ( request.readyState === 3 ) {

                    if ( progressCallback ) {

                        if ( length === 0 ) {

                            length = request.getResponseHeader( "Content-Length" );

                        }

                        progressCallback( { total: length, loaded: request.responseText.length } );

                    }

                }

            };

            request.open( "GET", url, true );
            request.send( null );

        } else {

            alert( "Don't know how to parse XML!" );

        }

    }

    function parse( text, callBack, url ) {

        COLLADA = new DOMParser().parseFromString( text, 'text/xml' );
        callBack = callBack || readyCallbackFunc;

        if ( url !== undefined ) {

            var parts = url.split( '/' );
            parts.pop();
            baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/';

        }

        parseAsset();
        setUpConversion();
        images = parseLib( "library_images image", _Image, "image" );
        materials = parseLib( "library_materials material", Material, "material" );
        effects = parseLib( "library_effects effect", Effect, "effect" );
        geometries = parseLib( "library_geometries geometry", Geometry, "geometry" );
        cameras = parseLib( "library_cameras camera", Camera, "camera" );
        lights = parseLib( "library_lights light", Light, "light" );
        controllers = parseLib( "library_controllers controller", Controller, "controller" );
        animations = parseLib( "library_animations animation", Animation, "animation" );
        visualScenes = parseLib( "library_visual_scenes visual_scene", VisualScene, "visual_scene" );
        kinematicsModels = parseLib( "library_kinematics_models kinematics_model", KinematicsModel, "kinematics_model" );

        morphs = [];
        skins = [];

        visualScene = parseScene();
        scene = new IV.THREE.Group();

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

            scene.add( createSceneGraph( visualScene.nodes[ i ] ) );

        }

        // unit conversion
        scene.scale.multiplyScalar( colladaUnit );

        createAnimations();

        kinematicsModel = parseKinematicsModel();
        createKinematics();

        var result = {

            scene: scene,
            morphs: morphs,
            skins: skins,
            animations: animData,
            kinematics: kinematics,
            dae: {
                images: images,
                materials: materials,
                cameras: cameras,
                lights: lights,
                effects: effects,
                geometries: geometries,
                controllers: controllers,
                animations: animations,
                visualScenes: visualScenes,
                visualScene: visualScene,
                scene: visualScene,
                kinematicsModels: kinematicsModels,
                kinematicsModel: kinematicsModel
            }

        };

        if ( callBack ) {

            callBack( result );

        }

        return result;

    }

    function setPreferredShading ( shading ) {

        preferredShading = shading;

    }

    function parseAsset () {

        var elements = COLLADA.querySelectorAll('asset');

        var element = elements[0];

        if ( element && element.childNodes ) {

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

                var child = element.childNodes[ i ];

                switch ( child.nodeName ) {

                    case 'unit':

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

                        if ( meter ) {

                            colladaUnit = parseFloat( meter );

                        }

                        break;

                    case 'up_axis':

                        colladaUp = child.textContent.charAt(0);
                        break;

                }

            }

        }

    }

    function parseLib ( q, classSpec, prefix ) {

        var elements = COLLADA.querySelectorAll(q);

        var lib = {};

        var i = 0;

        var elementsLength = elements.length;

        for ( var j = 0; j < elementsLength; j ++ ) {

            var element = elements[j];
            var daeElement = ( new classSpec() ).parse( element );

            if ( !daeElement.id || daeElement.id.length === 0 ) daeElement.id = prefix + ( i ++ );
            lib[ daeElement.id ] = daeElement;

        }

        return lib;

    }

    function parseScene() {

        var sceneElement = COLLADA.querySelectorAll('scene instance_visual_scene')[0];

        if ( sceneElement ) {

            var url = sceneElement.getAttribute( 'url' ).replace( /^#/, '' );
            return visualScenes[ url.length > 0 ? url : 'visual_scene0' ];

        } else {

            return null;

        }

    }

    function parseKinematicsModel() {

        var kinematicsModelElement = COLLADA.querySelectorAll('instance_kinematics_model')[0];

        if ( kinematicsModelElement ) {

            var url = kinematicsModelElement.getAttribute( 'url' ).replace(/^#/, '');
            return kinematicsModels[ url.length > 0 ? url : 'kinematics_model0' ];

        } else {

            return null;

        }

    }

    function createAnimations() {

        animData = [];

        // fill in the keys
        recurseHierarchy( scene );

    }

    function recurseHierarchy( node ) {

        var n = visualScene.getChildById( node.colladaId, true ),
            newData = null;

        if ( n && n.keys ) {

            newData = {
                fps: 60,
                hierarchy: [ {
                    node: n,
                    keys: n.keys,
                    sids: n.sids
                } ],
                node: node,
                name: 'animation_' + node.name,
                length: 0
            };

            animData.push(newData);

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

                newData.length = Math.max( newData.length, n.keys[i].time );

            }

        } else {

            newData = {
                hierarchy: [ {
                    keys: [],
                    sids: []
                } ]
            }

        }

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

            var d = recurseHierarchy( node.children[i] );

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

                newData.hierarchy.push( {
                    keys: [],
                    sids: []
                } );

            }

        }

        return newData;

    }

    function calcAnimationBounds () {

        var start = 1000000;
        var end = -start;
        var frames = 0;
        var ID;
        for ( var id in animations ) {

            var animation = animations[ id ];
            ID = ID || animation.id;
            for ( var i = 0; i < animation.sampler.length; i ++ ) {

                var sampler = animation.sampler[ i ];

                sampler.create();

                start = Math.min( start, sampler.startTime );
                end = Math.max( end, sampler.endTime );
                frames = Math.max( frames, sampler.input.length );

            }

        }

        return { start:start, end:end, frames:frames,ID:ID };

    }

    function createMorph ( geometry, ctrl ) {

        var morphCtrl = ctrl instanceof InstanceController ? controllers[ ctrl.url ] : ctrl;

        if ( !morphCtrl || !morphCtrl.morph ) {

            console.log("could not find morph controller!");
            return;

        }

        var morph = morphCtrl.morph;

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

            var target_id = morph.targets[ i ];
            var daeGeometry = geometries[ target_id ];

            if ( !daeGeometry.mesh ||
                !daeGeometry.mesh.primitives ||
                !daeGeometry.mesh.primitives.length ) {
                continue;
            }

            var target = daeGeometry.mesh.primitives[ 0 ].geometry;

            if ( target.vertices.length === geometry.vertices.length ) {

                geometry.morphTargets.push( { name: "target_1", vertices: target.vertices } );

            }

        }

        geometry.morphTargets.push( { name: "target_Z", vertices: geometry.vertices } );

    }

    function createSkin ( geometry, ctrl, applyBindShape ) {

        var skinCtrl = controllers[ ctrl.url ];

        if ( !skinCtrl || !skinCtrl.skin ) {

            console.log( "could not find skin controller!" );
            return;

        }

        if ( !ctrl.skeleton || !ctrl.skeleton.length ) {

            console.log( "could not find the skeleton for the skin!" );
            return;

        }

        var skin = skinCtrl.skin;
        var skeleton = visualScene.getChildById( ctrl.skeleton[ 0 ] );
        var hierarchy = [];

        applyBindShape = applyBindShape !== undefined ? applyBindShape : true;

        var bones = [];
        geometry.skinWeights = [];
        geometry.skinIndices = [];

        //createBones( geometry.bones, skin, hierarchy, skeleton, null, -1 );
        //createWeights( skin, geometry.bones, geometry.skinIndices, geometry.skinWeights );

        /*
		geometry.animation = {
			name: 'take_001',
			fps: 30,
			length: 2,
			JIT: true,
			hierarchy: hierarchy
		};
		*/

        if ( applyBindShape ) {

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

                geometry.vertices[ i ].applyMatrix4( skin.bindShapeMatrix );

            }

        }

    }

    function setupSkeleton ( node, bones, frame, parent ) {

        node.world = node.world || new IV.THREE.Matrix4();
        node.localworld = node.localworld || new IV.THREE.Matrix4();
        node.world.copy( node.matrix );
        node.localworld.copy( node.matrix );

        if ( node.channels && node.channels.length ) {

            var channel = node.channels[ 0 ];
            var m = channel.sampler.output[ frame ];

            if ( m instanceof IV.THREE.Matrix4 ) {

                node.world.copy( m );
                node.localworld.copy(m);
                if (frame === 0)
                    node.matrix.copy(m);
            }

        }

        if ( parent ) {

            node.world.multiplyMatrices( parent, node.world );

        }

        bones.push( node );

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

            setupSkeleton( node.nodes[ i ], bones, frame, node.world );

        }

    }

    function setupSkinningMatrices ( bones, skin ) {

        // FIXME: this is dumb...

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

            var bone = bones[ i ];
            var found = -1;

            if ( bone.type != 'JOINT' ) continue;

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

                if ( bone.sid === skin.joints[ j ] ) {

                    found = j;
                    break;

                }

            }

            if ( found >= 0 ) {

                var inv = skin.invBindMatrices[ found ];

                bone.invBindMatrix = inv;
                bone.skinningMatrix = new IV.THREE.Matrix4();
                bone.skinningMatrix.multiplyMatrices(bone.world, inv); // (IBMi * JMi)
                bone.animatrix = new IV.THREE.Matrix4();

                bone.animatrix.copy(bone.localworld);
                bone.weights = [];

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

                    for (var k = 0; k < skin.weights[ j ].length; k ++ ) {

                        var w = skin.weights[ j ][ k ];

                        if ( w.joint === found ) {

                            bone.weights.push( w );

                        }

                    }

                }

            } else {

                console.warn( "ColladaLoader: Could not find joint '" + bone.sid + "'." );

                bone.skinningMatrix = new IV.THREE.Matrix4();
                bone.weights = [];

            }
        }

    }

    //Walk the Collada tree and flatten the bones into a list, extract the position, quat and scale from the matrix
    function flattenSkeleton(skeleton) {

        var list = [];
        var walk = function(parentid, node, list) {

            var bone = {};
            bone.name = node.sid;
            bone.parent = parentid;
            bone.matrix = node.matrix;
            var data = [ new IV.THREE.Vector3(),new IV.THREE.Quaternion(),new IV.THREE.Vector3() ];
            bone.matrix.decompose(data[0], data[1], data[2]);

            bone.pos = [ data[0].x,data[0].y,data[0].z ];

            bone.scl = [ data[2].x,data[2].y,data[2].z ];
            bone.rotq = [ data[1].x,data[1].y,data[1].z,data[1].w ];
            list.push(bone);

            for (var i in node.nodes) {

                walk(node.sid, node.nodes[i], list);

            }

        };

        walk(-1, skeleton, list);
        return list;

    }

    //Move the vertices into the pose that is proper for the start of the animation
    function skinToBindPose(geometry,skeleton,skinController) {

        var bones = [];
        setupSkeleton( skeleton, bones, -1 );
        setupSkinningMatrices( bones, skinController.skin );
        var v = new IV.THREE.Vector3();
        var skinned = [];

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

            skinned.push(new IV.THREE.Vector3());

        }

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

            if ( bones[ i ].type != 'JOINT' ) continue;

            for ( var j = 0; j < bones[ i ].weights.length; j ++ ) {

                var w = bones[ i ].weights[ j ];
                var vidx = w.index;
                var weight = w.weight;

                var o = geometry.vertices[vidx];
                var s = skinned[vidx];

                v.x = o.x;
                v.y = o.y;
                v.z = o.z;

                v.applyMatrix4( bones[i].skinningMatrix );

                s.x += (v.x * weight);
                s.y += (v.y * weight);
                s.z += (v.z * weight);
            }

        }

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

            geometry.vertices[i] = skinned[i];

        }

    }

    function applySkin ( geometry, instanceCtrl, frame ) {

        var skinController = controllers[ instanceCtrl.url ];

        frame = frame !== undefined ? frame : 40;

        if ( !skinController || !skinController.skin ) {

            console.log( 'ColladaLoader: Could not find skin controller.' );
            return;

        }

        if ( !instanceCtrl.skeleton || !instanceCtrl.skeleton.length ) {

            console.log( 'ColladaLoader: Could not find the skeleton for the skin. ' );
            return;

        }

        var animationBounds = calcAnimationBounds();
        var skeleton = visualScene.getChildById( instanceCtrl.skeleton[0], true ) || visualScene.getChildBySid( instanceCtrl.skeleton[0], true );

        //flatten the skeleton into a list of bones
        var bonelist = flattenSkeleton(skeleton);
        var joints = skinController.skin.joints;

        //sort that list so that the order reflects the order in the joint list
        var sortedbones = [];
        for (var i = 0; i < joints.length; i ++) {

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

                if (bonelist[j].name === joints[i]) {

                    sortedbones[i] = bonelist[j];

                }

            }

        }

        //hook up the parents by index instead of name
        for (var i = 0; i < sortedbones.length; i ++) {

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

                if (sortedbones[i].parent === sortedbones[j].name) {

                    sortedbones[i].parent = j;

                }

            }

        }


        var i, j, w, vidx, weight;
        var v = new IV.THREE.Vector3(), o, s;

        // move vertices to bind shape
        for ( i = 0; i < geometry.vertices.length; i ++ ) {
            geometry.vertices[i].applyMatrix4( skinController.skin.bindShapeMatrix );
        }

        var skinIndices = [];
        var skinWeights = [];
        var weights = skinController.skin.weights;

        // hook up the skin weights
        // TODO - this might be a good place to choose greatest 4 weights
        for ( var i =0; i < weights.length; i ++ ) {

            var indicies = new IV.THREE.Vector4(weights[i][0] ? weights[i][0].joint : 0,weights[i][1] ? weights[i][1].joint : 0,weights[i][2] ? weights[i][2].joint : 0,weights[i][3] ? weights[i][3].joint : 0);
            var weight = new IV.THREE.Vector4(weights[i][0] ? weights[i][0].weight : 0,weights[i][1] ? weights[i][1].weight : 0,weights[i][2] ? weights[i][2].weight : 0,weights[i][3] ? weights[i][3].weight : 0);

            skinIndices.push(indicies);
            skinWeights.push(weight);

        }

        geometry.skinIndices = skinIndices;
        geometry.skinWeights = skinWeights;
        geometry.bones = sortedbones;
        // process animation, or simply pose the rig if no animation

        //create an animation for the animated bones
        //NOTE: this has no effect when using morphtargets
        var animationdata = { "name":animationBounds.ID,"fps":30,"length":animationBounds.frames / 30,"hierarchy":[] };

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

            animationdata.hierarchy.push({ parent:sortedbones[j].parent, name:sortedbones[j].name, keys:[] });

        }

        console.log( 'ColladaLoader:', animationBounds.ID + ' has ' + sortedbones.length + ' bones.' );



        skinToBindPose(geometry, skeleton, skinController);


        for ( frame = 0; frame < animationBounds.frames; frame ++ ) {

            var bones = [];
            var skinned = [];
            // process the frame and setup the rig with a fresh
            // transform, possibly from the bone's animation channel(s)

            setupSkeleton( skeleton, bones, frame );
            setupSkinningMatrices( bones, skinController.skin );

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

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

                    if (animationdata.hierarchy[j].name === bones[i].sid) {

                        var key = {};
                        key.time = (frame / 30);
                        key.matrix = bones[i].animatrix;

                        if (frame === 0)
                            bones[i].matrix = key.matrix;

                        var data = [ new IV.THREE.Vector3(),new IV.THREE.Quaternion(),new IV.THREE.Vector3() ];
                        key.matrix.decompose(data[0], data[1], data[2]);

                        key.pos = [ data[0].x,data[0].y,data[0].z ];

                        key.scl = [ data[2].x,data[2].y,data[2].z ];
                        key.rot = data[1];

                        animationdata.hierarchy[j].keys.push(key);

                    }

                }

            }

            geometry.animation = animationdata;

        }

    }

    function createKinematics() {

        if ( kinematicsModel && kinematicsModel.joints.length === 0 ) {
            kinematics = undefined;
            return;
        }

        var jointMap = {};

        var _addToMap = function( jointIndex, parentVisualElement ) {

            var parentVisualElementId = parentVisualElement.getAttribute( 'id' );
            var colladaNode = visualScene.getChildById( parentVisualElementId, true );
            var joint = kinematicsModel.joints[ jointIndex ];

            scene.traverse(function( node ) {

                if ( node.colladaId == parentVisualElementId ) {

                    jointMap[ jointIndex ] = {
                        node: node,
                        transforms: colladaNode.transforms,
                        joint: joint,
                        position: joint.zeroPosition
                    };

                }

            });

        };

        kinematics = {

            joints: kinematicsModel && kinematicsModel.joints,

            getJointValue: function( jointIndex ) {

                var jointData = jointMap[ jointIndex ];

                if ( jointData ) {

                    return jointData.position;

                } else {

                    console.log( 'getJointValue: 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.log( 'setJointValue: joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ')' );

                    } else if ( joint.static ) {

                        console.log( 'setJointValue: joint ' + jointIndex + ' is static' );

                    } else {

                        var threejsNode = jointData.node;
                        var axis = joint.axis;
                        var transforms = jointData.transforms;

                        var matrix = new IV.THREE.Matrix4();

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

                            var transform = transforms[ i ];

                            // kinda ghetto joint detection
                            if ( transform.sid && transform.sid.indexOf( 'joint' + jointIndex ) !== -1 ) {

                                // apply actual joint value here
                                switch ( joint.type ) {

                                    case 'revolute':

                                        matrix.multiply( m1.makeRotationAxis( axis, IV.THREE.Math.degToRad(value) ) );
                                        break;

                                    case 'prismatic':

                                        matrix.multiply( m1.makeTranslation(axis.x * value, axis.y * value, axis.z * value ) );
                                        break;

                                    default:

                                        console.warn( 'setJointValue: unknown joint type: ' + joint.type );
                                        break;

                                }

                            } else {

                                var m1 = new IV.THREE.Matrix4();

                                switch ( transform.type ) {

                                    case 'matrix':

                                        matrix.multiply( transform.obj );

                                        break;

                                    case 'translate':

                                        matrix.multiply( m1.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) );

                                        break;

                                    case 'rotate':

                                        matrix.multiply( m1.makeRotationAxis( transform.obj, transform.angle ) );

                                        break;

                                }
                            }
                        }

                        // apply the matrix to the threejs node
                        var elementsFloat32Arr = matrix.elements;
                        var elements = Array.prototype.slice.call( elementsFloat32Arr );

                        var elementsRowMajor = [
                            elements[ 0 ],
                            elements[ 4 ],
                            elements[ 8 ],
                            elements[ 12 ],
                            elements[ 1 ],
                            elements[ 5 ],
                            elements[ 9 ],
                            elements[ 13 ],
                            elements[ 2 ],
                            elements[ 6 ],
                            elements[ 10 ],
                            elements[ 14 ],
                            elements[ 3 ],
                            elements[ 7 ],
                            elements[ 11 ],
                            elements[ 15 ]
                        ];

                        threejsNode.matrix.set.apply( threejsNode.matrix, elementsRowMajor );
                        threejsNode.matrix.decompose( threejsNode.position, threejsNode.quaternion, threejsNode.scale );
                    }

                } else {

                    console.log( 'setJointValue: joint ' + jointIndex + ' doesn\'t exist' );

                }

            }

        };

        var element = COLLADA.querySelector('scene instance_kinematics_scene');

        if ( element ) {

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

                var child = element.childNodes[ i ];

                if ( child.nodeType != 1 ) continue;

                switch ( child.nodeName ) {

                    case 'bind_joint_axis':

                        var visualTarget = child.getAttribute( 'target' ).split( '/' ).pop();
                        var axis = child.querySelector('axis param').textContent;
                        var jointIndex = parseInt( axis.split( 'joint' ).pop().split( '.' )[0] );
                        var visualTargetElement = COLLADA.querySelector( '[sid="' + visualTarget + '"]' );

                        if ( visualTargetElement ) {
                            var parentVisualElement = visualTargetElement.parentElement;
                            _addToMap(jointIndex, parentVisualElement);
                        }

                        break;

                    default:

                        break;

                }

            }
        }

    }

    function createSceneGraph ( node, parent ) {

        var obj = new IV.THREE.Object3D();
        var skinned = false;
        var skinController;
        var morphController;
        var i, j;

        // FIXME: controllers

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

            var controller = controllers[ node.controllers[ i ].url ];

            switch ( controller.type ) {

                case 'skin':

                    if ( geometries[ controller.skin.source ] ) {

                        var inst_geom = new InstanceGeometry();

                        inst_geom.url = controller.skin.source;
                        inst_geom.instance_material = node.controllers[ i ].instance_material;

                        node.geometries.push( inst_geom );
                        skinned = true;
                        skinController = node.controllers[ i ];

                    } else if ( controllers[ controller.skin.source ] ) {

                        // urgh: controller can be chained
                        // handle the most basic case...

                        var second = controllers[ controller.skin.source ];
                        morphController = second;
                        //	skinController = node.controllers[i];

                        if ( second.morph && geometries[ second.morph.source ] ) {

                            var inst_geom = new InstanceGeometry();

                            inst_geom.url = second.morph.source;
                            inst_geom.instance_material = node.controllers[ i ].instance_material;

                            node.geometries.push( inst_geom );

                        }

                    }

                    break;

                case 'morph':

                    if ( geometries[ controller.morph.source ] ) {

                        var inst_geom = new InstanceGeometry();

                        inst_geom.url = controller.morph.source;
                        inst_geom.instance_material = node.controllers[ i ].instance_material;

                        node.geometries.push( inst_geom );
                        morphController = node.controllers[ i ];

                    }

                    console.log( 'ColladaLoader: Morph-controller partially supported.' );

                default:
                    break;

            }

        }

        // geometries

        var double_sided_materials = {};

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

            var instance_geometry = node.geometries[i];
            var instance_materials = instance_geometry.instance_material;
            var geometry = geometries[ instance_geometry.url ];
            var used_materials = {};
            var used_materials_array = [];
            var num_materials = 0;
            var first_material;

            if ( geometry ) {

                if ( !geometry.mesh || !geometry.mesh.primitives )
                    continue;

                if ( obj.name.length === 0 ) {

                    obj.name = geometry.id;

                }

                // collect used fx for this geometry-instance

                if ( instance_materials ) {

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

                        var instance_material = instance_materials[ j ];
                        var mat = materials[ instance_material.target ];
                        var effect_id = mat.instance_effect.url;
                        var shader = effects[ effect_id ].shader;
                        var material3js = shader.material;

                        if ( geometry.doubleSided ) {

                            if ( !( instance_material.symbol in double_sided_materials ) ) {

                                var _copied_material = material3js.clone();
                                _copied_material.side = IV.THREE.DoubleSide;
                                double_sided_materials[ instance_material.symbol ] = _copied_material;

                            }

                            material3js = double_sided_materials[ instance_material.symbol ];

                        }

                        material3js.opacity = !material3js.opacity ? 1 : material3js.opacity;
                        used_materials[ instance_material.symbol ] = num_materials;
                        used_materials_array.push( material3js );
                        first_material = material3js;
                        first_material.name = mat.name === null || mat.name === '' ? mat.id : mat.name;
                        num_materials ++;

                    }

                }

                var mesh;
                var material = first_material || new IV.THREE.MeshLambertMaterial( { color: 0xdddddd, side: geometry.doubleSided ? IV.THREE.DoubleSide : IV.THREE.FrontSide } );
                var geom = geometry.mesh.geometry3js;

                if ( num_materials > 1 ) {

                    material = new IV.THREE.MultiMaterial( used_materials_array );

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

                        var face = geom.faces[ j ];
                        face.materialIndex = used_materials[ face.daeMaterial ]

                    }

                }

                if ( skinController !== undefined ) {


                    applySkin( geom, skinController );

                    if ( geom.morphTargets.length > 0 ) {

                        material.morphTargets = true;
                        material.skinning = false;

                    } else {

                        material.morphTargets = false;
                        material.skinning = true;

                    }


                    mesh = new IV.THREE.SkinnedMesh( geom, material, false );


                    //mesh.skeleton = skinController.skeleton;
                    //mesh.skinController = controllers[ skinController.url ];
                    //mesh.skinInstanceController = skinController;
                    mesh.name = 'skin_' + skins.length;



                    //mesh.animationHandle.setKey(0);
                    skins.push( mesh );

                } else if ( morphController !== undefined ) {

                    createMorph( geom, morphController );

                    material.morphTargets = true;

                    mesh = new IV.THREE.Mesh( geom, material );
                    mesh.name = 'morph_' + morphs.length;

                    morphs.push( mesh );

                } else {

                    if ( geom.isLineStrip === true ) {

                        mesh = new IV.THREE.Line( geom );

                    } else {

                        mesh = new IV.THREE.Mesh( geom, material );

                    }

                }

                obj.add(mesh);

            }

        }

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

            var instance_camera = node.cameras[i];
            var cparams = cameras[instance_camera.url];

            var cam = new IV.THREE.PerspectiveCamera(cparams.yfov, parseFloat(cparams.aspect_ratio),
                parseFloat(cparams.znear), parseFloat(cparams.zfar));

            obj.add(cam);
        }

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

            var light = null;
            var instance_light = node.lights[i];
            var lparams = lights[instance_light.url];

            if ( lparams && lparams.technique ) {

                var color = lparams.color.getHex();
                var intensity = lparams.intensity;
                var distance = lparams.distance;
                var angle = lparams.falloff_angle;

                switch ( lparams.technique ) {

                    case 'directional':

                        light = new IV.THREE.DirectionalLight( color, intensity, distance );
                        light.position.set(0, 0, 1);
                        break;

                    case 'point':

                        light = new IV.THREE.PointLight( color, intensity, distance );
                        break;

                    case 'spot':

                        light = new IV.THREE.SpotLight( color, intensity, distance, angle );
                        light.position.set(0, 0, 1);
                        break;

                    case 'ambient':

                        light = new IV.THREE.AmbientLight( color );
                        break;

                }

            }

            if (light) {
                obj.add(light);
            }
        }

        obj.name = node.name || node.id || "";
        obj.colladaId = node.id || "";
        obj.layer = node.layer || "";
        obj.matrix = node.matrix;
        obj.matrix.decompose( obj.position, obj.quaternion, obj.scale );

        if ( options.centerGeometry && obj.geometry ) {

            var delta = obj.geometry.center();
            delta.multiply( obj.scale );
            delta.applyQuaternion( obj.quaternion );

            obj.position.sub( delta );

        }

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

            obj.add( createSceneGraph( node.nodes[i], node ) );

        }

        return obj;

    }

    function getJointId( skin, id ) {

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

            if ( skin.joints[ i ] === id ) {

                return i;

            }

        }

    }

    function getLibraryNode( id ) {

        var nodes = COLLADA.querySelectorAll('library_nodes node');

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

            var attObj = nodes[i].attributes.getNamedItem('id');

            if ( attObj && attObj.value === id ) {

                return nodes[i];

            }

        }

        return undefined;

    }

    function getChannelsForNode ( node ) {

        var channels = [];
        var startTime = 1000000;
        var endTime = -1000000;

        for ( var id in animations ) {

            var animation = animations[id];

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

                var channel = animation.channel[i];
                var sampler = animation.sampler[i];
                var id = channel.target.split('/')[0];

                if ( id == node.id ) {

                    sampler.create();
                    channel.sampler = sampler;
                    startTime = Math.min(startTime, sampler.startTime);
                    endTime = Math.max(endTime, sampler.endTime);
                    channels.push(channel);

                }

            }

        }

        if ( channels.length ) {

            node.startTime = startTime;
            node.endTime = endTime;

        }

        return channels;

    }

    function calcFrameDuration( node ) {

        var minT = 10000000;

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

            var sampler = node.channels[i].sampler;

            for ( var j = 0; j < sampler.input.length - 1; j ++ ) {

                var t0 = sampler.input[ j ];
                var t1 = sampler.input[ j + 1 ];
                minT = Math.min( minT, t1 - t0 );

            }
        }

        return minT;

    }

    function calcMatrixAt( node, t ) {

        var animated = {};

        var i, j;

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

            var channel = node.channels[ i ];
            animated[ channel.sid ] = channel;

        }

        var matrix = new IV.THREE.Matrix4();

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

            var transform = node.transforms[ i ];
            var channel = animated[ transform.sid ];

            if ( channel !== undefined ) {

                var sampler = channel.sampler;
                var value;

                for ( j = 0; j < sampler.input.length - 1; j ++ ) {

                    if ( sampler.input[ j + 1 ] > t ) {

                        value = sampler.output[ j ];
                        //console.log(value.flatten)
                        break;

                    }

                }

                if ( value !== undefined ) {

                    if ( value instanceof IV.THREE.Matrix4 ) {

                        matrix.multiplyMatrices( matrix, value );

                    } else {

                        // FIXME: handle other types

                        matrix.multiplyMatrices( matrix, transform.matrix );

                    }

                } else {

                    matrix.multiplyMatrices( matrix, transform.matrix );

                }

            } else {

                matrix.multiplyMatrices( matrix, transform.matrix );

            }

        }

        return matrix;

    }

    function bakeAnimations ( node ) {

        if ( node.channels && node.channels.length ) {

            var keys = [],
                sids = [];

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

                var channel = node.channels[i],
                    fullSid = channel.fullSid,
                    sampler = channel.sampler,
                    input = sampler.input,
                    transform = node.getTransformBySid( channel.sid ),
                    member;

                if ( channel.arrIndices ) {

                    member = [];

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

                        member[ j ] = getConvertedIndex( channel.arrIndices[ j ] );

                    }

                } else {

                    member = getConvertedMember( channel.member );

                }

                if ( transform ) {

                    if ( sids.indexOf( fullSid ) === -1 ) {

                        sids.push( fullSid );

                    }

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

                        var time = input[j],
                            data = sampler.getData( transform.type, j, member ),
                            key = findKey( keys, time );

                        if ( !key ) {

                            key = new Key( time );
                            var timeNdx = findTimeNdx( keys, time );
                            keys.splice( timeNdx === -1 ? keys.length : timeNdx, 0, key );

                        }

                        key.addTarget( fullSid, transform, member, data );

                    }

                } else {

                    console.log( 'Could not find transform "' + channel.sid + '" in node ' + node.id );

                }

            }

            // post process
            for ( var i = 0; i < sids.length; i ++ ) {

                var sid = sids[ i ];

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

                    var key = keys[ j ];

                    if ( !key.hasTarget( sid ) ) {

                        interpolateKeys( keys, key, j, sid );

                    }

                }

            }

            node.keys = keys;
            node.sids = sids;

        }

    }

    function findKey ( keys, time) {

        var retVal = null;

        for ( var i = 0, il = keys.length; i < il && retVal === null; i ++ ) {

            var key = keys[i];

            if ( key.time === time ) {

                retVal = key;

            } else if ( key.time > time ) {

                break;

            }

        }

        return retVal;

    }

    function findTimeNdx ( keys, time) {

        var ndx = -1;

        for ( var i = 0, il = keys.length; i < il && ndx === -1; i ++ ) {

            var key = keys[i];

            if ( key.time >= time ) {

                ndx = i;

            }

        }

        return ndx;

    }

    function interpolateKeys ( keys, key, ndx, fullSid ) {

        var prevKey = getPrevKeyWith( keys, fullSid, ndx ? ndx - 1 : 0 ),
            nextKey = getNextKeyWith( keys, fullSid, ndx + 1 );

        if ( prevKey && nextKey ) {

            var scale = (key.time - prevKey.time) / (nextKey.time - prevKey.time),
                prevTarget = prevKey.getTarget( fullSid ),
                nextData = nextKey.getTarget( fullSid ).data,
                prevData = prevTarget.data,
                data;

            if ( prevTarget.type === 'matrix' ) {

                data = prevData;

            } else if ( prevData.length ) {

                data = [];

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

                    data[ i ] = prevData[ i ] + ( nextData[ i ] - prevData[ i ] ) * scale;

                }

            } else {

                data = prevData + ( nextData - prevData ) * scale;

            }

            key.addTarget( fullSid, prevTarget.transform, prevTarget.member, data );

        }

    }

    // Get next key with given sid

    function getNextKeyWith( keys, fullSid, ndx ) {

        for ( ; ndx < keys.length; ndx ++ ) {

            var key = keys[ ndx ];

            if ( key.hasTarget( fullSid ) ) {

                return key;

            }

        }

        return null;

    }

    // Get previous key with given sid

    function getPrevKeyWith( keys, fullSid, ndx ) {

        ndx = ndx >= 0 ? ndx : ndx + keys.length;

        for ( ; ndx >= 0; ndx -- ) {

            var key = keys[ ndx ];

            if ( key.hasTarget( fullSid ) ) {

                return key;

            }

        }

        return null;

    }

    function _Image() {

        this.id = "";
        this.init_from = "";

    }

    _Image.prototype.parse = function(element) {

        this.id = element.getAttribute('id');

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

            var child = element.childNodes[ i ];

            if ( child.nodeName === 'init_from' ) {

                this.init_from = child.textContent;

            }

        }

        return this;

    };

    function Controller() {

        this.id = "";
        this.name = "";
        this.type = "";
        this.skin = null;
        this.morph = null;

    }

    Controller.prototype.parse = function( element ) {

        this.id = element.getAttribute('id');
        this.name = element.getAttribute('name');
        this.type = "none";

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

            var child = element.childNodes[ i ];

            switch ( child.nodeName ) {

                case 'skin':

                    this.skin = (new Skin()).parse(child);
                    this.type = child.nodeName;
                    break;

                case 'morph':

                    this.morph = (new Morph()).parse(child);
                    this.type = child.nodeName;
                    break;

                default:
                    break;

            }
        }

        return this;

    };

    function Morph() {

        this.method = null;
        this.source = null;
        this.targets = null;
        this.weights = null;

    }

    Morph.prototype.parse = function( element ) {

        var sources = {};
        var inputs = [];
        var i;

        this.method = element.getAttribute( 'method' );
        this.source = element.getAttribute( 'source' ).replace( /^#/, '' );

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'source':

                    var source = ( new Source() ).parse( child );
                    sources[ source.id ] = source;
                    break;

                case 'targets':

                    inputs = this.parseInputs( child );
                    break;

                default:

                    console.log( child.nodeName );
                    break;

            }

        }

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

            var input = inputs[ i ];
            var source = sources[ input.source ];

            switch ( input.semantic ) {

                case 'MORPH_TARGET':

                    this.targets = source.read();
                    break;

                case 'MORPH_WEIGHT':

                    this.weights = source.read();
                    break;

                default:
                    break;

            }
        }

        return this;

    };

    Morph.prototype.parseInputs = function(element) {

        var inputs = [];

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

            var child = element.childNodes[i];
            if ( child.nodeType != 1) continue;

            switch ( child.nodeName ) {

                case 'input':

                    inputs.push( (new Input()).parse(child) );
                    break;

                default:
                    break;
            }
        }

        return inputs;

    };

    function Skin() {

        this.source = "";
        this.bindShapeMatrix = null;
        this.invBindMatrices = [];
        this.joints = [];
        this.weights = [];

    }

    Skin.prototype.parse = function( element ) {

        var sources = {};
        var joints, weights;

        this.source = element.getAttribute( 'source' ).replace( /^#/, '' );
        this.invBindMatrices = [];
        this.joints = [];
        this.weights = [];

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

            var child = element.childNodes[i];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'bind_shape_matrix':

                    var f = _floats(child.textContent);
                    this.bindShapeMatrix = getConvertedMat4( f );
                    break;

                case 'source':

                    var src = new Source().parse(child);
                    sources[ src.id ] = src;
                    break;

                case 'joints':

                    joints = child;
                    break;

                case 'vertex_weights':

                    weights = child;
                    break;

                default:

                    console.log( child.nodeName );
                    break;

            }
        }

        this.parseJoints( joints, sources );
        this.parseWeights( weights, sources );

        return this;

    };

    Skin.prototype.parseJoints = function ( element, sources ) {

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'input':

                    var input = ( new Input() ).parse( child );
                    var source = sources[ input.source ];

                    if ( input.semantic === 'JOINT' ) {

                        this.joints = source.read();

                    } else if ( input.semantic === 'INV_BIND_MATRIX' ) {

                        this.invBindMatrices = source.read();

                    }

                    break;

                default:
                    break;
            }

        }

    };

    Skin.prototype.parseWeights = function ( element, sources ) {

        var v, vcount, inputs = [];

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'input':

                    inputs.push( ( new Input() ).parse( child ) );
                    break;

                case 'v':

                    v = _ints( child.textContent );
                    break;

                case 'vcount':

                    vcount = _ints( child.textContent );
                    break;

                default:
                    break;

            }

        }

        var index = 0;

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

            var numBones = vcount[i];
            var vertex_weights = [];

            for ( var j = 0; j < numBones; j ++ ) {

                var influence = {};

                for ( var k = 0; k < inputs.length; k ++ ) {

                    var input = inputs[ k ];
                    var value = v[ index + input.offset ];

                    switch ( input.semantic ) {

                        case 'JOINT':

                            influence.joint = value;//this.joints[value];
                            break;

                        case 'WEIGHT':

                            influence.weight = sources[ input.source ].data[ value ];
                            break;

                        default:
                            break;

                    }

                }

                vertex_weights.push( influence );
                index += inputs.length;
            }

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

                vertex_weights[ j ].index = i;

            }

            this.weights.push( vertex_weights );

        }

    };

    function VisualScene () {

        this.id = "";
        this.name = "";
        this.nodes = [];
        this.scene = new IV.THREE.Group();

    }

    VisualScene.prototype.getChildById = function( id, recursive ) {

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

            var node = this.nodes[ i ].getChildById( id, recursive );

            if ( node ) {

                return node;

            }

        }

        return null;

    };

    VisualScene.prototype.getChildBySid = function( sid, recursive ) {

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

            var node = this.nodes[ i ].getChildBySid( sid, recursive );

            if ( node ) {

                return node;

            }

        }

        return null;

    };

    VisualScene.prototype.parse = function( element ) {

        this.id = element.getAttribute( 'id' );
        this.name = element.getAttribute( 'name' );
        this.nodes = [];

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'node':

                    this.nodes.push( ( new Node() ).parse( child ) );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    function Node() {

        this.id = "";
        this.name = "";
        this.sid = "";
        this.nodes = [];
        this.controllers = [];
        this.transforms = [];
        this.geometries = [];
        this.channels = [];
        this.matrix = new IV.THREE.Matrix4();

    }

    Node.prototype.getChannelForTransform = function( transformSid ) {

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

            var channel = this.channels[i];
            var parts = channel.target.split('/');
            var id = parts.shift();
            var sid = parts.shift();
            var dotSyntax = (sid.indexOf(".") >= 0);
            var arrSyntax = (sid.indexOf("(") >= 0);
            var arrIndices;
            var member;

            if ( dotSyntax ) {

                parts = sid.split(".");
                sid = parts.shift();
                member = parts.shift();

            } else if ( arrSyntax ) {

                arrIndices = sid.split("(");
                sid = arrIndices.shift();

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

                    arrIndices[ j ] = parseInt( arrIndices[ j ].replace( /\)/, '' ) );

                }

            }

            if ( sid === transformSid ) {

                channel.info = { sid: sid, dotSyntax: dotSyntax, arrSyntax: arrSyntax, arrIndices: arrIndices };
                return channel;

            }

        }

        return null;

    };

    Node.prototype.getChildById = function ( id, recursive ) {

        if ( this.id === id ) {

            return this;

        }

        if ( recursive ) {

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

                var n = this.nodes[ i ].getChildById( id, recursive );

                if ( n ) {

                    return n;

                }

            }

        }

        return null;

    };

    Node.prototype.getChildBySid = function ( sid, recursive ) {

        if ( this.sid === sid ) {

            return this;

        }

        if ( recursive ) {

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

                var n = this.nodes[ i ].getChildBySid( sid, recursive );

                if ( n ) {

                    return n;

                }

            }
        }

        return null;

    };

    Node.prototype.getTransformBySid = function ( sid ) {

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

            if ( this.transforms[ i ].sid === sid ) return this.transforms[ i ];

        }

        return null;

    };

    Node.prototype.parse = function( element ) {

        var url;

        this.id = element.getAttribute('id');
        this.sid = element.getAttribute('sid');
        this.name = element.getAttribute('name');
        this.type = element.getAttribute('type');
        this.layer = element.getAttribute('layer');

        this.type = this.type === 'JOINT' ? this.type : 'NODE';

        this.nodes = [];
        this.transforms = [];
        this.geometries = [];
        this.cameras = [];
        this.lights = [];
        this.controllers = [];
        this.matrix = new IV.THREE.Matrix4();

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'node':

                    this.nodes.push( ( new Node() ).parse( child ) );
                    break;

                case 'instance_camera':

                    this.cameras.push( ( new InstanceCamera() ).parse( child ) );
                    break;

                case 'instance_controller':

                    this.controllers.push( ( new InstanceController() ).parse( child ) );
                    break;

                case 'instance_geometry':

                    this.geometries.push( ( new InstanceGeometry() ).parse( child ) );
                    break;

                case 'instance_light':

                    this.lights.push( ( new InstanceLight() ).parse( child ) );
                    break;

                case 'instance_node':

                    url = child.getAttribute( 'url' ).replace( /^#/, '' );
                    var iNode = getLibraryNode( url );

                    if ( iNode ) {

                        this.nodes.push( ( new Node() ).parse( iNode )) ;

                    }

                    break;

                case 'rotate':
                case 'translate':
                case 'scale':
                case 'matrix':
                case 'lookat':
                case 'skew':

                    this.transforms.push( ( new Transform() ).parse( child ) );
                    break;

                case 'extra':
                    break;

                default:

                    console.log( child.nodeName );
                    break;

            }

        }

        this.channels = getChannelsForNode( this );
        bakeAnimations( this );

        this.updateMatrix();

        return this;

    };

    Node.prototype.updateMatrix = function () {

        this.matrix.identity();

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

            this.transforms[ i ].apply( this.matrix );

        }

    };

    function Transform () {

        this.sid = "";
        this.type = "";
        this.data = [];
        this.obj = null;

    }

    Transform.prototype.parse = function ( element ) {

        this.sid = element.getAttribute( 'sid' );
        this.type = element.nodeName;
        this.data = _floats( element.textContent );
        this.convert();

        return this;

    };

    Transform.prototype.convert = function () {

        switch ( this.type ) {

            case 'matrix':

                this.obj = getConvertedMat4( this.data );
                break;

            case 'rotate':

                this.angle = IV.THREE.Math.degToRad( this.data[3] );

            case 'translate':

                fixCoords( this.data, -1 );
                this.obj = new IV.THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] );
                break;

            case 'scale':

                fixCoords( this.data, 1 );
                this.obj = new IV.THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] );
                break;

            default:
                console.log( 'Can not convert Transform of type ' + this.type );
                break;

        }

    };

    Transform.prototype.apply = function () {

        var m1 = new IV.THREE.Matrix4();

        return function ( matrix ) {

            switch ( this.type ) {

                case 'matrix':

                    matrix.multiply( this.obj );

                    break;

                case 'translate':

                    matrix.multiply( m1.makeTranslation( this.obj.x, this.obj.y, this.obj.z ) );

                    break;

                case 'rotate':

                    matrix.multiply( m1.makeRotationAxis( this.obj, this.angle ) );

                    break;

                case 'scale':

                    matrix.scale( this.obj );

                    break;

            }

        };

    }();

    Transform.prototype.update = function ( data, member ) {

        var members = [ 'X', 'Y', 'Z', 'ANGLE' ];

        switch ( this.type ) {

            case 'matrix':

                if ( ! member ) {

                    this.obj.copy( data );

                } else if ( member.length === 1 ) {

                    switch ( member[ 0 ] ) {

                        case 0:

                            this.obj.n11 = data[ 0 ];
                            this.obj.n21 = data[ 1 ];
                            this.obj.n31 = data[ 2 ];
                            this.obj.n41 = data[ 3 ];

                            break;

                        case 1:

                            this.obj.n12 = data[ 0 ];
                            this.obj.n22 = data[ 1 ];
                            this.obj.n32 = data[ 2 ];
                            this.obj.n42 = data[ 3 ];

                            break;

                        case 2:

                            this.obj.n13 = data[ 0 ];
                            this.obj.n23 = data[ 1 ];
                            this.obj.n33 = data[ 2 ];
                            this.obj.n43 = data[ 3 ];

                            break;

                        case 3:

                            this.obj.n14 = data[ 0 ];
                            this.obj.n24 = data[ 1 ];
                            this.obj.n34 = data[ 2 ];
                            this.obj.n44 = data[ 3 ];

                            break;

                    }

                } else if ( member.length === 2 ) {

                    var propName = 'n' + ( member[ 0 ] + 1 ) + ( member[ 1 ] + 1 );
                    this.obj[ propName ] = data;

                } else {

                    console.log('Incorrect addressing of matrix in transform.');

                }

                break;

            case 'translate':
            case 'scale':

                if ( Object.prototype.toString.call( member ) === '[object Array]' ) {

                    member = members[ member[ 0 ] ];

                }

                switch ( member ) {

                    case 'X':

                        this.obj.x = data;
                        break;

                    case 'Y':

                        this.obj.y = data;
                        break;

                    case 'Z':

                        this.obj.z = data;
                        break;

                    default:

                        this.obj.x = data[ 0 ];
                        this.obj.y = data[ 1 ];
                        this.obj.z = data[ 2 ];
                        break;

                }

                break;

            case 'rotate':

                if ( Object.prototype.toString.call( member ) === '[object Array]' ) {

                    member = members[ member[ 0 ] ];

                }

                switch ( member ) {

                    case 'X':

                        this.obj.x = data;
                        break;

                    case 'Y':

                        this.obj.y = data;
                        break;

                    case 'Z':

                        this.obj.z = data;
                        break;

                    case 'ANGLE':

                        this.angle = IV.THREE.Math.degToRad( data );
                        break;

                    default:

                        this.obj.x = data[ 0 ];
                        this.obj.y = data[ 1 ];
                        this.obj.z = data[ 2 ];
                        this.angle = IV.THREE.Math.degToRad( data[ 3 ] );
                        break;

                }
                break;

        }

    };

    function InstanceController() {

        this.url = "";
        this.skeleton = [];
        this.instance_material = [];

    }

    InstanceController.prototype.parse = function ( element ) {

        this.url = element.getAttribute('url').replace(/^#/, '');
        this.skeleton = [];
        this.instance_material = [];

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

            var child = element.childNodes[ i ];
            if ( child.nodeType !== 1 ) continue;

            switch ( child.nodeName ) {

                case 'skeleton':

                    this.skeleton.push( child.textContent.replace(/^#/, '') );
                    break;

                case 'bind_material':

                    var instances = child.querySelectorAll('instance_material');

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

                        var instance = instances[j];
                        this.instance_material.push( (new InstanceMaterial()).parse(instance) );

                    }


                    break;

                case 'extra':
                    break;

                default:
                    break;

            }
        }

        return this;

    };

    function InstanceMaterial () {

        this.symbol = "";
        this.target = "";

    }

    InstanceMaterial.prototype.parse = function ( element ) {

        this.symbol = element.getAttribute('symbol');
        this.target = element.getAttribute('target').replace(/^#/, '');
        return this;

    };

    function InstanceGeometry() {

        this.url = "";
        this.instance_material = [];

    }

    InstanceGeometry.prototype.parse = function ( element ) {

        this.url = element.getAttribute('url').replace(/^#/, '');
        this.instance_material = [];

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

            var child = element.childNodes[i];
            if ( child.nodeType != 1 ) continue;

            if ( child.nodeName === 'bind_material' ) {

                var instances = child.querySelectorAll('instance_material');

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

                    var instance = instances[j];
                    this.instance_material.push( (new InstanceMaterial()).parse(instance) );

                }

                break;

            }

        }

        return this;

    };

    function Geometry() {

        this.id = "";
        this.mesh = null;

    }

    Geometry.prototype.parse = function ( element ) {

        this.id = element.getAttribute('id');

        extractDoubleSided( this, element );

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

            var child = element.childNodes[i];

            switch ( child.nodeName ) {

                case 'mesh':

                    this.mesh = (new Mesh(this)).parse(child);
                    break;

                case 'extra':

                    // console.log( child );
                    break;

                default:
                    break;
            }
        }

        return this;

    };

    function Mesh( geometry ) {

        this.geometry = geometry.id;
        this.primitives = [];
        this.vertices = null;
        this.geometry3js = null;

    }

    Mesh.prototype.parse = function ( element ) {

        this.primitives = [];

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

            var child = element.childNodes[ i ];

            switch ( child.nodeName ) {

                case 'source':

                    _source( child );
                    break;

                case 'vertices':

                    this.vertices = ( new Vertices() ).parse( child );
                    break;

                case 'linestrips':

                    this.primitives.push( ( new LineStrips().parse( child ) ) );
                    break;

                case 'triangles':

                    this.primitives.push( ( new Triangles().parse( child ) ) );
                    break;

                case 'polygons':

                    this.primitives.push( ( new Polygons().parse( child ) ) );
                    break;

                case 'polylist':

                    this.primitives.push( ( new Polylist().parse( child ) ) );
                    break;

                default:
                    break;

            }

        }

        this.geometry3js = new IV.THREE.Geometry();

        if ( this.vertices === null ) {

            // TODO (mrdoob): Study case when this is null (carrier.dae)

            return this;

        }

        var vertexData = sources[ this.vertices.input['POSITION'].source ].data;

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

            this.geometry3js.vertices.push( getConvertedVec3( vertexData, i ).clone() );

        }

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

            var primitive = this.primitives[ i ];
            primitive.setVertices( this.vertices );
            this.handlePrimitive( primitive, this.geometry3js );

        }

        if ( this.geometry3js.calcNormals ) {

            this.geometry3js.computeVertexNormals();
            delete this.geometry3js.calcNormals;

        }

        return this;

    };

    Mesh.prototype.handlePrimitive = function ( primitive, geom ) {

        if ( primitive instanceof LineStrips ) {

            // TODO: Handle indices. Maybe easier with BufferGeometry?

            geom.isLineStrip = true;
            return;

        }

        var j, k, pList = primitive.p, inputs = primitive.inputs;
        var input, index, idx32;
        var source, numParams;
        var vcIndex = 0, vcount = 3, maxOffset = 0;
        var texture_sets = [];

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

            input = inputs[ j ];

            var offset = input.offset + 1;
            maxOffset = (maxOffset < offset) ? offset : maxOffset;

            switch ( input.semantic ) {

                case 'TEXCOORD':
                    texture_sets.push( input.set );
                    break;

            }

        }

        for ( var pCount = 0; pCount < pList.length; ++ pCount ) {

            var p = pList[ pCount ], i = 0;

            while ( i < p.length ) {

                var vs = [];
                var ns = [];
                var ts = null;
                var cs = [];

                if ( primitive.vcount ) {

                    vcount = primitive.vcount.length ? primitive.vcount[ vcIndex ++ ] : primitive.vcount;

                } else {

                    vcount = p.length / maxOffset;

                }


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

                    for ( k = 0; k < inputs.length; k ++ ) {

                        input = inputs[ k ];
                        source = sources[ input.source ];

                        index = p[ i + ( j * maxOffset ) + input.offset ];
                        numParams = source.accessor.params.length;
                        idx32 = index * numParams;

                        switch ( input.semantic ) {

                            case 'VERTEX':

                                vs.push( index );

                                break;

                            case 'NORMAL':

                                ns.push( getConvertedVec3( source.data, idx32 ) );

                                break;

                            case 'TEXCOORD':

                                ts = ts || { };
                                if ( ts[ input.set ] === undefined ) ts[ input.set ] = [];
                                // invert the V
                                ts[ input.set ].push( new IV.THREE.Vector2( source.data[ idx32 ], source.data[ idx32 + 1 ] ) );

                                break;

                            case 'COLOR':

                                cs.push( new IV.THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) );

                                break;

                            default:

                                break;

                        }

                    }

                }

                if ( ns.length === 0 ) {

                    // check the vertices inputs
                    input = this.vertices.input.NORMAL;

                    if ( input ) {

                        source = sources[ input.source ];
                        numParams = source.accessor.params.length;

                        for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) {

                            ns.push( getConvertedVec3( source.data, vs[ ndx ] * numParams ) );

                        }

                    } else {

                        geom.calcNormals = true;

                    }

                }

                if ( !ts ) {

                    ts = { };
                    // check the vertices inputs
                    input = this.vertices.input.TEXCOORD;

                    if ( input ) {

                        texture_sets.push( input.set );
                        source = sources[ input.source ];
                        numParams = source.accessor.params.length;

                        for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) {

                            idx32 = vs[ ndx ] * numParams;
                            if ( ts[ input.set ] === undefined ) ts[ input.set ] = [ ];
                            // invert the V
                            ts[ input.set ].push( new IV.THREE.Vector2( source.data[ idx32 ], 1.0 - source.data[ idx32 + 1 ] ) );

                        }

                    }

                }

                if ( cs.length === 0 ) {

                    // check the vertices inputs
                    input = this.vertices.input.COLOR;

                    if ( input ) {

                        source = sources[ input.source ];
                        numParams = source.accessor.params.length;

                        for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) {

                            idx32 = vs[ ndx ] * numParams;
                            cs.push( new IV.THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) );

                        }

                    }

                }

                var face = null, faces = [], uv, uvArr;

                if ( vcount === 3 ) {

                    faces.push( new IV.THREE.Face3( vs[0], vs[1], vs[2], ns, cs.length ? cs : new IV.THREE.Color() ) );

                } else if ( vcount === 4 ) {

                    faces.push( new IV.THREE.Face3( vs[0], vs[1], vs[3], ns.length ? [ ns[0].clone(), ns[1].clone(), ns[3].clone() ] : [], cs.length ? [ cs[0], cs[1], cs[3] ] : new IV.THREE.Color() ) );

                    faces.push( new IV.THREE.Face3( vs[1], vs[2], vs[3], ns.length ? [ ns[1].clone(), ns[2].clone(), ns[3].clone() ] : [], cs.length ? [ cs[1], cs[2], cs[3] ] : new IV.THREE.Color() ) );

                } else if ( vcount > 4 && options.subdivideFaces ) {

                    var clr = cs.length ? cs : new IV.THREE.Color(),
                        vec1, vec2, vec3, v1, v2, norm;

                    // subdivide into multiple Face3s

                    for ( k = 1; k < vcount - 1; ) {

                        faces.push( new IV.THREE.Face3( vs[0], vs[k], vs[k + 1], ns.length ? [ ns[0].clone(), ns[k ++].clone(), ns[k].clone() ] : [], clr ) );

                    }

                }

                if ( faces.length ) {

                    for ( var ndx = 0, len = faces.length; ndx < len; ndx ++ ) {

                        face = faces[ndx];
                        face.daeMaterial = primitive.material;
                        geom.faces.push( face );

                        for ( k = 0; k < texture_sets.length; k ++ ) {

                            uv = ts[ texture_sets[k] ];

                            if ( vcount > 4 ) {

                                // Grab the right UVs for the vertices in this face
                                uvArr = [ uv[0], uv[ndx + 1], uv[ndx + 2] ];

                            } else if ( vcount === 4 ) {

                                if ( ndx === 0 ) {

                                    uvArr = [ uv[0], uv[1], uv[3] ];

                                } else {

                                    uvArr = [ uv[1].clone(), uv[2], uv[3].clone() ];

                                }

                            } else {

                                uvArr = [ uv[0], uv[1], uv[2] ];

                            }

                            if ( geom.faceVertexUvs[k] === undefined ) {

                                geom.faceVertexUvs[k] = [];

                            }

                            geom.faceVertexUvs[k].push( uvArr );

                        }

                    }

                } else {

                    console.log( 'dropped face with vcount ' + vcount + ' for geometry with id: ' + geom.id );

                }

                i += maxOffset * vcount;

            }

        }

    };

    function Polygons () {

        this.material = "";
        this.count = 0;
        this.inputs = [];
        this.vcount = null;
        this.p = [];
        this.geometry = new IV.THREE.Geometry();

    }

    Polygons.prototype.setVertices = function ( vertices ) {

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

            if ( this.inputs[ i ].source === vertices.id ) {

                this.inputs[ i ].source = vertices.input[ 'POSITION' ].source;

            }

        }

    };

    Polygons.prototype.parse = function ( element ) {

        this.material = element.getAttribute( 'material' );
        this.count = _attr_as_int( element, 'count', 0 );

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

            var child = element.childNodes[ i ];

            switch ( child.nodeName ) {

                case 'input':

                    this.inputs.push( ( new Input() ).parse( element.childNodes[ i ] ) );
                    break;

                case 'vcount':

                    this.vcount = _ints( child.textContent );
                    break;

                case 'p':

                    this.p.push( _ints( child.textContent ) );
                    break;

                case 'ph':

                    console.warn( 'polygon holes not yet supported!' );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    function Polylist () {

        Polygons.call( this );

        this.vcount = [];

    }

    Polylist.prototype = Object.create( Polygons.prototype );
    Polylist.prototype.constructor = Polylist;

    function LineStrips() {

        Polygons.call( this );

        this.vcount = 1;

    }

    LineStrips.prototype = Object.create( Polygons.prototype );
    LineStrips.prototype.constructor = LineStrips;

    function Triangles () {

        Polygons.call( this );

        this.vcount = 3;

    }

    Triangles.prototype = Object.create( Polygons.prototype );
    Triangles.prototype.constructor = Triangles;

    function Accessor() {

        this.source = "";
        this.count = 0;
        this.stride = 0;
        this.params = [];

    }

    Accessor.prototype.parse = function ( element ) {

        this.params = [];
        this.source = element.getAttribute( 'source' );
        this.count = _attr_as_int( element, 'count', 0 );
        this.stride = _attr_as_int( element, 'stride', 0 );

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

            var child = element.childNodes[ i ];

            if ( child.nodeName === 'param' ) {

                var param = {};
                param[ 'name' ] = child.getAttribute( 'name' );
                param[ 'type' ] = child.getAttribute( 'type' );
                this.params.push( param );

            }

        }

        return this;

    };

    function Vertices() {

        this.input = {};

    }

    Vertices.prototype.parse = function ( element ) {

        this.id = element.getAttribute('id');

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

            if ( element.childNodes[i].nodeName === 'input' ) {

                var input = ( new Input() ).parse( element.childNodes[ i ] );
                this.input[ input.semantic ] = input;

            }

        }

        return this;

    };

    function Input () {

        this.semantic = "";
        this.offset = 0;
        this.source = "";
        this.set = 0;

    }

    Input.prototype.parse = function ( element ) {

        this.semantic = element.getAttribute('semantic');
        this.source = element.getAttribute('source').replace(/^#/, '');
        this.set = _attr_as_int(element, 'set', -1);
        this.offset = _attr_as_int(element, 'offset', 0);

        if ( this.semantic === 'TEXCOORD' && this.set < 0 ) {

            this.set = 0;

        }

        return this;

    };

    function Source ( id ) {

        this.id = id;
        this.type = null;

    }

    Source.prototype.parse = function ( element ) {

        this.id = element.getAttribute( 'id' );

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

            var child = element.childNodes[i];

            switch ( child.nodeName ) {

                case 'bool_array':

                    this.data = _bools( child.textContent );
                    this.type = child.nodeName;
                    break;

                case 'float_array':

                    this.data = _floats( child.textContent );
                    this.type = child.nodeName;
                    break;

                case 'int_array':

                    this.data = _ints( child.textContent );
                    this.type = child.nodeName;
                    break;

                case 'IDREF_array':
                case 'Name_array':

                    this.data = _strings( child.textContent );
                    this.type = child.nodeName;
                    break;

                case 'technique_common':

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

                        if ( child.childNodes[ j ].nodeName === 'accessor' ) {

                            this.accessor = ( new Accessor() ).parse( child.childNodes[ j ] );
                            break;

                        }
                    }
                    break;

                default:
                    // console.log(child.nodeName);
                    break;

            }

        }

        return this;

    };

    Source.prototype.read = function () {

        var result = [];

        //for (var i = 0; i < this.accessor.params.length; i++) {

        var param = this.accessor.params[ 0 ];

        //console.log(param.name + " " + param.type);

        switch ( param.type ) {

            case 'IDREF':
            case 'Name': case 'name':
            case 'float':

                return this.data;

            case 'float4x4':

                for ( var j = 0; j < this.data.length; j += 16 ) {

                    var s = this.data.slice( j, j + 16 );
                    var m = getConvertedMat4( s );
                    result.push( m );
                }

                break;

            default:

                console.log( 'ColladaLoader: Source: Read dont know how to read ' + param.type + '.' );
                break;

        }

        //}

        return result;

    };

    function Material () {

        this.id = "";
        this.name = "";
        this.instance_effect = null;

    }

    Material.prototype.parse = function ( element ) {

        this.id = element.getAttribute( 'id' );
        this.name = element.getAttribute( 'name' );

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

            if ( element.childNodes[ i ].nodeName === 'instance_effect' ) {

                this.instance_effect = ( new InstanceEffect() ).parse( element.childNodes[ i ] );
                break;

            }

        }

        return this;

    };

    function ColorOrTexture () {

        this.color = new IV.THREE.Color();
        this.color.setRGB( Math.random(), Math.random(), Math.random() );
        this.color.a = 1.0;

        this.texture = null;
        this.texcoord = null;
        this.texOpts = null;

    }

    ColorOrTexture.prototype.isColor = function () {

        return ( this.texture === null );

    };

    ColorOrTexture.prototype.isTexture = function () {

        return ( this.texture != null );

    };

    ColorOrTexture.prototype.parse = function ( element ) {

        if (element.nodeName === 'transparent') {

            this.opaque = element.getAttribute('opaque');

        }

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'color':

                    var rgba = _floats( child.textContent );
                    this.color = new IV.THREE.Color();
                    this.color.setRGB( rgba[0], rgba[1], rgba[2] );
                    this.color.a = rgba[3];
                    break;

                case 'texture':

                    this.texture = child.getAttribute('texture');
                    this.texcoord = child.getAttribute('texcoord');
                    // Defaults from:
                    // https://collada.org/mediawiki/index.php/Maya_texture_placement_MAYA_extension
                    this.texOpts = {
                        offsetU: 0,
                        offsetV: 0,
                        repeatU: 1,
                        repeatV: 1,
                        wrapU: 1,
                        wrapV: 1
                    };
                    this.parseTexture( child );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    ColorOrTexture.prototype.parseTexture = function ( element ) {

        if ( ! element.childNodes ) return this;

        // This should be supported by Maya, 3dsMax, and MotionBuilder

        if ( element.childNodes[1] && element.childNodes[1].nodeName === 'extra' ) {

            element = element.childNodes[1];

            if ( element.childNodes[1] && element.childNodes[1].nodeName === 'technique' ) {

                element = element.childNodes[1];

            }

        }

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

            var child = element.childNodes[ i ];

            switch ( child.nodeName ) {

                case 'offsetU':
                case 'offsetV':
                case 'repeatU':
                case 'repeatV':

                    this.texOpts[ child.nodeName ] = parseFloat( child.textContent );

                    break;

                case 'wrapU':
                case 'wrapV':

                    // some dae have a value of true which becomes NaN via parseInt

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

                        this.texOpts[ child.nodeName ] = 1;

                    } else {

                        this.texOpts[ child.nodeName ] = parseInt( child.textContent );

                    }
                    break;

                default:

                    this.texOpts[ child.nodeName ] = child.textContent;

                    break;

            }

        }

        return this;

    };

    function Shader ( type, effect ) {

        this.type = type;
        this.effect = effect;
        this.material = null;

    }

    Shader.prototype.parse = function ( element ) {

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'emission':
                case 'diffuse':
                case 'specular':
                case 'transparent':

                    this[ child.nodeName ] = ( new ColorOrTexture() ).parse( child );
                    break;

                case 'bump':

                    // If 'bumptype' is 'heightfield', create a 'bump' property
                    // Else if 'bumptype' is 'normalmap', create a 'normal' property
                    // (Default to 'bump')
                    var bumpType = child.getAttribute( 'bumptype' );
                    if ( bumpType ) {
                        if ( bumpType.toLowerCase() === "heightfield" ) {
                            this[ 'bump' ] = ( new ColorOrTexture() ).parse( child );
                        } else if ( bumpType.toLowerCase() === "normalmap" ) {
                            this[ 'normal' ] = ( new ColorOrTexture() ).parse( child );
                        } else {
                            console.error( "Shader.prototype.parse: Invalid value for attribute 'bumptype' (" + bumpType + ") - valid bumptypes are 'HEIGHTFIELD' and 'NORMALMAP' - defaulting to 'HEIGHTFIELD'" );
                            this[ 'bump' ] = ( new ColorOrTexture() ).parse( child );
                        }
                    } else {
                        console.warn( "Shader.prototype.parse: Attribute 'bumptype' missing from bump node - defaulting to 'HEIGHTFIELD'" );
                        this[ 'bump' ] = ( new ColorOrTexture() ).parse( child );
                    }

                    break;

                case 'shininess':
                case 'reflectivity':
                case 'index_of_refraction':
                case 'transparency':

                    var f = child.querySelectorAll('float');

                    if ( f.length > 0 )
                        this[ child.nodeName ] = parseFloat( f[ 0 ].textContent );

                    break;

                default:
                    break;

            }

        }

        this.create();
        return this;

    };

    Shader.prototype.create = function() {

        var props = {};

        var transparent = false;

        if (this['transparency'] !== undefined && this['transparent'] !== undefined) {
            // convert transparent color RBG to average value
            var transparentColor = this['transparent'];
            var transparencyLevel = (this.transparent.color.r + this.transparent.color.g + this.transparent.color.b) / 3 * this.transparency;

            if (transparencyLevel > 0) {
                transparent = true;
                props[ 'transparent' ] = true;
                props[ 'opacity' ] = 1 - transparencyLevel;

            }

        }

        var keys = {
            'diffuse':'map',
            'ambient':'lightMap',
            'specular':'specularMap',
            'emission':'emissionMap',
            'bump':'bumpMap',
            'normal':'normalMap'
        };

        for ( var prop in this ) {

            switch ( prop ) {

                case 'ambient':
                case 'emission':
                case 'diffuse':
                case 'specular':
                case 'bump':
                case 'normal':

                    var cot = this[ prop ];

                    if ( cot instanceof ColorOrTexture ) {

                        if ( cot.isTexture() ) {

                            var samplerId = cot.texture;
                            var surfaceId = this.effect.sampler[samplerId];

                            if ( surfaceId !== undefined && surfaceId.source !== undefined ) {

                                var surface = this.effect.surface[surfaceId.source];

                                if ( surface !== undefined ) {

                                    var image = images[ surface.init_from ];

                                    if ( image ) {

                                        var url = baseUrl + image.init_from;

                                        var texture;
                                        var loader = IV.THREE.Loader.Handlers.get( url );

                                        if ( loader !== null ) {

                                            texture = loader.load( url );

                                        } else {

                                            texture = new IV.THREE.Texture();

                                            loadTextureImage( texture, url );

                                        }

                                        texture.wrapS = cot.texOpts.wrapU ? IV.THREE.RepeatWrapping : IV.THREE.ClampToEdgeWrapping;
                                        texture.wrapT = cot.texOpts.wrapV ? IV.THREE.RepeatWrapping : IV.THREE.ClampToEdgeWrapping;
                                        texture.offset.x = cot.texOpts.offsetU;
                                        texture.offset.y = cot.texOpts.offsetV;
                                        texture.repeat.x = cot.texOpts.repeatU;
                                        texture.repeat.y = cot.texOpts.repeatV;
                                        props[keys[prop]] = texture;

                                        // Texture with baked lighting?
                                        if (prop === 'emission') props['emissive'] = 0xffffff;

                                    }

                                }

                            }

                        } else if ( prop === 'diffuse' || !transparent ) {

                            if ( prop === 'emission' ) {

                                props[ 'emissive' ] = cot.color.getHex();

                            } else {

                                props[ prop ] = cot.color.getHex();

                            }

                        }

                    }

                    break;

                case 'shininess':

                    props[ prop ] = this[ prop ];
                    break;

                case 'reflectivity':

                    props[ prop ] = this[ prop ];
                    if ( props[ prop ] > 0.0 ) props['envMap'] = options.defaultEnvMap;
                    props['combine'] = IV.THREE.MixOperation;	//mix regular shading with reflective component
                    break;

                case 'index_of_refraction':

                    props[ 'refractionRatio' ] = this[ prop ]; //TODO: "index_of_refraction" becomes "refractionRatio" in shader, but I'm not sure if the two are actually comparable
                    if ( this[ prop ] !== 1.0 ) props['envMap'] = options.defaultEnvMap;
                    break;

                case 'transparency':
                    // gets figured out up top
                    break;

                default:
                    break;

            }

        }

        props[ 'shading' ] = preferredShading;
        props[ 'side' ] = this.effect.doubleSided ? IV.THREE.DoubleSide : IV.THREE.FrontSide;

        if ( props.diffuse !== undefined ) {

            props.color = props.diffuse;
            delete props.diffuse;

        }

        switch ( this.type ) {

            case 'constant':

                if (props.emissive != undefined) props.color = props.emissive;
                this.material = new IV.THREE.MeshBasicMaterial( props );
                break;

            case 'phong':
            case 'blinn':

                this.material = new IV.THREE.MeshPhongMaterial( props );
                break;

            case 'lambert':
            default:

                this.material = new IV.THREE.MeshLambertMaterial( props );
                break;

        }

        return this.material;

    };

    function Surface ( effect ) {

        this.effect = effect;
        this.init_from = null;
        this.format = null;

    }

    Surface.prototype.parse = function ( element ) {

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'init_from':

                    this.init_from = child.textContent;
                    break;

                case 'format':

                    this.format = child.textContent;
                    break;

                default:

                    console.log( "unhandled Surface prop: " + child.nodeName );
                    break;

            }

        }

        return this;

    };

    function Sampler2D ( effect ) {

        this.effect = effect;
        this.source = null;
        this.wrap_s = null;
        this.wrap_t = null;
        this.minfilter = null;
        this.magfilter = null;
        this.mipfilter = null;

    }

    Sampler2D.prototype.parse = function ( element ) {

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'source':

                    this.source = child.textContent;
                    break;

                case 'minfilter':

                    this.minfilter = child.textContent;
                    break;

                case 'magfilter':

                    this.magfilter = child.textContent;
                    break;

                case 'mipfilter':

                    this.mipfilter = child.textContent;
                    break;

                case 'wrap_s':

                    this.wrap_s = child.textContent;
                    break;

                case 'wrap_t':

                    this.wrap_t = child.textContent;
                    break;

                default:

                    console.log( "unhandled Sampler2D prop: " + child.nodeName );
                    break;

            }

        }

        return this;

    };

    function Effect () {

        this.id = "";
        this.name = "";
        this.shader = null;
        this.surface = {};
        this.sampler = {};

    }

    Effect.prototype.create = function () {

        if ( this.shader === null ) {

            return null;

        }

    };

    Effect.prototype.parse = function ( element ) {

        this.id = element.getAttribute( 'id' );
        this.name = element.getAttribute( 'name' );

        extractDoubleSided( this, element );

        this.shader = null;

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'profile_COMMON':

                    this.parseTechnique( this.parseProfileCOMMON( child ) );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    Effect.prototype.parseNewparam = function ( element ) {

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

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'surface':

                    this.surface[sid] = ( new Surface( this ) ).parse( child );
                    break;

                case 'sampler2D':

                    this.sampler[sid] = ( new Sampler2D( this ) ).parse( child );
                    break;

                case 'extra':

                    break;

                default:

                    console.log( child.nodeName );
                    break;

            }

        }

    };

    Effect.prototype.parseProfileCOMMON = function ( element ) {

        var technique;

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

            var child = element.childNodes[ i ];

            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'profile_COMMON':

                    this.parseProfileCOMMON( child );
                    break;

                case 'technique':

                    technique = child;
                    break;

                case 'newparam':

                    this.parseNewparam( child );
                    break;

                case 'image':

                    var _image = ( new _Image() ).parse( child );
                    images[ _image.id ] = _image;
                    break;

                case 'extra':
                    break;

                default:

                    console.log( child.nodeName );
                    break;

            }

        }

        return technique;

    };

    Effect.prototype.parseTechnique = function ( element ) {

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

            var child = element.childNodes[i];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'constant':
                case 'lambert':
                case 'blinn':
                case 'phong':

                    this.shader = ( new Shader( child.nodeName, this ) ).parse( child );
                    break;
                case 'extra':
                    this.parseExtra(child);
                    break;
                default:
                    break;

            }

        }

    };

    Effect.prototype.parseExtra = function ( element ) {

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

            var child = element.childNodes[i];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'technique':
                    this.parseExtraTechnique( child );
                    break;
                default:
                    break;

            }

        }

    };

    Effect.prototype.parseExtraTechnique = function ( element ) {

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

            var child = element.childNodes[i];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'bump':
                    this.shader.parse( element );
                    break;
                default:
                    break;

            }

        }

    };

    function InstanceEffect () {

        this.url = "";

    }

    InstanceEffect.prototype.parse = function ( element ) {

        this.url = element.getAttribute( 'url' ).replace( /^#/, '' );
        return this;

    };

    function Animation() {

        this.id = "";
        this.name = "";
        this.source = {};
        this.sampler = [];
        this.channel = [];

    }

    Animation.prototype.parse = function ( element ) {

        this.id = element.getAttribute( 'id' );
        this.name = element.getAttribute( 'name' );
        this.source = {};

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

            var child = element.childNodes[ i ];

            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'animation':

                    var anim = ( new Animation() ).parse( child );

                    for ( var src in anim.source ) {

                        this.source[ src ] = anim.source[ src ];

                    }

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

                        this.channel.push( anim.channel[ j ] );
                        this.sampler.push( anim.sampler[ j ] );

                    }

                    break;

                case 'source':

                    var src = ( new Source() ).parse( child );
                    this.source[ src.id ] = src;
                    break;

                case 'sampler':

                    this.sampler.push( ( new Sampler( this ) ).parse( child ) );
                    break;

                case 'channel':

                    this.channel.push( ( new Channel( this ) ).parse( child ) );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    function Channel( animation ) {

        this.animation = animation;
        this.source = "";
        this.target = "";
        this.fullSid = null;
        this.sid = null;
        this.dotSyntax = null;
        this.arrSyntax = null;
        this.arrIndices = null;
        this.member = null;

    }

    Channel.prototype.parse = function ( element ) {

        this.source = element.getAttribute( 'source' ).replace( /^#/, '' );
        this.target = element.getAttribute( 'target' );

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

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

        var dotSyntax = ( sid.indexOf(".") >= 0 );
        var arrSyntax = ( sid.indexOf("(") >= 0 );

        if ( dotSyntax ) {

            parts = sid.split(".");
            this.sid = parts.shift();
            this.member = parts.shift();

        } else if ( arrSyntax ) {

            var arrIndices = sid.split("(");
            this.sid = arrIndices.shift();

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

                arrIndices[j] = parseInt( arrIndices[j].replace(/\)/, '') );

            }

            this.arrIndices = arrIndices;

        } else {

            this.sid = sid;

        }

        this.fullSid = sid;
        this.dotSyntax = dotSyntax;
        this.arrSyntax = arrSyntax;

        return this;

    };

    function Sampler ( animation ) {

        this.id = "";
        this.animation = animation;
        this.inputs = [];
        this.input = null;
        this.output = null;
        this.strideOut = null;
        this.interpolation = null;
        this.startTime = null;
        this.endTime = null;
        this.duration = 0;

    }

    Sampler.prototype.parse = function ( element ) {

        this.id = element.getAttribute( 'id' );
        this.inputs = [];

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'input':

                    this.inputs.push( (new Input()).parse( child ) );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    Sampler.prototype.create = function () {

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

            var input = this.inputs[ i ];
            var source = this.animation.source[ input.source ];

            switch ( input.semantic ) {

                case 'INPUT':

                    this.input = source.read();
                    break;

                case 'OUTPUT':

                    this.output = source.read();
                    this.strideOut = source.accessor.stride;
                    break;

                case 'INTERPOLATION':

                    this.interpolation = source.read();
                    break;

                case 'IN_TANGENT':

                    break;

                case 'OUT_TANGENT':

                    break;

                default:

                    console.log(input.semantic);
                    break;

            }

        }

        this.startTime = 0;
        this.endTime = 0;
        this.duration = 0;

        if ( this.input.length ) {

            this.startTime = 100000000;
            this.endTime = -100000000;

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

                this.startTime = Math.min( this.startTime, this.input[ i ] );
                this.endTime = Math.max( this.endTime, this.input[ i ] );

            }

            this.duration = this.endTime - this.startTime;

        }

    };

    Sampler.prototype.getData = function ( type, ndx, member ) {

        var data;

        if ( type === 'matrix' && this.strideOut === 16 ) {

            data = this.output[ ndx ];

        } else if ( this.strideOut > 1 ) {

            data = [];
            ndx *= this.strideOut;

            for ( var i = 0; i < this.strideOut; ++ i ) {

                data[ i ] = this.output[ ndx + i ];

            }

            if ( this.strideOut === 3 ) {

                switch ( type ) {

                    case 'rotate':
                    case 'translate':

                        fixCoords( data, -1 );
                        break;

                    case 'scale':

                        fixCoords( data, 1 );
                        break;

                }

            } else if ( this.strideOut === 4 && type === 'matrix' ) {

                fixCoords( data, -1 );

            }

        } else {

            data = this.output[ ndx ];

            if ( member && type === 'translate' ) {
                data = getConvertedTranslation( member, data );
            }

        }

        return data;

    };

    function Key ( time ) {

        this.targets = [];
        this.time = time;

    }

    Key.prototype.addTarget = function ( fullSid, transform, member, data ) {

        this.targets.push( {
            sid: fullSid,
            member: member,
            transform: transform,
            data: data
        } );

    };

    Key.prototype.apply = function ( opt_sid ) {

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

            var target = this.targets[ i ];

            if ( !opt_sid || target.sid === opt_sid ) {

                target.transform.update( target.data, target.member );

            }

        }

    };

    Key.prototype.getTarget = function ( fullSid ) {

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

            if ( this.targets[ i ].sid === fullSid ) {

                return this.targets[ i ];

            }

        }

        return null;

    };

    Key.prototype.hasTarget = function ( fullSid ) {

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

            if ( this.targets[ i ].sid === fullSid ) {

                return true;

            }

        }

        return false;

    };

    // TODO: Currently only doing linear interpolation. Should support full COLLADA spec.
    Key.prototype.interpolate = function ( nextKey, time ) {

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

            var target = this.targets[ i ],
                nextTarget = nextKey.getTarget( target.sid ),
                data;

            if ( target.transform.type !== 'matrix' && nextTarget ) {

                var scale = ( time - this.time ) / ( nextKey.time - this.time ),
                    nextData = nextTarget.data,
                    prevData = target.data;

                if ( scale < 0 ) scale = 0;
                if ( scale > 1 ) scale = 1;

                if ( prevData.length ) {

                    data = [];

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

                        data[ j ] = prevData[ j ] + ( nextData[ j ] - prevData[ j ] ) * scale;

                    }

                } else {

                    data = prevData + ( nextData - prevData ) * scale;

                }

            } else {

                data = target.data;

            }

            target.transform.update( data, target.member );

        }

    };

    // Camera
    function Camera() {

        this.id = "";
        this.name = "";
        this.technique = "";

    }

    Camera.prototype.parse = function ( element ) {

        this.id = element.getAttribute( 'id' );
        this.name = element.getAttribute( 'name' );

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'optics':

                    this.parseOptics( child );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    Camera.prototype.parseOptics = function ( element ) {

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

            if ( element.childNodes[ i ].nodeName === 'technique_common' ) {

                var technique = element.childNodes[ i ];

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

                    this.technique = technique.childNodes[ j ].nodeName;

                    if ( this.technique === 'perspective' ) {

                        var perspective = technique.childNodes[ j ];

                        for ( var k = 0; k < perspective.childNodes.length; k ++ ) {

                            var param = perspective.childNodes[ k ];

                            switch ( param.nodeName ) {

                                case 'yfov':
                                    this.yfov = param.textContent;
                                    break;
                                case 'xfov':
                                    this.xfov = param.textContent;
                                    break;
                                case 'znear':
                                    this.znear = param.textContent;
                                    break;
                                case 'zfar':
                                    this.zfar = param.textContent;
                                    break;
                                case 'aspect_ratio':
                                    this.aspect_ratio = param.textContent;
                                    break;

                            }

                        }

                    } else if ( this.technique === 'orthographic' ) {

                        var orthographic = technique.childNodes[ j ];

                        for ( var k = 0; k < orthographic.childNodes.length; k ++ ) {

                            var param = orthographic.childNodes[ k ];

                            switch ( param.nodeName ) {

                                case 'xmag':
                                    this.xmag = param.textContent;
                                    break;
                                case 'ymag':
                                    this.ymag = param.textContent;
                                    break;
                                case 'znear':
                                    this.znear = param.textContent;
                                    break;
                                case 'zfar':
                                    this.zfar = param.textContent;
                                    break;
                                case 'aspect_ratio':
                                    this.aspect_ratio = param.textContent;
                                    break;

                            }

                        }

                    }

                }

            }

        }

        return this;

    };

    function InstanceCamera() {

        this.url = "";

    }

    InstanceCamera.prototype.parse = function ( element ) {

        this.url = element.getAttribute('url').replace(/^#/, '');

        return this;

    };

    // Light

    function Light() {

        this.id = "";
        this.name = "";
        this.technique = "";

    }

    Light.prototype.parse = function ( element ) {

        this.id = element.getAttribute( 'id' );
        this.name = element.getAttribute( 'name' );

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'technique_common':

                    this.parseCommon( child );
                    break;

                case 'technique':

                    this.parseTechnique( child );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    Light.prototype.parseCommon = function ( element ) {

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

            switch ( element.childNodes[ i ].nodeName ) {

                case 'directional':
                case 'point':
                case 'spot':
                case 'ambient':

                    this.technique = element.childNodes[ i ].nodeName;

                    var light = element.childNodes[ i ];

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

                        var child = light.childNodes[j];

                        switch ( child.nodeName ) {

                            case 'color':

                                var rgba = _floats( child.textContent );
                                this.color = new IV.THREE.Color(0);
                                this.color.setRGB( rgba[0], rgba[1], rgba[2] );
                                this.color.a = rgba[3];
                                break;

                            case 'falloff_angle':

                                this.falloff_angle = parseFloat( child.textContent );
                                break;

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

                    }

            }

        }

        return this;

    };

    Light.prototype.parseTechnique = function ( element ) {

        this.profile = element.getAttribute( 'profile' );

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

            var child = element.childNodes[ i ];

            switch ( child.nodeName ) {

                case 'intensity':

                    this.intensity = parseFloat(child.textContent);
                    break;

            }

        }

        return this;

    };

    function InstanceLight() {

        this.url = "";

    }

    InstanceLight.prototype.parse = function ( element ) {

        this.url = element.getAttribute('url').replace(/^#/, '');

        return this;

    };

    function KinematicsModel( ) {

        this.id = '';
        this.name = '';
        this.joints = [];
        this.links = [];

    }

    KinematicsModel.prototype.parse = function( element ) {

        this.id = element.getAttribute('id');
        this.name = element.getAttribute('name');
        this.joints = [];
        this.links = [];

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'technique_common':

                    this.parseCommon(child);
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    KinematicsModel.prototype.parseCommon = function( element ) {

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( element.childNodes[ i ].nodeName ) {

                case 'joint':
                    this.joints.push( (new Joint()).parse(child) );
                    break;

                case 'link':
                    this.links.push( (new Link()).parse(child) );
                    break;

                default:
                    break;

            }

        }

        return this;

    };

    function Joint( ) {

        this.sid = '';
        this.name = '';
        this.axis = new IV.THREE.Vector3();
        this.limits = {
            min: 0,
            max: 0
        };
        this.type = '';
        this.static = false;
        this.zeroPosition = 0.0;
        this.middlePosition = 0.0;

    }

    Joint.prototype.parse = function( element ) {

        this.sid = element.getAttribute('sid');
        this.name = element.getAttribute('name');
        this.axis = new IV.THREE.Vector3();
        this.limits = {
            min: 0,
            max: 0
        };
        this.type = '';
        this.static = false;
        this.zeroPosition = 0.0;
        this.middlePosition = 0.0;

        var axisElement = element.querySelector('axis');
        var _axis = _floats(axisElement.textContent);
        this.axis = getConvertedVec3(_axis, 0);

        var min = element.querySelector('limits min') ? parseFloat(element.querySelector('limits min').textContent) : -360;
        var max = element.querySelector('limits max') ? parseFloat(element.querySelector('limits max').textContent) : 360;

        this.limits = {
            min: min,
            max: max
        };

        var jointTypes = [ 'prismatic', 'revolute' ];
        for (var i = 0; i < jointTypes.length; i ++ ) {

            var type = jointTypes[ i ];

            var jointElement = element.querySelector(type);

            if ( jointElement ) {

                this.type = type;

            }

        }

        // if the min is equal to or somehow greater than the max, consider the joint static
        if ( this.limits.min >= this.limits.max ) {

            this.static = true;

        }

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

    };

    function Link( ) {

        this.sid = '';
        this.name = '';
        this.transforms = [];
        this.attachments = [];

    }

    Link.prototype.parse = function( element ) {

        this.sid = element.getAttribute('sid');
        this.name = element.getAttribute('name');
        this.transforms = [];
        this.attachments = [];

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'attachment_full':
                    this.attachments.push( (new Attachment()).parse(child) );
                    break;

                case 'rotate':
                case 'translate':
                case 'matrix':

                    this.transforms.push( (new Transform()).parse(child) );
                    break;

                default:

                    break;

            }

        }

        return this;

    };

    function Attachment( ) {

        this.joint = '';
        this.transforms = [];
        this.links = [];

    }

    Attachment.prototype.parse = function( element ) {

        this.joint = element.getAttribute('joint').split('/').pop();
        this.links = [];

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

            var child = element.childNodes[ i ];
            if ( child.nodeType != 1 ) continue;

            switch ( child.nodeName ) {

                case 'link':
                    this.links.push( (new Link()).parse(child) );
                    break;

                case 'rotate':
                case 'translate':
                case 'matrix':

                    this.transforms.push( (new Transform()).parse(child) );
                    break;

                default:

                    break;

            }

        }

        return this;

    };

    function _source( element ) {

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

        if ( sources[ id ] != undefined ) {

            return sources[ id ];

        }

        sources[ id ] = ( new Source(id )).parse( element );
        return sources[ id ];

    }

    function _nsResolver( nsPrefix ) {

        if ( nsPrefix === "dae" ) {

            return "http://www.collada.org/2005/11/COLLADASchema";

        }

        return null;

    }

    function _bools( str ) {

        var raw = _strings( str );
        var data = [];

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

            data.push( (raw[i] === 'true' || raw[i] === '1') ? true : false );

        }

        return data;

    }

    function _floats( str ) {

        var raw = _strings(str);
        var data = [];

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

            data.push( parseFloat( raw[ i ] ) );

        }

        return data;

    }

    function _ints( str ) {

        var raw = _strings( str );
        var data = [];

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

            data.push( parseInt( raw[ i ], 10 ) );

        }

        return data;

    }

    function _strings( str ) {

        return ( str.length > 0 ) ? _trimString( str ).split( /\s+/ ) : [];

    }

    function _trimString( str ) {

        return str.replace( /^\s+/, "" ).replace( /\s+$/, "" );

    }

    function _attr_as_float( element, name, defaultValue ) {

        if ( element.hasAttribute( name ) ) {

            return parseFloat( element.getAttribute( name ) );

        } else {

            return defaultValue;

        }

    }

    function _attr_as_int( element, name, defaultValue ) {

        if ( element.hasAttribute( name ) ) {

            return parseInt( element.getAttribute( name ), 10) ;

        } else {

            return defaultValue;

        }

    }

    function _attr_as_string( element, name, defaultValue ) {

        if ( element.hasAttribute( name ) ) {

            return element.getAttribute( name );

        } else {

            return defaultValue;

        }

    }

    function _format_float( f, num ) {

        if ( f === undefined ) {

            var s = '0.';

            while ( s.length < num + 2 ) {

                s += '0';

            }

            return s;

        }

        num = num || 2;

        var parts = f.toString().split( '.' );
        parts[ 1 ] = parts.length > 1 ? parts[ 1 ].substr( 0, num ) : "0";

        while ( parts[ 1 ].length < num ) {

            parts[ 1 ] += '0';

        }

        return parts.join( '.' );

    }

    function loadTextureImage ( texture, url ) {

        var loader = new IV.THREE.ImageLoader();

        loader.load( url, function ( image ) {

            texture.image = image;
            texture.needsUpdate = true;

        } );

    }

    function extractDoubleSided( obj, element ) {

        obj.doubleSided = false;

        var node = element.querySelectorAll('extra double_sided')[0];

        if ( node ) {

            if ( node && parseInt( node.textContent, 10 ) === 1 ) {

                obj.doubleSided = true;

            }

        }

    }

    // Up axis conversion

    function setUpConversion() {

        if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) {

            upConversion = null;

        } else {

            switch ( colladaUp ) {

                case 'X':

                    upConversion = options.upAxis === 'Y' ? 'XtoY' : 'XtoZ';
                    break;

                case 'Y':

                    upConversion = options.upAxis === 'X' ? 'YtoX' : 'YtoZ';
                    break;

                case 'Z':

                    upConversion = options.upAxis === 'X' ? 'ZtoX' : 'ZtoY';
                    break;

            }

        }

    }

    function fixCoords( data, sign ) {

        if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) {

            return;

        }

        switch ( upConversion ) {

            case 'XtoY':

                var tmp = data[ 0 ];
                data[ 0 ] = sign * data[ 1 ];
                data[ 1 ] = tmp;
                break;

            case 'XtoZ':

                var tmp = data[ 2 ];
                data[ 2 ] = data[ 1 ];
                data[ 1 ] = data[ 0 ];
                data[ 0 ] = tmp;
                break;

            case 'YtoX':

                var tmp = data[ 0 ];
                data[ 0 ] = data[ 1 ];
                data[ 1 ] = sign * tmp;
                break;

            case 'YtoZ':

                var tmp = data[ 1 ];
                data[ 1 ] = sign * data[ 2 ];
                data[ 2 ] = tmp;
                break;

            case 'ZtoX':

                var tmp = data[ 0 ];
                data[ 0 ] = data[ 1 ];
                data[ 1 ] = data[ 2 ];
                data[ 2 ] = tmp;
                break;

            case 'ZtoY':

                var tmp = data[ 1 ];
                data[ 1 ] = data[ 2 ];
                data[ 2 ] = sign * tmp;
                break;

        }

    }

    function getConvertedTranslation( axis, data ) {

        if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) {

            return data;

        }

        switch ( axis ) {
            case 'X':
                data = upConversion === 'XtoY' ? data * -1 : data;
                break;
            case 'Y':
                data = upConversion === 'YtoZ' || upConversion === 'YtoX' ? data * -1 : data;
                break;
            case 'Z':
                data = upConversion === 'ZtoY' ? data * -1 : data ;
                break;
            default:
                break;
        }

        return data;
    }

    function getConvertedVec3( data, offset ) {

        var arr = [ data[ offset ], data[ offset + 1 ], data[ offset + 2 ] ];
        fixCoords( arr, -1 );
        return new IV.THREE.Vector3( arr[ 0 ], arr[ 1 ], arr[ 2 ] );

    }

    function getConvertedMat4( data ) {

        if ( options.convertUpAxis ) {

            // First fix rotation and scale

            // Columns first
            var arr = [ data[ 0 ], data[ 4 ], data[ 8 ] ];
            fixCoords( arr, -1 );
            data[ 0 ] = arr[ 0 ];
            data[ 4 ] = arr[ 1 ];
            data[ 8 ] = arr[ 2 ];
            arr = [ data[ 1 ], data[ 5 ], data[ 9 ] ];
            fixCoords( arr, -1 );
            data[ 1 ] = arr[ 0 ];
            data[ 5 ] = arr[ 1 ];
            data[ 9 ] = arr[ 2 ];
            arr = [ data[ 2 ], data[ 6 ], data[ 10 ] ];
            fixCoords( arr, -1 );
            data[ 2 ] = arr[ 0 ];
            data[ 6 ] = arr[ 1 ];
            data[ 10 ] = arr[ 2 ];
            // Rows second
            arr = [ data[ 0 ], data[ 1 ], data[ 2 ] ];
            fixCoords( arr, -1 );
            data[ 0 ] = arr[ 0 ];
            data[ 1 ] = arr[ 1 ];
            data[ 2 ] = arr[ 2 ];
            arr = [ data[ 4 ], data[ 5 ], data[ 6 ] ];
            fixCoords( arr, -1 );
            data[ 4 ] = arr[ 0 ];
            data[ 5 ] = arr[ 1 ];
            data[ 6 ] = arr[ 2 ];
            arr = [ data[ 8 ], data[ 9 ], data[ 10 ] ];
            fixCoords( arr, -1 );
            data[ 8 ] = arr[ 0 ];
            data[ 9 ] = arr[ 1 ];
            data[ 10 ] = arr[ 2 ];

            // Now fix translation
            arr = [ data[ 3 ], data[ 7 ], data[ 11 ] ];
            fixCoords( arr, -1 );
            data[ 3 ] = arr[ 0 ];
            data[ 7 ] = arr[ 1 ];
            data[ 11 ] = arr[ 2 ];

        }

        return new IV.THREE.Matrix4().set(
            data[0], data[1], data[2], data[3],
            data[4], data[5], data[6], data[7],
            data[8], data[9], data[10], data[11],
            data[12], data[13], data[14], data[15]
        );

    }

    function getConvertedIndex( index ) {

        if ( index > -1 && index < 3 ) {

            var members = [ 'X', 'Y', 'Z' ],
                indices = { X: 0, Y: 1, Z: 2 };

            index = getConvertedMember( members[ index ] );
            index = indices[ index ];

        }

        return index;

    }

    function getConvertedMember( member ) {

        if ( options.convertUpAxis ) {

            switch ( member ) {

                case 'X':

                    switch ( upConversion ) {

                        case 'XtoY':
                        case 'XtoZ':
                        case 'YtoX':

                            member = 'Y';
                            break;

                        case 'ZtoX':

                            member = 'Z';
                            break;

                    }

                    break;

                case 'Y':

                    switch ( upConversion ) {

                        case 'XtoY':
                        case 'YtoX':
                        case 'ZtoX':

                            member = 'X';
                            break;

                        case 'XtoZ':
                        case 'YtoZ':
                        case 'ZtoY':

                            member = 'Z';
                            break;

                    }

                    break;

                case 'Z':

                    switch ( upConversion ) {

                        case 'XtoZ':

                            member = 'X';
                            break;

                        case 'YtoZ':
                        case 'ZtoX':
                        case 'ZtoY':

                            member = 'Y';
                            break;

                    }

                    break;

            }

        }        return member;

    }

    return {

        load: load,
        parse: parse,
        setPreferredShading: setPreferredShading,
        applySkin: applySkin,
        geometries : geometries,
        options: options

    };

};