视频: WPF helix-toolkit 官方3DCoreWpfDemo_哔哩哔哩_bilibili
源码: WpfDemo/3DCoreWpfDemo at master · liugang198409/WpfDemo · GitHub
目录
1. 环境
Visual Studio 2019 + .NET Framework 4.8.1
2. NuGet导入依赖
HelixToolkit
HelixToolkit.wpf
3. 创建类LegoVisual3D.cs
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LegoVisual3D.cs" company="Helix Toolkit">
// Copyright (c) 2014 Helix Toolkit contributors
// </copyright>
// <summary>
// Traditional Lego bricks.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using HelixToolkit.Wpf;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media.Media3D;
namespace _3DCoreWpfDemo
{
/// <summary>
/// Traditional Lego bricks.
/// </summary>
public class LegoVisual3D : MeshElement3D
{
private const double grid = 0.008;
private const double margin = 0.0001;
private const double wallThickness = 0.001;
private const double plateThickness = 0.0032;
private const double brickThickness = 0.0096;
private const double knobHeight = 0.0018;
private const double knobDiameter = 0.0048;
private const double outerDiameter = 0.00651;
private const double axleDiameter = 0.00475;
private const double holeDiameter = 0.00485;
public static readonly DependencyProperty HeightProperty =
DependencyProperty.Register("Height", typeof(int), typeof(LegoVisual3D),
new UIPropertyMetadata(3, GeometryChanged));
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(int), typeof(LegoVisual3D),
new UIPropertyMetadata(2, GeometryChanged));
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(int), typeof(LegoVisual3D),
new UIPropertyMetadata(6, GeometryChanged));
public int Divisions
{
get { return (int)GetValue(DivisionsProperty); }
set { SetValue(DivisionsProperty, value); }
}
public static readonly DependencyProperty DivisionsProperty =
DependencyProperty.Register("Divisions", typeof(int), typeof(LegoVisual3D), new UIPropertyMetadata(12));
[Category("Lego attributes")]
public int Height
{
get { return (int)GetValue(HeightProperty); }
set { SetValue(HeightProperty, value); }
}
public int Rows
{
get { return (int)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
public int Columns
{
get { return (int)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
// http://www.robertcailliau.eu/Lego/Dimensions/zMeasurements-en.xhtml
public static double GridUnit
{
get { return grid; }
}
public static double HeightUnit
{
get { return plateThickness; }
}
protected override MeshGeometry3D Tessellate()
{
double width = Columns * grid - margin * 2;
double length = Rows * grid - margin * 2;
double height = Height * plateThickness;
var builder = new MeshBuilder(true, true);
for (int i = 0; i < Columns; i++)
for (int j = 0; j < Rows; j++)
{
var o = new Point3D((i + 0.5) * grid, (j + 0.5) * grid, height);
builder.AddCone(o, new Vector3D(0, 0, 1), knobDiameter / 2, knobDiameter / 2, knobHeight, false, true,
Divisions);
builder.AddPipe(new Point3D(o.X, o.Y, o.Z - wallThickness), new Point3D(o.X, o.Y, wallThickness),
knobDiameter, outerDiameter, Divisions);
}
builder.AddBox(new Point3D(Columns * 0.5 * grid, Rows * 0.5 * grid, height - wallThickness / 2), width, length,
wallThickness,
BoxFaces.All);
builder.AddBox(new Point3D(margin + wallThickness / 2, Rows * 0.5 * grid, height / 2 - wallThickness / 2),
wallThickness, length, height - wallThickness,
BoxFaces.All ^ BoxFaces.Top);
builder.AddBox(
new Point3D(Columns * grid - margin - wallThickness / 2, Rows * 0.5 * grid, height / 2 - wallThickness / 2),
wallThickness, length, height - wallThickness,
BoxFaces.All ^ BoxFaces.Top);
builder.AddBox(new Point3D(Columns * 0.5 * grid, margin + wallThickness / 2, height / 2 - wallThickness / 2),
width, wallThickness, height - wallThickness,
BoxFaces.All ^ BoxFaces.Top);
builder.AddBox(
new Point3D(Columns * 0.5 * grid, Rows * grid - margin - wallThickness / 2, height / 2 - wallThickness / 2),
width, wallThickness, height - wallThickness,
BoxFaces.All ^ BoxFaces.Top);
return builder.ToMesh();
}
}
}
4.编辑MainWindow.xaml.cs
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MainWindow.xaml.cs" company="Helix Toolkit">
// Copyright (c) 2014 Helix Toolkit contributors
// </copyright>
// <summary>
// Interaction logic for MainWindow.xaml
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace _3DCoreWpfDemo
{
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using HelixToolkit.Wpf;
using Microsoft.Win32;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// http://guide.lugnet.com/color/
private static readonly Brush[] brushes = new[]
{
Brushes.Red, Brushes.Green, Brushes.Blue, Brushes.Yellow, Brushes.White,
Brushes.Gray, Brushes.Black, BrushHelper.ChangeOpacity(Brushes.Red, 0.3)
};
public static readonly DependencyProperty BrickRowsProperty =
DependencyProperty.Register("BrickRows", typeof(int), typeof(MainWindow), new UIPropertyMetadata(2));
public static readonly DependencyProperty BrickColumnsProperty =
DependencyProperty.Register("BrickColumns", typeof(int), typeof(MainWindow), new UIPropertyMetadata(4));
public static readonly DependencyProperty BrickHeightProperty =
DependencyProperty.Register("BrickHeight", typeof(int), typeof(MainWindow), new UIPropertyMetadata(3));
public static readonly DependencyProperty CurrentBrushProperty =
DependencyProperty.Register("CurrentBrush", typeof(Brush), typeof(MainWindow),
new UIPropertyMetadata(brushes[0]));
private int currentBrushIndex;
public MainWindow()
{
InitializeComponent();
DataContext = this;
Loaded += MainWindow_Loaded;
}
public int BrickRows
{
get { return (int)GetValue(BrickRowsProperty); }
set { SetValue(BrickRowsProperty, value); }
}
public int BrickColumns
{
get { return (int)GetValue(BrickColumnsProperty); }
set { SetValue(BrickColumnsProperty, value); }
}
public int BrickHeight
{
get { return (int)GetValue(BrickHeightProperty); }
set { SetValue(BrickHeightProperty, value); }
}
public Brush CurrentBrush
{
get { return (Brush)GetValue(CurrentBrushProperty); }
set { SetValue(CurrentBrushProperty, value); }
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
view1.Camera.NearPlaneDistance = LegoVisual3D.GridUnit;
view1.Camera.FarPlaneDistance = 10;
view1.ZoomExtents();
}
private void view1_MouseDown(object sender, MouseButtonEventArgs e)
{
var lego = new LegoVisual3D();
lego.Rows = BrickRows;
lego.Columns = BrickColumns;
lego.Height = BrickHeight;
lego.Fill = CurrentBrush;
Point3D? pt = view1.FindNearestPoint(e.GetPosition(view1));
if (pt.HasValue)
{
Point3D p = pt.Value;
double gu = LegoVisual3D.GridUnit;
double hu = LegoVisual3D.HeightUnit;
p.X = gu * Math.Floor(p.X / gu);
p.Y = gu * Math.Floor(p.Y / gu);
p.Z = hu * Math.Floor(p.Z / hu);
lego.Transform = new TranslateTransform3D(p.X, p.Y, p.Z);
view1.Children.Add(lego);
}
}
private void CurrentColor_Click(object sender, RoutedEventArgs routedEventArgs)
{
currentBrushIndex++;
CurrentBrush = brushes[currentBrushIndex % brushes.Length];
}
private void Export_Click(object sender, RoutedEventArgs e)
{
var d = new SaveFileDialog();
d.Filter = Exporters.Filter;
if (d.ShowDialog().Value)
{
Export(d.FileName);
}
}
private void Export(string fileName)
{
if (fileName != null)
view1.Export(fileName);
}
private void Exit_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
}
5. 编辑MainWindow.xaml
<Window x:Class="_3DCoreWpfDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="http://helix-toolkit.org/wpf"
xmlns:local="clr-namespace:_3DCoreWpfDemo" Title="LegoDemo"
Height="480" Width="640">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Export..." Click="Export_Click"/>
<Separator/>
<MenuItem Header="E_xit" Click="Exit_Click"/>
</MenuItem>
</Menu>
<Grid>
<t:HelixViewport3D x:Name="view1" Background="Black" MouseDown="view1_MouseDown" ZoomExtentsWhenLoaded="True">
<!--<t:HelixViewport3D.DefaultCamera>
<PerspectiveCamera Position="-7,7,10" LookDirection="7,-7,-10" UpDirection="0,0,1" NearPlaneDistance="0.0001" FarPlaneDistance="50"/>
</t:HelixViewport3D.DefaultCamera>-->
<t:DefaultLights/>
<!--<t:PlaneVisual3D Fill="Green" Width="0.2" Length="0.2"/>-->
<local:LegoVisual3D Divisions="9" Rows="40" Columns="40" Height="1" Fill="Green"/>
<t:RectangleVisual3D Fill="{t:ImageBrush 'pack://application:,,,/lego.png'}" Width="0.02" Length="0.02" Normal="0,1,0" Origin="0.01,-0.0,0.013" LengthDirection="0,0,-1"/>
</t:HelixViewport3D>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
<TextBox ToolTip="Rows" Text="{Binding BrickRows, UpdateSourceTrigger=PropertyChanged}" Width="30" Margin="2"/>
<TextBox ToolTip="Columns" Text="{Binding BrickColumns, UpdateSourceTrigger=PropertyChanged}" Width="30" Margin="2"/>
<TextBox ToolTip="Height" Text="{Binding BrickHeight, UpdateSourceTrigger=PropertyChanged}" Width="30" Margin="2"/>
<Button Click="CurrentColor_Click" Margin="2" Width="30">
<Button.Content>
<Rectangle Width="16" Height="16" Fill="{Binding CurrentBrush}"/>
</Button.Content>
</Button>
</StackPanel>
</Grid>
</DockPanel>
</Window>