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
14namespace skottie {
15namespace internal {
16
17TransformAdapter2D::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 : INHERITED(sksg::Matrix<SkMatrix>::Make(SkMatrix::I())) {
25
26 this->bind(abuilder, janchor_point, fAnchorPoint);
27 this->bind(abuilder, jposition , fPosition);
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
34TransformAdapter2D::~TransformAdapter2D() {}
35
36void TransformAdapter2D::onSync() {
37 this->node()->setMatrix(this->totalMatrix());
38}
39
40SkMatrix TransformAdapter2D::totalMatrix() const {
41 SkMatrix t = SkMatrix::MakeTrans(-fAnchorPoint.x, -fAnchorPoint.y);
42
43 t.postScale(fScale.x / 100, fScale.y / 100); // 100% based
44 t.postRotate(fRotation);
45 t.postTranslate(fPosition.x, fPosition.y);
46 // TODO: skew
47
48 return t;
49}
50
51SkPoint TransformAdapter2D::getAnchorPoint() const {
52 return { fAnchorPoint.x, fAnchorPoint.y };
53}
54
55void TransformAdapter2D::setAnchorPoint(const SkPoint& ap) {
56 fAnchorPoint = { ap.x(), ap.y() };
57 this->onSync();
58}
59
60SkPoint TransformAdapter2D::getPosition() const {
61 return { fPosition.x, fPosition.y };
62}
63
64void TransformAdapter2D::setPosition(const SkPoint& p) {
65 fPosition = { p.x(), p.y() };
66 this->onSync();
67}
68
69SkVector TransformAdapter2D::getScale() const {
70 return { fScale.x, fScale.y };
71}
72
73void TransformAdapter2D::setScale(const SkVector& s) {
74 fScale = { s.x(), s.y() };
75 this->onSync();
76}
77
78void TransformAdapter2D::setRotation(float r) {
79 fRotation = r;
80 this->onSync();
81}
82
83void TransformAdapter2D::setSkew(float sk) {
84 fSkew = sk;
85 this->onSync();
86}
87
88void TransformAdapter2D::setSkewAxis(float sa) {
89 fSkewAxis = sa;
90 this->onSync();
91}
92
93sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& jtransform,
94 sk_sp<sksg::Transform> parent) const {
95 const auto* jrotation = &jtransform["r"];
96 if (jrotation->is<skjson::NullValue>()) {
97 // Some 2D rotations are disguised as 3D...
98 jrotation = &jtransform["rz"];
99 }
100
101 auto adapter = TransformAdapter2D::Make(*this,
102 jtransform["a"],
103 jtransform["p"],
104 jtransform["s"],
105 *jrotation,
106 jtransform["sk"],
107 jtransform["sa"]);
108 SkASSERT(adapter);
109
110 const auto dispatched = this->dispatchTransformProperty(adapter);
111
112 if (adapter->isStatic()) {
113 if (!dispatched && adapter->totalMatrix().isIdentity()) {
114 // The transform has no observable effects - we can discard.
115 return parent;
116 }
117 adapter->seek(0);
118 } else {
119 fCurrentAnimatorScope->push_back(adapter);
120 }
121
122 return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
123}
124
125TransformAdapter3D::TransformAdapter3D(const skjson::ObjectValue& jtransform,
126 const AnimationBuilder& abuilder)
127 : INHERITED(sksg::Matrix<SkM44>::Make(SkM44())) {
128
129 this->bind(abuilder, jtransform["a"], fAnchorPoint);
130 this->bind(abuilder, jtransform["p"], fPosition);
131 this->bind(abuilder, jtransform["s"], fScale);
132
133 // Axis-wise rotation and orientation are mapped to the same rotation property (3D rotation).
134 // The difference is in how they get interpolated (scalar/decomposed vs. vector).
135 this->bind(abuilder, jtransform["rx"], fRx);
136 this->bind(abuilder, jtransform["ry"], fRy);
137 this->bind(abuilder, jtransform["rz"], fRz);
138 this->bind(abuilder, jtransform["or"], fOrientation);
139}
140
141TransformAdapter3D::~TransformAdapter3D() = default;
142
143void TransformAdapter3D::onSync() {
144 this->node()->setMatrix(this->totalMatrix());
145}
146
147SkV3 TransformAdapter3D::anchor_point() const {
148 return fAnchorPoint;
149}
150
151SkV3 TransformAdapter3D::position() const {
152 return fPosition;
153}
154
155SkV3 TransformAdapter3D::rotation() const {
156 // orientation and axis-wise rotation map onto the same property.
157 return static_cast<SkV3>(fOrientation) + SkV3{ fRx, fRy, fRz };
158}
159
160SkM44 TransformAdapter3D::totalMatrix() const {
161 const auto anchor_point = this->anchor_point(),
162 position = this->position(),
163 scale = static_cast<SkV3>(fScale),
164 rotation = this->rotation();
165
166 return SkM44::Translate(position.x, position.y, position.z)
167 * SkM44::Rotate({ 1, 0, 0 }, SkDegreesToRadians(rotation.x))
168 * SkM44::Rotate({ 0, 1, 0 }, SkDegreesToRadians(rotation.y))
169 * SkM44::Rotate({ 0, 0, 1 }, SkDegreesToRadians(rotation.z))
170 * SkM44::Scale(scale.x / 100, scale.y / 100, scale.z / 100)
171 * SkM44::Translate(-anchor_point.x, -anchor_point.y, -anchor_point.z);
172}
173
174sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& jtransform,
175 sk_sp<sksg::Transform> parent) const {
176 auto adapter = TransformAdapter3D::Make(jtransform, *this);
177 SkASSERT(adapter);
178
179 if (adapter->isStatic()) {
180 // TODO: SkM44::isIdentity?
181 if (adapter->totalMatrix() == SkM44()) {
182 // The transform has no observable effects - we can discard.
183 return parent;
184 }
185 adapter->seek(0);
186 } else {
187 fCurrentAnimatorScope->push_back(adapter);
188 }
189
190 return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
191}
192
193} // namespace internal
194} // namespace skottie
195