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 | |
14 | namespace skottie::internal { |
15 | |
16 | namespace { |
17 | |
18 | // Spatial 2D specialization: stores SkV2s and optional contour interpolators externally. |
19 | class Vec2KeyframeAnimator final : public KeyframeAnimator { |
20 | struct SpatialValue { |
21 | Vec2Value v2; |
22 | sk_sp<SkContourMeasure> cmeasure; |
23 | }; |
24 | |
25 | public: |
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 | |
127 | private: |
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 | |
166 | template <> |
167 | bool 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 | |