/* * 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::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 AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& jtransform, sk_sp parent, bool auto_orient) const { const auto* jrotation = &jtransform["r"]; if (jrotation->is()) { // 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::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(fOrientation) + SkV3{ fRx, fRy, fRz }; } SkM44 TransformAdapter3D::totalMatrix() const { const auto anchor_point = this->anchor_point(), position = this->position(), scale = static_cast(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 AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& jtransform, sk_sp 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