WPF 3D 圆顶创建器
使用 MVVM 的 WPF 3D 圆顶创建器。

引言
我是一个乐高(LEGO)的成年粉丝,在进行我最近的乐高项目时,我需要用乐高积木搭建一个穹顶。在尝试了几个乐高 CAD 应用程序后,我仍然在建造穹顶方面遇到困难。然后我偶然发现了一个乐高 穹顶创建器 应用程序,是一位名叫 Arthur Gugick 的家伙开发的。这个应用程序是用 VB6 开发的,有一些 bug,比如当窗口最小化时,窗体就会被清空。这个应用程序会给出穹顶的高度和每一个螺柱的位置,WPF 3D 穹顶创建器也是这样工作的。然而,这一切都是 2D 的,无法直观地看到穹顶的外观。所以我联系了 Arthur,询问是否有可能发送源代码。他非常乐意提供,我以此为基础开发了 WPF 3D 穹顶创建器。
关于 LDrawPartLib
LDrawPartLib
是我为类似于 MLCad 的乐高 CAD 应用程序开发的,目前还在开发中,但库已经完成。LDrawPartLib
使用 LDraw 零件库来获取零件的几何信息。LDraw
是乐高 CAD 应用程序的一个开源标准。他们有一个非常活跃的社区,并定期更新库中的新零件。我认为在这里讨论 LDrawPartLib
超出了本文的范围。然而,如果你想了解 LDrawPartLib
如何解析和创建 3D 乐高零件,你需要查看代码,其中有相当多的注释和 LDraw
的文档。当然,如果你有非常具体的问题,随时可以联系我。
关于穹顶创建器
我使用这个应用程序来更多地了解 WPF、3D 和著名的 MVVM。这个应用程序对于理解 MVVM 和 WPF 的实现非常有帮助。我欢迎您的评论和改进建议。由于该应用程序只使用一个零件,所有必需的文件都放在“Parts”文件夹中。如果您想使用 LDraw
零件库,您需要从 这里 下载,并在 App.Config 中指定路径。此外,ldconfig.ldr
,它是 LDraw
的一部分,需要放在应用程序路径下,因为它包含了零件颜色的所有详细信息。
应用程序本身非常简单易用。选择您想建造的穹顶类型,设置穹顶的直径和高度等各种参数,当穹顶类型不是球形时,才会考虑高度。您可以通过点击调色板来更改穹顶的颜色。
关注点
首先,它使用了 WPF 3D。我使用 WPF 3D 是因为我想了解它的能力。它是一个很棒的 3D 框架,但我严重怀疑它是否会被用于开发游戏。有很多缺点,一方面,该框架不内置支持创建立方体、球体等基本图形,另一方面,正如我所学的,在 3D 画布上绘制线条可能会非常困难。然而,后来我发现了如何通过使用 3DTools 和 Charles Petzold 的一个库来绘制线条。他还写了一本关于 WPF 3D 的优秀书籍,名为 3D Programming for Windows。前者在实现上有一个 bug,更多信息 在此,所以我没有使用 3DTools 的线条实现。但是,3DTools
的 TrackBall 实现非常棒。后者有一些优秀的特性,但当 3D 场景中带有线条的零件数量增加时,性能会显著下降。如果您想看到带有线条的零件,请取消注释以下行
public class Dome3DViewModel : DomeViewModel
{
...
public void CalculateDomeValues() {
...
int y = (currentHeight - noOfPlates) * (int)ScaleHeight;
for (int i = 0; i < noOfPlates; i++, y += (int)ScaleHeight) {
Point3D pt = new Point3D(x * ScaleWidth, y, z * ScaleWidth);
PartColors color =
(PartColors)Enum.Parse(typeof(PartColors),
MainWindowVM.ColorChooserVM.CurrentColor.Name);
Part3D part =
new Part3D( PLATE_PART_CODE,
PLATE_PART_CODE, color) { Position = pt };
//part.ShowLines = true;
Viewport.Children.Add(part);
}
...
}
}
这里有一个关于 WPF 3D 的优秀教程:在此。
其次,正如我之前提到的,Arthur 的程序会给出特定位置的乐高积木板高度。我本可以插入从“地面”开始的确切积木板数量,但这会创建一个实心穹顶。因此,我决定只添加“可见”的积木板。这是通过查找网格中其八个邻居中高度最低的那个,然后只添加最低高度与当前高度之间的差值来实现的。为了实现这一点,我使用了以下方法
List<int> neighbours = new List<int>();
...
// Fill all the values from the surrounding neighbours.
FillNeighbours(diameter, z, drCurrent, neighbours);
if (drPrev != null) FillNeighbours(dt.Rows.Count, z, drPrev, neighbours);
if (drNext != null) FillNeighbours(dt.Rows.Count, z, drNext, neighbours);
// Gets the lowest values on the top.
neighbours.Sort();
// no of viewable plates.
if (drNext == null || drPrev == null || z == 0 || z == (diameter - 1))
noOfPlates = currentHeight; // if dome edges then show all the plates.
else if (currentHeight == neighbours[0] && currentHeight > 0)
noOfPlates = 1; // If lowest neighbours is of the current height
// then add only one plate.
else // no need the show all the plate. Just show the 'viewable' plates.
noOfPlates = currentHeight - neighbours[0];
第三,我希望能够从一个视图模型访问另一个。为了实现这一点,我创建了一个名为 ApplicationViewModel
的基类。
public abstract class ApplicationViewModel : ViewModelBase
{
#region ctor
protected ApplicationViewModel(MainWindowViewModel mainWindowModel) {
MainWindowVM = mainWindowModel;
}
#endregion
#region Public Properties
public MainWindowViewModel MainWindowVM { get; set; }
#endregion
}
这个类的构造函数将 MainWindowViewModel
作为参数。所有需要在应用程序中相互通信的视图模型都派生自 ApplicationViewModel
。
public class ParametersViewModel : ApplicationViewModel
{
#region ctor
public ParametersViewModel(MainWindowViewModel mainWindowModel)
: base(mainWindowModel) {
DomeDiameter = 12;
DomeHeight = 16;
}
#endregion
...
}
MainWindowViewModel
为这些模型中的每一个都提供了属性。
public class MainWindowViewModel : ViewModelBase
{
#region ctor
public MainWindowViewModel() {
ParametersVM = new ParametersViewModel(this);
Dome2DVM = new Dome2DViewModel(this);
Dome3DVM = new Dome3DViewModel(this);
ColorChooserVM = new ColorChooserViewModel(this);
}
#endregion
#region Public Properties
public ParametersViewModel ParametersVM { get; set; }
public Dome2DViewModel Dome2DVM { get; set; }
public Dome3DViewModel Dome3DVM { get; set; }
public ColorChooserViewModel ColorChooserVM { get; set; }
#endregion
}
结论
作为一个学习者,我希望能知道您对这个应用程序的开发方式有何看法。如果批评具有建设性,那将是很好的。
最后,LEGO 是 LEGO 集团的注册商标,LEGO 集团不赞助、认可或授权本文或本应用程序。请访问官方乐高网站:www.lego.com。
历史
- 2011 年 1 月 14 日:初次发布