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

Windows Presentation Foundation 中的曲面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (7投票s)

2010年11月22日

CPOL

5分钟阅读

viewsIcon

37795

downloadIcon

2184

本文演示了如何绘制简单曲面和参数曲面。

引言

前言:关于表面的说明

要运行这些应用程序,请下载 zip 文件并将它们解压缩到您Projects 目录中一个新创建的文件夹。之后,您可以双击解决方案文件,或者右键单击它们,如果您想将它们加载到 VS 2010 或 Expression Blend 4.0 中。

从数学上讲,表面会在感兴趣的区域内的每个 X 和 Y 坐标上绘制一个 Z 函数。对于每个 X 和 Y 值,一个简单的表面最多可以有一个 Z 值。更确切地说,在数学中,尤其是在拓扑学中,表面是一个二维拓扑流形。“二维”的含义是,对于每个点,都有一个坐标块,在该坐标块上定义了一个二维坐标系统。例如,地球的表面(理想情况下)是一个二维球体,经度和纬度提供了其上的坐标。那么,我们的目标就是绘制一个表面。因为我们要绘制 2D 和 3D 几何形状,所以我们需要理解什么是网格。网格基本上是表面的表示。网格通过点和线的系统来表示表面。点描述了表面的高区域和低区域,线连接点以确定如何从一个点到达下一个点。

至少,表面是一个平面。一个平面需要三个点来定义它。因此,在网格中可以描述的最简单的表面是单个三角形。事实证明,网格只能用三角形来描述。这是因为三角形是定义表面的最简单、最细粒度的方式。网格通过许多三角形来表示表面。整个网格由网格位置、三角形索引和三角形法线组成。在 WPF 中,添加网格位置的顺序很重要。在添加三角形索引时,会使用网格位置集合中某个位置的索引值。例如,假设您有一个由五个位置 {p0, p1, p2, p3, p4} 组成的表面。如果您想从 p1p3p4 定义一个三角形,您将添加索引值为 1、3 和 4 的三角形索引。如果位置以不同的顺序添加 {p3, p4, p0, p2, p1},并且您想要一个由相同位置组成的三角形,您将添加索引值为 4、0 和 1 的三角形索引。

因此,让我们使用 WPF,以便利用这些概念来绘制表面。我们将回顾一下表面由五个位置 {p0, p1, p2, p3, p4} 组成。如果您想从 p1p3p4 定义一个三角形,您将添加索引值为 1、3 和 4 的三角形索引。如果位置以不同的顺序添加 {p3, p4, p0, p2, p1},并且您想要一个由相同位置组成的三角形,您将添加索引值为 4、0 和 1 的三角形索引。现在检查一下这段代码。3DTools.dll 是一个可下载的组件,可在 www.codeplex.com 获得。这个程序集将帮助我们构建 Utility 类,该类将是我们应用程序中使用的其中一个引用的 DLL。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Controls;
using _3DTools;
 public class Utility
    {
        public static void CreateRectangleFace(
        Point3D p0, Point3D p1, Point3D p2, Point3D p3,
        Color surfaceColor, Viewport3D viewport)
        {
            MeshGeometry3D mesh = new MeshGeometry3D();
            mesh.Positions.Add(p0);
            mesh.Positions.Add(p1);
            mesh.Positions.Add(p2);
            mesh.Positions.Add(p3);
            mesh.TriangleIndices.Add(0);
            mesh.TriangleIndices.Add(1);
            mesh.TriangleIndices.Add(2);
            mesh.TriangleIndices.Add(2);
            mesh.TriangleIndices.Add(3);
            mesh.TriangleIndices.Add(0);
            SolidColorBrush brush = new SolidColorBrush();
            brush.Color = surfaceColor;
            Material material = new DiffuseMaterial(brush);
            GeometryModel3D geometry =
            new GeometryModel3D(mesh, material);
            ModelVisual3D model = new ModelVisual3D();
            model.Content = geometry;
            viewport.Children.Add(model);
        }

        public static void CreateWireframe(
        Point3D p0, Point3D p1, Point3D p2, Point3D p3,
        Color lineColor, Viewport3D viewport)
        {
            ScreenSpaceLines3D ssl = new ScreenSpaceLines3D();
            ssl.Points.Add(p0);
            ssl.Points.Add(p1);
            ssl.Points.Add(p1);
            ssl.Points.Add(p2);
            ssl.Points.Add(p2);
            ssl.Points.Add(p3);
            ssl.Points.Add(p3);
            ssl.Points.Add(p0);
            ssl.Color = lineColor;
            ssl.Thickness = 2;
            viewport.Children.Add(ssl);
        }
        public static Point3D GetNormalize(Point3D pt,
        double xmin, double xmax,
        double ymin, double ymax,
        double zmin, double zmax)
        {
            pt.X = -1 + 2 * (pt.X - xmin) / (xmax - xmin);
            pt.Y = -1 + 2 * (pt.Y - ymin) / (ymax - ymin);
            pt.Z = -1 + 2 * (pt.Z - zmin) / (zmax - zmin);
            return pt;
        }
    }

现在我们已经构建了Utility.dll,我们将构建另一个类文件,通过引用先前创建的 DLL 来将其编译成 DLL。这是SimpleSurface.cs

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Controls;
public class SimpleSurface
{
public delegate Point3D Function(double x, double z);
private double xmin = -3;
private double xmax = 3;
private double ymin = -8;
private double ymax = 8;
private double zmin = -3;
private double zmax = 3;
private int nx = 30;
private int nz = 30;
private Color lineColor = Colors.Black;
private Color surfaceColor = Colors.White;
private Point3D center = new Point3D();
private bool isHiddenLine = false;
private bool isWireframe = true;
private Viewport3D viewport3d = new Viewport3D();
public bool IsWireframe
{
get { return isWireframe; }
set { isWireframe = value; }
}
public bool IsHiddenLine
{
get { return isHiddenLine; }
set { isHiddenLine = value; }
}
public Color LineColor
{
get { return lineColor; }
set { lineColor = value; }
}
public Color SurfaceColor
{
get { return surfaceColor; }
set { surfaceColor = value; }
}
public double Xmin
{

get { return xmin; }
set { xmin = value; }
}
public double Xmax
{
get { return xmax; }
set { xmax = value; }
}
public double Ymin
{
get { return ymin; }
set { ymin = value; }
}
public double Ymax
{
get { return ymax; }
set { ymax = value; }
}
public double Zmin
{
get { return zmin; }
set { zmin = value; }
}
public double Zmax
{
get { return zmax; }
set { zmax = value; }
}
public int Nx
{
get { return nx; }
set { nx = value; }
}
public int Nz
{
get { return nz; }
set { nz = value; }
}
public Point3D Center
{
get { return center; }
set { center = value; }
}
public Viewport3D Viewport3d
{

get { return viewport3d; }
set { viewport3d = value; }
}
public void CreateSurface(Function f)
{
double dx = (Xmax - Xmin) / Nx;
double dz = (Zmax - Zmin) / Nz;
if (Nx < 2 || Nz < 2)
return;
Point3D[,] pts = new Point3D[Nx, Nz];
for (int i = 0; i < Nx; i++)
{
double x = Xmin + i * dx;
for (int j = 0; j < Nz; j++)
{
double z = Zmin + j * dz;
pts[i, j] = f(x, z);
pts[i, j] += (Vector3D)Center;
pts[i, j] = Utility.GetNormalize(
pts[i, j], Xmin, Xmax,
Ymin, Ymax, Zmin, Zmax);
}
}
Point3D[] p = new Point3D[4];
for (int i = 0; i < Nx - 1; i++)
{
for (int j = 0; j < Nz - 1; j++)
{
p[0] = pts[i, j];
p[1] = pts[i, j + 1];
p[2] = pts[i + 1, j + 1];
p[3] = pts[i + 1, j];
//Create rectangular face:
if (IsHiddenLine == false)
Utility.CreateRectangleFace(
p[0], p[1], p[2], p[3],
SurfaceColor, Viewport3d);
// Create wireframe:
if (IsWireframe == true)
Utility.CreateWireframe(
p[0], p[1], p[2], p[3],
LineColor, Viewport3d);
}
         }
      }
}

请注意上面几行代码:Utility.CreateWireFrame()Utility 是类,CreateWireframe() 是在此类中定义的方法。回顾一下

public static void CreateWireframe(
Point3D p0, Point3D p1, Point3D p2, Point3D p3,
Color lineColor, Viewport3D viewport)

一个简单的表面

现在我们有了Utility.dllSimpleSurface.dll。这些可以作为类库构建。如果我们要在命令行上构建它们,最简单的方法是转到c:\program files\referenced assemblies\Microsoft\Framework\.NETFramework\v4.0\。此目录包含主要的 WPF 程序集:PresentationCore.dllPresentationFramework.dllSystem.Xaml.dllWindowsBase.dll。我们将不得不设置 .NET 环境路径

set PATH=%PATH%;.;C:\windows\Microsoft.NET\Framework\v4.0.30319

无论我们是使用命令行还是 Visual Studio 2010,当我们使用 Expression Blend 或 Visual Studio 2010 构建 WPF 项目时,我们都会使用这一系列的 DLL。我们将项目命名为 Surf。我们添加一个新项(一个新的 WPF 窗口),并将其命名为 SimpleSurfaceTest。这会创建相应的SimpleSurfaceTest.xaml 文件和SimpleSurfaceTest.xaml.cs 代码隐藏文件。哦,我们右键单击MainWindow.xaml(cs) 文件,然后单击“从项目移除”。当您复制粘贴 XAML 文件时,您会注意到除了渐变之外,没有表面。表面是通过代码隐藏文件绘制的。以下是两个SimpleSurfaceTest 文件

Untitled.jpg

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace Surf
{
public partial class SimpleSurfaceTest : Window
{
  private SimpleSurface ss = new SimpleSurface();
  public SimpleSurfaceTest()
  {
   InitializeComponent();
   ss.IsHiddenLine = false;
   ss.Viewport3d = viewport;
   AddSinc();
   }
 private void AddSinc()
 {
   ss.Xmin = -8;
   ss.Xmax = 8;
   ss.Zmin = -8;
   ss.Zmax = 8;
   ss.Ymin = -1;
   ss.Ymax = 1;
   ss.CreateSurface(Sinc);
  }
private Point3D Sinc(double x, double z)
{
  double r = Math.Sqrt(x * x + z * z) + 0.00001;
  double y = Math.Sin(r) / r;
  return new Point3D(x, y, z);
}

private void AddPeaks()
{
ss.Xmin = -3;
ss.Xmax = 3;
ss.Zmin = -3;
ss.Zmax = 3;
ss.Ymin = -8;
ss.Ymax = 8;
ss.CreateSurface(Peaks);
}
private Point3D Peaks(double x, double z)
{
  double y = 3 * Math.Pow((1 - x), 2) *
  Math.Exp(-x * x - (z + 1) * (z + 1)) -
  10 * (0.2 * x - Math.Pow(x, 3)  - Math.Pow(z, 5)) * Math.Exp(-x * x - z * z) -
  1 / 3 * Math.Exp(-(x + 1) * (x + 1) - z * z);
  return new Point3D(x, y, z);
}
private void AddRandomSurface()
{
   ss.Xmin = -8;
   ss.Xmax = 8;
   ss.Zmin = -8;
   ss.Zmax = 8;
   ss.Ymin = -1;
   ss.Ymax = 1;
   ss.CreateSurface(RandomSurface);
}
private Random rand = new Random();
private Point3D RandomSurface(double x, double z)

{
   double r = Math.Sqrt(x * x + z * z) + 0.00001;
   double y = Math.Sin(r) / r +
   0.2 * rand.NextDouble();
 return new Point3D(x, y, z);
   }
 }
}

输出如下

Capture.JPG

参数曲面

上面显示的曲面是一个简单的曲面。这种曲面的一個关键特性是,对于每一对 X 和 Z 值,最多只有一个 Y 值。但是,有时您可能想创建一个具有特定形状的复杂曲面。这种复杂的曲面无法用简单的函数表示。对于某些 X 和 Z 值,该曲面具有多个 Y 值。表示这种曲面的一种方法是使用一组参数方程。这些方程根据参数变量 u v 来定义曲面上点的 X、Y 和 Z 坐标。因此,下一步是构建一个 ParametricSurface 类,将其编译成一个 DLL,在执行代码测试该曲面时将引用该 DLL。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Controls;
public class ParametricSurface
{
   public delegate Point3D Function(double u, double v);
   private int nu = 30;
   private int nv = 30;
   private double umin = -3;
   private double umax = 3;
   private double vmin = -8;
   private double vmax = 8;
   private double xmin = -1;
   private double xmax = 1;
   private double ymin = -1;
   private double ymax = 1;
   private double zmin = -1;
   private double zmax = 1;
   private Color lineColor = Colors.Black;
   private Color surfaceColor = Colors.White;
   private Point3D center = new Point3D();
   private bool isHiddenLine = false;
   private bool isWireframe = true;
   private Viewport3D viewport3d = new Viewport3D();
   public bool IsWireframe
              {
      get { return isWireframe; }
      set { isWireframe = value; }
          }
    public bool IsHiddenLine
         {
      get { return isHiddenLine; }
      set { isHiddenLine = value; }
       }

    public Color LineColor
      {
        get { return lineColor; }
        set { lineColor = value; }
     }
   public Color SurfaceColor
     {
       get { return surfaceColor; }
       set { surfaceColor = value; }
    }
   public double Umin
      {
        get { return umin; }
        set { umin = value; }
      }
public double Umax
{
    get { return umax; }
    set { umax = value; }
}
public double Vmin
{
   get { return vmin; }
   set { vmin = value; }
}
public double Vmax
{
   get { return vmax; }
   set { vmax = value; }
}
public int Nu
{
   get { return nu; }
   set { nu = value; }
}
public int Nv
{
   get { return nv; }
   set { nv = value; }
}
public double Xmin
{
   get { return xmin; }
   set { xmin = value; }
}

public double Xmax
{
   get { return xmax; }
   set { xmax = value; }
}
public double Ymin
{
get { return ymin; }
set { ymin = value; }
}
public double Ymax
{
get { return ymax; }
set { ymax = value; }
}
public double Zmin
{
get { return zmin; }
set { zmin = value; }
}
public double Zmax
{
get { return zmax; }
set { zmax = value; }
}
public Point3D Center
{
get { return center; }
set { center = value; }
}
public Viewport3D Viewport3d
{
get { return viewport3d; }
set { viewport3d = value; }
}
public void CreateSurface(Function f)
{
    double du = (Umax - Umin) / (Nu - 1);
    double dv = (Vmax - Vmin) / (Nv - 1);
     if (Nu < 2 || Nv < 2)
      return;
Point3D[,] pts = new Point3D[Nu, Nv];
  for (int i = 0; i < Nu; i++)
   {
     double u = Umin + i * du;

   for (int j = 0; j < Nv; j++)
    {
       double v = Vmin + j * dv;
       pts[i, j] = f(u, v);
       pts[i, j] += (Vector3D)Center;
       pts[i, j] = Utility.GetNormalize(
       pts[i, j], Xmin, Xmax,
       Ymin, Ymax, Zmin, Zmax);
    }
   }
Point3D[] p = new Point3D[4];
   for (int i = 0; i < Nu - 1; i++)
   {
     for (int j = 0; j < Nv - 1; j++)
      {
        p[0] = pts[i, j];
        p[1] = pts[i, j + 1];
        p[2] = pts[i + 1, j + 1];
        p[3] = pts[i + 1, j];

       if (IsHiddenLine == false)
      Utility.CreateRectangleFace(
      p[0], p[1], p[2], p[3],
      SurfaceColor, Viewport3d);
// Create wireframe:
     if (IsWireframe == true)
      Utility.CreateWireframe(
      p[0], p[1], p[2], p[3],
      LineColor, Viewport3d);
      }
   }
 }
}

我们将这个类文件构建成一个 DLL。回顾一下,我们仍然必须引用utility.dll(它通过引用下载的3DTools.dll 构建)和SimpleSurface.dll。使用 Expression Blend 4.0,构建一个新的 WPF 项目并将其命名为 Dave。添加一个新窗口并命名为 ParametricSurfaceTest。从项目中移除MainWindow 文件。下面是 XAML 和代码隐藏文件

Untitled1.jpg

ParametricSurfaceTest 代码隐藏文件

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace Dave
{
public partial class ParametricSurfaceTest : Window
{
private ParametricSurface ps =
new ParametricSurface();
public ParametricSurfaceTest()
{
InitializeComponent();
ps.IsHiddenLine = false;
ps.Viewport3d = viewport;
AddHelicoid();
}
private void AddHelicoid()
{
ps.Umin = 0;
ps.Umax = 1;
ps.Vmin = -3 * Math.PI;
ps.Vmax = 3 * Math.PI;

ps.Nv = 100;
ps.Nu = 10;
ps.Ymin = ps.Vmin;
ps.Ymax = ps.Vmax;
ps.CreateSurface(Helicoid);
}
private Point3D Helicoid(double u, double v)
{
double x = u * Math.Cos(v);
double z = u * Math.Sin(v);
double y = v;
return new Point3D(x, y, z);
}
}
}

输出的参数曲面

2.JPG

让我们使用 ParametricSurface 类,并将 Helicoid AddHelicoid 方法替换为一个名为 AppSphere() 的方法

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace Dave
{
public partial class ParametricSurfaceTest : Window
{
private ParametricSurface ps =
new ParametricSurface();
public ParametricSurfaceTest()
{
InitializeComponent();
ps.IsHiddenLine = false;
ps.Viewport3d = viewport;
AddSphere();
}
private void AddSphere()
   {
ps.Umin = 0;

ps.Umax = 2 * Math.PI;
ps.Vmin = -0.5 * Math.PI;
ps.Vmax = 0.5 * Math.PI;
ps.Nu = 20;
ps.Nv = 20;
ps.CreateSurface(Sphere);
   }
private Point3D Sphere(double u, double v)
  {
double x = Math.Cos(v) * Math.Cos(u);
double z = Math.Cos(v) * Math.Sin(u);
double y = Math.Sin(v);
return new Point3D(x, y, z);
   }
  }
}

这是球体,一种参数曲面

3.JPG

我们可以通过将 AddSphere 方法替换为 AddTorus 方法来重复此过程

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace Dave
{
public partial class ParametricSurfaceTest : Window
{
private ParametricSurface ps =
new ParametricSurface();
public ParametricSurfaceTest()
{
InitializeComponent();
ps.IsHiddenLine = false;
ps.Viewport3d = viewport;
AddTorus();
}
private void AddTorus()
{
ps.Umin = 0;
ps.Umax = 2 * Math.PI;
ps.Vmin = 0;
ps.Vmax = 2 * Math.PI;
ps.Nu = 50;
ps.Nv = 20;
ps.CreateSurface(Torus);
}

private Point3D Torus(double u, double v)
{
double x = (1 + 0.3 * Math.Cos(v)) * Math.Cos(u);
double z = (1 + 0.3 * Math.Cos(v)) * Math.Sin(u);
double y = 0.3 * Math.Sin(v);
return new Point3D(x, y, z);
}
}
}

4.JPG

诚然,曲面这个主题听起来很简单。然而,它在科学和拓扑研究中占有重要地位。例如,地球被认为是一个具有经纬度作为其坐标系统的表面。

© . All rights reserved.