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 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
37TransformAdapter2D::~TransformAdapter2D() {}
38
39void TransformAdapter2D::onSync() {
40 this->node()->setMatrix(this->totalMatrix());
41}
42
43SkMatrix TransformAdapter2D::totalMatrix() const {
44 // TODO: skew
45 return SkMatrix::Translate(fPosition.x, fPosition.y)
46 * SkMatrix::RotateDeg(fRotation + fOrientation)
47 * SkMatrix::Scale (fScale.x / 100, fScale.y / 100) // 100% based
48 * SkMatrix::Translate(-fAnchorPoint.x, -fAnchorPoint.y);
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,
95 bool auto_orient) const {
96 const auto* jrotation = &jtransform["r"];
97 if (jrotation->is<skjson::NullValue>()) {
98 // Some 2D rotations are disguised as 3D...
99 jrotation = &jtransform["rz"];
100 }
101
102 auto adapter = TransformAdapter2D::Make(*this,
103 jtransform["a"],
104 jtransform["p"],
105 jtransform["s"],
106 *jrotation,
107 jtransform["sk"],
108 jtransform["sa"],
109 auto_orient);
110 SkASSERT(adapter);
111
112 const auto dispatched = this->dispatchTransformProperty(adapter);
113
114 if (adapter->isStatic()) {
115 if (!dispatched && adapter->totalMatrix().isIdentity()) {
116 // The transform has no observable effects - we can discard.
117 return parent;
118 }
119 adapter->seek(0);
120 } else {
121 fCurrentAnimatorScope->push_back(adapter);
122 }
123
124 return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
125}
126
127TransformAdapter3D::TransformAdapter3D(const skjson::ObjectValue& jtransform,
128 const AnimationBuilder& abuilder)
129 : INHERITED(sksg::Matrix<SkM44>::Make(SkM44())) {
130
131 this->bind(abuilder, jtransform["a"], fAnchorPoint);
132 this->bind(abuilder, jtransform["p"], fPosition);
133 this->bind(abuilder, jtransform["s"], fScale);
134
135 // Axis-wise rotation and orientation are mapped to the same rotation property (3D rotation).
136 // The difference is in how they get interpolated (scalar/decomposed vs. vector).
137 this->bind(abuilder, jtransform["rx"], fRx);
138 this->bind(abuilder, jtransform["ry"], fRy);
139 this->bind(abuilder, jtransform["rz"], fRz);
140 this->bind(abuilder, jtransform["or"], fOrientation);
141}
142
143TransformAdapter3D::~TransformAdapter3D() = default;
144
145void TransformAdapter3D::onSync() {
146 this->node()->setMatrix(this->totalMatrix());
147}
148
149SkV3 TransformAdapter3D::anchor_point() const {
150 return fAnchorPoint;
151}
152
153SkV3 TransformAdapter3D::position() const {
154 return fPosition;
155}
156
157SkV3 TransformAdapter3D::rotation() const {
158 // orientation and axis-wise rotation map onto the same property.
159 return static_cast<SkV3>(fOrientation) + SkV3{ fRx, fRy, fRz };
160}
161
162SkM44 TransformAdapter3D::totalMatrix() const {
163 const auto anchor_point = this->anchor_point(),
164 position = this->position(),
165 scale = static_cast<SkV3>(fScale),
166 rotation = this->rotation();
167
168 return SkM44::Translate(position.x, position.y, position.z)
169 * SkM44::Rotate({ 1, 0, 0 }, SkDegreesToRadians(rotation.x))
170 * SkM44::Rotate({ 0, 1, 0 }, SkDegreesToRadians(rotation.y))
171 * SkM44::Rotate({ 0, 0, 1 }, SkDegreesToRadians(rotation.z))
172 * SkM44::Scale(scale.x / 100, scale.y / 100, scale.z / 100)
173 * SkM44::Translate(-anchor_point.x, -anchor_point.y, -anchor_point.z);
174}
175
176sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& jtransform,
177 sk_sp<sksg::Transform> parent,
178 bool /*TODO: auto_orient*/) const {
179 auto adapter = TransformAdapter3D::Make(jtransform, *this);
180 SkASSERT(adapter);
181
182 if (adapter->isStatic()) {
183 // TODO: SkM44::isIdentity?
184 if (adapter->totalMatrix() == SkM44()) {
185 // The transform has no observable effects - we can discard.
186 return parent;
187 }
188 adapter->seek(0);
189 } else {
190 fCurrentAnimatorScope->push_back(adapter);
191 }
192
193 return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
194}
195
196} // namespace internal
197} // namespace skottie
198