/**
  * Uses {@link #getTrackTime()} to compute the <code>animationTime</code>, which is between
  * {@link #getAnimationStart()} and {@link #getAnimationEnd()}. When the <code>trackTime</code>
  * is 0, the <code>animationTime</code> is equal to the <code>animationStart</code> time.
  */
 public float getAnimationTime() {
   if (loop) {
     float duration = animationEnd - animationStart;
     if (duration == 0) return animationStart;
     return (trackTime % duration) + animationStart;
   }
   return Math.min(trackTime + animationStart, animationEnd);
 }
  private void setCurrent(int index, TrackEntry current, boolean interrupt) {
    TrackEntry from = expandToIndex(index);
    tracks.set(index, current);

    if (from != null) {
      if (interrupt) queue.interrupt(from);
      current.mixingFrom = from;
      current.mixTime = 0;

      from.timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in.

      // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
      if (from.mixingFrom != null && from.mixDuration > 0)
        current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
    }

    queue.start(current);
  }
  private void applyRotateTimeline(
      Timeline timeline,
      Skeleton skeleton,
      float time,
      float alpha,
      boolean setupPose,
      float[] timelinesRotation,
      int i,
      boolean firstFrame) {

    if (firstFrame) timelinesRotation[i] = 0;

    if (alpha == 1) {
      timeline.apply(skeleton, 0, time, null, 1, setupPose, false);
      return;
    }

    RotateTimeline rotateTimeline = (RotateTimeline) timeline;
    Bone bone = skeleton.bones.get(rotateTimeline.boneIndex);
    float[] frames = rotateTimeline.frames;
    if (time < frames[0]) { // Time is before first frame.
      if (setupPose) bone.rotation = bone.data.rotation;
      return;
    }

    float r2;
    if (time >= frames[frames.length - ENTRIES]) // Time is after last frame.
    r2 = bone.data.rotation + frames[frames.length + PREV_ROTATION];
    else {
      // Interpolate between the previous frame and the current frame.
      int frame = Animation.binarySearch(frames, time, ENTRIES);
      float prevRotation = frames[frame + PREV_ROTATION];
      float frameTime = frames[frame];
      float percent =
          rotateTimeline.getCurvePercent(
              (frame >> 1) - 1, 1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime));

      r2 = frames[frame + ROTATION] - prevRotation;
      r2 -= (16384 - (int) (16384.499999999996 - r2 / 360)) * 360;
      r2 = prevRotation + r2 * percent + bone.data.rotation;
      r2 -= (16384 - (int) (16384.499999999996 - r2 / 360)) * 360;
    }

    // Mix between rotations using the direction of the shortest route on the first frame while
    // detecting crosses.
    float r1 = setupPose ? bone.data.rotation : bone.rotation;
    float total, diff = r2 - r1;
    if (diff == 0) total = timelinesRotation[i];
    else {
      diff -= (16384 - (int) (16384.499999999996 - diff / 360)) * 360;
      float lastTotal, lastDiff;
      if (firstFrame) {
        lastTotal = 0;
        lastDiff = diff;
      } else {
        lastTotal = timelinesRotation[i]; // Angle and direction of mix, including loops.
        lastDiff = timelinesRotation[i + 1]; // Difference between bones.
      }
      boolean current = diff > 0, dir = lastTotal >= 0;
      // Detect cross at 0 (not 180).
      if (Math.signum(lastDiff) != Math.signum(diff) && Math.abs(lastDiff) <= 90) {
        // A cross after a 360 rotation is a loop.
        if (Math.abs(lastTotal) > 180) lastTotal += 360 * Math.signum(lastTotal);
        dir = current;
      }
      total = diff + lastTotal - lastTotal % 360; // Store loops as part of lastTotal.
      if (dir != current) total += 360 * Math.signum(lastTotal);
      timelinesRotation[i] = total;
    }
    timelinesRotation[i + 1] = diff;
    r1 += total * alpha;
    bone.rotation = r1 - (16384 - (int) (16384.499999999996 - r1 / 360)) * 360;
  }