[C# Helper]
Index Books FAQ Contact About Rod
[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

[C# 24-Hour Trainer]

[Beginning Software Engineering]

[C# 5.0 Programmer's Reference]

[MCSD Certification Toolkit (Exam 70-483): Programming in C#]

Title: Draw smooth cylinders using WPF and C#

[Draw smooth cylinders using WPF and C#]

This example shows how to draw smooth cylinders in a WPF application. The example Draw cylinders using WPF and C# shows how to draw cylinders. See that example to learn the basic techniques.

To make WPF produce smooth cylinders, all you need to do is make the appropriate triangles share vertices. If two triangles share an edge, WPF interpolates the surface normal (the direction perpendicular to the surface) across the two triangles so they blend smoothly together instead of showing a sharp edge between them.

The program could use the previous code to draw the cylinder's end caps. Because the triangles in an end cap lie in the same plane, they have the same normals whether they share points or not. However, this example makes them share points to save a little memory in the mesh's Positions collection.

The following code shows this example's DrawSmoothCylinder method.

// Add a cylinder with smooth sides. private void AddSmoothCylinder(MeshGeometry3D mesh, Point3D end_point, Vector3D axis, double radius, int num_sides) { // Get two vectors perpendicular to the axis. Vector3D v1; if ((axis.Z < -0.01) || (axis.Z > 0.01)) v1 = new Vector3D(axis.Z, axis.Z, -axis.X - axis.Y); else v1 = new Vector3D(-axis.Y - axis.Z, axis.X, axis.X); Vector3D v2 = Vector3D.CrossProduct(v1, axis); // Make the vectors have length radius. v1 *= (radius / v1.Length); v2 *= (radius / v2.Length); // Make the top end cap. // Make the end point. int pt0 = mesh.Positions.Count; // Index of end_point. mesh.Positions.Add(end_point); // Make the top points. double theta = 0; double dtheta = 2 * Math.PI / num_sides; for (int i = 0; i < num_sides; i++) { mesh.Positions.Add(end_point + Math.Cos(theta) * v1 + Math.Sin(theta) * v2); theta += dtheta; } // Make the top triangles. int pt1 = mesh.Positions.Count - 1; // Index of last point. int pt2 = pt0 + 1; // Index of first point. for (int i = 0; i < num_sides; i++) { mesh.TriangleIndices.Add(pt0); mesh.TriangleIndices.Add(pt1); mesh.TriangleIndices.Add(pt2); pt1 = pt2++; } // Make the bottom end cap. // Make the end point. pt0 = mesh.Positions.Count; // Index of end_point2. Point3D end_point2 = end_point + axis; mesh.Positions.Add(end_point2); // Make the bottom points. theta = 0; for (int i = 0; i < num_sides; i++) { mesh.Positions.Add(end_point2 + Math.Cos(theta) * v1 + Math.Sin(theta) * v2); theta += dtheta; } // Make the bottom triangles. theta = 0; pt1 = mesh.Positions.Count - 1; // Index of last point. pt2 = pt0 + 1; // Index of first point. for (int i = 0; i < num_sides; i++) { mesh.TriangleIndices.Add(num_sides + 1); // end_point2 mesh.TriangleIndices.Add(pt2); mesh.TriangleIndices.Add(pt1); pt1 = pt2++; } // Make the sides. // Add the points to the mesh. int first_side_point = mesh.Positions.Count; theta = 0; for (int i = 0; i < num_sides; i++) { Point3D p1 = end_point + Math.Cos(theta) * v1 + Math.Sin(theta) * v2; mesh.Positions.Add(p1); Point3D p2 = p1 + axis; mesh.Positions.Add(p2); theta += dtheta; } // Make the side triangles. pt1 = mesh.Positions.Count - 2; pt2 = pt1 + 1; int pt3 = first_side_point; int pt4 = pt3 + 1; for (int i = 0; i < num_sides; i++) { mesh.TriangleIndices.Add(pt1); mesh.TriangleIndices.Add(pt2); mesh.TriangleIndices.Add(pt4); mesh.TriangleIndices.Add(pt1); mesh.TriangleIndices.Add(pt4); mesh.TriangleIndices.Add(pt3); pt1 = pt3; pt3 += 2; pt2 = pt4; pt4 += 2; } }

This code first creates perpendicular vectors as before. Next it adds the end point to the mesh's Positions collection. The code then adds the points around the edge of the first end cap, and loops through those points, using them to create the first end cap's triangles.

The program repeats those steps for the second end cap.

Next the code makes another loop somewhat similar to the one it used to create the top end cap's points. This time it adds the top end cap point and the corresponding bottom end cap point to the mesh. (Note that the sides and ends of the cylinder must use separate versions of the end cap points. If they share those points, then WPF will smooth out the edge between the end caps and the sides, which looks really strange.)

After it creates all of the side points, the code loops num_sides times and connects the points to make two triangles for each strip along the cylinder's side. These are the same triangles created by the previous example, just created in a different way so the triangles can share vertices.

When you draw smooth cylinders in this way, WPF makes the edges between the cylinder's side triangles look smooth. The end caps are still polygons, however, so you can see their corners. For example, you can clearly see in the picture above that the red cylinder's end cap has 8 sides. When a cylinder intersects another objects, you can also see the edges

When a cylinder intersects with another object, you can also see effects caused by the fact that the cylinder's sides are rectangles. For example, in the picture above you can see polygonal edges where the cylinders intersect.

[Draw smooth cylinders using WPF and C#]

You can reduce those effects by increasing the number of triangles used to make the cylinders. For example, in the cylinders in the picture on the right, num_sides = 50. This produces a much smoother result, although it makes each cylinder use 202 points and 600 triangles. You'll have to decide where to set the tradeoff between memory use and quality. (num_sides = 20 produces a very reasonable result.)

Download the example to experiment with it and to see additional details.

© 2009-2022 Rocky Mountain Computer Consulting, Inc. All rights reserved.