private void readAnimation(String name, DataInput input, SkeletonData skeletonData) {
    Array<Timeline> timelines = new Array();
    float scale = this.scale;
    float duration = 0;

    try {
      // Slot timelines.
      for (int i = 0, n = input.readInt(true); i < n; i++) {
        int slotIndex = input.readInt(true);
        for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
          int timelineType = input.readByte();
          int frameCount = input.readInt(true);
          switch (timelineType) {
            case TIMELINE_COLOR:
              {
                ColorTimeline timeline = new ColorTimeline(frameCount);
                timeline.slotIndex = slotIndex;
                for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
                  float time = input.readFloat();
                  Color.rgba8888ToColor(tempColor, input.readInt());
                  timeline.setFrame(
                      frameIndex, time, tempColor.r, tempColor.g, tempColor.b, tempColor.a);
                  if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
                }
                timelines.add(timeline);
                duration = Math.max(duration, timeline.getFrames()[frameCount * 5 - 5]);
                break;
              }
            case TIMELINE_ATTACHMENT:
              AttachmentTimeline timeline = new AttachmentTimeline(frameCount);
              timeline.slotIndex = slotIndex;
              for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
                timeline.setFrame(frameIndex, input.readFloat(), input.readString());
              timelines.add(timeline);
              duration = Math.max(duration, timeline.getFrames()[frameCount - 1]);
              break;
          }
        }
      }

      // Bone timelines.
      for (int i = 0, n = input.readInt(true); i < n; i++) {
        int boneIndex = input.readInt(true);
        for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
          int timelineType = input.readByte();
          int frameCount = input.readInt(true);
          switch (timelineType) {
            case TIMELINE_ROTATE:
              {
                RotateTimeline timeline = new RotateTimeline(frameCount);
                timeline.boneIndex = boneIndex;
                for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
                  timeline.setFrame(frameIndex, input.readFloat(), input.readFloat());
                  if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
                }
                timelines.add(timeline);
                duration = Math.max(duration, timeline.getFrames()[frameCount * 2 - 2]);
                break;
              }
            case TIMELINE_TRANSLATE:
            case TIMELINE_SCALE:
              TranslateTimeline timeline;
              float timelineScale = 1;
              if (timelineType == TIMELINE_SCALE) timeline = new ScaleTimeline(frameCount);
              else {
                timeline = new TranslateTimeline(frameCount);
                timelineScale = scale;
              }
              timeline.boneIndex = boneIndex;
              for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
                timeline.setFrame(
                    frameIndex,
                    input.readFloat(),
                    input.readFloat() * timelineScale,
                    input.readFloat() * timelineScale);
                if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
              }
              timelines.add(timeline);
              duration = Math.max(duration, timeline.getFrames()[frameCount * 3 - 3]);
              break;
          }
        }
      }

      // FFD timelines.
      for (int i = 0, n = input.readInt(true); i < n; i++) {
        Skin skin = skeletonData.getSkins().get(input.readInt(true) + 1);
        for (int ii = 0, nn = input.readInt(true); ii < nn; ii++) {
          int slotIndex = input.readInt(true);
          for (int iii = 0, nnn = input.readInt(true); iii < nnn; iii++) {
            Attachment attachment = skin.getAttachment(slotIndex, input.readString());
            int frameCount = input.readInt(true);
            FfdTimeline timeline = new FfdTimeline(frameCount);
            timeline.slotIndex = slotIndex;
            timeline.attachment = attachment;
            for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) {
              float time = input.readFloat();

              float[] vertices;
              int vertexCount;
              if (attachment instanceof MeshAttachment)
                vertexCount = ((MeshAttachment) attachment).getVertices().length;
              else vertexCount = ((SkinnedMeshAttachment) attachment).getWeights().length / 3 * 2;

              int end = input.readInt(true);
              if (end == 0) {
                if (attachment instanceof MeshAttachment)
                  vertices = ((MeshAttachment) attachment).getVertices();
                else vertices = new float[vertexCount];
              } else {
                vertices = new float[vertexCount];
                int start = input.readInt(true);
                end += start;
                if (scale == 1) {
                  for (int v = start; v < end; v++) vertices[v] = input.readFloat();
                } else {
                  for (int v = start; v < end; v++) vertices[v] = input.readFloat() * scale;
                }
                if (attachment instanceof MeshAttachment) {
                  float[] meshVertices = ((MeshAttachment) attachment).getVertices();
                  for (int v = 0, vn = vertices.length; v < vn; v++) vertices[v] += meshVertices[v];
                }
              }

              timeline.setFrame(frameIndex, time, vertices);
              if (frameIndex < frameCount - 1) readCurve(input, frameIndex, timeline);
            }
            timelines.add(timeline);
            duration = Math.max(duration, timeline.getFrames()[frameCount - 1]);
          }
        }
      }

      // Draw order timeline.
      int drawOrderCount = input.readInt(true);
      if (drawOrderCount > 0) {
        DrawOrderTimeline timeline = new DrawOrderTimeline(drawOrderCount);
        int slotCount = skeletonData.slots.size;
        for (int i = 0; i < drawOrderCount; i++) {
          int offsetCount = input.readInt(true);
          int[] drawOrder = new int[slotCount];
          for (int ii = slotCount - 1; ii >= 0; ii--) drawOrder[ii] = -1;
          int[] unchanged = new int[slotCount - offsetCount];
          int originalIndex = 0, unchangedIndex = 0;
          for (int ii = 0; ii < offsetCount; ii++) {
            int slotIndex = input.readInt(true);
            // Collect unchanged items.
            while (originalIndex != slotIndex) unchanged[unchangedIndex++] = originalIndex++;
            // Set changed items.
            drawOrder[originalIndex + input.readInt(true)] = originalIndex++;
          }
          // Collect remaining unchanged items.
          while (originalIndex < slotCount) unchanged[unchangedIndex++] = originalIndex++;
          // Fill in unchanged items.
          for (int ii = slotCount - 1; ii >= 0; ii--)
            if (drawOrder[ii] == -1) drawOrder[ii] = unchanged[--unchangedIndex];
          timeline.setFrame(i, input.readFloat(), drawOrder);
        }
        timelines.add(timeline);
        duration = Math.max(duration, timeline.getFrames()[drawOrderCount - 1]);
      }

      // Event timeline.
      int eventCount = input.readInt(true);
      if (eventCount > 0) {
        EventTimeline timeline = new EventTimeline(eventCount);
        for (int i = 0; i < eventCount; i++) {
          float time = input.readFloat();
          EventData eventData = skeletonData.events.get(input.readInt(true));
          Event event = new Event(eventData);
          event.intValue = input.readInt(false);
          event.floatValue = input.readFloat();
          event.stringValue = input.readBoolean() ? input.readString() : eventData.stringValue;
          timeline.setFrame(i, time, event);
        }
        timelines.add(timeline);
        duration = Math.max(duration, timeline.getFrames()[eventCount - 1]);
      }
    } catch (IOException ex) {
      throw new SerializationException("Error reading skeleton file.", ex);
    }

    timelines.shrink();
    skeletonData.addAnimation(new Animation(name, timelines, duration));
  }
  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;
  }
  private void readAnimation(String name, JsonValue map, SkeletonData skeletonData) {
    Array<Timeline> timelines = new Array();
    float duration = 0;

    for (JsonValue boneMap = map.getChild("bones"); boneMap != null; boneMap = boneMap.next()) {
      int boneIndex = skeletonData.findBoneIndex(boneMap.name());
      if (boneIndex == -1) throw new SerializationException("Bone not found: " + boneMap.name());

      for (JsonValue timelineMap = boneMap.child();
          timelineMap != null;
          timelineMap = timelineMap.next()) {
        String timelineName = timelineMap.name();
        if (timelineName.equals(TIMELINE_ROTATE)) {
          RotateTimeline timeline = new RotateTimeline(timelineMap.size());
          timeline.setBoneIndex(boneIndex);

          int frameIndex = 0;
          for (JsonValue valueMap = timelineMap.child();
              valueMap != null;
              valueMap = valueMap.next()) {
            float time = valueMap.getFloat("time");
            timeline.setFrame(frameIndex, time, valueMap.getFloat("angle"));
            readCurve(timeline, frameIndex, valueMap);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 2 - 2]);

        } else if (timelineName.equals(TIMELINE_TRANSLATE) || timelineName.equals(TIMELINE_SCALE)) {
          TranslateTimeline timeline;
          float timelineScale = 1;
          if (timelineName.equals(TIMELINE_SCALE)) timeline = new ScaleTimeline(timelineMap.size());
          else {
            timeline = new TranslateTimeline(timelineMap.size());
            timelineScale = scale;
          }
          timeline.setBoneIndex(boneIndex);

          int frameIndex = 0;
          for (JsonValue valueMap = timelineMap.child();
              valueMap != null;
              valueMap = valueMap.next()) {
            float time = valueMap.getFloat("time");
            float x = valueMap.getFloat("x", 0), y = valueMap.getFloat("y", 0);
            timeline.setFrame(frameIndex, time, x * timelineScale, y * timelineScale);
            readCurve(timeline, frameIndex, valueMap);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 3 - 3]);

        } else
          throw new RuntimeException(
              "Invalid timeline type for a bone: " + timelineName + " (" + boneMap.name() + ")");
      }
    }

    for (JsonValue slotMap = map.getChild("slots"); slotMap != null; slotMap = slotMap.next()) {
      int slotIndex = skeletonData.findSlotIndex(slotMap.name());

      for (JsonValue timelineMap = slotMap.child();
          timelineMap != null;
          timelineMap = timelineMap.next()) {
        String timelineName = timelineMap.name();
        if (timelineName.equals(TIMELINE_COLOR)) {
          ColorTimeline timeline = new ColorTimeline(timelineMap.size());
          timeline.setSlotIndex(slotIndex);

          int frameIndex = 0;
          for (JsonValue valueMap = timelineMap.child();
              valueMap != null;
              valueMap = valueMap.next()) {
            float time = valueMap.getFloat("time");
            Color color = Color.valueOf(valueMap.getString("color"));
            timeline.setFrame(frameIndex, time, color.r, color.g, color.b, color.a);
            readCurve(timeline, frameIndex, valueMap);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() * 5 - 5]);

        } else if (timelineName.equals(TIMELINE_ATTACHMENT)) {
          AttachmentTimeline timeline = new AttachmentTimeline(timelineMap.size());
          timeline.setSlotIndex(slotIndex);

          int frameIndex = 0;
          for (JsonValue valueMap = timelineMap.child();
              valueMap != null;
              valueMap = valueMap.next()) {
            float time = valueMap.getFloat("time");
            timeline.setFrame(frameIndex++, time, valueMap.getString("name"));
          }
          timelines.add(timeline);
          duration = Math.max(duration, timeline.getFrames()[timeline.getFrameCount() - 1]);

        } else
          throw new RuntimeException(
              "Invalid timeline type for a slot: " + timelineName + " (" + slotMap.name() + ")");
      }
    }

    timelines.shrink();
    skeletonData.addAnimation(new Animation(name, timelines, duration));
  }