1 /*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "modules/skottie/src/Transform.h"
9
10 #include "include/core/SkRefCnt.h"
11 #include "include/core/SkScalar.h"
12 #include "include/private/base/SkAssert.h"
13 #include "include/private/base/SkTPin.h"
14 #include "modules/skottie/src/SkottiePriv.h"
15 #include "modules/sksg/include/SkSGTransform.h"
16 #include "src/utils/SkJSON.h"
17
18 #include <cmath>
19
20 namespace skottie {
21 namespace internal {
22
TransformAdapter2D(const AnimationBuilder & abuilder,const skjson::ObjectValue * janchor_point,const skjson::ObjectValue * jposition,const skjson::ObjectValue * jscale,const skjson::ObjectValue * jrotation,const skjson::ObjectValue * jskew,const skjson::ObjectValue * jskew_axis,bool auto_orient)23 TransformAdapter2D::TransformAdapter2D(const AnimationBuilder& abuilder,
24 const skjson::ObjectValue* janchor_point,
25 const skjson::ObjectValue* jposition,
26 const skjson::ObjectValue* jscale,
27 const skjson::ObjectValue* jrotation,
28 const skjson::ObjectValue* jskew,
29 const skjson::ObjectValue* jskew_axis,
30 bool auto_orient)
31 : INHERITED(sksg::Matrix<SkMatrix>::Make(SkMatrix::I())) {
32
33 this->bind(abuilder, janchor_point, fAnchorPoint);
34 this->bind(abuilder, jscale , fScale);
35 this->bind(abuilder, jrotation , fRotation);
36 this->bind(abuilder, jskew , fSkew);
37 this->bind(abuilder, jskew_axis , fSkewAxis);
38
39 this->bindAutoOrientable(abuilder, jposition, &fPosition, auto_orient ? &fOrientation
40 : nullptr);
41 }
42
~TransformAdapter2D()43 TransformAdapter2D::~TransformAdapter2D() {}
44
onSync()45 void TransformAdapter2D::onSync() {
46 this->node()->setMatrix(this->totalMatrix());
47 }
48
totalMatrix() const49 SkMatrix TransformAdapter2D::totalMatrix() const {
50 auto skew_matrix = [](float sk, float sa) {
51 if (!sk) return SkMatrix::I();
52
53 // AE control limit.
54 static constexpr float kMaxSkewAngle = 85;
55 sk = -SkDegreesToRadians(SkTPin(sk, -kMaxSkewAngle, kMaxSkewAngle));
56 sa = SkDegreesToRadians(sa);
57
58 // Similar to CSS/SVG SkewX [1] with an explicit rotation.
59 // [1] https://www.w3.org/TR/css-transforms-1/#SkewXDefined
60 return SkMatrix::RotateRad(sa)
61 * SkMatrix::Skew(std::tan(sk), 0)
62 * SkMatrix::RotateRad(-sa);
63 };
64
65 return SkMatrix::Translate(fPosition.x, fPosition.y)
66 * SkMatrix::RotateDeg(fRotation + fOrientation)
67 * skew_matrix (fSkew, fSkewAxis)
68 * SkMatrix::Scale (fScale.x / 100, fScale.y / 100) // 100% based
69 * SkMatrix::Translate(-fAnchorPoint.x, -fAnchorPoint.y);
70 }
71
getAnchorPoint() const72 SkPoint TransformAdapter2D::getAnchorPoint() const {
73 return { fAnchorPoint.x, fAnchorPoint.y };
74 }
75
setAnchorPoint(const SkPoint & ap)76 void TransformAdapter2D::setAnchorPoint(const SkPoint& ap) {
77 fAnchorPoint = { ap.x(), ap.y() };
78 this->onSync();
79 }
80
getPosition() const81 SkPoint TransformAdapter2D::getPosition() const {
82 return { fPosition.x, fPosition.y };
83 }
84
setPosition(const SkPoint & p)85 void TransformAdapter2D::setPosition(const SkPoint& p) {
86 fPosition = { p.x(), p.y() };
87 this->onSync();
88 }
89
getScale() const90 SkVector TransformAdapter2D::getScale() const {
91 return { fScale.x, fScale.y };
92 }
93
setScale(const SkVector & s)94 void TransformAdapter2D::setScale(const SkVector& s) {
95 fScale = { s.x(), s.y() };
96 this->onSync();
97 }
98
setRotation(float r)99 void TransformAdapter2D::setRotation(float r) {
100 fRotation = r;
101 this->onSync();
102 }
103
setSkew(float sk)104 void TransformAdapter2D::setSkew(float sk) {
105 fSkew = sk;
106 this->onSync();
107 }
108
setSkewAxis(float sa)109 void TransformAdapter2D::setSkewAxis(float sa) {
110 fSkewAxis = sa;
111 this->onSync();
112 }
113
attachMatrix2D(const skjson::ObjectValue & jtransform,sk_sp<sksg::Transform> parent,bool auto_orient) const114 sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& jtransform,
115 sk_sp<sksg::Transform> parent,
116 bool auto_orient) const {
117 const auto* jrotation = &jtransform["r"];
118 if (jrotation->is<skjson::NullValue>()) {
119 // Some 2D rotations are disguised as 3D...
120 jrotation = &jtransform["rz"];
121 }
122
123 auto adapter = TransformAdapter2D::Make(*this,
124 jtransform["a"],
125 jtransform["p"],
126 jtransform["s"],
127 *jrotation,
128 jtransform["sk"],
129 jtransform["sa"],
130 auto_orient);
131 SkASSERT(adapter);
132
133 const auto dispatched = this->dispatchTransformProperty(adapter);
134
135 if (adapter->isStatic()) {
136 if (!dispatched && adapter->totalMatrix().isIdentity()) {
137 // The transform has no observable effects - we can discard.
138 return parent;
139 }
140 adapter->seek(0);
141 } else {
142 fCurrentAnimatorScope->push_back(adapter);
143 }
144
145 return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
146 }
147
TransformAdapter3D(const skjson::ObjectValue & jtransform,const AnimationBuilder & abuilder)148 TransformAdapter3D::TransformAdapter3D(const skjson::ObjectValue& jtransform,
149 const AnimationBuilder& abuilder)
150 : INHERITED(sksg::Matrix<SkM44>::Make(SkM44())) {
151
152 this->bind(abuilder, jtransform["a"], fAnchorPoint);
153 this->bind(abuilder, jtransform["p"], fPosition);
154 this->bind(abuilder, jtransform["s"], fScale);
155
156 // Axis-wise rotation and orientation are mapped to the same rotation property (3D rotation).
157 // The difference is in how they get interpolated (scalar/decomposed vs. vector).
158 this->bind(abuilder, jtransform["rx"], fRx);
159 this->bind(abuilder, jtransform["ry"], fRy);
160 this->bind(abuilder, jtransform["rz"], fRz);
161 this->bind(abuilder, jtransform["or"], fOrientation);
162 }
163
164 TransformAdapter3D::~TransformAdapter3D() = default;
165
onSync()166 void TransformAdapter3D::onSync() {
167 this->node()->setMatrix(this->totalMatrix());
168 }
169
anchor_point() const170 SkV3 TransformAdapter3D::anchor_point() const {
171 return fAnchorPoint;
172 }
173
position() const174 SkV3 TransformAdapter3D::position() const {
175 return fPosition;
176 }
177
rotation() const178 SkV3 TransformAdapter3D::rotation() const {
179 // orientation and axis-wise rotation map onto the same property.
180 return static_cast<SkV3>(fOrientation) + SkV3{ fRx, fRy, fRz };
181 }
182
totalMatrix() const183 SkM44 TransformAdapter3D::totalMatrix() const {
184 const auto anchor_point = this->anchor_point(),
185 position = this->position(),
186 scale = static_cast<SkV3>(fScale),
187 rotation = this->rotation();
188
189 return SkM44::Translate(position.x, position.y, position.z)
190 * SkM44::Rotate({ 1, 0, 0 }, SkDegreesToRadians(rotation.x))
191 * SkM44::Rotate({ 0, 1, 0 }, SkDegreesToRadians(rotation.y))
192 * SkM44::Rotate({ 0, 0, 1 }, SkDegreesToRadians(rotation.z))
193 * SkM44::Scale(scale.x / 100, scale.y / 100, scale.z / 100)
194 * SkM44::Translate(-anchor_point.x, -anchor_point.y, -anchor_point.z);
195 }
196
attachMatrix3D(const skjson::ObjectValue & jtransform,sk_sp<sksg::Transform> parent,bool) const197 sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& jtransform,
198 sk_sp<sksg::Transform> parent,
199 bool /*TODO: auto_orient*/) const {
200 auto adapter = TransformAdapter3D::Make(jtransform, *this);
201 SkASSERT(adapter);
202
203 if (adapter->isStatic()) {
204 // TODO: SkM44::isIdentity?
205 if (adapter->totalMatrix() == SkM44()) {
206 // The transform has no observable effects - we can discard.
207 return parent;
208 }
209 adapter->seek(0);
210 } else {
211 fCurrentAnimatorScope->push_back(adapter);
212 }
213
214 return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
215 }
216
217 } // namespace internal
218 } // namespace skottie
219