text: //
  //  walk.js
  //
  //  version 1.12
  //
  //  Created by David Wooldridge, Autumn 2014
  //
  //  Animates an avatar using procedural animation techniques
  //
  //  Distributed under the Apache License, Version 2.0.
  //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
  //
  
  // constants
  var MALE = 1;
  var FEMALE = 2;
  var MAX_WALK_SPEED = 2.5;
  var TAKE_FLIGHT_SPEED = 4.55;
  var TOP_SPEED = 300;
  var UP = 1;
  var DOWN = 2;
  var LEFT = 4;
  var RIGHT = 8;
  var FORWARDS = 16;
  var BACKWARDS = 32;
  var PITCH = 64;
  var YAW = 128;
  var ROLL = 256;
  var SAWTOOTH = 1;
  var TRIANGLE = 2;
  var SQUARE = 4;
  var VERY_LONG_TIME = 1000000.0;
  var VERY_SHORT_TIME = 0.000001;
  
  // ovelay images location
  var pathToAssets = 'http://s3.amazonaws.com/hifi-public/procedural-animator/';
  
  // load the UI
  Script.include("./libraries/walkInterface.js");
  
  // load filters (Bezier, Butterworth, add harmonics, averaging)
  Script.include("./libraries/walkFilters.js");
  
  // load objects, constructors and assets (state, Motion, Transition, walkAssets)
  Script.include("./libraries/walkApi.js");
  
  // initialise the motion properties and history object
  var motion = new Motion();
  
  // initialise Transitions
  var nullTransition = new Transition();
  motion.currentTransition = nullTransition;
  
  // initialise the UI
  walkInterface.initialise(state, motion, walkAssets);
  
  // Begin by setting the STANDING internal state
  state.setInternalState(state.STANDING);
  
  // smoothing filters
  var leanPitchFilter = filter.createButterworthFilter1();
  var leanPitchSmoothingFilter = filter.createAveragingFilter(10);
  var leanRollSmoothingFilter = filter.createAveragingFilter(10);
  var flyUpFilter = filter.createAveragingFilter(30);
  var flyFilter = filter.createAveragingFilter(30);
  
  // Main loop
  Script.update.connect(function(deltaTime) {
  
      if (state.powerOn) {
  
          // calculate avi linear speed
          var speed = Vec3.length(MyAvatar.getVelocity());
  
          // decide which animation should be playing
          selectAnimation(deltaTime, speed);
  
          // turn the frequency time wheels
          turnFrequencyTimeWheels(deltaTime, speed);
  
          // calculate (or fetch pre-calculated) stride length for this avi
          setStrideLength(speed);
  
          // update the progress of any live transition
          updateTransition();
  
          // apply translation and rotations
          animateAvatar(deltaTime, speed);
      }
  });
  
  // helper function for selectAnimation()
  function determineLocomotionMode(speed) {
  
      var locomotionMode = undefined;
  
      if (speed < 0.1 && motion.currentAnimation !== motion.selectedFlyBlend) {
  
          locomotionMode = state.STANDING;
  
      } else if (speed === 0 && motion.currentAnimation === motion.selectedFlyBlend) {
  
          locomotionMode = state.STANDING;
  
      } else if (speed < TAKE_FLIGHT_SPEED) {
  
          locomotionMode = state.WALKING;
  
      } else if (speed >= TAKE_FLIGHT_SPEED) {
  
          locomotionMode = state.FLYING;
      }
  
      // maybe travelling at walking speed, but sideways?
      if (locomotionMode === state.WALKING &&
         (motion.direction === LEFT ||
          motion.direction === RIGHT)) {
  
              locomotionMode = state.SIDE_STEP;
      }
  
      // maybe completing a final step during a walking to standing transition?
      if (locomotionMode === state.WALKING &&
          motion.currentTransition.percentToMove > 0) {
  
              locomotionMode = state.STANDING;
      }
  
      // maybe starting to fly up or down?
      if (locomotionMode === state.WALKING &&
          motion.direction === UP || motion.direction === DOWN) {
  
              locomotionMode = state.FLYING;
      }
  
      // are we on a voxel surface?
      if(spatialInformation.distanceToVoxels() > 0.2 && speed > 0.1) {
  
          locomotionMode = state.FLYING;
      }
  
      return locomotionMode;
  }
  
  // helper function for selectAnimation()
  function determineDirection(localVelocity) {
  
      if (Math.abs(localVelocity.x) > Math.abs(localVelocity.y) &&
          Math.abs(localVelocity.x) > Math.abs(localVelocity.z)) {
  
          if (localVelocity.x < 0) {
              return LEFT;
          } else {
              return RIGHT;
          }
  
      } else if (Math.abs(localVelocity.y) > Math.abs(localVelocity.x) &&
                 Math.abs(localVelocity.y) > Math.abs(localVelocity.z)) {
  
          if (localVelocity.y > 0) {
              return UP;
          } else {
              return DOWN;
          }
  
      } else if (Math.abs(localVelocity.z) > Math.abs(localVelocity.x) &&
                 Math.abs(localVelocity.z) > Math.abs(localVelocity.y)) {
  
          if (localVelocity.z < 0) {
              return FORWARDS;
          } else {
              return BACKWARDS;
          }
      }
  }
  
  // advance the animation's frequency time wheels. advance frequency time wheels for any live transitions also
  function turnFrequencyTimeWheels(deltaTime, speed) {
  
      var wheelAdvance = 0;
  
      // turn the frequency time wheel
      if (motion.currentAnimation === motion.selectedWalk ||
          motion.currentAnimation === motion.selectedSideStepLeft ||
          motion.currentAnimation === motion.selectedSideStepRight) {
  
          // Using technique described here: http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach
          // wrap the stride length around a 'surveyor's wheel' twice and calculate the angular speed at the given (linear) speed
          // omega = v / r , where r = circumference / 2 PI , where circumference = 2 * stride length
          motion.frequencyTimeWheelRadius = motion.strideLength / Math.PI;
          var angularVelocity = speed / motion.frequencyTimeWheelRadius;
  
          // calculate the degrees turned (at this angular speed) since last frame
          wheelAdvance = filter.radToDeg(deltaTime * angularVelocity);
  
      } else {
  
          // turn the frequency time wheel by the amount specified for this animation
          wheelAdvance = filter.radToDeg(motion.currentAnimation.calibration.frequency * deltaTime);
      }
  
      if(motion.currentTransition !== nullTransition) {
  
          // the last animation is still playing so we turn it's frequency time wheel to maintain the animation
          if (motion.currentTransition.lastAnimation === motion.selectedWalk) {
  
              // if at a stop angle (i.e. feet now under the avi) hold the wheel position for remainder of transition
              var tolerance = motion.currentTransition.lastFrequencyTimeIncrement + 0.1;
              if ((motion.currentTransition.lastFrequencyTimeWheelPos >
                  (motion.currentTransition.stopAngle - tolerance)) &&
                  (motion.currentTransition.lastFrequencyTimeWheelPos <
                  (motion.currentTransition.stopAngle + tolerance))) {
  
                  motion.currentTransition.lastFrequencyTimeIncrement = 0;
              }
          }
          motion.currentTransition.advancePreviousFrequencyTimeWheel();
      }
  
      // advance the walk wheel the appropriate amount
      motion.advanceFrequencyTimeWheel(wheelAdvance);
  }
  
  // helper function for selectAnimation()
  function setTransition(nextAnimation) {
  
      var lastTransition = motion.currentTransition;
      motion.currentTransition = new Transition(nextAnimation,
                                                motion.currentAnimation,
                                                motion.currentTransition);
  
      if(motion.currentTransition.recursionDepth > 5) {
  
          delete motion.currentTransition;
          motion.currentTransition = lastTransition;
      }
  }
  
  // select which animation should be played based on speed, mode of locomotion and direction of travel
  function selectAnimation(deltaTime, speed) {
  
      var localVelocity = {x: 0, y: 0, z: 0};
      if (speed > 0) {
          localVelocity = Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), MyAvatar.getVelocity());
      }
  
      // determine principle direction of movement
      motion.direction = determineDirection(localVelocity);
  
      // determine mode of locomotion
      var locomotionMode = determineLocomotionMode(speed);
  
      // select appropriate animation. If changing animation, initiate a new transition
      // note: The transitions are work in progress: https://worklist.net/20186
      switch (locomotionMode) {
  
          case state.STANDING: {
  
              var onVoxelSurface = spatialInformation.distanceToVoxels() < 0.3 ? true : false;
  
              if (state.currentState !== state.STANDING) {
  
                  if (onVoxelSurface) {
  
                      setTransition(motion.selectedStand);
  
                  } else {
  
                      setTransition(motion.selectedHovering);
                  }
                  state.setInternalState(state.STANDING);
              }
              if (onVoxelSurface) {
  
                  motion.currentAnimation = motion.selectedStand;
  
              } else {
  
                  motion.currentAnimation = motion.selectedHovering;
              }
              break;
          }
  
          case state.WALKING: {
  
              if (state.currentState !== state.WALKING) {
  
                  setTransition(motion.selectedWalk);
                  // set the walk wheel to it's starting point for this animation
                  if (motion.direction === BACKWARDS) {
  
                      motion.frequencyTimeWheelPos = motion.selectedWalk.calibration.startAngleBackwards;
  
                  } else {
  
                      motion.frequencyTimeWheelPos = motion.selectedWalk.calibration.startAngleForwards;
                  }
                  state.setInternalState(state.WALKING);
              }
              motion.currentAnimation = motion.selectedWalk;
              break;
          }
  
          case state.SIDE_STEP: {
  
              var selSideStep = undefined;
  
              if (state.currentState !== state.SIDE_STEP) {
  
                  if (motion.direction === LEFT) {
                      // set the walk wheel to it's starting point for this animation
                      motion.frequencyTimeWheelPos = motion.selectedSideStepLeft.calibration.startAngle;
                      selSideStep = motion.selectedSideStepLeft;
  
                  } else {
                      // set the walk wheel to it's starting point for this animation
                      motion.frequencyTimeWheelPos = motion.selectedSideStepRight.calibration.startAngle;
                      selSideStep = motion.selectedSideStepRight;
                  }
                  setTransition(selSideStep);
                  state.setInternalState(state.SIDE_STEP);
  
              } else if (motion.direction === LEFT) {
  
                  if (motion.lastDirection !== LEFT) {
                      // set the walk wheel to it's starting point for this animation
                      motion.frequencyTimeWheelPos = motion.selectedSideStepLeft.calibration.startAngle;
                      setTransition(motion.selectedSideStepLeft);
                  }
                  selSideStep = motion.selectedSideStepLeft;
  
              } else {
  
                  if (motion.lastDirection !== RIGHT) {
                      // set the walk wheel to it's starting point for this animation
                      motion.frequencyTimeWheelPos = motion.selectedSideStepRight.calibration.startAngle;
                      setTransition(motion.selectedSideStepRight);
                  }
                  selSideStep = motion.selectedSideStepRight;
              }
              motion.currentAnimation = selSideStep;
              break;
          }
  
          case state.FLYING: {
  
              // blend the up, down and forward flying animations relative to motion direction
              animation.zeroAnimation(motion.selectedFlyBlend);
  
              var upVector = 0;
              var forwardVector = 0;
  
              if (speed > 0) {
  
                  // calculate directionals
                  upVector = localVelocity.y / speed;
                  forwardVector = -localVelocity.z / speed;
  
                  // add damping
                  upVector = flyUpFilter.process(upVector);
                  forwardVector = flyFilter.process(forwardVector);
  
                  // normalise
                  var denominator = Math.abs(upVector) + Math.abs(forwardVector);
                  upVector = upVector / denominator;
                  forwardVector = forwardVector / denominator;
              }
  
              if (upVector > 0) {
  
                  animation.blendAnimation(motion.selectedFlyUp,
                                           motion.selectedFlyBlend,
                                           upVector);
  
              } else if (upVector < 0) {
  
                  animation.blendAnimation(motion.selectedFlyDown,
                                           motion.selectedFlyBlend,
                                           -upVector);
              } else {
  
                  forwardVector = 1;
              }
              animation.blendAnimation(motion.selectedFly,
                                       motion.selectedFlyBlend,
                                       Math.abs(forwardVector));
  
              if (state.currentState !== state.FLYING) {
  
                  state.setInternalState(state.FLYING);
                  setTransition(motion.selectedFlyBlend);
              }
              motion.currentAnimation = motion.selectedFlyBlend;
              break;
          }
  
      } // end switch(locomotionMode)
  
      // record frame details for future reference
      motion.lastDirection = motion.direction;
      motion.lastSpeed = localVelocity;
      motion.lastDistanceToVoxels = motion.calibration.distanceToVoxels;
  
      return;
  }
  
  // if the timing's right, recalculate the stride length. if not, fetch the previously calculated value
  function setStrideLength(speed) {
  
      // if not at full speed the calculation could be wrong, as we may still be transitioning
      var atMaxSpeed = speed / MAX_WALK_SPEED < 0.97 ? false : true;
  
      // walking? then get the stride length
      if (motion.currentAnimation === motion.selectedWalk) {
  
          var strideMaxAt = motion.currentAnimation.calibration.forwardStrideMaxAt;
          if (motion.direction === BACKWARDS) {
              strideMaxAt = motion.currentAnimation.calibration.backwardsStrideMaxAt;
          }
  
          var tolerance = 1.0;
          if (motion.frequencyTimeWheelPos < (strideMaxAt + tolerance) &&
              motion.frequencyTimeWheelPos > (strideMaxAt - tolerance) &&
              atMaxSpeed && motion.currentTransition === nullTransition) {
  
              // measure and save stride length
              var footRPos = MyAvatar.getJointPosition("RightFoot");
              var footLPos = MyAvatar.getJointPosition("LeftFoot");
              motion.strideLength = Vec3.distance(footRPos, footLPos);
  
              if (motion.direction === FORWARDS) {
                  motion.currentAnimation.calibration.strideLengthForwards = motion.strideLength;
              } else if (motion.direction === BACKWARDS) {
                  motion.currentAnimation.calibration.strideLengthBackwards = motion.strideLength;
              }
  
          } else {
  
              // use the previously saved value for stride length
              if (motion.direction === FORWARDS) {
                  motion.strideLength = motion.currentAnimation.calibration.strideLengthForwards;
              } else if (motion.direction === BACKWARDS) {
                  motion.strideLength = motion.currentAnimation.calibration.strideLengthBackwards;
              }
          }
      } // end get walk stride length
  
      // sidestepping? get the stride length
      if (motion.currentAnimation === motion.selectedSideStepLeft ||
          motion.currentAnimation === motion.selectedSideStepRight) {
  
          // if the timing's right, take a snapshot of the stride max and recalibrate the stride length
          var tolerance = 1.0;
          if (motion.direction === LEFT) {
  
              if (motion.frequencyTimeWheelPos < motion.currentAnimation.calibration.strideMaxAt + tolerance &&
                  motion.frequencyTimeWheelPos > motion.currentAnimation.calibration.strideMaxAt - tolerance &&
                  atMaxSpeed && motion.currentTransition === nullTransition) {
  
                  var footRPos = MyAvatar.getJointPosition("RightFoot");
                  var footLPos = MyAvatar.getJointPosition("LeftFoot");
                  motion.strideLength = Vec3.distance(footRPos, footLPos);
                  motion.currentAnimation.calibration.strideLength = motion.strideLength;
  
              } else {
  
                  motion.strideLength = motion.selectedSideStepLeft.calibration.strideLength;
              }
  
          } else if (motion.direction === RIGHT) {
  
              if (motion.frequencyTimeWheelPos < motion.currentAnimation.calibration.strideMaxAt + tolerance &&
                  motion.frequencyTimeWheelPos > motion.currentAnimation.calibration.strideMaxAt - tolerance &&
                  atMaxSpeed && motion.currentTransition === nullTransition) {
  
                  var footRPos = MyAvatar.getJointPosition("RightFoot");
                  var footLPos = MyAvatar.getJointPosition("LeftFoot");
                  motion.strideLength = Vec3.distance(footRPos, footLPos);
                  motion.currentAnimation.calibration.strideLength = motion.strideLength;
  
              } else {
  
                  motion.strideLength = motion.selectedSideStepRight.calibration.strideLength;
              }
          }
      } // end get sidestep stride length
  }
  
  // initialise a new transition. update progress of a live transition
  function updateTransition() {
  
      if (motion.currentTransition !== nullTransition) {
  
          // new transition?
          if (motion.currentTransition.progress === 0) {
  
              // overlapping transitions?
              if (motion.currentTransition.lastTransition !== nullTransition) {
  
                  // is the last animation for the nested transition the same as the new animation?
                  if (motion.currentTransition.lastTransition.lastAnimation === motion.currentAnimation) {
  
                      // sync the nested transitions's frequency time wheel for a smooth animation blend
                      motion.frequencyTimeWheelPos = motion.currentTransition.lastTransition.lastFrequencyTimeWheelPos;
                  }
              }
  
              if (motion.currentTransition.lastAnimation === motion.selectedWalk) {
  
                  // decide at which angle we should stop the frequency time wheel
                  var stopAngle = motion.selectedWalk.calibration.stopAngleForwards;
                  var percentToMove = 0;
                  var lastFrequencyTimeWheelPos = motion.currentTransition.lastFrequencyTimeWheelPos;
                  var lastElapsedFTDegrees = motion.currentTransition.lastElapsedFTDegrees;
  
                  // set the stop angle depending on which quadrant of the walk cycle we are currently in
                  // and decide whether we need to take an extra step to complete the walk cycle or not
                  // - currently work in progress
                  if(lastFrequencyTimeWheelPos <= stopAngle && lastElapsedFTDegrees < 180) {
  
                      // we have not taken a complete step yet, so we do need to do so before stopping
                      percentToMove = 100;
                      stopAngle += 180;
  
                  } else if(lastFrequencyTimeWheelPos > stopAngle && lastFrequencyTimeWheelPos <= stopAngle + 90) {
  
                      // take an extra step to complete the walk cycle and stop at the second stop angle
                      percentToMove = 100;
                      stopAngle += 180;
  
                  } else if(lastFrequencyTimeWheelPos > stopAngle + 90 && lastFrequencyTimeWheelPos <= stopAngle + 180) {
  
                      // stop on the other foot at the second stop angle for this walk cycle
                      percentToMove = 0;
                      if (motion.currentTransition.lastDirection === BACKWARDS) {
  
                          percentToMove = 100;
  
                      } else {
  
                          stopAngle += 180;
                      }
  
                  } else if(lastFrequencyTimeWheelPos > stopAngle + 180 && lastFrequencyTimeWheelPos <= stopAngle + 270) {
  
                      // take an extra step to complete the walk cycle and stop at the first stop angle
                      percentToMove = 100;
                  }
  
                  // set it all in motion
                  motion.currentTransition.stopAngle = stopAngle;
                  motion.currentTransition.percentToMove = percentToMove;
              }
  
          } // end if new transition
  
          // update the Transition progress
          if (motion.currentTransition.updateProgress() >= 1) {
  
              // it's time to kill off this transition
              delete motion.currentTransition;
              motion.currentTransition = nullTransition;
          }
      }
  }
  
  // helper function for animateAvatar(). calculate the amount to lean forwards (or backwards) based on the avi's acceleration
  function getLeanPitch(speed) {
  
      var leanProgress = undefined;
  
      if (motion.direction === LEFT ||
          motion.direction === RIGHT ||
          motion.direction === UP) {
  
          leanProgress = 0;
  
      } else {
  
          // boost lean for flying (leaning whilst walking is work in progress)
          var signedSpeed = -Vec3.multiplyQbyV(Quat.inverse(MyAvatar.orientation), MyAvatar.getVelocity()).z;
          var reverseMod = 1;
          var BOOST = 6;
  
          if (signedSpeed < 0) {
  
              reverseMod = -1.9;
              if (motion.direction === DOWN) {
  
                  reverseMod *= -1;
              }
          }
          leanProgress = reverseMod * BOOST * speed / TOP_SPEED;
      }
  
      // use filters to shape the walking acceleration response
      leanProgress = leanPitchFilter.process(leanProgress);
      leanProgress = leanPitchSmoothingFilter.process(leanProgress);
      return motion.calibration.pitchMax * leanProgress;
  }
  
  // helper function for animateAvatar(). calculate the angle at which to bank into corners whilst turning
  function getLeanRoll(deltaTime, speed) {
  
      var leanRollProgress = 0;
      var angularVelocity = filter.radToDeg(MyAvatar.getAngularVelocity().y);
  
      leanRollProgress = speed / TOP_SPEED;
      leanRollProgress *= Math.abs(angularVelocity) / motion.calibration.angularVelocityMax;
  
      // shape the response curve
      leanRollProgress = filter.bezier((1 - leanRollProgress),
          {x: 0, y: 0}, {x: 0, y: 1.3}, {x: 0.25, y: 0.5}, {x: 1, y: 1}).y;
  
      // which way to lean?
      var turnSign = -1;
      if (angularVelocity < 0.001) {
  
          turnSign = 1;
      }
      if (motion.direction === BACKWARDS ||
          motion.direction === LEFT) {
  
          turnSign *= -1;
      }
  
      // add damping with averaging filter
      leanRollProgress = leanRollSmoothingFilter.process(turnSign * leanRollProgress);
      return motion.calibration.rollMax * leanRollProgress;
  }
  
  // check for existence of object property (e.g. animation waveform synthesis filters)
  function isDefined(value) {
  
      try {
          if (typeof value != 'undefined') return true;
      } catch (e) {
          return false;
      }
  }
  
  // helper function for animateAvatar(). calculate joint translations based on animation file settings and frequency * time
  function calculateTranslations(animation, ft, direction) {
  
      var jointName = "Hips";
      var joint = animation.joints[jointName];
      var jointTranslations = {x:0, y:0, z:0};
  
      // gather modifiers and multipliers
      modifiers = new JointModifiers(joint, direction);
  
      // calculate translations. Use synthesis filters where specified by the animation file.
  
      // sway (oscillation on the x-axis)
      if(animation.filters.hasOwnProperty(jointName) &&
         'swayFilter' in animation.filters[jointName]) {
  
          jointTranslations.x = joint.sway *
              animation.filters[jointName].swayFilter.
              calculate(filter.degToRad(ft + joint.swayPhase)) +
              joint.swayOffset;
  
      } else {
  
          jointTranslations.x = joint.sway *
              Math.sin(filter.degToRad(ft + joint.swayPhase)) +
              joint.swayOffset;
      }
  
      // bob (oscillation on the y-axis)
      if(animation.filters.hasOwnProperty(jointName) &&
         'bobFilter' in animation.filters[jointName]) {
  
          jointTranslations.y = joint.bob *
              animation.filters[jointName].bobFilter.calculate
              (filter.degToRad(modifiers.bobFrequencyMultiplier * ft +
              joint.bobPhase + modifiers.bobReverseModifier)) +
              joint.bobOffset;
  
      } else {
  
          jointTranslations.y = joint.bob *
              Math.sin(filter.degToRad(modifiers.bobFrequencyMultiplier * ft +
              joint.bobPhase + modifiers.bobReverseModifier)) +
              joint.bobOffset;
  
          // check for specified low pass filter for this joint (currently Hips bob only)
          if(animation.filters.hasOwnProperty(jointName) &&
                 'bobLPFilter' in animation.filters[jointName]) {
  
              jointTranslations.y = filter.clipTrough(jointTranslations.y, joint, 2);
              jointTranslations.y = animation.filters[jointName].bobLPFilter.process(jointTranslations.y);
          }
      }
  
      // thrust (oscillation on the z-axis)
      if(animation.filters.hasOwnProperty(jointName) &&
         'thrustFilter' in animation.filters[jointName]) {
  
          jointTranslations.z = joint.thrust *
              animation.filters[jointName].thrustFilter.
              calculate(filter.degToRad(modifiers.thrustFrequencyMultiplier * ft +
              joint.thrustPhase)) +
              joint.thrustOffset;
  
      } else {
  
          jointTranslations.z = joint.thrust *
              Math.sin(filter.degToRad(modifiers.thrustFrequencyMultiplier * ft +
              joint.thrustPhase)) +
              joint.thrustOffset;
      }
  
      return jointTranslations;
  }
  
  // helper function for animateAvatar(). calculate joint rotations based on animation file settings and frequency * time
  function calculateRotations(jointName, animation, ft, direction) {
  
      var joint = animation.joints[jointName];
      var jointRotations = {x:0, y:0, z:0};
  
      // gather modifiers and multipliers for this joint
      modifiers = new JointModifiers(joint, direction);
  
      // calculate rotations. Use synthesis filters where specified by the animation file.
  
      // calculate pitch
      if(animation.filters.hasOwnProperty(jointName) &&
         'pitchFilter' in animation.filters[jointName]) {
  
          jointRotations.x = modifiers.pitchReverseInvert * (modifiers.pitchSign * joint.pitch *
              animation.filters[jointName].pitchFilter.calculate
              (filter.degToRad(ft * modifiers.pitchFrequencyMultiplier +
              joint.pitchPhase + modifiers.pitchPhaseModifier + modifiers.pitchReverseModifier)) +
              modifiers.pitchOffsetSign * joint.pitchOffset);
  
      } else {
  
          jointRotations.x = modifiers.pitchReverseInvert * (modifiers.pitchSign * joint.pitch *
              Math.sin(filter.degToRad(ft * modifiers.pitchFrequencyMultiplier +
              joint.pitchPhase + modifiers.pitchPhaseModifier + modifiers.pitchReverseModifier)) +
              modifiers.pitchOffsetSign * joint.pitchOffset);
      }
  
      // calculate yaw
      if(animation.filters.hasOwnProperty(jointName) &&
         'yawFilter' in animation.filters[jointName]) {
  
          jointRotations.y = modifiers.yawSign * joint.yaw *
              animation.filters[jointName].yawFilter.calculate
              (filter.degToRad(ft + joint.yawPhase + modifiers.yawReverseModifier)) +
              modifiers.yawOffsetSign * joint.yawOffset;
  
      } else {
  
          jointRotations.y = modifiers.yawSign * joint.yaw *
              Math.sin(filter.degToRad(ft + joint.yawPhase + modifiers.yawReverseModifier)) +
              modifiers.yawOffsetSign * joint.yawOffset;
      }
  
      // calculate roll
      if(animation.filters.hasOwnProperty(jointName) &&
         'rollFilter' in animation.filters[jointName]) {
  
          jointRotations.z = modifiers.rollSign * joint.roll *
              animation.filters[jointName].rollFilter.calculate
              (filter.degToRad(ft + joint.rollPhase + modifiers.rollReverseModifier)) +
              modifiers.rollOffsetSign * joint.rollOffset;
  
      } else {
  
          jointRotations.z = modifiers.rollSign * joint.roll *
              Math.sin(filter.degToRad(ft + joint.rollPhase + modifiers.rollReverseModifier)) +
              modifiers.rollOffsetSign * joint.rollOffset;
      }
      return jointRotations;
  }
  
  // animate the avatar using sine waves, geometric waveforms and harmonic generators
  function animateAvatar(deltaTime, speed) {
  
      // adjust leaning direction for flying
      var flyingModifier = 1;
      if (state.currentState.FLYING) {
  
          flyingModifier = -1;
      }
  
      // leaning in response to speed and acceleration (affects Hips pitch and z-axis acceleration driven offset)
      var leanPitch = getLeanPitch(speed);
  
      // hips translations
      var hipsTranslations = undefined;
      if (motion.currentTransition !== nullTransition) {
  
          hipsTranslations = motion.currentTransition.blendTranslations(motion.frequencyTimeWheelPos, motion.direction);
  
      } else {
  
          hipsTranslations = calculateTranslations(motion.currentAnimation,
                                                   motion.frequencyTimeWheelPos,
                                                   motion.direction);
      }
  
      // factor in any acceleration driven leaning
      hipsTranslations.z += motion.calibration.hipsToFeet * Math.sin(filter.degToRad(leanPitch));
  
      // apply translations
      MyAvatar.setSkeletonOffset(hipsTranslations);
  
      // joint rotations
      for (jointName in walkAssets.animationReference.joints) {
  
          // ignore arms rotations if 'arms free' is selected for Leap / Hydra use
          if((walkAssets.animationReference.joints[jointName].IKChain === "LeftArm" ||
              walkAssets.animationReference.joints[jointName].IKChain === "RightArm") &&
              motion.armsFree) {
  
                  continue;
          }
  
          if (isDefined(motion.currentAnimation.joints[jointName])) {
  
              var jointRotations = undefined;
  
              // if there's a live transition, blend the rotations with the last animation's rotations for this joint
              if (motion.currentTransition !== nullTransition) {
  
                  jointRotations = motion.currentTransition.blendRotations(jointName,
                                                                           motion.frequencyTimeWheelPos,
                                                                           motion.direction);
              } else {
  
                  jointRotations = calculateRotations(jointName,
                                                      motion.currentAnimation,
                                                      motion.frequencyTimeWheelPos,
                                                      motion.direction);
              }
  
              // apply lean
              if (jointName === "Hips") {
  
                  jointRotations.x += leanPitch;
                  jointRotations.z += getLeanRoll(deltaTime, speed);
              }
  
              // apply rotation
              MyAvatar.setJointData(jointName, Quat.fromVec3Degrees(jointRotations));
          }
      }
  }