| /* | |
| * Copyright 2020 Google LLC | |
| * | |
| * 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 "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkTPin.h" | |
| #include "modules/jsonreader/SkJSONReader.h" | |
| #include "modules/skottie/src/SkottiePriv.h" | |
| #include "modules/sksg/include/SkSGTransform.h" | |
| #include <cmath> | |
| 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 |