| /* |
| * Copyright 2020 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "modules/skottie/src/Transform.h" |
| |
| #include "modules/skottie/src/SkottieJson.h" |
| #include "modules/skottie/src/SkottiePriv.h" |
| #include "modules/sksg/include/SkSGTransform.h" |
| |
| namespace skottie { |
| namespace internal { |
| |
| TransformAdapter2D::TransformAdapter2D(const AnimationBuilder& abuilder, |
| const skjson::ObjectValue* janchor_point, |
| const skjson::ObjectValue* jposition, |
| const skjson::ObjectValue* jscale, |
| const skjson::ObjectValue* jrotation, |
| const skjson::ObjectValue* jskew, |
| const skjson::ObjectValue* jskew_axis, |
| bool auto_orient) |
| : INHERITED(sksg::Matrix<SkMatrix>::Make(SkMatrix::I())) { |
| |
| this->bind(abuilder, janchor_point, fAnchorPoint); |
| this->bind(abuilder, jscale , fScale); |
| this->bind(abuilder, jrotation , fRotation); |
| this->bind(abuilder, jskew , fSkew); |
| this->bind(abuilder, jskew_axis , fSkewAxis); |
| |
| this->bindAutoOrientable(abuilder, jposition, &fPosition, auto_orient ? &fOrientation |
| : nullptr); |
| } |
| |
| TransformAdapter2D::~TransformAdapter2D() {} |
| |
| void TransformAdapter2D::onSync() { |
| this->node()->setMatrix(this->totalMatrix()); |
| } |
| |
| SkMatrix TransformAdapter2D::totalMatrix() const { |
| auto skew_matrix = [](float sk, float sa) { |
| if (!sk) return SkMatrix::I(); |
| |
| // AE control limit. |
| static constexpr float kMaxSkewAngle = 85; |
| sk = -SkDegreesToRadians(SkTPin(sk, -kMaxSkewAngle, kMaxSkewAngle)); |
| sa = SkDegreesToRadians(sa); |
| |
| // Similar to CSS/SVG SkewX [1] with an explicit rotation. |
| // [1] https://www.w3.org/TR/css-transforms-1/#SkewXDefined |
| return SkMatrix::RotateRad(sa) |
| * SkMatrix::Skew(std::tan(sk), 0) |
| * SkMatrix::RotateRad(-sa); |
| }; |
| |
| return SkMatrix::Translate(fPosition.x, fPosition.y) |
| * SkMatrix::RotateDeg(fRotation + fOrientation) |
| * skew_matrix (fSkew, fSkewAxis) |
| * SkMatrix::Scale (fScale.x / 100, fScale.y / 100) // 100% based |
| * SkMatrix::Translate(-fAnchorPoint.x, -fAnchorPoint.y); |
| } |
| |
| SkPoint TransformAdapter2D::getAnchorPoint() const { |
| return { fAnchorPoint.x, fAnchorPoint.y }; |
| } |
| |
| void TransformAdapter2D::setAnchorPoint(const SkPoint& ap) { |
| fAnchorPoint = { ap.x(), ap.y() }; |
| this->onSync(); |
| } |
| |
| SkPoint TransformAdapter2D::getPosition() const { |
| return { fPosition.x, fPosition.y }; |
| } |
| |
| void TransformAdapter2D::setPosition(const SkPoint& p) { |
| fPosition = { p.x(), p.y() }; |
| this->onSync(); |
| } |
| |
| SkVector TransformAdapter2D::getScale() const { |
| return { fScale.x, fScale.y }; |
| } |
| |
| void TransformAdapter2D::setScale(const SkVector& s) { |
| fScale = { s.x(), s.y() }; |
| this->onSync(); |
| } |
| |
| void TransformAdapter2D::setRotation(float r) { |
| fRotation = r; |
| this->onSync(); |
| } |
| |
| void TransformAdapter2D::setSkew(float sk) { |
| fSkew = sk; |
| this->onSync(); |
| } |
| |
| void TransformAdapter2D::setSkewAxis(float sa) { |
| fSkewAxis = sa; |
| this->onSync(); |
| } |
| |
| sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& jtransform, |
| sk_sp<sksg::Transform> parent, |
| bool auto_orient) const { |
| const auto* jrotation = &jtransform["r"]; |
| if (jrotation->is<skjson::NullValue>()) { |
| // Some 2D rotations are disguised as 3D... |
| jrotation = &jtransform["rz"]; |
| } |
| |
| auto adapter = TransformAdapter2D::Make(*this, |
| jtransform["a"], |
| jtransform["p"], |
| jtransform["s"], |
| *jrotation, |
| jtransform["sk"], |
| jtransform["sa"], |
| auto_orient); |
| SkASSERT(adapter); |
| |
| const auto dispatched = this->dispatchTransformProperty(adapter); |
| |
| if (adapter->isStatic()) { |
| if (!dispatched && adapter->totalMatrix().isIdentity()) { |
| // The transform has no observable effects - we can discard. |
| return parent; |
| } |
| adapter->seek(0); |
| } else { |
| fCurrentAnimatorScope->push_back(adapter); |
| } |
| |
| return sksg::Transform::MakeConcat(std::move(parent), adapter->node()); |
| } |
| |
| TransformAdapter3D::TransformAdapter3D(const skjson::ObjectValue& jtransform, |
| const AnimationBuilder& abuilder) |
| : INHERITED(sksg::Matrix<SkM44>::Make(SkM44())) { |
| |
| this->bind(abuilder, jtransform["a"], fAnchorPoint); |
| this->bind(abuilder, jtransform["p"], fPosition); |
| this->bind(abuilder, jtransform["s"], fScale); |
| |
| // Axis-wise rotation and orientation are mapped to the same rotation property (3D rotation). |
| // The difference is in how they get interpolated (scalar/decomposed vs. vector). |
| this->bind(abuilder, jtransform["rx"], fRx); |
| this->bind(abuilder, jtransform["ry"], fRy); |
| this->bind(abuilder, jtransform["rz"], fRz); |
| this->bind(abuilder, jtransform["or"], fOrientation); |
| } |
| |
| TransformAdapter3D::~TransformAdapter3D() = default; |
| |
| void TransformAdapter3D::onSync() { |
| this->node()->setMatrix(this->totalMatrix()); |
| } |
| |
| SkV3 TransformAdapter3D::anchor_point() const { |
| return fAnchorPoint; |
| } |
| |
| SkV3 TransformAdapter3D::position() const { |
| return fPosition; |
| } |
| |
| SkV3 TransformAdapter3D::rotation() const { |
| // orientation and axis-wise rotation map onto the same property. |
| return static_cast<SkV3>(fOrientation) + SkV3{ fRx, fRy, fRz }; |
| } |
| |
| SkM44 TransformAdapter3D::totalMatrix() const { |
| const auto anchor_point = this->anchor_point(), |
| position = this->position(), |
| scale = static_cast<SkV3>(fScale), |
| rotation = this->rotation(); |
| |
| return SkM44::Translate(position.x, position.y, position.z) |
| * SkM44::Rotate({ 1, 0, 0 }, SkDegreesToRadians(rotation.x)) |
| * SkM44::Rotate({ 0, 1, 0 }, SkDegreesToRadians(rotation.y)) |
| * SkM44::Rotate({ 0, 0, 1 }, SkDegreesToRadians(rotation.z)) |
| * SkM44::Scale(scale.x / 100, scale.y / 100, scale.z / 100) |
| * SkM44::Translate(-anchor_point.x, -anchor_point.y, -anchor_point.z); |
| } |
| |
| sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& jtransform, |
| sk_sp<sksg::Transform> parent, |
| bool /*TODO: auto_orient*/) const { |
| auto adapter = TransformAdapter3D::Make(jtransform, *this); |
| SkASSERT(adapter); |
| |
| if (adapter->isStatic()) { |
| // TODO: SkM44::isIdentity? |
| if (adapter->totalMatrix() == SkM44()) { |
| // The transform has no observable effects - we can discard. |
| return parent; |
| } |
| adapter->seek(0); |
| } else { |
| fCurrentAnimatorScope->push_back(adapter); |
| } |
| |
| return sksg::Transform::MakeConcat(std::move(parent), adapter->node()); |
| } |
| |
| } // namespace internal |
| } // namespace skottie |