65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 3D 曲面图控件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (20投票s)

2021 年 2 月 19 日

CPOL

6分钟阅读

viewsIcon

48363

downloadIcon

3220

C# 中的 3D 曲面图控件

引言

本文介绍了一个C#控件,它将Z值的二维数组绘制成曲面图或散点图。该控件可用于WPF和WinForms应用程序。它专为实时数据显示而设计,即数据会不断更新。将控件添加到应用程序中非常简单。

显示高度可配置,包括:

  1. 每个轴上的可选标签
  2. 用户可以选择标签的颜色、大小和方向。
  3. 可选的Z轴条,显示每个Z轴值对应的颜色。
  4. 连接数据点的可选网格
  5. 用户可以选择网格颜色。
  6. 可选的曲面着色
  7. 可选的散点图显示
  8. 保持功能,即只显示每个X,Y点处的最大(或最小)Z值。
  9. 用户可选择的投影:3D、正交和鸟瞰
  10. 用户可选择的背景颜色
  11. 显示器侧面的可选框架
  12. 用户可以选择框架颜色。
  13. 一个允许用户配置显示的WPF视图。
  14. 用户可以使用键盘操作视图,包括缩放、旋转和移动。
  15. 用户可选择的透视。

该控件通过C# OpenTK库使用Open GL绘制形状和文本。OpenGL速度很快,因为它使用PC的显卡。性能当然取决于PC和显卡的性质。

设置作为实现IConfiguration接口的类的实例传递给控件。我提供了一个基本实现。配置可以从注册表读取和写入。

该库包含一个WPF用户控件,可以添加到表单或对话框中,并允许用户配置显示设置,例如框架颜色和标签大小。设置包括缩放、Z轴缩放和透视。我还创建了一个简单的导出方法,供WinForms应用程序使用,用于显示配置视图。

有两个简单的演示应用程序,一个使用WPF,另一个使用WinForms。

示例图

下面显示了一个带有轴、标签和框架的简单曲面图

在上面,曲面着色是连续的,即平滑的。

以下示例的浮点轴由调用应用程序格式化

标签和轴标题等元素可以删除。在以下示例中,标签和轴标题未绘制

配色方案可以更改。在以下示例中,背景为黑色

在以下示例中,网格未绘制,并且网格着色是粗糙的而不是连续的

数据可以绘制为点

曲面可以显示为鸟瞰图

数据可以显示为正交投影(从侧面)

配置视图

该库包含一个视图,它作为WPF用户控件实现,允许用户调整显示设置

键盘控制

用户可以使用鼠标和键盘按以下方式操作视图

  • 放大和缩小:按住鼠标左键并旋转鼠标滚轮。
  • 移动图表:按住鼠标左键和Ctrl键,然后移动鼠标。
  • 绕X、Y和Z轴旋转:按住鼠标左键并移动鼠标。

背景

您需要对使用C#编写Windows应用程序有很好的理解。WPF的基本知识有所帮助,但并非必不可少。您无需了解任何关于OpenGL的知识。

Using the Code

我提供了一个简单的演示应用程序,展示了如何使用该控件

曲面图控件嵌入在应用程序的主窗口中,如下所示

<WindowsFormsHost Grid.Row="0" Grid.RowSpan="2" 
 Grid.Column="1" Margin="0" Background="Transparent" 
 HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <SurfacePlot:SurfacePlotControl x:Name="_surfacePlotControl"/>
</WindowsFormsHost>

主窗口在窗口加载后初始化控件

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    OpenControls.Wpf.SurfacePlotterDemo.ViewModel.MainViewModel mainViewModel = 
    new OpenControls.Wpf.SurfacePlotterDemo.ViewModel.MainViewModel();
    DataContext = mainViewModel;

    mainViewModel.Load();
    _configurationControl.DataContext = 
    new OpenControls.Wpf.SurfacePlot.ViewModel.ConfigurationControlViewModel
    (mainViewModel.IConfiguration);
    _surfacePlotControl.Initialise(mainViewModel.IConfiguration);
}

主窗口视图模型类的IConfiguration属性声明如下

    private readonly Model.IConfiguration IConfiguration;

IConfiguration接口定义了配置设置

public interface IConfiguration : IConfigurationSubject
{
    // Signalled whenever a configuration setting is changed
    event ConfigurationChangedEventHandler ConfigurationChanged;

    void Load(IConfigurationSerialiser configurationSerialiser);
    void Save(IConfigurationSerialiser configurationSerialiser);

    int Zoom { get; set; }
    int MaximumZoom { get; }
    int MinimumZoom { get; }
    double ZScale { get; set; }
    string BackgroundColour { get; set; }
    bool ShowAxes { get; set; }
    bool ShowAxesTitles { get; set; }
    bool ShowZBar { get; set; }
    bool ShowFrame { get; set; }
    string FrameColour { get; set; }
    bool ShowLabels { get; set; }
    string LabelColour { get; set; }
    int LabelFontSize { get; set; }
    int LabelAngleInDegrees { get; set; }
    bool TransparentLabelBackground { get; set; }
    XYLabelPosition XYLabelPosition { get; set; }
    float Perspective { get; set; }
    ViewProjection ViewProjection { get; set; }
    ShadingMethod ShadingMethod { get; set; }
    bool ShowGrid { get; set; }
    string GridColour { get; set; }
    bool ShowScatterPlot { get; set; }
    bool ShowShading { get; set; }
    ShadingAlgorithm ShadingAlgorithm { get; set; }
    short BlueLevel { get; set; }
    short RedLevel { get; set; }
    bool Hold { get; set; }
    bool HoldMaximum { get; set; }
}

观察者(包括曲面图控件)通过向ConfigurationChanged事件添加处理程序来请求设置更改时的通知。应用程序还可以将设置序列化到存储介质和从存储介质反序列化。

IConfiguration接口由Configuration类实现。

    IConfiguration = new OpenControls.Wpf.SurfacePlot.Model.Configuration();

通过调用曲面图控件上的SetData方法来更新绘图。此方法必须在UI线程上调用,如下例所示

    this.Dispatcher.Invoke(delegate
    {
        _surfacePlotControl.SetData(data, -50, 50, 21, -50, 50, 21, zMin, zMax, 21);
    });

SetData方法的参数如下

numberOfZLabels

类型 名称 含义
List<List<float>> lineData 要绘制的Z值
float xMin 最小X值
float xMax 最大X值
int numberOfXLabels X轴标签的数量
float yMin 最小Y值
float yMax 最大Y值
int numberOfYLabels Y轴标签的数量
float zMin 最小Z值
float zMax 最大Z值
float numberOfZLabels Z轴标签的数量

请注意,最小值和最大值定义了轴标签值。

存储配置

主窗口按如下方式加载配置

    IConfiguration.Load(IConfigurationSerialiser);

并按如下方式保存配置

    IConfiguration.Save(IConfigurationSerialiser);

IConfigurationSerialiser参数是IConfigurationSerialiser接口的实例

public interface IConfigurationSerialiser
{
    void WriteEntry<T>(string key, T value);
    T ReadEntry<T>(string key, T value);
}

该接口定义了将设置序列化到存储介质和从存储介质反序列化的方法。

IConfigurationSerialiser接口由ConfigurationSerialiser类实现

    IConfigurationSerialiser = new SurfacePlot.Model.ConfigurationSerialiser();

ConfigurationSerialiser类将设置序列化到注册表和从注册表反序列化。

当然,您可以实现自己的类并从IConfigurationSerialiser接口派生。

标签格式

默认情况下,标签按以下示例格式化

    public string XLabel(float x, int index) => x.ToString("G4");

因此,100将显示为"100",12.5将显示为"12.5"。

应用程序可以通过将ILabelFormatter接口的实例传递给曲面图控件来覆盖默认标签格式。例如

        _surfacePlotControl.ILabelFormatter = this;

在上面的示例中,this指针是实现该接口的类的实例。

ILabelFormatter接口定义如下

public interface ILabelFormatter
{
    string XLabel(float x, int index);
    string YLabel(float y, int index);
    string ZLabel(float z);
}

WinForms:显示配置对话框

配置对话框供WinForms应用程序使用

以下代码显示对话框

    OpenControls.Wpf.SurfacePlot.Exports.ShowConfigurationDialog(configuration);

其中configuration参数是前面描述的Configuration类的实例。

缺点

该控件不将散点图点渲染为球体或圆形。我尝试绘制多边形来近似球体,但这需要太多的处理,并使绘图速度降低到不可接受的程度。

关注点

OpenGL没有定义文本绘制方法。在初始化时,代码创建并保存字符的位图。要绘制字符,它使用位图的一部分作为填充纹理绘制一个正方形。这对于每个字符串中的每个字符重复。

OpenGL可以在直接和间接模式下使用。在间接模式下,要渲染的数据被复制到内部缓冲区,以便在绘制请求期间后续显示。在直接模式下,数据在绘制请求期间直接传递给Open GL。间接模式效率更高,速度更快。然而,在显示实时数据时,它几乎没有任何优势,因为缓冲区必须不断更新。曲面图控件在直接模式下使用OpenGL。

GitHub

该代码可在GitHub上作为OpenControls解决方案的一部分提供

历史

  • 2021年2月19日:第一个版本
  • 2021年2月21日:添加了一个函数,用于显示设置视图,供WinForms应用程序使用。
  • 2021年2月23日:调整了投影参数,因为在绘制大量点时,远点太近了
© . All rights reserved.