/* * Copyright (c) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "bezier_curve.h" #include #include #include #include META_BEGIN_NAMESPACE() namespace Curves { namespace Easing { bool CubicBezierEasingCurve::Build(const IMetadata::Ptr& meta) { if (Super::Build(meta)) { auto update = MakeCallback(this, &CubicBezierEasingCurve::UpdateCoefficients); META_ACCESS_PROPERTY(ControlPoint1)->OnChanged()->AddHandler(update); META_ACCESS_PROPERTY(ControlPoint2)->OnChanged()->AddHandler(update); UpdateCoefficients(); return true; } return false; } float CubicBezierEasingCurve::Transform(float t) const { // First, solve "linear" X coordinate for t [0..1], then sample the bezier's Y at that X. return GetY(GetLinearX(t)); } void CubicBezierEasingCurve::UpdateCoefficients() { auto cp1 = META_NS::GetValue(META_ACCESS_PROPERTY(ControlPoint1)); auto cp2 = META_NS::GetValue(META_ACCESS_PROPERTY(ControlPoint2)); // Clamp x-coordinates of our control points between [0,1] cp1.x = BASE_NS::Math::clamp01(cp1.x); cp2.x = BASE_NS::Math::clamp01(cp2.x); // Cache the polynomial coefficients, first and last control points are implicity [0,0] and [1,1] coeff_[2] = cp1 * 3.f; coeff_[1] = (cp2 - cp1) * 3.f - coeff_[2]; coeff_[0] = BASE_NS::Math::Vec2(1.f, 1.f) - coeff_[2] - coeff_[1]; } constexpr bool IsFloatNull(float v, float epsilon) noexcept { return BASE_NS::Math::abs(v) < epsilon; } float CubicBezierEasingCurve::GetLinearX(float t) const noexcept { static constexpr int newtonMaxIter = 8; static constexpr float epsilon = 0.001f; if (t < 0) { return 0.f; } if (t > 1.f) { return 1.f; } float x; float dx; float tt = t; // Fast iteration with Newton's method for (int i = 0; i < newtonMaxIter; i++) { if (x = GetX(tt) - t; IsFloatNull(x, epsilon)) { return tt; } if (dx = GetDX(tt); IsFloatNull(dx, BASE_NS::Math::EPSILON)) { break; } tt = tt - x / dx; } // Didn't get a result with fast iteration, fallback to linear bisection float t0 = 0.f; float t1 = 1.f; while (t0 < t1) { if (x = GetX(tt); IsFloatNull(x - t, epsilon)) { return tt; } if (t > x) { t0 = tt; } else { t1 = tt; } tt = (t1 - t0) * .5f + t0; } // Fallback return tt; } } // namespace Easing } // namespace Curves META_END_NAMESPACE()