[skottie] Cleanup: convert transform adapters to new pattern

Use discardable adapters for 2D-transforms, 3D-transforms, cameras
and transform effects.

Improvements:
  - ~4KB smaller object size
  - 3D rotation and orientation now compose correctly
    (instead of overriding each other)
  - streamlined no-op check discards no-effect transform nodes more
    aggressively (20% more nodes discarded)

TBR=
Change-Id: If75e53021871a15b7ea9e3ccb988c734a018f06d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/266635
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2020-01-27 11:58:49 -05:00 committed by Skia Commit-Bot
parent 2fa68dbcc7
commit e7bd58f512
13 changed files with 446 additions and 331 deletions

View File

@ -18,7 +18,9 @@ class DiscardableAdapterBase : public AnimatablePropertyContainer {
public:
template <typename... Args>
static sk_sp<AdapterT> Make(Args&&... args) {
return sk_sp<AdapterT>(new AdapterT(std::forward<Args>(args)...));
sk_sp<AdapterT> adapter(new AdapterT(std::forward<Args>(args)...));
adapter->shrink_to_fit();
return adapter;
}
const sk_sp<T>& node() const { return fNode; }

View File

@ -25,6 +25,14 @@ void AnimatablePropertyContainer::onTick(float t) {
this->onSync();
}
void AnimatablePropertyContainer::addNestedContainer(sk_sp<AnimatablePropertyContainer> nested) {
fAnimators.push_back(nested);
}
void AnimatablePropertyContainer::shrink_to_fit() {
fAnimators.shrink_to_fit();
}
namespace {
class KeyframeAnimatorBase : public sksg::Animator {

View File

@ -36,9 +36,13 @@ public:
bool isStatic() const { return fAnimators.empty(); }
void addNestedContainer(sk_sp<AnimatablePropertyContainer>);
protected:
virtual void onSync() = 0;
void shrink_to_fit();
private:
void onTick(float) final;

View File

@ -8,71 +8,35 @@
#include "modules/skottie/src/Camera.h"
#include "include/utils/Sk3D.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottiePriv.h"
#include "modules/sksg/include/SkSGTransform.h"
namespace skottie {
namespace internal {
CameraAdapter:: CameraAdapter(const SkSize& viewport_size, Type type)
: fViewportSize(viewport_size)
, fType(type)
{}
namespace {
CameraAdapter::~CameraAdapter() = default;
sk_sp<CameraAdapter> CameraAdapter::MakeDefault(const SkSize &viewport_size) {
auto adapter = sk_make_sp<CameraAdapter>(viewport_size, Type::kOneNode);
static constexpr float kDefaultAEZoom = 879.13f;
const auto center = SkVector::Make(viewport_size.width() * 0.5f,
viewport_size.height() * 0.5f);
adapter->setZoom(kDefaultAEZoom);
adapter->setPosition (TransformAdapter3D::Vec3({center.fX, center.fY, -kDefaultAEZoom}));
return adapter;
}
SkPoint3 CameraAdapter::poi() const {
// AE supports two camera types:
//
// - one-node camera: does not auto-orient, and starts off perpendicular to the z = 0 plane,
// facing "forward" (decreasing z).
//
// - two-node camera: has a point of interest (encoded as the anchor point), and auto-orients
// to point in its direction.
return fType == Type::kOneNode
? SkPoint3{ this->getPosition().fX,
this->getPosition().fY,
-this->getPosition().fZ - 1 }
: SkPoint3{ this->getAnchorPoint().fX,
this->getAnchorPoint().fY,
-this->getAnchorPoint().fZ};
}
SkMatrix44 CameraAdapter::totalMatrix() const {
// Camera parameters:
//
// * location -> position attribute
// * point of interest -> anchor point attribute (two-node camera only)
// * orientation -> rotation attribute
//
const auto pos = SkPoint3{ this->getPosition().fX,
this->getPosition().fY,
-this->getPosition().fZ },
up = SkPoint3{ 0, 1, 0 };
SkMatrix44 ComputeCameraMatrix(const SkPoint3& position,
const SkPoint3& poi,
const SkPoint3& rotation,
const SkSize& viewport_size,
float zoom) {
const auto pos = SkPoint3{ position.fX, position.fY, -position.fZ },
up = SkPoint3{ 0, 1, 0 };
// Initial camera vector.
SkMatrix44 cam_t;
Sk3LookAt(&cam_t, pos, this->poi(), up);
Sk3LookAt(&cam_t, pos, poi, up);
// Rotation origin is camera position.
{
SkMatrix44 rot;
rot.setRotateDegreesAbout(1, 0, 0, this->getRotation().fX);
rot.setRotateDegreesAbout(1, 0, 0, rotation.fX);
cam_t.postConcat(rot);
rot.setRotateDegreesAbout(0, 1, 0, this->getRotation().fY);
rot.setRotateDegreesAbout(0, 1, 0, rotation.fY);
cam_t.postConcat(rot);
rot.setRotateDegreesAbout(0, 0, 1, -this->getRotation().fZ);
rot.setRotateDegreesAbout(0, 0, 1, -rotation.fZ);
cam_t.postConcat(rot);
}
@ -84,20 +48,101 @@ SkMatrix44 CameraAdapter::totalMatrix() const {
// * size -> composition size (TODO: AE seems to base it on width only?)
// * distance -> "zoom" camera attribute
//
const auto view_size = SkTMax(fViewportSize.width(), fViewportSize.height()),
view_distance = this->getZoom(),
const auto view_size = SkTMax(viewport_size.width(), viewport_size.height()),
view_distance = zoom,
view_angle = std::atan(sk_ieee_float_divide(view_size * 0.5f, view_distance));
SkMatrix44 persp_t;
Sk3Perspective(&persp_t, 0, view_distance, 2 * view_angle);
persp_t.postScale(view_size * 0.5f, view_size * 0.5f, 1);
SkMatrix44 t;
t.setTranslate(fViewportSize.width() * 0.5f, fViewportSize.height() * 0.5f, 0);
t.preConcat(persp_t);
t.preConcat(cam_t);
SkMatrix44 m;
m.setTranslate(viewport_size.width() * 0.5f, viewport_size.height() * 0.5f, 0);
m.preConcat(persp_t);
m.preConcat(cam_t);
return t;
return m;
}
} // namespace
CameraAdaper::CameraAdaper(const skjson::ObjectValue& jlayer,
const skjson::ObjectValue& jtransform,
const AnimationBuilder& abuilder,
const SkSize& viewport_size)
: INHERITED(jtransform, abuilder)
, fViewportSize(viewport_size)
// The presence of an anchor point property ('a') differentiates
// one-node vs. two-node cameras.
, fType(jtransform["a"].is<skjson::NullValue>() ? CameraType::kOneNode
: CameraType::kTwoNode) {
// 'pe' (perspective?) corresponds to AE's "zoom" camera property.
this->bind(abuilder, jlayer["pe"], fZoom);
}
CameraAdaper::~CameraAdaper() = default;
SkMatrix44 CameraAdaper::totalMatrix() const {
// Camera parameters:
//
// * location -> position attribute
// * point of interest -> anchor point attribute (two-node camera only)
// * orientation -> rotation attribute
//
const auto position = this->position();
return ComputeCameraMatrix(position,
this->poi(position),
this->rotation(),
fViewportSize,
fZoom);
}
SkPoint3 CameraAdaper::poi(const SkPoint3& pos) const {
// AE supports two camera types:
//
// - one-node camera: does not auto-orient, and starts off perpendicular
// to the z = 0 plane, facing "forward" (decreasing z).
//
// - two-node camera: has a point of interest (encoded as the anchor point),
// and auto-orients to point in its direction.
if (fType == CameraType::kOneNode) {
return { pos.fX, pos.fY, -pos.fZ - 1};
}
const auto ap = this->anchor_point();
return { ap.fX, ap.fY, -ap.fZ };
}
sk_sp<sksg::Transform> CameraAdaper::DefaultCameraTransform(const SkSize& viewport_size) {
const auto center = SkVector::Make(viewport_size.width() * 0.5f,
viewport_size.height() * 0.5f);
static constexpr float kDefaultAEZoom = 879.13f;
const SkPoint3 pos = { center.fX, center.fY, -kDefaultAEZoom },
poi = { pos.fX, pos.fY, -pos.fZ - 1 },
rot = { 0, 0, 0 };
return sksg::Matrix<SkMatrix44>::Make(
ComputeCameraMatrix(pos, poi, rot, viewport_size, kDefaultAEZoom));
}
sk_sp<sksg::Transform> AnimationBuilder::attachCamera(const skjson::ObjectValue& jlayer,
const skjson::ObjectValue& jtransform,
sk_sp<sksg::Transform> parent,
const SkSize& viewport_size) const {
auto adapter = sk_make_sp<CameraAdaper>(jlayer, jtransform, *this, viewport_size);
if (adapter->isStatic()) {
adapter->tick(0);
} else {
fCurrentAnimatorScope->push_back(adapter);
}
return sksg::Transform::MakeConcat(adapter->node(), std::move(parent));
}
} // namespace internal

View File

@ -10,32 +10,34 @@
#include "modules/skottie/src/Transform.h"
#include "include/core/SkMatrix44.h"
namespace skottie {
namespace internal {
class CameraAdapter final : public TransformAdapter3D {
class CameraAdaper final : public TransformAdapter3D {
public:
enum class Type {
CameraAdaper(const skjson::ObjectValue& jlayer,
const skjson::ObjectValue& jtransform,
const AnimationBuilder& abuilder,
const SkSize& viewport_size);
~CameraAdaper() override;
// Used in the absence of an explicit camera layer.
static sk_sp<sksg::Transform> DefaultCameraTransform(const SkSize& viewport_size);
SkMatrix44 totalMatrix() const override;
private:
enum class CameraType {
kOneNode, // implicitly facing forward (decreasing z), does not auto-orient
kTwoNode, // explicitly facing a POI (the anchor point), auto-orients
};
static sk_sp<CameraAdapter> MakeDefault(const SkSize& viewport_size);
SkPoint3 poi(const SkPoint3& pos) const;
CameraAdapter(const SkSize& viewport_size, Type);
~CameraAdapter() override;
const SkSize fViewportSize;
const CameraType fType;
ADAPTER_PROPERTY(Zoom, SkScalar, 0)
private:
SkMatrix44 totalMatrix() const override;
SkPoint3 poi() const;
const SkSize fViewportSize;
const Type fType;
ScalarValue fZoom = 0;
using INHERITED = TransformAdapter3D;
};

View File

@ -166,7 +166,7 @@ CompositionBuilder::CompositionBuilder(const AnimationBuilder& abuilder,
fCameraTransform = fLayerBuilders[camera_builder_index].buildTransform(abuilder, this);
} else if (ParseDefault<int>(jcomp["ddd"], 0)) {
// Default/implicit camera when 3D layers are present.
fCameraTransform = CameraAdapter::MakeDefault(fSize)->refTransform();
fCameraTransform = CameraAdaper::DefaultCameraTransform(fSize);
}
}

View File

@ -325,30 +325,15 @@ sk_sp<sksg::Transform> LayerBuilder::doAttachTransform(const AnimationBuilder& a
auto parent_transform = this->getParentTransform(abuilder, cbuilder, ttype);
if (this->isCamera()) {
// The presence of an anchor point property ('a') differentiates
// one-node vs. two-node cameras.
const auto camera_type = (*jtransform)["a"].is<skjson::NullValue>()
? CameraAdapter::Type::kOneNode
: CameraAdapter::Type::kTwoNode;
auto camera_adapter = sk_make_sp<CameraAdapter>(cbuilder->fSize, camera_type);
abuilder.bindProperty<ScalarValue>(fJlayer["pe"],
[camera_adapter] (const ScalarValue& pe) {
// 'pe' (perspective?) corresponds to AE's "zoom" camera property.
camera_adapter->setZoom(pe);
});
// parent_transform applies to the camera itself => it pre-composes inverted to the
// camera/view/adapter transform.
//
// T_camera' = T_camera x Inv(parent_transform)
//
parent_transform = sksg::Transform::MakeInverse(std::move(parent_transform));
return abuilder.attachMatrix3D(*jtransform,
std::move(parent_transform),
std::move(camera_adapter),
true); // pre-compose parent
return abuilder.attachCamera(fJlayer,
*jtransform,
sksg::Transform::MakeInverse(std::move(parent_transform)),
cbuilder->fSize);
}
return this->is3D()

View File

@ -68,114 +68,6 @@ void AnimationBuilder::log(Logger::Level lvl, const skjson::Value* json,
fLogger->log(lvl, buff, jsonstr.c_str());
}
sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& t,
sk_sp<sksg::Transform> parent) const {
static const VectorValue g_default_vec_0 = { 0, 0},
g_default_vec_100 = {100, 100};
auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
auto adapter = sk_make_sp<TransformAdapter2D>(matrix);
auto bound = this->bindProperty<VectorValue>(t["a"],
[adapter](const VectorValue& a) {
adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(a));
}, g_default_vec_0);
bound |= this->bindProperty<VectorValue>(t["p"],
[adapter](const VectorValue& p) {
adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
}, g_default_vec_0);
bound |= this->bindProperty<VectorValue>(t["s"],
[adapter](const VectorValue& s) {
adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
}, g_default_vec_100);
const auto* jrotation = &t["r"];
if (jrotation->is<skjson::NullValue>()) {
// 3d rotations have separate rx,ry,rz components. While we don't fully support them,
// we can still make use of rz.
jrotation = &t["rz"];
}
bound |= this->bindProperty<ScalarValue>(*jrotation,
[adapter](const ScalarValue& r) {
adapter->setRotation(r);
}, 0.0f);
bound |= this->bindProperty<ScalarValue>(t["sk"],
[adapter](const ScalarValue& sk) {
adapter->setSkew(sk);
}, 0.0f);
bound |= this->bindProperty<ScalarValue>(t["sa"],
[adapter](const ScalarValue& sa) {
adapter->setSkewAxis(sa);
}, 0.0f);
const auto dispatched = this->dispatchTransformProperty(adapter);
return (bound || dispatched)
? sksg::Transform::MakeConcat(std::move(parent), std::move(matrix))
: parent;
}
sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t,
sk_sp<sksg::Transform> parent,
sk_sp<TransformAdapter3D> adapter,
bool precompose_parent) const {
static const VectorValue g_default_vec_0 = { 0, 0, 0},
g_default_vec_100 = {100, 100, 100};
if (!adapter) {
// Default to TransformAdapter3D (we only use external adapters for cameras).
adapter = sk_make_sp<TransformAdapter3D>();
}
auto bound = this->bindProperty<VectorValue>(t["a"],
[adapter](const VectorValue& a) {
adapter->setAnchorPoint(TransformAdapter3D::Vec3(a));
}, g_default_vec_0);
bound |= this->bindProperty<VectorValue>(t["p"],
[adapter](const VectorValue& p) {
adapter->setPosition(TransformAdapter3D::Vec3(p));
}, g_default_vec_0);
bound |= this->bindProperty<VectorValue>(t["s"],
[adapter](const VectorValue& s) {
adapter->setScale(TransformAdapter3D::Vec3(s));
}, g_default_vec_100);
// Orientation and rx/ry/rz are mapped to the same rotation property -- the difference is
// in how they get interpolated (vector vs. scalar/decomposed interpolation).
bound |= this->bindProperty<VectorValue>(t["or"],
[adapter](const VectorValue& o) {
adapter->setRotation(TransformAdapter3D::Vec3(o));
}, g_default_vec_0);
bound |= this->bindProperty<ScalarValue>(t["rx"],
[adapter](const ScalarValue& rx) {
const auto& r = adapter->getRotation();
adapter->setRotation(TransformAdapter3D::Vec3({rx, r.fY, r.fZ}));
}, 0.0f);
bound |= this->bindProperty<ScalarValue>(t["ry"],
[adapter](const ScalarValue& ry) {
const auto& r = adapter->getRotation();
adapter->setRotation(TransformAdapter3D::Vec3({r.fX, ry, r.fZ}));
}, 0.0f);
bound |= this->bindProperty<ScalarValue>(t["rz"],
[adapter](const ScalarValue& rz) {
const auto& r = adapter->getRotation();
adapter->setRotation(TransformAdapter3D::Vec3({r.fX, r.fY, rz}));
}, 0.0f);
// TODO: dispatch 3D transform properties
if (!bound) {
return parent;
}
return precompose_parent
? sksg::Transform::MakeConcat(adapter->refTransform(), std::move(parent))
: sksg::Transform::MakeConcat(std::move(parent), adapter->refTransform());
}
sk_sp<sksg::RenderNode> AnimationBuilder::attachOpacity(const skjson::ObjectValue& jtransform,
sk_sp<sksg::RenderNode> childNode) const {
if (!childNode)

View File

@ -80,9 +80,13 @@ public:
sk_sp<sksg::Color> attachColor(const skjson::ObjectValue&, const char prop_name[]) const;
sk_sp<sksg::Transform> attachMatrix2D(const skjson::ObjectValue&, sk_sp<sksg::Transform>) const;
sk_sp<sksg::Transform> attachMatrix3D(const skjson::ObjectValue&, sk_sp<sksg::Transform>,
sk_sp<TransformAdapter3D> = nullptr,
bool precompose_parent = false) const;
sk_sp<sksg::Transform> attachMatrix3D(const skjson::ObjectValue&, sk_sp<sksg::Transform>) const;
sk_sp<sksg::Transform> attachCamera(const skjson::ObjectValue& jlayer,
const skjson::ObjectValue& jtransform,
sk_sp<sksg::Transform>,
const SkSize&) const;
sk_sp<sksg::RenderNode> attachOpacity(const skjson::ObjectValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::Path> attachPath(const skjson::Value&) const;

View File

@ -9,6 +9,7 @@
#include "include/core/SkColor.h"
#include "include/core/SkPoint.h"
#include "include/core/SkPoint3.h"
#include "include/core/SkSize.h"
#include "include/private/SkNx.h"
#include "modules/skottie/src/SkottieJson.h"
@ -93,10 +94,22 @@ SkColor4f ValueTraits<VectorValue>::As<SkColor4f>(const VectorValue& v) {
template <>
template <>
SkPoint ValueTraits<VectorValue>::As<SkPoint>(const VectorValue& vec) {
// best effort to turn this into a point
const auto x = vec.size() > 0 ? vec[0] : 0,
y = vec.size() > 1 ? vec[1] : 0;
return SkPoint::Make(x, y);
// best effort to turn this into a 2D point
return SkPoint {
vec.size() > 0 ? vec[0] : 0,
vec.size() > 1 ? vec[1] : 0,
};
}
template <>
template <>
SkPoint3 ValueTraits<VectorValue>::As<SkPoint3>(const VectorValue& vec) {
// best effort to turn this into a 3D point
return SkPoint3 {
vec.size() > 0 ? vec[0] : 0,
vec.size() > 1 ? vec[1] : 0,
vec.size() > 2 ? vec[2] : 0,
};
}
template <>

View File

@ -7,67 +7,199 @@
#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(sk_sp<sksg::Matrix<SkMatrix>> matrix)
: fMatrixNode(std::move(matrix)) {}
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)
: INHERITED(sksg::Matrix<SkMatrix>::Make(SkMatrix::I())) {
TransformAdapter2D::~TransformAdapter2D() = default;
this->bind(abuilder, janchor_point, fAnchorPoint);
this->bind(abuilder, jposition , fPosition);
this->bind(abuilder, jscale , fScale);
this->bind(abuilder, jrotation , fRotation);
this->bind(abuilder, jskew , fSkew);
this->bind(abuilder, jskew_axis , fSkewAxis);
}
TransformAdapter2D::~TransformAdapter2D() {}
void TransformAdapter2D::onSync() {
this->node()->setMatrix(this->totalMatrix());
}
SkMatrix TransformAdapter2D::totalMatrix() const {
SkMatrix t = SkMatrix::MakeTrans(-fAnchorPoint.x(), -fAnchorPoint.y());
const auto anchor_point = ValueTraits<VectorValue>::As<SkPoint>(fAnchorPoint),
position = ValueTraits<VectorValue>::As<SkPoint>(fPosition),
scale = ValueTraits<VectorValue>::As<SkPoint>(fScale);
t.postScale(fScale.x() / 100, fScale.y() / 100); // 100% based
SkMatrix t = SkMatrix::MakeTrans(-anchor_point.x(), -anchor_point.y());
t.postScale(scale.x() / 100, scale.y() / 100); // 100% based
t.postRotate(fRotation);
t.postTranslate(fPosition.x(), fPosition.y());
t.postTranslate(position.x(), position.y());
// TODO: skew
return t;
}
void TransformAdapter2D::apply() {
fMatrixNode->setMatrix(this->totalMatrix());
SkPoint TransformAdapter2D::getAnchorPoint() const {
return ValueTraits<VectorValue>::As<SkPoint>(fAnchorPoint);
}
TransformAdapter3D::Vec3::Vec3(const VectorValue& v) {
fX = v.size() > 0 ? v[0] : 0;
fY = v.size() > 1 ? v[1] : 0;
fZ = v.size() > 2 ? v[2] : 0;
void TransformAdapter2D::setAnchorPoint(const SkPoint& ap) {
fAnchorPoint = { ap.x(), ap.y() };
this->onSync();
}
TransformAdapter3D::TransformAdapter3D()
: fMatrixNode(sksg::Matrix<SkMatrix44>::Make(SkMatrix::I())) {}
SkPoint TransformAdapter2D::getPosition() const {
return ValueTraits<VectorValue>::As<SkPoint>(fPosition);
}
void TransformAdapter2D::setPosition(const SkPoint& p) {
fPosition = { p.x(), p.y() };
this->onSync();
}
SkVector TransformAdapter2D::getScale() const {
return ValueTraits<VectorValue>::As<SkVector>(fScale);
}
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) 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"]);
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->tick(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<SkMatrix44>::Make(SkMatrix44::I())) {
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;
sk_sp<sksg::Transform> TransformAdapter3D::refTransform() const {
return fMatrixNode;
void TransformAdapter3D::onSync() {
this->node()->setMatrix(this->totalMatrix());
}
SkPoint3 TransformAdapter3D::anchor_point() const {
return ValueTraits<VectorValue>::As<SkPoint3>(fAnchorPoint);
}
SkPoint3 TransformAdapter3D::position() const {
return ValueTraits<VectorValue>::As<SkPoint3>(fPosition);
}
SkVector3 TransformAdapter3D::rotation() const {
// orientation and axis-wise rotation map onto the same property.
return ValueTraits<VectorValue>::As<SkPoint3>(fOrientation) + SkPoint3{ fRx, fRy, fRz };
}
SkMatrix44 TransformAdapter3D::totalMatrix() const {
SkMatrix44 t;
const auto anchor_point = this->anchor_point(),
positon = this->position(),
scale = ValueTraits<VectorValue>::As<SkPoint3>(fScale),
rotation = this->rotation();
t.setTranslate(-fAnchorPoint.fX, -fAnchorPoint.fY, -fAnchorPoint.fZ);
t.postScale(fScale.fX / 100, fScale.fY / 100, fScale.fZ / 100);
SkMatrix44 m;
m.setTranslate(-anchor_point.fX, -anchor_point.fY, -anchor_point.fZ);
m.postScale(scale.fX / 100, scale.fY / 100, scale.fZ / 100);
SkMatrix44 r;
r.setRotateDegreesAbout(0, 0, 1, fRotation.fZ);
t.postConcat(r);
r.setRotateDegreesAbout(0, 1, 0, fRotation.fY);
t.postConcat(r);
r.setRotateDegreesAbout(1, 0, 0, fRotation.fX);
t.postConcat(r);
r.setRotateDegreesAbout(0, 0, 1, rotation.fZ);
m.postConcat(r);
r.setRotateDegreesAbout(0, 1, 0, rotation.fY);
m.postConcat(r);
r.setRotateDegreesAbout(1, 0, 0, rotation.fX);
m.postConcat(r);
t.postTranslate(fPosition.fX, fPosition.fY, fPosition.fZ);
m.postTranslate(positon.fX, positon.fY, positon.fZ);
return t;
return m;
}
void TransformAdapter3D::apply() {
fMatrixNode->setMatrix(this->totalMatrix());
sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& jtransform,
sk_sp<sksg::Transform> parent) const {
auto adapter = TransformAdapter3D::Make(jtransform, *this);
SkASSERT(adapter);
if (adapter->isStatic()) {
if (adapter->totalMatrix().isIdentity()) {
// The transform has no observable effects - we can discard.
return parent;
}
adapter->tick(0);
} else {
fCurrentAnimatorScope->push_back(adapter);
}
return sksg::Transform::MakeConcat(std::move(parent), adapter->node());
}
} // namespace internal

View File

@ -12,62 +12,91 @@
#include "include/core/SkMatrix.h"
#include "include/core/SkMatrix44.h"
#include "include/core/SkPoint.h"
#include "include/core/SkPoint3.h"
#include "modules/skottie/src/Adapter.h"
namespace skjson {
class ObjectValue;
} // namespace skjson
namespace skottie {
namespace internal {
class TransformAdapter2D final : public SkNVRefCnt<TransformAdapter2D> {
class TransformAdapter2D final : public DiscardableAdapterBase<TransformAdapter2D,
sksg::Matrix<SkMatrix>> {
public:
explicit TransformAdapter2D(sk_sp<sksg::Matrix<SkMatrix>>);
~TransformAdapter2D();
TransformAdapter2D(const AnimationBuilder&,
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);
~TransformAdapter2D() override;
ADAPTER_PROPERTY(AnchorPoint, SkPoint , SkPoint::Make(0, 0))
ADAPTER_PROPERTY(Position , SkPoint , SkPoint::Make(0, 0))
ADAPTER_PROPERTY(Scale , SkVector, SkPoint::Make(100, 100))
ADAPTER_PROPERTY(Rotation , SkScalar, 0)
ADAPTER_PROPERTY(Skew , SkScalar, 0)
ADAPTER_PROPERTY(SkewAxis , SkScalar, 0)
// Accessors needed for public property APIs.
// TODO: introduce a separate public type.
SkPoint getAnchorPoint() const;
void setAnchorPoint(const SkPoint&);
SkPoint getPosition() const;
void setPosition(const SkPoint&);
SkVector getScale() const;
void setScale(const SkVector&);
float getRotation() const { return fRotation; }
void setRotation(float r);
float getSkew() const { return fSkew; }
void setSkew(float sk);
float getSkewAxis() const { return fSkewAxis; }
void setSkewAxis(float sa );
SkMatrix totalMatrix() const;
private:
void apply();
void onSync() override;
sk_sp<sksg::Matrix<SkMatrix>> fMatrixNode;
VectorValue fAnchorPoint,
fPosition,
fScale = { 100, 100 };
ScalarValue fRotation = 0,
fSkew = 0,
fSkewAxis = 0;
using INHERITED = DiscardableAdapterBase<TransformAdapter2D, sksg::Matrix<SkMatrix>>;
};
class TransformAdapter3D : public SkRefCnt {
class TransformAdapter3D : public DiscardableAdapterBase<TransformAdapter3D,
sksg::Matrix<SkMatrix44>> {
public:
TransformAdapter3D();
TransformAdapter3D(const skjson::ObjectValue&, const AnimationBuilder&);
~TransformAdapter3D() override;
struct Vec3 {
float fX, fY, fZ;
explicit Vec3(const VectorValue&);
bool operator==(const Vec3& other) const {
return fX == other.fX && fY == other.fY && fZ == other.fZ;
}
bool operator!=(const Vec3& other) const { return !(*this == other); }
};
ADAPTER_PROPERTY(AnchorPoint, Vec3, Vec3({ 0, 0, 0}))
ADAPTER_PROPERTY(Position , Vec3, Vec3({ 0, 0, 0}))
ADAPTER_PROPERTY(Rotation , Vec3, Vec3({ 0, 0, 0}))
ADAPTER_PROPERTY(Scale , Vec3, Vec3({100, 100, 100}))
sk_sp<sksg::Transform> refTransform() const;
protected:
void apply();
private:
virtual SkMatrix44 totalMatrix() const;
sk_sp<sksg::Matrix<SkMatrix44>> fMatrixNode;
protected:
SkPoint3 anchor_point() const;
SkPoint3 position() const;
SkVector3 rotation() const;
using INHERITED = SkRefCnt;
private:
void onSync() final;
VectorValue fAnchorPoint,
fPosition,
fOrientation,
fScale = { 100, 100, 100 };
ScalarValue fRx = 0,
fRy = 0,
fRz = 0;
using INHERITED = DiscardableAdapterBase<TransformAdapter3D, sksg::Matrix<SkMatrix44>>;
};
} // namespace internal

View File

@ -17,26 +17,50 @@ namespace internal {
namespace {
// Transform effects can operate in either uniform or anisotropic mode, with each
// component (including mode) animated separately.
class ScaleAdapter final : public SkNVRefCnt<ScaleAdapter> {
class TransformEffectAdapter final : public DiscardableAdapterBase<TransformEffectAdapter,
sksg::OpacityEffect> {
public:
explicit ScaleAdapter(sk_sp<TransformAdapter2D> tadapter)
: fTransformAdapter(std::move(tadapter)) {}
TransformEffectAdapter(const AnimationBuilder& abuilder,
const skjson::ObjectValue* jopacity,
const skjson::ObjectValue* jscale_uniform,
const skjson::ObjectValue* jscale_width,
const skjson::ObjectValue* jscale_height,
sk_sp<TransformAdapter2D> tadapter,
sk_sp<sksg::RenderNode> child)
: INHERITED(sksg::OpacityEffect::Make(std::move(child)))
, fTransformAdapter(std::move(tadapter)) {
this->bind(abuilder, jopacity , fOpacity );
this->bind(abuilder, jscale_uniform, fUniformScale);
this->bind(abuilder, jscale_width , fScaleWidth );
this->bind(abuilder, jscale_height , fScaleHeight );
ADAPTER_PROPERTY(IsUniform , bool , false)
ADAPTER_PROPERTY(ScaleWidth , SkScalar, 100)
ADAPTER_PROPERTY(ScaleHeight, SkScalar, 100)
if (!fTransformAdapter->isStatic()) {
// Connected animated transform addapters to the animator tree.
this->addNestedContainer(fTransformAdapter);
}
}
private:
void apply() {
void onSync() override {
this->node()->setOpacity(fOpacity * 0.01f);
// In uniform mode, the scale is based solely in ScaleHeight.
const auto scale = SkVector::Make(fIsUniform ? fScaleHeight : fScaleWidth,
const auto scale = SkVector::Make(SkScalarRoundToInt(fUniformScale) ? fScaleHeight
: fScaleWidth,
fScaleHeight);
// NB: this triggers an transform adapter -> SG sync.
fTransformAdapter->setScale(scale);
}
const sk_sp<TransformAdapter2D> fTransformAdapter;
ScalarValue fOpacity = 100,
fUniformScale = 0, // bool
fScaleWidth = 100,
fScaleHeight = 100;
using INHERITED = DiscardableAdapterBase<TransformEffectAdapter, sksg::OpacityEffect>;
};
} // anonymous ns
@ -58,53 +82,28 @@ sk_sp<sksg::RenderNode> EffectBuilder::attachTransformEffect(const skjson::Array
// kSampling_Index = 11,
};
auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
auto t_adapter = sk_make_sp<TransformAdapter2D>(matrix);
auto s_adapter = sk_make_sp<ScaleAdapter>(t_adapter);
auto transform_adapter = TransformAdapter2D::Make(*fBuilder,
GetPropValue(jprops, kAnchorPoint_Index),
GetPropValue(jprops, kPosition_Index),
nullptr, // scale is handled externally
GetPropValue(jprops, kRotation_Index),
GetPropValue(jprops, kSkew_Index),
GetPropValue(jprops, kSkewAxis_Index));
if (!transform_adapter) {
return nullptr;
}
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kAnchorPoint_Index),
[t_adapter](const VectorValue& ap) {
t_adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(ap));
});
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kPosition_Index),
[t_adapter](const VectorValue& p) {
t_adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRotation_Index),
[t_adapter](const ScalarValue& r) {
t_adapter->setRotation(r);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSkew_Index),
[t_adapter](const ScalarValue& s) {
t_adapter->setSkew(s);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSkewAxis_Index),
[t_adapter](const ScalarValue& sa) {
t_adapter->setSkewAxis(sa);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kUniformScale_Index),
[s_adapter](const ScalarValue& u) {
s_adapter->setIsUniform(SkScalarRoundToInt(u));
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kScaleHeight_Index),
[s_adapter](const ScalarValue& sh) {
s_adapter->setScaleHeight(sh);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kScaleWidth_Index),
[s_adapter](const ScalarValue& sw) {
s_adapter->setScaleWidth(sw);
});
auto opacity_node = sksg::OpacityEffect::Make(sksg::TransformEffect::Make(std::move(layer),
std::move(matrix)));
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOpacity_Index),
[opacity_node](const ScalarValue& o) {
opacity_node->setOpacity(o * 0.01f);
});
return opacity_node;
auto transform_effect_node = sksg::TransformEffect::Make(std::move(layer),
transform_adapter->node());
return fBuilder->attachDiscardableAdapter<TransformEffectAdapter>
(*fBuilder,
GetPropValue(jprops, kOpacity_Index),
GetPropValue(jprops, kUniformScale_Index),
GetPropValue(jprops, kScaleWidth_Index),
GetPropValue(jprops, kScaleHeight_Index),
std::move(transform_adapter),
std::move(transform_effect_node)
);
}
} // namespace internal