Added support for repeaters (#364)
Fixes #194
diff --git a/LottieSample/screenshots/Tests_Repeater.png b/LottieSample/screenshots/Tests_Repeater.png
new file mode 100644
index 0000000..7decfa0
--- /dev/null
+++ b/LottieSample/screenshots/Tests_Repeater.png
Binary files differ
diff --git a/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.java b/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.java
index e28736b..20e31d1 100644
--- a/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.java
+++ b/LottieSample/src/androidTest/java/com/airbnb/lottie/LottieTest.java
@@ -75,6 +75,7 @@
TestRobot.testLinearAnimation(activity, "Tests/Parenting.json");
TestRobot.testLinearAnimation(activity, "Tests/Precomps.json");
TestRobot.testLinearAnimation(activity, "Tests/Remap.json");
+ TestRobot.testLinearAnimation(activity, "Tests/Repeater.json");
TestRobot.testLinearAnimation(activity, "Tests/ShapeTypes.json");
TestRobot.testLinearAnimation(activity, "Tests/SplitDimensions.json");
TestRobot.testLinearAnimation(activity, "Tests/Stroke.json");
diff --git a/LottieSample/src/main/assets/Tests/Repeater.json b/LottieSample/src/main/assets/Tests/Repeater.json
new file mode 100644
index 0000000..70d6e51
--- /dev/null
+++ b/LottieSample/src/main/assets/Tests/Repeater.json
@@ -0,0 +1 @@
+{"v":"4.7.0","fr":60,"ip":0,"op":120,"w":300,"h":300,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","ks":{"o":{"a":0,"k":100},"r":{"a":0,"k":0},"p":{"a":0,"k":[49,48,0]},"a":{"a":0,"k":[0,0,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[65.734,65.734]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"Rectangle ","mn":"ADBE Vector Shape - Rect"},{"ty":"rp","c":{"a":0,"k":2,"ix":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[1]},{"t":119}],"ix":2},"m":1,"ix":2,"tr":{"ty":"tr","p":{"a":0,"k":[0,100],"ix":2},"a":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[0,0],"e":[0,53],"to":[0,8.83333301544189],"ti":[0,-8.83333301544189]},{"t":119}],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[89]},{"t":119}],"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Vertical Repeater","mn":"ADBE Vector Filter - Repeater"},{"ty":"fl","c":{"a":0,"k":[1,0,0,1]},"o":{"a":0,"k":50},"r":1,"nm":"Fill 2","mn":"ADBE Vector Graphic - Fill"},{"ty":"fl","c":{"a":0,"k":[0.132077,0,1,1]},"o":{"a":0,"k":50},"r":1,"nm":"Fill","mn":"ADBE Vector Graphic - Fill"},{"ty":"rp","c":{"a":0,"k":2,"ix":1},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[1]},{"t":119}],"ix":2},"m":1,"ix":5,"tr":{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[100,0],"e":[141,0],"to":[6.83333349227905,0],"ti":[-6.83333349227905,0]},{"t":119}],"ix":2},"a":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[0,0],"e":[67,0],"to":[11.1666669845581,0],"ti":[-11.1666669845581,0]},{"t":119}],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[45]},{"t":119}],"ix":4},"so":{"a":0,"k":100,"ix":5},"eo":{"a":0,"k":100,"ix":6},"nm":"Transform"},"nm":"Horizontal Repeater","mn":"ADBE Vector Filter - Repeater"}],"ip":0,"op":120,"st":0,"bm":0,"sr":1}]}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/AnimatablePathValue.java b/lottie/src/main/java/com/airbnb/lottie/AnimatablePathValue.java
index b7cf401..03c6d25 100644
--- a/lottie/src/main/java/com/airbnb/lottie/AnimatablePathValue.java
+++ b/lottie/src/main/java/com/airbnb/lottie/AnimatablePathValue.java
@@ -56,7 +56,7 @@
}
@Override
- public BaseKeyframeAnimation<?, PointF> createAnimation() {
+ public KeyframeAnimation<PointF> createAnimation() {
if (!hasAnimation()) {
return new StaticKeyframeAnimation<>(initialPoint);
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/AnimatableTransform.java b/lottie/src/main/java/com/airbnb/lottie/AnimatableTransform.java
index 952e5c8..631bf01 100644
--- a/lottie/src/main/java/com/airbnb/lottie/AnimatableTransform.java
+++ b/lottie/src/main/java/com/airbnb/lottie/AnimatableTransform.java
@@ -15,13 +15,20 @@
private final AnimatableFloatValue rotation;
private final AnimatableIntegerValue opacity;
+ // Used for repeaters
+ @Nullable private final AnimatableFloatValue startOpacity;
+ @Nullable private final AnimatableFloatValue endOpacity;
+
private AnimatableTransform(AnimatablePathValue anchorPoint, AnimatableValue<PointF> position,
- AnimatableScaleValue scale, AnimatableFloatValue rotation, AnimatableIntegerValue opacity) {
+ AnimatableScaleValue scale, AnimatableFloatValue rotation, AnimatableIntegerValue opacity,
+ @Nullable AnimatableFloatValue startOpacity, @Nullable AnimatableFloatValue endOpacity) {
this.anchorPoint = anchorPoint;
this.position = position;
this.scale = scale;
this.rotation = rotation;
this.opacity = opacity;
+ this.startOpacity = startOpacity;
+ this.endOpacity = endOpacity;
}
AnimatablePathValue getAnchorPoint() {
@@ -44,6 +51,14 @@
return opacity;
}
+ @Nullable public AnimatableFloatValue getStartOpacity() {
+ return startOpacity;
+ }
+
+ @Nullable public AnimatableFloatValue getEndOpacity() {
+ return endOpacity;
+ }
+
public TransformKeyframeAnimation createAnimation() {
return new TransformKeyframeAnimation(this);
}
@@ -62,7 +77,10 @@
AnimatableScaleValue scale = AnimatableScaleValue.Factory.newInstance();
AnimatableFloatValue rotation = AnimatableFloatValue.Factory.newInstance();
AnimatableIntegerValue opacity = AnimatableIntegerValue.Factory.newInstance();
- return new AnimatableTransform(anchorPoint, position, scale, rotation, opacity);
+ AnimatableFloatValue startOpacity = AnimatableFloatValue.Factory.newInstance();
+ AnimatableFloatValue endOpacity = AnimatableFloatValue.Factory.newInstance();
+ return new AnimatableTransform(anchorPoint, position, scale, rotation, opacity, startOpacity,
+ endOpacity);
}
static AnimatableTransform newInstance(JSONObject json, LottieComposition composition) {
@@ -71,6 +89,8 @@
AnimatableScaleValue scale;
AnimatableFloatValue rotation = null;
AnimatableIntegerValue opacity;
+ AnimatableFloatValue startOpacity = null;
+ AnimatableFloatValue endOpacity = null;
JSONObject anchorJson = json.optJSONObject("a");
if (anchorJson != null) {
anchorPoint = new AnimatablePathValue(anchorJson.opt("k"), composition);
@@ -112,10 +132,24 @@
if (opacityJson != null) {
opacity = AnimatableIntegerValue.Factory.newInstance(opacityJson, composition);
} else {
- // Somehow some community animations don't have opacity in the transform.
+ // Repeaters have start/end opacity instead of opacity
opacity = new AnimatableIntegerValue(Collections.<Keyframe<Integer>>emptyList(), 100);
}
- return new AnimatableTransform(anchorPoint, position, scale, rotation, opacity);
+
+ JSONObject startOpacityJson = json.optJSONObject("so");
+ if (startOpacityJson != null) {
+ startOpacity =
+ AnimatableFloatValue.Factory.newInstance(startOpacityJson, composition, false);
+ }
+
+ JSONObject endOpacityJson = json.optJSONObject("eo");
+ if (endOpacityJson != null) {
+ endOpacity =
+ AnimatableFloatValue.Factory.newInstance(endOpacityJson, composition, false);
+ }
+
+ return new AnimatableTransform(
+ anchorPoint, position, scale, rotation, opacity, startOpacity, endOpacity);
}
private static void throwMissingTransform(String missingProperty) {
diff --git a/lottie/src/main/java/com/airbnb/lottie/ContentGroup.java b/lottie/src/main/java/com/airbnb/lottie/ContentGroup.java
index afc65af..3518903 100644
--- a/lottie/src/main/java/com/airbnb/lottie/ContentGroup.java
+++ b/lottie/src/main/java/com/airbnb/lottie/ContentGroup.java
@@ -12,39 +12,62 @@
class ContentGroup implements DrawingContent, PathContent,
BaseKeyframeAnimation.AnimationListener {
+
+ private static List<Content> contentsFromModels(LottieDrawable drawable, BaseLayer layer,
+ List<ContentModel> contentModels) {
+ List<Content> contents = new ArrayList<>(contentModels.size());
+ for (int i = 0; i < contentModels.size(); i++) {
+ Content content = contentModels.get(i).toContent(drawable, layer);
+ if (content != null) {
+ contents.add(content);
+ }
+ }
+ return contents;
+ }
+
+ @Nullable static AnimatableTransform findTransform(List<ContentModel> contentModels) {
+ for (int i = 0; i < contentModels.size(); i++) {
+ ContentModel contentModel = contentModels.get(i);
+ if (contentModel instanceof AnimatableTransform) {
+ return (AnimatableTransform) contentModel;
+ }
+ }
+ return null;
+ }
+
private final Matrix matrix = new Matrix();
private final Path path = new Path();
private final RectF rect = new RectF();
private final String name;
- private final List<Content> contents = new ArrayList<>();
+ private final List<Content> contents;
private final LottieDrawable lottieDrawable;
@Nullable private List<PathContent> pathContents;
@Nullable private TransformKeyframeAnimation transformAnimation;
ContentGroup(final LottieDrawable lottieDrawable, BaseLayer layer, ShapeGroup shapeGroup) {
- name = shapeGroup.getName();
- this.lottieDrawable = lottieDrawable;
- List<ContentModel> items = shapeGroup.getItems();
- if (items.isEmpty()) {
- return;
- }
+ this(lottieDrawable, layer, shapeGroup.getName(),
+ contentsFromModels(lottieDrawable, layer, shapeGroup.getItems()),
+ findTransform(shapeGroup.getItems()));
+ }
- Object potentialTransform = items.get(items.size() - 1);
- if (potentialTransform instanceof AnimatableTransform) {
- transformAnimation = ((AnimatableTransform) potentialTransform).createAnimation();
+ ContentGroup(final LottieDrawable lottieDrawable, BaseLayer layer,
+ String name, List<Content> contents, @Nullable AnimatableTransform transform) {
+ this.name = name;
+ this.lottieDrawable = lottieDrawable;
+ this.contents = contents;
+
+ if (transform != null) {
+ transformAnimation = transform.createAnimation();
transformAnimation.addAnimationsToLayer(layer);
transformAnimation.addListener(this);
}
List<GreedyContent> greedyContents = new ArrayList<>();
- for (int i = 0; i < items.size(); i++) {
- Content content = items.get(i).toContent(lottieDrawable, layer);
- if (content != null) {
- contents.add(content);
- if (content instanceof GreedyContent) {
- greedyContents.add((GreedyContent) content);
- }
+ for (int i = contents.size() - 1; i >= 0; i--) {
+ Content content = contents.get(i);
+ if (content instanceof GreedyContent) {
+ greedyContents.add((GreedyContent) content);
}
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/Repeater.java b/lottie/src/main/java/com/airbnb/lottie/Repeater.java
new file mode 100644
index 0000000..3364ab0
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/Repeater.java
@@ -0,0 +1,58 @@
+package com.airbnb.lottie;
+
+import android.support.annotation.Nullable;
+
+import org.json.JSONObject;
+
+class Repeater implements ContentModel {
+ private final String name;
+ private final AnimatableFloatValue copies;
+ private final AnimatableFloatValue offset;
+ private final AnimatableTransform transform;
+
+ Repeater(String name, AnimatableFloatValue copies, AnimatableFloatValue offset,
+ AnimatableTransform transform) {
+ this.name = name;
+ this.copies = copies;
+ this.offset = offset;
+ this.transform = transform;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ AnimatableFloatValue getCopies() {
+ return copies;
+ }
+
+ AnimatableFloatValue getOffset() {
+ return offset;
+ }
+
+ AnimatableTransform getTransform() {
+ return transform;
+ }
+
+ @Nullable @Override public Content toContent(LottieDrawable drawable, BaseLayer layer) {
+ return new RepeaterContent(drawable, layer, this);
+ }
+
+ final static class Factory {
+
+ private Factory() {
+ }
+
+ static Repeater newInstance(JSONObject json, LottieComposition composition) {
+ String name = json.optString("nm");
+ AnimatableFloatValue copies =
+ AnimatableFloatValue.Factory.newInstance(json.optJSONObject("c"), composition, false);
+ AnimatableFloatValue offset =
+ AnimatableFloatValue.Factory.newInstance(json.optJSONObject("o"), composition, false);
+ AnimatableTransform transform =
+ AnimatableTransform.Factory.newInstance(json.optJSONObject("tr"), composition);
+
+ return new Repeater(name, copies, offset, transform);
+ }
+ }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/RepeaterContent.java b/lottie/src/main/java/com/airbnb/lottie/RepeaterContent.java
new file mode 100644
index 0000000..1af0f6c
--- /dev/null
+++ b/lottie/src/main/java/com/airbnb/lottie/RepeaterContent.java
@@ -0,0 +1,122 @@
+package com.airbnb.lottie;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+public class RepeaterContent implements
+ DrawingContent, PathContent, GreedyContent, BaseKeyframeAnimation.AnimationListener {
+ private final Matrix matrix = new Matrix();
+ private final Path path = new Path();
+
+ private final LottieDrawable lottieDrawable;
+ private final BaseLayer layer;
+ private final String name;
+ private final KeyframeAnimation<Float> copies;
+ private final KeyframeAnimation<Float> offset;
+ private final TransformKeyframeAnimation transform;
+ private ContentGroup contentGroup;
+
+
+ RepeaterContent(LottieDrawable lottieDrawable, BaseLayer layer, Repeater repeater) {
+ this.lottieDrawable = lottieDrawable;
+ this.layer = layer;
+ name = repeater.getName();
+ copies = repeater.getCopies().createAnimation();
+ layer.addAnimation(copies);
+ copies.addUpdateListener(this);
+
+ offset = repeater.getOffset().createAnimation();
+ layer.addAnimation(offset);
+ offset.addUpdateListener(this);
+
+ transform = repeater.getTransform().createAnimation();
+ transform.addAnimationsToLayer(layer);
+ transform.addListener(this);
+ }
+
+ @Override public void absorbContent(ListIterator<Content> contentsIter) {
+ // This check prevents a repeater from getting added twice.
+ // This can happen in the following situation:
+ // RECTANGLE
+ // REPEATER 1
+ // FILL
+ // REPEATER 2
+ // In this case, the expected structure would be:
+ // REPEATER 2
+ // REPEATER 1
+ // RECTANGLE
+ // FILL
+ // Without this check, REPEATER 1 will try and absorb contents once it is already inside of
+ // REPEATER 2.
+ if (contentGroup != null) {
+ return;
+ }
+ // Fast forward the iterator until after this content.
+ //noinspection StatementWithEmptyBody
+ while (contentsIter.hasPrevious() && contentsIter.previous() != this) {}
+ List<Content> contents = new ArrayList<>();
+ while (contentsIter.hasPrevious()) {
+ contents.add(contentsIter.previous());
+ contentsIter.remove();
+ }
+ Collections.reverse(contents);
+ contentGroup = new ContentGroup(lottieDrawable, layer, "Repeater", contents, null);
+ }
+
+ @Override public String getName() {
+ return name;
+ }
+
+ @Override public void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
+ contentGroup.setContents(contentsBefore, contentsAfter);
+ }
+
+ @Override public Path getPath() {
+ Path contentPath = contentGroup.getPath();
+ path.reset();
+ float copies = this.copies.getValue();
+ float offset = this.offset.getValue();
+ for (int i = (int) copies - 1; i >= 0; i--) {
+ matrix.set(transform.getMatrixForRepeater(i + offset));
+ path.addPath(contentPath, matrix);
+ }
+ return path;
+ }
+
+ @Override public void draw(Canvas canvas, Matrix parentMatrix, int alpha) {
+ float copies = this.copies.getValue();
+ float offset = this.offset.getValue();
+ //noinspection ConstantConditions
+ float startOpacity = this.transform.getStartOpacity().getValue() / 100f;
+ //noinspection ConstantConditions
+ float endOpacity = this.transform.getEndOpacity().getValue() / 100f;
+ for (int i = (int) copies - 1; i >= 0; i--) {
+ matrix.set(parentMatrix);
+ matrix.preConcat(transform.getMatrixForRepeater(i + offset));
+ float newAlpha = alpha * MiscUtils.lerp(startOpacity, endOpacity, i / copies);
+ contentGroup.draw(canvas, matrix, (int) newAlpha);
+ }
+ }
+
+ @Override public void getBounds(RectF outBounds, Matrix parentMatrix) {
+ contentGroup.getBounds(outBounds, parentMatrix);
+ }
+
+ @Override public void addColorFilter(@Nullable String layerName, @Nullable String contentName,
+ @Nullable ColorFilter colorFilter) {
+ contentGroup.addColorFilter(layerName, contentName, colorFilter);
+ }
+
+ @Override public void onValueChanged() {
+ lottieDrawable.invalidateSelf();
+ }
+}
diff --git a/lottie/src/main/java/com/airbnb/lottie/ShapeGroup.java b/lottie/src/main/java/com/airbnb/lottie/ShapeGroup.java
index 30875a4..b8486c4 100644
--- a/lottie/src/main/java/com/airbnb/lottie/ShapeGroup.java
+++ b/lottie/src/main/java/com/airbnb/lottie/ShapeGroup.java
@@ -39,6 +39,8 @@
return PolystarShape.Factory.newInstance(json, composition);
case "mm":
return MergePaths.Factory.newInstance(json);
+ case "rp":
+ return Repeater.Factory.newInstance(json, composition);
default:
Log.w(L.TAG, "Unknown shape type " + type);
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/TransformKeyframeAnimation.java b/lottie/src/main/java/com/airbnb/lottie/TransformKeyframeAnimation.java
index cb5bef4..466ba70 100644
--- a/lottie/src/main/java/com/airbnb/lottie/TransformKeyframeAnimation.java
+++ b/lottie/src/main/java/com/airbnb/lottie/TransformKeyframeAnimation.java
@@ -2,15 +2,20 @@
import android.graphics.Matrix;
import android.graphics.PointF;
+import android.support.annotation.Nullable;
class TransformKeyframeAnimation {
private final Matrix matrix = new Matrix();
- private final BaseKeyframeAnimation<?, PointF> anchorPoint;
+ private final KeyframeAnimation<PointF> anchorPoint;
private final BaseKeyframeAnimation<?, PointF> position;
- private final BaseKeyframeAnimation<?, ScaleXY> scale;
- private final BaseKeyframeAnimation<?, Float> rotation;
- private final BaseKeyframeAnimation<?, Integer> opacity;
+ private final KeyframeAnimation<ScaleXY> scale;
+ private final KeyframeAnimation<Float> rotation;
+ private final KeyframeAnimation<Integer> opacity;
+
+ // Used for repeaters
+ @Nullable private final BaseKeyframeAnimation<?, Float> startOpacity;
+ @Nullable private final BaseKeyframeAnimation<?, Float> endOpacity;
TransformKeyframeAnimation(AnimatableTransform animatableTransform) {
anchorPoint = animatableTransform.getAnchorPoint().createAnimation();
@@ -18,6 +23,16 @@
scale = animatableTransform.getScale().createAnimation();
rotation = animatableTransform.getRotation().createAnimation();
opacity = animatableTransform.getOpacity().createAnimation();
+ if (animatableTransform.getStartOpacity() != null) {
+ startOpacity = animatableTransform.getStartOpacity().createAnimation();
+ } else {
+ startOpacity = null;
+ }
+ if (animatableTransform.getEndOpacity() != null) {
+ endOpacity = animatableTransform.getEndOpacity().createAnimation();
+ } else {
+ endOpacity = null;
+ }
}
void addAnimationsToLayer(BaseLayer layer) {
@@ -26,6 +41,12 @@
layer.addAnimation(scale);
layer.addAnimation(rotation);
layer.addAnimation(opacity);
+ if (startOpacity != null) {
+ layer.addAnimation(startOpacity);
+ }
+ if (endOpacity != null) {
+ layer.addAnimation(endOpacity);
+ }
}
void addListener(final BaseKeyframeAnimation.AnimationListener listener) {
@@ -34,12 +55,27 @@
scale.addUpdateListener(listener);
rotation.addUpdateListener(listener);
opacity.addUpdateListener(listener);
+ if (startOpacity != null) {
+ startOpacity.addUpdateListener(listener);
+ }
+ if (endOpacity != null) {
+ endOpacity.addUpdateListener(listener);
+ }
}
BaseKeyframeAnimation<?, Integer> getOpacity() {
return opacity;
}
+ @Nullable public BaseKeyframeAnimation<?, Float> getStartOpacity() {
+ return startOpacity;
+ }
+
+ @Nullable public BaseKeyframeAnimation<?, Float> getEndOpacity() {
+ return endOpacity;
+ }
+
+
Matrix getMatrix() {
matrix.reset();
PointF position = this.position.getValue();
@@ -63,4 +99,24 @@
}
return matrix;
}
+
+ /**
+ * TODO: see if we can use this for the main {@link #getMatrix()} method.
+ */
+ Matrix getMatrixForRepeater(float amount) {
+ PointF position = this.position.getValue();
+ PointF anchorPoint = this.anchorPoint.getValue();
+ ScaleXY scale = this.scale.getValue();
+ float rotation = this.rotation.getValue();
+
+ matrix.reset();
+ matrix.preTranslate(position.x * amount, position.y * amount);
+ matrix.preScale(
+ (float) Math.pow(scale.getScaleX(), amount),
+ (float) Math.pow(scale.getScaleY(), amount));
+ matrix.preRotate(rotation * amount, anchorPoint.x, anchorPoint.y);
+
+ return matrix;
+ }
+
}