Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Vec2KeyframeAnimator.cpp
Go to the documentation of this file.
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
10#include "include/core/SkM44.h"
25#include "src/utils/SkJSON.h"
26
27#include <algorithm>
28#include <cmath>
29#include <utility>
30#include <vector>
31
32namespace skottie::internal {
33
34namespace {
35
36// Spatial 2D specialization: stores SkV2s and optional contour interpolators externally.
37class Vec2KeyframeAnimator final : public KeyframeAnimator {
38public:
39 struct SpatialValue {
42 };
43
44 Vec2KeyframeAnimator(std::vector<Keyframe> kfs, std::vector<SkCubicMap> cms,
45 std::vector<SpatialValue> vs, Vec2Value* vec_target, float* rot_target)
46 : INHERITED(std::move(kfs), std::move(cms))
47 , fValues(std::move(vs))
48 , fVecTarget(vec_target)
49 , fRotTarget(rot_target) {}
50
51private:
52 StateChanged update(const Vec2Value& new_vec_value, const Vec2Value& new_tan_value) {
53 auto changed = (new_vec_value != *fVecTarget);
54 *fVecTarget = new_vec_value;
55
56 if (fRotTarget) {
57 const auto new_rot_value = SkRadiansToDegrees(std::atan2(new_tan_value.y,
58 new_tan_value.x));
59 changed |= new_rot_value != *fRotTarget;
60 *fRotTarget = new_rot_value;
61 }
62
63 return changed;
64 }
65
66 StateChanged onSeek(float t) override {
67 auto get_lerp_info = [this](float t) {
68 auto lerp_info = this->getLERPInfo(t);
69
70 // When tracking rotation/orientation, the last keyframe requires special handling:
71 // it doesn't store any spatial information but it is expected to maintain the
72 // previous orientation (per AE semantics).
73 //
74 // The easiest way to achieve this is to actually swap with the previous keyframe,
75 // with an adjusted weight of 1.
76 const auto vidx = lerp_info.vrec0.idx;
77 if (fRotTarget && vidx == fValues.size() - 1 && vidx > 0) {
78 SkASSERT(!fValues[vidx].cmeasure);
79 SkASSERT(lerp_info.vrec1.idx == vidx);
80
81 // Change LERPInfo{0, SIZE - 1, SIZE - 1}
82 // to LERPInfo{1, SIZE - 2, SIZE - 1}
83 lerp_info.weight = 1;
84 lerp_info.vrec0 = {vidx - 1};
85
86 // This yields equivalent lerp results because keyframed values are contiguous
87 // i.e frame[n-1].end_val == frame[n].start_val.
88 }
89
90 return lerp_info;
91 };
92
93 const auto lerp_info = get_lerp_info(t);
94
95 const auto& v0 = fValues[lerp_info.vrec0.idx];
96 if (v0.cmeasure) {
97 // Spatial keyframe: the computed weight is relative to the interpolation path
98 // arc length.
100 SkVector tan;
101 const float len = v0.cmeasure->length(),
102 distance = len * lerp_info.weight;
103 if (v0.cmeasure->getPosTan(distance, &pos, &tan)) {
104 // Easing can yield a sub/super normal weight, which in turn can cause the
105 // interpolation position to become negative or larger than the path length.
106 // In those cases the expectation is to extrapolate using the endpoint tangent.
107 if (distance < 0 || distance > len) {
108 const float overshoot = std::copysign(std::max(-distance, distance - len),
109 distance);
110 pos += tan * overshoot;
111 }
112
113 return this->update({ pos.fX, pos.fY }, {tan.fX, tan.fY});
114 }
115 }
116
117 const auto& v1 = fValues[lerp_info.vrec1.idx];
118 const auto tan = v1.v2 - v0.v2;
119
120 return this->update(Lerp(v0.v2, v1.v2, lerp_info.weight), tan);
121 }
122
123 const std::vector<Vec2KeyframeAnimator::SpatialValue> fValues;
124 Vec2Value* fVecTarget;
125 float* fRotTarget;
126
127 using INHERITED = KeyframeAnimator;
128};
129
130class Vec2ExpressionAnimator final : public Animator {
131public:
132 Vec2ExpressionAnimator(sk_sp<ExpressionEvaluator<std::vector<float>>> expression_evaluator,
133 Vec2Value* target_value)
134 : fExpressionEvaluator(std::move(expression_evaluator))
135 , fTarget(target_value) {}
136
137private:
138
139 StateChanged onSeek(float t) override {
140 auto old_value = *fTarget;
141
142 std::vector<float> result = fExpressionEvaluator->evaluate(t);
143 fTarget->x = result.size() > 0 ? result[0] : 0;
144 fTarget->y = result.size() > 1 ? result[1] : 0;
145
146 return *fTarget != old_value;
147 }
148
150 Vec2Value* fTarget;
151};
152
153class Vec2AnimatorBuilder final : public AnimatorBuilder {
154 public:
155 Vec2AnimatorBuilder(Vec2Value* vec_target, float* rot_target)
156 : INHERITED(Keyframe::Value::Type::kIndex)
157 , fVecTarget(vec_target)
158 , fRotTarget(rot_target) {}
159
160 sk_sp<KeyframeAnimator> makeFromKeyframes(const AnimationBuilder& abuilder,
161 const skjson::ArrayValue& jkfs) override {
162 SkASSERT(jkfs.size() > 0);
163
164 fValues.reserve(jkfs.size());
165 if (!this->parseKeyframes(abuilder, jkfs)) {
166 return nullptr;
167 }
168 fValues.shrink_to_fit();
169
171 new Vec2KeyframeAnimator(std::move(fKFs),
172 std::move(fCMs),
173 std::move(fValues),
174 fVecTarget,
175 fRotTarget));
176 }
177
178 sk_sp<Animator> makeFromExpression(ExpressionManager& em, const char* expr) override {
180 em.createArrayExpressionEvaluator(expr);
181 return sk_make_sp<Vec2ExpressionAnimator>(expression_evaluator, fVecTarget);
182 }
183
184 bool parseValue(const AnimationBuilder&, const skjson::Value& jv) const override {
185 return ::skottie::Parse(jv, fVecTarget);
186 }
187
188 private:
189 void backfill_spatial(const Vec2KeyframeAnimator::SpatialValue& val) {
190 SkASSERT(!fValues.empty());
191 auto& prev_val = fValues.back();
192 SkASSERT(!prev_val.cmeasure);
193
194 if (val.v2 == prev_val.v2) {
195 // spatial interpolation only make sense for noncoincident values
196 return;
197 }
198
199 // Check whether v0 and v1 have the same direction AND ||v0||>=||v1||
200 auto check_vecs = [](const SkV2& v0, const SkV2& v1) {
201 const auto v0_len2 = v0.lengthSquared(),
202 v1_len2 = v1.lengthSquared();
203
204 // check magnitude
205 if (v0_len2 < v1_len2) {
206 return false;
207 }
208
209 // v0, v1 have the same direction iff dot(v0,v1) = ||v0||*||v1||
210 // <=> dot(v0,v1)^2 = ||v0||^2 * ||v1||^2
211 const auto dot = v0.dot(v1);
212 return SkScalarNearlyEqual(dot * dot, v0_len2 * v1_len2);
213 };
214
215 if (check_vecs(val.v2 - prev_val.v2, fTo) &&
216 check_vecs(prev_val.v2 - val.v2, fTi)) {
217 // Both control points lie on the [prev_val..val] segment
218 // => we can power-reduce the Bezier "curve" to a straight line.
219 return;
220 }
221
222 // Finally, this looks like a legitimate spatial keyframe.
224 p.moveTo (prev_val.v2.x , prev_val.v2.y);
225 p.cubicTo(prev_val.v2.x + fTo.x, prev_val.v2.y + fTo.y,
226 val.v2.x + fTi.x, val.v2.y + fTi.y,
227 val.v2.x, val.v2.y);
228 prev_val.cmeasure = SkContourMeasureIter(p.detach(), false).next();
229 }
230
231 bool parseKFValue(const AnimationBuilder&,
232 const skjson::ObjectValue& jkf,
233 const skjson::Value& jv,
234 Keyframe::Value* v) override {
235 Vec2KeyframeAnimator::SpatialValue val;
236 if (!::skottie::Parse(jv, &val.v2)) {
237 return false;
238 }
239
240 if (fPendingSpatial) {
241 this->backfill_spatial(val);
242 }
243
244 // Track the last keyframe spatial tangents (checked on next parseValue).
245 fTi = ParseDefault<SkV2>(jkf["ti"], {0,0});
246 fTo = ParseDefault<SkV2>(jkf["to"], {0,0});
247 fPendingSpatial = fTi != SkV2{0,0} || fTo != SkV2{0,0};
248
249 if (fValues.empty() || val.v2 != fValues.back().v2 || fPendingSpatial) {
250 fValues.push_back(std::move(val));
251 }
252
253 v->idx = SkToU32(fValues.size() - 1);
254
255 return true;
256 }
257
258 std::vector<Vec2KeyframeAnimator::SpatialValue> fValues;
259 Vec2Value* fVecTarget; // required
260 float* fRotTarget; // optional
261 SkV2 fTi{0,0},
262 fTo{0,0};
263 bool fPendingSpatial = false;
264
265 using INHERITED = AnimatorBuilder;
266 };
267
268} // namespace
269
271 const skjson::ObjectValue* jprop,
272 Vec2Value* v, float* orientation) {
273 if (!jprop) {
274 return false;
275 }
276
277 if (const auto* sid = ParseSlotID(jprop)) {
278 fHasSlotID = true;
279 abuilder.fSlotManager->trackVec2Value(SkString(sid->begin()), v, sk_ref_sp(this));
280 }
281
282 if (!ParseDefault<bool>((*jprop)["s"], false)) {
283 // Regular (static or keyframed) 2D value.
284 Vec2AnimatorBuilder builder(v, orientation);
285 return this->bindImpl(abuilder, jprop, builder);
286 }
287
288 // Separate-dimensions vector value: each component is animated independently.
289 bool boundX = this->bind(abuilder, (*jprop)["x"], &v->x);
290 bool boundY = this->bind(abuilder, (*jprop)["y"], &v->y);
291 return boundX || boundY;
292}
293
294template <>
295bool AnimatablePropertyContainer::bind<Vec2Value>(const AnimationBuilder& abuilder,
296 const skjson::ObjectValue* jprop,
297 Vec2Value* v) {
298 return this->bindAutoOrientable(abuilder, jprop, v, nullptr);
299}
300
301} // namespace skottie::internal
SkPoint pos
#define SkASSERT(cond)
Definition SkAssert.h:116
#define INHERITED(method,...)
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
#define SkRadiansToDegrees(radians)
Definition SkScalar.h:78
constexpr uint32_t SkToU32(S x)
Definition SkTo.h:26
sk_sp< SkContourMeasure > cmeasure
Vec2Value v2
sk_sp< SkContourMeasure > next()
size_t size() const
Definition SkJSON.h:262
bool bindAutoOrientable(const AnimationBuilder &abuilder, const skjson::ObjectValue *jobject, SkV2 *v, float *orientation)
bool bind(const AnimationBuilder &, const skjson::ObjectValue *, T *)
GAsyncResult * result
T Lerp(const T &a, const T &b, float t)
SkV2 Vec2Value
bool Parse(const skjson::Value &, T *)
const skjson::StringValue * ParseSlotID(const skjson::ObjectValue *jobj)
SINT T dot(const Vec< N, T > &a, const Vec< N, T > &b)
Definition SkVx.h:964
Definition ref_ptr.h:256
float fX
x-axis value
float fY
y-axis value
Definition SkM44.h:19
SkScalar dot(SkV2 v) const
Definition SkM44.h:48
float x
Definition SkM44.h:20
SkScalar lengthSquared() const
Definition SkM44.h:45
float y
Definition SkM44.h:20