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    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
66SkPoint TransformAdapter2D::getAnchorPoint() const {
67    return { fAnchorPoint.x, fAnchorPoint.y };
68}
69
70void TransformAdapter2D::setAnchorPoint(const SkPoint& ap) {
71    fAnchorPoint = { ap.x(), ap.y() };
72    this->onSync();
73}
74
75SkPoint TransformAdapter2D::getPosition() const {
76    return { fPosition.x, fPosition.y };
77}
78
79void TransformAdapter2D::setPosition(const SkPoint& p) {
80    fPosition = { p.x(), p.y() };
81    this->onSync();
82}
83
84SkVector TransformAdapter2D::getScale() const {
85    return { fScale.x, fScale.y };
86}
87
88void TransformAdapter2D::setScale(const SkVector& s) {
89    fScale = { s.x(), s.y() };
90    this->onSync();
91}
92
93void TransformAdapter2D::setRotation(float r) {
94    fRotation = r;
95    this->onSync();
96}
97
98void TransformAdapter2D::setSkew(float sk) {
99    fSkew = sk;
100    this->onSync();
101}
102
103void TransformAdapter2D::setSkewAxis(float sa) {
104    fSkewAxis = sa;
105    this->onSync();
106}
107
108sk_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
142TransformAdapter3D::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
158TransformAdapter3D::~TransformAdapter3D() = default;
159
160void TransformAdapter3D::onSync() {
161    this->node()->setMatrix(this->totalMatrix());
162}
163
164SkV3 TransformAdapter3D::anchor_point() const {
165    return fAnchorPoint;
166}
167
168SkV3 TransformAdapter3D::position() const {
169    return fPosition;
170}
171
172SkV3 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
177SkM44 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
191sk_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