Browse Source

Add spring object

pull/8992/head
Wiesław Šoltés 4 years ago
parent
commit
8314eb9ddd
  1. 143
      src/Avalonia.Base/Animation/Spring.cs
  2. 70
      src/Avalonia.Base/Animation/SpringSolver.cs
  3. 21
      src/Avalonia.Base/Animation/SpringTypeConverter.cs

143
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
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
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);
}
}
Loading…
Cancel
Save