blob: 0e5929334e83ced4363b04d3acc67db9503b53b7 [file] [log] [blame]
/*
* Copyright 2017 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkottyAnimator_DEFINED
#define SkottyAnimator_DEFINED
#include "SkCubicMap.h"
#include "SkMakeUnique.h"
#include "SkottyPriv.h"
#include "SkottyProperties.h"
#include "SkTArray.h"
#include "SkTypes.h"
#include <memory>
namespace skotty {
class AnimatorBase : public SkNoncopyable {
public:
virtual ~AnimatorBase() = default;
virtual void tick(SkMSec) = 0;
protected:
AnimatorBase() = default;
// Compute a cubic-Bezier-interpolated t relative to [t0..t1].
static float ComputeLocalT(float t, float t0, float t1,
const SkCubicMap*);
};
// Describes a keyframe interpolation interval (v0@t0) -> (v1@t1).
// TODO: add interpolation params.
template <typename T>
struct KeyframeInterval {
// Start/end values.
T fV0,
fV1;
// Start/end times.
float fT0 = 0,
fT1 = 0;
// Initialized for non-linear lerp.
std::unique_ptr<SkCubicMap> fCubicMap;
// Parse the current interval AND back-fill prev interval t1.
bool parse(const Json::Value& k, KeyframeInterval* prev) {
SkASSERT(k.isObject());
fT0 = fT1 = ParseScalar(k["t"], SK_ScalarMin);
if (fT0 == SK_ScalarMin) {
return false;
}
if (prev) {
if (prev->fT1 >= fT0) {
LOG("!! Dropping out-of-order key frame (t: %f < t: %f)\n", fT0, prev->fT1);
return false;
}
// Back-fill t1 in prev interval. Note: we do this even if we end up discarding
// the current interval (to support "t"-only final frames).
prev->fT1 = fT0;
}
if (!T::Parse(k["s"], &fV0) ||
!T::Parse(k["e"], &fV1) ||
fV0.cardinality() != fV1.cardinality() ||
(prev && fV0.cardinality() != prev->fV0.cardinality())) {
return false;
}
// default is linear lerp
static constexpr SkPoint kDefaultC0 = { 0, 0 },
kDefaultC1 = { 1, 1 };
const auto c0 = ParsePoint(k["i"], kDefaultC0),
c1 = ParsePoint(k["o"], kDefaultC1);
if (c0 != kDefaultC0 || c1 != kDefaultC1) {
fCubicMap = skstd::make_unique<SkCubicMap>();
// TODO: why do we have to plug these inverted?
fCubicMap->setPts(c1, c0);
}
return true;
}
void lerp(float t, T*) const;
};
// Binds an animated/keyframed property to a node attribute.
template <typename ValT, typename AttrT, typename NodeT>
class Animator : public AnimatorBase {
public:
using ApplyFuncT = void(*)(const sk_sp<NodeT>&, const AttrT&);
static std::unique_ptr<Animator> Make(const Json::Value& frames, sk_sp<NodeT> node,
ApplyFuncT&& applyFunc);
void tick(SkMSec t) override {
const auto& frame = this->findInterval(t);
ValT val;
frame.lerp(ComputeLocalT(t, frame.fT0, frame.fT1, frame.fCubicMap.get()), &val);
fFunc(fTarget, val.template as<AttrT>());
}
private:
Animator(SkTArray<KeyframeInterval<ValT>>&& intervals, sk_sp<NodeT> node,
ApplyFuncT&& applyFunc)
: fIntervals(std::move(intervals))
, fTarget(std::move(node))
, fFunc(std::move(applyFunc)) {}
const KeyframeInterval<ValT>& findInterval(float t) const;
const SkTArray<KeyframeInterval<ValT>> fIntervals;
sk_sp<NodeT> fTarget;
ApplyFuncT fFunc;
};
template <typename ValT, typename AttrT, typename NodeT>
std::unique_ptr<Animator<ValT, AttrT, NodeT>>
Animator<ValT, AttrT, NodeT>::Make(const Json::Value& frames, sk_sp<NodeT> node,
ApplyFuncT&& applyFunc) {
if (!frames.isArray())
return nullptr;
SkTArray<KeyframeInterval<ValT>> intervals;
intervals.reserve(frames.size());
for (const auto& frame : frames) {
if (!frame.isObject())
return nullptr;
auto& curr_interval = intervals.push_back();
auto* prev_interval = intervals.count() > 1 ? &intervals.fromBack(1) : nullptr;
if (!curr_interval.parse(frame, prev_interval)) {
// Invalid frame, or "t"-only frame.
intervals.pop_back();
continue;
}
}
// If we couldn't determine a t1 for the last interval, discard it.
if (!intervals.empty() && intervals.back().fT0 == intervals.back().fT1) {
intervals.pop_back();
}
if (intervals.empty()) {
return nullptr;
}
return std::unique_ptr<Animator>(
new Animator(std::move(intervals), node, std::move(applyFunc)));
}
template <typename ValT, typename AttrT, typename NodeT>
const KeyframeInterval<ValT>& Animator<ValT, AttrT, NodeT>::findInterval(float t) const {
SkASSERT(!fIntervals.empty());
// TODO: cache last/current frame?
auto f0 = fIntervals.begin(),
f1 = fIntervals.end() - 1;
SkASSERT(f0->fT0 < f0->fT1);
SkASSERT(f1->fT0 < f1->fT1);
if (t < f0->fT0) {
return *f0;
}
if (t > f1->fT1) {
return *f1;
}
while (f0 != f1) {
SkASSERT(f0 < f1);
SkASSERT(t >= f0->fT0 && t <= f1->fT1);
const auto f = f0 + (f1 - f0) / 2;
SkASSERT(f->fT0 < f->fT1);
if (t > f->fT1) {
f0 = f + 1;
} else {
f1 = f;
}
}
SkASSERT(f0 == f1);
SkASSERT(t >= f0->fT0 && t <= f1->fT1);
return *f0;
}
} // namespace skotty
#endif // SkottyAnimator_DEFINED