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