blob: 4d4a73a585c172f15834d1fcd3d5af32397171ad [file] [log] [blame]
/*
* 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 "include/core/SkContourMeasure.h"
#include "include/core/SkPathBuilder.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/skottie/src/animator/Animator.h"
#include "modules/skottie/src/animator/KeyframeAnimator.h"
#include <cmath>
namespace skottie::internal {
namespace {
// Spatial 2D specialization: stores SkV2s and optional contour interpolators externally.
class Vec2KeyframeAnimator final : public KeyframeAnimator {
public:
struct SpatialValue {
Vec2Value v2;
sk_sp<SkContourMeasure> cmeasure;
};
Vec2KeyframeAnimator(std::vector<Keyframe> kfs, std::vector<SkCubicMap> cms,
std::vector<SpatialValue> vs, Vec2Value* vec_target, float* rot_target)
: INHERITED(std::move(kfs), std::move(cms))
, fValues(std::move(vs))
, fVecTarget(vec_target)
, fRotTarget(rot_target) {}
private:
StateChanged update(const Vec2Value& new_vec_value, const Vec2Value& new_tan_value) {
auto changed = (new_vec_value != *fVecTarget);
*fVecTarget = new_vec_value;
if (fRotTarget) {
const auto new_rot_value = SkRadiansToDegrees(std::atan2(new_tan_value.y,
new_tan_value.x));
changed |= new_rot_value != *fRotTarget;
*fRotTarget = new_rot_value;
}
return changed;
}
StateChanged onSeek(float t) override {
auto get_lerp_info = [this](float t) {
auto lerp_info = this->getLERPInfo(t);
// When tracking rotation/orientation, the last keyframe requires special handling:
// it doesn't store any spatial information but it is expected to maintain the
// previous orientation (per AE semantics).
//
// The easiest way to achieve this is to actually swap with the previous keyframe,
// with an adjusted weight of 1.
const auto vidx = lerp_info.vrec0.idx;
if (fRotTarget && vidx == fValues.size() - 1 && vidx > 0) {
SkASSERT(!fValues[vidx].cmeasure);
SkASSERT(lerp_info.vrec1.idx == vidx);
// Change LERPInfo{0, SIZE - 1, SIZE - 1}
// to LERPInfo{1, SIZE - 2, SIZE - 1}
lerp_info.weight = 1;
lerp_info.vrec0 = {vidx - 1};
// This yields equivalent lerp results because keyframed values are contiguous
// i.e frame[n-1].end_val == frame[n].start_val.
}
return lerp_info;
};
const auto lerp_info = get_lerp_info(t);
const auto& v0 = fValues[lerp_info.vrec0.idx];
if (v0.cmeasure) {
// Spatial keyframe: the computed weight is relative to the interpolation path
// arc length.
SkPoint pos;
SkVector tan;
if (v0.cmeasure->getPosTan(lerp_info.weight * v0.cmeasure->length(), &pos, &tan)) {
return this->update({ pos.fX, pos.fY }, {tan.fX, tan.fY});
}
}
const auto& v1 = fValues[lerp_info.vrec1.idx];
const auto tan = v1.v2 - v0.v2;
return this->update(Lerp(v0.v2, v1.v2, lerp_info.weight), tan);
}
const std::vector<Vec2KeyframeAnimator::SpatialValue> fValues;
Vec2Value* fVecTarget;
float* fRotTarget;
using INHERITED = KeyframeAnimator;
};
class Vec2ExpressionAnimator final : public Animator {
public:
Vec2ExpressionAnimator(sk_sp<ExpressionEvaluator<std::vector<float>>> expression_evaluator,
Vec2Value* target_value)
: fExpressionEvaluator(std::move(expression_evaluator))
, fTarget(target_value) {}
private:
StateChanged onSeek(float t) override {
auto old_value = *fTarget;
std::vector<float> result = fExpressionEvaluator->evaluate(t);
fTarget->x = result.size() > 0 ? result[0] : 0;
fTarget->y = result.size() > 1 ? result[1] : 0;
return *fTarget != old_value;
}
sk_sp<ExpressionEvaluator<std::vector<float>>> fExpressionEvaluator;
Vec2Value* fTarget;
};
class Vec2AnimatorBuilder final : public AnimatorBuilder {
public:
Vec2AnimatorBuilder(Vec2Value* vec_target, float* rot_target)
: INHERITED(Keyframe::Value::Type::kIndex)
, fVecTarget(vec_target)
, fRotTarget(rot_target) {}
sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder& abuilder,
const skjson::ArrayValue& jkfs) override {
SkASSERT(jkfs.size() > 0);
fValues.reserve(jkfs.size());
if (!this->parseKeyframes(abuilder, jkfs)) {
return nullptr;
}
fValues.shrink_to_fit();
return sk_sp<Vec2KeyframeAnimator>(
new Vec2KeyframeAnimator(std::move(fKFs),
std::move(fCMs),
std::move(fValues),
fVecTarget,
fRotTarget));
}
sk_sp<Animator> makeFromExpression(ExpressionManager& em, const char* expr) override {
sk_sp<ExpressionEvaluator<std::vector<SkScalar>>> expression_evaluator =
em.createArrayExpressionEvaluator(expr);
return sk_make_sp<Vec2ExpressionAnimator>(expression_evaluator, fVecTarget);
}
bool parseValue(const AnimationBuilder&, const skjson::Value& jv) const override {
return Parse(jv, fVecTarget);
}
private:
void backfill_spatial(const Vec2KeyframeAnimator::SpatialValue& val) {
SkASSERT(!fValues.empty());
auto& prev_val = fValues.back();
SkASSERT(!prev_val.cmeasure);
if (val.v2 == prev_val.v2) {
// spatial interpolation only make sense for noncoincident values
return;
}
// Check whether v0 and v1 have the same direction AND ||v0||>=||v1||
auto check_vecs = [](const SkV2& v0, const SkV2& v1) {
const auto v0_len2 = v0.lengthSquared(),
v1_len2 = v1.lengthSquared();
// check magnitude
if (v0_len2 < v1_len2) {
return false;
}
// v0, v1 have the same direction iff dot(v0,v1) = ||v0||*||v1||
// <=> dot(v0,v1)^2 = ||v0||^2 * ||v1||^2
const auto dot = v0.dot(v1);
return SkScalarNearlyEqual(dot * dot, v0_len2 * v1_len2);
};
if (check_vecs(val.v2 - prev_val.v2, fTo) &&
check_vecs(prev_val.v2 - val.v2, fTi)) {
// Both control points lie on the [prev_val..val] segment
// => we can power-reduce the Bezier "curve" to a straight line.
return;
}
// Finally, this looks like a legitimate spatial keyframe.
SkPathBuilder p;
p.moveTo (prev_val.v2.x , prev_val.v2.y);
p.cubicTo(prev_val.v2.x + fTo.x, prev_val.v2.y + fTo.y,
val.v2.x + fTi.x, val.v2.y + fTi.y,
val.v2.x, val.v2.y);
prev_val.cmeasure = SkContourMeasureIter(p.detach(), false).next();
}
bool parseKFValue(const AnimationBuilder&,
const skjson::ObjectValue& jkf,
const skjson::Value& jv,
Keyframe::Value* v) override {
Vec2KeyframeAnimator::SpatialValue val;
if (!Parse(jv, &val.v2)) {
return false;
}
if (fPendingSpatial) {
this->backfill_spatial(val);
}
// Track the last keyframe spatial tangents (checked on next parseValue).
fTi = ParseDefault<SkV2>(jkf["ti"], {0,0});
fTo = ParseDefault<SkV2>(jkf["to"], {0,0});
fPendingSpatial = fTi != SkV2{0,0} || fTo != SkV2{0,0};
if (fValues.empty() || val.v2 != fValues.back().v2 || fPendingSpatial) {
fValues.push_back(std::move(val));
}
v->idx = SkToU32(fValues.size() - 1);
return true;
}
std::vector<Vec2KeyframeAnimator::SpatialValue> fValues;
Vec2Value* fVecTarget; // required
float* fRotTarget; // optional
SkV2 fTi{0,0},
fTo{0,0};
bool fPendingSpatial = false;
using INHERITED = AnimatorBuilder;
};
} // namespace
bool AnimatablePropertyContainer::bindAutoOrientable(const AnimationBuilder& abuilder,
const skjson::ObjectValue* jprop,
Vec2Value* v, float* orientation) {
if (!jprop) {
return false;
}
if (!ParseDefault<bool>((*jprop)["s"], false)) {
// Regular (static or keyframed) 2D value.
Vec2AnimatorBuilder builder(v, orientation);
return this->bindImpl(abuilder, jprop, builder);
}
// Separate-dimensions vector value: each component is animated independently.
bool boundX = this->bind(abuilder, (*jprop)["x"], &v->x);
bool boundY = this->bind(abuilder, (*jprop)["y"], &v->y);
return boundX || boundY;
}
template <>
bool AnimatablePropertyContainer::bind<Vec2Value>(const AnimationBuilder& abuilder,
const skjson::ObjectValue* jprop,
Vec2Value* v) {
return this->bindAutoOrientable(abuilder, jprop, v, nullptr);
}
} // namespace skottie::internal