Make a 3D globe in WPF and C#

[3D globe]

This example shows how you can draw a 3D globe. More generally it shows how you can make a three-dimensional textured sphere in WPF.

The example Draw smooth spheres using WPF and C# shows how to make smooth colored spheres. This example modifies that technique to make a textured sphere.

The following code shows how the example program builds the 3D globe.

// Add the model to the Model3DGroup.
private void DefineModel(Model3DGroup model_group)
{
    // Globe. Place it in a new model so we can transform it.
    Model3DGroup globe_model = new Model3DGroup();
    model_group.Children.Add(globe_model);

    ImageBrush globe_brush = new ImageBrush(new BitmapImage(
        new Uri("pack://application:,,,/world.jpg")));
    Material globe_material = new DiffuseMaterial(globe_brush);
    MeshGeometry3D globe_mesh = null;
    MakeSphere(globe_model, ref globe_mesh, globe_material,
        1, 0, 0, 0, 20, 30);
}

This code creates a Model3DGroup to hold the 3D globe and adds it to the program’s model group.

Next the code creates a brush that uses the image file world.jpg. It uses the brush to make a material for the 3D globe and then calls the MakeSphere method to create the sphere.

The code shows the MakeSphere method.

// Make a sphere.
private void MakeSphere(Model3DGroup model_group,
    ref MeshGeometry3D sphere_mesh, Material sphere_material,
    double radius, double cx, double cy, double cz, int num_phi,
    int num_theta)
{
    // Make the mesh if we must.
    if (sphere_mesh == null)
    {
        sphere_mesh = new MeshGeometry3D();
        GeometryModel3D new_model =
            new GeometryModel3D(sphere_mesh, sphere_material);
        model_group.Children.Add(new_model);
    }

    double dphi = Math.PI / num_phi;
    double dtheta = 2 * Math.PI / num_theta;

    // Remember the first point.
    int pt0 = sphere_mesh.Positions.Count;

    // Make the points.
    double phi1 = Math.PI / 2;
    for (int p = 0; p <= num_phi; p++)
    {
        double r1 = radius * Math.Cos(phi1);
        double y1 = radius * Math.Sin(phi1);

        double theta = 0;
        for (int t = 0; t <= num_theta; t++)
        {
            sphere_mesh.Positions.Add(new Point3D(
                cx + r1 * Math.Cos(theta),
                cy + y1,
                cz + -r1 * Math.Sin(theta)));
            sphere_mesh.TextureCoordinates.Add(new Point(
                (double)t / num_theta, (double)p / num_phi));
            theta += dtheta;
        }
        phi1 -= dphi;
    }

    // Make the triangles.
    int i1, i2, i3, i4;
    for (int p = 0; p <= num_phi - 1; p++)
    {
        i1 = p * (num_theta + 1);
        i2 = i1 + (num_theta + 1);
        for (int t = 0; t <= num_theta - 1; t++)
        {
            i3 = i1 + 1;
            i4 = i2 + 1;
            sphere_mesh.TriangleIndices.Add(pt0 + i1);
            sphere_mesh.TriangleIndices.Add(pt0 + i2);
            sphere_mesh.TriangleIndices.Add(pt0 + i4);

            sphere_mesh.TriangleIndices.Add(pt0 + i1);
            sphere_mesh.TriangleIndices.Add(pt0 + i4);
            sphere_mesh.TriangleIndices.Add(pt0 + i3);
            i1 += 1;
            i2 += 1;
        }
    }
}

The method starts by creating a mesh if necessary. It then uses spherical coordinates to generate points on the sphere. It loops through phi values between 0 and π/2, and theta values between 0 and 2π.

As it generates the points, it adds them to the mesh’s Positions collection. It also adds their texture coordinates to the mesh’s TextureCoordinates collection. The texture coordinates are scaled so they range from 0 to 1 for the phi and theta values.

After is finishes defining the points, the code loops through the phi and theta values again and defines the sphere’s triangles. It takes some non-trivial bookkeeping to keep track of which points should be combined to make the triangles.

Because adjacent triangles share vertices, the sphere is smooth.

It may not be obvious, but finding a good image to wrap around the sphere is one of the harder parts of the program. This example uses the best globe image I could find after a fair amount of searching.

[3D globe]

When the program is working with the top and bottom of the sphere, the trapezoids it uses to build the sphere degenerate into triangles as shown in the picture on the right. That means the texture image fits the sides reasonably well but it probably won’t fit the top or bottom very well.



There are several ways you can address that problem.

[3D globe]

  • You can use a lot of phi values. That makes the triangles at the sphere’s top and bottom smaller so any mismatch is smaller.
  • You can pick a texture image that has a solid color at the top and bottom. If the triangles are solid colors, then they will match up well.
  • You can explicitly pick a texture designed to exactly fit the sphere. Its top and bottom would be warped to fit triangular areas. Unfortunately mapping the points to places on the warped image would be hard.
  • You could use a geodesic tiling of the sphere. In theory that would provide the best result but I don’t even know where you would get a texture map for that.
  • You can just ignore the issue. If the texture is chaotic, the user probably won’t notice.

This is why there are so many different kinds of maps of the globe. None of them are perfectly accurate everywhere. The most accurate map of the Earth is a globe.

This example relies on the fact that the North Pole is mostly water, the South Pole is mostly land, and that most people don’t really know what the poles look like in detail.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in algorithms, drawing, geometry, graphics, mathematics, wpf, XAML and tagged , , , , , , , , , , , , , , , , . Bookmark the permalink.

3 Responses to Make a 3D globe in WPF and C#

  1. Juergen Schroether says:

    Hello,
    pieces of work like yours mentioned above are simply priceless, if one’s about to struggle through the complex worlds of WPF on one hand – hoping to be spared entering the not less complex worlds of OpenGL on the other.

    With regard to my current issue of concern, it seems unclear to me, whether i’ll be that lucky (or not):

    Compared to programs of similar functionality using OpenGL, one problem of using WPF in the current context always seems to be having relatively blurred images on one’s globe, regardless of how high the used image’s resolution ever may be. This point especially concerns mid- or equatorial latitudes, the polar regions instead being represented quite crisp.

    Do you – or anybody else – have an idea how that could be cured?

    Sincerely & with best regards

    Juergen

    • RodStephens says:

      I don’t think I’ve seen this problem. Maybe it has to do with the interpolation that the drawing system uses.

      If you zoom in closely enough, any image will become pixellated. No matter how fine the texture is, you can still zoom in closer until it doesn’t look right. You might be able to fix that if you use multiple textures for different zoom levels, but that would be hard at best and in the worst case you would need to write your own vertex mapper.

      Sorry I don’t have a better answer for you.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.