Browse Source

Add basic 3d transformation.

pull/7402/head
Andreas Schauerte 5 years ago
committed by Jan-Peter Zurek
parent
commit
c40410cdb3
  1. 2
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  2. 3
      samples/RenderDemo/MainWindow.xaml
  3. 81
      samples/RenderDemo/Pages/Transform3DPage.axaml
  4. 21
      samples/RenderDemo/Pages/Transform3DPage.axaml.cs
  5. 61
      samples/RenderDemo/ViewModels/Transform3DPageViewModel.cs
  6. 2
      src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
  7. 1
      src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
  8. 50
      src/Avalonia.Visuals/Matrix.cs
  9. 192
      src/Avalonia.Visuals/Media/Transform3D.cs
  10. 6
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

2
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
{

3
samples/RenderDemo/MainWindow.xaml

@ -63,5 +63,8 @@
<TabItem Header="Path Measurement">
<pages:PathMeasurementPage />
</TabItem>
<TabItem Header="3D Transformation">
<pages:Transform3DPage />
</TabItem>
</controls:HamburgerMenu>
</Window>

81
samples/RenderDemo/Pages/Transform3DPage.axaml

@ -0,0 +1,81 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.Transform3DPage">
<UserControl.Styles>
<Styles>
<Styles.Resources>
<Template x:Key="Acorn">
<Path Fill="White" Stretch="Uniform"
Data="F1 M 16.6309,18.6563C 17.1309,
8.15625 29.8809,14.1563 29.8809,
14.1563C 30.8809,11.1563 34.1308,
11.4063 34.1308,11.4063C 33.5,12
34.6309,13.1563 34.6309,13.1563C
32.1309,13.1562 31.1309,14.9062
31.1309,14.9062C 41.1309,23.9062
32.6309,27.9063 32.6309,27.9062C
24.6309,24.9063 21.1309,22.1562
16.6309,18.6563 Z M 16.6309,19.9063C
21.6309,24.1563 25.1309,26.1562
31.6309,28.6562C 31.6309,28.6562
26.3809,39.1562 18.3809,36.1563C
18.3809,36.1563 18,38 16.3809,36.9063C
15,36 16.3809,34.9063 16.3809,34.9063C
16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z"/>
</Template>
<Template x:Key="Heart">
<Path Fill="Red" Stretch="Uniform" Data="
M 272.70141,238.71731
C 206.46141,238.71731 152.70146,292.4773 152.70146,358.71731
C 152.70146,493.47282 288.63461,528.80461 381.26391,662.02535
C 468.83815,529.62199 609.82641,489.17075 609.82641,358.71731
C 609.82641,292.47731 556.06651,238.7173 489.82641,238.71731
C 441.77851,238.71731 400.42481,267.08774 381.26391,307.90481
C 362.10311,267.08773 320.74941,238.7173 272.70141,238.71731 z "/>
</Template>
</Styles.Resources>
<Style Selector="Border.Test">
<Setter Property="Margin" Value="15"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Child" Value="{StaticResource Acorn}"/>
</Style>
</Styles>
</UserControl.Styles>
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" ClipToBounds="False">
<StackPanel.Clock>
<Clock />
</StackPanel.Clock>
<StackPanel ClipToBounds="False">
<Border Classes="Test" Background="DarkRed">
<Border.RenderTransform>
<Transform3D RotationY="{Binding RotationY}"
Depth="{Binding Depth}"
RotationX="{Binding RotationX}"
RotationZ="{Binding RotationZ}"
X="{Binding X}"
Y="{Binding Y}"
Z="{Binding Z}" />
</Border.RenderTransform>
</Border>
<TextBlock>X-Rotation:</TextBlock>
<Slider Minimum="0" Maximum="360" Value="{Binding RotationX}" Width="100" TickFrequency="1"></Slider>
<TextBlock>Y-Rotation:</TextBlock>
<Slider Minimum="0" Maximum="360" Value="{Binding RotationY}" Width="100" TickFrequency="1"></Slider>
<TextBlock>Z-Rotation:</TextBlock>
<Slider Minimum="0" Maximum="360" Value="{Binding RotationZ}" Width="100" TickFrequency="1"></Slider>
<TextBlock>X:</TextBlock>
<Slider Minimum="-100" Maximum="100" Value="{Binding X}" Width="100" TickFrequency="1"></Slider>
<TextBlock>Y:</TextBlock>
<Slider Minimum="-100" Maximum="100" Value="{Binding Y}" Width="100" TickFrequency="1"></Slider>
<TextBlock>Z:</TextBlock>
<Slider Minimum="-100" Maximum="100" Value="{Binding Z}" Width="100" TickFrequency="1"></Slider>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>

21
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);
}
}

61
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);
}
}
}

2
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
{

1
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;
}

50
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).
/// </remakrs>
#if !BUILDTASK
public
@ -30,8 +30,7 @@ namespace Avalonia
private readonly double _m31;
private readonly double _m32;
private readonly double _m33;
/// <summary>
/// Initializes a new instance of the <see cref="Matrix"/> struct (equivalent to a 2x3 Matrix without perspective).
/// </summary>
@ -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));
}
/// <summary>
@ -288,7 +290,7 @@ namespace Avalonia
}
/// <summary>
/// Prpends another matrix as pre-multiplication operation.
/// Prepends another matrix as pre-multiplication operation.
/// Equivalent to value * this;
/// </summary>
/// <param name="value">A matrix.</param>
@ -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
/// <returns>The inverted matrix.</returns>
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
/// </summary>
/// <param name="matrix">Matrix to decompose.</param>
/// <param name="decomposed">Decomposed matrix.</param>
/// <returns>The status of the operation.</returns>
/// <returns>The status of the operation.</returns>
public static bool TryDecomposeTransform(Matrix matrix, out Decomposed decomposed)
{
decomposed = default;

192
src/Avalonia.Visuals/Media/Transform3D.cs

@ -0,0 +1,192 @@
using System;
using System.Numerics;
namespace Avalonia.Media;
public class Transform3D : Transform
{
/// <summary>
/// Defines the <see cref="RotationX"/> property.
/// </summary>
public static readonly StyledProperty<double> s_rotationXProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(RotationX));
/// <summary>
/// Defines the <see cref="RotationY"/> property.
/// </summary>
public static readonly StyledProperty<double> s_rotationYProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(RotationY));
/// <summary>
/// Defines the <see cref="RotationZ"/> property.
/// </summary>
public static readonly StyledProperty<double> s_rotationZProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(RotationZ));
/// <summary>
/// Defines the <see cref="Depth"/> property.
/// </summary>
public static readonly StyledProperty<double> s_depthProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(Depth));
/// <summary>
/// Defines the <see cref="CenterX"/> property.
/// </summary>
public static readonly StyledProperty<double> s_centerXProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(CenterX));
/// <summary>
/// Defines the <see cref="CenterY"/> property.
/// </summary>
public static readonly StyledProperty<double> s_centerYProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(CenterY));
/// <summary>
/// Defines the <see cref="CenterY"/> property.
/// </summary>
public static readonly StyledProperty<double> s_xProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(X));
/// <summary>
/// Defines the <see cref="CenterY"/> property.
/// </summary>
public static readonly StyledProperty<double> s_yProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(Y));
/// <summary>
/// Defines the <see cref="CenterY"/> property.
/// </summary>
public static readonly StyledProperty<double> s_zProperty =
AvaloniaProperty.Register<Transform3D, double>(nameof(Z));
/// <summary>
/// Initializes a new instance of the <see cref="Transform3D"/> class.
/// </summary>
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());
}
/// <summary>
/// Initializes a new instance of the <see cref="Transform3D"/> class.
/// </summary>
/// <param name="rotationX">The skew angle of X-axis, in degrees.</param>
/// <param name="rotationY">The skew angle of Y-axis, in degrees.</param>
/// <param name="rotationZ"></param>
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;
}
/// <summary>
/// Gets or sets the X property.
/// </summary>
public double RotationX
{
get => GetValue(s_rotationXProperty);
set => SetValue(s_rotationXProperty, value);
}
/// <summary>
/// Gets or sets the Y property.
/// </summary>
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);
}
/// <summary>
/// Gets the transform's <see cref="Matrix"/>.
/// </summary>
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;
}
}
}

6
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;

Loading…
Cancel
Save