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