From e5d32c2b252f2bdd32a9f4e5b08f330bc61bbd8c Mon Sep 17 00:00:00 2001
From: Sandro Weber <webers@in.tum.de>
Date: Thu, 26 Mar 2020 09:49:20 +0000
Subject: [PATCH] =?UTF-8?q?Fusion=20effectu=C3=A9e=20NRRPLT-7843-lookat-mo?=
 =?UTF-8?q?de-broken=20(pull=20request=20#35)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

NRRPLT-7843 lookat mode broken

* [NRRPLT-7843] updated getBoundingSphere() calls

* [NRRPLT-7843] prettified

* [NRRPLT-7843] precommit stage lint messing up prettier formatting?

* [NRRPLT-7843] fixed formatting

* [NRRPLT-7843] added /include dir to prettier list of files

Approuvé par : Axel von Arnim
Approuvé par : Daniel Dyrda
---
 .prettierignore                               |    2 +-
 gz3d/build/gz3d.js                            |   40 +-
 gz3d/client/js/include/ColladaLoader.js       | 6005 ++++++++---------
 .../js/include/ThreeBackwardsCompatibility.js |   19 +-
 gz3d/client/js/include/avatar-controls.js     |  427 +-
 gz3d/client/js/include/gzwebsocket.js         |    9 +-
 gz3d/client/js/include/lookat-controls.js     |  487 +-
 gz3d/src/gz3d-fluid-vis.js                    |   20 +-
 gz3d/src/gziface.js                           |   18 +-
 gz3d/src/gzlabelmanager.js                    |    3 +-
 package.json                                  |    3 +-
 11 files changed, 3425 insertions(+), 3608 deletions(-)

diff --git a/.prettierignore b/.prettierignore
index 337b2f9..60a1650 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,7 +1,7 @@
 package.json
 gz3d/utils/package.json
 gz3d/utils/Gruntfile.js
-gz3d/client/js
+gz3d/client/js/external
 bower_components
 node_modules
 build
diff --git a/gz3d/build/gz3d.js b/gz3d/build/gz3d.js
index 0579629..6cda9ab 100644
--- a/gz3d/build/gz3d.js
+++ b/gz3d/build/gz3d.js
@@ -387,8 +387,7 @@ GZ3D.AnimatedModel.prototype.updateJoint = function(
   }
 };
 
-
-GZ3D.VisualFluidModel = function (scene) {
+GZ3D.VisualFluidModel = function(scene) {
   this.scene = scene;
   this.rootGroup = new THREE.Group();
   this.rootGroup.name = 'VisualFluidModel';
@@ -396,7 +395,7 @@ GZ3D.VisualFluidModel = function (scene) {
   this.rootGroup.visible = true;
 };
 
-GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) {
+GZ3D.VisualFluidModel.prototype.initFluidBuffers = function(message) {
   this.scales = new Float32Array(message.position.length);
   this.scales.fill(10.0);
   this.positions = new Float32Array(message.position.length * 3);
@@ -404,7 +403,10 @@ GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) {
   this.positionAttribute = new THREE.BufferAttribute(this.positions, 3);
   this.positionAttribute.setDynamic(true);
   this.geometry.addAttribute('position', this.positionAttribute);
-  this.geometry.addAttribute('scale', new THREE.BufferAttribute(this.scales, 1));
+  this.geometry.addAttribute(
+    'scale',
+    new THREE.BufferAttribute(this.scales, 1)
+  );
   this.material = new THREE.ParticleBasicMaterial({
     size: 5.0,
     sizeAttenuation: false,
@@ -418,14 +420,19 @@ GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) {
   this.rootGroup.add(this.particles);
 };
 
-GZ3D.VisualFluidModel.prototype.updateVisualization = function (message) {
+GZ3D.VisualFluidModel.prototype.updateVisualization = function(message) {
   var vertex;
   for (var i = 0, l = message.position.length; i < l; i++) {
-    vertex = new THREE.Vector3(message.position[i].x, message.position[i].y, message.position[i].z);
+    vertex = new THREE.Vector3(
+      message.position[i].x,
+      message.position[i].y,
+      message.position[i].z
+    );
     vertex.toArray(this.positions, i * 3);
   }
   this.positionAttribute.needsUpdate = true;
 };
+
 GZ3D.VisualMuscleModel = function(scene, robotName) {
   this.scene = scene;
   this.cylinderShapes = {};
@@ -4870,11 +4877,11 @@ GZ3D.GZIface.prototype.onConnected = function() {
     ros: this.webSocket,
     name: '~/fluid_pos',
     message_type: 'fluid',
-    throttle_rate: 1.0 / 200.0 * 1000.0,
+    throttle_rate: 1.0 / 200.0 * 1000.0
   });
 
   // function for updating client system visualization
-  var updateMuscleVisualization = function (message) {
+  var updateMuscleVisualization = function(message) {
     if (!(message.robot_name in this.scene.muscleVisuzalizations)) {
       this.scene.muscleVisuzalizations[
         message.robot_name
@@ -4887,11 +4894,13 @@ GZ3D.GZIface.prototype.onConnected = function() {
       );
     }
   };
- 
+
   // function for updating client system visualization
-  var updateFluidVisualization = function (message) {
+  var updateFluidVisualization = function(message) {
     if (!this.scene.fluidVisualizations.length) {
-      this.scene.fluidVisualizations.push(new GZ3D.VisualFluidModel(this.scene, message));
+      this.scene.fluidVisualizations.push(
+        new GZ3D.VisualFluidModel(this.scene, message)
+      );
       this.scene.fluidVisualizations[0].initFluidBuffers(message);
     }
     this.scene.fluidVisualizations[0].updateVisualization(message);
@@ -4903,9 +4912,11 @@ GZ3D.GZIface.prototype.onConnected = function() {
   );
 
   // Subscription to fluid_pos topic
-  this.fluidVisualizationSubscriber.subscribe(updateFluidVisualization.bind(this));
+  this.fluidVisualizationSubscriber.subscribe(
+    updateFluidVisualization.bind(this)
+  );
 
-  var requestUpdate = function (message) {
+  var requestUpdate = function(message) {
     if (message.request === 'entity_delete') {
       var entity = this.scene.getByName(message.data);
       if (entity) {
@@ -6695,7 +6706,8 @@ GZ3D.LabelManager.prototype.onRender = function() {
         var labelPos = [];
 
         bbox.setFromObject(root);
-        var bsphere = bbox.getBoundingSphere();
+        var bsphere = new THREE.Sphere();
+        bbox.getBoundingSphere(bsphere);
 
         for (var i = 0; i < labelInfoList.length; i++) {
           var labelInfo = labelInfoList[i];
diff --git a/gz3d/client/js/include/ColladaLoader.js b/gz3d/client/js/include/ColladaLoader.js
index 7712ab9..19e183e 100644
--- a/gz3d/client/js/include/ColladaLoader.js
+++ b/gz3d/client/js/include/ColladaLoader.js
@@ -3,3538 +3,3111 @@
  * @author Mugen87 / https://github.com/Mugen87
  */
 
-THREE.ColladaLoader = function ( manager ) {
+THREE.ColladaLoader = function(manager) {
+  this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager;
+};
 
-			this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+THREE.ColladaLoader.prototype = {
+  constructor: THREE.ColladaLoader,
 
-		};
+  crossOrigin: 'Anonymous',
 
-		THREE.ColladaLoader.prototype = {
+  load: function(url, onLoad, onProgress, onError) {
+    var scope = this;
 
-			constructor: THREE.ColladaLoader,
+    var path = THREE.LoaderUtils.extractUrlBase(url);
 
-			crossOrigin: 'Anonymous',
+    var loader = new THREE.FileLoader(scope.manager);
+    loader.load(
+      url,
+      function(text) {
+        onLoad(scope.parse(text, path));
+      },
+      onProgress,
+      onError
+    );
+  },
 
-			load: function ( url, onLoad, onProgress, onError ) {
+  options: {
+    set convertUpAxis(value) {
+      console.warn(
+        'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.'
+      );
+    }
+  },
 
-				var scope = this;
+  setCrossOrigin: function(value) {
+    this.crossOrigin = value;
+  },
 
-				var path = THREE.LoaderUtils.extractUrlBase( url );
+  parse: function(text, path) {
+    function getElementsByTagName(xml, name) {
+      // Non recursive xml.getElementsByTagName() ...
 
-				var loader = new THREE.FileLoader( scope.manager );
-				loader.load( url, function ( text ) {
+      var array = [];
+      var childNodes = xml.childNodes;
 
-					onLoad( scope.parse( text, path ) );
+      for (var i = 0, l = childNodes.length; i < l; i++) {
+        var child = childNodes[i];
 
-				}, onProgress, onError );
+        if (child.nodeName === name) {
+          array.push(child);
+        }
+      }
 
-			},
+      return array;
+    }
 
-			options: {
+    function parseStrings(text) {
+      if (text.length === 0) return [];
 
-				set convertUpAxis( value ) {
+      var parts = text.trim().split(/\s+/);
+      var array = new Array(parts.length);
 
-					console.warn( 'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.' );
+      for (var i = 0, l = parts.length; i < l; i++) {
+        array[i] = parts[i];
+      }
 
-				}
+      return array;
+    }
 
-			},
+    function parseFloats(text) {
+      if (text.length === 0) return [];
 
-			setCrossOrigin: function ( value ) {
+      var parts = text.trim().split(/\s+/);
+      var array = new Array(parts.length);
 
-				this.crossOrigin = value;
+      for (var i = 0, l = parts.length; i < l; i++) {
+        array[i] = parseFloat(parts[i]);
+      }
 
-			},
+      return array;
+    }
 
-			parse: function ( text, path ) {
+    function parseInts(text) {
+      if (text.length === 0) return [];
 
-				function getElementsByTagName( xml, name ) {
+      var parts = text.trim().split(/\s+/);
+      var array = new Array(parts.length);
 
-					// Non recursive xml.getElementsByTagName() ...
+      for (var i = 0, l = parts.length; i < l; i++) {
+        array[i] = parseInt(parts[i]);
+      }
 
-					var array = [];
-					var childNodes = xml.childNodes;
+      return array;
+    }
 
-					for ( var i = 0, l = childNodes.length; i < l; i ++ ) {
+    function parseId(text) {
+      return text.substring(1);
+    }
 
-						var child = childNodes[ i ];
+    function generateId() {
+      return 'three_default_' + count++;
+    }
 
-						if ( child.nodeName === name ) {
+    function isEmpty(object) {
+      return Object.keys(object).length === 0;
+    }
 
-							array.push( child );
+    // asset
 
-						}
+    function parseAsset(xml) {
+      return {
+        unit: parseAssetUnit(getElementsByTagName(xml, 'unit')[0]),
+        upAxis: parseAssetUpAxis(getElementsByTagName(xml, 'up_axis')[0])
+      };
+    }
+
+    function parseAssetUnit(xml) {
+      return xml !== undefined ? parseFloat(xml.getAttribute('meter')) : 1;
+    }
 
-					}
+    function parseAssetUpAxis(xml) {
+      return xml !== undefined ? xml.textContent : 'Y_UP';
+    }
 
-					return array;
+    // library
 
-				}
+    function parseLibrary(xml, libraryName, nodeName, parser) {
+      var library = getElementsByTagName(xml, libraryName)[0];
 
-				function parseStrings( text ) {
+      if (library !== undefined) {
+        var elements = getElementsByTagName(library, nodeName);
 
-					if ( text.length === 0 ) return [];
+        for (var i = 0; i < elements.length; i++) {
+          parser(elements[i]);
+        }
+      }
+    }
 
-					var parts = text.trim().split( /\s+/ );
-					var array = new Array( parts.length );
+    function buildLibrary(data, builder) {
+      for (var name in data) {
+        var object = data[name];
+        object.build = builder(data[name]);
+      }
+    }
 
-					for ( var i = 0, l = parts.length; i < l; i ++ ) {
+    // get
 
-						array[ i ] = parts[ i ];
+    function getBuild(data, builder) {
+      if (data !== undefined) {
+        if (data.build !== undefined) return data.build;
 
-					}
+        data.build = builder(data);
 
-					return array;
+        return data.build;
+      }
+      return null;
+    }
 
-				}
+    // animation
 
-				function parseFloats( text ) {
+    function parseAnimation(xml) {
+      var data = {
+        sources: {},
+        samplers: {},
+        channels: {}
+      };
 
-					if ( text.length === 0 ) return [];
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-					var parts = text.trim().split( /\s+/ );
-					var array = new Array( parts.length );
+        if (child.nodeType !== 1) continue;
 
-					for ( var i = 0, l = parts.length; i < l; i ++ ) {
+        var id;
 
-						array[ i ] = parseFloat( parts[ i ] );
+        switch (child.nodeName) {
+          case 'source':
+            id = child.getAttribute('id');
+            data.sources[id] = parseSource(child);
+            break;
 
-					}
+          case 'sampler':
+            id = child.getAttribute('id');
+            data.samplers[id] = parseAnimationSampler(child);
+            break;
 
-					return array;
+          case 'channel':
+            id = child.getAttribute('target');
+            data.channels[id] = parseAnimationChannel(child);
+            break;
 
-				}
+          default:
+            break;
+        }
+      }
 
-				function parseInts( text ) {
+      library.animations[xml.getAttribute('id')] = data;
+    }
 
-					if ( text.length === 0 ) return [];
+    function parseAnimationSampler(xml) {
+      var data = {
+        inputs: {}
+      };
 
-					var parts = text.trim().split( /\s+/ );
-					var array = new Array( parts.length );
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-					for ( var i = 0, l = parts.length; i < l; i ++ ) {
+        if (child.nodeType !== 1) continue;
 
-						array[ i ] = parseInt( parts[ i ] );
+        switch (child.nodeName) {
+          case 'input':
+            var id = parseId(child.getAttribute('source'));
+            var semantic = child.getAttribute('semantic');
+            data.inputs[semantic] = id;
+            break;
+        }
+      }
 
-					}
+      return data;
+    }
 
-					return array;
+    function parseAnimationChannel(xml) {
+      var data = {};
 
-				}
+      var target = xml.getAttribute('target');
 
-				function parseId( text ) {
+      // parsing SID Addressing Syntax
 
-					return text.substring( 1 );
+      var parts = target.split('/');
 
-				}
+      var id = parts.shift();
+      var sid = parts.shift();
 
-				function generateId() {
+      // check selection syntax
 
-					return 'three_default_' + ( count ++ );
+      var arraySyntax = sid.indexOf('(') !== -1;
+      var memberSyntax = sid.indexOf('.') !== -1;
 
-				}
+      if (memberSyntax) {
+        //  member selection access
 
-				function isEmpty( object ) {
+        parts = sid.split('.');
+        sid = parts.shift();
+        data.member = parts.shift();
+      } else if (arraySyntax) {
+        // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices.
 
-					return Object.keys( object ).length === 0;
+        var indices = sid.split('(');
+        sid = indices.shift();
 
-				}
+        for (var i = 0; i < indices.length; i++) {
+          indices[i] = parseInt(indices[i].replace(/\)/, ''));
+        }
 
-				// asset
+        data.indices = indices;
+      }
 
-				function parseAsset( xml ) {
+      data.id = id;
+      data.sid = sid;
 
-					return {
-						unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ),
-						upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] )
-					};
+      data.arraySyntax = arraySyntax;
+      data.memberSyntax = memberSyntax;
 
-				}
+      data.sampler = parseId(xml.getAttribute('source'));
 
-				function parseAssetUnit( xml ) {
+      return data;
+    }
 
-					return xml !== undefined ? parseFloat( xml.getAttribute( 'meter' ) ) : 1;
+    function buildAnimation(data) {
+      var tracks = [];
 
-				}
+      var channels = data.channels;
+      var samplers = data.samplers;
+      var sources = data.sources;
 
-				function parseAssetUpAxis( xml ) {
+      for (var target in channels) {
+        if (channels.hasOwnProperty(target)) {
+          var channel = channels[target];
+          var sampler = samplers[channel.sampler];
 
-					return xml !== undefined ? xml.textContent : 'Y_UP';
+          var inputId = sampler.inputs.INPUT;
+          var outputId = sampler.inputs.OUTPUT;
 
-				}
+          var inputSource = sources[inputId];
+          var outputSource = sources[outputId];
 
-				// library
+          var animation = buildAnimationChannel(
+            channel,
+            inputSource,
+            outputSource
+          );
 
-				function parseLibrary( xml, libraryName, nodeName, parser ) {
+          createKeyframeTracks(animation, tracks);
+        }
+      }
 
-					var library = getElementsByTagName( xml, libraryName )[ 0 ];
+      return tracks;
+    }
 
-					if ( library !== undefined ) {
+    function getAnimation(id) {
+      return getBuild(library.animations[id], buildAnimation);
+    }
 
-						var elements = getElementsByTagName( library, nodeName );
+    function buildAnimationChannel(channel, inputSource, outputSource) {
+      var node = library.nodes[channel.id];
+      var object3D = getNode(node.id);
 
-						for ( var i = 0; i < elements.length; i ++ ) {
+      var transform = node.transforms[channel.sid];
+      var defaultMatrix = node.matrix.clone().transpose();
 
-							parser( elements[ i ] );
+      var time, stride;
+      var i, il, j, jl;
 
-						}
+      var data = {};
 
-					}
+      // the collada spec allows the animation of data in various ways.
+      // depending on the transform type (matrix, translate, rotate, scale), we execute different logic
 
-				}
+      switch (transform) {
+        case 'matrix':
+          for (i = 0, il = inputSource.array.length; i < il; i++) {
+            time = inputSource.array[i];
+            stride = i * outputSource.stride;
 
-				function buildLibrary( data, builder ) {
+            if (data[time] === undefined) data[time] = {};
 
-					for ( var name in data ) {
+            if (channel.arraySyntax === true) {
+              var value = outputSource.array[stride];
+              var index = channel.indices[0] + 4 * channel.indices[1];
 
-						var object = data[ name ];
-						object.build = builder( data[ name ] );
+              data[time][index] = value;
+            } else {
+              for (j = 0, jl = outputSource.stride; j < jl; j++) {
+                data[time][j] = outputSource.array[stride + j];
+              }
+            }
+          }
 
-					}
+          break;
 
-				}
+        case 'translate':
+          console.warn(
+            'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.',
+            transform
+          );
+          break;
 
-				// get
+        case 'rotate':
+          console.warn(
+            'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.',
+            transform
+          );
+          break;
 
-				function getBuild( data, builder ) {
-					if (data !== undefined) {
-						if (data.build !== undefined) return data.build;
+        case 'scale':
+          console.warn(
+            'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.',
+            transform
+          );
+          break;
+      }
 
-						data.build = builder(data);
+      var keyframes = prepareAnimationData(data, defaultMatrix);
 
-						return data.build;
-					}
-					return null;
-				}
+      var animation = {
+        name: object3D.uuid,
+        keyframes: keyframes
+      };
+
+      return animation;
+    }
+
+    function prepareAnimationData(data, defaultMatrix) {
+      var keyframes = [];
+
+      // transfer data into a sortable array
+
+      for (var time in data) {
+        keyframes.push({ time: parseFloat(time), value: data[time] });
+      }
+
+      // ensure keyframes are sorted by time
+
+      keyframes.sort(ascending);
+
+      // now we clean up all animation data, so we can use them for keyframe tracks
+
+      for (var i = 0; i < 16; i++) {
+        transformAnimationData(keyframes, i, defaultMatrix.elements[i]);
+      }
+
+      return keyframes;
+
+      // array sort function
+
+      function ascending(a, b) {
+        return a.time - b.time;
+      }
+    }
+
+    var position = new THREE.Vector3();
+    var scale = new THREE.Vector3();
+    var quaternion = new THREE.Quaternion();
+
+    function createKeyframeTracks(animation, tracks) {
+      var keyframes = animation.keyframes;
+      var name = animation.name;
+
+      var times = [];
+      var positionData = [];
+      var quaternionData = [];
+      var scaleData = [];
+
+      for (var i = 0, l = keyframes.length; i < l; i++) {
+        var keyframe = keyframes[i];
+
+        var time = keyframe.time;
+        var value = keyframe.value;
+
+        matrix.fromArray(value).transpose();
+        matrix.decompose(position, quaternion, scale);
+
+        times.push(time);
+        positionData.push(position.x, position.y, position.z);
+        quaternionData.push(
+          quaternion.x,
+          quaternion.y,
+          quaternion.z,
+          quaternion.w
+        );
+        scaleData.push(scale.x, scale.y, scale.z);
+      }
+
+      if (positionData.length > 0)
+        tracks.push(
+          new THREE.VectorKeyframeTrack(name + '.position', times, positionData)
+        );
+      if (quaternionData.length > 0)
+        tracks.push(
+          new THREE.QuaternionKeyframeTrack(
+            name + '.quaternion',
+            times,
+            quaternionData
+          )
+        );
+      if (scaleData.length > 0)
+        tracks.push(
+          new THREE.VectorKeyframeTrack(name + '.scale', times, scaleData)
+        );
 
-				// animation
+      return tracks;
+    }
 
-				function parseAnimation( xml ) {
+    function transformAnimationData(keyframes, property, defaultValue) {
+      var keyframe;
+
+      var empty = true;
+      var i, l;
 
-					var data = {
-						sources: {},
-						samplers: {},
-						channels: {}
-					};
+      // check, if values of a property are missing in our keyframes
 
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+      for (i = 0, l = keyframes.length; i < l; i++) {
+        keyframe = keyframes[i];
 
-						var child = xml.childNodes[ i ];
+        if (keyframe.value[property] === undefined) {
+          keyframe.value[property] = null; // mark as missing
+        } else {
+          empty = false;
+        }
+      }
 
-						if ( child.nodeType !== 1 ) continue;
+      if (empty === true) {
+        // no values at all, so we set a default value
 
-						var id;
+        for (i = 0, l = keyframes.length; i < l; i++) {
+          keyframe = keyframes[i];
 
-						switch ( child.nodeName ) {
+          keyframe.value[property] = defaultValue;
+        }
+      } else {
+        // filling gaps
+
+        createMissingKeyframes(keyframes, property);
+      }
+    }
+
+    function createMissingKeyframes(keyframes, property) {
+      var prev, next;
+
+      for (var i = 0, l = keyframes.length; i < l; i++) {
+        var keyframe = keyframes[i];
+
+        if (keyframe.value[property] === null) {
+          prev = getPrev(keyframes, i, property);
+          next = getNext(keyframes, i, property);
+
+          if (prev === null) {
+            keyframe.value[property] = next.value[property];
+            continue;
+          }
+
+          if (next === null) {
+            keyframe.value[property] = prev.value[property];
+            continue;
+          }
+
+          interpolate(keyframe, prev, next, property);
+        }
+      }
+    }
+
+    function getPrev(keyframes, i, property) {
+      while (i >= 0) {
+        var keyframe = keyframes[i];
+
+        if (keyframe.value[property] !== null) return keyframe;
+
+        i--;
+      }
+
+      return null;
+    }
+
+    function getNext(keyframes, i, property) {
+      while (i < keyframes.length) {
+        var keyframe = keyframes[i];
+
+        if (keyframe.value[property] !== null) return keyframe;
+
+        i++;
+      }
+
+      return null;
+    }
+
+    function interpolate(key, prev, next, property) {
+      if (next.time - prev.time === 0) {
+        key.value[property] = prev.value[property];
+        return;
+      }
 
-							case 'source':
-								id = child.getAttribute( 'id' );
-								data.sources[ id ] = parseSource( child );
-								break;
+      key.value[property] =
+        (key.time - prev.time) *
+          (next.value[property] - prev.value[property]) /
+          (next.time - prev.time) +
+        prev.value[property];
+    }
 
-							case 'sampler':
-								id = child.getAttribute( 'id' );
-								data.samplers[ id ] = parseAnimationSampler( child );
-								break;
+    // animation clips
 
-							case 'channel':
-								id = child.getAttribute( 'target' );
-								data.channels[ id ] = parseAnimationChannel( child );
-								break;
+    function parseAnimationClip(xml) {
+      var data = {
+        name: xml.getAttribute('id') || 'default',
+        start: parseFloat(xml.getAttribute('start') || 0),
+        end: parseFloat(xml.getAttribute('end') || 0),
+        animations: []
+      };
 
-							default:
-								break;
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-						}
+        if (child.nodeType !== 1) continue;
 
-					}
+        switch (child.nodeName) {
+          case 'instance_animation':
+            data.animations.push(parseId(child.getAttribute('url')));
+            break;
+        }
+      }
 
-					library.animations[ xml.getAttribute( 'id' ) ] = data;
+      library.clips[xml.getAttribute('id')] = data;
+    }
 
-				}
+    function buildAnimationClip(data) {
+      var tracks = [];
 
-				function parseAnimationSampler( xml ) {
+      var name = data.name;
+      var duration = data.end - data.start || -1;
+      var animations = data.animations;
 
-					var data = {
-						inputs: {},
-					};
+      for (var i = 0, il = animations.length; i < il; i++) {
+        var animationTracks = getAnimation(animations[i]);
+
+        for (var j = 0, jl = animationTracks.length; j < jl; j++) {
+          tracks.push(animationTracks[j]);
+        }
+      }
 
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+      return new THREE.AnimationClip(name, duration, tracks);
+    }
 
-						var child = xml.childNodes[ i ];
+    function getAnimationClip(id) {
+      return getBuild(library.clips[id], buildAnimationClip);
+    }
+
+    // controller
+
+    function parseController(xml) {
+      var data = {};
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'skin':
+            // there is exactly one skin per controller
+            data.id = parseId(child.getAttribute('source'));
+            data.skin = parseSkin(child);
+            break;
+
+          case 'morph':
+            data.id = parseId(child.getAttribute('source'));
+            console.warn(
+              'THREE.ColladaLoader: Morph target animation not supported yet.'
+            );
+            break;
+        }
+      }
+
+      library.controllers[xml.getAttribute('id')] = data;
+    }
+
+    function parseSkin(xml) {
+      var data = {
+        sources: {}
+      };
 
-						if ( child.nodeType !== 1 ) continue;
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-						switch ( child.nodeName ) {
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'bind_shape_matrix':
+            data.bindShapeMatrix = parseFloats(child.textContent);
+            break;
+
+          case 'source':
+            var id = child.getAttribute('id');
+            data.sources[id] = parseSource(child);
+            break;
+
+          case 'joints':
+            data.joints = parseJoints(child);
+            break;
+
+          case 'vertex_weights':
+            data.vertexWeights = parseVertexWeights(child);
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseJoints(xml) {
+      var data = {
+        inputs: {}
+      };
 
-							case 'input':
-								var id = parseId( child.getAttribute( 'source' ) );
-								var semantic = child.getAttribute( 'semantic' );
-								data.inputs[ semantic ] = id;
-								break;
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
 
-						}
+        switch (child.nodeName) {
+          case 'input':
+            var semantic = child.getAttribute('semantic');
+            var id = parseId(child.getAttribute('source'));
+            data.inputs[semantic] = id;
+            break;
+        }
+      }
 
-					}
+      return data;
+    }
 
-					return data;
+    function parseVertexWeights(xml) {
+      var data = {
+        inputs: {}
+      };
 
-				}
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-				function parseAnimationChannel( xml ) {
+        if (child.nodeType !== 1) continue;
 
-					var data = {};
+        switch (child.nodeName) {
+          case 'input':
+            var semantic = child.getAttribute('semantic');
+            var id = parseId(child.getAttribute('source'));
+            var offset = parseInt(child.getAttribute('offset'));
+            data.inputs[semantic] = { id: id, offset: offset };
+            break;
 
-					var target = xml.getAttribute( 'target' );
+          case 'vcount':
+            data.vcount = parseInts(child.textContent);
+            break;
+
+          case 'v':
+            data.v = parseInts(child.textContent);
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function buildController(data) {
+      var build = {
+        id: data.id
+      };
 
-					// parsing SID Addressing Syntax
+      var geometry = library.geometries[build.id];
 
-					var parts = target.split( '/' );
+      if (data.skin !== undefined) {
+        build.skin = buildSkin(data.skin);
 
-					var id = parts.shift();
-					var sid = parts.shift();
+        // we enhance the 'sources' property of the corresponding geometry with our skin data
 
-					// check selection syntax
+        geometry.sources.skinIndices = build.skin.indices;
+        geometry.sources.skinWeights = build.skin.weights;
+      }
 
-					var arraySyntax = ( sid.indexOf( '(' ) !== - 1 );
-					var memberSyntax = ( sid.indexOf( '.' ) !== - 1 );
+      return build;
+    }
 
-					if ( memberSyntax ) {
+    function buildSkin(data) {
+      var BONE_LIMIT = 4;
 
-						//  member selection access
+      var build = {
+        joints: [], // this must be an array to preserve the joint order
+        indices: {
+          array: [],
+          stride: BONE_LIMIT
+        },
+        weights: {
+          array: [],
+          stride: BONE_LIMIT
+        }
+      };
 
-						parts = sid.split( '.' );
-						sid = parts.shift();
-						data.member = parts.shift();
+      var sources = data.sources;
+      var vertexWeights = data.vertexWeights;
 
-					} else if ( arraySyntax ) {
+      var vcount = vertexWeights.vcount;
+      var v = vertexWeights.v;
+      var jointOffset = vertexWeights.inputs.JOINT.offset;
+      var weightOffset = vertexWeights.inputs.WEIGHT.offset;
 
-						// array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices.
+      var jointSource = data.sources[data.joints.inputs.JOINT];
+      var inverseSource = data.sources[data.joints.inputs.INV_BIND_MATRIX];
 
-						var indices = sid.split( '(' );
-						sid = indices.shift();
+      var weights = sources[vertexWeights.inputs.WEIGHT.id].array;
+      var stride = 0;
 
-						for ( var i = 0; i < indices.length; i ++ ) {
+      var i, j, l;
 
-							indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) );
+      // procces skin data for each vertex
 
-						}
+      for (i = 0, l = vcount.length; i < l; i++) {
+        var jointCount = vcount[i]; // this is the amount of joints that affect a single vertex
+        var vertexSkinData = [];
 
-						data.indices = indices;
+        for (j = 0; j < jointCount; j++) {
+          var skinIndex = v[stride + jointOffset];
+          var weightId = v[stride + weightOffset];
+          var skinWeight = weights[weightId];
 
-					}
+          vertexSkinData.push({ index: skinIndex, weight: skinWeight });
 
-					data.id = id;
-					data.sid = sid;
+          stride += 2;
+        }
 
-					data.arraySyntax = arraySyntax;
-					data.memberSyntax = memberSyntax;
+        // we sort the joints in descending order based on the weights.
+        // this ensures, we only procced the most important joints of the vertex
 
-					data.sampler = parseId( xml.getAttribute( 'source' ) );
+        vertexSkinData.sort(descending);
 
-					return data;
+        // now we provide for each vertex a set of four index and weight values.
+        // the order of the skin data matches the order of vertices
 
-				}
+        for (j = 0; j < BONE_LIMIT; j++) {
+          var d = vertexSkinData[j];
 
-				function buildAnimation( data ) {
+          if (d !== undefined) {
+            build.indices.array.push(d.index);
+            build.weights.array.push(d.weight);
+          } else {
+            build.indices.array.push(0);
+            build.weights.array.push(0);
+          }
+        }
+      }
 
-					var tracks = [];
+      // setup bind matrix
 
-					var channels = data.channels;
-					var samplers = data.samplers;
-					var sources = data.sources;
+      build.bindMatrix = new THREE.Matrix4()
+        .fromArray(data.bindShapeMatrix)
+        .transpose();
 
-					for ( var target in channels ) {
+      // process bones and inverse bind matrix data
 
-						if ( channels.hasOwnProperty( target ) ) {
+      for (i = 0, l = jointSource.array.length; i < l; i++) {
+        var name = jointSource.array[i];
+        var boneInverse = new THREE.Matrix4()
+          .fromArray(inverseSource.array, i * inverseSource.stride)
+          .transpose();
 
-							var channel = channels[ target ];
-							var sampler = samplers[ channel.sampler ];
+        build.joints.push({ name: name, boneInverse: boneInverse });
+      }
 
-							var inputId = sampler.inputs.INPUT;
-							var outputId = sampler.inputs.OUTPUT;
+      return build;
 
-							var inputSource = sources[ inputId ];
-							var outputSource = sources[ outputId ];
+      // array sort function
 
-							var animation = buildAnimationChannel( channel, inputSource, outputSource );
+      function descending(a, b) {
+        return b.weight - a.weight;
+      }
+    }
 
-							createKeyframeTracks( animation, tracks );
+    function getController(id) {
+      return getBuild(library.controllers[id], buildController);
+    }
 
-						}
+    // image
 
-					}
+    function parseImage(xml) {
+      var data = {
+        init_from: getElementsByTagName(xml, 'init_from')[0].textContent
+      };
 
-					return tracks;
+      library.images[xml.getAttribute('id')] = data;
+    }
 
-				}
+    function buildImage(data) {
+      if (data.build !== undefined) return data.build;
 
-				function getAnimation( id ) {
+      return data.init_from;
+    }
 
-					return getBuild( library.animations[ id ], buildAnimation );
+    function getImage(id) {
+      return getBuild(library.images[id], buildImage);
+    }
 
-				}
+    // effect
 
-				function buildAnimationChannel( channel, inputSource, outputSource ) {
+    function parseEffect(xml) {
+      var data = {};
 
-					var node = library.nodes[ channel.id ];
-					var object3D = getNode( node.id );
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-					var transform = node.transforms[ channel.sid ];
-					var defaultMatrix = node.matrix.clone().transpose();
+        if (child.nodeType !== 1) continue;
 
-					var time, stride;
-					var i, il, j, jl;
+        switch (child.nodeName) {
+          case 'profile_COMMON':
+            data.profile = parseEffectProfileCOMMON(child);
+            break;
+        }
+      }
 
-					var data = {};
+      library.effects[xml.getAttribute('id')] = data;
+    }
 
-					// the collada spec allows the animation of data in various ways.
-					// depending on the transform type (matrix, translate, rotate, scale), we execute different logic
+    function parseEffectProfileCOMMON(xml) {
+      var data = {
+        surfaces: {},
+        samplers: {}
+      };
 
-					switch ( transform ) {
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-						case 'matrix':
+        if (child.nodeType !== 1) continue;
 
-							for ( i = 0, il = inputSource.array.length; i < il; i ++ ) {
+        switch (child.nodeName) {
+          case 'newparam':
+            parseEffectNewparam(child, data);
+            break;
 
-								time = inputSource.array[ i ];
-								stride = i * outputSource.stride;
+          case 'technique':
+            data.technique = parseEffectTechnique(child);
+            break;
+        }
+      }
 
-								if ( data[ time ] === undefined ) data[ time ] = {};
+      return data;
+    }
 
-								if ( channel.arraySyntax === true ) {
+    function parseEffectNewparam(xml, data) {
+      var sid = xml.getAttribute('sid');
 
-									var value = outputSource.array[ stride ];
-									var index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ];
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-									data[ time ][ index ] = value;
+        if (child.nodeType !== 1) continue;
 
-								} else {
+        switch (child.nodeName) {
+          case 'surface':
+            data.surfaces[sid] = parseEffectSurface(child);
+            break;
 
-									for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) {
+          case 'sampler2D':
+            data.samplers[sid] = parseEffectSampler(child);
+            break;
+        }
+      }
+    }
 
-										data[ time ][ j ] = outputSource.array[ stride + j ];
+    function parseEffectSurface(xml) {
+      var data = {};
 
-									}
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-								}
+        if (child.nodeType !== 1) continue;
 
-							}
+        switch (child.nodeName) {
+          case 'init_from':
+            data.init_from = child.textContent;
+            break;
+        }
+      }
 
-							break;
+      return data;
+    }
 
-						case 'translate':
-							console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
-							break;
+    function parseEffectSampler(xml) {
+      var data = {};
 
-						case 'rotate':
-							console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
-							break;
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'source':
+            data.source = child.textContent;
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseEffectTechnique(xml) {
+      var data = { parameters: {} };
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'constant':
+          case 'lambert':
+          case 'blinn':
+          case 'phong':
+            data.type = child.nodeName;
+
+          case 'extra':
+            data.parameters = Object.assign(
+              {},
+              data.parameters,
+              parseEffectParameters(child)
+            );
+            break;
+
+          case 'bump':
+            data.bump = parseEffectParameter(child);
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseEffectParameters(xml) {
+      var data = {};
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'ambient':
+          case 'emission':
+          case 'diffuse':
+          case 'specular':
+          case 'shininess':
+          case 'transparent':
+          case 'transparency':
+          case 'reflective':
+            var effect = parseEffectParameter(child);
+            if (child.nodeName !== 'ambient' || effect.texture) {
+              data[child.nodeName] = effect;
+            }
+            break;
+
+          case 'technique':
+            return parseEffectTechnique(child);
+        }
+      }
+
+      return data;
+    }
+
+    function parseEffectParameter(xml) {
+      var data = {};
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'color':
+            data[child.nodeName] = parseFloats(child.textContent);
+            break;
+
+          case 'float':
+            data[child.nodeName] = parseFloat(child.textContent);
+            break;
+
+          case 'texture':
+            data[child.nodeName] = {
+              id: child.getAttribute('texture'),
+              extra: parseEffectParameterTexture(child)
+            };
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseEffectParameterTexture(xml) {
+      var data = {
+        technique: {}
+      };
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'extra':
+            parseEffectParameterTextureExtra(child, data);
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseEffectParameterTextureExtra(xml, data) {
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'technique':
+            parseEffectParameterTextureExtraTechnique(child, data);
+            break;
+        }
+      }
+    }
+
+    function parseEffectParameterTextureExtraTechnique(xml, data) {
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'repeatU':
+          case 'repeatV':
+          case 'offsetU':
+          case 'offsetV':
+            data.technique[child.nodeName] = parseFloat(child.textContent);
+            break;
+
+          case 'wrapU':
+          case 'wrapV':
+            // some files have values for wrapU/wrapV which become NaN via parseInt
+
+            if (child.textContent.toUpperCase() === 'TRUE') {
+              data.technique[child.nodeName] = 1;
+            } else if (child.textContent.toUpperCase() === 'FALSE') {
+              data.technique[child.nodeName] = 0;
+            } else {
+              data.technique[child.nodeName] = parseInt(child.textContent);
+            }
+
+            break;
+        }
+      }
+    }
+
+    function buildEffect(data) {
+      return data;
+    }
+
+    function getEffect(id) {
+      return getBuild(library.effects[id], buildEffect);
+    }
+
+    // material
+
+    function parseMaterial(xml) {
+      var data = {
+        name: xml.getAttribute('name')
+      };
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'instance_effect':
+            data.url = parseId(child.getAttribute('url'));
+            break;
+        }
+      }
+
+      library.materials[xml.getAttribute('id')] = data;
+    }
+
+    function buildMaterial(data) {
+      var effect = getEffect(data.url);
+      var technique = effect.profile.technique;
+
+      var material;
+
+      switch (technique.type) {
+        case 'phong':
+        case 'blinn':
+          material = new THREE.MeshPhongMaterial();
+          break;
+
+        case 'lambert':
+          material = new THREE.MeshLambertMaterial();
+          break;
+
+        default:
+          material = new THREE.MeshBasicMaterial();
+          break;
+      }
+
+      material.name = data.name;
+
+      function getTexturePath(textureObject) {
+        var sampler = effect.profile.samplers[textureObject.id];
+        var surface = effect.profile.surfaces[sampler.source];
+
+        if (sampler !== undefined && surface !== null) {
+          var img = getImage(surface.init_from);
+
+          return textureLoader.path + img;
+        }
+      }
+
+      function isTexturePBR(textureObject) {
+        var p = getTexturePath(textureObject);
+        if (!p) return false;
+
+        return p.indexOf('PBR_') >= 0 || p.indexOf('PBRFULL_') >= 0;
+      }
+
+      function getTexture(textureObject) {
+        var sampler = effect.profile.samplers[textureObject.id];
+        var surface = effect.profile.surfaces[sampler.source];
+
+        if (sampler !== undefined && surface !== null) {
+          var img = getImage(surface.init_from);
+          if (img) {
+            var texture = textureLoader.load(img, textureLoadedCallback);
+
+            var extra = textureObject.extra;
+
+            if (
+              extra !== undefined &&
+              extra.technique !== undefined &&
+              isEmpty(extra.technique) === false
+            ) {
+              var technique = extra.technique;
+
+              texture.wrapS = technique.wrapU
+                ? THREE.RepeatWrapping
+                : THREE.ClampToEdgeWrapping;
+              texture.wrapT = technique.wrapV
+                ? THREE.RepeatWrapping
+                : THREE.ClampToEdgeWrapping;
+
+              texture.offset.set(
+                technique.offsetU || 0,
+                technique.offsetV || 0
+              );
+              texture.repeat.set(
+                technique.repeatU || 1,
+                technique.repeatV || 1
+              );
+            } else {
+              texture.wrapS = THREE.RepeatWrapping;
+              texture.wrapT = THREE.RepeatWrapping;
+            }
+
+            return texture;
+          }
+        }
+
+        return null;
+      }
+
+      var parameters = technique.parameters;
+      var parameter;
+      var pbrMaterial = null;
+
+      var pbrKeyToThreeJSMap = {
+        Base_Color: 'map',
+        Metallic: 'metalnessMap',
+        Roughness: 'roughnessMap',
+        Mixed_AO: 'aoMap',
+        Emissive: 'emissiveMap',
+        Normal: 'normalMap',
+        Height: 'displacementMap'
+      };
+
+      for (var key in parameters) {
+        parameter = parameters[key];
+        if (parameter.texture) {
+          var texturePath = getTexturePath(parameter.texture);
+
+          if (
+            texturePath &&
+            (texturePath.indexOf('PBR_') >= 0 ||
+              texturePath.indexOf('PBRFULL_') >= 0)
+          )
+            for (var k in pbrKeyToThreeJSMap) {
+              if (texturePath.indexOf(k) >= 0) {
+                if (pbrMaterial === null) {
+                  pbrMaterial = {};
+                }
+
+                pbrMaterial[pbrKeyToThreeJSMap[k]] = texturePath;
+
+                if (
+                  texturePath.indexOf('PBRFULL_') >= 0 &&
+                  k === 'Base_Color'
+                ) {
+                  // If the prefix is PBRFULL it means that all the PBR textures are present
+                  // but that only the basic color map have been linked to the dae.
+
+                  var allpbrkeywords = [
+                    'Metallic',
+                    'Mixed_AO',
+                    'Roughness',
+                    'Normal',
+                    'Roughness'
+                  ];
+
+                  for (var pbri in allpbrkeywords) {
+                    var pbrk = allpbrkeywords[pbri];
+                    var path = texturePath;
+
+                    path = path.replace('Base_Color', pbrk);
+                    if (pbrk === 'Normal') {
+                      // Normal map must be png. In case the Base_Color is a jpg,
+                      // change extension to png
+
+                      path = path.replace('.jpg', '.png');
+                      path = path.replace('.jpeg', '.png');
+                    }
+
+                    pbrMaterial[pbrKeyToThreeJSMap[pbrk]] = path;
+                  }
+                }
+              }
+            }
+        }
+
+        switch (key) {
+          case 'diffuse':
+            if (parameter.color) material.color.fromArray(parameter.color);
+            if (parameter.texture && !isTexturePBR(parameter.texture))
+              material.map = getTexture(parameter.texture);
+            break;
+          case 'specular':
+            if (parameter.color && material.specular)
+              material.specular.fromArray(parameter.color);
+            if (parameter.texture && !isTexturePBR(parameter.texture))
+              material.specularMap = getTexture(parameter.texture);
+            break;
+          case 'shininess':
+            if (parameter.float && material.shininess)
+              material.shininess = parameter.float;
+            break;
+          case 'emission':
+            if (parameter.color && material.emissive)
+              material.emissive.fromArray(parameter.color);
+            break;
+          case 'transparent':
+            if (parameter.texture && !isTexturePBR(parameter.texture))
+              material.alphaMap = getTexture(parameter.texture);
+            material.transparent = true;
+            break;
+          case 'transparency':
+            if (parameter.float !== undefined)
+              material.opacity = parameter.float;
+            material.transparent = true;
+            break;
+        }
+      }
+
+      if (pbrMaterial !== null) {
+        material.pbrMaterialDescription = pbrMaterial; // If PBR material has been found, store it for future usage
+      }
+
+      return material;
+    }
+
+    function getMaterial(id) {
+      return getBuild(library.materials[id], buildMaterial);
+    }
+
+    // camera
+
+    function parseCamera(xml) {
+      var data = {
+        name: xml.getAttribute('name')
+      };
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'optics':
+            data.optics = parseCameraOptics(child);
+            break;
+        }
+      }
+
+      library.cameras[xml.getAttribute('id')] = data;
+    }
+
+    function parseCameraOptics(xml) {
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        switch (child.nodeName) {
+          case 'technique_common':
+            return parseCameraTechnique(child);
+        }
+      }
+
+      return {};
+    }
+
+    function parseCameraTechnique(xml) {
+      var data = {};
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        switch (child.nodeName) {
+          case 'perspective':
+          case 'orthographic':
+            data.technique = child.nodeName;
+            data.parameters = parseCameraParameters(child);
+
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseCameraParameters(xml) {
+      var data = {};
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        switch (child.nodeName) {
+          case 'xfov':
+          case 'yfov':
+          case 'xmag':
+          case 'ymag':
+          case 'znear':
+          case 'zfar':
+          case 'aspect_ratio':
+            data[child.nodeName] = parseFloat(child.textContent);
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function buildCamera(data) {
+      var camera;
+
+      switch (data.optics.technique) {
+        case 'perspective':
+          camera = new THREE.PerspectiveCamera(
+            data.optics.parameters.yfov,
+            data.optics.parameters.aspect_ratio,
+            data.optics.parameters.znear,
+            data.optics.parameters.zfar
+          );
+          break;
+
+        case 'orthographic':
+          var ymag = data.optics.parameters.ymag;
+          var xmag = data.optics.parameters.xmag;
+          var aspectRatio = data.optics.parameters.aspect_ratio;
+
+          xmag = xmag === undefined ? ymag * aspectRatio : xmag;
+          ymag = ymag === undefined ? xmag / aspectRatio : ymag;
+
+          xmag *= 0.5;
+          ymag *= 0.5;
+
+          camera = new THREE.OrthographicCamera(
+            -xmag,
+            xmag,
+            ymag,
+            -ymag, // left, right, top, bottom
+            data.optics.parameters.znear,
+            data.optics.parameters.zfar
+          );
+          break;
+
+        default:
+          camera = new THREE.PerspectiveCamera();
+          break;
+      }
+
+      camera.name = data.name;
+
+      return camera;
+    }
+
+    function getCamera(id) {
+      return getBuild(library.cameras[id], buildCamera);
+    }
+
+    // light
+
+    function parseLight(xml) {
+      var data = {};
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'technique_common':
+            data = parseLightTechnique(child);
+            break;
+        }
+      }
+
+      library.lights[xml.getAttribute('id')] = data;
+    }
+
+    function parseLightTechnique(xml) {
+      var data = {};
 
-						case 'scale':
-							console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
-							break;
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'directional':
+          case 'point':
+          case 'spot':
+          case 'ambient':
+            data.technique = child.nodeName;
+            data.parameters = parseLightParameters(child);
+        }
+      }
+
+      return data;
+    }
+
+    function parseLightParameters(xml) {
+      var data = {};
+
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'color':
+            var array = parseFloats(child.textContent);
+            data.color = new THREE.Color().fromArray(array);
+            break;
+
+          case 'falloff_angle':
+            data.falloffAngle = parseFloat(child.textContent);
+            break;
+
+          case 'quadratic_attenuation':
+            var f = parseFloat(child.textContent);
+            data.distance = f ? Math.sqrt(1 / f) : 0;
+            break;
+        }
+      }
 
-					}
+      return data;
+    }
 
-					var keyframes = prepareAnimationData( data, defaultMatrix );
+    function buildLight(data) {
+      var light;
 
-					var animation = {
-						name: object3D.uuid,
-						keyframes: keyframes
-					};
+      switch (data.technique) {
+        case 'directional':
+          light = new THREE.DirectionalLight();
+          break;
 
-					return animation;
+        case 'point':
+          light = new THREE.PointLight();
+          break;
 
-				}
+        case 'spot':
+          light = new THREE.SpotLight();
+          break;
 
-				function prepareAnimationData( data, defaultMatrix ) {
+        case 'ambient':
+          light = new THREE.AmbientLight();
+          break;
+      }
 
-					var keyframes = [];
+      if (data.parameters.color) light.color.copy(data.parameters.color);
+      if (data.parameters.distance) light.distance = data.parameters.distance;
 
-					// transfer data into a sortable array
+      return light;
+    }
 
-					for ( var time in data ) {
+    function getLight(id) {
+      return getBuild(library.lights[id], buildLight);
+    }
 
-						keyframes.push( { time: parseFloat( time ), value: data[ time ] } );
+    // geometry
 
-					}
+    function parseGeometry(xml) {
+      var data = {
+        name: xml.getAttribute('name'),
+        sources: {},
+        vertices: {},
+        primitives: []
+      };
+
+      var mesh = getElementsByTagName(xml, 'mesh')[0];
+
+      // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep
+      if (mesh === undefined) return;
 
-					// ensure keyframes are sorted by time
+      for (var i = 0; i < mesh.childNodes.length; i++) {
+        var child = mesh.childNodes[i];
 
-					keyframes.sort( ascending );
+        if (child.nodeType !== 1) continue;
 
-					// now we clean up all animation data, so we can use them for keyframe tracks
+        var id = child.getAttribute('id');
+
+        switch (child.nodeName) {
+          case 'source':
+            data.sources[id] = parseSource(child);
+            break;
+
+          case 'vertices':
+            // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ];
+            data.vertices = parseGeometryVertices(child);
+            break;
+
+          case 'polygons':
+            console.warn(
+              'THREE.ColladaLoader: Unsupported primitive type: ',
+              child.nodeName
+            );
+            break;
+
+          case 'lines':
+          case 'linestrips':
+          case 'polylist':
+          case 'triangles':
+            data.primitives.push(parseGeometryPrimitive(child));
+            break;
+
+          default:
+            break;
+        }
+      }
+
+      library.geometries[xml.getAttribute('id')] = data;
+    }
 
-					for ( var i = 0; i < 16; i ++ ) {
+    function parseSource(xml) {
+      var data = {
+        array: [],
+        stride: 3
+      };
 
-						transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] );
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
 
-					}
+        if (child.nodeType !== 1) continue;
 
-					return keyframes;
+        switch (child.nodeName) {
+          case 'float_array':
+            data.array = parseFloats(child.textContent);
+            break;
 
-					// array sort function
+          case 'Name_array':
+            data.array = parseStrings(child.textContent);
+            break;
 
-					function ascending( a, b ) {
+          case 'technique_common':
+            var accessor = getElementsByTagName(child, 'accessor')[0];
 
-						return a.time - b.time;
+            if (accessor !== undefined) {
+              data.stride = parseInt(accessor.getAttribute('stride'));
+            }
+            break;
+        }
+      }
 
-					}
+      return data;
+    }
 
-				}
+    function parseGeometryVertices(xml) {
+      var data = {};
 
-				var position = new THREE.Vector3();
-				var scale = new THREE.Vector3();
-				var quaternion = new THREE.Quaternion();
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
 
-				function createKeyframeTracks( animation, tracks ) {
+        if (child.nodeType !== 1) continue;
 
-					var keyframes = animation.keyframes;
-					var name = animation.name;
+        data[child.getAttribute('semantic')] = parseId(
+          child.getAttribute('source')
+        );
+      }
 
-					var times = [];
-					var positionData = [];
-					var quaternionData = [];
-					var scaleData = [];
+      return data;
+    }
 
-					for ( var i = 0, l = keyframes.length; i < l; i ++ ) {
+    function parseGeometryPrimitive(xml) {
+      var primitive = {
+        type: xml.nodeName,
+        material: xml.getAttribute('material'),
+        count: parseInt(xml.getAttribute('count')),
+        inputs: {},
+        stride: 0
+      };
 
-						var keyframe = keyframes[ i ];
+      for (var i = 0, l = xml.childNodes.length; i < l; i++) {
+        var child = xml.childNodes[i];
 
-						var time = keyframe.time;
-						var value = keyframe.value;
+        if (child.nodeType !== 1) continue;
 
-						matrix.fromArray( value ).transpose();
-						matrix.decompose( position, quaternion, scale );
+        switch (child.nodeName) {
+          case 'input':
+            var id = parseId(child.getAttribute('source'));
+            var semantic = child.getAttribute('semantic');
+            var offset = parseInt(child.getAttribute('offset'));
+            primitive.inputs[semantic] = { id: id, offset: offset };
+            primitive.stride = Math.max(primitive.stride, offset + 1);
+            break;
 
-						times.push( time );
-						positionData.push( position.x, position.y, position.z );
-						quaternionData.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w );
-						scaleData.push( scale.x, scale.y, scale.z );
+          case 'vcount':
+            primitive.vcount = parseInts(child.textContent);
+            break;
 
-					}
+          case 'p':
+            primitive.p = parseInts(child.textContent);
+            break;
+        }
+      }
 
-					if ( positionData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.position', times, positionData ) );
-					if ( quaternionData.length > 0 ) tracks.push( new THREE.QuaternionKeyframeTrack( name + '.quaternion', times, quaternionData ) );
-					if ( scaleData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.scale', times, scaleData ) );
+      return primitive;
+    }
 
-					return tracks;
+    function groupPrimitives(primitives) {
+      var build = {};
 
-				}
+      for (var i = 0; i < primitives.length; i++) {
+        var primitive = primitives[i];
 
-				function transformAnimationData( keyframes, property, defaultValue ) {
+        if (build[primitive.type] === undefined) build[primitive.type] = [];
 
-					var keyframe;
+        build[primitive.type].push(primitive);
+      }
 
-					var empty = true;
-					var i, l;
+      return build;
+    }
 
-					// check, if values of a property are missing in our keyframes
+    function buildGeometry(data) {
+      var build = {};
 
-					for ( i = 0, l = keyframes.length; i < l; i ++ ) {
+      var sources = data.sources;
+      var vertices = data.vertices;
+      var primitives = data.primitives;
 
-						keyframe = keyframes[ i ];
+      if (primitives.length === 0) return {};
 
-						if ( keyframe.value[ property ] === undefined ) {
+      // our goal is to create one buffer geoemtry for a single type of primitives
+      // first, we group all primitives by their type
+
+      var groupedPrimitives = groupPrimitives(primitives);
+
+      for (var type in groupedPrimitives) {
+        // second, we create for each type of primitives (polylist,triangles or lines) a buffer geometry
+
+        build[type] = buildGeometryType(
+          groupedPrimitives[type],
+          sources,
+          vertices
+        );
+      }
+
+      return build;
+    }
+
+    function buildGeometryType(primitives, sources, vertices) {
+      var build = {};
+
+      var position = { array: [], stride: 0 };
+      var normal = { array: [], stride: 0 };
+      var uv = { array: [], stride: 0 };
+      var color = { array: [], stride: 0 };
+
+      var skinIndex = { array: [], stride: 4 };
+      var skinWeight = { array: [], stride: 4 };
+
+      var geometry = new THREE.BufferGeometry();
+
+      var materialKeys = [];
+
+      var start = 0,
+        count = 0;
+
+      for (var p = 0; p < primitives.length; p++) {
+        var primitive = primitives[p];
+        var inputs = primitive.inputs;
+        var triangleCount = 1;
+
+        if (primitive.vcount && primitive.vcount[0] === 4) {
+          triangleCount = 2; // one quad -> two triangles
+        }
+
+        // groups
+
+        if (primitive.type === 'lines' || primitive.type === 'linestrips') {
+          count = primitive.count * 2;
+        } else {
+          count = primitive.count * 3 * triangleCount;
+        }
+
+        geometry.addGroup(start, count, p);
+        start += count;
+
+        // material
+
+        if (primitive.material) {
+          materialKeys.push(primitive.material);
+        }
+
+        // geometry data
+
+        for (var name in inputs) {
+          var input = inputs[name];
+
+          switch (name) {
+            case 'VERTEX':
+              for (var key in vertices) {
+                var id = vertices[key];
+
+                switch (key) {
+                  case 'POSITION':
+                    buildGeometryData(
+                      primitive,
+                      sources[id],
+                      input.offset,
+                      position.array
+                    );
+                    position.stride = sources[id].stride;
+
+                    if (sources.skinWeights && sources.skinIndices) {
+                      buildGeometryData(
+                        primitive,
+                        sources.skinIndices,
+                        input.offset,
+                        skinIndex.array
+                      );
+                      buildGeometryData(
+                        primitive,
+                        sources.skinWeights,
+                        input.offset,
+                        skinWeight.array
+                      );
+                    }
+                    break;
+
+                  case 'NORMAL':
+                    buildGeometryData(
+                      primitive,
+                      sources[id],
+                      input.offset,
+                      normal.array
+                    );
+                    normal.stride = sources[id].stride;
+                    break;
+
+                  case 'COLOR':
+                    buildGeometryData(
+                      primitive,
+                      sources[id],
+                      input.offset,
+                      color.array
+                    );
+                    color.stride = sources[id].stride;
+                    break;
+
+                  case 'TEXCOORD':
+                    buildGeometryData(
+                      primitive,
+                      sources[id],
+                      input.offset,
+                      uv.array
+                    );
+                    uv.stride = sources[id].stride;
+                    break;
+
+                  default:
+                    console.warn(
+                      'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.',
+                      key
+                    );
+                }
+              }
+              break;
+
+            case 'NORMAL':
+              buildGeometryData(
+                primitive,
+                sources[input.id],
+                input.offset,
+                normal.array
+              );
+              normal.stride = sources[input.id].stride;
+              break;
+
+            case 'COLOR':
+              buildGeometryData(
+                primitive,
+                sources[input.id],
+                input.offset,
+                color.array
+              );
+              color.stride = sources[input.id].stride;
+              break;
+
+            case 'TEXCOORD':
+              buildGeometryData(
+                primitive,
+                sources[input.id],
+                input.offset,
+                uv.array
+              );
+              uv.stride = sources[input.id].stride;
+              break;
+          }
+        }
+      }
+
+      // build geometry
+
+      if (position.array.length > 0)
+        geometry.addAttribute(
+          'position',
+          new THREE.Float32BufferAttribute(position.array, position.stride)
+        );
+      if (normal.array.length > 0)
+        geometry.addAttribute(
+          'normal',
+          new THREE.Float32BufferAttribute(normal.array, normal.stride)
+        );
+      if (color.array.length > 0)
+        geometry.addAttribute(
+          'color',
+          new THREE.Float32BufferAttribute(color.array, color.stride)
+        );
+      if (uv.array.length > 0)
+        geometry.addAttribute(
+          'uv',
+          new THREE.Float32BufferAttribute(uv.array, uv.stride)
+        );
+
+      if (skinIndex.array.length > 0)
+        geometry.addAttribute(
+          'skinIndex',
+          new THREE.Float32BufferAttribute(skinIndex.array, skinIndex.stride)
+        );
+      if (skinWeight.array.length > 0)
+        geometry.addAttribute(
+          'skinWeight',
+          new THREE.Float32BufferAttribute(skinWeight.array, skinWeight.stride)
+        );
+
+      build.data = geometry;
+      build.type = primitives[0].type;
+      build.materialKeys = materialKeys;
+
+      return build;
+    }
+
+    function buildGeometryData(primitive, source, offset, array) {
+      var indices = primitive.p;
+      var stride = primitive.stride;
+      var vcount = primitive.vcount;
+
+      function pushVector(i) {
+        var index = indices[i + offset] * sourceStride;
+        var length = index + sourceStride;
+
+        for (; index < length; index++) {
+          array.push(sourceArray[index]);
+        }
+      }
+
+      var maxcount = 0;
+
+      var sourceArray = source.array;
+      var sourceStride = source.stride;
+
+      if (primitive.vcount !== undefined) {
+        var index = 0;
+
+        for (var i = 0, l = vcount.length; i < l; i++) {
+          var count = vcount[i];
+
+          if (count === 4) {
+            var a = index + stride * 0;
+            var b = index + stride * 1;
+            var c = index + stride * 2;
+            var d = index + stride * 3;
+
+            pushVector(a);
+            pushVector(b);
+            pushVector(d);
+            pushVector(b);
+            pushVector(c);
+            pushVector(d);
+          } else if (count === 3) {
+            var a = index + stride * 0;
+            var b = index + stride * 1;
+            var c = index + stride * 2;
+
+            pushVector(a);
+            pushVector(b);
+            pushVector(c);
+          } else {
+            maxcount = Math.max(maxcount, count);
+          }
+
+          index += stride * count;
+        }
+
+        if (maxcount > 0) {
+          console.log(
+            'THREE.ColladaLoader: Geometry has faces with more than 4 vertices.'
+          );
+        }
+      } else {
+        for (var i = 0, l = indices.length; i < l; i += stride) {
+          pushVector(i);
+        }
+      }
+    }
+
+    function getGeometry(id) {
+      return getBuild(library.geometries[id], buildGeometry);
+    }
+
+    // kinematics
+
+    function parseKinematicsModel(xml) {
+      var data = {
+        name: xml.getAttribute('name') || '',
+        joints: {},
+        links: []
+      };
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'technique_common':
+            parseKinematicsTechniqueCommon(child, data);
+            break;
+        }
+      }
+
+      library.kinematicsModels[xml.getAttribute('id')] = data;
+    }
+
+    function buildKinematicsModel(data) {
+      if (data.build !== undefined) return data.build;
+
+      return data;
+    }
+
+    function getKinematicsModel(id) {
+      return getBuild(library.kinematicsModels[id], buildKinematicsModel);
+    }
+
+    function parseKinematicsTechniqueCommon(xml, data) {
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'joint':
+            data.joints[child.getAttribute('sid')] = parseKinematicsJoint(
+              child
+            );
+            break;
+
+          case 'link':
+            data.links.push(parseKinematicsLink(child));
+            break;
+        }
+      }
+    }
+
+    function parseKinematicsJoint(xml) {
+      var data;
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'prismatic':
+          case 'revolute':
+            data = parseKinematicsJointParameter(child);
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseKinematicsJointParameter(xml, data) {
+      var data = {
+        sid: xml.getAttribute('sid'),
+        name: xml.getAttribute('name') || '',
+        axis: new THREE.Vector3(),
+        limits: {
+          min: 0,
+          max: 0
+        },
+        type: xml.nodeName,
+        static: false,
+        zeroPosition: 0,
+        middlePosition: 0
+      };
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'axis':
+            var array = parseFloats(child.textContent);
+            data.axis.fromArray(array);
+            break;
+          case 'limits':
+            var max = child.getElementsByTagName('max')[0];
+            var min = child.getElementsByTagName('min')[0];
+
+            data.limits.max = parseFloat(max.textContent);
+            data.limits.min = parseFloat(min.textContent);
+            break;
+        }
+      }
+
+      // if min is equal to or greater than max, consider the joint static
+
+      if (data.limits.min >= data.limits.max) {
+        data.static = true;
+      }
+
+      // calculate middle position
+
+      data.middlePosition = (data.limits.min + data.limits.max) / 2.0;
+
+      return data;
+    }
+
+    function parseKinematicsLink(xml) {
+      var data = {
+        sid: xml.getAttribute('sid'),
+        name: xml.getAttribute('name') || '',
+        attachments: [],
+        transforms: []
+      };
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'attachment_full':
+            data.attachments.push(parseKinematicsAttachment(child));
+            break;
+
+          case 'matrix':
+          case 'translate':
+          case 'rotate':
+            data.transforms.push(parseKinematicsTransform(child));
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseKinematicsAttachment(xml) {
+      var data = {
+        joint: xml
+          .getAttribute('joint')
+          .split('/')
+          .pop(),
+        transforms: [],
+        links: []
+      };
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'link':
+            data.links.push(parseKinematicsLink(child));
+            break;
+
+          case 'matrix':
+          case 'translate':
+          case 'rotate':
+            data.transforms.push(parseKinematicsTransform(child));
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function parseKinematicsTransform(xml) {
+      var data = {
+        type: xml.nodeName
+      };
+
+      var array = parseFloats(xml.textContent);
+
+      switch (data.type) {
+        case 'matrix':
+          data.obj = new THREE.Matrix4();
+          data.obj.fromArray(array).transpose();
+          break;
+
+        case 'translate':
+          data.obj = new THREE.Vector3();
+          data.obj.fromArray(array);
+          break;
+
+        case 'rotate':
+          data.obj = new THREE.Vector3();
+          data.obj.fromArray(array);
+          data.angle = THREE.Math.degToRad(array[3]);
+          break;
+      }
+
+      return data;
+    }
+
+    function parseKinematicsScene(xml) {
+      var data = {
+        bindJointAxis: []
+      };
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'bind_joint_axis':
+            data.bindJointAxis.push(parseKinematicsBindJointAxis(child));
+            break;
+        }
+      }
+
+      library.kinematicsScenes[parseId(xml.getAttribute('url'))] = data;
+    }
+
+    function parseKinematicsBindJointAxis(xml) {
+      var data = {
+        target: xml
+          .getAttribute('target')
+          .split('/')
+          .pop()
+      };
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'axis':
+            var param = child.getElementsByTagName('param')[0];
+            data.axis = param.textContent;
+            var tmpJointIndex = data.axis
+              .split('inst_')
+              .pop()
+              .split('axis')[0];
+            data.jointIndex = tmpJointIndex.substr(0, tmpJointIndex.length - 1);
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function buildKinematicsScene(data) {
+      if (data.build !== undefined) return data.build;
+
+      return data;
+    }
+
+    function getKinematicsScene(id) {
+      return getBuild(library.kinematicsScenes[id], buildKinematicsScene);
+    }
+
+    function setupKinematics() {
+      var kinematicsModelId = Object.keys(library.kinematicsModels)[0];
+      var kinematicsSceneId = Object.keys(library.kinematicsScenes)[0];
+      var visualSceneId = Object.keys(library.visualScenes)[0];
+
+      if (kinematicsModelId === undefined || kinematicsSceneId === undefined)
+        return;
+
+      var kinematicsModel = getKinematicsModel(kinematicsModelId);
+      var kinematicsScene = getKinematicsScene(kinematicsSceneId);
+      var visualScene = getVisualScene(visualSceneId);
+
+      var bindJointAxis = kinematicsScene.bindJointAxis;
+      var jointMap = {};
+
+      for (var i = 0, l = bindJointAxis.length; i < l; i++) {
+        var axis = bindJointAxis[i];
+
+        // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix'
+
+        var targetElement = collada.querySelector(
+          '[sid="' + axis.target + '"]'
+        );
+
+        if (targetElement) {
+          // get the parent of the transfrom element
+
+          var parentVisualElement = targetElement.parentElement;
+
+          // connect the joint of the kinematics model with the element in the visual scene
+
+          connect(axis.jointIndex, parentVisualElement);
+        }
+      }
+
+      function connect(jointIndex, visualElement) {
+        var visualElementName = visualElement.getAttribute('name');
+        var joint = kinematicsModel.joints[jointIndex];
+
+        visualScene.traverse(function(object) {
+          if (object.name === visualElementName) {
+            jointMap[jointIndex] = {
+              object: object,
+              transforms: buildTransformList(visualElement),
+              joint: joint,
+              position: joint.zeroPosition
+            };
+          }
+        });
+      }
+
+      var m0 = new THREE.Matrix4();
+
+      kinematics = {
+        joints: kinematicsModel && kinematicsModel.joints,
+
+        getJointValue: function(jointIndex) {
+          var jointData = jointMap[jointIndex];
+
+          if (jointData) {
+            return jointData.position;
+          } else {
+            console.warn(
+              'THREE.ColladaLoader: Joint ' + jointIndex + " doesn't exist."
+            );
+          }
+        },
+
+        setJointValue: function(jointIndex, value) {
+          var jointData = jointMap[jointIndex];
+
+          if (jointData) {
+            var joint = jointData.joint;
+
+            if (value > joint.limits.max || value < joint.limits.min) {
+              console.warn(
+                'THREE.ColladaLoader: Joint ' +
+                  jointIndex +
+                  ' value ' +
+                  value +
+                  ' outside of limits (min: ' +
+                  joint.limits.min +
+                  ', max: ' +
+                  joint.limits.max +
+                  ').'
+              );
+            } else if (joint.static) {
+              console.warn(
+                'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.'
+              );
+            } else {
+              var object = jointData.object;
+              var axis = joint.axis;
+              var transforms = jointData.transforms;
+
+              matrix.identity();
+
+              // each update, we have to apply all transforms in the correct order
+
+              for (var i = 0; i < transforms.length; i++) {
+                var transform = transforms[i];
+
+                // if there is a connection of the transform node with a joint, apply the joint value
+
+                if (transform.sid && transform.sid.indexOf(jointIndex) !== -1) {
+                  switch (joint.type) {
+                    case 'revolute':
+                      matrix.multiply(
+                        m0.makeRotationAxis(axis, THREE.Math.degToRad(value))
+                      );
+                      break;
+
+                    case 'prismatic':
+                      matrix.multiply(
+                        m0.makeTranslation(
+                          axis.x * value,
+                          axis.y * value,
+                          axis.z * value
+                        )
+                      );
+                      break;
+
+                    default:
+                      console.warn(
+                        'THREE.ColladaLoader: Unknown joint type: ' + joint.type
+                      );
+                      break;
+                  }
+                } else {
+                  switch (transform.type) {
+                    case 'matrix':
+                      matrix.multiply(transform.obj);
+                      break;
+
+                    case 'translate':
+                      matrix.multiply(
+                        m0.makeTranslation(
+                          transform.obj.x,
+                          transform.obj.y,
+                          transform.obj.z
+                        )
+                      );
+                      break;
+
+                    case 'scale':
+                      matrix.scale(transform.obj);
+                      break;
+
+                    case 'rotate':
+                      matrix.multiply(
+                        m0.makeRotationAxis(transform.obj, transform.angle)
+                      );
+                      break;
+                  }
+                }
+              }
+
+              object.matrix.copy(matrix);
+              object.matrix.decompose(
+                object.position,
+                object.quaternion,
+                object.scale
+              );
+
+              jointMap[jointIndex].position = value;
+            }
+          } else {
+            console.log(
+              'THREE.ColladaLoader: ' + jointIndex + ' does not exist.'
+            );
+          }
+        }
+      };
+    }
+
+    function buildTransformList(node) {
+      var transforms = [];
+
+      var xml = collada.querySelector('[id="' + node.id + '"]');
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'matrix':
+            var array = parseFloats(child.textContent);
+            var matrix = new THREE.Matrix4().fromArray(array).transpose();
+            transforms.push({
+              sid: child.getAttribute('sid'),
+              type: child.nodeName,
+              obj: matrix
+            });
+            break;
+
+          case 'translate':
+          case 'scale':
+            var array = parseFloats(child.textContent);
+            var vector = new THREE.Vector3().fromArray(array);
+            transforms.push({
+              sid: child.getAttribute('sid'),
+              type: child.nodeName,
+              obj: vector
+            });
+            break;
+
+          case 'rotate':
+            var array = parseFloats(child.textContent);
+            var vector = new THREE.Vector3().fromArray(array);
+            var angle = THREE.Math.degToRad(array[3]);
+            transforms.push({
+              sid: child.getAttribute('sid'),
+              type: child.nodeName,
+              obj: vector,
+              angle: angle
+            });
+            break;
+        }
+      }
+
+      return transforms;
+    }
+
+    // nodes
+
+    function prepareNodes(xml) {
+      var elements = xml.getElementsByTagName('node');
+
+      // ensure all node elements have id attributes
+
+      for (var i = 0; i < elements.length; i++) {
+        var element = elements[i];
+
+        if (element.hasAttribute('id') === false) {
+          element.setAttribute('id', generateId());
+        }
+      }
+    }
+
+    var matrix = new THREE.Matrix4();
+    var vector = new THREE.Vector3();
+
+    function parseNode(xml) {
+      var data = {
+        name: xml.getAttribute('name') || '',
+        type: xml.getAttribute('type'),
+        id: xml.getAttribute('id'),
+        sid: xml.getAttribute('sid'),
+        matrix: new THREE.Matrix4(),
+        nodes: [],
+        instanceCameras: [],
+        instanceControllers: [],
+        instanceLights: [],
+        instanceGeometries: [],
+        instanceNodes: [],
+        transforms: {}
+      };
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        if (child.nodeType !== 1) continue;
+
+        switch (child.nodeName) {
+          case 'node':
+            data.nodes.push(child.getAttribute('id'));
+            parseNode(child);
+            break;
+
+          case 'instance_camera':
+            data.instanceCameras.push(parseId(child.getAttribute('url')));
+            break;
+
+          case 'instance_controller':
+            data.instanceControllers.push(parseNodeInstance(child));
+            break;
+
+          case 'instance_light':
+            data.instanceLights.push(parseId(child.getAttribute('url')));
+            break;
+
+          case 'instance_geometry':
+            data.instanceGeometries.push(parseNodeInstance(child));
+            break;
+
+          case 'instance_node':
+            data.instanceNodes.push(parseId(child.getAttribute('url')));
+            break;
+
+          case 'matrix':
+            var array = parseFloats(child.textContent);
+            data.matrix.multiply(matrix.fromArray(array).transpose());
+            data.transforms[child.getAttribute('sid')] = child.nodeName;
+            break;
+
+          case 'translate':
+            var array = parseFloats(child.textContent);
+            vector.fromArray(array);
+            data.matrix.multiply(
+              matrix.makeTranslation(vector.x, vector.y, vector.z)
+            );
+            data.transforms[child.getAttribute('sid')] = child.nodeName;
+            break;
+
+          case 'rotate':
+            var array = parseFloats(child.textContent);
+            var angle = THREE.Math.degToRad(array[3]);
+            data.matrix.multiply(
+              matrix.makeRotationAxis(vector.fromArray(array), angle)
+            );
+            data.transforms[child.getAttribute('sid')] = child.nodeName;
+            break;
+
+          case 'scale':
+            var array = parseFloats(child.textContent);
+            data.matrix.scale(vector.fromArray(array));
+            data.transforms[child.getAttribute('sid')] = child.nodeName;
+            break;
+
+          case 'extra':
+            break;
+
+          default:
+            break;
+        }
+      }
+
+      library.nodes[data.id] = data;
+
+      return data;
+    }
+
+    function parseNodeInstance(xml) {
+      var data = {
+        id: parseId(xml.getAttribute('url')),
+        materials: {},
+        skeletons: []
+      };
+
+      for (var i = 0; i < xml.childNodes.length; i++) {
+        var child = xml.childNodes[i];
+
+        switch (child.nodeName) {
+          case 'bind_material':
+            var instances = child.getElementsByTagName('instance_material');
+
+            for (var j = 0; j < instances.length; j++) {
+              var instance = instances[j];
+              var symbol = instance.getAttribute('symbol');
+              var target = instance.getAttribute('target');
+
+              data.materials[symbol] = parseId(target);
+            }
+
+            break;
 
-							keyframe.value[ property ] = null; // mark as missing
+          case 'skeleton':
+            data.skeletons.push(parseId(child.textContent));
+            break;
 
-						} else {
+          default:
+            break;
+        }
+      }
+
+      return data;
+    }
+
+    function buildSkeleton(skeletons, joints) {
+      var boneData = [];
+      var sortedBoneData = [];
+
+      var i, j, data;
+
+      // a skeleton can have multiple root bones. collada expresses this
+      // situtation with multiple "skeleton" tags per controller instance
+
+      for (i = 0; i < skeletons.length; i++) {
+        var skeleton = skeletons[i];
+        var root = getNode(skeleton);
+
+        // setup bone data for a single bone hierarchy
+
+        buildBoneHierarchy(root, joints, boneData);
+      }
+
+      // sort bone data (the order is defined in the corresponding controller)
+
+      for (i = 0; i < joints.length; i++) {
+        for (j = 0; j < boneData.length; j++) {
+          data = boneData[j];
+
+          if (data.bone.name === joints[i].name) {
+            sortedBoneData[i] = data;
+            data.processed = true;
+            break;
+          }
+        }
+      }
 
-							empty = false;
+      // add unprocessed bone data at the end of the list
 
-						}
+      for (i = 0; i < boneData.length; i++) {
+        data = boneData[i];
 
-					}
+        if (data.processed === false) {
+          sortedBoneData.push(data);
+          data.processed = true;
+        }
+      }
 
-					if ( empty === true ) {
+      // setup arrays for skeleton creation
 
-						// no values at all, so we set a default value
+      var bones = [];
+      var boneInverses = [];
 
-						for ( i = 0, l = keyframes.length; i < l; i ++ ) {
+      for (i = 0; i < sortedBoneData.length; i++) {
+        data = sortedBoneData[i];
 
-							keyframe = keyframes[ i ];
+        bones.push(data.bone);
+        boneInverses.push(data.boneInverse);
+      }
 
-							keyframe.value[ property ] = defaultValue;
+      return new THREE.Skeleton(bones, boneInverses);
+    }
 
-						}
+    function buildBoneHierarchy(root, joints, boneData) {
+      if (root === undefined) {
+        console.log('buildBoneHierarchy: Undefined root node given, return');
+        return;
+      }
 
-					} else {
+      // setup bone data from visual scene
 
-						// filling gaps
+      root.traverse(function(object) {
+        if (object.isBone === true) {
+          var boneInverse;
 
-						createMissingKeyframes( keyframes, property );
+          // retrieve the boneInverse from the controller data
 
-					}
+          for (var i = 0; i < joints.length; i++) {
+            var joint = joints[i];
 
-				}
+            if (joint.name === object.name) {
+              boneInverse = joint.boneInverse;
+              break;
+            }
+          }
 
-				function createMissingKeyframes( keyframes, property ) {
+          if (boneInverse === undefined) {
+            // Unfortunately, there can be joints in the visual scene that are not part of the
+            // corresponding controller. In this case, we have to create a dummy boneInverse matrix
+            // for the respective bone. This bone won't affect any vertices, because there are no skin indices
+            // and weights defined for it. But we still have to add the bone to the sorted bone list in order to
+            // ensure a correct animation of the model.
 
-					var prev, next;
+            boneInverse = new THREE.Matrix4();
+          }
 
-					for ( var i = 0, l = keyframes.length; i < l; i ++ ) {
+          boneData.push({
+            bone: object,
+            boneInverse: boneInverse,
+            processed: false
+          });
+        }
+      });
+    }
 
-						var keyframe = keyframes[ i ];
+    function buildNode(data) {
+      var objects = [];
 
-						if ( keyframe.value[ property ] === null ) {
+      var matrix = data.matrix;
+      var nodes = data.nodes;
+      var type = data.type;
+      var instanceCameras = data.instanceCameras;
+      var instanceControllers = data.instanceControllers;
+      var instanceLights = data.instanceLights;
+      var instanceGeometries = data.instanceGeometries;
+      var instanceNodes = data.instanceNodes;
 
-							prev = getPrev( keyframes, i, property );
-							next = getNext( keyframes, i, property );
+      // nodes
 
-							if ( prev === null ) {
+      for (var i = 0, l = nodes.length; i < l; i++) {
+        objects.push(getNode(nodes[i]));
+      }
 
-								keyframe.value[ property ] = next.value[ property ];
-								continue;
+      // instance cameras
 
-							}
+      for (var i = 0, l = instanceCameras.length; i < l; i++) {
+        objects.push(getCamera(instanceCameras[i]).clone());
+      }
 
-							if ( next === null ) {
+      // instance controllers
 
-								keyframe.value[ property ] = prev.value[ property ];
-								continue;
+      for (var i = 0, l = instanceControllers.length; i < l; i++) {
+        var instance = instanceControllers[i];
+        var controller = getController(instance.id);
+        var geometries = getGeometry(controller.id);
+        var newObjects = buildObjects(geometries, instance.materials);
 
-							}
+        var skeletons = instance.skeletons;
+        var joints = controller.skin.joints;
 
-							interpolate( keyframe, prev, next, property );
+        var skeleton = buildSkeleton(skeletons, joints);
 
-						}
+        for (var j = 0, jl = newObjects.length; j < jl; j++) {
+          var object = newObjects[j];
 
-					}
+          if (object.isSkinnedMesh) {
+            object.bind(skeleton, controller.skin.bindMatrix);
+            object.normalizeSkinWeights();
+          }
 
-				}
+          objects.push(object);
+        }
+      }
 
-				function getPrev( keyframes, i, property ) {
+      // instance lights
 
-					while ( i >= 0 ) {
+      for (var i = 0, l = instanceLights.length; i < l; i++) {
+        objects.push(getLight(instanceLights[i]).clone());
+      }
 
-						var keyframe = keyframes[ i ];
+      // instance geometries
 
-						if ( keyframe.value[ property ] !== null ) return keyframe;
+      for (var i = 0, l = instanceGeometries.length; i < l; i++) {
+        var instance = instanceGeometries[i];
 
-						i --;
+        // a single geometry instance in collada can lead to multiple object3Ds.
+        // this is the case when primitives are combined like triangles and lines
 
-					}
+        var geometries = getGeometry(instance.id);
+        var newObjects = buildObjects(geometries, instance.materials);
 
-					return null;
+        for (var j = 0, jl = newObjects.length; j < jl; j++) {
+          objects.push(newObjects[j]);
+        }
+      }
 
-				}
+      // instance nodes
 
-				function getNext( keyframes, i, property ) {
+      for (var i = 0, l = instanceNodes.length; i < l; i++) {
+        objects.push(getNode(instanceNodes[i]).clone());
+      }
 
-					while ( i < keyframes.length ) {
+      var object;
 
-						var keyframe = keyframes[ i ];
+      if (nodes.length === 0 && objects.length === 1) {
+        object = objects[0];
+      } else {
+        object = type === 'JOINT' ? new THREE.Bone() : new THREE.Group();
 
-						if ( keyframe.value[ property ] !== null ) return keyframe;
+        for (var i = 0; i < objects.length; i++) {
+          object.add(objects[i]);
+        }
+      }
 
-						i ++;
+      object.name = type === 'JOINT' ? data.sid : data.name;
+      object.matrix.copy(matrix);
+      object.matrix.decompose(object.position, object.quaternion, object.scale);
 
-					}
+      return object;
+    }
 
-					return null;
+    function resolveMaterialBinding(keys, instanceMaterials) {
+      var materials = [];
 
-				}
+      for (var i = 0, l = keys.length; i < l; i++) {
+        var id = instanceMaterials[keys[i]];
+        materials.push(getMaterial(id));
+      }
 
-				function interpolate( key, prev, next, property ) {
+      return materials;
+    }
 
-					if ( ( next.time - prev.time ) === 0 ) {
+    function buildObjects(geometries, instanceMaterials) {
+      var objects = [];
 
-						key.value[ property ] = prev.value[ property ];
-						return;
+      for (var type in geometries) {
+        var geometry = geometries[type];
 
-					}
+        var materials = resolveMaterialBinding(
+          geometry.materialKeys,
+          instanceMaterials
+        );
 
-					key.value[ property ] = ( ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) ) + prev.value[ property ];
+        // handle case if no materials are defined
 
-				}
+        if (materials.length === 0) {
+          if (type === 'lines' || type === 'linestrips') {
+            materials.push(new THREE.LineBasicMaterial());
+          } else {
+            materials.push(new THREE.MeshPhongMaterial());
+          }
+        }
 
-				// animation clips
+        // regard skinning
 
-				function parseAnimationClip( xml ) {
+        var skinning = geometry.data.attributes.skinIndex !== undefined;
 
-					var data = {
-						name: xml.getAttribute( 'id' ) || 'default',
-						start: parseFloat( xml.getAttribute( 'start' ) || 0 ),
-						end: parseFloat( xml.getAttribute( 'end' ) || 0 ),
-						animations: []
-					};
+        if (skinning) {
+          for (var i = 0, l = materials.length; i < l; i++) {
+            materials[i].skinning = true;
+          }
+        }
 
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+        // choose between a single or multi materials (material array)
 
-						var child = xml.childNodes[ i ];
+        var material = materials.length === 1 ? materials[0] : materials;
 
-						if ( child.nodeType !== 1 ) continue;
+        // now create a specific 3D object
 
-						switch ( child.nodeName ) {
+        var object;
 
-							case 'instance_animation':
-								data.animations.push( parseId( child.getAttribute( 'url' ) ) );
-								break;
+        switch (type) {
+          case 'lines':
+            object = new THREE.LineSegments(geometry.data, material);
+            break;
 
-						}
+          case 'linestrips':
+            object = new THREE.Line(geometry.data, material);
+            break;
 
-					}
+          case 'triangles':
+          case 'polylist':
+            if (skinning) {
+              object = new THREE.SkinnedMesh(geometry.data, material);
+            } else {
+              object = new THREE.Mesh(geometry.data, material);
+            }
+            break;
+        }
 
-					library.clips[ xml.getAttribute( 'id' ) ] = data;
+        objects.push(object);
+      }
 
-				}
+      return objects;
+    }
 
-				function buildAnimationClip( data ) {
+    function getNode(id) {
+      return getBuild(library.nodes[id], buildNode);
+    }
 
-					var tracks = [];
+    // visual scenes
 
-					var name = data.name;
-					var duration = ( data.end - data.start ) || - 1;
-					var animations = data.animations;
+    function parseVisualScene(xml) {
+      var data = {
+        name: xml.getAttribute('name'),
+        children: []
+      };
 
-					for ( var i = 0, il = animations.length; i < il; i ++ ) {
+      prepareNodes(xml);
 
-						var animationTracks = getAnimation( animations[ i ] );
+      var elements = getElementsByTagName(xml, 'node');
 
-						for ( var j = 0, jl = animationTracks.length; j < jl; j ++ ) {
+      for (var i = 0; i < elements.length; i++) {
+        data.children.push(parseNode(elements[i]));
+      }
 
-							tracks.push( animationTracks[ j ] );
+      library.visualScenes[xml.getAttribute('id')] = data;
+    }
 
-						}
+    function buildVisualScene(data) {
+      var group = new THREE.Group();
+      group.name = data.name;
 
-					}
+      var children = data.children;
 
-					return new THREE.AnimationClip( name, duration, tracks );
+      for (var i = 0; i < children.length; i++) {
+        var child = children[i];
 
-				}
+        if (child.id === null) {
+          group.add(buildNode(child));
+        } else {
+          // if there is an ID, let's try to get the finished build (e.g. joints are already build)
 
-				function getAnimationClip( id ) {
+          group.add(getNode(child.id));
+        }
+      }
 
-					return getBuild( library.clips[ id ], buildAnimationClip );
+      return group;
+    }
 
-				}
+    function getVisualScene(id) {
+      return getBuild(library.visualScenes[id], buildVisualScene);
+    }
 
-				// controller
+    // scenes
 
-				function parseController( xml ) {
+    function parseScene(xml) {
+      var instance = getElementsByTagName(xml, 'instance_visual_scene')[0];
+      return getVisualScene(parseId(instance.getAttribute('url')));
+    }
 
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'skin':
-								// there is exactly one skin per controller
-								data.id = parseId( child.getAttribute( 'source' ) );
-								data.skin = parseSkin( child );
-								break;
-
-							case 'morph':
-								data.id = parseId( child.getAttribute( 'source' ) );
-								console.warn( 'THREE.ColladaLoader: Morph target animation not supported yet.' );
-								break;
-
-						}
-
-					}
-
-					library.controllers[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function parseSkin( xml ) {
-
-					var data = {
-						sources: {}
-					};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'bind_shape_matrix':
-								data.bindShapeMatrix = parseFloats( child.textContent );
-								break;
-
-							case 'source':
-								var id = child.getAttribute( 'id' );
-								data.sources[ id ] = parseSource( child );
-								break;
-
-							case 'joints':
-								data.joints = parseJoints( child );
-								break;
-
-							case 'vertex_weights':
-								data.vertexWeights = parseVertexWeights( child );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseJoints( xml ) {
-
-					var data = {
-						inputs: {}
-					};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'input':
-								var semantic = child.getAttribute( 'semantic' );
-								var id = parseId( child.getAttribute( 'source' ) );
-								data.inputs[ semantic ] = id;
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseVertexWeights( xml ) {
-
-					var data = {
-						inputs: {}
-					};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'input':
-								var semantic = child.getAttribute( 'semantic' );
-								var id = parseId( child.getAttribute( 'source' ) );
-								var offset = parseInt( child.getAttribute( 'offset' ) );
-								data.inputs[ semantic ] = { id: id, offset: offset };
-								break;
-
-							case 'vcount':
-								data.vcount = parseInts( child.textContent );
-								break;
-
-							case 'v':
-								data.v = parseInts( child.textContent );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function buildController( data ) {
-
-					var build = {
-						id: data.id
-					};
-
-					var geometry = library.geometries[ build.id ];
-
-					if ( data.skin !== undefined ) {
-
-						build.skin = buildSkin( data.skin );
-
-						// we enhance the 'sources' property of the corresponding geometry with our skin data
-
-						geometry.sources.skinIndices = build.skin.indices;
-						geometry.sources.skinWeights = build.skin.weights;
-
-					}
-
-					return build;
-
-				}
-
-				function buildSkin( data ) {
-
-					var BONE_LIMIT = 4;
-
-					var build = {
-						joints: [], // this must be an array to preserve the joint order
-						indices: {
-							array: [],
-							stride: BONE_LIMIT
-						},
-						weights: {
-							array: [],
-							stride: BONE_LIMIT
-						}
-					};
-
-					var sources = data.sources;
-					var vertexWeights = data.vertexWeights;
-
-					var vcount = vertexWeights.vcount;
-					var v = vertexWeights.v;
-					var jointOffset = vertexWeights.inputs.JOINT.offset;
-					var weightOffset = vertexWeights.inputs.WEIGHT.offset;
-
-					var jointSource = data.sources[ data.joints.inputs.JOINT ];
-					var inverseSource = data.sources[ data.joints.inputs.INV_BIND_MATRIX ];
-
-					var weights = sources[ vertexWeights.inputs.WEIGHT.id ].array;
-					var stride = 0;
-
-					var i, j, l;
-
-					// procces skin data for each vertex
-
-					for ( i = 0, l = vcount.length; i < l; i ++ ) {
-
-						var jointCount = vcount[ i ]; // this is the amount of joints that affect a single vertex
-						var vertexSkinData = [];
-
-						for ( j = 0; j < jointCount; j ++ ) {
-
-							var skinIndex = v[ stride + jointOffset ];
-							var weightId = v[ stride + weightOffset ];
-							var skinWeight = weights[ weightId ];
-
-							vertexSkinData.push( { index: skinIndex, weight: skinWeight } );
-
-							stride += 2;
-
-						}
-
-						// we sort the joints in descending order based on the weights.
-						// this ensures, we only procced the most important joints of the vertex
-
-						vertexSkinData.sort( descending );
-
-						// now we provide for each vertex a set of four index and weight values.
-						// the order of the skin data matches the order of vertices
-
-						for ( j = 0; j < BONE_LIMIT; j ++ ) {
-
-							var d = vertexSkinData[ j ];
-
-							if ( d !== undefined ) {
-
-								build.indices.array.push( d.index );
-								build.weights.array.push( d.weight );
-
-							} else {
-
-								build.indices.array.push( 0 );
-								build.weights.array.push( 0 );
-
-							}
-
-						}
-
-					}
-
-					// setup bind matrix
-
-					build.bindMatrix = new THREE.Matrix4().fromArray( data.bindShapeMatrix ).transpose();
-
-					// process bones and inverse bind matrix data
-
-					for ( i = 0, l = jointSource.array.length; i < l; i ++ ) {
-
-						var name = jointSource.array[ i ];
-						var boneInverse = new THREE.Matrix4().fromArray( inverseSource.array, i * inverseSource.stride ).transpose();
-
-						build.joints.push( { name: name, boneInverse: boneInverse } );
-
-					}
-
-					return build;
-
-					// array sort function
-
-					function descending( a, b ) {
-
-						return b.weight - a.weight;
-
-					}
-
-				}
-
-				function getController( id ) {
-
-					return getBuild( library.controllers[ id ], buildController );
-
-				}
-
-				// image
-
-				function parseImage( xml ) {
-
-					var data = {
-						init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent
-					};
-
-					library.images[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function buildImage( data ) {
-
-					if ( data.build !== undefined ) return data.build;
-
-					return data.init_from;
-
-				}
-
-				function getImage( id ) {
-
-					return getBuild( library.images[ id ], buildImage );
-
-				}
-
-				// effect
-
-				function parseEffect( xml ) {
-
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'profile_COMMON':
-								data.profile = parseEffectProfileCOMMON( child );
-								break;
-
-						}
-
-					}
-
-					library.effects[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function parseEffectProfileCOMMON( xml ) {
-
-					var data = {
-						surfaces: {},
-						samplers: {}
-					};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'newparam':
-								parseEffectNewparam( child, data );
-								break;
-
-							case 'technique':
-								data.technique = parseEffectTechnique( child );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseEffectNewparam( xml, data ) {
-
-					var sid = xml.getAttribute( 'sid' );
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'surface':
-								data.surfaces[ sid ] = parseEffectSurface( child );
-								break;
-
-							case 'sampler2D':
-								data.samplers[ sid ] = parseEffectSampler( child );
-								break;
-
-						}
-
-					}
-
-				}
-
-				function parseEffectSurface( xml ) {
-
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'init_from':
-								data.init_from = child.textContent;
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseEffectSampler( xml ) {
-
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'source':
-								data.source = child.textContent;
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseEffectTechnique( xml ) {
-
-					var data = {parameters:{}};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'constant':
-							case 'lambert':
-							case 'blinn':
-							case 'phong':
-							data.type = child.nodeName;
-
-							case 'extra':
-							data.parameters = Object.assign({}, data.parameters, parseEffectParameters( child ) );
-							break;
-
-							case 'bump':
-							data.bump = parseEffectParameter(child);
-							break;
-						}
-					}
-
-					return data;
-
-				}
-
-				function parseEffectParameters( xml ) {
-
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'ambient':
-							case 'emission':
-							case 'diffuse':
-							case 'specular':
-							case 'shininess':
-							case 'transparent':
-							case 'transparency':
-							case 'reflective':
-							var effect = parseEffectParameter( child );
-							if (child.nodeName!=='ambient' || effect.texture )
-							{
-								data[ child.nodeName ] = effect;
-							}
-							break;
-
-							case 'technique':
-							return parseEffectTechnique(child);
-						}
-					}
-
-					return data;
-				}
-
-				function parseEffectParameter( xml ) {
-
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'color':
-								data[ child.nodeName ] = parseFloats( child.textContent );
-								break;
-
-							case 'float':
-								data[ child.nodeName ] = parseFloat( child.textContent );
-								break;
-
-							case 'texture':
-								data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), extra: parseEffectParameterTexture( child ) };
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseEffectParameterTexture( xml ) {
-
-					var data = {
-						technique: {}
-					};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'extra':
-								parseEffectParameterTextureExtra( child, data );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseEffectParameterTextureExtra( xml, data ) {
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'technique':
-								parseEffectParameterTextureExtraTechnique( child, data );
-								break;
-
-						}
-
-					}
-
-				}
-
-				function parseEffectParameterTextureExtraTechnique( xml, data ) {
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'repeatU':
-							case 'repeatV':
-							case 'offsetU':
-							case 'offsetV':
-								data.technique[ child.nodeName ] = parseFloat( child.textContent );
-								break;
-
-							case 'wrapU':
-							case 'wrapV':
-
-								// some files have values for wrapU/wrapV which become NaN via parseInt
-
-								if ( child.textContent.toUpperCase() === 'TRUE' ) {
-
-									data.technique[ child.nodeName ] = 1;
-
-								} else if ( child.textContent.toUpperCase() === 'FALSE' ) {
-
-									data.technique[ child.nodeName ] = 0;
-
-								} else {
-
-									data.technique[ child.nodeName ] = parseInt( child.textContent );
-
-								}
-
-								break;
-
-						}
-
-					}
-
-				}
-
-				function buildEffect( data ) {
-
-					return data;
-
-				}
-
-				function getEffect( id ) {
-
-					return getBuild( library.effects[ id ], buildEffect );
-
-				}
-
-				// material
-
-				function parseMaterial( xml ) {
-
-					var data = {
-						name: xml.getAttribute( 'name' )
-					};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'instance_effect':
-								data.url = parseId( child.getAttribute( 'url' ) );
-								break;
-
-						}
-
-					}
-
-					library.materials[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function buildMaterial( data ) {
-
-					var effect = getEffect( data.url );
-					var technique = effect.profile.technique;
-
-					var material;
-
-					switch ( technique.type ) {
-
-						case 'phong':
-						case 'blinn':
-							material = new THREE.MeshPhongMaterial();
-							break;
-
-						case 'lambert':
-							material = new THREE.MeshLambertMaterial();
-							break;
-
-						default:
-							material = new THREE.MeshBasicMaterial();
-							break;
-
-					}
-
-					material.name = data.name;
-
-					function getTexturePath( textureObject ) {
-						var sampler = effect.profile.samplers[ textureObject.id ];
-						var surface = effect.profile.surfaces[ sampler.source ];
-
-						if ( sampler !== undefined && surface !== null) {
-
-							var img = getImage( surface.init_from );
-
-							return textureLoader.path+img;
-						}
-					}
-
-					function isTexturePBR( textureObject ) {
-
-						var p = getTexturePath(textureObject);
-						if (!p) return false;
-
-						return (p.indexOf("PBR_") >= 0  ||  p.indexOf("PBRFULL_") >= 0);
-					}
-
-					function getTexture( textureObject) {
-
-						var sampler = effect.profile.samplers[ textureObject.id ];
-						var surface = effect.profile.surfaces[ sampler.source ];
-
-						if ( sampler !== undefined && surface !== null) {
-							var img = getImage( surface.init_from );
-							if (img)
-							{
-								var texture = textureLoader.load( img, textureLoadedCallback );
-
-								var extra = textureObject.extra;
-
-								if ( extra !== undefined && extra.technique !== undefined && isEmpty( extra.technique ) === false ) {
-
-									var technique = extra.technique;
-
-									texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
-									texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
-
-									texture.offset.set( technique.offsetU || 0, technique.offsetV || 0 );
-									texture.repeat.set( technique.repeatU || 1, technique.repeatV || 1 );
-
-								} else {
-
-									texture.wrapS = THREE.RepeatWrapping;
-									texture.wrapT = THREE.RepeatWrapping;
-
-								}
-
-								return texture;
-							}
-
-						}
-
-						return null;
-
-					}
-
-					var parameters = technique.parameters;
-					var parameter;
-					var pbrMaterial = null;
-
-					var pbrKeyToThreeJSMap = {
-						'Base_Color': 'map',
-						'Metallic': 'metalnessMap',
-						'Roughness': 'roughnessMap',
-						'Mixed_AO': 'aoMap',
-						'Emissive': 'emissiveMap',
-						'Normal': 'normalMap',
-						'Height': 'displacementMap'
-					};
-
-					for ( var key in parameters )
-					{
-						parameter = parameters[ key ];
-						if (parameter.texture)
-						{
-							var texturePath = getTexturePath(parameter.texture);
-
-							if (texturePath && (texturePath.indexOf("PBR_") >= 0 || texturePath.indexOf("PBRFULL_") >= 0) )
-
-							for (var k in pbrKeyToThreeJSMap)
-							{
-								if (texturePath.indexOf(k) >= 0)
-								{
-									if (pbrMaterial === null)
-									{
-										pbrMaterial = {};
-									}
-
-									pbrMaterial[pbrKeyToThreeJSMap[k]] = texturePath;
-
-									if (texturePath.indexOf("PBRFULL_") >= 0 && k==='Base_Color')
-									{
-										// If the prefix is PBRFULL it means that all the PBR textures are present
-										// but that only the basic color map have been linked to the dae.
-
-										var allpbrkeywords = ['Metallic','Mixed_AO','Roughness','Normal','Roughness'];
-
-										for(var pbri in allpbrkeywords)
-										{
-											var pbrk = allpbrkeywords[pbri];
-											var path = texturePath;
-
-											path = path.replace('Base_Color', pbrk);
-											if (pbrk==='Normal')
-											{
-												// Normal map must be png. In case the Base_Color is a jpg,
-												// change extension to png
-
-												path = path.replace('.jpg', '.png');
-												path = path.replace('.jpeg', '.png');
-											}
-
-											pbrMaterial[pbrKeyToThreeJSMap[pbrk]] = path;
-										}
-									}
-								}
-							}
-						}
-
-						switch ( key ) {
-							case 'diffuse':
-								if ( parameter.color ) material.color.fromArray( parameter.color );
-								if ( parameter.texture && !isTexturePBR(parameter.texture)) material.map = getTexture( parameter.texture );
-								break;
-							case 'specular':
-								if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color );
-								if ( parameter.texture && !isTexturePBR(parameter.texture)) material.specularMap = getTexture( parameter.texture );
-								break;
-							case 'shininess':
-								if ( parameter.float && material.shininess )
-									material.shininess = parameter.float;
-								break;
-							case 'emission':
-								if ( parameter.color && material.emissive )
-									material.emissive.fromArray( parameter.color );
-								break;
-							case 'transparent':
-								if ( parameter.texture && !isTexturePBR(parameter.texture)) material.alphaMap = getTexture( parameter.texture );
-								material.transparent = true;
-								break;
-							case 'transparency':
-								if ( parameter.float !== undefined ) material.opacity = parameter.float;
-								material.transparent = true;
-								break;
-						}
-					}
-
-					if (pbrMaterial !== null)
-					{
-						material.pbrMaterialDescription = pbrMaterial;	// If PBR material has been found, store it for future usage
-					}
-
-					return material;
-				}
-
-				function getMaterial( id ) {
-
-					return getBuild( library.materials[ id ], buildMaterial );
-
-				}
-
-				// camera
-
-				function parseCamera( xml ) {
-
-					var data = {
-						name: xml.getAttribute( 'name' )
-					};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'optics':
-								data.optics = parseCameraOptics( child );
-								break;
-
-						}
-
-					}
-
-					library.cameras[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function parseCameraOptics( xml ) {
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						switch ( child.nodeName ) {
-
-							case 'technique_common':
-								return parseCameraTechnique( child );
-
-						}
-
-					}
-
-					return {};
-
-				}
-
-				function parseCameraTechnique( xml ) {
-
-					var data = {};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						switch ( child.nodeName ) {
-
-							case 'perspective':
-							case 'orthographic':
-
-								data.technique = child.nodeName;
-								data.parameters = parseCameraParameters( child );
-
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseCameraParameters( xml ) {
-
-					var data = {};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						switch ( child.nodeName ) {
-
-							case 'xfov':
-							case 'yfov':
-							case 'xmag':
-							case 'ymag':
-							case 'znear':
-							case 'zfar':
-							case 'aspect_ratio':
-								data[ child.nodeName ] = parseFloat( child.textContent );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function buildCamera( data ) {
-
-					var camera;
-
-					switch ( data.optics.technique ) {
-
-						case 'perspective':
-							camera = new THREE.PerspectiveCamera(
-								data.optics.parameters.yfov,
-								data.optics.parameters.aspect_ratio,
-								data.optics.parameters.znear,
-								data.optics.parameters.zfar
-							);
-							break;
-
-						case 'orthographic':
-							var ymag = data.optics.parameters.ymag;
-							var xmag = data.optics.parameters.xmag;
-							var aspectRatio = data.optics.parameters.aspect_ratio;
-
-							xmag = ( xmag === undefined ) ? ( ymag * aspectRatio ) : xmag;
-							ymag = ( ymag === undefined ) ? ( xmag / aspectRatio ) : ymag;
-
-							xmag *= 0.5;
-							ymag *= 0.5;
-
-							camera = new THREE.OrthographicCamera(
-								- xmag, xmag, ymag, - ymag, // left, right, top, bottom
-								data.optics.parameters.znear,
-								data.optics.parameters.zfar
-							);
-							break;
-
-						default:
-							camera = new THREE.PerspectiveCamera();
-							break;
-
-					}
-
-					camera.name = data.name;
-
-					return camera;
-
-				}
-
-				function getCamera( id ) {
-
-					return getBuild( library.cameras[ id ], buildCamera );
-
-				}
-
-				// light
-
-				function parseLight( xml ) {
-
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'technique_common':
-								data = parseLightTechnique( child );
-								break;
-
-						}
-
-					}
-
-					library.lights[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function parseLightTechnique( xml ) {
-
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'directional':
-							case 'point':
-							case 'spot':
-							case 'ambient':
-
-								data.technique = child.nodeName;
-								data.parameters = parseLightParameters( child );
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseLightParameters( xml ) {
-
-					var data = {};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'color':
-								var array = parseFloats( child.textContent );
-								data.color = new THREE.Color().fromArray( array );
-								break;
-
-							case 'falloff_angle':
-								data.falloffAngle = parseFloat( child.textContent );
-								break;
-
-							case 'quadratic_attenuation':
-								var f = parseFloat( child.textContent );
-								data.distance = f ? Math.sqrt( 1 / f ) : 0;
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function buildLight( data ) {
-
-					var light;
-
-					switch ( data.technique ) {
-
-						case 'directional':
-							light = new THREE.DirectionalLight();
-							break;
-
-						case 'point':
-							light = new THREE.PointLight();
-							break;
-
-						case 'spot':
-							light = new THREE.SpotLight();
-							break;
-
-						case 'ambient':
-							light = new THREE.AmbientLight();
-							break;
-
-					}
-
-					if ( data.parameters.color ) light.color.copy( data.parameters.color );
-					if ( data.parameters.distance ) light.distance = data.parameters.distance;
-
-					return light;
-
-				}
-
-				function getLight( id ) {
-
-					return getBuild( library.lights[ id ], buildLight );
-
-				}
-
-				// geometry
-
-				function parseGeometry( xml ) {
-
-					var data = {
-						name: xml.getAttribute( 'name' ),
-						sources: {},
-						vertices: {},
-						primitives: []
-					};
-
-					var mesh = getElementsByTagName( xml, 'mesh' )[ 0 ];
-
-					// the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep
-					if ( mesh === undefined ) return;
-
-					for ( var i = 0; i < mesh.childNodes.length; i ++ ) {
-
-						var child = mesh.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						var id = child.getAttribute( 'id' );
-
-						switch ( child.nodeName ) {
-
-							case 'source':
-								data.sources[ id ] = parseSource( child );
-								break;
-
-							case 'vertices':
-								// data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ];
-								data.vertices = parseGeometryVertices( child );
-								break;
-
-							case 'polygons':
-								console.warn( 'THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName );
-								break;
-
-							case 'lines':
-							case 'linestrips':
-							case 'polylist':
-							case 'triangles':
-								data.primitives.push( parseGeometryPrimitive( child ) );
-								break;
-
-							default:
-							break;
-
-						}
-
-					}
-
-					library.geometries[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function parseSource( xml ) {
-
-					var data = {
-						array: [],
-						stride: 3
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'float_array':
-								data.array = parseFloats( child.textContent );
-								break;
-
-							case 'Name_array':
-								data.array = parseStrings( child.textContent );
-								break;
-
-							case 'technique_common':
-								var accessor = getElementsByTagName( child, 'accessor' )[ 0 ];
-
-								if ( accessor !== undefined ) {
-
-									data.stride = parseInt( accessor.getAttribute( 'stride' ) );
-
-								}
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseGeometryVertices( xml ) {
-
-					var data = {};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) );
-
-					}
-
-					return data;
-
-				}
-
-				function parseGeometryPrimitive( xml ) {
-
-					var primitive = {
-						type: xml.nodeName,
-						material: xml.getAttribute( 'material' ),
-						count: parseInt( xml.getAttribute( 'count' ) ),
-						inputs: {},
-						stride: 0
-					};
-
-					for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'input':
-								var id = parseId( child.getAttribute( 'source' ) );
-								var semantic = child.getAttribute( 'semantic' );
-								var offset = parseInt( child.getAttribute( 'offset' ) );
-								primitive.inputs[ semantic ] = { id: id, offset: offset };
-								primitive.stride = Math.max( primitive.stride, offset + 1 );
-								break;
-
-							case 'vcount':
-								primitive.vcount = parseInts( child.textContent );
-								break;
-
-							case 'p':
-								primitive.p = parseInts( child.textContent );
-								break;
-
-						}
-
-					}
-
-					return primitive;
-
-				}
-
-				function groupPrimitives( primitives ) {
-
-					var build = {};
-
-					for ( var i = 0; i < primitives.length; i ++ ) {
-
-						var primitive = primitives[ i ];
-
-						if ( build[ primitive.type ] === undefined ) build[ primitive.type ] = [];
-
-						build[ primitive.type ].push( primitive );
-
-					}
-
-					return build;
-
-				}
-
-				function buildGeometry( data ) {
-
-					var build = {};
-
-					var sources = data.sources;
-					var vertices = data.vertices;
-					var primitives = data.primitives;
-
-					if ( primitives.length === 0 ) return {};
-
-					// our goal is to create one buffer geoemtry for a single type of primitives
-					// first, we group all primitives by their type
-
-					var groupedPrimitives = groupPrimitives( primitives );
-
-					for ( var type in groupedPrimitives ) {
-
-						// second, we create for each type of primitives (polylist,triangles or lines) a buffer geometry
-
-						build[ type ] = buildGeometryType( groupedPrimitives[ type ], sources, vertices );
-
-					}
-
-					return build;
-
-				}
-
-				function buildGeometryType( primitives, sources, vertices ) {
-
-					var build = {};
-
-					var position = { array: [], stride: 0 };
-					var normal = { array: [], stride: 0 };
-					var uv = { array: [], stride: 0 };
-					var color = { array: [], stride: 0 };
-
-					var skinIndex = { array: [], stride: 4 };
-					var skinWeight = { array: [], stride: 4 };
-
-					var geometry = new THREE.BufferGeometry();
-
-					var materialKeys = [];
-
-					var start = 0, count = 0;
-
-					for ( var p = 0; p < primitives.length; p ++ ) {
-
-						var primitive = primitives[ p ];
-						var inputs = primitive.inputs;
-						var triangleCount = 1;
-
-						if ( primitive.vcount && primitive.vcount[ 0 ] === 4 ) {
-
-							triangleCount = 2; // one quad -> two triangles
-
-						}
-
-						// groups
-
-						if ( primitive.type === 'lines' || primitive.type === 'linestrips' ) {
-
-							count = primitive.count * 2;
-
-						} else {
-
-							count = primitive.count * 3 * triangleCount;
-
-						}
-
-						geometry.addGroup( start, count, p );
-						start += count;
-
-						// material
-
-						if ( primitive.material ) {
-
-							materialKeys.push( primitive.material );
-
-						}
-
-						// geometry data
-
-						for ( var name in inputs ) {
-
-							var input = inputs[ name ];
-
-							switch ( name )	{
-
-								case 'VERTEX':
-									for ( var key in vertices ) {
-
-										var id = vertices[ key ];
-
-										switch ( key ) {
-
-											case 'POSITION':
-												buildGeometryData( primitive, sources[ id ], input.offset, position.array );
-												position.stride = sources[ id ].stride;
-
-												if ( sources.skinWeights && sources.skinIndices ) {
-
-													buildGeometryData( primitive, sources.skinIndices, input.offset, skinIndex.array );
-													buildGeometryData( primitive, sources.skinWeights, input.offset, skinWeight.array );
-
-												}
-												break;
-
-											case 'NORMAL':
-												buildGeometryData( primitive, sources[ id ], input.offset, normal.array );
-												normal.stride = sources[ id ].stride;
-												break;
-
-											case 'COLOR':
-												buildGeometryData( primitive, sources[ id ], input.offset, color.array );
-												color.stride = sources[ id ].stride;
-												break;
-
-											case 'TEXCOORD':
-												buildGeometryData( primitive, sources[ id ], input.offset, uv.array );
-												uv.stride = sources[ id ].stride;
-												break;
-
-											default:
-												console.warn( 'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key );
-
-										}
-
-									}
-									break;
-
-								case 'NORMAL':
-									buildGeometryData( primitive, sources[ input.id ], input.offset, normal.array );
-									normal.stride = sources[ input.id ].stride;
-									break;
-
-								case 'COLOR':
-									buildGeometryData( primitive, sources[ input.id ], input.offset, color.array );
-									color.stride = sources[ input.id ].stride;
-									break;
-
-								case 'TEXCOORD':
-									buildGeometryData( primitive, sources[ input.id ], input.offset, uv.array );
-									uv.stride = sources[ input.id ].stride;
-									break;
-
-							}
-
-						}
-
-					}
-
-					// build geometry
-
-					if ( position.array.length > 0 ) geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( position.array, position.stride ) );
-					if ( normal.array.length > 0 ) geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normal.array, normal.stride ) );
-					if ( color.array.length > 0 ) geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( color.array, color.stride ) );
-					if ( uv.array.length > 0 ) geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uv.array, uv.stride ) );
-
-					if ( skinIndex.array.length > 0 ) geometry.addAttribute( 'skinIndex', new THREE.Float32BufferAttribute( skinIndex.array, skinIndex.stride ) );
-					if ( skinWeight.array.length > 0 ) geometry.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeight.array, skinWeight.stride ) );
-
-					build.data = geometry;
-					build.type = primitives[ 0 ].type;
-					build.materialKeys = materialKeys;
-
-					return build;
-
-				}
-
-				function buildGeometryData( primitive, source, offset, array ) {
-
-					var indices = primitive.p;
-					var stride = primitive.stride;
-					var vcount = primitive.vcount;
-
-					function pushVector( i ) {
-
-						var index = indices[ i + offset ] * sourceStride;
-						var length = index + sourceStride;
-
-						for ( ; index < length; index ++ ) {
-
-							array.push( sourceArray[ index ] );
-
-						}
-
-					}
-
-					var maxcount = 0;
-
-					var sourceArray = source.array;
-					var sourceStride = source.stride;
-
-					if ( primitive.vcount !== undefined ) {
-
-						var index = 0;
-
-						for ( var i = 0, l = vcount.length; i < l; i ++ ) {
-
-							var count = vcount[ i ];
-
-							if ( count === 4 ) {
-
-								var a = index + stride * 0;
-								var b = index + stride * 1;
-								var c = index + stride * 2;
-								var d = index + stride * 3;
-
-								pushVector( a ); pushVector( b ); pushVector( d );
-								pushVector( b ); pushVector( c ); pushVector( d );
-
-							} else if ( count === 3 ) {
-
-								var a = index + stride * 0;
-								var b = index + stride * 1;
-								var c = index + stride * 2;
-
-								pushVector( a ); pushVector( b ); pushVector( c );
-
-							} else {
-
-								maxcount = Math.max( maxcount, count );
-
-							}
-
-							index += stride * count;
-
-						}
-
-						if ( maxcount > 0 ) {
-
-							console.log( 'THREE.ColladaLoader: Geometry has faces with more than 4 vertices.' );
-
-						}
-
-					} else {
-
-						for ( var i = 0, l = indices.length; i < l; i += stride ) {
-
-							pushVector( i );
-
-						}
-
-					}
-
-				}
-
-				function getGeometry( id ) {
-
-					return getBuild( library.geometries[ id ], buildGeometry );
-
-				}
-
-				// kinematics
-
-				function parseKinematicsModel( xml ) {
-
-					var data = {
-						name: xml.getAttribute( 'name' ) || '',
-						joints: {},
-						links: []
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'technique_common':
-								parseKinematicsTechniqueCommon( child, data );
-								break;
-
-						}
-
-					}
-
-					library.kinematicsModels[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function buildKinematicsModel( data ) {
-
-					if ( data.build !== undefined ) return data.build;
-
-					return data;
-
-				}
-
-				function getKinematicsModel( id ) {
-
-					return getBuild( library.kinematicsModels[ id ], buildKinematicsModel );
-
-				}
-
-				function parseKinematicsTechniqueCommon( xml, data ) {
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'joint':
-								data.joints[ child.getAttribute( 'sid' ) ] = parseKinematicsJoint( child );
-								break;
-
-							case 'link':
-								data.links.push( parseKinematicsLink( child ) );
-								break;
-
-						}
-
-					}
-
-				}
-
-				function parseKinematicsJoint( xml ) {
-
-					var data;
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'prismatic':
-							case 'revolute':
-								data = parseKinematicsJointParameter( child );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseKinematicsJointParameter( xml, data ) {
-
-					var data = {
-						sid: xml.getAttribute( 'sid' ),
-						name: xml.getAttribute( 'name' ) || '',
-						axis: new THREE.Vector3(),
-						limits: {
-							min: 0,
-							max: 0
-						},
-						type: xml.nodeName,
-						static: false,
-						zeroPosition: 0,
-						middlePosition: 0
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'axis':
-								var array = parseFloats( child.textContent );
-								data.axis.fromArray( array );
-								break;
-							case 'limits':
-								var max = child.getElementsByTagName( 'max' )[ 0 ];
-								var min = child.getElementsByTagName( 'min' )[ 0 ];
-
-								data.limits.max = parseFloat( max.textContent );
-								data.limits.min = parseFloat( min.textContent );
-								break;
-
-						}
-
-					}
-
-					// if min is equal to or greater than max, consider the joint static
-
-					if ( data.limits.min >= data.limits.max ) {
-
-						data.static = true;
-
-					}
-
-					// calculate middle position
-
-					data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0;
-
-					return data;
-
-				}
-
-				function parseKinematicsLink( xml ) {
-
-					var data = {
-						sid: xml.getAttribute( 'sid' ),
-						name: xml.getAttribute( 'name' ) || '',
-						attachments: [],
-						transforms: []
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'attachment_full':
-								data.attachments.push( parseKinematicsAttachment( child ) );
-								break;
-
-							case 'matrix':
-							case 'translate':
-							case 'rotate':
-								data.transforms.push( parseKinematicsTransform( child ) );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseKinematicsAttachment( xml ) {
-
-					var data = {
-						joint: xml.getAttribute( 'joint' ).split( '/' ).pop(),
-						transforms: [],
-						links: []
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'link':
-								data.links.push( parseKinematicsLink( child ) );
-								break;
-
-							case 'matrix':
-							case 'translate':
-							case 'rotate':
-								data.transforms.push( parseKinematicsTransform( child ) );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function parseKinematicsTransform( xml ) {
-
-					var data = {
-						type: xml.nodeName
-					};
-
-					var array = parseFloats( xml.textContent );
-
-					switch ( data.type ) {
-
-						case 'matrix':
-							data.obj = new THREE.Matrix4();
-							data.obj.fromArray( array ).transpose();
-							break;
-
-						case 'translate':
-							data.obj = new THREE.Vector3();
-							data.obj.fromArray( array );
-							break;
-
-						case 'rotate':
-							data.obj = new THREE.Vector3();
-							data.obj.fromArray( array );
-							data.angle = THREE.Math.degToRad( array[ 3 ] );
-							break;
-
-					}
-
-					return data;
-
-				}
-
-				function parseKinematicsScene( xml ) {
-
-					var data = {
-						bindJointAxis: []
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'bind_joint_axis':
-								data.bindJointAxis.push( parseKinematicsBindJointAxis( child ) );
-								break;
-
-						}
-
-					}
-
-					library.kinematicsScenes[ parseId( xml.getAttribute( 'url' ) ) ] = data;
-
-				}
-
-				function parseKinematicsBindJointAxis( xml ) {
-
-					var data = {
-						target: xml.getAttribute( 'target' ).split( '/' ).pop()
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'axis':
-								var param = child.getElementsByTagName( 'param' )[ 0 ];
-								data.axis = param.textContent;
-								var tmpJointIndex = data.axis.split( 'inst_' ).pop().split( 'axis' )[ 0 ];
-								data.jointIndex = tmpJointIndex.substr( 0, tmpJointIndex.length - 1 );
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function buildKinematicsScene( data ) {
-
-					if ( data.build !== undefined ) return data.build;
-
-					return data;
-
-				}
-
-				function getKinematicsScene( id ) {
-
-					return getBuild( library.kinematicsScenes[ id ], buildKinematicsScene );
-
-				}
-
-				function setupKinematics() {
-
-					var kinematicsModelId = Object.keys( library.kinematicsModels )[ 0 ];
-					var kinematicsSceneId = Object.keys( library.kinematicsScenes )[ 0 ];
-					var visualSceneId = Object.keys( library.visualScenes )[ 0 ];
-
-					if ( kinematicsModelId === undefined || kinematicsSceneId === undefined ) return;
-
-					var kinematicsModel = getKinematicsModel( kinematicsModelId );
-					var kinematicsScene = getKinematicsScene( kinematicsSceneId );
-					var visualScene = getVisualScene( visualSceneId );
-
-					var bindJointAxis = kinematicsScene.bindJointAxis;
-					var jointMap = {};
-
-					for ( var i = 0, l = bindJointAxis.length; i < l; i ++ ) {
-
-						var axis = bindJointAxis[ i ];
-
-						// the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix'
-
-						var targetElement = collada.querySelector( '[sid="' + axis.target + '"]' );
-
-						if ( targetElement ) {
-
-							// get the parent of the transfrom element
-
-							var parentVisualElement = targetElement.parentElement;
-
-							// connect the joint of the kinematics model with the element in the visual scene
-
-							connect( axis.jointIndex, parentVisualElement );
-
-						}
-
-					}
-
-					function connect( jointIndex, visualElement ) {
-
-						var visualElementName = visualElement.getAttribute( 'name' );
-						var joint = kinematicsModel.joints[ jointIndex ];
-
-						visualScene.traverse( function ( object ) {
-
-							if ( object.name === visualElementName ) {
-
-								jointMap[ jointIndex ] = {
-									object: object,
-									transforms: buildTransformList( visualElement ),
-									joint: joint,
-									position: joint.zeroPosition
-								};
-
-							}
-
-						} );
-
-					}
-
-					var m0 = new THREE.Matrix4();
-
-					kinematics = {
-
-						joints: kinematicsModel && kinematicsModel.joints,
-
-						getJointValue: function ( jointIndex ) {
-
-							var jointData = jointMap[ jointIndex ];
-
-							if ( jointData ) {
-
-								return jointData.position;
-
-							} else {
-
-								console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.' );
-
-							}
-
-						},
-
-						setJointValue: function ( jointIndex, value ) {
-
-							var jointData = jointMap[ jointIndex ];
-
-							if ( jointData ) {
-
-								var joint = jointData.joint;
-
-								if ( value > joint.limits.max || value < joint.limits.min ) {
-
-									console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').' );
-
-								} else if ( joint.static ) {
-
-									console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.' );
-
-								} else {
-
-									var object = jointData.object;
-									var axis = joint.axis;
-									var transforms = jointData.transforms;
-
-									matrix.identity();
-
-									// each update, we have to apply all transforms in the correct order
-
-									for ( var i = 0; i < transforms.length; i ++ ) {
-
-										var transform = transforms[ i ];
-
-										// if there is a connection of the transform node with a joint, apply the joint value
-
-										if ( transform.sid && transform.sid.indexOf( jointIndex ) !== - 1 ) {
-
-											switch ( joint.type ) {
-
-												case 'revolute':
-													matrix.multiply( m0.makeRotationAxis( axis, THREE.Math.degToRad( value ) ) );
-													break;
-
-												case 'prismatic':
-													matrix.multiply( m0.makeTranslation( axis.x * value, axis.y * value, axis.z * value ) );
-													break;
-
-												default:
-													console.warn( 'THREE.ColladaLoader: Unknown joint type: ' + joint.type );
-													break;
-
-											}
-
-										} else {
-
-											switch ( transform.type ) {
-
-												case 'matrix':
-													matrix.multiply( transform.obj );
-													break;
-
-												case 'translate':
-													matrix.multiply( m0.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) );
-													break;
-
-												case 'scale':
-													matrix.scale( transform.obj );
-													break;
-
-												case 'rotate':
-													matrix.multiply( m0.makeRotationAxis( transform.obj, transform.angle ) );
-													break;
-
-											}
-
-										}
-
-									}
-
-									object.matrix.copy( matrix );
-									object.matrix.decompose( object.position, object.quaternion, object.scale );
-
-									jointMap[ jointIndex ].position = value;
-
-								}
-
-							} else {
-
-								console.log( 'THREE.ColladaLoader: ' + jointIndex + ' does not exist.' );
-
-							}
-
-						}
-
-					};
-
-				}
-
-				function buildTransformList( node ) {
-
-					var transforms = [];
-
-					var xml = collada.querySelector( '[id="' + node.id + '"]' );
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'matrix':
-								var array = parseFloats( child.textContent );
-								var matrix = new THREE.Matrix4().fromArray( array ).transpose();
-								transforms.push( {
-									sid: child.getAttribute( 'sid' ),
-									type: child.nodeName,
-									obj: matrix
-								} );
-								break;
-
-							case 'translate':
-							case 'scale':
-								var array = parseFloats( child.textContent );
-								var vector = new THREE.Vector3().fromArray( array );
-								transforms.push( {
-									sid: child.getAttribute( 'sid' ),
-									type: child.nodeName,
-									obj: vector
-								} );
-								break;
-
-							case 'rotate':
-								var array = parseFloats( child.textContent );
-								var vector = new THREE.Vector3().fromArray( array );
-								var angle = THREE.Math.degToRad( array[ 3 ] );
-								transforms.push( {
-									sid: child.getAttribute( 'sid' ),
-									type: child.nodeName,
-									obj: vector,
-									angle: angle
-								} );
-								break;
-
-						}
-
-					}
-
-					return transforms;
-
-				}
-
-				// nodes
-
-				function prepareNodes( xml ) {
-
-					var elements = xml.getElementsByTagName( 'node' );
-
-					// ensure all node elements have id attributes
-
-					for ( var i = 0; i < elements.length; i ++ ) {
-
-						var element = elements[ i ];
-
-						if ( element.hasAttribute( 'id' ) === false ) {
-
-							element.setAttribute( 'id', generateId() );
-
-						}
-
-					}
-
-				}
-
-				var matrix = new THREE.Matrix4();
-				var vector = new THREE.Vector3();
-
-				function parseNode( xml ) {
-
-					var data = {
-						name: xml.getAttribute( 'name' ) || '',
-						type: xml.getAttribute( 'type' ),
-						id: xml.getAttribute( 'id' ),
-						sid: xml.getAttribute( 'sid' ),
-						matrix: new THREE.Matrix4(),
-						nodes: [],
-						instanceCameras: [],
-						instanceControllers: [],
-						instanceLights: [],
-						instanceGeometries: [],
-						instanceNodes: [],
-						transforms: {}
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						if ( child.nodeType !== 1 ) continue;
-
-						switch ( child.nodeName ) {
-
-							case 'node':
-								data.nodes.push( child.getAttribute( 'id' ) );
-								parseNode( child );
-								break;
-
-							case 'instance_camera':
-								data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) );
-								break;
-
-							case 'instance_controller':
-								data.instanceControllers.push( parseNodeInstance( child ) );
-								break;
-
-							case 'instance_light':
-								data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) );
-								break;
-
-							case 'instance_geometry':
-								data.instanceGeometries.push( parseNodeInstance( child ) );
-								break;
-
-							case 'instance_node':
-								data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) );
-								break;
-
-							case 'matrix':
-								var array = parseFloats( child.textContent );
-								data.matrix.multiply( matrix.fromArray( array ).transpose() );
-								data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
-								break;
-
-							case 'translate':
-								var array = parseFloats( child.textContent );
-								vector.fromArray( array );
-								data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) );
-								data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
-								break;
-
-							case 'rotate':
-								var array = parseFloats( child.textContent );
-								var angle = THREE.Math.degToRad( array[ 3 ] );
-								data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) );
-								data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
-								break;
-
-							case 'scale':
-								var array = parseFloats( child.textContent );
-								data.matrix.scale( vector.fromArray( array ) );
-								data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
-								break;
-
-							case 'extra':
-								break;
-
-							default:
-								break;
-
-						}
-
-					}
-
-					library.nodes[ data.id ] = data;
-
-					return data;
-
-				}
-
-				function parseNodeInstance( xml ) {
-
-					var data = {
-						id: parseId( xml.getAttribute( 'url' ) ),
-						materials: {},
-						skeletons: []
-					};
-
-					for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-						var child = xml.childNodes[ i ];
-
-						switch ( child.nodeName ) {
-
-							case 'bind_material':
-								var instances = child.getElementsByTagName( 'instance_material' );
-
-								for ( var j = 0; j < instances.length; j ++ ) {
-
-									var instance = instances[ j ];
-									var symbol = instance.getAttribute( 'symbol' );
-									var target = instance.getAttribute( 'target' );
-
-									data.materials[ symbol ] = parseId( target );
-
-								}
-
-								break;
-
-							case 'skeleton':
-								data.skeletons.push( parseId( child.textContent ) );
-								break;
-
-							default:
-								break;
-
-						}
-
-					}
-
-					return data;
-
-				}
-
-				function buildSkeleton( skeletons, joints ) {
-
-					var boneData = [];
-					var sortedBoneData = [];
-
-					var i, j, data;
-
-					// a skeleton can have multiple root bones. collada expresses this
-					// situtation with multiple "skeleton" tags per controller instance
-
-					for ( i = 0; i < skeletons.length; i ++ ) {
-
-						var skeleton = skeletons[ i ];
-						var root = getNode( skeleton );
-
-						// setup bone data for a single bone hierarchy
-
-						buildBoneHierarchy( root, joints, boneData );
-
-					}
-
-					// sort bone data (the order is defined in the corresponding controller)
-
-					for ( i = 0; i < joints.length; i ++ ) {
-
-						for ( j = 0; j < boneData.length; j ++ ) {
-
-							data = boneData[ j ];
-
-							if ( data.bone.name === joints[ i ].name ) {
-
-								sortedBoneData[ i ] = data;
-								data.processed = true;
-								break;
-
-							}
-
-						}
-
-					}
-
-					// add unprocessed bone data at the end of the list
-
-					for ( i = 0; i < boneData.length; i ++ ) {
-
-						data = boneData[ i ];
-
-						if ( data.processed === false ) {
-
-							sortedBoneData.push( data );
-							data.processed = true;
-
-						}
-
-					}
-
-					// setup arrays for skeleton creation
-
-					var bones = [];
-					var boneInverses = [];
-
-					for ( i = 0; i < sortedBoneData.length; i ++ ) {
-
-						data = sortedBoneData[ i ];
-
-						bones.push( data.bone );
-						boneInverses.push( data.boneInverse );
-
-					}
-
-					return new THREE.Skeleton( bones, boneInverses );
-
-				}
-
-				function buildBoneHierarchy( root, joints, boneData ) {
-					if (root === undefined) {
-						console.log('buildBoneHierarchy: Undefined root node given, return');
-						return;
-					}
-
-					// setup bone data from visual scene
-
-					root.traverse( function ( object ) {
-
-						if ( object.isBone === true ) {
-
-							var boneInverse;
-
-							// retrieve the boneInverse from the controller data
-
-							for ( var i = 0; i < joints.length; i ++ ) {
-
-								var joint = joints[ i ];
-
-								if ( joint.name === object.name ) {
-
-									boneInverse = joint.boneInverse;
-									break;
-
-								}
-
-							}
-
-							if ( boneInverse === undefined ) {
-
-								// Unfortunately, there can be joints in the visual scene that are not part of the
-								// corresponding controller. In this case, we have to create a dummy boneInverse matrix
-								// for the respective bone. This bone won't affect any vertices, because there are no skin indices
-								// and weights defined for it. But we still have to add the bone to the sorted bone list in order to
-								// ensure a correct animation of the model.
-
-								 boneInverse = new THREE.Matrix4();
-
-							}
-
-							boneData.push( { bone: object, boneInverse: boneInverse, processed: false } );
-
-						}
-
-					} );
-
-				}
-
-				function buildNode( data ) {
-
-					var objects = [];
-
-					var matrix = data.matrix;
-					var nodes = data.nodes;
-					var type = data.type;
-					var instanceCameras = data.instanceCameras;
-					var instanceControllers = data.instanceControllers;
-					var instanceLights = data.instanceLights;
-					var instanceGeometries = data.instanceGeometries;
-					var instanceNodes = data.instanceNodes;
-
-					// nodes
-
-					for ( var i = 0, l = nodes.length; i < l; i ++ ) {
-
-						objects.push( getNode( nodes[ i ] ) );
-
-					}
-
-					// instance cameras
-
-					for ( var i = 0, l = instanceCameras.length; i < l; i ++ ) {
-
-						objects.push( getCamera( instanceCameras[ i ] ).clone() );
-
-					}
-
-					// instance controllers
-
-					for ( var i = 0, l = instanceControllers.length; i < l; i ++ ) {
-
-						var instance = instanceControllers[ i ];
-						var controller = getController( instance.id );
-						var geometries = getGeometry( controller.id );
-						var newObjects = buildObjects( geometries, instance.materials );
-
-						var skeletons = instance.skeletons;
-						var joints = controller.skin.joints;
-
-						var skeleton = buildSkeleton( skeletons, joints );
-
-						for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) {
-
-							var object = newObjects[ j ];
-
-							if ( object.isSkinnedMesh ) {
-
-								object.bind( skeleton, controller.skin.bindMatrix );
-								object.normalizeSkinWeights();
-
-							}
-
-							objects.push( object );
-
-						}
-
-					}
-
-					// instance lights
-
-					for ( var i = 0, l = instanceLights.length; i < l; i ++ ) {
-
-						objects.push( getLight( instanceLights[ i ] ).clone() );
-
-					}
-
-					// instance geometries
-
-					for ( var i = 0, l = instanceGeometries.length; i < l; i ++ ) {
-
-						var instance = instanceGeometries[ i ];
-
-						// a single geometry instance in collada can lead to multiple object3Ds.
-						// this is the case when primitives are combined like triangles and lines
-
-						var geometries = getGeometry( instance.id );
-						var newObjects = buildObjects( geometries, instance.materials );
-
-						for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) {
-
-							objects.push( newObjects[ j ] );
-
-						}
-
-					}
-
-					// instance nodes
-
-					for ( var i = 0, l = instanceNodes.length; i < l; i ++ ) {
-
-						objects.push( getNode( instanceNodes[ i ] ).clone() );
-
-					}
-
-					var object;
-
-					if ( nodes.length === 0 && objects.length === 1 ) {
-
-						object = objects[ 0 ];
-
-					} else {
-
-						object = ( type === 'JOINT' ) ? new THREE.Bone() : new THREE.Group();
-
-						for ( var i = 0; i < objects.length; i ++ ) {
-
-							object.add( objects[ i ] );
-
-						}
-
-					}
-
-					object.name = ( type === 'JOINT' ) ? data.sid : data.name;
-					object.matrix.copy( matrix );
-					object.matrix.decompose( object.position, object.quaternion, object.scale );
-
-					return object;
-
-				}
-
-				function resolveMaterialBinding( keys, instanceMaterials ) {
-
-					var materials = [];
-
-					for ( var i = 0, l = keys.length; i < l; i ++ ) {
-
-						var id = instanceMaterials[ keys[ i ] ];
-						materials.push( getMaterial( id ) );
-
-					}
-
-					return materials;
-
-				}
-
-				function buildObjects( geometries, instanceMaterials ) {
-
-					var objects = [];
-
-					for ( var type in geometries ) {
-
-						var geometry = geometries[ type ];
-
-						var materials = resolveMaterialBinding( geometry.materialKeys, instanceMaterials );
-
-						// handle case if no materials are defined
-
-						if ( materials.length === 0 ) {
-
-							if ( type === 'lines' || type === 'linestrips' ) {
-
-								materials.push( new THREE.LineBasicMaterial() );
-
-							} else {
-
-								materials.push( new THREE.MeshPhongMaterial() );
-
-							}
-
-						}
-
-						// regard skinning
-
-						var skinning = ( geometry.data.attributes.skinIndex !== undefined );
-
-						if ( skinning ) {
-
-							for ( var i = 0, l = materials.length; i < l; i ++ ) {
-
-								materials[ i ].skinning = true;
-
-							}
-
-						}
-
-						// choose between a single or multi materials (material array)
-
-						var material = ( materials.length === 1 ) ? materials[ 0 ] : materials;
-
-						// now create a specific 3D object
-
-						var object;
-
-						switch ( type ) {
-
-							case 'lines':
-								object = new THREE.LineSegments( geometry.data, material );
-								break;
-
-							case 'linestrips':
-								object = new THREE.Line( geometry.data, material );
-								break;
-
-							case 'triangles':
-							case 'polylist':
-								if ( skinning ) {
-
-									object = new THREE.SkinnedMesh( geometry.data, material );
-
-								} else {
-
-									object = new THREE.Mesh( geometry.data, material );
-
-								}
-								break;
-
-						}
-
-						objects.push( object );
-
-					}
-
-					return objects;
-
-				}
-
-				function getNode( id ) {
-
-					return getBuild( library.nodes[ id ], buildNode );
-
-				}
-
-				// visual scenes
-
-				function parseVisualScene( xml ) {
-
-					var data = {
-						name: xml.getAttribute( 'name' ),
-						children: []
-					};
-
-					prepareNodes( xml );
-
-					var elements = getElementsByTagName( xml, 'node' );
-
-					for ( var i = 0; i < elements.length; i ++ ) {
-
-						data.children.push( parseNode( elements[ i ] ) );
-
-					}
-
-					library.visualScenes[ xml.getAttribute( 'id' ) ] = data;
-
-				}
-
-				function buildVisualScene( data ) {
-
-					var group = new THREE.Group();
-					group.name = data.name;
-
-					var children = data.children;
-
-					for ( var i = 0; i < children.length; i ++ ) {
-
-						var child = children[ i ];
-
-						if ( child.id === null ) {
-
-							group.add( buildNode( child ) );
-
-						} else {
-
-							// if there is an ID, let's try to get the finished build (e.g. joints are already build)
-
-							group.add( getNode( child.id ) );
-
-						}
-
-					}
-
-					return group;
-
-				}
-
-				function getVisualScene( id ) {
-
-					return getBuild( library.visualScenes[ id ], buildVisualScene );
-
-				}
-
-				// scenes
-
-				function parseScene( xml ) {
-
-					var instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ];
-					return getVisualScene( parseId( instance.getAttribute( 'url' ) ) );
-
-				}
-
-				function setupAnimations() {
-
-					var clips = library.clips;
-
-					if ( isEmpty( clips ) === true ) {
-
-						if ( isEmpty( library.animations ) === false ) {
-
-							// if there are animations but no clips, we create a default clip for playback
-
-							var tracks = [];
-
-							for ( var id in library.animations ) {
-
-								var animationTracks = getAnimation( id );
-
-								for ( var i = 0, l = animationTracks.length; i < l; i ++ ) {
-
-									tracks.push( animationTracks[ i ] );
-
-								}
-
-							}
-
-							animations.push( new THREE.AnimationClip( 'default', - 1, tracks ) );
-
-						}
-
-					} else {
-
-						for ( var id in clips ) {
-
-							animations.push( getAnimationClip( id ) );
-
-						}
-
-					}
-
-				}
-
-				//console.time( 'THREE.ColladaLoader' );
-
-				if ( text.length === 0 ) {
-
-					return { scene: new THREE.Scene() };
-
-				}
-
-				//console.time( 'THREE.ColladaLoader: DOMParser' );
-
-				var xml = new DOMParser().parseFromString( text, 'application/xml' );
-
-				//console.timeEnd( 'THREE.ColladaLoader: DOMParser' );
-
-				var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ];
-
-				// metadata
-
-				var version = collada.getAttribute( 'version' );
-
-				var asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] );
-				var textureLoader = new THREE.TextureLoader( this.manager );
-				textureLoader.setPath( path ).setCrossOrigin( this.crossOrigin );
-
-				//
-
-				var animations = [];
-				var kinematics = {};
-				var count = 0;
-
-				//
-
-				var library = {
-					animations: {},
-					clips: {},
-					controllers: {},
-					images: {},
-					effects: {},
-					materials: {},
-					cameras: {},
-					lights: {},
-					geometries: {},
-					nodes: {},
-					visualScenes: {},
-					kinematicsModels: {},
-					kinematicsScenes: {}
-				};
-
-				var textureLoadedCallback = this.textureLoadedCallback;
-
-				//console.time( 'THREE.ColladaLoader: Parse' );
-
-				parseLibrary( collada, 'library_animations', 'animation', parseAnimation );
-				parseLibrary( collada, 'library_animation_clips', 'animation_clip', parseAnimationClip );
-				parseLibrary( collada, 'library_controllers', 'controller', parseController );
-				parseLibrary( collada, 'library_images', 'image', parseImage );
-				parseLibrary( collada, 'library_effects', 'effect', parseEffect );
-				parseLibrary( collada, 'library_materials', 'material', parseMaterial );
-				parseLibrary( collada, 'library_cameras', 'camera', parseCamera );
-				parseLibrary( collada, 'library_lights', 'light', parseLight );
-				parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry );
-				parseLibrary( collada, 'library_nodes', 'node', parseNode );
-				parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene );
-				parseLibrary( collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel );
-				parseLibrary( collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene );
-
-				//console.timeEnd( 'THREE.ColladaLoader: Parse' );
-
-				//console.time( 'THREE.ColladaLoader: Build' );
-
-				buildLibrary( library.animations, buildAnimation );
-				buildLibrary( library.clips, buildAnimationClip );
-				buildLibrary( library.controllers, buildController );
-				buildLibrary( library.images, buildImage );
-				buildLibrary( library.effects, buildEffect );
-				buildLibrary( library.materials, buildMaterial );
-				buildLibrary( library.cameras, buildCamera );
-				buildLibrary( library.lights, buildLight );
-				buildLibrary( library.geometries, buildGeometry );
-				buildLibrary( library.visualScenes, buildVisualScene );
-
-				//console.timeEnd( 'THREE.ColladaLoader: Build' );
-
-				setupAnimations();
-				setupKinematics();
-
-				var scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] );
-
-				/*if ( asset.upAxis === 'Z_UP' ) {
+    function setupAnimations() {
+      var clips = library.clips;
+
+      if (isEmpty(clips) === true) {
+        if (isEmpty(library.animations) === false) {
+          // if there are animations but no clips, we create a default clip for playback
+
+          var tracks = [];
+
+          for (var id in library.animations) {
+            var animationTracks = getAnimation(id);
+
+            for (var i = 0, l = animationTracks.length; i < l; i++) {
+              tracks.push(animationTracks[i]);
+            }
+          }
+
+          animations.push(new THREE.AnimationClip('default', -1, tracks));
+        }
+      } else {
+        for (var id in clips) {
+          animations.push(getAnimationClip(id));
+        }
+      }
+    }
+
+    //console.time( 'THREE.ColladaLoader' );
+
+    if (text.length === 0) {
+      return { scene: new THREE.Scene() };
+    }
+
+    //console.time( 'THREE.ColladaLoader: DOMParser' );
+
+    var xml = new DOMParser().parseFromString(text, 'application/xml');
+
+    //console.timeEnd( 'THREE.ColladaLoader: DOMParser' );
+
+    var collada = getElementsByTagName(xml, 'COLLADA')[0];
+
+    // metadata
+
+    var version = collada.getAttribute('version');
+
+    var asset = parseAsset(getElementsByTagName(collada, 'asset')[0]);
+    var textureLoader = new THREE.TextureLoader(this.manager);
+    textureLoader.setPath(path).setCrossOrigin(this.crossOrigin);
+
+    //
+
+    var animations = [];
+    var kinematics = {};
+    var count = 0;
+
+    //
+
+    var library = {
+      animations: {},
+      clips: {},
+      controllers: {},
+      images: {},
+      effects: {},
+      materials: {},
+      cameras: {},
+      lights: {},
+      geometries: {},
+      nodes: {},
+      visualScenes: {},
+      kinematicsModels: {},
+      kinematicsScenes: {}
+    };
+
+    var textureLoadedCallback = this.textureLoadedCallback;
+
+    //console.time( 'THREE.ColladaLoader: Parse' );
+
+    parseLibrary(collada, 'library_animations', 'animation', parseAnimation);
+    parseLibrary(
+      collada,
+      'library_animation_clips',
+      'animation_clip',
+      parseAnimationClip
+    );
+    parseLibrary(collada, 'library_controllers', 'controller', parseController);
+    parseLibrary(collada, 'library_images', 'image', parseImage);
+    parseLibrary(collada, 'library_effects', 'effect', parseEffect);
+    parseLibrary(collada, 'library_materials', 'material', parseMaterial);
+    parseLibrary(collada, 'library_cameras', 'camera', parseCamera);
+    parseLibrary(collada, 'library_lights', 'light', parseLight);
+    parseLibrary(collada, 'library_geometries', 'geometry', parseGeometry);
+    parseLibrary(collada, 'library_nodes', 'node', parseNode);
+    parseLibrary(
+      collada,
+      'library_visual_scenes',
+      'visual_scene',
+      parseVisualScene
+    );
+    parseLibrary(
+      collada,
+      'library_kinematics_models',
+      'kinematics_model',
+      parseKinematicsModel
+    );
+    parseLibrary(
+      collada,
+      'scene',
+      'instance_kinematics_scene',
+      parseKinematicsScene
+    );
+
+    //console.timeEnd( 'THREE.ColladaLoader: Parse' );
+
+    //console.time( 'THREE.ColladaLoader: Build' );
+
+    buildLibrary(library.animations, buildAnimation);
+    buildLibrary(library.clips, buildAnimationClip);
+    buildLibrary(library.controllers, buildController);
+    buildLibrary(library.images, buildImage);
+    buildLibrary(library.effects, buildEffect);
+    buildLibrary(library.materials, buildMaterial);
+    buildLibrary(library.cameras, buildCamera);
+    buildLibrary(library.lights, buildLight);
+    buildLibrary(library.geometries, buildGeometry);
+    buildLibrary(library.visualScenes, buildVisualScene);
+
+    //console.timeEnd( 'THREE.ColladaLoader: Build' );
+
+    setupAnimations();
+    setupKinematics();
+
+    var scene = parseScene(getElementsByTagName(collada, 'scene')[0]);
+
+    /*if ( asset.upAxis === 'Z_UP' ) {
 
 					scene.rotation.x = - Math.PI / 2;
 
 				}*/
 
-				scene.scale.multiplyScalar( asset.unit );
-
-				//console.timeEnd( 'THREE.ColladaLoader' );
-
-				return {
-					animations: animations,
-					kinematics: kinematics,
-					library: library,
-					scene: scene
-				};
+    scene.scale.multiplyScalar(asset.unit);
 
-			}
+    //console.timeEnd( 'THREE.ColladaLoader' );
 
-		};
+    return {
+      animations: animations,
+      kinematics: kinematics,
+      library: library,
+      scene: scene
+    };
+  }
+};
diff --git a/gz3d/client/js/include/ThreeBackwardsCompatibility.js b/gz3d/client/js/include/ThreeBackwardsCompatibility.js
index 2260fda..8bc34c8 100644
--- a/gz3d/client/js/include/ThreeBackwardsCompatibility.js
+++ b/gz3d/client/js/include/ThreeBackwardsCompatibility.js
@@ -2,21 +2,18 @@
  * Created by Sandro Weber (webers@in.tum.de) on 07.08.15.
  */
 
-
 /**
  * getDescendants() was removed in r68, this reimplements it to ensure compatibility
  * all getDescendants() calls could eventually be replaced with Object3D.traverse(function(node){...});
  */
-THREE.Object3D.prototype.getDescendants = function ( array ) {
-    if ( array === undefined ) array = [];
-
-    Array.prototype.push.apply( array, this.children );
-
-    for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+THREE.Object3D.prototype.getDescendants = function(array) {
+  if (array === undefined) array = [];
 
-        this.children[ i ].getDescendants( array );
+  Array.prototype.push.apply(array, this.children);
 
-    }
+  for (var i = 0, l = this.children.length; i < l; i++) {
+    this.children[i].getDescendants(array);
+  }
 
-    return array;
-};
\ No newline at end of file
+  return array;
+};
diff --git a/gz3d/client/js/include/avatar-controls.js b/gz3d/client/js/include/avatar-controls.js
index 455e67c..5d51035 100644
--- a/gz3d/client/js/include/avatar-controls.js
+++ b/gz3d/client/js/include/avatar-controls.js
@@ -5,8 +5,7 @@
 /* global THREE: true */
 /* global console: false */
 
-THREE.AvatarControls = function(userNavigationService, gz3d)
-{
+THREE.AvatarControls = function(userNavigationService, gz3d) {
   'use strict';
 
   var that = this;
@@ -40,7 +39,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
   this.azimuth = 0.0;
   this.zenith = 0.0;
   this.zenithMin = Math.PI;
-  this.zenithMax = 2*Math.PI;
+  this.zenithMax = 2 * Math.PI;
   this.azimuthOnRotStart = this.azimuth;
   this.zenithOnRotStart = this.zenith;
 
@@ -76,7 +75,9 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
   this.cameraLookDirection = new THREE.Vector3();
 
   // init ROS velocity topic for avatar movement
-  this.rosConnection = this.userNavigationService.roslib.getOrCreateConnectionTo(this.userNavigationService.rosbridgeWebsocketUrl);
+  this.rosConnection = this.userNavigationService.roslib.getOrCreateConnectionTo(
+    this.userNavigationService.rosbridgeWebsocketUrl
+  );
 
   this.linearVelocity = new THREE.Vector3();
 
@@ -94,22 +95,23 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
   };
 
   this.createAvatarTopics = function(avatarName) {
-    this.linearVelocityTopicName = '/' + avatarName + '/user_avatar_basic/body/cmd_vel';
+    this.linearVelocityTopicName =
+      '/' + avatarName + '/user_avatar_basic/body/cmd_vel';
     this.linearVelocityTopic = this.userNavigationService.roslib.createTopic(
-    this.rosConnection,
-    this.linearVelocityTopicName,
-    'geometry_msgs/Vector3'
+      this.rosConnection,
+      this.linearVelocityTopicName,
+      'geometry_msgs/Vector3'
     );
 
     this.avatarRotationTopicName = '/' + avatarName + '/cmd_rot';
     this.avatarRotationTopic = this.userNavigationService.roslib.createTopic(
-    this.rosConnection,
-    this.avatarRotationTopicName,
-    'geometry_msgs/Quaternion'
+      this.rosConnection,
+      this.avatarRotationTopicName,
+      'geometry_msgs/Quaternion'
     );
   };
 
-  this.onMouseDown = function (event) {
+  this.onMouseDown = function(event) {
     // HBP-NRP: The next three lines are commented since this leads to problems in chrome with respect
     // to AngularJS, also see: [NRRPLT-1992]
     //if (this.domElementPointerBindings !== document) {
@@ -135,7 +137,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     }
   };
 
-  this.onMouseUp = function (event) {
+  this.onMouseUp = function(event) {
     event.preventDefault();
 
     // We do not stop the event propagation here, since there may be other
@@ -156,7 +158,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     }
   };
 
-  this.onMouseMove = function (event) {
+  this.onMouseMove = function(event) {
     // only update the position, when a mouse button is pressed
     // else end the lookAround-mode
 
@@ -171,7 +173,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     }
   };
 
-  this.onTouchStart = function (event) {
+  this.onTouchStart = function(event) {
     switch (event.touches.length) {
       case 1:
         // look around
@@ -182,7 +184,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
           return;
         }
 
-        that.singleTouchPosOnStart.set(event.touches[0].pageX, event.touches[0].pageY);
+        that.singleTouchPosOnStart.set(
+          event.touches[0].pageX,
+          event.touches[0].pageY
+        );
         that.singleTouchPosCurrent.copy(that.singleTouchPosOnStart);
         that.azimuthOnRotStart = that.azimuth;
         that.zenithOnRotStart = that.zenith;
@@ -194,25 +199,36 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
         // move
         that.endLookAround();
 
-        var touch1 = new THREE.Vector2(event.touches[0].pageX, event.touches[0].pageY);
-        var touch2 = new THREE.Vector2(event.touches[1].pageX, event.touches[1].pageY);
+        var touch1 = new THREE.Vector2(
+          event.touches[0].pageX,
+          event.touches[0].pageY
+        );
+        var touch2 = new THREE.Vector2(
+          event.touches[1].pageX,
+          event.touches[1].pageY
+        );
 
         // Compute distance of both touches when they start touching the display
         that.doubleTouchDistanceOnStart = touch1.distanceTo(touch2);
 
         // Compute the mid of both touches
-        that.doubleTouchMidPosOnStart.addVectors(touch1, touch2).divideScalar(2.0);
+        that.doubleTouchMidPosOnStart
+          .addVectors(touch1, touch2)
+          .divideScalar(2.0);
 
         break;
     }
   };
 
-  this.onTouchMove = function (event) {
+  this.onTouchMove = function(event) {
     event.preventDefault();
     switch (event.touches.length) {
       case 1:
         // look around
-        that.singleTouchPosCurrent.set(event.touches[0].pageX, event.touches[0].pageY);
+        that.singleTouchPosCurrent.set(
+          event.touches[0].pageX,
+          event.touches[0].pageY
+        );
         break;
 
       case 2:
@@ -220,12 +236,19 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
         that.endLookAround();
 
         // Compute distance of both touches
-        var touch1 = new THREE.Vector2(event.touches[0].pageX, event.touches[0].pageY);
-        var touch2 = new THREE.Vector2(event.touches[1].pageX, event.touches[1].pageY);
+        var touch1 = new THREE.Vector2(
+          event.touches[0].pageX,
+          event.touches[0].pageY
+        );
+        var touch2 = new THREE.Vector2(
+          event.touches[1].pageX,
+          event.touches[1].pageY
+        );
 
         //TODO: this could become relative to the magnitude of the delta later
         that.doubleTouchDistanceCurrent = touch1.distanceTo(touch2);
-        var distanceDelta = that.doubleTouchDistanceCurrent - that.doubleTouchDistanceOnStart;
+        var distanceDelta =
+          that.doubleTouchDistanceCurrent - that.doubleTouchDistanceOnStart;
         // move forward / backward
         if (distanceDelta > that.TOUCH_MOVE_THRESHOLD) {
           that.moveForward = true;
@@ -236,8 +259,13 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
           that.moveBackward = false;
         }
 
-        that.doubleTouchMidPosCurrent.addVectors(touch1, touch2).divideScalar(2.0);
-        var posDelta = new THREE.Vector2().sub(that.doubleTouchMidPosCurrent, that.doubleTouchMidPosOnStart);
+        that.doubleTouchMidPosCurrent
+          .addVectors(touch1, touch2)
+          .divideScalar(2.0);
+        var posDelta = new THREE.Vector2().sub(
+          that.doubleTouchMidPosCurrent,
+          that.doubleTouchMidPosOnStart
+        );
         // move up / down
         if (posDelta.y > that.TOUCH_MOVE_THRESHOLD) {
           that.moveDown = true;
@@ -261,7 +289,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     }
   };
 
-  this.onTouchEnd = function (event) {
+  this.onTouchEnd = function(event) {
     switch (event.touches.length) {
       case 0:
         that.endLookAround();
@@ -290,82 +318,105 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     this.moveDown = false;
   };
 
-  this.onKeyDown = function (event) {
-    if(that.keyboardBindingsEnabled === false || event.metaKey || event.ctrlKey) {
+  this.onKeyDown = function(event) {
+    if (
+      that.keyboardBindingsEnabled === false ||
+      event.metaKey ||
+      event.ctrlKey
+    ) {
       return;
     }
     that.shiftHold = event.shiftKey;
     that.altHold = event.altKey;
-    switch(event.code) {
-      case "ArrowUp":
-        that.keyboardRotateUp = true; break;
-      case "KeyW":
-        that.moveForward = true; break;
+    switch (event.code) {
+      case 'ArrowUp':
+        that.keyboardRotateUp = true;
+        break;
+      case 'KeyW':
+        that.moveForward = true;
+        break;
 
-      case "ArrowLeft":
-        that.keyboardRotateLeft = true; break;
-      case "KeyA":
-        that.moveLeft = true; break;
+      case 'ArrowLeft':
+        that.keyboardRotateLeft = true;
+        break;
+      case 'KeyA':
+        that.moveLeft = true;
+        break;
 
-      case "ArrowDown":
-        that.keyboardRotateDown = true; break;
-      case "KeyS":
-        that.moveBackward = true; break;
+      case 'ArrowDown':
+        that.keyboardRotateDown = true;
+        break;
+      case 'KeyS':
+        that.moveBackward = true;
+        break;
 
-      case "ArrowRight":
-        that.keyboardRotateRight = true; break;
-      case "KeyD":
-        that.moveRight = true; break;
+      case 'ArrowRight':
+        that.keyboardRotateRight = true;
+        break;
+      case 'KeyD':
+        that.moveRight = true;
+        break;
 
-      case "PageUp":
-      case "KeyR":
-        that.moveUp = true; break;
+      case 'PageUp':
+      case 'KeyR':
+        that.moveUp = true;
+        break;
 
-      case "PageDown":
-      case "KeyF":
-        that.moveDown = true; break;
+      case 'PageDown':
+      case 'KeyF':
+        that.moveDown = true;
+        break;
     }
 
     event.preventDefault();
   };
 
-  this.onKeyUp = function (event) {
-    if(that.keyboardBindingsEnabled === false) {
+  this.onKeyUp = function(event) {
+    if (that.keyboardBindingsEnabled === false) {
       return;
     }
     that.shiftHold = event.shiftKey;
     that.altHold = event.altKey;
-    switch(event.code) {
-      case "ArrowUp":
-        that.keyboardRotateUp = false; break;
-      case "KeyW":
-        that.moveForward = false; break;
-
-      case "ArrowLeft":
-        that.keyboardRotateLeft = false; break;
-      case "KeyA":
-        that.moveLeft = false; break;
-
-      case "ArrowDown":
-        that.keyboardRotateDown = false; break;
-      case "KeyS":
-        that.moveBackward = false; break;
-
-      case "ArrowRight":
-        that.keyboardRotateRight = false; break;
-      case "KeyD":
-        that.moveRight = false; break;
-
-      case "PageUp":
-      case "KeyR":
-        that.moveUp = false; break;
-
-      case "PageDown":
-      case "KeyF":
-        that.moveDown = false; break;
-
-      case "KeyT":
-      {
+    switch (event.code) {
+      case 'ArrowUp':
+        that.keyboardRotateUp = false;
+        break;
+      case 'KeyW':
+        that.moveForward = false;
+        break;
+
+      case 'ArrowLeft':
+        that.keyboardRotateLeft = false;
+        break;
+      case 'KeyA':
+        that.moveLeft = false;
+        break;
+
+      case 'ArrowDown':
+        that.keyboardRotateDown = false;
+        break;
+      case 'KeyS':
+        that.moveBackward = false;
+        break;
+
+      case 'ArrowRight':
+        that.keyboardRotateRight = false;
+        break;
+      case 'KeyD':
+        that.moveRight = false;
+        break;
+
+      case 'PageUp':
+      case 'KeyR':
+        that.moveUp = false;
+        break;
+
+      case 'PageDown':
+      case 'KeyF':
+        that.moveDown = false;
+        break;
+
+      case 'KeyT': {
         that.thirdPerson = !that.thirdPerson;
         if (that.thirdPerson) {
           that.camera.position.set(0, -5, that.avatarEyeHeight + 1);
@@ -384,7 +435,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     // rotate left/right
     // rotation happens around the world up axis so up remains up (no upside-down)
     this.azimuth += rightAmount;
-    this.zenith = Math.max(this.zenithMin, Math.min(this.zenithMax, this.zenith - upAmount));
+    this.zenith = Math.max(
+      this.zenithMin,
+      Math.min(this.zenithMax, this.zenith - upAmount)
+    );
   };
 
   /**
@@ -396,7 +450,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     this.applyPosePosition = position;
     this.applyPoseLookAt = lookAt;
     this.applyPoseDuringUpdate = true;
-  }
+  };
 
   /**
    * set pose from a position and point to look at
@@ -404,7 +458,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
    * @param lookAt THREE.Vector3 point to look at (world coordinates)
    */
   this.setPose = function(position, lookAt) {
-    if (!(position instanceof THREE.Vector3) || !(lookAt instanceof THREE.Vector3)) {
+    if (
+      !(position instanceof THREE.Vector3) ||
+      !(lookAt instanceof THREE.Vector3)
+    ) {
       return;
     }
 
@@ -414,15 +471,17 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
       //TODO: animate "fall down"
       this.avatar.position.copy(posOnGround);
       this.avatar.updateMatrixWorld();
-    }
-    else {
+    } else {
       this.avatar.position.copy(position);
       this.avatar.updateMatrixWorld();
     }
 
     // update orientation
     var camWorldPosition = this.camera.getWorldPosition();
-    var vecForward = new THREE.Vector3().add(lookAt).sub(camWorldPosition).normalize();
+    var vecForward = new THREE.Vector3()
+      .add(lookAt)
+      .sub(camWorldPosition)
+      .normalize();
     this.updateSphericalAnglesFromForwardVector(vecForward);
   };
 
@@ -447,10 +506,16 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     // check for intersection below position
     var upVector = this.camera.up.clone();
     var raycaster = new THREE.Raycaster(position, upVector.negate());
-    var intersects = raycaster.intersectObjects(gz3d.scene.scene.children, true);
-    for (var i=0; i < intersects.length; i=i+1) {
+    var intersects = raycaster.intersectObjects(
+      gz3d.scene.scene.children,
+      true
+    );
+    for (var i = 0; i < intersects.length; i = i + 1) {
       // check that we hit some actual geometry and it is not our own avatar
-      if (intersects[i].object instanceof THREE.Mesh && !isObjectPartOfAvatar(intersects[i].object)) {
+      if (
+        intersects[i].object instanceof THREE.Mesh &&
+        !isObjectPartOfAvatar(intersects[i].object)
+      ) {
         return intersects[i].point;
       }
     }
@@ -458,8 +523,11 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     // if no hits below, check if avatar can be placed above
     raycaster.set(position, upVector.negate());
     intersects = raycaster.intersectObjects(gz3d.scene.scene.children, true);
-    for (var j=0; j < intersects.length; j=j+1) {
-      if (intersects[i].object instanceof THREE.Mesh && !isObjectPartOfAvatar(intersects[i].object)) {
+    for (var j = 0; j < intersects.length; j = j + 1) {
+      if (
+        intersects[i].object instanceof THREE.Mesh &&
+        !isObjectPartOfAvatar(intersects[i].object)
+      ) {
         return intersects[i].point;
       }
     }
@@ -477,19 +545,26 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     this.azimuth = Math.atan2(vecForward.y, vecForward.x) - 0.5 * Math.PI;
     this.azimuthOnRotStart = this.azimuth;
 
-    this.zenith = Math.max(this.zenithMin, Math.min(this.zenithMax, Math.acos(vecForward.z) + Math.PI));
+    this.zenith = Math.max(
+      this.zenithMin,
+      Math.min(this.zenithMax, Math.acos(vecForward.z) + Math.PI)
+    );
     this.zenithOnRotStart = this.zenith;
   };
 
-  this.updateSphericalAnglesFromUserInput = function(timeDelta, rotationSensitivity) {
+  this.updateSphericalAnglesFromUserInput = function(
+    timeDelta,
+    rotationSensitivity
+  ) {
     /* --- rotation by means of a manipulator --- */
-    var speedup ;
+    var speedup;
 
     if (this.shiftHold) speedup = 2.0;
     else if (this.altHold) speedup = 0.3;
     else speedup = 1.0;
 
-    var keyboardRotationSpeed = speedup * this.KEYBOARD_ROTATION_SPEED * rotationSensitivity;
+    var keyboardRotationSpeed =
+      speedup * this.KEYBOARD_ROTATION_SPEED * rotationSensitivity;
     if (this.keyboardRotateUp || this.keyboardRotateDown) {
       var sign = this.keyboardRotateUp ? 1.0 : -1.0;
       this.keyboardRotate(0.0, sign * keyboardRotationSpeed);
@@ -508,7 +583,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
         actualLookSpeed = 0;
       }
 
-      var mouseDelta = new THREE.Vector2().subVectors(this.mousePosCurrent, this.mousePosOnKeyDown);
+      var mouseDelta = new THREE.Vector2().subVectors(
+        this.mousePosCurrent,
+        this.mousePosOnKeyDown
+      );
 
       this.azimuth = this.azimuthOnRotStart - mouseDelta.x * actualLookSpeed;
 
@@ -516,7 +594,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
 
       if (this.lookVertical) {
         this.zenith = this.zenithOnRotStart + mouseDelta.y * actualLookSpeed;
-        this.zenith = Math.max(this.zenithMin, Math.min(this.zenithMax, this.zenith));
+        this.zenith = Math.max(
+          this.zenithMin,
+          Math.min(this.zenithMax, this.zenith)
+        );
       } else {
         this.zenith = Math.PI / 2;
       }
@@ -529,7 +610,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
         actualLookSpeed = 0;
       }
 
-      var touchDelta = new THREE.Vector2().subVectors(this.singleTouchPosCurrent, this.singleTouchPosOnStart);
+      var touchDelta = new THREE.Vector2().subVectors(
+        this.singleTouchPosCurrent,
+        this.singleTouchPosOnStart
+      );
 
       this.azimuth = this.azimuthOnRotStart + touchDelta.x * actualLookSpeed;
 
@@ -537,7 +621,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
 
       if (this.lookVertical) {
         this.zenith = this.zenithOnRotStart - touchDelta.y * actualLookSpeed;
-        this.zenith = Math.max(this.zenithMin, Math.min(this.zenithMax, this.zenith));
+        this.zenith = Math.max(
+          this.zenithMin,
+          Math.min(this.zenithMax, this.zenith)
+        );
       } else {
         this.zenith = Math.PI / 2;
       }
@@ -554,7 +641,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
      this.azimuth += 2 * Math.PI;
      }*/
 
-    this.avatarRotation.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.azimuth);
+    this.avatarRotation.setFromAxisAngle(
+      new THREE.Vector3(0, 0, 1),
+      this.azimuth
+    );
     this.avatarRotation.normalize();
   };
 
@@ -577,24 +667,24 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
    */
   this.updateCameraRotation = function() {
     var rotation = new THREE.Quaternion();
-    rotation.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(-1, 0, 0), this.zenith));
+    rotation.multiply(
+      new THREE.Quaternion().setFromAxisAngle(
+        new THREE.Vector3(-1, 0, 0),
+        this.zenith
+      )
+    );
     this.camera.quaternion.copy(rotation);
     this.camera.updateMatrixWorld();
   };
 
   this.updateLinearVelocity = function(delta, translationSensitivity) {
-
     var speedFactor;
 
     if (this.shiftHold) {
-      speedFactor =  this.SHIFT_SPEEDUP_FACTOR;
-    }
-    else if (this.altHold)
-    {
+      speedFactor = this.SHIFT_SPEEDUP_FACTOR;
+    } else if (this.altHold) {
       speedFactor = this.ALT_SPEEDUP_FACTOR;
-    }
-    else
-    {
+    } else {
       speedFactor = 1.0;
     }
 
@@ -613,7 +703,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     if (this.moveRight) {
       this.linearVelocity.x = 1;
     }
-    if (!(this.lockVerticalMovement)) {
+    if (!this.lockVerticalMovement) {
       if (this.moveUp) {
         this.linearVelocity.z = 1;
       }
@@ -641,8 +731,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
    */
   this.update = function(delta, translationSensitivity, rotationSensitivity) {
     if (!this.enabled) {
-      if (this.mouseRotationEnabled || this.touchRotationEnabled)
-      {
+      if (this.mouseRotationEnabled || this.touchRotationEnabled) {
         this.endLookAround();
       }
       return;
@@ -656,12 +745,13 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
       this.setPose(this.applyPosePosition, this.applyPoseLookAt);
       gz3d.scene.emitter.emit('entityChanged', this.avatar);
 
-      var vecForward = new THREE.Vector3().subVectors(this.applyPoseLookAt, this.applyPosePosition).normalize();
+      var vecForward = new THREE.Vector3()
+        .subVectors(this.applyPoseLookAt, this.applyPosePosition)
+        .normalize();
       this.updateSphericalAnglesFromForwardVector(vecForward);
 
       this.applyPoseDuringUpdate = false;
-    }
-    else {
+    } else {
       if (!this.freeze) {
         this.updateLinearVelocity(delta, translationSensitivity);
         this.publishLinearVelocity();
@@ -689,33 +779,96 @@ THREE.AvatarControls = function(userNavigationService, gz3d)
     this.domElementPointerBindings = userViewDOM ? userViewDOM : document;
     this.domElementKeyboardBindings = document;
 
-    this.domElementPointerBindings.addEventListener('mousedown', this.onMouseDown, false);
-    this.domElementPointerBindings.addEventListener('mousemove', this.onMouseMove, false);
-    this.domElementPointerBindings.addEventListener('mouseup', this.onMouseUp, false);
-    this.domElementPointerBindings.addEventListener('touchstart', this.onTouchStart, false);
-    this.domElementPointerBindings.addEventListener('touchmove', this.onTouchMove, false);
-    this.domElementPointerBindings.addEventListener('touchend', this.onTouchEnd, false);
+    this.domElementPointerBindings.addEventListener(
+      'mousedown',
+      this.onMouseDown,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'mousemove',
+      this.onMouseMove,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'mouseup',
+      this.onMouseUp,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'touchstart',
+      this.onTouchStart,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'touchmove',
+      this.onTouchMove,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'touchend',
+      this.onTouchEnd,
+      false
+    );
 
-    this.domElementKeyboardBindings.addEventListener('keydown', this.onKeyDown, false);
-    this.domElementKeyboardBindings.addEventListener('keyup',  this.onKeyUp, false);
+    this.domElementKeyboardBindings.addEventListener(
+      'keydown',
+      this.onKeyDown,
+      false
+    );
+    this.domElementKeyboardBindings.addEventListener(
+      'keyup',
+      this.onKeyUp,
+      false
+    );
   };
 
   this.detachEventListeners = function() {
     if (this.domElementPointerBindings) {
-      this.domElementPointerBindings.removeEventListener('mousedown', this.onMouseDown, false);
-      this.domElementPointerBindings.removeEventListener('mousemove', this.onMouseMove, false);
-      this.domElementPointerBindings.removeEventListener('mouseup', this.onMouseUp, false);
-      this.domElementPointerBindings.removeEventListener('touchstart', this.onTouchStart, false);
-      this.domElementPointerBindings.removeEventListener('touchmove', this.onTouchMove, false);
-      this.domElementPointerBindings.removeEventListener('touchend', this.onTouchEnd, false);
+      this.domElementPointerBindings.removeEventListener(
+        'mousedown',
+        this.onMouseDown,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'mousemove',
+        this.onMouseMove,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'mouseup',
+        this.onMouseUp,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'touchstart',
+        this.onTouchStart,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'touchmove',
+        this.onTouchMove,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'touchend',
+        this.onTouchEnd,
+        false
+      );
     }
 
     if (this.domElementKeyboardBindings) {
-      this.domElementKeyboardBindings.removeEventListener('keydown', this.onKeyDown, false);
-      this.domElementKeyboardBindings.removeEventListener('keyup', this.onKeyUp, false);
+      this.domElementKeyboardBindings.removeEventListener(
+        'keydown',
+        this.onKeyDown,
+        false
+      );
+      this.domElementKeyboardBindings.removeEventListener(
+        'keyup',
+        this.onKeyUp,
+        false
+      );
     }
   };
-
 };
 
 THREE.AvatarControls.prototype = Object.create(THREE.EventDispatcher.prototype);
diff --git a/gz3d/client/js/include/gzwebsocket.js b/gz3d/client/js/include/gzwebsocket.js
index aaf36ce..762ec4f 100644
--- a/gz3d/client/js/include/gzwebsocket.js
+++ b/gz3d/client/js/include/gzwebsocket.js
@@ -29,7 +29,7 @@ GZWebSocket.prototype.connect = function(url) {
    * @param event - the argument to emit with the event.
    */
   function onOpen(event) {
-    console.log("connected!");
+    console.log('connected!');
     that.emit('connection', event);
   }
 
@@ -66,16 +66,15 @@ GZWebSocket.prototype.connect = function(url) {
       }
     }
 
-    console.log("got msg!" + message.data);
+    console.log('got msg!' + message.data);
 
     var data = JSON.parse(message.data);
     handleMessage(data);
-
   }
 
-  console.log("connecting to " + url);
+  console.log('connecting to ' + url);
 
-  this.socket = new WebSocket(url, "default");
+  this.socket = new WebSocket(url, 'default');
   this.socket.onopen = onOpen;
   this.socket.onclose = onClose;
   this.socket.onerror = onError;
diff --git a/gz3d/client/js/include/lookat-controls.js b/gz3d/client/js/include/lookat-controls.js
index 629d7f4..0d4bc92 100644
--- a/gz3d/client/js/include/lookat-controls.js
+++ b/gz3d/client/js/include/lookat-controls.js
@@ -6,8 +6,7 @@
 /* global THREE: true */
 /* global console: false */
 
-THREE.LookatControls = function (userView)
-{
+THREE.LookatControls = function(userView) {
   'use strict';
 
   var that = this;
@@ -31,7 +30,7 @@ THREE.LookatControls = function (userView)
   this.startTouchDistance = new THREE.Vector2();
   this.startTouchMid = new THREE.Vector2();
 
-  this.mouseWheelSensitivity = Math.min(0.25*this.minDistance,0.25);
+  this.mouseWheelSensitivity = Math.min(0.25 * this.minDistance, 0.25);
   this.speedUpFactor = 3.0;
   this.speedDownFactor = 0.1;
 
@@ -49,8 +48,7 @@ THREE.LookatControls = function (userView)
   this.rotateUp = false;
   this.rotateDown = false;
 
-  this.onMouseDown = function (event)
-  {
+  this.onMouseDown = function(event) {
     // HBP-NRP: The next three lines are commented since this leads to problems in chrome with respect
     // to AngularJS, also see: [NRRPLT-1992]
     //if (this.domElement !== document) {
@@ -61,10 +59,8 @@ THREE.LookatControls = function (userView)
 
     event.preventDefault();
 
-    if (that.activeLook)
-    {
-      switch (event.button)
-      {
+    if (that.activeLook) {
+      switch (event.button) {
         case 0:
           that.mousePosLast.set(event.pageX, event.pageY);
           that.mousePosCurrent.copy(that.mousePosLast);
@@ -74,24 +70,19 @@ THREE.LookatControls = function (userView)
     }
   };
 
-  this.setLookatTarget = function (target)
-  {
+  this.setLookatTarget = function(target) {
     this.lookAtTarget = target;
 
-    if (this.lookAtTarget)
-    {
+    if (this.lookAtTarget) {
       var bbox = new THREE.Box3().setFromObject(this.lookAtTarget);
-      this.minDistance = bbox.getBoundingSphere().radius;
-      if (this.minDistance === Infinity)
-      {
+      this.minDistance = bbox.getBoundingSphere(new THREE.Sphere()).radius;
+      if (this.minDistance === Infinity) {
         this.minDistance = 0.25;
       }
     }
-
   };
 
-  this.onMouseUp = function (event)
-  {
+  this.onMouseUp = function(event) {
     event.preventDefault();
 
     // We do not stop the event propagation here, since there may be other
@@ -99,10 +90,8 @@ THREE.LookatControls = function (userView)
     // and expect the event to be fired.
     //event.stopPropagation();
 
-    if (that.activeLook)
-    {
-      switch (event.button)
-      {
+    if (that.activeLook) {
+      switch (event.button) {
         case 0:
           that.mouseDragOn = false;
           break;
@@ -110,29 +99,24 @@ THREE.LookatControls = function (userView)
     }
   };
 
-  this.onMouseMove = function (event)
-  {
+  this.onMouseMove = function(event) {
     // only update the position, when a mouse button is pressed
     // else end the lookAround-mode
-    if (event.buttons !== 0)
-    {
+    if (event.buttons !== 0) {
       that.mousePosCurrent.set(event.pageX, event.pageY);
-    } else
-    {
+    } else {
       that.mouseDragOn = false;
     }
   };
 
-  this.onMouseWheel = function (event)
-  {
-    var delta = Math.max(-1, Math.min(1, (-event.wheelDelta || event.detail)));
-    window.lookatControls.distanceDelta += delta * window.lookatControls.mouseWheelSensitivity;
+  this.onMouseWheel = function(event) {
+    var delta = Math.max(-1, Math.min(1, -event.wheelDelta || event.detail));
+    window.lookatControls.distanceDelta +=
+      delta * window.lookatControls.mouseWheelSensitivity;
   };
 
-  this.onTouchStart = function (event)
-  {
-    switch (event.touches.length)
-    {
+  this.onTouchStart = function(event) {
+    switch (event.touches.length) {
       case 1:
         // look around
         that.mousePosLast.set(event.touches[0].pageX, event.touches[0].pageY);
@@ -145,14 +129,15 @@ THREE.LookatControls = function (userView)
     }
   };
 
-  this.onTouchMove = function (event)
-  {
+  this.onTouchMove = function(event) {
     event.preventDefault();
-    switch (event.touches.length)
-    {
+    switch (event.touches.length) {
       case 1:
         // look around
-        that.mousePosCurrent.set(event.touches[0].pageX, event.touches[0].pageY);
+        that.mousePosCurrent.set(
+          event.touches[0].pageX,
+          event.touches[0].pageY
+        );
         break;
       case 2:
         that.mouseDragOn = false;
@@ -160,129 +145,133 @@ THREE.LookatControls = function (userView)
     }
   };
 
-  this.onTouchEnd = function (event)
-  {
+  this.onTouchEnd = function(event) {
     that.mouseDragOn = false;
   };
 
-  this.onKeyDown = function (event)
-  {
-    if (that.keyBindingsEnabled === false || event.metaKey || event.ctrlKey)
-    {
+  this.onKeyDown = function(event) {
+    if (that.keyBindingsEnabled === false || event.metaKey || event.ctrlKey) {
       return;
     }
     that.shiftHold = event.shiftKey;
     that.altHold = event.altKey;
-    switch (event.code)
-    {
-      case "PageUp":
-      case "KeyR":
-        that.moveForward = true; break;
+    switch (event.code) {
+      case 'PageUp':
+      case 'KeyR':
+        that.moveForward = true;
+        break;
 
-      case "PageDown":
-      case "KeyF":
-        that.moveBackward = true; break;
+      case 'PageDown':
+      case 'KeyF':
+        that.moveBackward = true;
+        break;
 
-      case "KeyA":
-        that.moveLeft = true; break;
+      case 'KeyA':
+        that.moveLeft = true;
+        break;
 
-      case "KeyD":
-        that.moveRight = true; break;
+      case 'KeyD':
+        that.moveRight = true;
+        break;
 
-      case "ArrowUp":
-        that.rotateUp = true; break;
+      case 'ArrowUp':
+        that.rotateUp = true;
+        break;
 
-      case "ArrowLeft":
-        that.rotateLeft = true; break;
+      case 'ArrowLeft':
+        that.rotateLeft = true;
+        break;
 
-      case "ArrowDown":
-        that.rotateDown = true; break;
+      case 'ArrowDown':
+        that.rotateDown = true;
+        break;
 
-      case "ArrowRight":
-        that.rotateRight = true; break;
+      case 'ArrowRight':
+        that.rotateRight = true;
+        break;
 
-      case "KeyW":
-        that.moveUp = true; break;
+      case 'KeyW':
+        that.moveUp = true;
+        break;
 
-      case "KeyS":
-        that.moveDown = true; break;
+      case 'KeyS':
+        that.moveDown = true;
+        break;
     }
 
     event.preventDefault();
   };
 
-  this.onKeyUp = function (event)
-  {
-    if (that.keyBindingsEnabled === false)
-    {
+  this.onKeyUp = function(event) {
+    if (that.keyBindingsEnabled === false) {
       return;
     }
     that.shiftHold = event.shiftKey;
     that.altHold = event.altKey;
-    switch (event.code)
-    {
-
-      case "PageUp":
-      case "KeyR":
-        that.moveForward = false; break;
+    switch (event.code) {
+      case 'PageUp':
+      case 'KeyR':
+        that.moveForward = false;
+        break;
 
-      case "PageDown":
-      case "KeyF":
-        that.moveBackward = false; break;
+      case 'PageDown':
+      case 'KeyF':
+        that.moveBackward = false;
+        break;
 
-      case "KeyA":
-        that.moveLeft = false; break;
+      case 'KeyA':
+        that.moveLeft = false;
+        break;
 
-      case "KeyD":
-        that.moveRight = false; break;
+      case 'KeyD':
+        that.moveRight = false;
+        break;
 
-      case "ArrowUp":
-        that.rotateUp = false; break;
+      case 'ArrowUp':
+        that.rotateUp = false;
+        break;
 
-      case "ArrowLeft":
-        that.rotateLeft = false; break;
+      case 'ArrowLeft':
+        that.rotateLeft = false;
+        break;
 
-      case "ArrowDown":
-        that.rotateDown = false; break;
+      case 'ArrowDown':
+        that.rotateDown = false;
+        break;
 
-      case "ArrowRight":
-        that.rotateRight = false; break;
+      case 'ArrowRight':
+        that.rotateRight = false;
+        break;
 
-      case "KeyW":
-        that.moveUp = false; break;
+      case 'KeyW':
+        that.moveUp = false;
+        break;
 
-      case "KeyS":
-        that.moveDown = false; break;
+      case 'KeyS':
+        that.moveDown = false;
+        break;
     }
 
     event.preventDefault();
   };
 
-  this.setDistance = function (dist)
-  {
+  this.setDistance = function(dist) {
     this.applyAbsDistance = dist;
   };
 
-  this.update = function (elapsed, translationSensitivity, rotationSensitivity)
-  {
-    if (!elapsed)
-    {
+  this.update = function(elapsed, translationSensitivity, rotationSensitivity) {
+    if (!elapsed) {
       elapsed = 0;
     }
 
-    if (this.shiftHold)
-    {
+    if (this.shiftHold) {
       elapsed *= this.speedUpFactor;
-    }
-    else if (this.altHold)
-    {
+    } else if (this.altHold) {
       elapsed *= this.speedDownFactor;
     }
 
-    if (!this.enabled)
-    {
-      if (this.mouseDragOn)
-      {
+    if (!this.enabled) {
+      if (this.mouseDragOn) {
         this.mouseDragOn = false;
       }
       return;
@@ -290,38 +279,30 @@ THREE.LookatControls = function (userView)
 
     var delta = null;
 
-    if (this.rotateLeft || this.moveLeft)
-    {
-      if (!delta)
-      {
-        delta = new THREE.Vector2(0,0);
+    if (this.rotateLeft || this.moveLeft) {
+      if (!delta) {
+        delta = new THREE.Vector2(0, 0);
       }
       delta.x += elapsed * 0.1;
     }
 
-    if (this.rotateRight || this.moveRight)
-    {
-      if (!delta)
-      {
-        delta = new THREE.Vector2(0,0);
+    if (this.rotateRight || this.moveRight) {
+      if (!delta) {
+        delta = new THREE.Vector2(0, 0);
       }
       delta.x -= elapsed * 0.1;
     }
 
-    if (this.rotateUp || this.moveForward)
-    {
-      if (!delta)
-      {
-        delta = new THREE.Vector2(0,0);
+    if (this.rotateUp || this.moveForward) {
+      if (!delta) {
+        delta = new THREE.Vector2(0, 0);
       }
       delta.y += elapsed * 0.1;
     }
 
-    if (this.rotateDown || this.moveBackward)
-    {
-      if (!delta)
-      {
-        delta = new THREE.Vector2(0,0);
+    if (this.rotateDown || this.moveBackward) {
+      if (!delta) {
+        delta = new THREE.Vector2(0, 0);
       }
       delta.y -= elapsed * 0.1;
     }
@@ -333,48 +314,53 @@ THREE.LookatControls = function (userView)
     currentDistance = this.currentVector.length();
     this.currentVector.normalize();
 
-    if (this.applyAbsDistance)
-    {
+    if (this.applyAbsDistance) {
       currentDistance = this.applyAbsDistance;
       this.applyAbsDistance = null;
     }
 
-    if (this.distanceDelta)
-    {
+    if (this.distanceDelta) {
       currentDistance += this.distanceDelta * translationSensitivity;
       this.distanceDelta = 0;
 
-      this.averageDistance = currentDistance = Math.max(Math.min(currentDistance, 20.0), this.minDistance);
+      this.averageDistance = currentDistance = Math.max(
+        Math.min(currentDistance, 20.0),
+        this.minDistance
+      );
     }
 
-
-    if (this.moveUp)
-    {
-      currentDistance -= elapsed * Math.min(0.004*this.minDistance,0.004);
-      currentDistance = Math.max(Math.min( currentDistance, 20.0), this.minDistance);
-      this.averageDistance = currentDistance = Math.max(Math.min(currentDistance, 20.0), this.minDistance);
+    if (this.moveUp) {
+      currentDistance -= elapsed * Math.min(0.004 * this.minDistance, 0.004);
+      currentDistance = Math.max(
+        Math.min(currentDistance, 20.0),
+        this.minDistance
+      );
+      this.averageDistance = currentDistance = Math.max(
+        Math.min(currentDistance, 20.0),
+        this.minDistance
+      );
     }
 
-    if (this.moveDown)
-    {
-      currentDistance += elapsed * Math.min(0.004*this.minDistance,0.004);
-      currentDistance = Math.max(Math.min( currentDistance, 20.0), this.minDistance);
-      this.averageDistance = currentDistance = Math.max(Math.min(currentDistance, 20.0), this.minDistance);
+    if (this.moveDown) {
+      currentDistance += elapsed * Math.min(0.004 * this.minDistance, 0.004);
+      currentDistance = Math.max(
+        Math.min(currentDistance, 20.0),
+        this.minDistance
+      );
+      this.averageDistance = currentDistance = Math.max(
+        Math.min(currentDistance, 20.0),
+        this.minDistance
+      );
     }
 
-    if (this.averageDistance <= 0)
-    {
+    if (this.averageDistance <= 0) {
       this.averageDistance = currentDistance;
     }
 
-    currentDistance = Math.min(currentDistance, this.averageDistance*1.2);
+    currentDistance = Math.min(currentDistance, this.averageDistance * 1.2);
 
-
-    if (this.mouseDragOn)
-    {
-
-      if (!delta)
-      {
+    if (this.mouseDragOn) {
+      if (!delta) {
         delta = new THREE.Vector2();
       }
 
@@ -384,22 +370,25 @@ THREE.LookatControls = function (userView)
       this.mousePosLast = this.mousePosCurrent.clone();
     }
 
-    if (delta)
-    {
-      this.currentVector.applyAxisAngle(new THREE.Vector3(0, 0, -1), delta.x * 0.01 * rotationSensitivity);
+    if (delta) {
+      this.currentVector.applyAxisAngle(
+        new THREE.Vector3(0, 0, -1),
+        delta.x * 0.01 * rotationSensitivity
+      );
 
       this.currentVector.z += delta.y * 0.01 * rotationSensitivity;
       this.currentVector.normalize();
       this.currentVector.z = Math.max(this.currentVector.z, 0.18);
       this.currentVector.normalize();
       this.averageZVector = this.currentVector.z;
-    }
-    else if (this.averageZVector<=0)
-    {
+    } else if (this.averageZVector <= 0) {
       this.averageZVector = this.currentVector.z;
     }
 
-    this.currentVector.z = Math.max(this.currentVector.z, this.averageZVector*0.8);
+    this.currentVector.z = Math.max(
+      this.currentVector.z,
+      this.averageZVector * 0.8
+    );
     this.currentVector.normalize();
 
     var v = this.currentVector.clone();
@@ -410,67 +399,151 @@ THREE.LookatControls = function (userView)
     this.userView.camera.updateMatrixWorld();
   };
 
-  this.onMouseDownManipulator = function (action)
-  {
+  this.onMouseDownManipulator = function(action) {
     that[action] = true;
   };
 
-  this.onMouseUpManipulator = function (action)
-  {
+  this.onMouseUpManipulator = function(action) {
     that[action] = false;
   };
 
-  this.attachEventListeners = function ()
-  {
+  this.attachEventListeners = function() {
     var userViewDOM = this.userView.container;
     this.domElementPointerBindings = userViewDOM ? userViewDOM : document;
     this.domElementKeyboardBindings = document;
 
-    this.domElementPointerBindings.addEventListener('contextmenu', function (event) { event.preventDefault(); }, false);
-    this.domElementPointerBindings.addEventListener('mousedown', this.onMouseDown, false);
-    this.domElementPointerBindings.addEventListener('mousemove', this.onMouseMove, false);
-    this.domElementPointerBindings.addEventListener('mouseup', this.onMouseUp, false);
-    this.domElementPointerBindings.addEventListener('touchstart', this.onTouchStart, false);
-    this.domElementPointerBindings.addEventListener('touchmove', this.onTouchMove, false);
-    this.domElementPointerBindings.addEventListener('touchend', this.onTouchEnd, false);
-
-    this.domElementPointerBindings.addEventListener('mousewheel', this.onMouseWheel, false);
-    this.domElementPointerBindings.addEventListener('DOMMouseScroll', this.onMouseWheel, false);
-
-    this.domElementKeyboardBindings.addEventListener('keydown', this.onKeyDown, false);
-    this.domElementKeyboardBindings.addEventListener('keyup', this.onKeyUp, false);
+    this.domElementPointerBindings.addEventListener(
+      'contextmenu',
+      function(event) {
+        event.preventDefault();
+      },
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'mousedown',
+      this.onMouseDown,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'mousemove',
+      this.onMouseMove,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'mouseup',
+      this.onMouseUp,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'touchstart',
+      this.onTouchStart,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'touchmove',
+      this.onTouchMove,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'touchend',
+      this.onTouchEnd,
+      false
+    );
+
+    this.domElementPointerBindings.addEventListener(
+      'mousewheel',
+      this.onMouseWheel,
+      false
+    );
+    this.domElementPointerBindings.addEventListener(
+      'DOMMouseScroll',
+      this.onMouseWheel,
+      false
+    );
+
+    this.domElementKeyboardBindings.addEventListener(
+      'keydown',
+      this.onKeyDown,
+      false
+    );
+    this.domElementKeyboardBindings.addEventListener(
+      'keyup',
+      this.onKeyUp,
+      false
+    );
   };
 
-  this.detachEventListeners = function ()
-  {
+  this.detachEventListeners = function() {
     if (this.domElementPointerBindings) {
-      this.domElementPointerBindings.removeEventListener('contextmenu', function (event) { event.preventDefault(); }, false);
-      this.domElementPointerBindings.removeEventListener('mousedown', this.onMouseDown, false);
-      this.domElementPointerBindings.removeEventListener('mousemove', this.onMouseMove, false);
-      this.domElementPointerBindings.removeEventListener('mouseup', this.onMouseUp, false);
-      this.domElementPointerBindings.removeEventListener('touchstart', this.onTouchStart, false);
-      this.domElementPointerBindings.removeEventListener('touchmove', this.onTouchMove, false);
-      this.domElementPointerBindings.removeEventListener('touchend', this.onTouchEnd, false);
-
-      this.domElementPointerBindings.removeEventListener('mousewheel', this.onMouseWheel, false);
-      this.domElementPointerBindings.removeEventListener('DOMMouseScroll', this.onMouseWheel, false);
+      this.domElementPointerBindings.removeEventListener(
+        'contextmenu',
+        function(event) {
+          event.preventDefault();
+        },
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'mousedown',
+        this.onMouseDown,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'mousemove',
+        this.onMouseMove,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'mouseup',
+        this.onMouseUp,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'touchstart',
+        this.onTouchStart,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'touchmove',
+        this.onTouchMove,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'touchend',
+        this.onTouchEnd,
+        false
+      );
+
+      this.domElementPointerBindings.removeEventListener(
+        'mousewheel',
+        this.onMouseWheel,
+        false
+      );
+      this.domElementPointerBindings.removeEventListener(
+        'DOMMouseScroll',
+        this.onMouseWheel,
+        false
+      );
     }
 
     if (this.domElementKeyboardBindings) {
-      this.domElementKeyboardBindings.removeEventListener('keydown', this.onKeyDown, false);
-      this.domElementKeyboardBindings.removeEventListener('keyup', this.onKeyUp, false);
+      this.domElementKeyboardBindings.removeEventListener(
+        'keydown',
+        this.onKeyDown,
+        false
+      );
+      this.domElementKeyboardBindings.removeEventListener(
+        'keyup',
+        this.onKeyUp,
+        false
+      );
     }
-
   };
 
-  function bind(scope, fn)
-  {
-    return function ()
-    {
+  function bind(scope, fn) {
+    return function() {
       fn.apply(scope, arguments);
     };
   }
-
 };
 
 THREE.LookatControls.prototype = Object.create(THREE.EventDispatcher.prototype);
diff --git a/gz3d/src/gz3d-fluid-vis.js b/gz3d/src/gz3d-fluid-vis.js
index 49305bb..9563bff 100644
--- a/gz3d/src/gz3d-fluid-vis.js
+++ b/gz3d/src/gz3d-fluid-vis.js
@@ -1,5 +1,4 @@
-
-GZ3D.VisualFluidModel = function (scene) {
+GZ3D.VisualFluidModel = function(scene) {
   this.scene = scene;
   this.rootGroup = new THREE.Group();
   this.rootGroup.name = 'VisualFluidModel';
@@ -7,7 +6,7 @@ GZ3D.VisualFluidModel = function (scene) {
   this.rootGroup.visible = true;
 };
 
-GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) {
+GZ3D.VisualFluidModel.prototype.initFluidBuffers = function(message) {
   this.scales = new Float32Array(message.position.length);
   this.scales.fill(10.0);
   this.positions = new Float32Array(message.position.length * 3);
@@ -15,7 +14,10 @@ GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) {
   this.positionAttribute = new THREE.BufferAttribute(this.positions, 3);
   this.positionAttribute.setDynamic(true);
   this.geometry.addAttribute('position', this.positionAttribute);
-  this.geometry.addAttribute('scale', new THREE.BufferAttribute(this.scales, 1));
+  this.geometry.addAttribute(
+    'scale',
+    new THREE.BufferAttribute(this.scales, 1)
+  );
   this.material = new THREE.ParticleBasicMaterial({
     size: 5.0,
     sizeAttenuation: false,
@@ -29,11 +31,15 @@ GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) {
   this.rootGroup.add(this.particles);
 };
 
-GZ3D.VisualFluidModel.prototype.updateVisualization = function (message) {
+GZ3D.VisualFluidModel.prototype.updateVisualization = function(message) {
   var vertex;
   for (var i = 0, l = message.position.length; i < l; i++) {
-    vertex = new THREE.Vector3(message.position[i].x, message.position[i].y, message.position[i].z);
+    vertex = new THREE.Vector3(
+      message.position[i].x,
+      message.position[i].y,
+      message.position[i].z
+    );
     vertex.toArray(this.positions, i * 3);
   }
   this.positionAttribute.needsUpdate = true;
-};
\ No newline at end of file
+};
diff --git a/gz3d/src/gziface.js b/gz3d/src/gziface.js
index 96c6b16..2d4e87c 100644
--- a/gz3d/src/gziface.js
+++ b/gz3d/src/gziface.js
@@ -363,11 +363,11 @@ GZ3D.GZIface.prototype.onConnected = function() {
     ros: this.webSocket,
     name: '~/fluid_pos',
     message_type: 'fluid',
-    throttle_rate: 1.0 / 200.0 * 1000.0,
+    throttle_rate: 1.0 / 200.0 * 1000.0
   });
 
   // function for updating client system visualization
-  var updateMuscleVisualization = function (message) {
+  var updateMuscleVisualization = function(message) {
     if (!(message.robot_name in this.scene.muscleVisuzalizations)) {
       this.scene.muscleVisuzalizations[
         message.robot_name
@@ -380,11 +380,13 @@ GZ3D.GZIface.prototype.onConnected = function() {
       );
     }
   };
- 
+
   // function for updating client system visualization
-  var updateFluidVisualization = function (message) {
+  var updateFluidVisualization = function(message) {
     if (!this.scene.fluidVisualizations.length) {
-      this.scene.fluidVisualizations.push(new GZ3D.VisualFluidModel(this.scene, message));
+      this.scene.fluidVisualizations.push(
+        new GZ3D.VisualFluidModel(this.scene, message)
+      );
       this.scene.fluidVisualizations[0].initFluidBuffers(message);
     }
     this.scene.fluidVisualizations[0].updateVisualization(message);
@@ -396,9 +398,11 @@ GZ3D.GZIface.prototype.onConnected = function() {
   );
 
   // Subscription to fluid_pos topic
-  this.fluidVisualizationSubscriber.subscribe(updateFluidVisualization.bind(this));
+  this.fluidVisualizationSubscriber.subscribe(
+    updateFluidVisualization.bind(this)
+  );
 
-  var requestUpdate = function (message) {
+  var requestUpdate = function(message) {
     if (message.request === 'entity_delete') {
       var entity = this.scene.getByName(message.data);
       if (entity) {
diff --git a/gz3d/src/gzlabelmanager.js b/gz3d/src/gzlabelmanager.js
index 97369b3..7647921 100644
--- a/gz3d/src/gzlabelmanager.js
+++ b/gz3d/src/gzlabelmanager.js
@@ -195,7 +195,8 @@ GZ3D.LabelManager.prototype.onRender = function() {
         var labelPos = [];
 
         bbox.setFromObject(root);
-        var bsphere = bbox.getBoundingSphere();
+        var bsphere = new THREE.Sphere();
+        bbox.getBoundingSphere(bsphere);
 
         for (var i = 0; i < labelInfoList.length; i++) {
           var labelInfo = labelInfoList[i];
diff --git a/package.json b/package.json
index 4f06b1f..190a859 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,6 @@
     ],
     "*.js": [
       "node_modules/prettier/bin/prettier.js --write",
-      "node_modules/eslint/bin/eslint.js",
       "git add"
     ]
   },
@@ -32,4 +31,4 @@
   "devDependencies": {
     "prettier": "1.7.4"
   }
-}
+}
\ No newline at end of file
-- 
GitLab