First Commit
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
// Copyright 2014 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "cubic_bezier.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace gfx {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kMaxNewtonIterations = 4;
|
||||
|
||||
} // namespace
|
||||
|
||||
static const double kBezierEpsilon = 1e-7;
|
||||
|
||||
double CubicBezier::ToFinite(double value) {
|
||||
// TODO(crbug.com/1275541): We can clamp this in numeric operation helper
|
||||
// function like ClampedNumeric.
|
||||
if (std::isinf(value)) {
|
||||
if (value > 0)
|
||||
return std::numeric_limits<double>::max();
|
||||
return std::numeric_limits<double>::lowest();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
CubicBezier::CubicBezier(double p1x, double p1y, double p2x, double p2y) {
|
||||
InitCoefficients(p1x, p1y, p2x, p2y);
|
||||
InitGradients(p1x, p1y, p2x, p2y);
|
||||
InitRange(p1y, p2y);
|
||||
InitSpline();
|
||||
}
|
||||
|
||||
CubicBezier::CubicBezier(const CubicBezier& other) = default;
|
||||
|
||||
void CubicBezier::InitCoefficients(double p1x,
|
||||
double p1y,
|
||||
double p2x,
|
||||
double p2y) {
|
||||
// Calculate the polynomial coefficients, implicit first and last control
|
||||
// points are (0,0) and (1,1).
|
||||
cx_ = 3.0 * p1x;
|
||||
bx_ = 3.0 * (p2x - p1x) - cx_;
|
||||
ax_ = 1.0 - cx_ - bx_;
|
||||
|
||||
cy_ = ToFinite(3.0 * p1y);
|
||||
by_ = ToFinite(3.0 * (p2y - p1y) - cy_);
|
||||
ay_ = ToFinite(1.0 - cy_ - by_);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Bezier curves with x-coordinates outside the range [0,1] for internal
|
||||
// control points may have multiple values for t for a given value of x.
|
||||
// In this case, calls to SolveCurveX may produce ambiguous results.
|
||||
monotonically_increasing_ = p1x >= 0 && p1x <= 1 && p2x >= 0 && p2x <= 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CubicBezier::InitGradients(double p1x,
|
||||
double p1y,
|
||||
double p2x,
|
||||
double p2y) {
|
||||
// End-point gradients are used to calculate timing function results
|
||||
// outside the range [0, 1].
|
||||
//
|
||||
// There are four possibilities for the gradient at each end:
|
||||
// (1) the closest control point is not horizontally coincident with regard to
|
||||
// (0, 0) or (1, 1). In this case the line between the end point and
|
||||
// the control point is tangent to the bezier at the end point.
|
||||
// (2) the closest control point is coincident with the end point. In
|
||||
// this case the line between the end point and the far control
|
||||
// point is tangent to the bezier at the end point.
|
||||
// (3) both internal control points are coincident with an endpoint. There
|
||||
// are two special case that fall into this category:
|
||||
// CubicBezier(0, 0, 0, 0) and CubicBezier(1, 1, 1, 1). Both are
|
||||
// equivalent to linear.
|
||||
// (4) the closest control point is horizontally coincident with the end
|
||||
// point, but vertically distinct. In this case the gradient at the
|
||||
// end point is Infinite. However, this causes issues when
|
||||
// interpolating. As a result, we break down to a simple case of
|
||||
// 0 gradient under these conditions.
|
||||
|
||||
if (p1x > 0)
|
||||
start_gradient_ = p1y / p1x;
|
||||
else if (!p1y && p2x > 0)
|
||||
start_gradient_ = p2y / p2x;
|
||||
else if (!p1y && !p2y)
|
||||
start_gradient_ = 1;
|
||||
else
|
||||
start_gradient_ = 0;
|
||||
|
||||
if (p2x < 1)
|
||||
end_gradient_ = (p2y - 1) / (p2x - 1);
|
||||
else if (p2y == 1 && p1x < 1)
|
||||
end_gradient_ = (p1y - 1) / (p1x - 1);
|
||||
else if (p2y == 1 && p1y == 1)
|
||||
end_gradient_ = 1;
|
||||
else
|
||||
end_gradient_ = 0;
|
||||
}
|
||||
|
||||
// This works by taking taking the derivative of the cubic bezier, on the y
|
||||
// axis. We can then solve for where the derivative is zero to find the min
|
||||
// and max distance along the line. We the have to solve those in terms of time
|
||||
// rather than distance on the x-axis
|
||||
void CubicBezier::InitRange(double p1y, double p2y) {
|
||||
range_min_ = 0;
|
||||
range_max_ = 1;
|
||||
if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1)
|
||||
return;
|
||||
|
||||
const double epsilon = kBezierEpsilon;
|
||||
|
||||
// Represent the function's derivative in the form at^2 + bt + c
|
||||
// as in sampleCurveDerivativeY.
|
||||
// (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros
|
||||
// but does not actually give the slope of the curve.)
|
||||
const double a = 3.0 * ay_;
|
||||
const double b = 2.0 * by_;
|
||||
const double c = cy_;
|
||||
|
||||
// Check if the derivative is constant.
|
||||
if (std::abs(a) < epsilon && std::abs(b) < epsilon)
|
||||
return;
|
||||
|
||||
// Zeros of the function's derivative.
|
||||
double t1 = 0;
|
||||
double t2 = 0;
|
||||
|
||||
if (std::abs(a) < epsilon) {
|
||||
// The function's derivative is linear.
|
||||
t1 = -c / b;
|
||||
} else {
|
||||
// The function's derivative is a quadratic. We find the zeros of this
|
||||
// quadratic using the quadratic formula.
|
||||
double discriminant = b * b - 4 * a * c;
|
||||
if (discriminant < 0)
|
||||
return;
|
||||
double discriminant_sqrt = sqrt(discriminant);
|
||||
t1 = (-b + discriminant_sqrt) / (2 * a);
|
||||
t2 = (-b - discriminant_sqrt) / (2 * a);
|
||||
}
|
||||
|
||||
double sol1 = 0;
|
||||
double sol2 = 0;
|
||||
|
||||
// If the solution is in the range [0,1] then we include it, otherwise we
|
||||
// ignore it.
|
||||
|
||||
// An interesting fact about these beziers is that they are only
|
||||
// actually evaluated in [0,1]. After that we take the tangent at that point
|
||||
// and linearly project it out.
|
||||
if (0 < t1 && t1 < 1)
|
||||
sol1 = SampleCurveY(t1);
|
||||
|
||||
if (0 < t2 && t2 < 1)
|
||||
sol2 = SampleCurveY(t2);
|
||||
|
||||
range_min_ = std::min({range_min_, sol1, sol2});
|
||||
range_max_ = std::max({range_max_, sol1, sol2});
|
||||
}
|
||||
|
||||
void CubicBezier::InitSpline() {
|
||||
double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1);
|
||||
for (int i = 0; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) {
|
||||
spline_samples_[i] = SampleCurveX(i * delta_t);
|
||||
}
|
||||
}
|
||||
|
||||
double CubicBezier::GetDefaultEpsilon() {
|
||||
return kBezierEpsilon;
|
||||
}
|
||||
|
||||
double CubicBezier::SolveCurveX(double x, double epsilon) const {
|
||||
jassert (x >= 0.0);
|
||||
jassert (x <= 1.0);
|
||||
|
||||
double t0;
|
||||
double t1;
|
||||
double t2 = x;
|
||||
double x2;
|
||||
double d2;
|
||||
int i;
|
||||
|
||||
#ifndef NDEBUG
|
||||
jassert (monotonically_increasing_);
|
||||
#endif
|
||||
|
||||
// Linear interpolation of spline curve for initial guess.
|
||||
double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1);
|
||||
for (i = 1; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) {
|
||||
if (x <= spline_samples_[i]) {
|
||||
t1 = delta_t * i;
|
||||
t0 = t1 - delta_t;
|
||||
t2 = t0 + (t1 - t0) * (x - spline_samples_[i - 1]) /
|
||||
(spline_samples_[i] - spline_samples_[i - 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a few iterations of Newton's method -- normally very fast.
|
||||
// See https://en.wikipedia.org/wiki/Newton%27s_method.
|
||||
double newton_epsilon = std::min(kBezierEpsilon, epsilon);
|
||||
for (i = 0; i < kMaxNewtonIterations; i++) {
|
||||
x2 = SampleCurveX(t2) - x;
|
||||
if (fabs(x2) < newton_epsilon)
|
||||
return t2;
|
||||
d2 = SampleCurveDerivativeX(t2);
|
||||
if (fabs(d2) < kBezierEpsilon)
|
||||
break;
|
||||
t2 = t2 - x2 / d2;
|
||||
}
|
||||
if (fabs(x2) < epsilon)
|
||||
return t2;
|
||||
|
||||
// Fall back to the bisection method for reliability.
|
||||
while (t0 < t1) {
|
||||
x2 = SampleCurveX(t2);
|
||||
if (fabs(x2 - x) < epsilon)
|
||||
return t2;
|
||||
if (x > x2)
|
||||
t0 = t2;
|
||||
else
|
||||
t1 = t2;
|
||||
t2 = (t1 + t0) * .5;
|
||||
}
|
||||
|
||||
// Failure.
|
||||
return t2;
|
||||
}
|
||||
|
||||
double CubicBezier::Solve(double x) const {
|
||||
return SolveWithEpsilon(x, kBezierEpsilon);
|
||||
}
|
||||
|
||||
double CubicBezier::SlopeWithEpsilon(double x, double epsilon) const {
|
||||
x = std::clamp(x, 0.0, 1.0);
|
||||
double t = SolveCurveX(x, epsilon);
|
||||
double dx = SampleCurveDerivativeX(t);
|
||||
double dy = SampleCurveDerivativeY(t);
|
||||
// TODO(crbug.com/1275534): We should clamp NaN to a proper value.
|
||||
// Please see the issue for detail.
|
||||
if (!dx && !dy)
|
||||
return 0;
|
||||
return ToFinite(dy / dx);
|
||||
}
|
||||
|
||||
double CubicBezier::Slope(double x) const {
|
||||
return SlopeWithEpsilon(x, kBezierEpsilon);
|
||||
}
|
||||
|
||||
double CubicBezier::GetX1() const {
|
||||
return cx_ / 3.0;
|
||||
}
|
||||
|
||||
double CubicBezier::GetY1() const {
|
||||
return cy_ / 3.0;
|
||||
}
|
||||
|
||||
double CubicBezier::GetX2() const {
|
||||
return (bx_ + cx_) / 3.0 + GetX1();
|
||||
}
|
||||
|
||||
double CubicBezier::GetY2() const {
|
||||
return (by_ + cy_) / 3.0 + GetY1();
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2014 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
|
||||
#define UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
|
||||
|
||||
namespace gfx {
|
||||
|
||||
#define CUBIC_BEZIER_SPLINE_SAMPLES 11
|
||||
|
||||
class CubicBezier {
|
||||
public:
|
||||
CubicBezier(double p1x, double p1y, double p2x, double p2y);
|
||||
CubicBezier(const CubicBezier& other);
|
||||
|
||||
CubicBezier& operator=(const CubicBezier&) = delete;
|
||||
|
||||
double SampleCurveX(double t) const {
|
||||
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
|
||||
// The x values are in the range [0, 1]. So it isn't needed toFinite
|
||||
// clamping.
|
||||
// https://drafts.csswg.org/css-easing-1/#funcdef-cubic-bezier-easing-function-cubic-bezier
|
||||
return ((ax_ * t + bx_) * t + cx_) * t;
|
||||
}
|
||||
|
||||
double SampleCurveY(double t) const {
|
||||
return ToFinite(((ay_ * t + by_) * t + cy_) * t);
|
||||
}
|
||||
|
||||
double SampleCurveDerivativeX(double t) const {
|
||||
return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_;
|
||||
}
|
||||
|
||||
double SampleCurveDerivativeY(double t) const {
|
||||
return ToFinite(
|
||||
ToFinite(ToFinite(3.0 * ay_) * t + ToFinite(2.0 * by_)) * t + cy_);
|
||||
}
|
||||
|
||||
static double GetDefaultEpsilon();
|
||||
|
||||
// Given an x value, find a parametric value it came from.
|
||||
// x must be in [0, 1] range. Doesn't use gradients.
|
||||
double SolveCurveX(double x, double epsilon) const;
|
||||
|
||||
// Evaluates y at the given x with default epsilon.
|
||||
double Solve(double x) const;
|
||||
// Evaluates y at the given x. The epsilon parameter provides a hint as to the
|
||||
// required accuracy and is not guaranteed. Uses gradients if x is
|
||||
// out of [0, 1] range.
|
||||
double SolveWithEpsilon(double x, double epsilon) const {
|
||||
if (x < 0.0)
|
||||
return ToFinite(0.0 + start_gradient_ * x);
|
||||
if (x > 1.0)
|
||||
return ToFinite(1.0 + end_gradient_ * (x - 1.0));
|
||||
return SampleCurveY(SolveCurveX(x, epsilon));
|
||||
}
|
||||
|
||||
// Returns an approximation of dy/dx at the given x with default epsilon.
|
||||
double Slope(double x) const;
|
||||
// Returns an approximation of dy/dx at the given x.
|
||||
// Clamps x to range [0, 1].
|
||||
double SlopeWithEpsilon(double x, double epsilon) const;
|
||||
|
||||
// These getters are used rarely. We reverse compute them from coefficients.
|
||||
// See CubicBezier::InitCoefficients. The speed has been traded for memory.
|
||||
double GetX1() const;
|
||||
double GetY1() const;
|
||||
double GetX2() const;
|
||||
double GetY2() const;
|
||||
|
||||
// Gets the bezier's minimum y value in the interval [0, 1].
|
||||
double range_min() const { return range_min_; }
|
||||
// Gets the bezier's maximum y value in the interval [0, 1].
|
||||
double range_max() const { return range_max_; }
|
||||
|
||||
private:
|
||||
void InitCoefficients(double p1x, double p1y, double p2x, double p2y);
|
||||
void InitGradients(double p1x, double p1y, double p2x, double p2y);
|
||||
void InitRange(double p1y, double p2y);
|
||||
void InitSpline();
|
||||
static double ToFinite(double value);
|
||||
|
||||
double ax_;
|
||||
double bx_;
|
||||
double cx_;
|
||||
|
||||
double ay_;
|
||||
double by_;
|
||||
double cy_;
|
||||
|
||||
double start_gradient_;
|
||||
double end_gradient_;
|
||||
|
||||
double range_min_;
|
||||
double range_max_;
|
||||
|
||||
double spline_samples_[CUBIC_BEZIER_SPLINE_SAMPLES];
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Guard against attempted to solve for t given x in the event that the curve
|
||||
// may have multiple values for t for some values of x in [0, 1].
|
||||
bool monotonically_increasing_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
#endif // UI_GFX_GEOMETRY_CUBIC_BEZIER_H_
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef DOXYGEN
|
||||
//==============================================================================
|
||||
/** The contents of this namespace are used to implement Animator and should not
|
||||
be used elsewhere. Their interfaces (and existence) are liable to change!
|
||||
*/
|
||||
namespace juce::detail::ArrayAndTupleOps
|
||||
{
|
||||
template <typename, typename = void>
|
||||
constexpr auto hasTupleSize = false;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto hasTupleSize<T, std::void_t<decltype (std::tuple_size<T>::value)>> = true;
|
||||
|
||||
static_assert (! hasTupleSize<float>);
|
||||
static_assert (hasTupleSize<std::tuple<float, float>>);
|
||||
static_assert (hasTupleSize<std::array<float, 5>>);
|
||||
|
||||
template <typename A, typename B, typename Op, size_t... Ix, std::enable_if_t<hasTupleSize<B>, int> = 0>
|
||||
constexpr auto& assignOpImpl (A& a, const B& b, Op&& op, std::index_sequence<Ix...>)
|
||||
{
|
||||
(op (std::get<Ix> (a), std::get<Ix> (b)), ...);
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename A, typename B, typename Op, size_t... Ix, std::enable_if_t<! hasTupleSize<B>, int> = 0>
|
||||
constexpr auto& assignOpImpl (A& a, const B& b, Op&& op, std::index_sequence<Ix...>)
|
||||
{
|
||||
(op (std::get<Ix> (a), b), ...);
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename A, typename B, typename Op, std::enable_if_t<hasTupleSize<A>, int> = 0>
|
||||
constexpr auto& assignOpImpl (A& a, const B& b, Op&& op)
|
||||
{
|
||||
return assignOpImpl (a, b, std::forward<Op> (op), std::make_index_sequence<std::tuple_size_v<A>>());
|
||||
}
|
||||
|
||||
template <typename A, typename B, std::enable_if_t<hasTupleSize<A>, int> = 0>
|
||||
constexpr auto& operator+= (A& a, const B& b)
|
||||
{
|
||||
return assignOpImpl (a, b, [] (auto& x, auto y)
|
||||
{
|
||||
using Tx = std::remove_reference_t<decltype (x)>;
|
||||
using Ty = std::remove_reference_t<decltype (y)>;
|
||||
|
||||
if constexpr (std::is_integral_v<Tx> && std::is_floating_point_v<Ty>)
|
||||
x = (Tx) std::round ((Ty) x + y);
|
||||
else
|
||||
x += y;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename A, typename B, std::enable_if_t<hasTupleSize<A>, int> = 0>
|
||||
constexpr auto& operator-= (A& a, const B& b)
|
||||
{
|
||||
return assignOpImpl (a, b, [] (auto& x, auto y)
|
||||
{
|
||||
using Tx = std::remove_reference_t<decltype (x)>;
|
||||
using Ty = std::remove_reference_t<decltype (y)>;
|
||||
|
||||
if constexpr (std::is_integral_v<Tx> && std::is_floating_point_v<Ty>)
|
||||
x = (Tx) std::round ((Ty) x - y);
|
||||
else
|
||||
x -= y;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename A, typename B, std::enable_if_t<hasTupleSize<A>, int> = 0>
|
||||
constexpr auto& operator*= (A& a, const B& b)
|
||||
{
|
||||
return assignOpImpl (a, b, [] (auto& x, auto y)
|
||||
{
|
||||
using Tx = std::remove_reference_t<decltype (x)>;
|
||||
using Ty = std::remove_reference_t<decltype (y)>;
|
||||
|
||||
if constexpr (std::is_integral_v<Tx> && std::is_floating_point_v<Ty>)
|
||||
x = (Tx) std::round ((Ty) x * y);
|
||||
else
|
||||
x *= y;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename A, typename B, std::enable_if_t<hasTupleSize<A>, int> = 0>
|
||||
constexpr auto& operator/= (A& a, const B& b)
|
||||
{
|
||||
return assignOpImpl (a, b, [] (auto& x, auto y)
|
||||
{
|
||||
using Tx = std::remove_reference_t<decltype (x)>;
|
||||
using Ty = std::remove_reference_t<decltype (y)>;
|
||||
|
||||
if constexpr (std::is_integral_v<Tx> && std::is_floating_point_v<Ty>)
|
||||
x = (Tx) std::round ((Ty) x / y);
|
||||
else
|
||||
x /= y;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename A, typename B, std::enable_if_t<hasTupleSize<A>, int> = 0> constexpr auto operator+ (const A& a, const B& b) { A copy { a }; return copy += b; }
|
||||
template <typename A, typename B, std::enable_if_t<hasTupleSize<A>, int> = 0> constexpr auto operator- (const A& a, const B& b) { A copy { a }; return copy -= b; }
|
||||
template <typename A, typename B, std::enable_if_t<hasTupleSize<A>, int> = 0> constexpr auto operator* (const A& a, const B& b) { A copy { a }; return copy *= b; }
|
||||
template <typename A, typename B, std::enable_if_t<hasTupleSize<A>, int> = 0> constexpr auto operator/ (const A& a, const B& b) { A copy { a }; return copy /= b; }
|
||||
|
||||
static_assert (std::tuple (1.0f, 5.0) + 3.0f == std::tuple (4.0f, 8.0));
|
||||
static_assert (std::tuple (1.0f, 5.0) - 1.0f == std::tuple (0.0f, 4.0));
|
||||
static_assert (std::tuple (1, 2, 3) * std::tuple (4, 5, 6) == std::tuple (4, 10, 18));
|
||||
} // namespace juce::detail::ArrayAndTupleOps
|
||||
#endif
|
||||
Reference in New Issue
Block a user