ECG/exercise3/src/terrain.cpp
2019-06-01 13:14:17 +02:00

562 lines
No EOL
16 KiB
C++
Raw Blame History

//
// This source code is property of the Computer Graphics and Visualization
// chair of the TU Dresden. Do not distribute in modified or unmodified form!
// Copyright (C) 2016 CGV TU Dresden - All Rights Reserved
//
#include "terrain.h"
// Load images and initialize variables
terrain::terrain()
{
initialized = false;
heightmap = 0;
angle = 0;
// The display list is not valid
dl_valid = false;
dl_handle = 0;
// Try to load the heightmap
if (!load_heightmap("../../data/tex_height.bmp"))
return;
// Try to load the texture
if (!load_texture("../../data/tex_topo.bmp", &texture_handle))
return;
initialized = true;
// Basic initial settings
set_show_solid(true);
set_show_wireframe(false);
set_show_levels(false);
}
// Unload images and textures
terrain::~terrain()
{
// Delete the texture
glDeleteTextures(1, &texture_handle);
// Free the memory for the height map
BMP_Free(heightmap);
// Delete the display list if neccessary
if (glIsList(dl_handle))
glDeleteLists(dl_handle, 1);
}
// Render the actual scene
void terrain::render()
{
// Enable depth testing
glEnable(GL_DEPTH_TEST);
// Enable automatic normalization of normals
glEnable(GL_NORMALIZE);
// Clear the screen
glClearColor(1,1,1,0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Nothing to do if we are not properly initialized
if (!initialized)
return;
// Setup perspective and modelview matrix
setup_projection();
// Setup light
setup_light();
// Render the solid terrain with lighting and texture mapping
if (show_solid)
render_solid_terrain();
// Render the outline of the terrain
if (show_wireframe)
render_wireframe_terrain();
// Eventually render level lines
if (show_levels)
render_level_lines();
}
// Render the terrain as solid
void terrain::render_solid_terrain()
{
// Enable lighting
glEnable(GL_LIGHTING);
// This is a trick which is neccessary in order to draw lines
// later on. Otherwise there would be artifacts due to a z-fight.
glPolygonOffset(1, 1);
glEnable(GL_POLYGON_OFFSET_FILL);
/********
Task 2.2.4. Activate 2D texture mapping and bind the texture that
is identified by the handle "texture_handle". Do not remove
any of the code in this method.
Aufgabe 2.2.4. Aktivieren Sie 2D-Texturierung und binden Sie die
Textur, die ueber das Handle "texture_handle" identifiziert
ist. Entfernen Sie keinen Code in dieser Methode.
************/
// Set the material color to white
glColor3d(1, 1, 1);
// Render the terrain
render_terrain();
// Disable texture mapping
glDisable(GL_TEXTURE_2D);
// Disable support for depth buffer offsets
glDisable(GL_POLYGON_OFFSET_FILL);
// Disable lighting
glDisable(GL_LIGHTING);
}
// Render the terrain as wireframe
void terrain::render_wireframe_terrain()
{
// Set the line width to be 1 pixel
glLineWidth(1.0);
// Set the draw mode to draw outlines of polygons
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// Set the color to black
glColor3d(0, 0, 0);
// Render the terrain
render_terrain();
// Set the draw mode to fill polygons
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
// Render the terrain
void terrain::render_terrain()
{
// Store width and height for faster access
int map_width = get_heightmap_width();
int map_height = get_heightmap_height();
// Move and scale the coordinate system so that we can work with
// whole units. That means that a vertex at height map position (x, y)
// with a height of h can be placed as glVertex3d(x, h, y). The
// terrain extents in the XZ-layer while the height is on the Y-axis.
glPushMatrix();
glTranslated(-1, 0, -1);
glScaled(2.0/static_cast<double>(map_width), 1.0/256.0, 2.0/static_cast<double>(map_height));
/********
Task 2.2.1. Complete the code below to create a regular grid which expands
along the X-Z-layer. Create the grid by specifying triangle
strips for each line. The coordinate system is already scaled
and translated to use the values of the variables "x" and "y"
defined below directly as coordinates. The initial height of
the grid shall be 0.
Aufgabe 2.2.1. Vervollstaendigen Sie den nachfolgenden Quelltext um die
Erstellung eines regelmaessigen Gitters das sich ueber die
X-Z-Ebene erstreckt. Erstellen Sie dieses Gitter, indem Sie
zeilenweise Dreiecksstreifen definieren. Das Koordinatensystem
ist bereits skaliert und verschoben, so dass Sie die Werte
der Variablen "x" und "y", die unten definiert werden direkt
als Koordinaten einsetzen koennen. Das Gitter soll zunaechst
eine Hoehe von 0 besitzen.
Task 2.2.2. Now elevate the height of the vertices to create the grid terrain
that corresponds to the height map. Use "get_heightmap_value(x, y)".
Aufgabe 2.2.2. Heben Sie jetzt die Vertices an um ein Drahtgitterterrain zu erstellen,
das zur Hoehenkarte korrespondiert. Nutzen Sie die Methode
"get_heightmap_value(x, y)".
Task 2.2.3. Make sure to call "set_normal" for every vertex and continue this
task in "set_normal".
Aufgabe 2.2.3. Stellen Sie sicher, dass Sie "set_normal" f<>r jeden Vertex aufrufen
und fahren Sie in "set_normal" mit der Aufgabe fort.
Task 2.2.4. Activate texture mapping and bind the texture in the method
"render_solid_terrain". Provide 2D texture coordinates per vertex in
this method using "glTexCoord2d".
Aufgabe 2.2.4. Aktivieren Sie Texturmapping und binden Sie eine Textur in der
Methode "render_solid_terrain". Spezifizieren Sie in dieser Methode pro
Vertex eine Texturkoordinate mittels der Methode "glTexCoord2d".
*********/
// Go through all rows (-1)
for (int y = 0; y < map_height-1; y++) {
glBegin(GL_TRIANGLE_STRIP);
// Draw one strip
for (int x = 0; x < map_width; x++) {
glVertex3f(static_cast<float>(x), get_heightmap_value(x, y), static_cast<float>(y));
glVertex3f(static_cast<float>(x), get_heightmap_value(x, y + 1), static_cast<float>(y) + 1.0f);
}
glEnd();
}
glPopMatrix();
}
// Calculate and set the normal for height map entry (x,y)
void terrain::set_normal(int x, int y)
{
/********
Task 2.2.3. Calculate the normal for the vertex that corresponds to
the height map value (x, y). You can either use forward differences,
backward differences or central differences. The latter will give
the best results. The calculation is done by first determining direction
vectors in X and Z and then using vector operations to get a vector that
is perpendicular to both. You find some examples for vector operations in
this project below. Do not forget to pass the normal to OpenGL using
the command glNormal3d.
Aufgabe 2.2.3. Berechnen Sie die Normalen fuer den Vertex der zum Hoehenwert (x, y)
gehoert. Sie koennen entweder Vorwaerts-, Rueckwaerts- oder Zentral-
Differenzen verwenden. Die letzte Methode erzeugt die besten Resultate.
Fuer die Berechnung werden zunaechst Richtungvektoren in X- und Z-Richtung
gebildet und anschliessend mittels Vektoroperationen ein zu beiden Vektoren
senkrechter Vektor berechnet. Nachfolgend ein paar Beispiele fuer Vektor-
rechnungen in diesem Projekt. Vergessen Sie nicht die Normale nach der
Berechnung mittels glNormal3d an OpenGL zu senden.
// Create a vector
vec3d vec1(1.0, 1.0, 1.0);
// Create another vector
vec3d vec2(0.5, -1.0, 2.0);
// Normalize the first vector
vec1.normalize();
// Add the first vector to the second
vec2 = vec1 + vec2;
// Increase the length of the first vector
vec1 *= 10.0;
// Calculate the dot product
double x = dot(vec1, vec2);
// Calculate the cross product
vec3d vec3 = cross(vec1, vec2);
// Get the length
double l = vec3.length();
// Get the components for a vector
double x = vec3.x();
double y = vec3.y();
double z = vec3.z();
*****************/
}
// Render height level lines
void terrain::render_level_lines()
{
// Set color to gray
glColor3d(0.5, 0.5, 0.5);
// Set the line width to 3 pixels
glLineWidth(2.0);
// Change the coordinate system so that one can work with whole units
glPushMatrix();
glTranslated(-1, 0, -1);
glScaled(2.0/static_cast<double>(get_heightmap_width()), 1.0/256.0, 2.0/static_cast<double>(get_heightmap_height()));
// Connect with lines
glBegin(GL_LINES);
// Render lines which were created in "create_level_lines" and stored into
// the list "level_lines"
for (int i=0; i<(int)level_lines.size(); i+=2) {
glVertex3d(level_lines[i ].x(), level_lines[i ].y(), level_lines[i ].z());
glVertex3d(level_lines[i+1].x(), level_lines[i+1].y(), level_lines[i+1].z());
}
glEnd();
// Set the width back to 1.0
glLineWidth(1.0);
glPopMatrix();
}
// Create height lines for the level "level"
void terrain::create_level_line(int level)
{
/********
Additional Task: Find iso lines with the "Marching Squares" algorithm for the
height value "level". Store the start and end points of the
found lines in the list "level_lines".
Zusatzaufgabe: Finden Sie Hoehenlinien mittels des "Marching Squares"-Algorithmus
fuer den Hoehenwert "level". Legen Sie die Start- und Endpunkte
der gefundenen Linien in der Liste "level_lines" ab.
*************/
}
// Set the light parameters
void terrain::setup_light()
{
// Enable lighting and colored materials
glEnable(GL_LIGHTING);
glEnable(GL_COLOR_MATERIAL);
// Enable light source 0
glEnable(GL_LIGHT0);
// Set the modelview matrix to be the identity to avoid
// having the light position moved with the terrain.
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
// Set light parameters
float position[4]={1.0f,1.0f,1.0f,1.0f};
float ambient_color[4]={0.0f,0.0f,0.0f,1.0f};
float diffuse_color[4]={1.0f,1.0f,1.0f,1.0f};
float specular_color[4]={1.0f,1.0f,1.0f,1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, position);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient_color);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_color);
glLightfv(GL_LIGHT0, GL_SPECULAR, specular_color);
glLightf(GL_LIGHT0,GL_SPOT_EXPONENT, 10.0f);
glPopMatrix();
glDisable(GL_LIGHTING);
}
// Set the projection and the view
void terrain::setup_projection()
{
// For projection choose a perspective matrix with an aperture angle of
// 45deg, an aspect ratio that corresponds to the width and height of the
// window, z_near at 0.01 and z_far at 10.0
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45, glutGet(GLUT_WINDOW_WIDTH) / static_cast<double>(glutGet(GLUT_WINDOW_HEIGHT)), 0.01, 10.0);
// For the modelview matrix choose a view from the position (2.5, 2.5, 0) to
// the position (0, 0, 0) where the up-direction is (0, 1, 0).
// Also rotate with the specified angle around the Y-axis
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(2.5,2.5,0, 0,0,0, 0,1,0);
glRotated(angle, 0,1,0);
}
// Advance one frame
void terrain::advance_frame()
{
// Increase angle and perform a modulo 360
angle = (angle+1)%360;
}
// (De)activate solid rendering
void terrain::set_show_solid(bool state)
{
show_solid = state;
// The current display list is outdated now
dl_valid = false;
}
// (De)activate wireframe rendering
void terrain::set_show_wireframe(bool state)
{
show_wireframe = state;
// The current display list is outdated now
dl_valid = false;
}
// (De)activate height level line rendering
void terrain::set_show_levels(bool state)
{
show_levels = state;
// If level lines shall be displayed then
// recreate them in equidistant ranges
if (show_levels) {
level_lines.clear();
for (int i=20; i<=255; i+=20)
create_level_line(i);
}
// The current display list is outdated now
dl_valid = false;
}
// Load the height map from the file "filename"
bool terrain::load_heightmap(const char *filename)
{
// Load the heightmap by reading a bmp file
heightmap = BMP_ReadFile(filename);
// Return false and show an error message if the file
// could not be loaded
if (BMP_GetError() != BMP_OK) {
std::cout<<BMP_GetErrorDescription()<<std::endl;
return false;
}
// All went well...
return true;
}
// Load a texture and store its handle in "handle"
bool terrain::load_texture(const char *filename, GLuint *handle)
{
BMP *bitmap;
// Load the texture by reading a bmp file
bitmap = BMP_ReadFile(filename);
// Return false and show an error message if the file
// could not be loaded
if (BMP_GetError() != BMP_OK) {
std::cout<<BMP_GetErrorDescription()<<std::endl;
return false;
}
// Get a pointer to the bitmap data
unsigned char* data = BMP_GetImageData(bitmap);
// Generate one texture and store its ID in "handle"
glGenTextures(1, handle);
// Bind the texture
glBindTexture(GL_TEXTURE_2D, *handle);
// Enable linear blending between different mipmapping levels
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
// Clamp the texture at the borders
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
// Transfer the image data to the graphics card.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, BMP_GetWidth(bitmap), BMP_GetHeight(bitmap), 0, GL_RGB, GL_UNSIGNED_BYTE, data);
// Not needed anymore
free(data);
BMP_Free(bitmap);
// Unbind texture
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
// Return the width of the height map
int terrain::get_heightmap_width() const
{
return BMP_GetWidth(heightmap);
}
// Return the height of the height map
int terrain::get_heightmap_height() const
{
return BMP_GetHeight(heightmap);
}
// Return the height value at (x,y)
int terrain::get_heightmap_value(int x, int y) const
{
// Returns one value from the heightmap. The image should
// be in grayscale so just the red channel is returned.
// The image is mirrored at the edges (by adjusting the
// coordinates) to assure correct normal estimation.
unsigned char r, g, b;
// Mirror at the left and upper edge if neccessary
x = abs(x);
y = abs(y);
// Mirror at the right and bottom edge if neccessary
if (x>=get_heightmap_width())
x = 2*get_heightmap_width()-x-1;
if (y>=get_heightmap_height())
y = 2*get_heightmap_height()-y-1;
// Read the pixel and return the red component
BMP_GetPixelRGB(heightmap, x, y, &r, &g, &b);
return r;
}
// Set debug text
void terrain::set_text(std::stringstream &stream)
{
if (!initialized) {
stream<<"Cannot show terrain - not all files were loaded!";
return;
}
stream<<"Showing terrain";
if (!show_solid && !show_wireframe && !show_levels) {
stream<<" (all features disabled)";
return;
}
stream<<" (";
if (show_solid)
stream<<"as solid geometry";
if (show_solid && show_wireframe)
stream<<", ";
if (show_wireframe)
stream<<"as wireframe";
if (show_wireframe && show_levels)
stream<<", ";
if (show_levels)
stream<<"with level lines";
stream<<")";
}