Draw a 3D surface from a set of data points with an altitude map using WPF and C#

3D surface from a set of data points

This example uses C# and XAML to draw a 3D surface from a set of data points that’s overlaid with a shaded altitude map. The example Draw a 3D surface overlaid with a shaded altitude map using WPF and C# explains how to draw a 3D surface generated by a function. This example generates its surface from a set of data points stored in an array.

The previous example also stores its data points in an array of values, so this version only needs a few changes. The following method defines the data.

// Make the data.
private double[,] MakeData()
{
    double[,] values =
    {
        {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}
    };

    xmin = 0;
    xmax = values.GetUpperBound(0);
    dx = 1;
    zmin = 0;
    zmax = values.GetUpperBound(1);
    dz = 1;

    texture_xscale = (xmax - xmin);
    texture_zscale = (zmax - zmin);

    return values;
}

The code creates a values array, sets a few global parameters, and then returns the values.

The CreateAltitudeMap method is similar to the previous version except it takes its values as an array passed into it as a parameter instead of using a function to generate the values. See the previous example for information about that method.

The last significant change is to the DefineModel method shown in the following code.

// Add the model to the Model3DGroup.
private void DefineModel(Model3DGroup model_group, double[,] values)
{
    // Make a mesh to hold the surface.
    MeshGeometry3D mesh = new MeshGeometry3D();

    // Make the surface's points and triangles.
    float offset_x = xmax / 2f;
    float offset_z = zmax / 2f;
    for (int x = xmin; x <= xmax - dx; x += dx)
    {
        for (int z = zmin; z <= zmax - dz; z += dx)
        {
            // Make points at the corners of the surface
            // over (x, z) - (x + dx, z + dz).
            Point3D p00 = new Point3D(
               x - offset_x,
               values[x, z],
               z - offset_z);
            Point3D p10 = new Point3D(
               x - offset_x + dx,
               values[x + dx, z],
               z - offset_z);
            Point3D p01 = new Point3D(
               x - offset_x,
               values[x, z + dz],
               z - offset_z + dz);
            Point3D p11 = new Point3D(
               x - offset_x + dx,
               values[x + dx, z + dz],
               z - offset_z + dz);

            // Add the triangles.
            AddTriangle(mesh, p00, p01, p11);
            AddTriangle(mesh, p00, p11, p10);
        }
    }
    Console.WriteLine(mesh.Positions.Count + " points");
    Console.WriteLine(mesh.TriangleIndices.Count / 3 +
        " triangles");
    Console.WriteLine();

    // 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);

    // Make the mesh's model.
    GeometryModel3D surface_model =
        new GeometryModel3D(mesh, surface_material);

    // Make the surface visible from both sides.
    surface_model.BackMaterial = surface_material;

    // Add the model to the model groups.
    model_group.Children.Add(surface_model);
}

This method creates triangles representing the surface to draw. This version is the same as the previous one with two changes. First, the previous version uses a function F to generate the Y coordinates for the points inside the nested for loops. This version takes its values from the values array.

Second, this version subtracts the values offset_x and offset_z from the points’ X and Z coordinates to center the surface at the origin in the X and Z directions.

The rest of the program is more or less the same as the previous version.


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, geometry, graphics, mathematics, wpf, XAML and tagged , , , , , , , , , , , , , , , , , , . Bookmark the permalink.

29 Responses to Draw a 3D surface from a set of data points with an altitude map using WPF and C#

  1. annon says:

    howto_whatever.zip?

  2. Arun Kumar K S says:

    Thank you very much for this article… This is not smooth as your previous example but this is very helpful for anyone who want to generate a 3d altitude map from an array of data.

    • RodStephens says:

      It’s not as smooth because the data isn’t as smooth. The previous surface examples used 10,000 points and 19,602 triangles. This example uses only 100 points and 162 triangles.

  3. Pingback: Draw cylinders using WPF and C# - C# HelperC# Helper

  4. Arun Kumar K S says:

    I tried to update the graph from a timer event But that drawn only the first image. In the second timer call the function draw an ugly image and in third it shown as blank. Here is the code used to update the graph.

    public void DrawNewGraph(float[,] newval)
    {
        double[,] values = MakeNewData(newval);
    
        MainViewport.Children.Clear();
        MainModel3Dgroup.Children.Clear();
        // Create an altitude map.
        CreateAltitudeMap(values);
    
        // Create the model.
        DefineModel(MainModel3Dgroup, values);
    
        // Add the group of models to a ModelVisual3D.
        ModelVisual3D model_visual = new ModelVisual3D();
        model_visual.Content = MainModel3Dgroup;
    
        // Display the main visual to the viewportt.
        MainViewport.Children.Add(model_visual);
    }
  5. Arun Kumar K S says:

    Please check this sample application I have uploaded in this link. In this sample application added a button to draw another graph. but that draw incorrectly. Please help me,I spend several days to draw this type of graph.

    http://www.filedropper.com/howtosamplealtitudemap

  6. Arun Kumar K S says:

    I apologies,
    I solved the issue. Actually I forgot to clear one array
    PointDictionary = new Dictionary(); in that application.

    Thank you for your valuable time and help.

  7. Manh Tuyen says:

    Dear RodStephens!

    I want to draw to canvas control a 3d-surface. The surface contains a series of discreate spline. Each spline contain a series of discreate point like a picture.http://tinypic.com/view.php?pic=bia592&s=8#.Vf-xaNKvGUk
    Could you have any idea for solution?

    thanks!

  8. Ramesh says:

    i had created a graph with numericupdown values as x and y coordinate , now i need to connect the points(x axis and y axis) as triangle mesh .help me out with solution.here is the code for graph plotting

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication10
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void chart1_Click(object sender, EventArgs e)
            {
                chart1.ChartAreas.Add("ChartArea1");
                chart1.ChartAreas[0].AxisX.Minimum = 0;
                chart1.ChartAreas[0].AxisX.Maximum = 20;
                chart1.ChartAreas[0].AxisX.Interval = 1;
                chart1.ChartAreas[0].AxisY.Minimum = 0;
                chart1.ChartAreas[0].AxisY.Interval = 10;
    
                chart1.Series.Add("sara");
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                chart1.Series["Series1"].Points.AddXY((numericUpDown1.Value), (numericUpDown2.Value));
            }
        }
    }
  9. Ramesh says:

    define model method example can you explain me ?with eg

    • RodStephens says:

      Sorry but I think you’re going to need to do this yourself. I don’t really know how your program works (and I don’t have the time to build something similar).

      Just take the values you’re getting from the NumericUpDown and put them in a two-dimensional array. Then you should be able to use the code from this example to create the surface.

  10. Edward says:

    Hello Stephens,

    First of all, thank you for your amazing explanation and implementation!

    Currently, I am using this implementation and I am trying to extend, such that it is also possible to draw 2D polygon on the surface. So basically, I have a 2D polygon (for example, it represents a river) and I want to apply this polygon on the 3D surface, such that I have a surface with some hills (explained here) and a river. To make it easier, the polygon is placed on the surface where the height/altitude is 0. However I am out of idea of how to do this.
    Do you have any tips on how to achieve this?

    Thanks in advance,
    Edward

  11. Kenneth says:

    I am need to plot scattered points data on a 3D plot but instead of using the standard shapes I see in many implementation, I want to plot short labels that identify the points, such as 1,2,3 or P1, P2, P3… I was trying to work with the helix-toolkit-master but no luck with that yet, any ideas?
    Thanks in advance!

    • RodStephens says:

      Sorry but I haven’t used helix.

      In WPF you would have two options. First you could draw a rectangle that used the label as its texture. That would work but would give the label a background. You can make the background color transparent, but WPF doesn’t seem to handle that perfectly from all angles if you have multiple labels.

      The second approach would be to convert the labels into polygons (possibly using GraphicsPath) and draw them. Then you would be able to see through the holes in the text.

      I don’t know if that helps with helix, though. Sorry.

      • Kenneth says:

        I appreciate your input, the helix toolkit does use WPF, and I can only add Spheres, Triangles etc… This is one of those cases that where I have to replace and old implementation of a similar component. We build this things back on the late 80s, but is written/compile with symantec, making all difficult.
        I was thinking along the same lines as what your suggestion, I was looking to see that instead of an ellipsoid, if a glyph object representing a letter value could be use as a point.

        old app: http://www.adeptscience.co.uk/wp-content/uploads/2013/07/view3d.gif

  12. joe valdivia says:

    Nice tutorial. How could I modify this to make a cylinder using using data points.
    I know you have a cylinder tutorial that I have messed with. I want to make a cylinder from data points that I generate?

    • RodStephens says:

      I don’t know what kind of data you have, but you should be able to do something similar to this post. Just use the adjacent data values to generate the polygons that make up the cylinder’s surface.

      • joe valdivia says:

        What I have is about 32 lasers and they are lined up length wise so they point along the length of various dia tubes. The tubes are then rotated 360 Degrees. At every degree of rotation the measurements of the distance from each laser to the tube surface are loaded into an array. So when I am done rotating the tube I have 360 arrays with measurements of the tube. I want to take the measurements and make a wireframe image of the tube. Any ideas on would be helpful. By the way I am working with WPF and C#

  13. joe valdivia says:

    OK here is the real deal. I work in a plywood mill. 8 ft logs are chucked into a giant lathe and pealed. Before the logs are placed into the lathe they are put into spotting spindles and rotated 360 degrees. The lasers measure the diameter of the log and determine the best position to place the log in the lathe to get maximum recovery when the logs as they are pealed. I have figured out how to replace the lasers with the depth data from a Kinect V2 camera. I want to use the depth data from the Kinect V2 to build a wire frame representation of the log for the operators to see. I am moving one row off pixel depth data from the Kinect into a array every degree of rotation. After one 360 degree rotation I have 360 arrays full of depth data measurements. I have already modified this program to only display the wire frame. I know this is asking a lot but any help would be appreciated.

  14. ertos says:

    Thank you for sample that you published. I made a project and it has 0 betveen 4.99 data inputs. I wanna divide this two datas and taking middle and threshold value. Also i wanna draw up and bottom of threshold value to the down. I wanna pick some values up of threshold value and draw with color red and i wanna pick some values bottom of threshold value and draw with color blue. The other values are green. I am so beginner degree about that subject, i hope you ll help me. Thank you already wish you peacefull happy days. Best regards .

    • RodStephens says:

      It sounds like you want to have normal values green, low values blue, and high values red. You should be able to modify it to do that. Just change the Texture.png file so it uses the right colors.

      This example may work better.

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

      Its CreateAltitudeMap method builds the altitude map in the Texture.png file. You can modify it to use the colors you want.

      Or you might be better off using this example:

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

      One way to do it would be to create three models with three materials that are red, green, and blue. Then put each triangle in the appropriate model. That would probably be the easiest approach.

      Unfortunately this kind of program isn’t easy so you need some practice. And I don’t have time to do it for you. (If you want me to consult on this, email me.)

Leave a Reply

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