我想移植一个旧应用程序以在 .net maui 上运行以实现跨平台。
在 UI 中,我还需要一个 3d 视图,在其中我在 3d 空间中绘制一些线条和曲面。这里只有用户交互是旋转和平移视图以及缩放。数据本身是模拟结果,没有导入3D模型。
模型以非常低的更新率(30秒)更新。
我在 .net maui 中没有找到此类功能:是否有跨平台包?
如果不是,除了 maui 之外,是否还有其他跨平台 ui 方法适用于此范围?后端将是 C#
GraphicsView
上的 Microsoft Learn .NET MAUI 页面解释了如何将 GraphicsView
与自定义的 IDrawable
一起使用。通过 IDrawable::Draw(ICanvas canvas, RectF dirtyRect
,您可以直接用基本的线条和多边形填充 GraphicsView
。
更进一步,您可以获得
System.Numerics
NuGet 包,其中包含可用于操作 3D 坐标的矢量和矩阵运算。
对于简单的 3D 几何形状,其中面涂有纯色填充颜色,这里有一些类:
// Face.cs
namespace Toolkit.Maui.Shapes3D;
public class Face
{
public Color Color { get; set; } = Colors.Red;
public IList<int> Vertices { get; set; }
}
// Shape3D.cs
using System.Numerics;
namespace Toolkit.Maui.Shapes3D;
public enum RenderMode
{
Solid,
Wireframe
};
public class Shape3D
{
public IList<Vector3> Points { get; set; }
public IList<Face> Faces { get; set; }
public Matrix4x4 Transform { get; set; } = Matrix4x4.Identity;
public float[] HiddenFaceDashPattern { get; set; } = new float[] { 2, 10};
public Point Project(Vector3 pt, Point Center)
{
Vector3 pt2 = Vector3.Transform(pt, Transform);
return new Point(
Math.Round(Center.X + pt2.X / (1 - pt2.Z / 800)),
Math.Round(Center.Y + pt2.Y / (1 - pt2.Z / 800))
);
}
public void Draw(ICanvas canvas, RectF dirtyRect, RenderMode Mode = RenderMode.Solid)
{
if (Points == null)
{
return;
}
if (Faces == null)
{
return;
}
List<Point> pts = new List<Point>(Points.Select(pt => Project(pt, dirtyRect.Center)));
foreach (var f in Faces)
{
Point pt0 = pts[f.Vertices[0]];
Point pt1 = pts[f.Vertices[1]];
Point pt2 = pts[f.Vertices[2]];
Point v1 = new Point(pt1.X - pt0.X, pt1.Y - pt0.Y);
Point v2 = new Point(pt2.X - pt1.X, pt2.Y - pt1.Y);
double vz = v1.X * v2.Y - v1.Y * v2.X;
bool HiddenFace = (vz <= 0);
if (Mode == RenderMode.Solid)
{
if (f.Color == Colors.Transparent) continue;
if (HiddenFace) continue;
}
PathF path = new PathF();
path.MoveTo((float)pts[f.Vertices[0]].X, (float)pts[f.Vertices[0]].Y);
for (int i = 1; i < f.Vertices.Count; i++)
{
path.LineTo((float)pts[f.Vertices[i]].X, (float)pts[f.Vertices[i]].Y);
}
path.Close();
if (Mode == RenderMode.Solid)
{
canvas.FillColor = f.Color;
canvas.FillPath(path);
}
if (Mode == RenderMode.Wireframe)
{
canvas.StrokeColor = f.Color;
canvas.StrokeSize = 1;
canvas.StrokeDashPattern = HiddenFace ? HiddenFaceDashPattern : null;
canvas.DrawPath(path);
}
}
}
}
这是填充这些类的示例 ViewModel:
// MainViewModel.cs
using System.ComponentModel;
using System.Numerics;
using System.Windows.Input;
namespace Toolkit.Maui.Shapes3D.Sample;
public class MainViewModel : INotifyPropertyChanged
{
public bool Wireframe { get; set; } = true;
public RenderMode Mode => Wireframe ? RenderMode.Wireframe : RenderMode.Solid;
public Shape3D Cube = new Shape3D()
{
Points = new List<Vector3>()
{
new Vector3() { X = -100, Y = -100, Z = -100 },
new Vector3() { X = 100, Y = -100, Z = -100 },
new Vector3() { X = -100, Y = 100, Z = -100 },
new Vector3() { X = 100, Y = 100, Z = -100 },
new Vector3() { X = -100, Y = -100, Z = 100 },
new Vector3() { X = 100, Y = -100, Z = 100 },
new Vector3() { X = -100, Y = 100, Z = 100 },
new Vector3() { X = 100, Y = 100, Z = 100 }
},
Faces = new List<Face>()
{
new Face() { Vertices = new List<int> {1,0,2,3}, Color = Colors.Red },
new Face() { Vertices = new List<int> {4,5,7,6}, Color = Colors.Orange },
new Face() { Vertices = new List<int> {2,0,4,6}, Color = Colors.Yellow },
new Face() { Vertices = new List<int> {1,3,7,5}, Color = Colors.White },
new Face() { Vertices = new List<int> {0,1,5,4}, Color = Colors.Blue },
new Face() { Vertices = new List<int> {3,2,6,7}, Color = Colors.Green }
}
};
private IDispatcherTimer _timer;
public EventHandler Frame;
public Matrix4x4 Delta { get; set; } =
Matrix4x4.Multiply(
Matrix4x4.CreateRotationX((float)(0.3 * Math.PI / 180)),
Matrix4x4.CreateRotationY((float)(0.5 * Math.PI / 180))
);
public MainViewModel()
{
ToggleRenderModeCommand = new Command(() => ToggleRenderMode());
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ToggleRenderModeCommand)));
_timer = App.Current.Dispatcher.CreateTimer();
_timer.Interval = TimeSpan.FromMilliseconds(10);
_timer.Tick += (s, e) =>
{
Cube.Transform = Matrix4x4.Multiply(Cube.Transform, Delta);
Frame?.Invoke(this, EventArgs.Empty);
};
_timer.Start();
}
public ICommand ToggleRenderModeCommand { get; set; }
public void ToggleRenderMode()
{
Wireframe = !Wireframe;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Wireframe)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Mode)));
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainViewModel
被声明为单例,因此 MainPage
应用程序可以使用它,并且您的 MainDrawable
可以从中渲染。
// MainDrawable.cs
using Microsoft.Maui.Graphics;
namespace Toolkit.Maui.Shapes3D.Sample;
public class MainDrawable : IDrawable
{
private MainViewModel _vm;
private MainViewModel VM
=> _vm ??= ServiceHelper.GetService<MainViewModel>();
public void Draw(ICanvas canvas, RectF dirtyRect)
{
VM.Cube.Draw(canvas, dirtyRect, VM.Mode);
}
}
<!-- MainPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Toolkit.Maui.Shapes3D.Sample"
x:Class="Toolkit.Maui.Shapes3D.Sample.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:MainDrawable x:Key="drawable" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid RowDefinitions="*,Auto">
<GraphicsView
x:Name="graphicsView"
Drawable="{StaticResource drawable}"
BackgroundColor="Transparent"/>
<Button
Grid.Row="1"
Text="Toggle Render Mode"
Command="{Binding ToggleRenderModeCommand}"
HorizontalOptions="Center" />
</Grid>
</ContentPage>
可以在此处找到上述内容的完整工作示例:https://github.com/stephenquan/Toolkit.Maui.Shapes3D