3 changed files with 234 additions and 0 deletions
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue