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));
}