readAnimation method

void readAnimation(
  1. Map<String, dynamic> map,
  2. String name,
  3. SkeletonData skeletonData
)

Implementation

void readAnimation(
    Map<String, dynamic> map, String name, SkeletonData skeletonData) {
  final double scale = this.scale;
  final List<Timeline> timelines = <Timeline>[];
  double duration = 0.0;

  // Slot timelines.
  if (map.containsKey('slots')) {
    for (String slotName in map['slots'].keys) {
      final dynamic slotMap = map['slots'][slotName];
      final int slotIndex = skeletonData.findSlotIndex(slotName);
      if (slotIndex == -1) throw StateError('Slot not found: ' + slotName);
      for (String timelineName in slotMap.keys) {
        final dynamic timelineMap = slotMap[timelineName];
        if (timelineName == 'attachment') {
          final AttachmentTimeline timeline =
              AttachmentTimeline(timelineMap.length, slotIndex);

          int frameIndex = 0;
          for (int i = 0; i < timelineMap.length; i++) {
            final dynamic valueMap = timelineMap[i];
            timeline.setFrame(frameIndex++, _getDouble(valueMap, 'time'),
                _getString(valueMap, 'name'));
          }
          timelines.add(timeline);
          duration = math.max(
              duration, timeline.frames[timeline.getFrameCount() - 1]);
        } else if (timelineName == 'color') {
          final ColorTimeline timeline = ColorTimeline(timelineMap.length)
            ..slotIndex = slotIndex;

          int frameIndex = 0;
          for (int i = 0; i < timelineMap.length; i++) {
            final dynamic valueMap = timelineMap[i];
            final Color color = Color()
              ..setFromString(_getString(valueMap, 'color'));
            timeline.setFrame(frameIndex, _getDouble(valueMap, 'time'),
                color.r, color.g, color.b, color.a);
            readCurve(valueMap, timeline, frameIndex);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = math.max(
              duration,
              timeline.frames[
                  (timeline.getFrameCount() - 1) * ColorTimeline.entries]);
        } else if (timelineName == 'twoColor') {
          final TwoColorTimeline timeline =
              TwoColorTimeline(timelineMap.length)..slotIndex = slotIndex;

          int frameIndex = 0;
          for (int i = 0; i < timelineMap.length; i++) {
            final dynamic valueMap = timelineMap[i];
            final Color light = Color();
            final Color dark = Color();
            light.setFromString(_getString(valueMap, 'light'));
            dark.setFromString(_getString(valueMap, 'dark'));
            timeline.setFrame(frameIndex, _getDouble(valueMap, 'time'),
                light.r, light.g, light.b, light.a, dark.r, dark.g, dark.b);
            readCurve(valueMap, timeline, frameIndex);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = math.max(
              duration,
              timeline.frames[
                  (timeline.getFrameCount() - 1) * TwoColorTimeline.entries]);
        } else {
          throw StateError(
              'Invalid timeline type for a slot: $timelineName ($slotName)');
        }
      }
    }
  }

  // Bone timelines.
  if (map.containsKey('bones')) {
    for (String boneName in map['bones'].keys) {
      final dynamic boneMap = map['bones'][boneName];
      final int boneIndex = skeletonData.findBoneIndex(boneName);
      if (boneIndex == -1) throw StateError('Bone not found: $boneName');
      for (String timelineName in boneMap.keys) {
        final dynamic timelineMap = boneMap[timelineName];
        if (timelineName == 'rotate') {
          final RotateTimeline timeline = RotateTimeline(timelineMap.length)
            ..boneIndex = boneIndex;

          int frameIndex = 0;
          for (int i = 0; i < timelineMap.length; i++) {
            final dynamic valueMap = timelineMap[i];
            timeline.setFrame(frameIndex, _getDouble(valueMap, 'time'),
                _getDouble(valueMap, 'angle', 0.0));
            readCurve(valueMap, timeline, frameIndex);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = math.max(
              duration,
              timeline.frames[
                  (timeline.getFrameCount() - 1) * RotateTimeline.entries]);
        } else if (timelineName == 'translate' ||
            timelineName == 'scale' ||
            timelineName == 'shear') {
          TranslateTimeline timeline;
          double timelineScale = 1.0;
          if (timelineName == 'scale') {
            timeline = ScaleTimeline(timelineMap.length);
          } else if (timelineName == 'shear') {
            timeline = ShearTimeline(timelineMap.length);
          } else {
            timeline = TranslateTimeline(timelineMap.length);
            timelineScale = scale;
          }
          timeline.boneIndex = boneIndex;

          int frameIndex = 0;
          for (int i = 0; i < timelineMap.length; i++) {
            final dynamic valueMap = timelineMap[i];
            final double x = _getDouble(valueMap, 'x', 0.0);
            final double y = _getDouble(valueMap, 'y', 0.0);
            timeline.setFrame(frameIndex, _getDouble(valueMap, 'time'),
                x * timelineScale, y * timelineScale);
            readCurve(valueMap, timeline, frameIndex);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = math.max(
              duration,
              timeline.frames[(timeline.getFrameCount() - 1) *
                  TranslateTimeline.entries]);
        } else {
          throw StateError(
              'Invalid timeline type for a bone: $timelineName ($boneName)');
        }
      }
    }
  }

  // IK constraint timelines.
  if (map.containsKey('ik')) {
    for (String constraintName in map['ik'].keys) {
      final dynamic constraintMap = map['ik'][constraintName];
      final IkConstraintData? constraint =
          skeletonData.findIkConstraint(constraintName);
      final IkConstraintTimeline timeline =
          IkConstraintTimeline(constraintMap.length)
            ..ikConstraintIndex =
                skeletonData.ikConstraints.indexOf(constraint);
      int frameIndex = 0;
      for (int i = 0; i < constraintMap.length; i++) {
        final dynamic valueMap = constraintMap[i];
        timeline.setFrame(
            frameIndex,
            _getDouble(valueMap, 'time'),
            _getDouble(valueMap, 'mix', 1.0),
            _getBool(valueMap, 'bendPositive', true) ? 1 : -1);
        readCurve(valueMap, timeline, frameIndex);
        frameIndex++;
      }
      timelines.add(timeline);
      duration = math.max(
          duration,
          timeline.frames[
              (timeline.getFrameCount() - 1) * IkConstraintTimeline.entries]);
    }
  }

  // Transform constraint timelines.
  if (map.containsKey('transform')) {
    for (String constraintName in map['transform'].keys) {
      final dynamic constraintMap = map['transform'][constraintName];
      final TransformConstraintData? constraint =
          skeletonData.findTransformConstraint(constraintName);
      final TransformConstraintTimeline timeline =
          TransformConstraintTimeline(constraintMap.length)
            ..transformConstraintIndex =
                skeletonData.transformConstraints.indexOf(constraint);
      int frameIndex = 0;
      for (int i = 0; i < constraintMap.length; i++) {
        final dynamic valueMap = constraintMap[i];
        timeline.setFrame(
            frameIndex,
            _getDouble(valueMap, 'time'),
            _getDouble(valueMap, 'rotateMix', 1.0),
            _getDouble(valueMap, 'translateMix', 1.0),
            _getDouble(valueMap, 'scaleMix', 1.0),
            _getDouble(valueMap, 'shearMix', 1.0));
        readCurve(valueMap, timeline, frameIndex);
        frameIndex++;
      }
      timelines.add(timeline);
      duration = math.max(
          duration,
          timeline.frames[(timeline.getFrameCount() - 1) *
              TransformConstraintTimeline.entries]);
    }
  }

  // Path constraint timelines.
  if (map.containsKey('paths')) {
    for (String constraintName in map['paths'].keys) {
      final dynamic constraintMap = map['paths'][constraintName];
      final int index = skeletonData.findPathConstraintIndex(constraintName);
      if (index == -1) {
        throw StateError('Path constraint not found: $constraintName');
      }
      final PathConstraintData data = skeletonData.pathConstraints[index];
      for (String timelineName in constraintMap.keys) {
        final dynamic timelineMap = constraintMap[timelineName];
        if (timelineName == 'position' || timelineName == 'spacing') {
          PathConstraintPositionTimeline timeline;
          double timelineScale = 1.0;
          if (timelineName == 'spacing') {
            timeline = PathConstraintSpacingTimeline(timelineMap.length);
            if (data.spacingMode == SpacingMode.length ||
                data.spacingMode == SpacingMode.fixed) timelineScale = scale;
          } else {
            timeline = PathConstraintPositionTimeline(timelineMap.length);
            if (data.positionMode == PositionMode.fixed) {
              timelineScale = scale;
            }
          }
          timeline.pathConstraintIndex = index;
          int frameIndex = 0;
          for (int i = 0; i < timelineMap.length; i++) {
            final dynamic valueMap = timelineMap[i];
            timeline.setFrame(frameIndex, _getDouble(valueMap, 'time'),
                _getDouble(valueMap, timelineName, 0.0) * timelineScale);
            readCurve(valueMap, timeline, frameIndex);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = math.max(
              duration,
              timeline.frames[(timeline.getFrameCount() - 1) *
                  PathConstraintPositionTimeline.entries]);
        } else if (timelineName == 'mix') {
          final PathConstraintMixTimeline timeline =
              PathConstraintMixTimeline(timelineMap.length)
                ..pathConstraintIndex = index;
          int frameIndex = 0;
          for (int i = 0; i < timelineMap.length; i++) {
            final dynamic valueMap = timelineMap[i];
            timeline.setFrame(
                frameIndex,
                _getDouble(valueMap, 'time'),
                _getDouble(valueMap, 'rotateMix', 1.0),
                _getDouble(valueMap, 'translateMix', 1.0));
            readCurve(valueMap, timeline, frameIndex);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = math.max(
              duration,
              timeline.frames[(timeline.getFrameCount() - 1) *
                  PathConstraintMixTimeline.entries]);
        }
      }
    }
  }

  // Deform timelines.
  if (map.containsKey('deform')) {
    for (String deformName in map['deform'].keys) {
      final dynamic deformMap = map['deform'][deformName];
      final Skin? skin = skeletonData.findSkin(deformName);
      if (skin == null) throw StateError('Skin not found: $deformName');
      for (String slotName in deformMap.keys) {
        final dynamic slotMap = deformMap[slotName];
        final int slotIndex = skeletonData.findSlotIndex(slotName);
        if (slotIndex == -1) {
          throw StateError('Slot not found: ${_getString(slotMap, 'name')}');
        }
        for (String timelineName in slotMap.keys) {
          final dynamic timelineMap = slotMap[timelineName];
          final VertexAttachment? attachment = skin.getAttachment(
              slotIndex, timelineName) as VertexAttachment?;
          if (attachment == null) {
            throw StateError(
                'Deform attachment not found: ${_getString(timelineMap, 'name')}');
          }
          final bool weighted = attachment.bones != null;
          final Float32List? vertices = attachment.vertices;
          final int deformLength =
              weighted ? vertices!.length ~/ 3 * 2 : vertices!.length;

          final DeformTimeline timeline =
              DeformTimeline(timelineMap.length, slotIndex, attachment);

          int frameIndex = 0;
          for (int j = 0; j < timelineMap.length; j++) {
            final dynamic valueMap = timelineMap[j];
            Float32List? deform;
            final Float32List? verticesValue =
                _getFloat32List(valueMap, 'vertices');
            if (verticesValue == null) {
              deform = weighted ? Float32List(deformLength) : vertices;
            } else {
              deform = Float32List(deformLength);
              final int start = _getInt(valueMap, 'offset', 0);
              deform = Float32List.fromList(ArrayUtils.arrayCopyWithGrowth(
                  verticesValue,
                  0,
                  deform,
                  start,
                  verticesValue.length,
                  0.0));
              if (scale != 1) {
                for (int i = start; i < i + verticesValue.length; i++) {
                  deform[i] *= scale;
                }
              }
              if (!weighted) {
                for (int i = 0; i < deformLength; i++) {
                  deform[i] += vertices[i];
                }
              }
            }

            timeline.setFrame(
                frameIndex, _getDouble(valueMap, 'time'), deform);
            readCurve(valueMap, timeline, frameIndex);
            frameIndex++;
          }
          timelines.add(timeline);
          duration = math.max(
              duration, timeline.frames[timeline.getFrameCount() - 1]);
        }
      }
    }
  }

  // Draw order timeline.
  if (map.containsKey('drawOrder') || map.containsKey('draworder')) {
    final List<dynamic> drawOrderNode =
        map[map.containsKey('drawOrder') ? 'drawOrder' : 'draworder'];
    final DrawOrderTimeline timeline =
        DrawOrderTimeline(drawOrderNode.length);
    final int slotCount = skeletonData.slots.length;
    int frameIndex = 0;
    for (int j = 0; j < drawOrderNode.length; j++) {
      final dynamic drawOrderMap = drawOrderNode[j];
      Int32List? drawOrder;
      final List<dynamic>? offsets = drawOrderMap['offsets'];
      if (offsets != null) {
        drawOrder = Int32List.fromList(List<int>.filled(slotCount, -1));
        final Int32List unchanged = Int32List.fromList(
            List<int>.filled(slotCount - offsets.length, 0));
        int originalIndex = 0, unchangedIndex = 0;
        for (int i = 0; i < offsets.length; i++) {
          final dynamic offsetMap = offsets[i];
          final int slotIndex =
              skeletonData.findSlotIndex(_getString(offsetMap, 'slot'));
          if (slotIndex == -1) {
            throw StateError(
                'Slot not found: ${_getString(offsetMap, 'slot')}');
          }
          // Collect unchanged items.
          while (originalIndex != slotIndex) {
            unchanged[unchangedIndex++] = originalIndex++;
          }
          // Set changed items.
          drawOrder[originalIndex + _getInt(offsetMap, 'offset')] =
              originalIndex++;
        }
        // Collect remaining unchanged items.
        while (originalIndex < slotCount) {
          unchanged[unchangedIndex++] = originalIndex++;
        }
        // Fill in unchanged items.
        for (int i = slotCount - 1; i >= 0; i--) {
          if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
        }
      }
      timeline.setFrame(
          frameIndex++, _getDouble(drawOrderMap, 'time'), drawOrder);
    }
    timelines.add(timeline);
    duration =
        math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
  }

  // Event timeline.
  if (map.containsKey('events')) {
    final EventTimeline timeline = EventTimeline(map['events'].length);
    int frameIndex = 0;
    for (int i = 0; i < map['events'].length; i++) {
      final dynamic eventMap = map['events'][i];
      final String eventDataName = _getString(eventMap, 'name');
      final EventData? eventData = skeletonData.findEvent(eventDataName);
      if (eventData == null) {
        throw StateError('Event not found: $eventDataName');
      }
      final Event event = Event(_getDouble(eventMap, 'time'), eventData)
        ..intValue = _getInt(eventMap, 'int', eventData.intValue)
        ..floatValue = _getDouble(eventMap, 'float', eventData.floatValue)
        ..stringValue = _getString(eventMap, 'string', eventData.stringValue);
      timeline.setFrame(frameIndex++, event);
    }
    timelines.add(timeline);
    duration =
        math.max(duration, timeline.frames[timeline.getFrameCount() - 1]);
  }

  if (duration.isNaN) {
    throw StateError('Error while parsing animation, duration is NaN');
  }

  skeletonData.animations.add(Animation(name, timelines, duration));
}