Removed no longer needed AffineTransform class.
This commit is contained in:
Родитель
47092a2a31
Коммит
ae10be192e
|
@ -377,7 +377,7 @@ namespace Microsoft.Maui.Graphics.GDI
|
|||
|
||||
public void NativeConcatenateTransform(Matrix3x2 transform)
|
||||
{
|
||||
_scale *= transform.GetLengthScale();
|
||||
_scale *= GetLengthScale(transform);
|
||||
var transformMatrix = new Matrix(transform.M11, transform.M12, transform.M21, transform.M22, transform.M31, transform.M32);
|
||||
_graphics.MultiplyTransform(transformMatrix);
|
||||
}
|
||||
|
|
|
@ -1,520 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Microsoft.Maui.Graphics
|
||||
{
|
||||
[DebuggerDisplay("X={Translation.X} Y={Translation.Y} W={Scaling.Width} H={Scaling.Height} R={RotationDegrees}° ")]
|
||||
public class AffineTransform
|
||||
{
|
||||
private const float Epsilon = 1E-10f;
|
||||
|
||||
private float _m11;
|
||||
private float _m21;
|
||||
private float _m31;
|
||||
private float _m12;
|
||||
private float _m22;
|
||||
private float _m32;
|
||||
|
||||
public AffineTransform()
|
||||
{
|
||||
_m11 = _m22 = 1.0f;
|
||||
_m12 = _m21 = _m31 = _m32 = 0.0f;
|
||||
}
|
||||
|
||||
public AffineTransform(AffineTransform t)
|
||||
{
|
||||
_m11 = t._m11;
|
||||
_m12 = t._m12;
|
||||
_m21 = t._m21;
|
||||
_m22 = t._m22;
|
||||
_m31 = t._m31;
|
||||
_m32 = t._m32;
|
||||
}
|
||||
|
||||
public AffineTransform(float m11, float m12, float m21, float m22, float m31, float m32)
|
||||
{
|
||||
_m11 = m11;
|
||||
_m12 = m12;
|
||||
_m21 = m21;
|
||||
_m22 = m22;
|
||||
_m31 = m31;
|
||||
_m32 = m32;
|
||||
}
|
||||
|
||||
public AffineTransform(float[] matrix)
|
||||
{
|
||||
_m11 = matrix[0];
|
||||
_m12 = matrix[1];
|
||||
_m21 = matrix[2];
|
||||
_m22 = matrix[3];
|
||||
if (matrix.Length > 4)
|
||||
{
|
||||
_m31 = matrix[4];
|
||||
_m32 = matrix[5];
|
||||
}
|
||||
}
|
||||
|
||||
public AffineTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_m11 = matrix.M11;
|
||||
_m12 = matrix.M12;
|
||||
_m21 = matrix.M21;
|
||||
_m22 = matrix.M22;
|
||||
_m31 = matrix.M31;
|
||||
_m32 = matrix.M32;
|
||||
}
|
||||
|
||||
public float M11 => _m11;
|
||||
|
||||
public float M22 => _m22;
|
||||
|
||||
public float M21 => _m21;
|
||||
|
||||
public float M12 => _m12;
|
||||
|
||||
public float M31 => _m31;
|
||||
|
||||
public float M32 => _m32;
|
||||
|
||||
public PointF Translation
|
||||
{
|
||||
get => new PointF(_m31, _m32);
|
||||
set { _m31 = value.X; _m32 = value.Y; }
|
||||
}
|
||||
|
||||
public float RotationDegrees
|
||||
{
|
||||
get => Geometry.RadiansToDegrees(Rotation);
|
||||
set => Rotation = Geometry.DegreesToRadians(value);
|
||||
}
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get => (float)Math.Atan2(M12, M11);
|
||||
set
|
||||
{
|
||||
var t = Translation;
|
||||
var s = Scale;
|
||||
SetTo(t, value, s);
|
||||
}
|
||||
}
|
||||
|
||||
public float AverageScale
|
||||
{
|
||||
get
|
||||
{
|
||||
var s = Scale;
|
||||
return (Math.Abs(s.Width) + Math.Abs(s.Height)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
public SizeF Scale
|
||||
{
|
||||
get
|
||||
{
|
||||
var sx = _m12 == 0 ? Math.Abs(_m11) : new Vector2(_m11, _m12).Length();
|
||||
var sy = _m21 == 0 ? Math.Abs(_m22) : new Vector2(_m21, _m22).Length();
|
||||
if (GetDeterminant() < 0) sy = -sy;
|
||||
return new SizeF(sx, sy);
|
||||
}
|
||||
set
|
||||
{
|
||||
var t = Translation;
|
||||
var r = Rotation;
|
||||
SetTo(t, r, value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsIdentity => _m11 == 1.0f && _m22 == 1.0f && _m12 == 0.0f && _m21 == 0.0f && _m31 == 0.0f && _m32 == 0.0f;
|
||||
|
||||
public void CopyTo(float[] matrix, int offset = 0, int count = 6)
|
||||
{
|
||||
count = Math.Min(matrix.Length, count);
|
||||
|
||||
matrix[offset + 0] = _m11;
|
||||
matrix[offset + 1] = _m12;
|
||||
matrix[offset + 2] = _m21;
|
||||
matrix[offset + 3] = _m22;
|
||||
if (count > 4)
|
||||
{
|
||||
matrix[offset + 4] = _m31;
|
||||
matrix[offset + 5] = _m32;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetDeterminant()
|
||||
{
|
||||
return _m11 * _m22 - _m21 * _m12;
|
||||
}
|
||||
|
||||
public void SetTransform(float m11, float m12, float m21, float m22, float m31, float m32)
|
||||
{
|
||||
_m11 = m11;
|
||||
_m12 = m12;
|
||||
_m21 = m21;
|
||||
_m22 = m22;
|
||||
_m31 = m31;
|
||||
_m32 = m32;
|
||||
}
|
||||
|
||||
public void SetTransform(float[] matrix)
|
||||
{
|
||||
_m11 = matrix[0];
|
||||
_m12 = matrix[1];
|
||||
_m21 = matrix[2];
|
||||
_m22 = matrix[3];
|
||||
if (matrix.Length > 4)
|
||||
{
|
||||
_m31 = matrix[4];
|
||||
_m32 = matrix[5];
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_m11 = matrix.M11;
|
||||
_m12 = matrix.M12;
|
||||
_m21 = matrix.M21;
|
||||
_m22 = matrix.M22;
|
||||
_m31 = matrix.M31;
|
||||
_m32 = matrix.M32;
|
||||
}
|
||||
|
||||
public void SetTransform(AffineTransform t)
|
||||
{
|
||||
SetTransform(t._m11, t._m12, t._m21, t._m22, t._m31, t._m32);
|
||||
}
|
||||
|
||||
public void SetToIdentity()
|
||||
{
|
||||
_m11 = _m22 = 1.0f;
|
||||
_m12 = _m21 = _m31 = _m32 = 0.0f;
|
||||
}
|
||||
|
||||
public void SetTo(PointF translation, float rotation, SizeF scale)
|
||||
{
|
||||
this.SetToTranslation(translation.X, translation.Y);
|
||||
this.ConcatenateRotation(rotation);
|
||||
this.ConcatenateScale(scale);
|
||||
}
|
||||
|
||||
public void SetToTranslation(float mx, float my)
|
||||
{
|
||||
_m11 = _m22 = 1.0f;
|
||||
_m21 = _m12 = 0.0f;
|
||||
_m31 = mx;
|
||||
_m32 = my;
|
||||
}
|
||||
|
||||
public void SetToScale(float scx, float scy)
|
||||
{
|
||||
_m11 = scx;
|
||||
_m22 = scy;
|
||||
_m12 = _m21 = _m31 = _m32 = 0.0f;
|
||||
}
|
||||
|
||||
public void SetToShear(float shx, float shy)
|
||||
{
|
||||
_m11 = _m22 = 1.0f;
|
||||
_m31 = _m32 = 0.0f;
|
||||
_m21 = shx;
|
||||
_m12 = shy;
|
||||
}
|
||||
|
||||
public void SetToRotation(float radians)
|
||||
{
|
||||
float sin = (float) Math.Sin(radians);
|
||||
float cos = (float) Math.Cos(radians);
|
||||
if (Math.Abs(cos) < Epsilon)
|
||||
{
|
||||
cos = 0.0f;
|
||||
sin = sin > 0.0f ? 1.0f : -1.0f;
|
||||
}
|
||||
else if (Math.Abs(sin) < Epsilon)
|
||||
{
|
||||
sin = 0.0f;
|
||||
cos = cos > 0.0f ? 1.0f : -1.0f;
|
||||
}
|
||||
|
||||
_m11 = _m22 = cos;
|
||||
_m21 = -sin;
|
||||
_m12 = sin;
|
||||
_m31 = _m32 = 0.0f;
|
||||
}
|
||||
|
||||
public void SetToRotation(float radians, float px, float py)
|
||||
{
|
||||
SetToRotation(radians);
|
||||
_m31 = px * (1.0f - _m11) + py * _m12;
|
||||
_m32 = py * (1.0f - _m11) - px * _m12;
|
||||
}
|
||||
|
||||
public static AffineTransform GetInstance(PointF translation, float rotation, SizeF scale)
|
||||
{
|
||||
var t = new AffineTransform();
|
||||
t.SetTo(translation, rotation, scale);
|
||||
return t;
|
||||
}
|
||||
|
||||
public static AffineTransform GetTranslateInstance(float mx, float my)
|
||||
{
|
||||
var t = new AffineTransform();
|
||||
t.SetToTranslation(mx, my);
|
||||
return t;
|
||||
}
|
||||
|
||||
public static AffineTransform GetScaleInstance(float scx, float scY)
|
||||
{
|
||||
var t = new AffineTransform();
|
||||
t.SetToScale(scx, scY);
|
||||
return t;
|
||||
}
|
||||
|
||||
public static AffineTransform GetShearInstance(float shx, float shy)
|
||||
{
|
||||
var m = new AffineTransform();
|
||||
m.SetToShear(shx, shy);
|
||||
return m;
|
||||
}
|
||||
|
||||
public static AffineTransform GetRotateInstance(float angle)
|
||||
{
|
||||
var t = new AffineTransform();
|
||||
t.SetToRotation(angle);
|
||||
return t;
|
||||
}
|
||||
|
||||
public static AffineTransform GetRotateInstance(float angle, float x, float y)
|
||||
{
|
||||
var t = new AffineTransform();
|
||||
t.SetToRotation(angle, x, y);
|
||||
return t;
|
||||
}
|
||||
|
||||
public void ConcatenateTranslation(PointF point)
|
||||
{
|
||||
Concatenate(GetTranslateInstance(point.X, point.Y));
|
||||
}
|
||||
|
||||
public void ConcatenateTranslation(float mx, float my)
|
||||
{
|
||||
Concatenate(GetTranslateInstance(mx, my));
|
||||
}
|
||||
|
||||
public void ConcatenateScale(SizeF scale)
|
||||
{
|
||||
Concatenate(GetScaleInstance(scale.Width, scale.Height));
|
||||
}
|
||||
|
||||
public void ConcatenateScale(float scx, float scy)
|
||||
{
|
||||
Concatenate(GetScaleInstance(scx, scy));
|
||||
}
|
||||
|
||||
public void ConcatenateShear(float shx, float shy)
|
||||
{
|
||||
Concatenate(GetShearInstance(shx, shy));
|
||||
}
|
||||
|
||||
public void ConcatenateRotationInDegrees(float degrees)
|
||||
{
|
||||
ConcatenateRotation(Geometry.DegreesToRadians(degrees));
|
||||
}
|
||||
|
||||
public void ConcatenateRotationInDegrees(float degrees, float px, float py)
|
||||
{
|
||||
ConcatenateRotation(Geometry.DegreesToRadians(degrees), px, py);
|
||||
}
|
||||
|
||||
public void ConcatenateRotation(float radians)
|
||||
{
|
||||
Concatenate(GetRotateInstance(radians));
|
||||
}
|
||||
|
||||
public void ConcatenateRotation(float radians, float px, float py)
|
||||
{
|
||||
Concatenate(GetRotateInstance(radians, px, py));
|
||||
}
|
||||
|
||||
public void Concatenate(AffineTransform t)
|
||||
{
|
||||
SetTransform(Multiply(t, this));
|
||||
}
|
||||
|
||||
public void PreConcatenate(AffineTransform t)
|
||||
{
|
||||
SetTransform(Multiply(this, t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiply two AffineTransform objects
|
||||
/// </summary>
|
||||
/// <param name="t1">the multiplicand</param>
|
||||
/// <param name="t2">the multiplier</param>
|
||||
/// <returns>an AffineTransform object that is a result of t1 multiplied by t2</returns>
|
||||
private AffineTransform Multiply(AffineTransform t1, AffineTransform t2)
|
||||
{
|
||||
return new AffineTransform(
|
||||
t1._m11 * t2._m11 + t1._m12 * t2._m21, // m11
|
||||
t1._m11 * t2._m12 + t1._m12 * t2._m22, // m21
|
||||
t1._m21 * t2._m11 + t1._m22 * t2._m21, // m12
|
||||
t1._m21 * t2._m12 + t1._m22 * t2._m22, // m22
|
||||
t1._m31 * t2._m11 + t1._m32 * t2._m21 + t2._m31, // m31
|
||||
t1._m31 * t2._m12 + t1._m32 * t2._m22 + t2._m32); // m32
|
||||
}
|
||||
|
||||
public AffineTransform CreateInverse()
|
||||
{
|
||||
float det = GetDeterminant();
|
||||
if (Math.Abs(det) < Epsilon)
|
||||
throw new Exception("Determinant is zero");
|
||||
|
||||
return new AffineTransform(
|
||||
_m22 / det,
|
||||
-_m12 / det,
|
||||
-_m21 / det,
|
||||
_m11 / det,
|
||||
(_m21 * _m32 - _m22 * _m31) / det,
|
||||
(_m12 * _m31 - _m11 * _m32) / det
|
||||
);
|
||||
}
|
||||
|
||||
public PointF Transform(PointF src)
|
||||
{
|
||||
return Transform(src.X, src.Y);
|
||||
}
|
||||
|
||||
public PointF Transform(float x, float y)
|
||||
{
|
||||
return new PointF(x * _m11 + y * _m21 + _m31, x * _m12 + y * _m22 + _m32);
|
||||
}
|
||||
|
||||
public PointF InverseTransform(PointF src)
|
||||
{
|
||||
float det = GetDeterminant();
|
||||
if (Math.Abs(det) < Epsilon)
|
||||
throw new Exception("Unable to inverse this transform.");
|
||||
|
||||
float x = src.X - _m31;
|
||||
float y = src.Y - _m32;
|
||||
|
||||
return new PointF((x * _m22 - y * _m21) / det, (y * _m11 - x * _m12) / det);
|
||||
}
|
||||
|
||||
public void Transform(float[] src, int srcOff, float[] dst, int dstOff, int length)
|
||||
{
|
||||
int step = 2;
|
||||
if (src == dst && srcOff < dstOff && dstOff < srcOff + length * 2)
|
||||
{
|
||||
srcOff = srcOff + length * 2 - 2;
|
||||
dstOff = dstOff + length * 2 - 2;
|
||||
step = -2;
|
||||
}
|
||||
|
||||
while (--length >= 0)
|
||||
{
|
||||
float x = src[srcOff + 0];
|
||||
float y = src[srcOff + 1];
|
||||
dst[dstOff + 0] = x * _m11 + y * _m21 + _m31;
|
||||
dst[dstOff + 1] = x * _m12 + y * _m22 + _m32;
|
||||
srcOff += step;
|
||||
dstOff += step;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsUnityTransform()
|
||||
{
|
||||
return !(HasScale() || HasRotate() || HasTranslate());
|
||||
}
|
||||
|
||||
private bool HasScale()
|
||||
{
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
|
||||
// if matrix has no rotation, we can do an exact check:
|
||||
|
||||
if (!HasRotate())
|
||||
{
|
||||
return _m11 != 1.0 || _m22 != 1.0;
|
||||
}
|
||||
|
||||
// when the transform is rotated, we have to deconstruct
|
||||
// the scaling and handle the check with precission loss:
|
||||
|
||||
var scale = this.Scale;
|
||||
|
||||
if (Math.Abs(scale.Width - 1) > Epsilon) return true;
|
||||
if (Math.Abs(scale.Height - 1) > Epsilon) return true;
|
||||
|
||||
return false;
|
||||
|
||||
// ReSharper restore CompareOfFloatsByEqualityOperator
|
||||
}
|
||||
|
||||
private bool HasRotate()
|
||||
{
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
return _m12 != 0.0 || _m21 != 0.0;
|
||||
// ReSharper restore CompareOfFloatsByEqualityOperator
|
||||
}
|
||||
|
||||
private bool HasTranslate()
|
||||
{
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
return _m31 != 0.0 || _m32 != 0.0;
|
||||
// ReSharper restore CompareOfFloatsByEqualityOperator
|
||||
}
|
||||
|
||||
public bool OnlyTranslate()
|
||||
{
|
||||
return !HasRotate() && !HasScale();
|
||||
}
|
||||
|
||||
public bool OnlyTranslateOrScale()
|
||||
{
|
||||
return !HasRotate();
|
||||
}
|
||||
|
||||
public bool OnlyScale()
|
||||
{
|
||||
return !HasRotate() && !HasTranslate();
|
||||
}
|
||||
|
||||
public void Deconstruct(out PointF translation, out float rotation, out SizeF scale)
|
||||
{
|
||||
scale = this.Scale;
|
||||
rotation = this.Rotation;
|
||||
translation = Translation;
|
||||
}
|
||||
|
||||
public static implicit operator AffineTransform(Matrix3x2 matrix) => new AffineTransform(matrix);
|
||||
|
||||
public static explicit operator Matrix3x2(AffineTransform matrix)
|
||||
{
|
||||
return new Matrix3x2(matrix._m11, matrix._m12, matrix._m21, matrix._m22, matrix._m31, matrix._m32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a matrix from an SRT.
|
||||
/// </summary>
|
||||
/// <param name="scale">The scale</param>
|
||||
/// <param name="rotation">The rotation, in radians</param>
|
||||
/// <param name="translation">the translation</param>
|
||||
/// <returns>A Matrix3x2</returns>
|
||||
/// <remarks>
|
||||
/// This is equivalent to:<br/>
|
||||
/// <c>
|
||||
/// m = Matrix3x2.CreateScale(scale)<br/>
|
||||
/// m *= Matri3x2.CreateRotation(rotation)<br/>
|
||||
/// m *= Matri3x2.CreateTranslation(translation)<br/>
|
||||
/// </c>
|
||||
/// </remarks>
|
||||
public static Matrix3x2 CreateMatrix3x2(Vector2 scale, float rotation, Vector2 translation)
|
||||
{
|
||||
return Matrix3x2Extensions.CreateMatrix3x2(scale, rotation, translation);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -48,6 +48,8 @@ namespace Microsoft.Maui.Graphics
|
|||
// let derived classes handle the transform change if needed.
|
||||
}
|
||||
|
||||
protected static float GetLengthScale(Matrix3x2 matrix) => matrix.GetLengthScale();
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
// Do nothing right now
|
||||
|
|
|
@ -3,9 +3,11 @@ using System.Collections.Generic;
|
|||
using System.Numerics;
|
||||
using System.Text;
|
||||
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.Maui.Graphics.Tests")]
|
||||
|
||||
namespace Microsoft.Maui.Graphics
|
||||
{
|
||||
public static class Matrix3x2Extensions
|
||||
static class Matrix3x2Extensions
|
||||
{
|
||||
public static bool IsZero(this in Matrix3x2 matrix)
|
||||
{
|
||||
|
|
|
@ -1,235 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Maui.Graphics.Tests
|
||||
{
|
||||
public class AffineTransformTests
|
||||
{
|
||||
public static IEnumerable<object[]> AffineTransformData()
|
||||
{
|
||||
yield return new object[] { 0, 1, 1 };
|
||||
yield return new object[] { 0, -1, -1 };
|
||||
yield return new object[] { 1, 2, 3 };
|
||||
yield return new object[] { -2, 1, 1 };
|
||||
yield return new object[] { -1, 5, 2 };
|
||||
yield return new object[] { -3, 1, -2 };
|
||||
yield return new object[] { 0, -1, 2 };
|
||||
yield return new object[] { 0, 1, -2 };
|
||||
yield return new object[] { 3, -1, -2 };
|
||||
yield return new object[] { 1, -2, -2 };
|
||||
yield return new object[] { -1, -2, -2 };
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestEquivalence()
|
||||
{
|
||||
// the identity matrix should be equivalent to a matrix
|
||||
// with negative scale and rotated 180 degrees
|
||||
|
||||
var a = new AffineTransform();
|
||||
|
||||
var b = new AffineTransform();
|
||||
b.ConcatenateRotationInDegrees(180);
|
||||
b.ConcatenateScale(-1, -1);
|
||||
|
||||
AssertEqual(a, b, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestConcatenationEquivalence()
|
||||
{
|
||||
float x = 5;
|
||||
float y = 7;
|
||||
float r = 1;
|
||||
|
||||
var affine = new AffineTransform();
|
||||
affine.ConcatenateTranslation(x, y);
|
||||
affine.ConcatenateRotation(r);
|
||||
affine.ConcatenateTranslation(-x, -y);
|
||||
var p1 = Vector2.Transform(Vector2.One, (Matrix3x2)affine);
|
||||
|
||||
var matrix = Matrix3x2.Identity;
|
||||
matrix = Matrix3x2.CreateTranslation(x, y) * matrix;
|
||||
matrix = Matrix3x2.CreateRotation(r) * matrix;
|
||||
matrix = Matrix3x2.CreateTranslation(-x, -y) * matrix;
|
||||
var p2 = Vector2.Transform(Vector2.One, matrix);
|
||||
|
||||
Assert.Equal(p1, p2);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AffineTransformData))]
|
||||
public void TestDeconstruct(float rotation, float scaleX, float scaleY)
|
||||
{
|
||||
var translation = new Vector2(5, 9);
|
||||
|
||||
var reference = Matrix3x2.Identity;
|
||||
reference *= Matrix3x2.CreateScale(scaleX, scaleY);
|
||||
reference *= Matrix3x2.CreateRotation(rotation);
|
||||
reference *= Matrix3x2.CreateTranslation(translation);
|
||||
ReferenceDeconstruct(reference, out var refScale, out var refRot, out var refTrans);
|
||||
|
||||
AffineTransform at = reference;
|
||||
at.Deconstruct(out var atTrans, out var atRot, out var atScale);
|
||||
|
||||
AssertEqual(refTrans, atTrans, 5);
|
||||
Assert.Equal(refRot, atRot, 5);
|
||||
AssertEqual(refScale, atScale, 5);
|
||||
|
||||
at = AffineTransform.GetInstance(refTrans, refRot, (SizeF)refScale);
|
||||
AssertEqual(reference, at, 5);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AffineTransformData))]
|
||||
public void TestRotationAndScale(float rotation, float scaleX, float scaleY)
|
||||
{
|
||||
var translation = new Vector2(5, 9);
|
||||
|
||||
var reference = Matrix3x2.Identity;
|
||||
reference *= Matrix3x2.CreateScale(scaleX, scaleY);
|
||||
reference *= Matrix3x2.CreateRotation(rotation);
|
||||
reference *= Matrix3x2.CreateTranslation(translation);
|
||||
|
||||
ReferenceDeconstruct(reference, out var refScale, out var refRot, out var refTrans);
|
||||
|
||||
// implicit conversion
|
||||
|
||||
AffineTransform at = reference;
|
||||
AssertOrthogonal(at, 7);
|
||||
AssertEqual(reference, at, 9);
|
||||
|
||||
// concatenation
|
||||
// notice that AffineTransform concatenation order
|
||||
// is in reverse order compared to Matrix3x2
|
||||
|
||||
at = new AffineTransform();
|
||||
at.ConcatenateTranslation(translation);
|
||||
at.ConcatenateRotation(rotation);
|
||||
at.ConcatenateScale(scaleX, scaleY);
|
||||
AssertOrthogonal(at, 7);
|
||||
AssertEqual(reference, at, 7);
|
||||
|
||||
// concatenation and rotate assign
|
||||
|
||||
|
||||
at = new AffineTransform();
|
||||
at.ConcatenateTranslation(translation);
|
||||
at.ConcatenateScale(scaleX, scaleY);
|
||||
at.Rotation = scaleX >= 0 ? rotation : rotation + (float)Math.PI;
|
||||
AssertOrthogonal(at, 7);
|
||||
AssertEqual(reference, at, 4);
|
||||
|
||||
// concatenation and scale assign
|
||||
|
||||
at = new AffineTransform();
|
||||
at.ConcatenateTranslation(translation);
|
||||
at.ConcatenateRotation(rotation);
|
||||
at.Scale = new SizeF(scaleX, scaleY); // changes scaling without affecting rotation/translation
|
||||
AssertOrthogonal(at, 7);
|
||||
AssertEqual(reference, at, 7);
|
||||
|
||||
// deconstruct/reconstruct
|
||||
|
||||
at = AffineTransform.GetInstance(translation,rotation, new SizeF(scaleX,scaleY) );
|
||||
at.Deconstruct(out var t, out var r, out var s);
|
||||
at = AffineTransform.GetInstance(t, r, s);
|
||||
AssertOrthogonal(at, 7);
|
||||
AssertEqual(reference, at, 5);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AffineTransformData))]
|
||||
public void TestTransformPoint(float rotation, float scaleX, float scaleY)
|
||||
{
|
||||
var translation = new Vector2(5, 9);
|
||||
var point = new Vector2(7, -9);
|
||||
|
||||
// notice the concatenation order of Matrix3x2 and
|
||||
// AffineTransform are defined in reverse order.
|
||||
var reference = Matrix3x2.Identity;
|
||||
reference *= Matrix3x2.CreateScale(scaleX, scaleY);
|
||||
reference *= Matrix3x2.CreateRotation(rotation);
|
||||
reference *= Matrix3x2.CreateTranslation(translation);
|
||||
|
||||
var pointx = Vector2.Transform(point, reference);
|
||||
|
||||
// create a matrix equivalent to reference.
|
||||
|
||||
var at = new AffineTransform();
|
||||
at.ConcatenateTranslation(translation);
|
||||
at.ConcatenateRotation(rotation);
|
||||
at.ConcatenateScale(scaleX, scaleY);
|
||||
AssertEqual(reference, at, 5);
|
||||
AssertEqual(pointx, at.Transform(point), 8);
|
||||
|
||||
// create in one shot
|
||||
|
||||
at = AffineTransform.GetInstance(translation, rotation, new SizeF(scaleX,scaleY));
|
||||
AssertEqual(reference, at, 5);
|
||||
AssertEqual(pointx, at.Transform(point), 8);
|
||||
|
||||
// de/reconstruct T R S for roundtrip
|
||||
|
||||
at.Deconstruct(out var t, out var r, out var s);
|
||||
at = AffineTransform.GetInstance(t, r, s);
|
||||
AssertEqual(reference, at, 5);
|
||||
AssertEqual(pointx, at.Transform(point), 4);
|
||||
}
|
||||
|
||||
private static void AssertEqual(AffineTransform a, AffineTransform b, int precision)
|
||||
{
|
||||
Assert.Equal(a.M11, b.M11, precision);
|
||||
Assert.Equal(a.M21, b.M21, precision);
|
||||
Assert.Equal(a.M31, b.M31, precision);
|
||||
Assert.Equal(a.M12, b.M12, precision);
|
||||
Assert.Equal(a.M22, b.M22, precision);
|
||||
Assert.Equal(a.M32, b.M32, precision);
|
||||
}
|
||||
|
||||
private static void AssertEqual(PointF a, PointF b, int precision)
|
||||
{
|
||||
Assert.Equal(a.X, b.X, precision);
|
||||
Assert.Equal(a.Y, b.Y, precision);
|
||||
}
|
||||
|
||||
private static void AssertEqual(Vector2 a, SizeF b, int precision)
|
||||
{
|
||||
Assert.Equal(a.X, b.Width, precision);
|
||||
Assert.Equal(a.Y, b.Height, precision);
|
||||
}
|
||||
|
||||
private static void AssertOrthogonal(AffineTransform transform, int precission)
|
||||
{
|
||||
var x = Vector2.Normalize(new Vector2(transform.M11, transform.M12));
|
||||
var y = Vector2.Normalize(new Vector2(transform.M21, transform.M22));
|
||||
var d = Vector2.Dot(x, y);
|
||||
if (d < -1) d = -1;
|
||||
if (d > 1) d = 1;
|
||||
|
||||
var skewAngle = (float) ((Math.PI/2) - Math.Acos(d));
|
||||
|
||||
Assert.Equal(0, skewAngle, precission);
|
||||
}
|
||||
|
||||
private static void ReferenceDeconstruct(Matrix3x2 m, out Vector2 scale, out float rotation, out Vector2 translation)
|
||||
{
|
||||
// Matrix3x2 lacks decompose, but Matrix4x4 has it.
|
||||
|
||||
Matrix4x4.Decompose(new Matrix4x4(m), out var s, out Quaternion r, out var t);
|
||||
|
||||
scale = new Vector2(s.X, s.Y);
|
||||
translation = new Vector2(t.X, t.Y);
|
||||
|
||||
var hand = Vector3.Transform(Vector3.UnitX, r);
|
||||
|
||||
rotation = (float)Math.Atan2(hand.Y, hand.X);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,7 +63,7 @@ namespace Microsoft.Maui.Graphics.Tests
|
|||
Assert.Equal(refRot, r, 5);
|
||||
AssertEqual(refTrans, t, 5);
|
||||
|
||||
var m = AffineTransform.CreateMatrix3x2(s, r, t);
|
||||
var m = Matrix3x2Extensions.CreateMatrix3x2(s, r, t);
|
||||
AssertEqual(reference, m, 5);
|
||||
}
|
||||
|
||||
|
@ -111,11 +111,11 @@ namespace Microsoft.Maui.Graphics.Tests
|
|||
|
||||
// deconstruct/reconstruct
|
||||
|
||||
m1 = AffineTransform.CreateMatrix3x2(new Vector2(scaleX, scaleY), rotation, translation);
|
||||
m1 = Matrix3x2Extensions.CreateMatrix3x2(new Vector2(scaleX, scaleY), rotation, translation);
|
||||
var s = m1.GetScale();
|
||||
var r = m1.GetRotation();
|
||||
var t = m1.GetTranslation();
|
||||
m1 = AffineTransform.CreateMatrix3x2(s, r, t);
|
||||
m1 = Matrix3x2Extensions.CreateMatrix3x2(s, r, t);
|
||||
AssertOrthogonal(m1, 7);
|
||||
AssertEqual(reference, m1, 5);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче