Browse Source

Use epsilon when checking for singular matrices. Add benchmark for decomposing a Matrix. Add more tests for Matrix struct.

feature/test-branch^2
Dariusz Komosinski 6 years ago
parent
commit
efa17b83af
  1. 1
      src/Avalonia.Base/Utilities/MathUtilities.cs
  2. 11
      src/Avalonia.Visuals/Matrix.cs
  3. 16
      tests/Avalonia.Benchmarks/Visuals/MatrixBenchmarks.cs
  4. 53
      tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs

1
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -1,5 +1,4 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Utilities
{

11
src/Avalonia.Visuals/Matrix.cs

@ -9,6 +9,8 @@ namespace Avalonia
/// </summary>
public readonly struct Matrix : IEquatable<Matrix>
{
private const float DecomposeEpsilon = 0.0001f;
private readonly double _m11;
private readonly double _m12;
private readonly double _m21;
@ -54,7 +56,7 @@ namespace Avalonia
/// <summary>
/// HasInverse Property - returns true if this matrix is invertible, false otherwise.
/// </summary>
public bool HasInverse => GetDeterminant() != 0;
public bool HasInverse => Math.Abs(GetDeterminant()) >= double.Epsilon;
/// <summary>
/// The first element of the first row
@ -286,7 +288,7 @@ namespace Avalonia
{
double d = GetDeterminant();
if (d == 0)
if (Math.Abs(d) < double.Epsilon)
{
throw new InvalidOperationException("Transform is not invertible.");
}
@ -325,8 +327,9 @@ namespace Avalonia
decomposed = default;
var determinant = matrix.GetDeterminant();
if (determinant == 0)
// Based upon constant in System.Numerics.Matrix4x4.
if (Math.Abs(determinant) < DecomposeEpsilon)
{
return false;
}

16
tests/Avalonia.Benchmarks/Visuals/MatrixBenchmarks.cs

@ -0,0 +1,16 @@
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Visuals
{
[MemoryDiagnoser, InProcess]
public class MatrixBenchmarks
{
private static readonly Matrix s_data = Matrix.Identity;
[Benchmark(Baseline = true)]
public bool Decompose()
{
return Matrix.TryDecomposeTransform(s_data, out Matrix.Decomposed decomposed);
}
}
}

53
tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs

@ -1,4 +1,4 @@
using System.Globalization;
using System;
using Avalonia.Utilities;
using Xunit;
@ -7,13 +7,29 @@ namespace Avalonia.Visuals.UnitTests.Media
public class MatrixTests
{
[Fact]
public void Parse_Parses()
public void Can_Parse()
{
var matrix = Matrix.Parse("1,2,3,-4,5 6");
var expected = new Matrix(1, 2, 3, -4, 5, 6);
Assert.Equal(expected, matrix);
}
[Fact]
public void Singular_Has_No_Inverse()
{
var matrix = new Matrix(0, 0, 0, 0, 0, 0);
Assert.False(matrix.HasInverse);
}
[Fact]
public void Identity_Has_Inverse()
{
var matrix = Matrix.Identity;
Assert.True(matrix.HasInverse);
}
[Fact]
public void Can_Decompose_Translation()
{
@ -26,17 +42,25 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Equal(10, decomposed.Translate.Y);
}
[Fact]
public void Can_Decompose_Angle()
[Theory]
[InlineData(30d)]
[InlineData(0d)]
[InlineData(90d)]
[InlineData(270d)]
public void Can_Decompose_Angle(double angleDeg)
{
var angleRad = MathUtilities.Deg2Rad(30);
var angleRad = MathUtilities.Deg2Rad(angleDeg);
var matrix = Matrix.CreateRotation(angleRad);
var result = Matrix.TryDecomposeTransform(matrix, out Matrix.Decomposed decomposed);
Assert.Equal(true, result);
Assert.Equal(angleRad, decomposed.Angle);
var expected = NormalizeAngle(angleRad);
var actual = NormalizeAngle(decomposed.Angle);
Assert.Equal(expected, actual, 4);
}
[Theory]
@ -54,5 +78,22 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Equal(x, decomposed.Scale.X);
Assert.Equal(y, decomposed.Scale.Y);
}
private static double NormalizeAngle(double rad)
{
double twoPi = 2 * Math.PI;
while (rad < 0)
{
rad += twoPi;
}
while (rad > twoPi)
{
rad -= twoPi;
}
return rad;
}
}
}

Loading…
Cancel
Save