Draw a smooth 3D surface with WPF, XAML, and C#

example

My post Draw a 3D surface with WPF, XAML, and C# explains how to use WPF, XAML, and C# to draw a three-dimensional surface. The following list recaps the main steps.

  1. Place a Viewport3D object on a WPF program’s window. Give it a name so you can refer to it in code.
  2. Add startup code to do the following.
    1. Set the viewport’s Camera property to a camera.
    2. Create a Model3DGroup to hold information about the three-dimensional scene.
    3. Create lights and add them to the group’s Children collection.
    4. Use the following steps to create the geometry model.
      1. Make one or more MeshGeometry3D objects to represent three-dimensional objects.
      2. Add points to the mesh’s Positions collection to define points used by the object.
      3. Add indexes to the mesh’s TriangleIndices collection to define triangles.
      4. Optionally add normal vectors to the mesh’s Normals collection to determine the points’ normal directions. (The previous example didn’t do this. I’ll cover this in a later post.)
      5. Optionally add values to the mesh’s TextureCoordinates collection to indicate how points are mapped to the object’s texture. (I’ll also cover this in a later post.)
      6. Create a material to represent the mesh’s object.
      7. Use the mesh and the material to create a new GeometryModel3D object.
      8. Optionally set the model object’s BackMaterial property.
      9. Add the model to the group’s Children collection.

Simple, right? Actually it’s not too bad if you use the previous example as a template for new programs.

One problem with the previous example is that it doesn’t draw a smooth surface. If you look at that program’s result, you can see the triangles that make up the surface. The reason you see each triangle is that the program doesn’t supply vertex normal information. The rendering engine calculates each triangle’s normal separately and uses those calculated normals to shade the triangles.

You could define normals for each of the surface’s points, but that would be a lot of work, particularly because you’d have to find the derivative of the messy function that generates the surface.

Fortunately there’s an easier approach. If two triangles share a vertex, then by default the rendering engine uses an average of their normals for the normal at that point. The previous example didn’t get this benefit because it added each triangle’s vertices to the mesh’s Positions collection separately so the triangles didn’t share vertices. To fix this, the new example reuses any vertices that are already in the Positions collection.

The following code shows how the program adds a new triangle to its mesh.

// Add a triangle to the indicated mesh.
// If the triangle's points already exist, reuse them.
private void AddTriangle(MeshGeometry3D mesh,
    Point3D point1, Point3D point2, Point3D point3)
{
    // Get the points' indices.
    int index1 = AddPoint(mesh.Positions, point1);
    int index2 = AddPoint(mesh.Positions, point2);
    int index3 = AddPoint(mesh.Positions, point3);

    // Create the triangle.
    mesh.TriangleIndices.Add(index1);
    mesh.TriangleIndices.Add(index2);
    mesh.TriangleIndices.Add(index3);
}

This method calls the AddPoint method (described next) to add the triangle’s vertices to the mesh’s Positions collection. The AddPoint method returns the index of a point in that collection. The code then adds the vertices’ indices to the mesh’s TriangleIndices collection to define the triangle.

The following code shows the AddPoint method.

// If the point already exists, return its index.
// Otherwise create the point and return its new index.
private int AddPoint(Point3DCollection points, Point3D point)
{
    // See if the point exists.
    for (int i = 0; i < points.Count; i++)
    {
        if ((point.X == points[i].X) &&
            (point.Y == points[i].Y) &&
            (point.Z == points[i].Z))
                return i;
    }

    // We didn't find the point. Create it.
    points.Add(point);
    return points.Count - 1;
}

This code loops through the mesh’s Positions collection to see if the desired point is already there. If it is, the method returns the point’s index in the collection. If the point isn’t there, the method adds it and returns the new point’s index.

Because the triangles reuse existing vertices, they share any vertices they can. The result is the smooth surface you see at the top of the post.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in algorithms, geometry, graphics, mathematics, wpf, XAML and tagged , , , , , , , , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

3 Responses to Draw a smooth 3D surface with WPF, XAML, and C#

  1. Create a 3-D surface more quickly with WPF, XAML, and C#

    IMHO I think the example Draw a smooth 3-D surface with WPF, XAML, and C# is pretty cool. Unfortunately it’s also a bit slow. It take around 16 seconds to generate the three-dimensional scene. The program is generating 10,000 points and more than 19,000 triangles, so it’s doing a lot of work, but the earlier non-smooth example is lightning fast. So the question is, why is the smooth version so much slower? The answer lies in the way it creates and uses its points. The smooth version reuses points if possible. That means to add a new …

  2. Create a 3-D surface really quickly with WPF, XAML, and C#

    The example Draw a smooth 3-D surface with WPF, XAML, and C# draw a smooth surface but take about 16 seconds on my computer. The example Create a 3-D surface more quickly with WPF, XAML, and C# searches for duplicate points from the rear of its points collection so it’s much faster, taking only about 2 seconds. This example stores its points in a Dictionary (as well as in the collection that the WPF drawing code needs) so it can locate points even more quickly. This example uses the following AddPoints method to create new points for …

  3. Pingback: Create a 3D surface more quickly with WPF, XAML, and C# -

Leave a Reply

Your email address will not be published. Required fields are marked *