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;