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