| 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 | |
| 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 | |
| 37 | TransformAdapter2D::~TransformAdapter2D() {} |
| 38 | |
| 39 | void TransformAdapter2D::onSync() { |
| 40 | this->node()->setMatrix(this->totalMatrix()); |
| 41 | } |
| 42 | |
| 43 | SkMatrix 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 | |
| 51 | SkPoint TransformAdapter2D::getAnchorPoint() const { |
| 52 | return { fAnchorPoint.x, fAnchorPoint.y }; |
| 53 | } |
| 54 | |
| 55 | void TransformAdapter2D::setAnchorPoint(const SkPoint& ap) { |
| 56 | fAnchorPoint = { ap.x(), ap.y() }; |
| 57 | this->onSync(); |
| 58 | } |
| 59 | |
| 60 | SkPoint TransformAdapter2D::getPosition() const { |
| 61 | return { fPosition.x, fPosition.y }; |
| 62 | } |
| 63 | |
| 64 | void TransformAdapter2D::setPosition(const SkPoint& p) { |
| 65 | fPosition = { p.x(), p.y() }; |
| 66 | this->onSync(); |
| 67 | } |
| 68 | |
| 69 | SkVector TransformAdapter2D::getScale() const { |
| 70 | return { fScale.x, fScale.y }; |
| 71 | } |
| 72 | |
| 73 | void TransformAdapter2D::setScale(const SkVector& s) { |
| 74 | fScale = { s.x(), s.y() }; |
| 75 | this->onSync(); |
| 76 | } |
| 77 | |
| 78 | void TransformAdapter2D::setRotation(float r) { |
| 79 | fRotation = r; |
| 80 | this->onSync(); |
| 81 | } |
| 82 | |
| 83 | void TransformAdapter2D::setSkew(float sk) { |
| 84 | fSkew = sk; |
| 85 | this->onSync(); |
| 86 | } |
| 87 | |
| 88 | void TransformAdapter2D::setSkewAxis(float sa) { |
| 89 | fSkewAxis = sa; |
| 90 | this->onSync(); |
| 91 | } |
| 92 | |
| 93 | sk_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 | |
| 127 | TransformAdapter3D::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 | |
| 143 | TransformAdapter3D::~TransformAdapter3D() = default; |
| 144 | |
| 145 | void TransformAdapter3D::onSync() { |
| 146 | this->node()->setMatrix(this->totalMatrix()); |
| 147 | } |
| 148 | |
| 149 | SkV3 TransformAdapter3D::anchor_point() const { |
| 150 | return fAnchorPoint; |
| 151 | } |
| 152 | |
| 153 | SkV3 TransformAdapter3D::position() const { |
| 154 | return fPosition; |
| 155 | } |
| 156 | |
| 157 | SkV3 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 | |
| 162 | SkM44 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 | |
| 176 | sk_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 | |