Draw a 3D surface overlaid with a shaded altitude map using WPF and C#


3D surface overlaid with a shaded altitude map

This example uses C# and XAML to draw a 3D surface overlaid with a shaded altitude map. The example Draw a 3D surface overlaid with a grid using WPF and C# explains how to overlay an image on a three-dimensional surface. This example does something similar. Instead of simply using an existing image containing a grid, however, it generates an image that is shaded to show the surface’s height.

The following CreateAltitudeMap method generates the texture bitmap.

// Create the altitude map texture bitmap.
private void CreateAltitudeMap()
{
    // Calculate the function's value over the area.
    const int xwidth = 512;
    const int zwidth = 512;
    const double dx = (xmax - xmin) / xwidth;
    const double dz = (zmax - zmin) / zwidth;
    double[,] values = new double[xwidth, zwidth];
    for (int ix = 0; ix < xwidth; ix++)
    {
        double x = xmin + ix * dx;
        for (int iz = 0; iz < zwidth; iz++)
        {
            double z = zmin + iz * dz;
            values[ix, iz] = F(x, z);
        }
    }

    // Get the upper and lower bounds on the values.
    var get_values =
        from double value in values
        select value;
    double ymin = get_values.Min();
    double ymax = get_values.Max();

    // Make the BitmapPixelMaker.
    BitmapPixelMaker bm_maker =
        new BitmapPixelMaker(xwidth, zwidth);

    // Set the pixel colors.
    for (int ix = 0; ix < xwidth; ix++)
    {
        for (int iz = 0; iz < zwidth; iz++)
        {
            byte red, green, blue;
            MapRainbowColor(values[ix, iz], ymin, ymax,
                out red, out green, out blue);
            bm_maker.SetPixel(ix, iz, red, green, blue, 255);
        }
    }

    // Convert the BitmapPixelMaker into a WriteableBitmap.
    WriteableBitmap wbitmap = bm_maker.MakeBitmap(96, 96);

    // Save the bitmap into a file.
    wbitmap.Save("Texture.png");
}

The method starts by calculating the surface function’s value over the area being drawn. It calculates a value for each pixel in the image it will create, in this case a 512×512 pixel image. The code then uses the LINQ Min and Max methods to get the largest and smallest values in the array.

The code then makes a BitmapPixelMaker object. (See this post.) It loops over the pixels in the image and uses the corresponding function value to determine a color for the pixel. The code uses the MapRainbowColor method (described shortly) to map each function value to an appropriate color.

The method finishes by calling the BitmapPixelMaker object’s MakeBitmap method to create a WriteableBitmap, and then using the bitmap’s Save extension method to save the result into a file. (See this post.)

The MapRainbowColor method uses the following code to map a value between given bounds to a color.

// Map a value to a rainbow color.
private void MapRainbowColor(double value,
    double min_value, double max_value,
    out byte red, out byte green, out byte blue)
{
    // Convert into a value between 0 and 1023.
    int int_value = (int)(1023 * (value - min_value) /
        (max_value - min_value));

    // Map different color bands.
    if (int_value < 256)
    {
        // Red to yellow. (255, 0, 0) to (255, 255, 0).
        red = 255;
        green = (byte)int_value;
        blue = 0;
    }
    else if (int_value < 512)
    {
        // Yellow to green. (255, 255, 0) to (0, 255, 0).
        int_value -= 256;
        red = (byte)(255 - int_value);
        green = 255;
        blue = 0;
    }
    else if (int_value < 768)
    {
        // Green to aqua. (0, 255, 0) to (0, 255, 255).
        int_value -= 512;
        red = 0;
        green = 255;
        blue = (byte)int_value;
    }
    else
    {
        // Aqua to blue. (0, 255, 255) to (0, 0, 255).
        int_value -= 768;
        red = 0;
        green = (byte)(255 - int_value);
        blue = 255;
    }
}

The code first scales the value so it ranges from 0 to 1023. Depending on whether the value is the range 0 – 255, 256 – 511, 512 – 767, or 768 – 1023, the code converts the color into different parts of a rainbow.

The rest of the program is similar to the previous one that maps a grid onto the surface. The following code shows how this example uses the texture image saved by the CreateAltitudeMap method to create the surface’s material.

// Make the surface's material using an image brush.
ImageBrush texture_brush = new ImageBrush();
texture_brush.ImageSource =
    new BitmapImage(new Uri("Texture.png", UriKind.Relative));
DiffuseMaterial surface_material =
    new DiffuseMaterial(texture_brush);

Download the example to see additional details.


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.

27 Responses to Draw a 3D surface overlaid with a shaded altitude map using WPF and C#

  1. Dietrich Hadler says:

    Hi,
    I have downloaded the code for the previous surface examples, and they are truly fantastic!
    Could you make the code for this example available as well?

    Best regards,

    Dietrich

  2. RodStephens says:

    Sorry. I looked at the post and I think my ISP’s conversion to WordPress messed up the download links. I’ve rebuilt this example (and the other one you commented on). I’ll try to do the others in this series soon.

  3. Dietrich Hadler says:

    Thank you! All of the examples work very well, and the output looks fantastic not only on screen but also when printed as pdf.
    I have played with the examples a bit, and have a number of questions:

    1. Is there an EASY way to toggle between a perspective camera and an orthographic camera, while keeping all features available (including zooming in and out)? I have naively replaced the perspective camera with an orthographic camera, and while it does display and rotate the mesh, the size is not right, and (in the absence of a FieldOfView) zooming in and out does not work.

    2. Is there an EASY way to shift the output vertically and horizontally on the canvas? This does have some inportance when printing the output (to pdf, for example).

    3. Could you provide an example on how to exchange the existing mesh with another one without having to restart the whole program? I guess that this must be really easy of one knows what one is doing, but my own attempts have all failed miserably.

    4. The current examples do not include a 3D coordinate system (i.e. x, y, z axis with tics and numbers), and I presume that the reason is that this is really hard to do in the absence of 3D lines. I was wondering however whether you could provide an example which “embeds” the mesh in a cube, which in turn has the appropriate tics, numbers, lables etc. attached to it (in 2D on its surfaces).

    5. The printed putput of the 3D mesh is always a bitmap (in really high quality, though). In principle it would be desirable to have everything that does not need to be a bitmap (i.e. axes, tics, labels, titles) in vector format. When using WPF, this seems to mean that this part would have to be in 2D. So instead of embedding 2D in 3D (as in question 4), this would mean embedding the 3D output (a bitmap) into the 2D rendering of a xyz coordinate system. My attempts to do this in WPF were unsuccessful, but I have managed to overlay the PNG output of your examples on top of a pseudo 3D coordinate system using the System.Windows.Forms.DataVisualization.Charting module in a WinForms application. When printed as pdf, everything is in vector format, except the bitmap.
    Do you know whether anyone has done this in WPF?

    Best regards,

    Dietrich

    • RodStephens says:

      Here are some quick answers. Providing examples will take a while.

      1. Is there an EASY way to toggle between a perspective camera and an orthographic camera, while keeping all features available (including zooming in and out)? I have naively replaced the perspective camera with an orthographic camera, and while it does display and rotate the mesh, the size is not right, and (in the absence of a FieldOfView) zooming in and out does not work.

      Because an orthographic camera preserves sizes, you can’t really zoom in and out with one. All you can do is take an image and then enlarge or shrink it, which is similar but not quite the same. For the same reason, the size won’t quite match the size of the perspective camera.

      You can probably use a ScrollViewer to zoom in on an image. See Zoom on a graph in WPF and C# for an example that uses one.

      A better approach might be to apply a transformation to the objects in the scene. For example, you can scale them by a factor of 2 or 0.5.

      You should be able to swap cameras in code without too much trouble. (Or at least not more trouble than anything else is with WPF in code.)

      2. Is there an EASY way to shift the output vertically and horizontally on the canvas? This does have some inportance when printing the output (to pdf, for example).

      You should be able to apply a transformation to either the scene’s objects or the camera. Basically you can “hold” the camera sideways.

      3. Could you provide an example on how to exchange the existing mesh with another one without having to restart the whole program? I guess that this must be really easy of one knows what one is doing, but my own attempts have all failed miserably.

      You should be able to do this, although I haven’t done it. You should be able to change the values in a MeshGeometry3D object or replace an entire MeshGeometry3D. I’ll try to write an example, but it’s going to be a while. I have a bunch of 3D examples I need to get to first. (Subscribe to my Twitter feed, the site’s RSS feed, or just keep an eye on the index or bloig page to look for new examples.)

      4. The current examples do not include a 3D coordinate system (i.e. x, y, z axis with tics and numbers), and I presume that the reason is that this is really hard to do in the absence of 3D lines. I was wondering however whether you could provide an example which “embeds” the mesh in a cube, which in turn has the appropriate tics, numbers, labels etc. attached to it (in 2D on its surfaces).

      Some of the examples (which I haven’t had time to rebuild yet) do have axes, although without tic marks. There’s no reason why you couldn’t add them, either with short line segments, small cubes, or small spheres. (I’d use small cubes). I’ll try to make an example but, again, it could be a while.

      5. The printed putput of the 3D mesh is always a bitmap (in really high quality, though). In principle it would be desirable to have everything that does not need to be a bitmap (i.e. axes, tics, labels, titles) in vector format. When using WPF, this seems to mean that this part would have to be in 2D. So instead of embedding 2D in 3D (as in question 4), this would mean embedding the 3D output (a bitmap) into the 2D rendering of a xyz coordinate system. My attempts to do this in WPF were unsuccessful, but I have managed to overlay the PNG output of your examples on top of a pseudo 3D coordinate system using the System.Windows.Forms.DataVisualization.Charting module in a WinForms application. When printed as pdf, everything is in vector format, except the bitmap.

      Do you know whether anyone has done this in WPF?

      I don’t know of anyone who’s done this. I think you could apply transformations to the axes’ end points to see where they would be drawn on the scene and then draw them yourself. It sounds like it might be tricky, but I haven’t done it.

  4. Dietrich Hadler says:

    Thank you very much for your comprehensive answer.
    I am looking forward to more examples!

  5. Arun Kumar K S says:

    I am new to WPF. I spend several days to draw a 3D Heatmap and found your article
    “Draw a 3D surface overlaid with a shaded altitude map using WPF and C#” looks fantastic. I go through the source code but I failed to create a 3D map from N*N matrix values. Could you provide a sample code to draw a 3D surface using an array of values. For example the array like

    //double[,] Z = new double[,]{{0,0,0,1,2,2,1,0,0,0},
    // {0,0,2,3,3,3,3,2,0,0},
    // {0,2,3,4,4,4,4,3,2,0},
    // {2,3,4,5,5,5,5,4,3,2},
    // {3,4,5,6,7,7,6,5,4,3},
    // {3,4,5,6,7,7,6,5,4,3},
    // {2,3,4,5,5,5,5,4,3,2},
    // {0,2,3,4,4,4,4,3,2,0},
    // {0,0,2,3,3,3,3,2,0,0},
    // {0,0,0,1,2,2,1,0,0,0}};
    // GnuPlot.HeatMap(Z);

    and the generated graph like this image.
    http://howtobyexamples.blogspot.in/2011/10/gnuplot-gnuplot-3d-plot-example.html

  6. Pingback: Draw a 3D surface from data points using WPF and C#C# Helper

  7. TT says:

    Dear Rod firs of all thank you for all this example.
    In this example I change to camera look direction code
    from :
    TheCamera.LookDirection = new Vector3D(-x, -y, -z);
    to :
    TheCamera.LookDirection = new Vector3D(0,0,0);
    But it dosen’t work. I can’t see anything. Each code direction intersect the 0,0,0 coordinate, isn’t it?

    • RodStephens says:

      The LookDirection is a vector that tells the camera in what direction to look. It’s not a point that the camera should be pointed at. For example, the vector <10, 0, 0> would mean the camera should look in the positive X direction parallel to the X axis.

      The vector <0, 0, 0> doesn’t tell the camera to look in any direction. I’m actually not sure how it interprets this. It may think the camera shouldn’t point in any direction. (I’m a bit surprised it doesn’t throw an exception.)

      So the vector <-x, -y, -z> makes the camera point back from the camera’s location (x, y, z) towards the origin (0, 0, 0), which is where the data lies.

      If you want to experiment with it a bit, try setting the direction to <-x, 1 – y, -z>, which would point the camera 1 unit above the origin. Or more generally, <a – x, b – y, c – z> points it toward the point (a, b, c).

  8. KONG says:

    Hello! How can you get this interpolated color on the surface? I use the MaprainbowColor function to assign color for bitmap too. But I couldn’t get this fine surface as yours. I can only have pixel-alike surface.

    • RodStephens says:

      My guess is you’re not using vertex coordinates. If you make the corners of the triangles share vertices, then the drawing system varies the colors smoothly at the corners. See this post:

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

      • KONG says:

        Really thank you for the reply. Do you know if I can run your program (XAML) in Winform? I have been trying for long. But so many bugs and lacks of references.

        • RodStephens says:

          You can look at Direct3D and XNA, but unfortunately Microsoft doesn’t provide much support for them any more. I would download the example that’s closest to what you need and then modify the code a little bit at a time to get what you want.

          • KONG says:

            I have been trying for long to find the closest way. I think this example is almost the same one. But I am failed in turning it into C#. I have already made a 2D interpolated color surface by myself. I would also like to make a 3D surface as your example.

          • RodStephens says:

            If you have the image, you should be able to replace the one this example is using with it. Then you just need to create the surface and make sure you map the points on the surface to the right texture coordinates.

            You might want to go back and work your way through the other 3D examples and work your way up to this one.

  9. Ramesh says:

    How to draw contour 3d surface by getting triangle files for eg .node files or .poly files? can you please explain me with an example?

    • RodStephens says:

      Sorry but I don’t know how to load .node or .poly files. If you can figure out how to read their data, you should be able to use them to build the triangles you need to draw the surface.

  10. Ramesh says:

    using (Graphics g = Graphics.FromImage((Bitmap)pictureBox1.Image))
    {
    foreach (TriangleNet.Data.Triangle triangle in mesh.Triangles)
    {
    lstPoints = new List();

    foreach (TriangleNet.Data.Vertex vertex in triangle.Vertices)
    lstPoints.Add(PointFromVertex(vertex));

    g.FillPolygon(Brushes.Green,lstPoints.ToArray());
    g.DrawPolygon(Pens.Red, lstPoints.ToArray());
    }
    }

    In this i need to fill polygon with different colour . here the code is used for only green. can you help me out please

    • RodStephens says:

      There are two main approaches to giving things different colors. First you can create a different model for each and give each model a different material. This is probably the wrong approach for this.

      The second method is to create a single bitmap that has patches with all of the colors you need. You can do that using any bitmap editor. Then set each triangle’s texture coordinates so it displays colors in the part of the image that you want.

      For example, you could make a 200×200 pixel bitmap divided into 50×50 pixel patches holding 16 different colors. Then use the texture coordinates to place the triangles in the correct patches.

  11. Ramesh says:

    How to convert .poly file(triangle file) into image using c# code. when i choose .poly file it is not displaying in my picturebox.

Leave a Reply

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