Title: Draw a 3D surface overlaid with a shaded altitude map using WPF and C#
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 experiment with it and to see additional details.
|