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 "include/core/SkPathBuilder.h"
10 #include "modules/skottie/src/SkottieJson.h"
11 #include "modules/skottie/src/SkottieValue.h"
12 #include "modules/skottie/src/animator/Animator.h"
13 #include "modules/skottie/src/animator/KeyframeAnimator.h"
14 
15 #include <cmath>
16 
17 namespace skottie::internal {
18 
19 namespace  {
20 
21 // Spatial 2D specialization: stores SkV2s and optional contour interpolators externally.
22 class Vec2KeyframeAnimator final : public KeyframeAnimator {
23     struct SpatialValue {
24         Vec2Value               v2;
25         sk_sp<SkContourMeasure> cmeasure;
26     };
27 
28 public:
29     class Builder final : public KeyframeAnimatorBuilder {
30     public:
Builder(Vec2Value * vec_target,float * rot_target)31         Builder(Vec2Value* vec_target, float* rot_target)
32             : fVecTarget(vec_target)
33             , fRotTarget(rot_target) {}
34 
make(const AnimationBuilder & abuilder,const skjson::ArrayValue & jkfs)35         sk_sp<KeyframeAnimator> make(const AnimationBuilder& abuilder,
36                                      const skjson::ArrayValue& jkfs) override {
37             SkASSERT(jkfs.size() > 0);
38 
39             fValues.reserve(jkfs.size());
40             if (!this->parseKeyframes(abuilder, jkfs)) {
41                 return nullptr;
42             }
43             fValues.shrink_to_fit();
44 
45             return sk_sp<Vec2KeyframeAnimator>(
46                         new Vec2KeyframeAnimator(std::move(fKFs),
47                                                  std::move(fCMs),
48                                                  std::move(fValues),
49                                                  fVecTarget,
50                                                  fRotTarget));
51         }
52 
parseValue(const AnimationBuilder &,const skjson::Value & jv) const53         bool parseValue(const AnimationBuilder&, const skjson::Value& jv) const override {
54             return Parse(jv, fVecTarget);
55         }
56 
57     private:
backfill_spatial(const SpatialValue & val)58         void backfill_spatial(const SpatialValue& val) {
59             SkASSERT(!fValues.empty());
60             auto& prev_val = fValues.back();
61             SkASSERT(!prev_val.cmeasure);
62 
63             if (val.v2 == prev_val.v2) {
64                 // spatial interpolation only make sense for noncoincident values
65                 return;
66             }
67 
68             // Check whether v0 and v1 have the same direction AND ||v0||>=||v1||
69             auto check_vecs = [](const SkV2& v0, const SkV2& v1) {
70                 const auto v0_len2 = v0.lengthSquared(),
71                            v1_len2 = v1.lengthSquared();
72 
73                 // check magnitude
74                 if (v0_len2 < v1_len2) {
75                     return false;
76                 }
77 
78                 // v0, v1 have the same direction iff dot(v0,v1) = ||v0||*||v1||
79                 // <=>    dot(v0,v1)^2 = ||v0||^2 * ||v1||^2
80                 const auto dot = v0.dot(v1);
81                 return SkScalarNearlyEqual(dot * dot, v0_len2 * v1_len2);
82             };
83 
84             if (check_vecs(val.v2 - prev_val.v2, fTo) &&
85                 check_vecs(prev_val.v2 - val.v2, fTi)) {
86                 // Both control points lie on the [prev_val..val] segment
87                 //   => we can power-reduce the Bezier "curve" to a straight line.
88                 return;
89             }
90 
91             // Finally, this looks like a legitimate spatial keyframe.
92             SkPathBuilder p;
93             p.moveTo (prev_val.v2.x        , prev_val.v2.y);
94             p.cubicTo(prev_val.v2.x + fTo.x, prev_val.v2.y + fTo.y,
95                            val.v2.x + fTi.x,      val.v2.y + fTi.y,
96                            val.v2.x,              val.v2.y);
97             prev_val.cmeasure = SkContourMeasureIter(p.detach(), false).next();
98         }
99 
parseKFValue(const AnimationBuilder &,const skjson::ObjectValue & jkf,const skjson::Value & jv,Keyframe::Value * v)100         bool parseKFValue(const AnimationBuilder&,
101                           const skjson::ObjectValue& jkf,
102                           const skjson::Value& jv,
103                           Keyframe::Value* v) override {
104             SpatialValue val;
105             if (!Parse(jv, &val.v2)) {
106                 return false;
107             }
108 
109             if (fPendingSpatial) {
110                 this->backfill_spatial(val);
111             }
112 
113             // Track the last keyframe spatial tangents (checked on next parseValue).
114             fTi             = ParseDefault<SkV2>(jkf["ti"], {0,0});
115             fTo             = ParseDefault<SkV2>(jkf["to"], {0,0});
116             fPendingSpatial = fTi != SkV2{0,0} || fTo != SkV2{0,0};
117 
118             if (fValues.empty() || val.v2 != fValues.back().v2 || fPendingSpatial) {
119                 fValues.push_back(std::move(val));
120             }
121 
122             v->idx = SkToU32(fValues.size() - 1);
123 
124             return true;
125         }
126 
127         std::vector<SpatialValue> fValues;
128         Vec2Value*                fVecTarget; // required
129         float*                    fRotTarget; // optional
130         SkV2                      fTi{0,0},
131                                   fTo{0,0};
132         bool                      fPendingSpatial = false;
133     };
134 
135 private:
Vec2KeyframeAnimator(std::vector<Keyframe> kfs,std::vector<SkCubicMap> cms,std::vector<SpatialValue> vs,Vec2Value * vec_target,float * rot_target)136     Vec2KeyframeAnimator(std::vector<Keyframe> kfs, std::vector<SkCubicMap> cms,
137                          std::vector<SpatialValue> vs, Vec2Value* vec_target, float* rot_target)
138         : INHERITED(std::move(kfs), std::move(cms))
139         , fValues(std::move(vs))
140         , fVecTarget(vec_target)
141         , fRotTarget(rot_target) {}
142 
update(const Vec2Value & new_vec_value,const Vec2Value & new_tan_value)143     StateChanged update(const Vec2Value& new_vec_value, const Vec2Value& new_tan_value) {
144         auto changed = (new_vec_value != *fVecTarget);
145         *fVecTarget = new_vec_value;
146 
147         if (fRotTarget) {
148             const auto new_rot_value = SkRadiansToDegrees(std::atan2(new_tan_value.y,
149                                                                      new_tan_value.x));
150             changed |= new_rot_value != *fRotTarget;
151             *fRotTarget = new_rot_value;
152         }
153 
154         return changed;
155     }
156 
onSeek(float t)157     StateChanged onSeek(float t) override {
158         auto get_lerp_info = [this](float t) {
159             auto lerp_info = this->getLERPInfo(t);
160 
161             // When tracking rotation/orientation, the last keyframe requires special handling:
162             // it doesn't store any spatial information but it is expected to maintain the
163             // previous orientation (per AE semantics).
164             //
165             // The easiest way to achieve this is to actually swap with the previous keyframe,
166             // with an adjusted weight of 1.
167             const auto vidx = lerp_info.vrec0.idx;
168             if (fRotTarget && vidx == fValues.size() - 1 && vidx > 0) {
169                 SkASSERT(!fValues[vidx].cmeasure);
170                 SkASSERT(lerp_info.vrec1.idx == vidx);
171 
172                 // Change LERPInfo{0, SIZE - 1, SIZE - 1}
173                 // to     LERPInfo{1, SIZE - 2, SIZE - 1}
174                 lerp_info.weight = 1;
175                 lerp_info.vrec0  = {vidx - 1};
176 
177                 // This yields equivalent lerp results because keyframed values are contiguous
178                 // i.e frame[n-1].end_val == frame[n].start_val.
179             }
180 
181             return lerp_info;
182         };
183 
184         const auto lerp_info = get_lerp_info(t);
185 
186         const auto& v0 = fValues[lerp_info.vrec0.idx];
187         if (v0.cmeasure) {
188             // Spatial keyframe: the computed weight is relative to the interpolation path
189             // arc length.
190             SkPoint  pos;
191             SkVector tan;
192             if (v0.cmeasure->getPosTan(lerp_info.weight * v0.cmeasure->length(), &pos, &tan)) {
193                 return this->update({ pos.fX, pos.fY }, {tan.fX, tan.fY});
194             }
195         }
196 
197         const auto& v1 = fValues[lerp_info.vrec1.idx];
198         const auto tan = v1.v2 - v0.v2;
199 
200         return this->update(Lerp(v0.v2, v1.v2, lerp_info.weight), tan);
201     }
202 
203     const std::vector<SpatialValue> fValues;
204     Vec2Value*                      fVecTarget;
205     float*                          fRotTarget;
206 
207     using INHERITED = KeyframeAnimator;
208 };
209 
210 } // namespace
211 
bindAutoOrientable(const AnimationBuilder & abuilder,const skjson::ObjectValue * jprop,Vec2Value * v,float * orientation)212 bool AnimatablePropertyContainer::bindAutoOrientable(const AnimationBuilder& abuilder,
213                                                      const skjson::ObjectValue* jprop,
214                                                      Vec2Value* v, float* orientation) {
215     if (!jprop) {
216         return false;
217     }
218 
219     if (!ParseDefault<bool>((*jprop)["s"], false)) {
220         // Regular (static or keyframed) 2D value.
221         Vec2KeyframeAnimator::Builder builder(v, orientation);
222         return this->bindImpl(abuilder, jprop, builder);
223     }
224 
225     // Separate-dimensions vector value: each component is animated independently.
226     return this->bind(abuilder, (*jprop)["x"], &v->x)
227          | this->bind(abuilder, (*jprop)["y"], &v->y);
228 }
229 
230 template <>
bind(const AnimationBuilder & abuilder,const skjson::ObjectValue * jprop,Vec2Value * v)231 bool AnimatablePropertyContainer::bind<Vec2Value>(const AnimationBuilder& abuilder,
232                                                   const skjson::ObjectValue* jprop,
233                                                   Vec2Value* v) {
234     return this->bindAutoOrientable(abuilder, jprop, v, nullptr);
235 }
236 
237 } // namespace skottie::internal
238