A Heightfield is a two-dimensional array in X and Y. The array values represent the Z value of a surface at each point.
The surface is smoothly interpolated between the defining points using a linear filter.
A heightfield primitive is defined in the square from 0 to 1 in X and Y, and the surface ranges from 0 to +1 in Z. The bottom of the heightfield is -1 Z.
A heightfield can be defined in two ways. You can define a 2 dimensional array of data which represents the height of the surface at each point or you can use a grayscale image file to represent the height of the surface at the points in the array.
Array Construction
object new_heightfield(int smooth_normals, int stop, int x_size, int y_size, height_val *field_data);
The parameters that the Heightfield takes are:
smooth_normals
The heightfield is defined by patches for each array point.
This flag says whether a smoothing function should be used on the normals at each point.
This is noticeable where the heightfield normals are not a continuous function.
Figure 27 shows an example with and without smoothing normals.
stop
A flag, either TRUE or FALSE that controls whether or not a ray stops when it hits an intersection with the heightfield.
If this is TRUE, the heightfield will raytrace faster, but subtracting objects from it may not work correctly.
x_size and y_size
the X and Y dimensions of the array of heightfield data.
field_data
an unsigned char * to an array of unsigned char field values.
The array values are type height_val which are integer values between 0 to 65534.
The height of the surface at each point is: array value / 65534.
The Heightfield constructor creates a Primitive object and then the newPrimitive routine to converts the Primitive into an object.
x = Dagnode::newPrimitive(new Heightfield(stop, jitter, x_size, y_size, field_data));
The example shows how to allocate an array of data, assign values to the array and create the heightfield for a four sided turret where the slope of each face is a cubic.
Figure 27: The Heightfield Primitive created with Array of Height Values
//Add a heightfield data construction primitive // Allocate an array of height_val data for the heightfield. int arraysize = 40; height_val * field_data = (height_val *) calloc(SQR(arraysize), sizeof(height_val)); if (field_data == NULL) { fatal_error("make_heightfield", "can't allocate field data", "must be low memory."); } // Fill the field data. int x_index, y_index; printf("Creating heightfield data ..."); fflush(stdout); for(x_index = 0; x_index<arraysize; x_index++) { for(y_index = 0; y_index< arraysize; y_index++) { int section; double value; if (x_index > y_index) { if (arraysize - x_index > y_index) section = 1; else section = 2;} else { if (arraysize - x_index > y_index) section = 3; else section = 4;} if (section == 1) value = pow(y_index/(arraysize/2.0), 3); else if (section == 2) value = pow((arraysize - x_index)/(arraysize/2.0), 3); else if (section == 3) value = pow(x_index/(arraysize/2.0), 3); else value = pow((arraysize - y_index)/(arraysize/2.0), 3); field_data[x_index + arraysize * y_index] = (unsigned short) (value * 65534); } } printf(" done.\n"); object turret = Dagnode::newPrimitive(new Heightfield(TRUE, TRUE, arraysize, arraysize, field_data)); turret->material = terracotta; LINK(the_scene, PLUS, bind(turret));
Grayscale Image Construction
object new_heightfield(int smooth_normals, int stop, int do_jitter, char *filename);
The parameters that the Heightfield takes are:
smooth_normals and stop
same as for array construction heightfield.
jitter
a flag to say whether or not to add a small random amount when constructing a heightfield from a file's gray values.
filename
the name of the image file to read. Must be a ppm, pgm or pbm file.
Figure 28 and the example code shows a grayscale image and the primitive constructed from it. It also shows hills that have been created by stretching the heightfield and posts that have been placed on the heightfield using HugHeightfield affect.
Objects can be placed on the height field using the HugHeightfield affect. The object is positioned so the point which you want to sit on the height field is at the origin. The object is bound and labelled in this position. The object is shifted to the correct x and y coordinates and the HugHeightfield affect positions it so the z coordinate of the point initially at the origin is at the position of the heightfield.
Figure 28: The Heightfield Primitive created with Grayscale Image
// Creating a heightfield primitive. the_object = new_heightfield(TRUE,TRUE,FALSE,"hills.pgm"); the_object->material = dkgreen; the_object->label("hills"); //Stretching the primitive to create hills LINK(the_scene, PLUS, stretch(9,9,1,bind(the_object))); //Make an object to place on the hills object the_post = shift(0.0,0.0,-0.1,stretch(0.05, 0.05, 0.75, new_unitcyl())); the_post->material = brown; object the_post1 = bind(the_post); the_post1->label("post1"); the_post1->affect = new HugHeightfieldAffect("post1","hills"); object the_post2 = bind(the_post); the_post2->label("post2"); the_post2->affect = new HugHeightfieldAffect("post2","hills"); object the_post3 = bind(the_post); the_post3->label("post3"); the_post3->affect = new HugHeightfieldAffect("post3","hills"); object the_post4 = bind(the_post); the_post4->label("post4"); the_post4->affect = new HugHeightfieldAffect("post4","hills"); object the_post5 = bind(the_post); the_post5->label("post5"); the_post5->affect = new HugHeightfieldAffect("post5","hills"); object the_post6 = bind(the_post); the_post6->label("post6"); the_post6->affect = new HugHeightfieldAffect("post6","hills"); object the_post7 = bind(the_post); the_post7->label("post7"); the_post7->affect = new HugHeightfieldAffect("post7","hills"); object the_post8 = bind(the_post); the_post8->label("post8"); the_post8->affect = new HugHeightfieldAffect("post8","hills"); object the_post9 = bind(the_post); the_post9->label("post9"); the_post9->affect = new HugHeightfieldAffect("post9","hills"); LINK(the_scene, PLUS, shift(3.8, 0.75, 0.0, bind(the_post1))); LINK(the_scene, PLUS, shift(3.0, 0.75, 0.0, bind(the_post2))); LINK(the_scene, PLUS, shift(2.2, 0.73, 0.0, bind(the_post3))); LINK(the_scene, PLUS, shift(1.4, 0.71, 0.0, bind(the_post4))); LINK(the_scene, PLUS, shift(0.6, 0.76, 0.0, bind(the_post5))); LINK(the_scene, PLUS, shift(0.6, 1.6, 0.0, bind(the_post6))); LINK(the_scene, PLUS, shift(0.6, 2.4, 0.0, bind(the_post9))); LINK(the_scene, PLUS, shift(4.6, 0.75, 0.0, bind(the_post7))); LINK(the_scene, PLUS, shift(5.4, 0.75, 0.0, bind(the_post8)));