1/*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "include/core/SkContourMeasure.h"
9#include "modules/skottie/src/SkottieJson.h"
10#include "modules/skottie/src/SkottieValue.h"
11#include "modules/skottie/src/animator/Animator.h"
12#include "modules/skottie/src/animator/KeyframeAnimator.h"
13
14namespace skottie::internal {
15
16namespace {
17
18// Spatial 2D specialization: stores SkV2s and optional contour interpolators externally.
19class Vec2KeyframeAnimator final : public KeyframeAnimator {
20 struct SpatialValue {
21 Vec2Value v2;
22 sk_sp<SkContourMeasure> cmeasure;
23 };
24
25public:
26 class Builder final : public KeyframeAnimatorBuilder {
27 public:
28 sk_sp<KeyframeAnimator> make(const AnimationBuilder& abuilder,
29 const skjson::ArrayValue& jkfs,
30 void* target_value) override {
31 SkASSERT(jkfs.size() > 0);
32
33 fValues.reserve(jkfs.size());
34 if (!this->parseKeyframes(abuilder, jkfs)) {
35 return nullptr;
36 }
37 fValues.shrink_to_fit();
38
39 return sk_sp<Vec2KeyframeAnimator>(
40 new Vec2KeyframeAnimator(std::move(fKFs),
41 std::move(fCMs),
42 std::move(fValues),
43 static_cast<Vec2Value*>(target_value)));
44 }
45
46 bool parseValue(const AnimationBuilder&, const skjson::Value& jv, void* v) const override {
47 return Parse(jv, static_cast<Vec2Value*>(v));
48 }
49
50 private:
51 void backfill_spatial(const SpatialValue& val) {
52 if (fTi == SkV2{0,0} && fTo == SkV2{0,0}) {
53 // no tangents => linear
54 return;
55 }
56
57 SkASSERT(!fValues.empty());
58 auto& prev_val = fValues.back();
59 SkASSERT(!prev_val.cmeasure);
60
61 if (val.v2 == prev_val.v2) {
62 // spatial interpolation only make sense for noncoincident values
63 return;
64 }
65
66 // Check whether v0 and v1 have the same direction AND ||v0||>=||v1||
67 auto check_vecs = [](const SkV2& v0, const SkV2& v1) {
68 const auto v0_len2 = v0.lengthSquared(),
69 v1_len2 = v1.lengthSquared();
70
71 // check magnitude
72 if (v0_len2 < v1_len2) {
73 return false;
74 }
75
76 // v0, v1 have the same direction iff dot(v0,v1) = ||v0||*||v1||
77 // <=> dot(v0,v1)^2 = ||v0||^2 * ||v1||^2
78 const auto dot = v0.dot(v1);
79 return SkScalarNearlyEqual(dot * dot, v0_len2 * v1_len2);
80 };
81
82 if (check_vecs(val.v2 - prev_val.v2, fTo) &&
83 check_vecs(prev_val.v2 - val.v2, fTi)) {
84 // Both control points lie on the [prev_val..val] segment
85 // => we can power-reduce the Bezier "curve" to a straight line.
86 return;
87 }
88
89 // Finally, this looks like a legitimate spatial keyframe.
90 SkPath p;
91 p.moveTo (prev_val.v2.x , prev_val.v2.y);
92 p.cubicTo(prev_val.v2.x + fTo.x, prev_val.v2.y + fTo.y,
93 val.v2.x + fTi.x, val.v2.y + fTi.y,
94 val.v2.x, val.v2.y);
95 prev_val.cmeasure = SkContourMeasureIter(p, false).next();
96 }
97
98 bool parseKFValue(const AnimationBuilder&,
99 const skjson::ObjectValue& jkf,
100 const skjson::Value& jv,
101 Keyframe::Value* v) override {
102 SpatialValue val;
103 if (!Parse(jv, &val.v2)) {
104 return false;
105 }
106
107 this->backfill_spatial(val);
108
109 // Track the last keyframe spatial tangents (checked on next parseValue).
110 fTi = ParseDefault<SkV2>(jkf["ti"], {0,0});
111 fTo = ParseDefault<SkV2>(jkf["to"], {0,0});
112
113 if (fValues.empty() || val.v2 != fValues.back().v2) {
114 fValues.push_back(std::move(val));
115 }
116
117 v->idx = SkToU32(fValues.size() - 1);
118
119 return true;
120 }
121
122 std::vector<SpatialValue> fValues;
123 SkV2 fTi{0,0},
124 fTo{0,0};
125 };
126
127private:
128 Vec2KeyframeAnimator(std::vector<Keyframe> kfs, std::vector<SkCubicMap> cms,
129 std::vector<SpatialValue> vs, Vec2Value* target_value)
130 : INHERITED(std::move(kfs), std::move(cms))
131 , fValues(std::move(vs))
132 , fTarget(target_value) {}
133
134 StateChanged update(const Vec2Value& new_value) {
135 const auto changed = (new_value != *fTarget);
136 *fTarget = new_value;
137
138 return changed;
139 }
140
141 StateChanged onSeek(float t) override {
142 const auto& lerp_info = this->getLERPInfo(t);
143
144 const auto& v0 = fValues[lerp_info.vrec0.idx];
145 if (v0.cmeasure) {
146 // Spatial keyframe: the computed weight is relative to the interpolation path
147 // arc length.
148 SkPoint pos;
149 if (v0.cmeasure->getPosTan(lerp_info.weight * v0.cmeasure->length(), &pos, nullptr)) {
150 return this->update({ pos.fX, pos.fY });
151 }
152 }
153
154 const auto& v1 = fValues[lerp_info.vrec1.idx];
155 return this->update(Lerp(v0.v2, v1.v2, lerp_info.weight));
156 }
157
158 const std::vector<SpatialValue> fValues;
159 Vec2Value* fTarget;
160
161 using INHERITED = KeyframeAnimator;
162};
163
164} // namespace
165
166template <>
167bool AnimatablePropertyContainer::bind<Vec2Value>(const AnimationBuilder& abuilder,
168 const skjson::ObjectValue* jprop,
169 Vec2Value* v) {
170 if (!jprop) {
171 return false;
172 }
173
174 if (!ParseDefault<bool>((*jprop)["s"], false)) {
175 // Regular (static or keyframed) 2D value.
176 Vec2KeyframeAnimator::Builder builder;
177 return this->bindImpl(abuilder, jprop, builder, v);
178 }
179
180 // Separate-dimensions vector value: each component is animated independently.
181 return this->bind(abuilder, (*jprop)["x"], &v->x)
182 | this->bind(abuilder, (*jprop)["y"], &v->y);
183}
184
185} // namespace skottie::internal
186