This example shows how you can draw smooth spheres in a 3D WPF program. The example Draw spheres using WPF and C# shows how to generate the points on a sphere. That program adds new positions for each point every time it is used in the sphere so every triangle that makes up the sphere has its own surface normal. That means the triangles seem flat and the sphere isn’t smooth.

To draw smooth spheres, you need to make the triangles that share a corner use the same positions in the mesh data. The example Draw smooth cylinders using WPF and C# uses that technique to draw smooth cylinders. You could use the same approach to draw smooth spheres. You would carefully figure out where each triangle corner was and then make the appropriate triangles share the points. That approach isn’t too bad with a cylinder where each point is shared by four adjacent triangles that differ by the angle `theta`, but for a sphere it’s a bit more complicated because each point is shared by eight triangles that differ by both angles `phi` and `theta`.

You could still take that approach, but this example uses a simpler solution based on the method used in the example Create a 3D surface really quickly with WPF, XAML, and C#. The idea is to make a `Dictionary` to keep track of the indexes of the points. When you need to use a point, you first see if it’s in the `Dictionary`. If it is, you reuse its index in the mesh’s `Positions` collection. If the point isn’t in the `Dictionary`, you add it to the `Positions` collection and you save its index in the `Dictionary`.

This example’s `AddSmoothSphere` method is similar to the version used by the previous example except it calls the following `AddSmoothTriangle` method to create triangles.

// Add a triangle to the indicated mesh. // Reuse points so triangles share normals. private void AddSmoothTriangle(MeshGeometry3D mesh, Dictionary<Point3D, int> dict, Point3D point1, Point3D point2, Point3D point3) { int index1, index2, index3; // Find or create the points. if (dict.ContainsKey(point1)) index1 = dict[point1]; else { index1 = mesh.Positions.Count; mesh.Positions.Add(point1); dict.Add(point1, index1); } if (dict.ContainsKey(point2)) index2 = dict[point2]; else { index2 = mesh.Positions.Count; mesh.Positions.Add(point2); dict.Add(point2, index2); } if (dict.ContainsKey(point3)) index3 = dict[point3]; else { index3 = mesh.Positions.Count; mesh.Positions.Add(point3); dict.Add(point3, index3); } // If two or more of the points are // the same, it's not a triangle. if ((index1 == index2) || (index2 == index3) || (index3 == index1)) return; // Create the triangle. mesh.TriangleIndices.Add(index1); mesh.TriangleIndices.Add(index2); mesh.TriangleIndices.Add(index3); }

This method first checks the `Dictionary` to see if the first point is present. If it is, the code sets `index1` equal to the point’s index stored in the `Dictionary`. If the point isn’t yet in the `Dictionary`, the code sets `index1` equal to the next index in the mesh’s `Positions` collection, adds the point to the collection, and saves the index in the `Dictionary` for later use.

The code repeats those steps for the triangle’s second and third points.

Next the code checks whether any of the points are the same. That happens when the `AddSmoothSphere` method is building the sphere’s top and bottom caps. For each `theta` value, each cap uses a single triangle. The other triangle that would be generated has two corners that are the same, so it really isn’t necessary. This code checks for that condition and doesn’t add the empty triangles.

Finally, the method adds entries the points’ indexes in the mesh’s `TriangleIndices` collection to define the triangle.

The rest of the program is almost the same as the previous version. The only other interesting changes are in the `DrawSmoothSphere` method. It uses the following statement to create a `Dictionary` to keep track of the sphere’s points.

// Make a dictionary to track the sphere's points. Dictionary<Point3D, int> dict = new Dictionary<Point3D, int>();

Later the method uses the following code to create triangles.

// Create the triangles. AddSmoothTriangle(mesh, dict, pt00, pt11, pt10); AddSmoothTriangle(mesh, dict, pt00, pt01, pt11);

Download the example and look at the code to see the rest of the details.

Pingback: Make a 3D globe in WPF and C# - C# HelperC# Helper

Hey Mr. Setephens! Thank you so much for this useful article, it’s been a great help with what I’m doing.

Can you shed some insight as to why I’m getting bizarre results when applying a roll, pitch, and yaw rotation to spheres created with this method?

This is the article I’m using to do the rotations: http://danceswithcode.net/engineeringnotes/rotations_in_3d/rotations_in_3d_part2.html

What results are you getting?

Are you building the matrices according to that article and then applying them yourself to the points? Or are you trying to make the WPF drawing system apply the matrix?

Here’s an example of a single sphere after rotation is applied: https://imgur.com/a/6O4vQSw

I am setting up the matrix like in the article and applying the three rotations to each point before they get added as triangles. I’ve even copy pasted the function I’m using into this example: https://pastebin.com/RNDXsGBV

The function is called “Rotate”

The spheres are getting torn when the rotation is applied to them.

Here’s an image example: https://imgur.com/a/6O4vQSw

I’ve added the rotation code to this example and copied it over to pastebin: https://pastebin.com/RNDXsGBV

I’ll take a look.

There were two problems, both involving the same lines of code. Here’s the original version.

The first four lines set point pt00 repeatedly when they should be setting p00, p01, p10, and p11.

The second problem is that the loop only calculates points pt01 and pt11, At the end of the loop it saves those points as pt00 and pt10 for the next trip through the loop. The Rotate code was modifying pt00 and pt10, and then modifying them again in the next trip through the loop so they were not where they needed to be the next time around.

One way to fix the problem is to save the rotated points in new variables that are then used to define the triangles leaving p00, p01, p10, and p11 unrotated so they will work the next time through the loop. The following code uses that approach.

I hope that helps. Your program seems to only rotate the spheres and not the cylinders, so you might still need to do that. I don’t *think* the AddCylinder method uses saved points in its loops so it shouldn’t have the same problem.