text: elation.require(['engine.external.leapmotion.leap'], function() {
    elation.component.add('engine.things.leapmotion', function() {
      this.postinit = function() {
        Leap.loop(elation.bind(this, this.loop));
      }
      this.createChildren = function() {
        this.hands = {
          left: this.spawn('leapmotion_hand', 'hand_left', { position: new THREE.Vector3(-.1, 0, 0) }),
          right: this.spawn('leapmotion_hand', 'hand_right', { position: new THREE.Vector3(.1, 0, 0) })
        };
      }
      this.loop = function(frame) {
        for (var i = 0; i < frame.hands.length; i++) {
          var hand = frame.hands[i];
          var handobj = this.hands[hand.type];
          if (handobj) {
            handobj.updateData(hand, 1);
          }
        }
        
      }
    }, elation.engine.things.generic);
  
    elation.component.add('engine.things.leapmotion_hand', function() {
      this.postinit = function() {
        this.palmrotation = new THREE.Euler();
        this.palmPosition = new THREE.Vector3();
        this.palmOrientation = new THREE.Quaternion();
        this.fingerTips = [
          new THREE.Vector3(),
          new THREE.Vector3(),
          new THREE.Vector3(),
          new THREE.Vector3(),
          new THREE.Vector3()
        ];
      }
      this.createObject3D = function() {
        var hand = new THREE.Object3D();
        return hand;
      }
      this.createChildren = function() {
        //var palmsize = [.07,.01,.05];
        var palmsize = [1,.01,1];
        this.materials = {
          bones: new THREE.MeshStandardMaterial({color: 0xdddddd, transparent: false, opacity: 1, blending: THREE.NormalBlending, side: THREE.DoubleSide, envMap: this.engine.systems.world.scene["world-3d"].background, roughness: .75}),
          joints: new THREE.MeshPhongMaterial({color: 0xccffcc, transparent: false, opacity: 1, blending: THREE.NormalBlending}),
          tips: new THREE.MeshPhongMaterial({color: 0xffcccc, transparent: true, opacity: .2, blending: THREE.NormalBlending})
        };
        this.palm = new THREE.Mesh(new THREE.BoxBufferGeometry(palmsize[0], palmsize[1], palmsize[2]), this.materials.bones);
        this.palm.geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(0, 0, -palmsize[2] / 4)));
        this.palm.scale.set(.01,.01,.01);
        this.palm.updateMatrix();
        this.palm.matrixAutoUpdate = false;
        this.palm.castShadow = true;
        this.palm.receiveShadow = true;
        this.objects['3d'].add(this.palm);
  
        this.arm = new THREE.Mesh(new THREE.CylinderBufferGeometry(0.75, 0.5, 1, undefined, undefined, true), this.materials.bones);
        //this.arm.geometry.applyMatrix(new THREE.Matrix4().setPosition(new THREE.Vector3(0, 0, -palmsize[2] / 4)));
        this.arm.geometry.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI/2));
        this.arm.scale.set(.01,.01,.01);
        this.arm.updateMatrix();
        this.arm.matrixAutoUpdate = false;
        this.arm.castShadow = true;
        this.arm.receiveShadow = true;
        this.objects['3d'].add(this.arm);
  
        this.wrist = new THREE.Mesh(new THREE.SphereBufferGeometry(0.5), this.materials.joints);
        this.wrist.scale.set(.01,.01,.01);
        this.wrist.updateMatrix();
        //this.wrist.matrixAutoUpdate = false;
        this.objects['3d'].add(this.wrist);
  
        this.fingers = [];
        for (var i = 0; i < 5; i++) {
          this.fingers[i] = this.spawn('leapmotion_finger', this.name + '_finger_' + i, { hand: this });
        }
      }
      this.updateData = (function() {
        var pos = new THREE.Vector3(),
            xdir = new THREE.Vector3(),
            ydir = new THREE.Vector3(),
            zdir = new THREE.Vector3(),
            fingerbase1 = new THREE.Vector3(),
            fingerbase2 = new THREE.Vector3(),
            fingerbase3 = new THREE.Vector3();
  
        return function(data, scalefactor) {
          if (!scalefactor) scalefactor = 1;
          if (!this.palm) return;
  
          for (var i = 0; i < data.fingers.length; i++) {
            this.fingers[i].updateData(data.fingers[i], scalefactor);
            if (data.fingers[i].tipPosition) {
              this.fingerTips[i].fromArray(data.fingers[i].tipPosition).multiplyScalar(scalefactor);
            }
          }
  
          pos.fromArray(data.palmPosition).multiplyScalar(scalefactor);
          zdir.fromArray(data.direction);
          ydir.fromArray(data.palmNormal);
          xdir.crossVectors(ydir, zdir);
          fingerbase1.setFromMatrixPosition(this.fingers[1].phalanges[0].matrixWorld);
          fingerbase2.setFromMatrixPosition(this.fingers[4].phalanges[0].matrixWorld);
          fingerbase3.setFromMatrixPosition(this.fingers[1].phalanges[1].matrixWorld);
  
          var scale = [
            fingerbase1.distanceTo(fingerbase2) * 1.4,
            1,
            fingerbase1.distanceTo(fingerbase3) * 1.4
          ];
          this.palm.matrix.set(
            xdir.x * scale[0], ydir.x * scale[1], zdir.x * scale[2], pos.x,
            xdir.y * scale[0], ydir.y * scale[1], zdir.y * scale[2], pos.y,
            xdir.z * scale[0], ydir.z * scale[1], zdir.z * scale[2], pos.z,
            0, 0, 0, 1
          );
          this.palm.updateMatrixWorld();
  
          this.palmPosition.setFromMatrixPosition(this.palm.matrixWorld);
          this.palmOrientation.setFromRotationMatrix(this.palm.matrixWorld);
  
          if (data.arm) {
            pos.fromArray(data.arm.center()).multiplyScalar(scalefactor);
            xdir.fromArray(data.arm.basis[0]);
            ydir.fromArray(data.arm.basis[1]);
            zdir.fromArray(data.arm.basis[2]);
            scale = [
              data.arm.width * scalefactor,
              data.arm.width * scalefactor * .75,
              data.arm.length * scalefactor
            ];
            this.arm.matrix.set(
              xdir.x * scale[0], ydir.x * scale[1], zdir.x * scale[2], pos.x,
              xdir.y * scale[0], ydir.y * scale[1], zdir.y * scale[2], pos.y,
              xdir.z * scale[0], ydir.z * scale[1], zdir.z * scale[2], pos.z,
              0, 0, 0, 1
            );
            this.arm.updateMatrixWorld();
  
            this.wrist.position.fromArray(data.arm.nextJoint).multiplyScalar(scalefactor);
            this.wrist.rotation.setFromRotationMatrix(this.arm.matrix);
            this.wrist.scale.set(scale[0], scale[1], scale[1]/4);
          }
  
          if (!this.materials.bones.envMap) {
            this.materials.bones.needsUpdate = true;
          }
          var worldbg = this.engine.systems.world.scene["world-3d"].background;
          this.materials.bones.envMap = worldbg;
          this.refresh();
        };
      })();
      this.setState = function(state, transform) {
        var values = state.split(' ').map(parseFloat);
        //console.log(values);
        var pos = new THREE.Vector3();
        var data = {
          palmPosition: pos.fromArray([values[12], values[13], values[14]]).applyMatrix4(transform).toArray(),
          direction: [values[8], values[9], values[10]],
          palmNormal: [values[4], values[5], values[6]],
          fingers: []
        };
  
        for (var i = 0; i < 5; i++) {
          data.fingers[i] = {
            bones: [],
            positions: [[0,0,0]]
          };
          for (var j = 0; j < 4; j++) {
            var offset = (i * 4 + j + 1) * 16;
            var center = pos.fromArray(values.slice(offset + 12, offset + 15)).toArray();
            var bone = {
              basis: [
                // Change order to match native: Y, Z, X
                values.slice(offset + 4, offset + 7),
                values.slice(offset + 8, offset + 11),
                values.slice(offset, offset + 3),
  /*
                values.slice(offset, offset + 3),
                values.slice(offset + 4, offset + 7),
                values.slice(offset + 8, offset + 11),
  */
              ],
              centerpos: center,
              length: .0200,
            };
            (function(c) {
              bone.center = function() { return c; };
            })(center);
            data.fingers[i].bones[j] = bone;
          }
          for (j = 0; j < 4; j++) {
            var bone = data.fingers[i].bones[j];
            var nextbone = data.fingers[i].bones[j + 1];
            if (nextbone) {
              bone.length = pos.fromArray(bone.centerpos).distanceTo(new THREE.Vector3().fromArray(nextbone.centerpos));
            }
            var basepos = pos.fromArray(bone.centerpos).add(new THREE.Vector3().fromArray(bone.basis[2]).normalize().multiplyScalar(-bone.length/2));
            data.fingers[i].positions[j+1] = basepos.toArray();
          };
        }
        this.updateData(data, 1);
      }
      this.serializeMatrix = (function() {
        var xdir = new THREE.Vector3(),
            ydir = new THREE.Vector3(),
            zdir = new THREE.Vector3(),
            pos = new THREE.Vector3(),
            pos2 = new THREE.Vector3();
        var mat4 = new THREE.Matrix4();
        var inverse = new THREE.Matrix4();
  
        return function(matrix, transform) {
          // Change order to match native: Y, Z, X
          mat4.copy(matrix);
          if (false && transform) {
            inverse.getInverse(transform);
            mat4.multiplyMatrices(inverse, matrix);
          }
  /*
          var m = [];
          for (var i = 0; i < 16; i++) {
            m[i] = mat4.elements[i].toFixed(4);
          }
          var r = [
            m[4], m[5], m[6], m[3],
            m[0], m[1], m[2], m[11],
            m[8], m[9], m[10], m[7],
            m[12], m[13], m[14], m[15],
          ];
  */
          pos.setFromMatrixPosition(mat4);
          if (transform) {
            //transform.worldToLocal(pos);
          }
          mat4.extractBasis(xdir, ydir, zdir);
          xdir.normalize();
          ydir.normalize();
          zdir.normalize();
  
          //inverse.makeBasis(xdir, ydir, zdir);
          //inverse.makeBasis(xdir, zdir, ydir);
          //inverse.makeBasis(ydir, xdir, zdir);
          //inverse.makeBasis(ydir, zdir, xdir);
          //inverse.makeBasis(zdir, xdir, ydir);
          //inverse.makeBasis(zdir, ydir, xdir);
          //inverse.setPosition(pos);
  //console.log(xdir, ydir, zdir);
  /*
          inverse.set(
            xdir.x, ydir.x, zdir.x, pos.x,
            xdir.y, ydir.y, zdir.y, pos.y,
            xdir.z, ydir.z, zdir.z, pos.z,
            0, 0, 0, 1
          );
  */
          inverse.set(
            xdir.x, xdir.y, xdir.z, 0,
            ydir.x, ydir.y, ydir.z, 0,
            zdir.x, zdir.y, zdir.x, 0,
            pos.x, pos.y, pos.z, 1
          );
  
  
          return inverse.toArray().join(' ');
        };
      })();
      this.getState = function(transform) {
        var state = '';
        state += this.serializeMatrix(this.palm.matrixWorld, transform);
  
        for (var i = 0; i < 5; i++) {
          for (var j = 0; j < 4; j++) {
            state += ' ' + this.serializeMatrix(this.fingers[i].phalanges[j].matrixWorld, transform);
          }
        }
        return state;
      }
    }, elation.engine.things.generic);
    elation.component.add('engine.things.leapmotion_finger', function() {
      this.postinit = function() {
        this.defineProperties({
          hand: { type: 'object' }
        });
      }
      this.createObject3D = function() {
        var obj = new THREE.Object3D();
        this.phalanges = [];
        this.joints = [];
        var material = this.hand.materials.bones;
        var jointmaterial = this.hand.materials.joints;
        var tipmaterial = this.hand.materials.tips;
        var fingersizes = [0.011, 0.011, 0.00985, 0.00806, 0.00481, 0.00388];
        var fingerSize = function(i) {
          //return Math.pow(.075 / (i + 3), 1.1);
          return fingersizes[i];
        }
        for (var i = 0; i < 4; i++) {
          this.phalanges[i] = new THREE.Mesh(new THREE.CylinderBufferGeometry(fingerSize(i), fingerSize(i+1), 1, 6, 2, false), material);
          this.joints[i] = new THREE.Mesh(new THREE.SphereBufferGeometry(fingerSize(i+1)), jointmaterial);
          this.joints[i].castShadow = true;
          this.joints[i].receiveShadow = true;
          this.phalanges[i].geometry.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI/2));
          this.phalanges[i].scale.z = .01;
          this.phalanges[i].updateMatrix();
          this.phalanges[i].matrixAutoUpdate = false;
          this.phalanges[i].castShadow = true;
          this.phalanges[i].receiveShadow = true;
          obj.add(this.phalanges[i]);
          if (i > 0) {
            obj.add(this.joints[i]);
          }
        }
        this.joints[4] = new THREE.Mesh(new THREE.SphereBufferGeometry(fingerSize(5)), tipmaterial.clone());
        this.fingertip = this.joints[4];
        obj.add(this.joints[4]);
        return obj;
      }
      this.updateData = function(data, scalefactor) {
        for (var i = 0; i < data.bones.length; i++) {
          var bone = data.bones[i];
          var center = bone.center();
          var length = Math.abs(Math.max(bone.length, .1) * scalefactor);
  
          if (length > 0) {
            //this.phalanges[i].scale.z = length * scalefactor;
          } else if (this.phalanges[i].parent) {
            this.objects['3d'].remove(this.phalanges[i]);
          }
          this.phalanges[i].matrix.set(
            bone.basis[0][0], bone.basis[1][0], bone.basis[2][0] * length, center[0] * scalefactor,
            bone.basis[0][1], bone.basis[1][1], bone.basis[2][1] * length, center[1] * scalefactor,
            bone.basis[0][2], bone.basis[1][2], bone.basis[2][2] * length, center[2] * scalefactor,
            0, 0, 0, 1
          );
          this.phalanges[i].updateMatrixWorld();
        }
        this.joints[0].position.fromArray(data.positions[0]).multiplyScalar(scalefactor);
        this.joints[1].position.fromArray(data.positions[1]).multiplyScalar(scalefactor);
        this.joints[2].position.fromArray(data.positions[2]).multiplyScalar(scalefactor);
        this.joints[3].position.fromArray(data.positions[3]).multiplyScalar(scalefactor);
        this.joints[4].position.fromArray(data.positions[4]).multiplyScalar(scalefactor);
  
        this.refresh();
      }
    }, elation.engine.things.generic);
    
  });
  