diff --git a/src/Avalonia.Base/Animation/Easings/CubicBezier.cs b/src/Avalonia.Base/Animation/Easings/CubicBezier.cs deleted file mode 100644 index 5c2487a5167..00000000000 --- a/src/Avalonia.Base/Animation/Easings/CubicBezier.cs +++ /dev/null @@ -1,306 +0,0 @@ -// ReSharper disable InconsistentNaming -// Ported from Chromium project /~https://github.com/chromium/chromium/blob/374d31b7704475fa59f7b2cb836b3b68afdc3d79/ui/gfx/geometry/cubic_bezier.cc - -using System; -using Avalonia.Utilities; - -// ReSharper disable CompareOfFloatsByEqualityOperator -// ReSharper disable CommentTypo -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable TooWideLocalVariableScope -// ReSharper disable UnusedMember.Global -#pragma warning disable 649 - -namespace Avalonia.Animation.Easings -{ - /// - /// Represents a cubic bezier curve and can compute Y coordinate for a given X - /// - internal unsafe struct CubicBezier - { - const int CUBIC_BEZIER_SPLINE_SAMPLES = 11; - double ax_; - double bx_; - double cx_; - - double ay_; - double by_; - double cy_; - - double start_gradient_; - double end_gradient_; - - double range_min_; - double range_max_; - private bool monotonically_increasing_; - - fixed double spline_samples_[CUBIC_BEZIER_SPLINE_SAMPLES]; - - public CubicBezier(double p1x, double p1y, double p2x, double p2y) : this() - { - InitCoefficients(p1x, p1y, p2x, p2y); - InitGradients(p1x, p1y, p2x, p2y); - InitRange(p1y, p2y); - InitSpline(); - } - - public readonly double SampleCurveX(double t) - { - // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. - return ((ax_ * t + bx_) * t + cx_) * t; - } - - readonly double SampleCurveY(double t) - { - return ((ay_ * t + by_) * t + cy_) * t; - } - - readonly double SampleCurveDerivativeX(double t) - { - return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_; - } - - readonly double SampleCurveDerivativeY(double t) - { - return (3.0 * ay_ * t + 2.0 * by_) * t + cy_; - } - - public readonly double SolveWithEpsilon(double x, double epsilon) - { - if (x < 0.0) - return 0.0 + start_gradient_ * x; - if (x > 1.0) - return 1.0 + end_gradient_ * (x - 1.0); - return SampleCurveY(SolveCurveX(x, epsilon)); - } - - void 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_ = 3.0 * p1y; - by_ = 3.0 * (p2y - p1y) - cy_; - ay_ = 1.0 - cy_ - by_; - -#if DEBUG - // 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 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 == 0 && p2x > 0) - start_gradient_ = p2y / p2x; - else if (p1y == 0 && p2y == 0) - 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; - } - - const double kBezierEpsilon = 1e-7; - - void InitRange(double p1y, double p2y) - { - range_min_ = 0; - range_max_ = 1; - if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1) - return; - - 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.) - double a = 3.0 * ay_; - double b = 2.0 * by_; - double c = cy_; - - // Check if the derivative is constant. - if (Math.Abs(a) < epsilon && Math.Abs(b) < epsilon) - return; - - // Zeros of the function's derivative. - double t1; - double t2 = 0; - - if (Math.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 = Math.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_ = Math.Min(Math.Min(range_min_, sol1), sol2); - range_max_ = Math.Max(Math.Max(range_max_, sol1), sol2); - } - - void 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); - } - } - - const int kMaxNewtonIterations = 4; - - - public readonly double SolveCurveX(double x, double epsilon) - { - if (x < 0 || x > 1) - throw new ArgumentException(); - - double t0 = 0; - double t1 = 0; - double t2 = x; - double x2 = 0; - double d2; - int i; - -#if DEBUG - if (!monotonically_increasing_) - throw new InvalidOperationException(); -#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 = Math.Min(kBezierEpsilon, epsilon); - for (i = 0; i < kMaxNewtonIterations; i++) - { - x2 = SampleCurveX(t2) - x; - if (Math.Abs(x2) < newton_epsilon) - return t2; - d2 = SampleCurveDerivativeX(t2); - if (Math.Abs(d2) < kBezierEpsilon) - break; - t2 = t2 - x2 / d2; - } - - if (Math.Abs(x2) < epsilon) - return t2; - - // Fall back to the bisection method for reliability. - while (t0 < t1) - { - x2 = SampleCurveX(t2); - if (Math.Abs(x2 - x) < epsilon) - return t2; - if (x > x2) - t0 = t2; - else - t1 = t2; - t2 = (t1 + t0) * .5; - } - - // Failure. - return t2; - } - - public readonly double Solve(double x) - { - return SolveWithEpsilon(x, kBezierEpsilon); - } - - public readonly double SlopeWithEpsilon(double x, double epsilon) - { - x = MathUtilities.Clamp(x, 0.0, 1.0); - double t = SolveCurveX(x, epsilon); - double dx = SampleCurveDerivativeX(t); - double dy = SampleCurveDerivativeY(t); - return dy / dx; - } - - public readonly double Slope(double x) - { - return SlopeWithEpsilon(x, kBezierEpsilon); - } - - public readonly double RangeMin => range_min_; - public readonly double RangeMax => range_max_; - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs b/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs index d6827f96439..2e43e97da2a 100644 --- a/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs +++ b/src/Avalonia.Base/Animation/Easings/CubicBezierEasing.cs @@ -2,26 +2,16 @@ namespace Avalonia.Animation.Easings; +[Obsolete("Use SplineEasing instead")] public sealed class CubicBezierEasing : IEasing { - private CubicBezier _bezier; - //cubic-bezier(0.25, 0.1, 0.25, 1.0) - internal CubicBezierEasing(Point controlPoint1, Point controlPoint2) + private CubicBezierEasing() { - ControlPoint1 = controlPoint1; - ControlPoint2 = controlPoint2; - if (controlPoint1.X < 0 || controlPoint1.X > 1 || controlPoint2.X < 0 || controlPoint2.X > 1) - throw new ArgumentException(); - _bezier = new CubicBezier(controlPoint1.X, controlPoint1.Y, controlPoint2.X, controlPoint2.Y); } public Point ControlPoint2 { get; set; } public Point ControlPoint1 { get; set; } - - internal static IEasing Ease { get; } = new CubicBezierEasing(new Point(0.25, 0.1), new Point(0.25, 1)); double IEasing.Ease(double progress) - { - return _bezier.Solve(progress); - } + => throw new NotSupportedException(); } diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index bf48f18db4c..ca4dfe5ed3c 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Media; using Avalonia.Metadata; @@ -85,7 +86,7 @@ internal Compositor(IRenderLoop loop, IPlatformGraphics? gpu, _server = new ServerCompositor(loop, gpu, _batchObjectPool, _batchMemoryPool); _triggerCommitRequested = () => scheduler.CommitRequested(this); - DefaultEasing = new CubicBezierEasing(new Point(0.25f, 0.1f), new Point(0.25f, 1f)); + DefaultEasing = new SplineEasing(new KeySpline(0.25, 0.1, 0.25, 1.0)); } ///