我正在将HelixToolkit3D用于一个小型学校项目,该项目需要基于现有的对象列表(自定义创建对象(生成3D对象(立方体(,并且每个立方体面都应该能够拥有自己的图像,该图像将作为字符串属性(图像的路径(到我之前提到的列表中的对象(例如front_image, back_image等(。我正在使用 Wpf,我想使用绑定来生成 3D 元素。搜索后,我找到了这个链接 https://github.com/helix-toolkit/helix-toolkit/tree/develop/Source/Examples/WPF/ExampleBrowser/Examples/DataTemplate 这正是我所需要的,我能够让它工作,但只能使用纯色的立方体。我尝试从图像设置材料,但它不起作用。我还想为每个立方体添加边缘线,例如线框。
这是我到目前为止的代码:
3D 视图 XAML 文件
<Controls:MetroWindow x:Class="Project1._3DView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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"
xmlns:local="clr-namespace:Planom"
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:h="http://helix-toolkit.org/wpf"
Closing="Window_Closing"
Icon="Images/meIcon.ico"
mc:Ignorable="d"
PreviewKeyDown="MetroWindow_PreviewKeyDown"
WindowStartupLocation="CenterScreen" WindowState="Maximized"
Title="Pamja 3-Dimensionale" Height="768" Width="1024">
<Window.Resources>
<local:ColorConverter3D x:Key="colorConverter"/>
<local:DataTemplate3D x:Key="{x:Type local:CubeElement}">
<local:GenericUIElement3D widthX="{Binding Depth}" Material="{Binding Material}" heightZ="{Binding Height}" depthY="{Binding Width}" Color="{Binding color}">
<local:GenericUIElement3D.Transform>
<TranslateTransform3D OffsetX="{Binding Position.X}" OffsetY="{Binding Position.Y}" OffsetZ="{Binding Position.Z}" />
</local:GenericUIElement3D.Transform>
</local:GenericUIElement3D>
</local:DataTemplate3D>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="71*"/>
<RowDefinition Height="666*"/>
</Grid.RowDefinitions>
<h:HelixViewport3D ShowCoordinateSystem="True" ZoomExtentsWhenLoaded="True" ShowFrameRate="True" ShowCameraTarget="True" Grid.RowSpan="2">
<h:SunLight>
<h:SunLight.Transform>
<TranslateTransform3D OffsetX="200" OffsetY="-200" OffsetZ="200" />
</h:SunLight.Transform>
</h:SunLight>
<h:GridLinesVisual3D Center="0,0,0" Width="400" Length="400" MinorDistance="10" MajorDistance="10" Thickness="0.1" Fill="Black"/>
<local:ItemsVisual3D ItemsSource="{Binding ObservableElements}" RefreshChildrenOnChange="True"/>
</h:HelixViewport3D>
</Grid>
文件的 3D 视图.cs
public partial class _3DView : MahApps.Metro.Controls.MetroWindow
{
Shelf currentShelf;
public ObservableCollection<Article> ObservableElements { get; set; }
public _3DView(Shelf currentShelf)
{
InitializeComponent();
this.ObservableElements = new ObservableCollection<CubeElement>();
this.DataContext = this;
this.currentShelf = currentShelf;
foreach (CubeElement a in currentShelf.books)
{
a.Position = new Point3D((a.Depth / 2) - (a.Depth * a.depthF) + currentShelf.Depth / 2 + 1, (a.Width / 2) + a.leftPush - currentShelf.Width / 2, 20);
ObservableElements.Add(a);
}
currentShelf.items3D = ObservableElements;
}
}
立方体元素.cs
public partial class CubeElement : INotifyPropertyChanged
{
public CubeElement()
{
fSize = 12;
changeTracking = false;
drawRatio = 1;
isSelectable = true;
}
public string Id { get; set; }
public string Name { get; set; }
public string left_image { get; set; }
public string front_image { get; set; }
public string back_image { get; set; }
public string right_image { get; set; }
public string top_image { get; set; }
public Shelf shelf{ get; set; }
private double _width { get; set; }
public virtual double Width
{
get { return _width; }
set
{
_width = value;
widthDraw = _width * mainDraw;
OnPropertyChanged("Width");
}
}
private double _height { get; set; }
public virtual double Height
{
get { return _height; }
set
{
_height = value;
heightDraw = _height * mainDraw;
OnPropertyChanged(nameof(Height));
}
}
public virtual double Depth { get; set; }
public double Weight { get; set; }
public string Color
{
get { return _Color; }
set
{
_Color = value;
color = (Color)ColorConverter.ConvertFromString(_Color);
OnPropertyChanged("Color");
}
}
private string _Color { get; set; }
public int _depthF { get; set; }
public int depthF
{
get { return _depthF; }
set
{
_depthF = value;
if (shelf!= null)
{
Position = new Point3D((Depth / 2) - (Depth * _depthF) + shelf.Depth / 2 + 1, (Width / 2) + leftPush - (shelf.Width / 2), 20);
}
OnPropertyChanged("depthF");
}
}
private double _leftPush { get; set; }
public double leftPush
{
get { return _leftPush; }
set
{
_leftPush = value;
leftPushP = value * drawRatioW;
OnPropertyChanged("leftPush");
Position = new Point3D((Depth / 2) - (Depth * depthF) + shelf.Depth / 2 + 1, (Width / 2) + (_leftPush / mainDraw) - (shelf.Width / 2), 20);
}
}
private string _imagePath { get; set; }
[System.ComponentModel.DataAnnotations.Schema.NotMapped]
public string imagePath
{
get { return _imagePath; }
set
{
_imagePath = value; OnPropertyChanged("imagePath");
if(_imagePath != null)
{
Material = MaterialHelper.CreateEmissiveImageMaterial(_imagePath, Brushes.Red, UriKind.Absolute);
}
else
{
Material = MaterialHelper.CreateMaterial(color);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public object this[string propertyName]
{
get { return this.GetType().GetProperty(propertyName).GetValue(this, null); }
set { this.GetType().GetProperty(propertyName).SetValue(this, value, null); }
}
public Model3D Model { get; set; }
public Material Material { get; set; }
public Point3D _position;
public Point3D Position
{
get { return _position; }
set
{
_position = value;
OnPropertyChanged("Position");
}
}
public double Radius { get; set; }
private bool isVisible = true;
public bool IsVisible
{
get { return isVisible; }
set
{
if (IsVisible != value)
{
isVisible = value;
OnPropertyChanged("IsVisible");
}
}
}
private Color _color { get; set; }
public Color color
{
get { return _color; }
set
{
_color = value;
OnPropertyChanged("color");
}
}
}
搁板.cs
public partial class Shelf
{
public string Id { get; set; }
public string Name { get; set; }
private double _width { get; set; }
public virtual double Width
{
get { return _width; }
set
{
_width = value;
OnPropertyChanged("Width");
}
}
private double _height { get; set; }
public virtual double Height
{
get { return _height; }
set
{
_height = value;
OnPropertyChanged("Height");
}
}
public double Depth { get; set; }
public double Weight { get; set; }
public ObservableCollection<CubeElement> books{ get; set; }
}
通用UIElement3D.cs
public class GenericUIElement3D : UIElement3D
{
protected GeometryModel3D Model { get; set; }
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(
nameof(Color), typeof(Color), typeof(GenericUIElement3D), new UIPropertyMetadata((s, e) => ((GenericUIElement3D)s).ColorChanged()));
public static readonly DependencyProperty MaterialProperty = DependencyProperty.Register(
nameof(Material), typeof(Material), typeof(GenericUIElement3D), new PropertyMetadata(null));
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(
nameof(widthX), typeof(double), typeof(GenericUIElement3D), new UIPropertyMetadata((s, e) => ((GenericUIElement3D)s).DimensionsChanged()));
public static readonly DependencyProperty HeightProperty = DependencyProperty.Register(
nameof(heightZ), typeof(double), typeof(GenericUIElement3D), new UIPropertyMetadata((s, e) => ((GenericUIElement3D)s).DimensionsChanged()));
public static readonly DependencyProperty DepthProperty = DependencyProperty.Register(
nameof(depthY), typeof(double), typeof(GenericUIElement3D), new UIPropertyMetadata((s, e) => ((GenericUIElement3D)s).DimensionsChanged()));
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
public double widthX
{
get { return (double)GetValue(WidthProperty); }
set { SetValue(WidthProperty, value); }
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
//MessageBox.Show("OnMouseDown raised. " + e.OriginalSource);
}
public double heightZ
{
get { return (double)GetValue(HeightProperty); }
set { SetValue(HeightProperty, value); }
}
public double depthY
{
get { return (double)GetValue(DepthProperty); }
set { SetValue(DepthProperty, value); }
}
public Material Material
{
get { return (Material)GetValue(MaterialProperty); }
set { SetValue(MaterialProperty, value); }
}
public GenericUIElement3D()
{
Model = new GeometryModel3D();
BindingOperations.SetBinding(Model, GeometryModel3D.MaterialProperty, new Binding(nameof(Material)) { Source = this });
Visual3DModel = Model;
}
private void SetGeometry()
{
MeshBuilder meshBuilder = new MeshBuilder(false, false);
meshBuilder.AddBox(new Point3D(0, 0, heightZ / 2), widthX, depthY, heightZ);
Model.Geometry = meshBuilder.ToMesh();
}
private void ColorChanged()
{
Material = MaterialHelper.CreateMaterial(Color);
}
private void DimensionsChanged()
{
SetGeometry();
}
private void DepthChanged()
{
SetGeometry();
}
}
这个项目是关于一个学校图书馆的,所以会有书和一个书架。代码被缩短,以便在有人尝试实验时更容易(我希望我没有删除任何关键行(。基本上,开始时将有一个2D视图,用于创建书架和添加书籍,然后如果需要,用户可以在并行窗口中切换到3D视图。到目前为止,我真的无法很好地理解3D"可视化树",这就是我需要帮助的原因。
我认为如果你适应它,你应该能够将这段代码用于你的多维数据集。
/// <summary>
/// A visual element that renders a box.
/// </summary>
/// <remarks>
/// The box is aligned with the local X, Y and Z coordinate system
/// Use a transform to orient the box in other directions.
/// </remarks>
public class TextureBoxVisual3D : ModelVisual3D
{
/// <summary>
/// Identifies the <see cref="Center"/> dependency property.
/// </summary>
public static readonly DependencyProperty CenterProperty = DependencyProperty.Register(
"Center", typeof(Point3D), typeof(TextureBoxVisual3D), new UIPropertyMetadata(new Point3D(), GeometryChanged));
/// <summary>
/// Identifies the <see cref="Height"/> dependency property.
/// </summary>
public static readonly DependencyProperty HeightProperty = DependencyProperty.Register(
"Height", typeof(double), typeof(TextureBoxVisual3D), new UIPropertyMetadata(1.0, GeometryChanged));
/// <summary>
/// Identifies the <see cref="Length"/> dependency property.
/// </summary>
public static readonly DependencyProperty LengthProperty = DependencyProperty.Register(
"Length", typeof(double), typeof(TextureBoxVisual3D), new UIPropertyMetadata(1.0, GeometryChanged));
/// <summary>
/// Identifies the <see cref="Width"/> dependency property.
/// </summary>
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(
"Width", typeof(double), typeof(TextureBoxVisual3D), new UIPropertyMetadata(1.0, GeometryChanged));
/// <summary>
/// Identifies the <see cref="Source"/> dependency property.
/// </summary>
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
"Source", typeof(string), typeof(TextureBoxVisual3D), new UIPropertyMetadata(null, GeometryChanged));
/// <summary>
/// The geometry changed.
/// </summary>
/// <param name="d">
/// The d.
/// </param>
/// <param name="e">
/// The event arguments.
/// </param>
private static void GeometryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TextureBoxVisual3D)d).UpdateModel();
}
/// <summary>
/// Gets or sets the center of the box.
/// </summary>
/// <value>The center.</value>
public Point3D Center
{
get
{
return (Point3D)this.GetValue(CenterProperty);
}
set
{
this.SetValue(CenterProperty, value);
}
}
/// <summary>
/// Gets or sets the height (along local z-axis).
/// </summary>
/// <value>The height.</value>
public double Height
{
get
{
return (double)this.GetValue(HeightProperty);
}
set
{
this.SetValue(HeightProperty, value);
}
}
/// <summary>
/// Gets or sets the length of the box (along local x-axis).
/// </summary>
/// <value>The length.</value>
public double Length
{
get
{
return (double)this.GetValue(LengthProperty);
}
set
{
this.SetValue(LengthProperty, value);
}
}
/// <summary>
/// Gets or sets the width of the box (along local y-axis).
/// </summary>
/// <value>The width.</value>
public double Width
{
get
{
return (double)this.GetValue(WidthProperty);
}
set
{
this.SetValue(WidthProperty, value);
}
}
/// <summary>
/// Gets or sets the panorama/skybox directory or file prefix.
/// </summary>
/// <remarks>
/// If a directory is specified, the filename prefix will be set to "cube".
/// If the filename prefix is "cube", the faces of the cube should be named
/// cube_f.jpg
/// cube_b.jpg
/// cube_l.jpg
/// cube_r.jpg
/// cube_u.jpg
/// cube_d.jpg
/// </remarks>
/// <value>The source.</value>
public string Source
{
get
{
return (string)this.GetValue(SourceProperty);
}
set
{
this.SetValue(SourceProperty, value);
}
}
/// <summary>
/// Do the tessellation and return the <see cref="MeshGeometry3D"/>.
/// </summary>
/// <returns>The mesh geometry.</returns>
protected void UpdateModel()
{
if (string.IsNullOrWhiteSpace(this.Source))
{
return;
}
string directory = Path.GetDirectoryName(this.Source);
string prefix = Path.GetFileName(this.Source);
if (string.IsNullOrEmpty(prefix))
{
prefix = "cube";
}
var front = Path.Combine(directory, prefix + "_f.jpg");
var left = Path.Combine(directory, prefix + "_l.jpg");
var right = Path.Combine(directory, prefix + "_r.jpg");
var back = Path.Combine(directory, prefix + "_b.jpg");
var up = Path.Combine(directory, prefix + "_u.jpg");
var down = Path.Combine(directory, prefix + "_d.jpg");
var group = new Model3DGroup();
group.Children.Add(this.AddCubeSide(front, new Vector3D(0, -1, 0), new Vector3D(0, 0, 1), this.Length, this.Width, this.Height));
group.Children.Add(this.AddCubeSide(left, new Vector3D(-1, 0, 0), new Vector3D(0, 0, 1), this.Width, this.Length, this.Height));
group.Children.Add(this.AddCubeSide(right, new Vector3D(1, 0, 0), new Vector3D(0, 0, 1), this.Width, this.Length, this.Height));
group.Children.Add(this.AddCubeSide(back, new Vector3D(0, 1, 0), new Vector3D(0, 0, 1), this.Length, this.Width, this.Height));
group.Children.Add(this.AddCubeSide(up, new Vector3D(0, 0, 1), new Vector3D(0, -1, 0), this.Height, this.Width, this.Length));
group.Children.Add(this.AddCubeSide(down, new Vector3D(0, 0, -1), new Vector3D(0, 1, 0), this.Height, this.Width, this.Length));
this.Content = group;
}
private static Dictionary<string, Material> _materialDict = new Dictionary<string, Material>();
/// <summary>
/// The add cube side.
/// </summary>
/// <param name="normal">
/// The normal.
/// </param>
/// <param name="up">
/// The up.
/// </param>
/// <param name="fileName">
/// The file name.
/// </param>
/// <returns>
/// </returns>
private GeometryModel3D AddCubeSide(string fileName, Vector3D normal, Vector3D up, double dist, double width, double height)
{
string fullPath = Path.GetFullPath(fileName);
if (!File.Exists(fullPath))
{
return null;
}
Material material = null;
if (_materialDict.ContainsKey(fileName))
{
material = _materialDict[fileName];
}
else
{
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(fullPath);
image.EndInit();
var brush = new ImageBrush(image);
material = new DiffuseMaterial(brush);
_materialDict.Add(fileName, material);
}
var mesh = new MeshGeometry3D();
var right = Vector3D.CrossProduct(normal, up);
var origin = Center;
var n = normal * dist / 2;
up *= height / 2;
right *= width / 2;
// p1 is the lower left corner
// p2 is the upper left
// p3 is the lower right
// p4 is the upper right
var p1 = origin + n - up - right;
var p2 = origin + n + up - right;
var p3 = origin + n - up + right;
var p4 = origin + n + up + right;
mesh.Positions.Add(p1);
mesh.Positions.Add(p2);
mesh.Positions.Add(p3);
mesh.Positions.Add(p4);
//doublesided
mesh.Positions.Add(p1); // 4
mesh.Positions.Add(p2); // 5
mesh.Positions.Add(p3); // 6
mesh.Positions.Add(p4); // 7
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(3);
mesh.TriangleIndices.Add(1);
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(2);
mesh.TriangleIndices.Add(3);
//doublesided
mesh.TriangleIndices.Add(4);
mesh.TriangleIndices.Add(5);
mesh.TriangleIndices.Add(7);
mesh.TriangleIndices.Add(4);
mesh.TriangleIndices.Add(7);
mesh.TriangleIndices.Add(6);
mesh.TextureCoordinates.Add(new Point(0, 1));
mesh.TextureCoordinates.Add(new Point(0, 0));
mesh.TextureCoordinates.Add(new Point(1, 1));
mesh.TextureCoordinates.Add(new Point(1, 0));
//doublesided
mesh.TextureCoordinates.Add(new Point(1, 1));
mesh.TextureCoordinates.Add(new Point(1, 0));
mesh.TextureCoordinates.Add(new Point(0, 1));
mesh.TextureCoordinates.Add(new Point(0, 0));
return new GeometryModel3D(mesh, material);
}
}