Bläddra i källkod

Add spring object

Wiesław Šoltés 3 år sedan
förälder
incheckning
8314eb9ddd

+ 143 - 0
src/Avalonia.Base/Animation/Spring.cs

@@ -0,0 +1,143 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using Avalonia;
+using Avalonia.Utilities;
+
+namespace Avalonia.Animation;
+
+/// <summary>
+/// Determines how an animation is used based on spring formula.
+/// </summary>
+[TypeConverter(typeof(SpringTypeConverter))]
+public class Spring : AvaloniaObject
+{
+    private SpringSolver _springSolver;
+    private double _mass;
+    private double _stiffness;
+    private double _damping;
+    private double _initialVelocity;
+    private bool _isDirty;
+
+    /// <summary>
+    /// Create a <see cref="Spring"/>.
+    /// </summary>
+    public Spring()
+    {
+        _mass = 0.0;
+        _stiffness = 0.0;
+        _damping = 0.0;
+        _initialVelocity = 0.0;
+        _isDirty = true;
+    }
+
+    /// <summary>
+    /// Create a <see cref="Spring"/> with the given parameters.
+    /// </summary>
+    /// <param name="mass">The spring mass.</param>
+    /// <param name="stiffness">The spring stiffness.</param>
+    /// <param name="damping">The spring damping.</param>
+    /// <param name="initialVelocity">The spring initial velocity.</param>
+    public Spring(double mass, double stiffness, double damping, double initialVelocity)
+    {
+        _mass = mass;
+        _stiffness = stiffness;
+        _damping = damping;
+        _initialVelocity = initialVelocity;
+        _isDirty = true;
+    }
+
+    /// <summary>
+    /// Parse a <see cref="Spring"/> from a string. The string needs to contain 4 values in it.
+    /// </summary>
+    /// <param name="value">string with 4 values in it</param>
+    /// <param name="culture">culture of the string</param>
+    /// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
+    /// <returns>A <see cref="Spring"/> with the appropriate values set</returns>
+    public static Spring Parse(string value, CultureInfo? culture)
+    {
+        if (culture is null)
+        {
+            culture = CultureInfo.InvariantCulture;
+        }
+
+        using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid Spring string: \"{value}\".");
+        return new Spring(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
+    }
+
+    /// <summary>
+    /// The spring mass.
+    /// </summary>
+    public double Mass
+    {
+        get => _mass;
+        set
+        {
+            _mass = value;
+            _isDirty = true;
+        }
+    }
+
+    /// <summary>
+    /// The spring stiffness.
+    /// </summary>
+    public double Stiffness
+    {
+        get => _stiffness;
+        set
+        {
+            _stiffness = value;
+            _isDirty = true;
+        }
+    }
+
+    /// <summary>
+    /// The spring damping.
+    /// </summary>
+    public double Damping
+    {
+        get => _damping;
+        set
+        {
+            _damping = value;
+            _isDirty = true;
+        }
+    }
+
+    /// <summary>
+    /// The spring initial velocity.
+    /// </summary>
+    public double InitialVelocity
+    {
+        get => _initialVelocity;
+        set
+        {
+            _initialVelocity = value;
+            _isDirty = true;
+        }
+    }
+
+    /// <summary>
+    /// Calculates spring progress from a linear progress.
+    /// </summary>
+    /// <param name="linearProgress">the linear progress</param>
+    /// <returns>The spring progress</returns>
+    public double GetSpringProgress(double linearProgress)
+    {
+        if (_isDirty)
+        {
+            Build();
+        }
+
+        return _springSolver.Solve(linearProgress);
+    }
+
+    /// <summary>
+    /// Create cached spring solver.
+    /// </summary>
+    private void Build()
+    {
+        _springSolver = new SpringSolver(_mass, _stiffness, _damping, _initialVelocity);
+        _isDirty = false;
+    }
+}

+ 70 - 0
src/Avalonia.Base/Animation/SpringSolver.cs

@@ -0,0 +1,70 @@
+// Ported from:
+// https://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/SpringSolver.h
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+using System;
+
+namespace Avalonia.Animation;
+
+internal struct SpringSolver 
+{
+    private double m_w0;
+    private double m_zeta;
+    private double m_wd;
+    private double m_A;
+    private double m_B;
+
+    public SpringSolver(double mass, double stiffness, double damping, double initialVelocity)
+    {
+        m_w0 = Math.Sqrt(stiffness / mass);
+        m_zeta = damping / (2 * Math.Sqrt(stiffness * mass));
+
+        if (m_zeta < 1) {
+            // Under-damped.
+            m_wd = m_w0 * Math.Sqrt(1 - m_zeta * m_zeta);
+            m_A = 1;
+            m_B = (m_zeta * m_w0 + -initialVelocity) / m_wd;
+        } else {
+            // Critically damped (ignoring over-damped case for now).
+            m_A = 1;
+            m_B = -initialVelocity + m_w0;
+            m_wd = 0;
+        }
+    }
+
+    public readonly double Solve(double t)
+    {
+        if (m_zeta < 1) {
+            // Under-damped
+            t = Math.Exp(-t * m_zeta * m_w0) * (m_A * Math.Cos(m_wd * t) + m_B * Math.Sin(m_wd * t));
+        } else {
+            // Critically damped (ignoring over-damped case for now).
+            t = (m_A + m_B * t) * Math.Exp(-t * m_w0);
+        }
+
+        // Map range from [1..0] to [0..1].
+        return 1 - t;
+    }
+}

+ 21 - 0
src/Avalonia.Base/Animation/SpringTypeConverter.cs

@@ -0,0 +1,21 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Avalonia.Animation;
+
+/// <summary>
+/// Converts string values to <see cref="Spring"/> values.
+/// </summary>
+public class SpringTypeConverter : TypeConverter
+{
+    public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+    {
+        return sourceType == typeof(string);
+    }
+
+    public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+    {
+        return Spring.Parse((string)value, CultureInfo.InvariantCulture);
+    }
+}