1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include "bezier_curve.h"
16 
17 #include <base/math/mathf.h>
18 
19 #include <meta/api/make_callback.h>
20 #include <meta/api/util.h>
21 #include <meta/interface/property/property_events.h>
22 
23 META_BEGIN_NAMESPACE()
24 
25 namespace Curves {
26 namespace Easing {
27 
Build(const IMetadata::Ptr & meta)28 bool CubicBezierEasingCurve::Build(const IMetadata::Ptr& meta)
29 {
30     if (Super::Build(meta)) {
31         auto update = MakeCallback<IOnChanged>(this, &CubicBezierEasingCurve::UpdateCoefficients);
32         META_ACCESS_PROPERTY(ControlPoint1)->OnChanged()->AddHandler(update);
33         META_ACCESS_PROPERTY(ControlPoint2)->OnChanged()->AddHandler(update);
34         UpdateCoefficients();
35         return true;
36     }
37     return false;
38 }
39 
Transform(float t) const40 float CubicBezierEasingCurve::Transform(float t) const
41 {
42     // First, solve "linear" X coordinate for t [0..1], then sample the bezier's Y at that X.
43     return GetY(GetLinearX(t));
44 }
45 
UpdateCoefficients()46 void CubicBezierEasingCurve::UpdateCoefficients()
47 {
48     auto cp1 = META_NS::GetValue(META_ACCESS_PROPERTY(ControlPoint1));
49     auto cp2 = META_NS::GetValue(META_ACCESS_PROPERTY(ControlPoint2));
50 
51     // Clamp x-coordinates of our control points between [0,1]
52     cp1.x = BASE_NS::Math::clamp01(cp1.x);
53     cp2.x = BASE_NS::Math::clamp01(cp2.x);
54 
55     // Cache the polynomial coefficients, first and last control points are implicity [0,0] and [1,1]
56     coeff_[2] = cp1 * 3.f;
57     coeff_[1] = (cp2 - cp1) * 3.f - coeff_[2];
58     coeff_[0] = BASE_NS::Math::Vec2(1.f, 1.f) - coeff_[2] - coeff_[1];
59 }
60 
IsFloatNull(float v,float epsilon)61 constexpr bool IsFloatNull(float v, float epsilon) noexcept
62 {
63     return BASE_NS::Math::abs(v) < epsilon;
64 }
65 
GetLinearX(float t) const66 float CubicBezierEasingCurve::GetLinearX(float t) const noexcept
67 {
68     static constexpr int newtonMaxIter = 8;
69     static constexpr float epsilon = 0.001f;
70 
71     if (t < 0) {
72         return 0.f;
73     }
74     if (t > 1.f) {
75         return 1.f;
76     }
77 
78     float x;
79     float dx;
80     float tt = t;
81 
82     // Fast iteration with Newton's method
83     for (int i = 0; i < newtonMaxIter; i++) {
84         if (x = GetX(tt) - t; IsFloatNull(x, epsilon)) {
85             return tt;
86         }
87         if (dx = GetDX(tt); IsFloatNull(dx, BASE_NS::Math::EPSILON)) {
88             break;
89         }
90         tt = tt - x / dx;
91     }
92 
93     // Didn't get a result with fast iteration, fallback to linear bisection
94     float t0 = 0.f;
95     float t1 = 1.f;
96 
97     while (t0 < t1) {
98         if (x = GetX(tt); IsFloatNull(x - t, epsilon)) {
99             return tt;
100         }
101         if (t > x) {
102             t0 = tt;
103         } else {
104             t1 = tt;
105         }
106         tt = (t1 - t0) * .5f + t0;
107     }
108 
109     // Fallback
110     return tt;
111 }
112 
113 } // namespace Easing
114 } // namespace Curves
115 
116 META_END_NAMESPACE()
117