/**
   * Poses the skeleton using the track entry animations. There are no side effects other than
   * invoking listeners, so the animation state can be applied to multiple skeletons to pose them
   * identically.
   */
  public void apply(Skeleton skeleton) {
    if (skeleton == null) throw new IllegalArgumentException("skeleton cannot be null.");
    if (animationsChanged) animationsChanged();

    Array<Event> events = this.events;

    for (int i = 0, n = tracks.size; i < n; i++) {
      TrackEntry current = tracks.get(i);
      if (current == null || current.delay > 0) continue;

      // Apply mixing from entries first.
      float mix = current.alpha;
      if (current.mixingFrom != null) mix *= applyMixingFrom(current, skeleton);
      else if (current.trackTime >= current.trackEnd) //
      mix = 0; // Set to setup pose the last time the entry will be applied.

      // Apply current entry.
      float animationLast = current.animationLast, animationTime = current.getAnimationTime();
      int timelineCount = current.animation.timelines.size;
      Object[] timelines = current.animation.timelines.items;
      if (mix == 1) {
        for (int ii = 0; ii < timelineCount; ii++)
          ((Timeline) timelines[ii])
              .apply(skeleton, animationLast, animationTime, events, 1, true, false);
      } else {
        boolean firstFrame = current.timelinesRotation.size == 0;
        if (firstFrame) current.timelinesRotation.setSize(timelineCount << 1);
        float[] timelinesRotation = current.timelinesRotation.items;

        boolean[] timelinesFirst = current.timelinesFirst.items;
        for (int ii = 0; ii < timelineCount; ii++) {
          Timeline timeline = (Timeline) timelines[ii];
          if (timeline instanceof RotateTimeline) {
            applyRotateTimeline(
                timeline,
                skeleton,
                animationTime,
                mix,
                timelinesFirst[ii],
                timelinesRotation,
                ii << 1,
                firstFrame);
          } else
            timeline.apply(
                skeleton, animationLast, animationTime, events, mix, timelinesFirst[ii], false);
        }
      }
      queueEvents(current, animationTime);
      events.clear();
      current.nextAnimationLast = animationTime;
      current.nextTrackLast = current.trackTime;
    }

    queue.drain();
  }