diff --git a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs
index dccc3f7159..b1ca548e2c 100644
--- a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs
+++ b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs
@@ -14,7 +14,7 @@ namespace ControlCatalog.Android
{
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index a4c6299278..2b5bba928a 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -63,5 +63,8 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml
new file mode 100644
index 0000000000..ebd838eb67
--- /dev/null
+++ b/samples/RenderDemo/Pages/Transform3DPage.axaml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ X-Rotation:
+
+ Y-Rotation:
+
+ Z-Rotation:
+
+ X:
+
+ Y:
+
+ Z:
+
+
+
+
+
+
diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml.cs b/samples/RenderDemo/Pages/Transform3DPage.axaml.cs
new file mode 100644
index 0000000000..5083189c4c
--- /dev/null
+++ b/samples/RenderDemo/Pages/Transform3DPage.axaml.cs
@@ -0,0 +1,21 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using RenderDemo.ViewModels;
+
+namespace RenderDemo.Pages;
+
+public class Transform3DPage : UserControl
+{
+ public Transform3DPage()
+ {
+ InitializeComponent();
+ this.DataContext = new Transform3DPageViewModel();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+}
+
diff --git a/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs
new file mode 100644
index 0000000000..7e7849bd01
--- /dev/null
+++ b/samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs
@@ -0,0 +1,61 @@
+using System;
+using MiniMvvm;
+using Avalonia.Animation;
+
+namespace RenderDemo.ViewModels
+{
+ public class Transform3DPageViewModel : ViewModelBase
+ {
+ private double _roationX = 0;
+ private double _rotationY = 0;
+ private double _rotationZ = 0;
+
+ private double _x = 0;
+ private double _y = 0;
+ private double _z = 0;
+
+ private double _depth = 200;
+
+ public double RoationX
+ {
+ get => _roationX;
+ set => RaiseAndSetIfChanged(ref _roationX, value);
+ }
+
+ public double RotationY
+ {
+ get => _rotationY;
+ set => RaiseAndSetIfChanged(ref _rotationY, value);
+ }
+
+ public double RotationZ
+ {
+ get => _rotationZ;
+ set => RaiseAndSetIfChanged(ref _rotationZ, value);
+ }
+
+ public double Depth
+ {
+ get => _depth;
+ set => RaiseAndSetIfChanged(ref _depth, value);
+ }
+
+ public double X
+ {
+ get => _x;
+ set => RaiseAndSetIfChanged(ref _x, value);
+ }
+
+ public double Y
+ {
+ get => _y;
+ set => RaiseAndSetIfChanged(ref _y, value);
+ }
+
+ public double Z
+ {
+ get => _z;
+ set => RaiseAndSetIfChanged(ref _z, value);
+ }
+ }
+}
diff --git a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
index 87fd47df25..83db67fcee 100644
--- a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
+++ b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
@@ -14,7 +14,7 @@ namespace Avalonia.AndroidTestApplication
{
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
index 34ec8ac503..a98f2ec65e 100644
--- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
+++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
@@ -43,6 +43,7 @@ namespace Avalonia.Animation.Animators
normalTransform.Children.Add(new SkewTransform());
normalTransform.Children.Add(new RotateTransform());
normalTransform.Children.Add(new TranslateTransform());
+ normalTransform.Children.Add(new Transform3D());
ctrl.RenderTransform = normalTransform;
}
diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs
index 9dd5bced86..e949d728d0 100644
--- a/src/Avalonia.Visuals/Matrix.cs
+++ b/src/Avalonia.Visuals/Matrix.cs
@@ -14,7 +14,7 @@ namespace Avalonia
/// 2nd row | skrewX | scaleY | persY |
/// 3rd row | transX | transY | persZ |
///
- /// Note: Skia.SkMatrix uses a transposed layout (where for example skrewX/skrewY and persX/transX are swapped).
+ /// Note: Skia.SkMatrix uses a transposed layout (where for example skrewX/skrewY and perspp0/tranX are swapped).
///
#if !BUILDTASK
public
@@ -30,8 +30,7 @@ namespace Avalonia
private readonly double _m31;
private readonly double _m32;
private readonly double _m33;
-
-
+
///
/// Initializes a new instance of the struct (equivalent to a 2x3 Matrix without perspective).
///
@@ -159,12 +158,15 @@ namespace Avalonia
public static Matrix operator *(Matrix value1, Matrix value2)
{
return new Matrix(
- (value1.M11 * value2.M11) + (value1.M12 * value2.M21),
- (value1.M11 * value2.M12) + (value1.M12 * value2.M22),
- (value1.M21 * value2.M11) + (value1.M22 * value2.M21),
- (value1.M21 * value2.M12) + (value1.M22 * value2.M22),
- (value1._m31 * value2.M11) + (value1._m32 * value2.M21) + value2._m31,
- (value1._m31 * value2.M12) + (value1._m32 * value2.M22) + value2._m32); //TODO: include perspective
+ (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31),
+ (value1.M11 * value2.M12) + (value1.M12 * value2.M22) + (value1.M13 * value2.M32),
+ (value1.M11 * value2.M13) + (value1.M12 * value2.M23) + (value1.M13 * value2.M33),
+ (value1.M21 * value2.M11) + (value1.M22 * value2.M21) + (value1.M23 * value2.M31),
+ (value1.M21 * value2.M12) + (value1.M22 * value2.M22) + (value1.M23 * value2.M32),
+ (value1.M21 * value2.M13) + (value1.M22 * value2.M23) + (value1.M23 * value2.M33),
+ (value1.M31 * value2.M11) + (value1.M32 * value2.M21) + (value1.M33 * value2.M31),
+ (value1.M31 * value2.M12) + (value1.M32 * value2.M22) + (value1.M33 * value2.M32),
+ (value1.M31 * value2.M13) + (value1.M32 * value2.M23) + (value1.M33 * value2.M33));
}
///
@@ -288,7 +290,7 @@ namespace Avalonia
}
///
- /// Prpends another matrix as pre-multiplication operation.
+ /// Prepends another matrix as pre-multiplication operation.
/// Equivalent to value * this;
///
/// A matrix.
@@ -359,7 +361,7 @@ namespace Avalonia
{
// ReSharper disable CompareOfFloatsByEqualityOperator
- return _m13 != 0 || _m23 != 0 || _m33 != 1;
+ return _m31 != 0 || _m32 != 0 || _m33 != 1;
// ReSharper restore CompareOfFloatsByEqualityOperator
}
@@ -399,21 +401,27 @@ namespace Avalonia
{
double d = GetDeterminant();
- if (MathUtilities.IsZero(d)) //TODO: decide if special handling is required for perspective
+ if (MathUtilities.IsZero(d))
{
inverted = default;
return false;
}
+ var invdet = 1 / d;
+
inverted = new Matrix(
- _m22 / d,
- -_m12 / d,
- -_m21 / d,
- _m11 / d,
- ((_m21 * _m32) - (_m22 * _m31)) / d,
- ((_m12 * _m31) - (_m11 * _m32)) / d);
-
+ (_m22 * _m33 - _m32 * _m23) * invdet,
+ (_m13 * _m31 - _m12 * _m33) * invdet,
+ (_m12 * _m23 - _m13 * _m22) * invdet,
+ (_m23 * _m31 - _m21 * _m33) * invdet,
+ (_m11 * _m33 - _m13 * _m31) * invdet,
+ (_m21 * _m13 - _m11 * _m23) * invdet,
+ (_m21 * _m32 - _m31 * _m22) * invdet,
+ (_m21 * _m12 - _m11 * _m32) * invdet,
+ (_m11 * _m22 - _m21 * _m12) * invdet
+ );
+
return true;
}
@@ -424,7 +432,7 @@ namespace Avalonia
/// The inverted matrix.
public Matrix Invert()
{
- if (!TryInvert(out Matrix inverted))
+ if (!TryInvert(out var inverted))
{
throw new InvalidOperationException("Transform is not invertible.");
}
@@ -467,7 +475,7 @@ namespace Avalonia
///
/// Matrix to decompose.
/// Decomposed matrix.
- /// The status of the operation.
+ /// The status of the operation.
public static bool TryDecomposeTransform(Matrix matrix, out Decomposed decomposed)
{
decomposed = default;
diff --git a/src/Avalonia.Visuals/Media/Transform3D.cs b/src/Avalonia.Visuals/Media/Transform3D.cs
new file mode 100644
index 0000000000..e063f76556
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Transform3D.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Numerics;
+
+namespace Avalonia.Media;
+
+public class Transform3D : Transform
+{
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_rotationXProperty =
+ AvaloniaProperty.Register(nameof(RotationX));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_rotationYProperty =
+ AvaloniaProperty.Register(nameof(RotationY));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_rotationZProperty =
+ AvaloniaProperty.Register(nameof(RotationZ));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_depthProperty =
+ AvaloniaProperty.Register(nameof(Depth));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_centerXProperty =
+ AvaloniaProperty.Register(nameof(CenterX));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_centerYProperty =
+ AvaloniaProperty.Register(nameof(CenterY));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_xProperty =
+ AvaloniaProperty.Register(nameof(X));
+
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_yProperty =
+ AvaloniaProperty.Register(nameof(Y));
+
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty s_zProperty =
+ AvaloniaProperty.Register(nameof(Z));
+
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Transform3D()
+ {
+ this.GetObservable(s_rotationXProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(s_rotationYProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(s_rotationZProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(s_depthProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(s_centerXProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(s_centerYProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(s_xProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(s_yProperty).Subscribe(_ => RaiseChanged());
+ this.GetObservable(s_zProperty).Subscribe(_ => RaiseChanged());
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The skew angle of X-axis, in degrees.
+ /// The skew angle of Y-axis, in degrees.
+ ///
+ public Transform3D(
+ double rotationX,
+ double rotationY,
+ double rotationZ,
+ double depth,
+ double centerX,
+ double centerY) : this()
+ {
+ RotationX = rotationX;
+ RotationY = rotationY;
+ RotationZ = rotationZ;
+ Depth = depth;
+ CenterX = centerX;
+ CenterY = centerY;
+ }
+
+ ///
+ /// Gets or sets the X property.
+ ///
+ public double RotationX
+ {
+ get => GetValue(s_rotationXProperty);
+ set => SetValue(s_rotationXProperty, value);
+ }
+
+ ///
+ /// Gets or sets the Y property.
+ ///
+ public double RotationY
+ {
+ get => GetValue(s_rotationYProperty);
+ set => SetValue(s_rotationYProperty, value);
+ }
+
+ public double RotationZ
+ {
+ get => GetValue(s_rotationZProperty);
+ set => SetValue(s_rotationZProperty, value);
+ }
+
+ public double Depth
+ {
+ get => GetValue(s_depthProperty);
+ set => SetValue(s_depthProperty, value);
+ }
+
+ public double CenterX
+ {
+ get => GetValue(s_centerXProperty);
+ set => SetValue(s_centerXProperty, value);
+ }
+
+ public double CenterY
+ {
+ get => GetValue(s_centerYProperty);
+ set => SetValue(s_centerYProperty, value);
+ }
+
+ public double X
+ {
+ get => GetValue(s_xProperty);
+ set => SetValue(s_xProperty, value);
+ }
+
+ public double Y
+ {
+ get => GetValue(s_yProperty);
+ set => SetValue(s_yProperty, value);
+ }
+
+ public double Z
+ {
+ get => GetValue(s_zProperty);
+ set => SetValue(s_zProperty, value);
+ }
+
+ ///
+ /// Gets the transform's .
+ ///
+ public override Matrix Value
+ {
+ get
+ {
+ var matrix44 = Matrix4x4.Identity;
+
+ matrix44 *= Matrix4x4.CreateTranslation((float)X, (float)Y, (float)Z);
+
+ matrix44 *= Matrix4x4.CreateRotationX((float)Matrix.ToRadians(RotationX));
+ matrix44 *= Matrix4x4.CreateRotationY((float)Matrix.ToRadians(RotationY));
+ matrix44 *= Matrix4x4.CreateRotationZ((float)Matrix.ToRadians(RotationZ));
+
+ var matrix = new Matrix(
+ matrix44.M11,
+ matrix44.M12,
+ matrix44.M14,
+ matrix44.M21,
+ matrix44.M22,
+ matrix44.M24,
+ matrix44.M41,
+ matrix44.M42,
+ matrix44.M44);
+
+ return matrix;
+ }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
index 75b4231640..c0afc8bd9c 100644
--- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
+++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
@@ -103,9 +103,9 @@ namespace Avalonia.Skia
SkewY = (float)m.M12,
ScaleY = (float)m.M22,
TransY = (float)m.M32,
- Persp0 = 0,
- Persp1 = 0,
- Persp2 = 1
+ Persp0 = (float)m.M13,
+ Persp1 = (float)m.M23,
+ Persp2 = (float)m.M33
};
return sm;