Skip to content

Commit

Permalink
Implement auto-orient (#2416)
Browse files Browse the repository at this point in the history
Fixes #1401
  • Loading branch information
gpeal committed Nov 15, 2023
1 parent 2ee2b28 commit 2bb8025
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@ public class TransformKeyframeAnimation {
@Nullable private BaseKeyframeAnimation<?, Float> startOpacity;
@Nullable private BaseKeyframeAnimation<?, Float> endOpacity;

private final boolean autoOrient;


public TransformKeyframeAnimation(AnimatableTransform animatableTransform) {
anchorPoint = animatableTransform.getAnchorPoint() == null ? null : animatableTransform.getAnchorPoint().createAnimation();
position = animatableTransform.getPosition() == null ? null : animatableTransform.getPosition().createAnimation();
scale = animatableTransform.getScale() == null ? null : animatableTransform.getScale().createAnimation();
rotation = animatableTransform.getRotation() == null ? null : animatableTransform.getRotation().createAnimation();
skew = animatableTransform.getSkew() == null ? null : (FloatKeyframeAnimation) animatableTransform.getSkew().createAnimation();
autoOrient = animatableTransform.isAutoOrient();
if (skew != null) {
skewMatrix1 = new Matrix();
skewMatrix2 = new Matrix();
Expand Down Expand Up @@ -174,16 +178,36 @@ public Matrix getMatrix() {
}
}

BaseKeyframeAnimation<Float, Float> rotation = this.rotation;
if (rotation != null) {
float rotationValue;
if (rotation instanceof ValueCallbackKeyframeAnimation) {
rotationValue = rotation.getValue();
} else {
rotationValue = ((FloatKeyframeAnimation) rotation).getFloatValue();
// If autoOrient is true, the rotation should follow the derivative of the position rather
// than the rotation property.
if (autoOrient) {
if (position != null) {
float currentProgress = position.getProgress();
PointF startPosition = position.getValue();
// Store the start X and Y values because the pointF will be overwritten by the next getValue call.
float startX = startPosition.x;
float startY = startPosition.y;
// 1) Find the next position value.
// 2) Create a vector from the current position to the next position.
// 3) Find the angle of that vector to the X axis (0 degrees).
position.setProgress(currentProgress + 0.0001f);
PointF nextPosition = position.getValue();
position.setProgress(currentProgress);
double rotationValue = Math.toDegrees(Math.atan2(nextPosition.y - startY, nextPosition.x - startX));
matrix.preRotate((float) rotationValue);
}
if (rotationValue != 0f) {
matrix.preRotate(rotationValue);
} else {
BaseKeyframeAnimation<Float, Float> rotation = this.rotation;
if (rotation != null) {
float rotationValue;
if (rotation instanceof ValueCallbackKeyframeAnimation) {
rotationValue = rotation.getValue();
} else {
rotationValue = ((FloatKeyframeAnimation) rotation).getFloatValue();
}
if (rotationValue != 0f) {
matrix.preRotate(rotationValue);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class AnimatableTransform implements ModifierContent, ContentModel {
@Nullable
private final AnimatableFloatValue endOpacity;

private boolean autoOrient = false;

public AnimatableTransform() {
this(null, null, null, null, null, null, null, null, null);
}
Expand All @@ -54,6 +56,13 @@ public AnimatableTransform(@Nullable AnimatablePathValue anchorPoint,
this.skewAngle = skewAngle;
}

/**
* This is set as a property of the layer so it is parsed and set separately.
*/
public void setAutoOrient(boolean autoOrient) {
this.autoOrient = autoOrient;
}

@Nullable
public AnimatablePathValue getAnchorPoint() {
return anchorPoint;
Expand Down Expand Up @@ -99,6 +108,10 @@ public AnimatableFloatValue getSkewAngle() {
return skewAngle;
}

public boolean isAutoOrient() {
return autoOrient;
}

public TransformKeyframeAnimation createAnimation() {
return new TransformKeyframeAnimation(this);
}
Expand Down
37 changes: 24 additions & 13 deletions lottie/src/main/java/com/airbnb/lottie/parser/LayerParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,30 @@ private LayerParser() {
}

private static final JsonReader.Options NAMES = JsonReader.Options.of(
"nm", // 0
"ind", // 1
"refId", // 2
"ty", // 3
"nm", // 0
"ind", // 1
"refId", // 2
"ty", // 3
"parent", // 4
"sw", // 5
"sh", // 6
"sc", // 7
"ks", // 8
"tt", // 9
"sw", // 5
"sh", // 6
"sc", // 7
"ks", // 8
"tt", // 9
"masksProperties", // 10
"shapes", // 11
"t", // 12
"t", // 12
"ef", // 13
"sr", // 14
"st", // 15
"w", // 16
"h", // 17
"w", // 16
"h", // 17
"ip", // 18
"op", // 19
"tm", // 20
"cl", // 21
"hd" // 22
"hd", // 22
"ao" // 23
);

public static Layer parse(LottieComposition composition) {
Expand Down Expand Up @@ -93,6 +94,7 @@ public static Layer parse(JsonReader reader, LottieComposition composition) thro
boolean hidden = false;
BlurEffect blurEffect = null;
DropShadowEffect dropShadowEffect = null;
boolean autoOrient = false;

Layer.MatteType matteType = Layer.MatteType.NONE;
AnimatableTransform transform = null;
Expand Down Expand Up @@ -256,6 +258,9 @@ public static Layer parse(JsonReader reader, LottieComposition composition) thro
case 22:
hidden = reader.nextBoolean();
break;
case 23:
autoOrient = reader.nextInt() == 1;
break;
default:
reader.skipName();
reader.skipValue();
Expand Down Expand Up @@ -284,6 +289,12 @@ public static Layer parse(JsonReader reader, LottieComposition composition) thro
composition.addWarning("Convert your Illustrator layers to shape layers.");
}

if (autoOrient) {
if (transform == null) {
transform = new AnimatableTransform();
}
transform.setAutoOrient(autoOrient);
}
return new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId,
masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startFrame,
preCompWidth, preCompHeight, text, textProperties, inOutKeyframes, matteType,
Expand Down
1 change: 1 addition & 0 deletions snapshot-tests/src/main/assets/Tests/AutoOrient.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"v":"5.12.1","fr":29.9700012207031,"ip":0,"op":284.000011567557,"w":400,"h":400,"nm":"ao","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"shape","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.641,"y":0.641},"o":{"x":0.167,"y":0.167},"t":0,"s":[68.5,44.5,0],"to":[2.395,1.173,0],"ti":[-52,-114,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.438,"y":0.438},"t":83.533,"s":[304.5,148.5,0],"to":[-8,108,0],"ti":[-2.164,-1.328,0]},{"i":{"x":0.575,"y":0.575},"o":{"x":0.167,"y":0.167},"t":114,"s":[340.035,204.247,0],"to":[60.399,77.002,0],"ti":[118,-2,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.407,"y":0.407},"t":189.251,"s":[240.5,270.5,0],"to":[-118,2,0],"ti":[52.68,50.729,0]},{"t":283.000011526826,"s":[14.5,222.5,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":1,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-172.23,-7.099],[-161.73,29.401],[-73.23,-21.099]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[54.125,22.142],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":284.000011567557,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}

0 comments on commit 2bb8025

Please sign in to comment.