[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 <reed@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
parent
4da3f3d5bd
commit
e3e8da5e52
@ -117,12 +117,15 @@ sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValu
|
||||
|
||||
sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t,
|
||||
AnimatorScope* ascope,
|
||||
sk_sp<sksg::Transform> parent) const {
|
||||
sk_sp<sksg::Transform> parent,
|
||||
sk_sp<TransformAdapter3D> adapter) const {
|
||||
static const VectorValue g_default_vec_0 = { 0, 0, 0},
|
||||
g_default_vec_100 = {100, 100, 100};
|
||||
|
||||
auto matrix = sksg::Matrix<SkMatrix44>::Make(SkMatrix::I());
|
||||
auto adapter = sk_make_sp<TransformAdapter3D>(matrix);
|
||||
if (!adapter) {
|
||||
// Default to TransformAdapter3D (we only use external adapters for cameras).
|
||||
adapter = sk_make_sp<TransformAdapter3D>();
|
||||
}
|
||||
|
||||
auto bound = this->bindProperty<VectorValue>(t["a"], ascope,
|
||||
[adapter](const VectorValue& a) {
|
||||
@ -165,7 +168,7 @@ sk_sp<sksg::Transform> 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<ResourceProvider> rp, sk_sp<SkFontMgr>
|
||||
sk_sp<PropertyObserver> pobserver, sk_sp<Logger> logger,
|
||||
sk_sp<MarkerObserver> 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> 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();
|
||||
|
@ -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<sksg::Matrix<SkMatrix44>> matrix)
|
||||
: fMatrixNode(std::move(matrix)) {}
|
||||
TransformAdapter3D::TransformAdapter3D()
|
||||
: fMatrixNode(sksg::Matrix<SkMatrix44>::Make(SkMatrix::I())) {}
|
||||
|
||||
TransformAdapter3D::~TransformAdapter3D() = default;
|
||||
|
||||
sk_sp<sksg::Transform> 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<sksg::RenderNode> repeater_node, Composite composite)
|
||||
: fRepeaterNode(repeater_node)
|
||||
, fComposite(composite)
|
||||
|
@ -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<sksg::Matrix<SkMatrix>> fMatrixNode;
|
||||
};
|
||||
|
||||
class TransformAdapter3D final : public SkNVRefCnt<TransformAdapter3D> {
|
||||
class TransformAdapter3D : public SkRefCnt {
|
||||
public:
|
||||
explicit TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>>);
|
||||
~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<sksg::Transform> refTransform() const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
void apply();
|
||||
|
||||
private:
|
||||
virtual SkMatrix44 totalMatrix() const;
|
||||
|
||||
sk_sp<sksg::Matrix<SkMatrix44>> 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<RepeaterAdapter> {
|
||||
|
@ -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<sksg::RenderNode> AttachMask(const skjson::ArrayValue* jmask,
|
||||
return sksg::MaskEffect::Make(std::move(childNode), std::move(maskNode));
|
||||
}
|
||||
|
||||
static constexpr int kCameraLayerType = 13;
|
||||
|
||||
} // namespace
|
||||
|
||||
sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name,
|
||||
@ -406,9 +409,13 @@ struct AnimationBuilder::AttachLayerContext {
|
||||
AnimatorScope* fScope;
|
||||
SkTHashMap<int, sk_sp<sksg::Transform>> fLayerMatrixMap;
|
||||
sk_sp<sksg::RenderNode> fCurrentMatte;
|
||||
sk_sp<sksg::Transform> fCameraTransform;
|
||||
|
||||
enum class TransformType { kLayer, kCamera };
|
||||
|
||||
sk_sp<sksg::Transform> attachLayerTransform(const skjson::ObjectValue& jlayer,
|
||||
const AnimationBuilder* abuilder) {
|
||||
const AnimationBuilder* abuilder,
|
||||
TransformType type = TransformType::kLayer) {
|
||||
const auto layer_index = ParseDefault<int>(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<int>((*l)["ind"], -1) == parent_index) {
|
||||
return this->attachLayerTransformImpl(*l, abuilder, parent_index);
|
||||
const auto parent_type = ParseDefault<int>((*l)["ty"], -1) == kCameraLayerType
|
||||
? TransformType::kCamera
|
||||
: TransformType::kLayer;
|
||||
return this->attachLayerTransformImpl(*l, abuilder, parent_type, parent_index);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sk_sp<sksg::Transform> attachTransformNode(const skjson::ObjectValue& jlayer,
|
||||
const AnimationBuilder* abuilder,
|
||||
sk_sp<sksg::Transform> 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<CameraAdapter>(abuilder->fSize);
|
||||
|
||||
abuilder->bindProperty<ScalarValue>(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<int>(jlayer["ddd"], 0) == 0)
|
||||
? abuilder->attachMatrix2D(*jtransform, fScope, std::move(parent_transform))
|
||||
: abuilder->attachMatrix3D(*jtransform, fScope, std::move(parent_transform));
|
||||
}
|
||||
|
||||
sk_sp<sksg::Transform> 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<int>(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<sksg::RenderNode> AnimationBuilder::attachLayer(const skjson::ObjectValue*
|
||||
&AnimationBuilder::attachTextLayer, // 'ty': 5
|
||||
};
|
||||
|
||||
int type = ParseDefault<int>((*jlayer)["ty"], -1);
|
||||
const auto type = ParseDefault<int>((*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<int>(SK_ARRAY_COUNT(gLayerAttachers))) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -593,9 +641,9 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachLayer(const skjson::ObjectValue*
|
||||
return std::move(controller_node);
|
||||
}
|
||||
|
||||
sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(const skjson::ObjectValue& comp,
|
||||
sk_sp<sksg::RenderNode> 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<sk_sp<sksg::RenderNode>> layers;
|
||||
@ -612,11 +660,22 @@ sk_sp<sksg::RenderNode> 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<sksg::RenderNode> 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
|
||||
|
@ -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<ResourceProvider>, sk_sp<SkFontMgr>, sk_sp<PropertyObserver>,
|
||||
sk_sp<Logger>, sk_sp<MarkerObserver>,
|
||||
Animation::Builder::Stats*, float duration, float framerate);
|
||||
Animation::Builder::Stats*, const SkSize& size,
|
||||
float duration, float framerate);
|
||||
|
||||
std::unique_ptr<sksg::Scene> parse(const skjson::ObjectValue&);
|
||||
|
||||
@ -74,7 +78,8 @@ public:
|
||||
sk_sp<sksg::Transform> attachMatrix2D(const skjson::ObjectValue&, AnimatorScope*,
|
||||
sk_sp<sksg::Transform>) const;
|
||||
sk_sp<sksg::Transform> attachMatrix3D(const skjson::ObjectValue&, AnimatorScope*,
|
||||
sk_sp<sksg::Transform>) const;
|
||||
sk_sp<sksg::Transform>,
|
||||
sk_sp<TransformAdapter3D> = nullptr) const;
|
||||
sk_sp<sksg::RenderNode> attachOpacity(const skjson::ObjectValue&, AnimatorScope*,
|
||||
sk_sp<sksg::RenderNode>) const;
|
||||
sk_sp<sksg::Path> attachPath(const skjson::Value&, AnimatorScope*) const;
|
||||
@ -174,6 +179,7 @@ private:
|
||||
sk_sp<Logger> fLogger;
|
||||
sk_sp<MarkerObserver> fMarkerObserver;
|
||||
Animation::Builder::Stats* fStats;
|
||||
const SkSize fSize;
|
||||
const float fDuration,
|
||||
fFrameRate;
|
||||
mutable const char* fPropertyObserverContext;
|
||||
|
Loading…
Reference in New Issue
Block a user