From e3e8da5e5286c4bf6a3f2b2149b93d5287e0093b Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Tue, 19 Mar 2019 13:26:42 -0400 Subject: [PATCH] [skottie] Initial camera support Camera layers introduce a top-level 3d camera/view matrix based on their transform properties: * position - camera location * point of interest (stored as anchor point by BM) - camera direction * rotation - camera orientation The perspective degree is controlled by a "zoom" camera property (which corresponds to the view distance), and the composition dimensions. Current limitations: * single camera track/layer * affects all layers (not just 3d-tagged layers) * parent layer transforms are likely not applied correctly Bug: skia: Change-Id: Ifc1b8b699ff09fa13b4804d18546b444d02e81c2 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/201651 Reviewed-by: Mike Reed Commit-Queue: Florin Malita --- modules/skottie/src/Skottie.cpp | 16 +++-- modules/skottie/src/SkottieAdapter.cpp | 65 ++++++++++++++++- modules/skottie/src/SkottieAdapter.h | 35 ++++++++-- modules/skottie/src/SkottieLayer.cpp | 97 +++++++++++++++++++++----- modules/skottie/src/SkottiePriv.h | 10 ++- 5 files changed, 189 insertions(+), 34 deletions(-) diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp index ebdd7ed313..d615611c64 100644 --- a/modules/skottie/src/Skottie.cpp +++ b/modules/skottie/src/Skottie.cpp @@ -117,12 +117,15 @@ sk_sp AnimationBuilder::attachMatrix2D(const skjson::ObjectValu sk_sp AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t, AnimatorScope* ascope, - sk_sp parent) const { + sk_sp parent, + sk_sp adapter) const { static const VectorValue g_default_vec_0 = { 0, 0, 0}, g_default_vec_100 = {100, 100, 100}; - auto matrix = sksg::Matrix::Make(SkMatrix::I()); - auto adapter = sk_make_sp(matrix); + if (!adapter) { + // Default to TransformAdapter3D (we only use external adapters for cameras). + adapter = sk_make_sp(); + } auto bound = this->bindProperty(t["a"], ascope, [adapter](const VectorValue& a) { @@ -165,7 +168,7 @@ sk_sp AnimationBuilder::attachMatrix3D(const skjson::ObjectValu // TODO: dispatch 3D transform properties return (bound) - ? sksg::Transform::MakeConcat(std::move(parent), std::move(matrix)) + ? sksg::Transform::MakeConcat(std::move(parent), adapter->refTransform()) : parent; } @@ -266,13 +269,14 @@ AnimationBuilder::AnimationBuilder(sk_sp rp, sk_sp sk_sp pobserver, sk_sp logger, sk_sp mobserver, Animation::Builder::Stats* stats, - float duration, float framerate) + const SkSize& size, float duration, float framerate) : fResourceProvider(std::move(rp)) , fLazyFontMgr(std::move(fontmgr)) , fPropertyObserver(std::move(pobserver)) , fLogger(std::move(logger)) , fMarkerObserver(std::move(mobserver)) , fStats(stats) + , fSize(size) , fDuration(duration) , fFrameRate(framerate) , fHasNontrivialBlending(false) {} @@ -499,7 +503,7 @@ sk_sp Animation::Builder::make(const char* data, size_t data_len) { std::move(fPropertyObserver), std::move(fLogger), std::move(fMarkerObserver), - &fStats, duration, fps); + &fStats, size, duration, fps); auto scene = builder.parse(json); const auto t2 = std::chrono::steady_clock::now(); diff --git a/modules/skottie/src/SkottieAdapter.cpp b/modules/skottie/src/SkottieAdapter.cpp index 1abfebcfab..ea69f44f0c 100644 --- a/modules/skottie/src/SkottieAdapter.cpp +++ b/modules/skottie/src/SkottieAdapter.cpp @@ -7,6 +7,7 @@ #include "SkottieAdapter.h" +#include "Sk3D.h" #include "SkFont.h" #include "SkMatrix.h" #include "SkMatrix44.h" @@ -75,11 +76,15 @@ TransformAdapter3D::Vec3::Vec3(const VectorValue& v) { fZ = v.size() > 2 ? v[2] : 0; } -TransformAdapter3D::TransformAdapter3D(sk_sp> matrix) - : fMatrixNode(std::move(matrix)) {} +TransformAdapter3D::TransformAdapter3D() + : fMatrixNode(sksg::Matrix::Make(SkMatrix::I())) {} TransformAdapter3D::~TransformAdapter3D() = default; +sk_sp TransformAdapter3D::refTransform() const { + return fMatrixNode; +} + SkMatrix44 TransformAdapter3D::totalMatrix() const { SkMatrix44 t; @@ -104,6 +109,62 @@ void TransformAdapter3D::apply() { fMatrixNode->setMatrix(this->totalMatrix()); } +CameraAdapter:: CameraAdapter(const SkSize& viewport_size) + : fViewportSize(viewport_size) {} + +CameraAdapter::~CameraAdapter() = default; + +SkMatrix44 CameraAdapter::totalMatrix() const { + // Camera parameters: + // + // * location -> position attribute + // * point of interest -> anchor point attribute + // * orientation -> rotation attribute + // + // Note: the orientation is specified post position/POI adjustment. + // + SkPoint3 pos = { this->getPosition().fX, + this->getPosition().fY, + -this->getPosition().fZ }, + poi = { this->getAnchorPoint().fX, + this->getAnchorPoint().fY, + -this->getAnchorPoint().fZ }, + up = { 0, 1, 0 }; + + SkMatrix44 cam_t; + Sk3LookAt(&cam_t, pos, poi, up); + + { + SkMatrix44 rot; + rot.setRotateDegreesAbout(1, 0, 0, this->getRotation().fX); + cam_t.postConcat(rot); + rot.setRotateDegreesAbout(0, 1, 0, this->getRotation().fY); + cam_t.postConcat(rot); + rot.setRotateDegreesAbout(0, 0, 1, this->getRotation().fZ); + cam_t.postConcat(rot); + } + + // View parameters: + // + // * 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(), + view_angle = std::atan(view_size * 0.5f / view_distance); + + SkMatrix44 view_t; + Sk3Perspective(&view_t, 0, view_distance, 2 * view_angle); + view_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(view_t); + t.preConcat(cam_t); + + return t; +} + RepeaterAdapter::RepeaterAdapter(sk_sp repeater_node, Composite composite) : fRepeaterNode(repeater_node) , fComposite(composite) diff --git a/modules/skottie/src/SkottieAdapter.h b/modules/skottie/src/SkottieAdapter.h index 1138eb6e3e..6452d8d9f4 100644 --- a/modules/skottie/src/SkottieAdapter.h +++ b/modules/skottie/src/SkottieAdapter.h @@ -29,11 +29,16 @@ class RadialGradient; class RenderNode; class RRect; class TextBlob; +class Transform; class TransformEffect; class TrimEffect; }; +namespace skjson { + class ObjectValue; +} + namespace skottie { #define ADAPTER_PROPERTY(p_name, p_type, p_default) \ @@ -108,10 +113,10 @@ private: sk_sp> fMatrixNode; }; -class TransformAdapter3D final : public SkNVRefCnt { +class TransformAdapter3D : public SkRefCnt { public: - explicit TransformAdapter3D(sk_sp>); - ~TransformAdapter3D(); + TransformAdapter3D(); + ~TransformAdapter3D() override; struct Vec3 { float fX, fY, fZ; @@ -129,12 +134,32 @@ public: ADAPTER_PROPERTY(Rotation , Vec3, Vec3({ 0, 0, 0})) ADAPTER_PROPERTY(Scale , Vec3, Vec3({100, 100, 100})) - SkMatrix44 totalMatrix() const; + sk_sp refTransform() const; -private: +protected: void apply(); +private: + virtual SkMatrix44 totalMatrix() const; + sk_sp> fMatrixNode; + + using INHERITED = SkRefCnt; +}; + +class CameraAdapter final : public TransformAdapter3D { +public: + explicit CameraAdapter(const SkSize& viewport_size); + ~CameraAdapter() override; + + ADAPTER_PROPERTY(Zoom, SkScalar, 0) + +private: + SkMatrix44 totalMatrix() const override; + + const SkSize fViewportSize; + + using INHERITED = TransformAdapter3D; }; class RepeaterAdapter final : public SkNVRefCnt { diff --git a/modules/skottie/src/SkottieLayer.cpp b/modules/skottie/src/SkottieLayer.cpp index 5cf304b414..399f396f9f 100644 --- a/modules/skottie/src/SkottieLayer.cpp +++ b/modules/skottie/src/SkottieLayer.cpp @@ -12,6 +12,7 @@ #include "SkImage.h" #include "SkJSON.h" #include "SkMakeUnique.h" +#include "SkottieAdapter.h" #include "SkottieJson.h" #include "SkottieValue.h" #include "SkParse.h" @@ -171,6 +172,8 @@ sk_sp AttachMask(const skjson::ArrayValue* jmask, return sksg::MaskEffect::Make(std::move(childNode), std::move(maskNode)); } +static constexpr int kCameraLayerType = 13; + } // namespace sk_sp AnimationBuilder::attachNestedAnimation(const char* name, @@ -406,9 +409,13 @@ struct AnimationBuilder::AttachLayerContext { AnimatorScope* fScope; SkTHashMap> fLayerMatrixMap; sk_sp fCurrentMatte; + sk_sp fCameraTransform; + + enum class TransformType { kLayer, kCamera }; sk_sp attachLayerTransform(const skjson::ObjectValue& jlayer, - const AnimationBuilder* abuilder) { + const AnimationBuilder* abuilder, + TransformType type = TransformType::kLayer) { const auto layer_index = ParseDefault(jlayer["ind"], -1); if (layer_index < 0) return nullptr; @@ -416,7 +423,7 @@ struct AnimationBuilder::AttachLayerContext { if (auto* m = fLayerMatrixMap.find(layer_index)) return *m; - return this->attachLayerTransformImpl(jlayer, abuilder, layer_index); + return this->attachLayerTransformImpl(jlayer, abuilder, type, layer_index); } private: @@ -434,16 +441,47 @@ private: if (!l) continue; if (ParseDefault((*l)["ind"], -1) == parent_index) { - return this->attachLayerTransformImpl(*l, abuilder, parent_index); + const auto parent_type = ParseDefault((*l)["ty"], -1) == kCameraLayerType + ? TransformType::kCamera + : TransformType::kLayer; + return this->attachLayerTransformImpl(*l, abuilder, parent_type, parent_index); } } return nullptr; } + sk_sp attachTransformNode(const skjson::ObjectValue& jlayer, + const AnimationBuilder* abuilder, + sk_sp parent_transform, + TransformType type) const { + const skjson::ObjectValue* jtransform = jlayer["ks"]; + if (!jtransform) { + return nullptr; + } + + if (type == TransformType::kCamera) { + auto camera_adapter = sk_make_sp(abuilder->fSize); + + abuilder->bindProperty(jlayer["pe"], fScope, + [camera_adapter] (const ScalarValue& pe) { + // 'pe' (perspective?) corresponds to AE's "zoom" camera property. + camera_adapter->setZoom(pe); + }); + + return abuilder->attachMatrix3D(*jtransform, fScope, + std::move(parent_transform), + std::move(camera_adapter)); + } + + return (ParseDefault(jlayer["ddd"], 0) == 0) + ? abuilder->attachMatrix2D(*jtransform, fScope, std::move(parent_transform)) + : abuilder->attachMatrix3D(*jtransform, fScope, std::move(parent_transform)); + } + sk_sp attachLayerTransformImpl(const skjson::ObjectValue& jlayer, const AnimationBuilder* abuilder, - int layer_index) { + TransformType type, int layer_index) { SkASSERT(!fLayerMatrixMap.find(layer_index)); // Add a stub entry to break recursion cycles. @@ -451,14 +489,10 @@ private: auto parent_matrix = this->attachParentLayerTransform(jlayer, abuilder, layer_index); - if (const skjson::ObjectValue* jtransform = jlayer["ks"]) { - auto transform_node = (ParseDefault(jlayer["ddd"], 0) == 0) - ? abuilder->attachMatrix2D(*jtransform, fScope, std::move(parent_matrix)) - : abuilder->attachMatrix3D(*jtransform, fScope, std::move(parent_matrix)); - - return *fLayerMatrixMap.set(layer_index, std::move(transform_node)); - } - return nullptr; + return *fLayerMatrixMap.set(layer_index, this->attachTransformNode(jlayer, + abuilder, + std::move(parent_matrix), + type)); } }; @@ -490,7 +524,21 @@ sk_sp AnimationBuilder::attachLayer(const skjson::ObjectValue* &AnimationBuilder::attachTextLayer, // 'ty': 5 }; - int type = ParseDefault((*jlayer)["ty"], -1); + const auto type = ParseDefault((*jlayer)["ty"], -1); + + if (type == kCameraLayerType) { + // Camera layers are special: they don't build normal SG fragments, but drive a root-level + // transform. + if (layerCtx->fCameraTransform) { + this->log(Logger::Level::kWarning, jlayer, "Ignoring duplicate camera layer."); + } else { + layerCtx->fCameraTransform = + layerCtx->attachLayerTransform(*jlayer, this, + AttachLayerContext::TransformType::kCamera); + } + return nullptr; + } + if (type < 0 || type >= SkTo(SK_ARRAY_COUNT(gLayerAttachers))) { return nullptr; } @@ -593,9 +641,9 @@ sk_sp AnimationBuilder::attachLayer(const skjson::ObjectValue* return std::move(controller_node); } -sk_sp AnimationBuilder::attachComposition(const skjson::ObjectValue& comp, +sk_sp AnimationBuilder::attachComposition(const skjson::ObjectValue& jcomp, AnimatorScope* scope) const { - const skjson::ArrayValue* jlayers = comp["layers"]; + const skjson::ArrayValue* jlayers = jcomp["layers"]; if (!jlayers) return nullptr; std::vector> layers; @@ -612,11 +660,22 @@ sk_sp AnimationBuilder::attachComposition(const skjson::Object return nullptr; } - // Layers are painted in bottom->top order. - std::reverse(layers.begin(), layers.end()); - layers.shrink_to_fit(); + sk_sp comp; + if (layers.size() == 1) { + comp = std::move(layers[0]); + } else { + // Layers are painted in bottom->top order. + std::reverse(layers.begin(), layers.end()); + layers.shrink_to_fit(); + comp = sksg::Group::Make(std::move(layers)); + } - return sksg::Group::Make(std::move(layers)); + // Optional camera. + if (layerCtx.fCameraTransform) { + comp = sksg::TransformEffect::Make(std::move(comp), std::move(layerCtx.fCameraTransform)); + } + + return comp; } } // namespace internal diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h index 7736912d59..979f694d97 100644 --- a/modules/skottie/src/SkottiePriv.h +++ b/modules/skottie/src/SkottiePriv.h @@ -37,6 +37,9 @@ class Transform; namespace skottie { +class TransformAdapter2D; +class TransformAdapter3D; + namespace internal { using AnimatorScope = sksg::AnimatorList; @@ -45,7 +48,8 @@ class AnimationBuilder final : public SkNoncopyable { public: AnimationBuilder(sk_sp, sk_sp, sk_sp, sk_sp, sk_sp, - Animation::Builder::Stats*, float duration, float framerate); + Animation::Builder::Stats*, const SkSize& size, + float duration, float framerate); std::unique_ptr parse(const skjson::ObjectValue&); @@ -74,7 +78,8 @@ public: sk_sp attachMatrix2D(const skjson::ObjectValue&, AnimatorScope*, sk_sp) const; sk_sp attachMatrix3D(const skjson::ObjectValue&, AnimatorScope*, - sk_sp) const; + sk_sp, + sk_sp = nullptr) const; sk_sp attachOpacity(const skjson::ObjectValue&, AnimatorScope*, sk_sp) const; sk_sp attachPath(const skjson::Value&, AnimatorScope*) const; @@ -174,6 +179,7 @@ private: sk_sp fLogger; sk_sp fMarkerObserver; Animation::Builder::Stats* fStats; + const SkSize fSize; const float fDuration, fFrameRate; mutable const char* fPropertyObserverContext;