summaryrefslogtreecommitdiff
path: root/terrain.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'terrain.cpp')
-rw-r--r--terrain.cpp551
1 files changed, 551 insertions, 0 deletions
diff --git a/terrain.cpp b/terrain.cpp
new file mode 100644
index 0000000..35aef58
--- /dev/null
+++ b/terrain.cpp
@@ -0,0 +1,551 @@
+#include "terrain.h"
+#include "vector.h"
+
+#include "gl.h"
+
+#include <noise/noise.h>
+#include "noiseutils/noiseutils.h"
+#include <boost/format.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+#include <cmath>
+#include <queue>
+#include <set>
+
+using namespace noise;
+
+namespace fs = boost::filesystem;
+
+using std::min;
+using std::max;
+
+Terrain::Node::Node(Chunk *chunk, float x, float y) {
+ this->chunk = chunk;
+ this->x = x;
+ this->y = y;
+
+ fill();
+}
+
+Terrain::Node::~Node() {
+}
+
+float Terrain::Node::distance(float px, float pz) {
+ bool in_width = px > x && px < x+1;
+ bool in_height = pz > y && pz < y+1;
+
+ if(in_width && in_height)
+ return 0;
+
+ Vector2 p(px, pz);
+
+ float a = (p - Vector2(x, y)).length();
+ float b = (p - Vector2(x+1, y)).length();
+ float c = (p - Vector2(x, y+1)).length();
+ float d = (p - Vector2(x+1, y+1)).length();
+
+ float dist = min(min(min(a, b), c), d);
+
+ if(in_width)
+ dist = min(dist, (float)min(abs(y - pz), abs(y+1 - pz)));
+ if(in_height)
+ dist = min(dist, (float)min(abs(x - px), abs(x+1 - px)));
+
+ return dist;
+}
+
+void Terrain::Node::fill() {
+ vertex_array[0] = x;
+ vertex_array[1] = chunk->heights[(int)floorf((x)*(chunk->h_height)+y)];
+ vertex_array[2] = y;
+
+ vertex_array[3] = x;
+ vertex_array[4] = chunk->heights[(int)floorf((x)*(chunk->h_height) + (y + 1))];
+ vertex_array[5] = y + 1;
+
+ vertex_array[6] = x + 1;
+ vertex_array[7] = chunk->heights[(int)floorf((x + 1)*(chunk->h_height) + (y + 1))];
+ vertex_array[8] = y + 1;
+
+ vertex_array[9] = x + 1;
+ vertex_array[10] = chunk->heights[(int)floorf((x + 1)*(chunk->h_height) + y)];
+ vertex_array[11] = y;
+}
+
+void Terrain::Node::draw_grid() {
+ glNormal3f(0, 1, 0);
+ glBegin(GL_LINE_LOOP);
+ glVertex3f(chunk->x + vertex_array[0], vertex_array[1], chunk->y + vertex_array[2]);
+ glVertex3f(chunk->x + vertex_array[3], vertex_array[4], chunk->y + vertex_array[5]);
+ glVertex3f(chunk->x + vertex_array[6], vertex_array[7], chunk->y + vertex_array[8]);
+ glVertex3f(chunk->x + vertex_array[9], vertex_array[10], chunk->y + vertex_array[11]);
+
+ glVertex3f(chunk->x + vertex_array[0], vertex_array[1], chunk->y + vertex_array[2]);
+ glVertex3f(chunk->x + vertex_array[6], vertex_array[7], chunk->y + vertex_array[8]);
+ glEnd();
+}
+
+void Terrain::Node::draw_normal() {
+ glNormal3f(0, 1, 0);
+ glColor3f(1, 0, 0);
+ int i = (int)(x*(chunk->h_height) + y);
+ glBegin(GL_LINES);
+ Vector3 N = chunk->normals[i];
+ glVertex3f(chunk->x + x, chunk->heights[i], chunk->y + y);
+ glVertex3f(chunk->x + x + N.x, chunk->heights[i] + N.y, chunk->y + y + N.z);
+ glEnd();
+}
+
+float Terrain::Node::get_height(float px, float py) {
+ px -= chunk->x;
+ py -= chunk->y;
+ bool left;
+ left = px - x >= py - y;
+ int ci = left ? 9 : 3;
+ Vector3 a(vertex_array[0], vertex_array[1], vertex_array[2]);
+ Vector3 b = Vector3(vertex_array[6], vertex_array[7], vertex_array[8]);
+ Vector3 c = Vector3(vertex_array[ci], vertex_array[ci+1], vertex_array[ci+2]);
+ float det1 = (b.z - c.z) * (a.x - c.x) + (c.x - b.x) * (a.z - c.z);
+ float det2 = (c.z - a.z) * (b.x - c.x) + (a.x - c.x) * (b.z - c.z);
+ float l1 = ((b.z - c.z) * (px - c.x) + (c.x - b.x) * (py - c.z)) / det1;
+ float l2 = ((c.z - a.z) * (px - c.x) + (a.x - c.x) * (py - c.z)) / det2;
+ float l3 = 1 - l1 - l2;
+ return l1 * a.y + l2 * b.y + l3 * c.y;
+}
+
+/* Chunk */
+
+Terrain::Chunk::Chunk(Terrain *terrain, float x, float y, float width, float height) {
+ this->terrain = terrain;
+ this->x = x;
+ this->y = y;
+ this->width = width;
+ this->height = height;
+ this->h_width = width+1;
+ this->h_height = height+1;
+ this->vbo_object = this->node_count = this->vertices = 0;
+ this->nodes = NULL;
+ heights = terrain->get_chunk(x, y, h_width, h_height);
+ normals = new Vector3[(int)((h_width)*(h_height))];
+
+ calc_normals();
+
+ node_count = width*height;
+ nodes = new Node*[node_count];
+ for(int i = 0; i < height; i++) {
+ for(int j = 0; j < width; j++) {
+ nodes[j*(int)height + i] = new Node(this, j, i);
+ }
+ }
+ make_vbo();
+}
+
+Terrain::Chunk::~Chunk() {
+ for(unsigned int i = 0; i < node_count; i++)
+ delete nodes[i];
+ delete[] nodes;
+ delete[] heights;
+ delete[] normals;
+}
+
+float Terrain::Chunk::distance(float px, float pz) {
+ bool in_width = px > x && px < x+width;
+ bool in_height = pz > y && pz < y+height;
+
+ if(in_width && in_height)
+ return 0;
+
+ Vector2 p(px, pz);
+
+ float a = (p - Vector2(x, y)).length();
+ float b = (p - Vector2(x+width, y)).length();
+ float c = (p - Vector2(x, y+height)).length();
+ float d = (p - Vector2(x+width, y+height)).length();
+
+ float dist = min(min(min(a, b), c), d);
+
+ if(in_width)
+ dist = min(dist, (float)min(abs(y - pz), abs(y+height - pz)));
+ if(in_height)
+ dist = min(dist, (float)min(abs(x - px), abs(x+width - px)));
+
+ return dist;
+}
+
+void Terrain::Chunk::make_vbo() {
+ node_count = width*height;
+ vertices = node_count*6;
+ if(vbo_object)
+ glDeleteBuffers(1, &vbo_object);
+ glGenBuffers(1, &vbo_object);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_object);
+
+ const size_t vertex_chunk_size = /*sizeof(float)*/3*6;
+ const size_t vertices_size = vertex_chunk_size*node_count;
+ const size_t normal_chunk_size = vertex_chunk_size;
+ const size_t normals_size = normal_chunk_size*node_count;
+ const size_t tex_coord_chunk_size = /*sizeof(float)*/2*6;
+ const size_t tex_coords_size = tex_coord_chunk_size*node_count;
+
+ buf_size = sizeof(float)*(vertices_size + normals_size + tex_coords_size);
+
+ glBufferData(GL_ARRAY_BUFFER, buf_size, NULL, GL_STATIC_DRAW);
+
+ float *buffer = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
+
+ for(unsigned int index = 0; index < node_count; index++) {
+ Terrain::Node *node = nodes[index];
+ // quad-node, triangle, vertex, texcoord
+ float tex_coords[4][2][3][2] = {
+ {{{0, 0}, {0, .5}, {.5, .5}},
+ {{0, 0}, {.5, .5}, {.5, 0}}},
+
+ {{{.5, 0}, {.5, .5}, {1, .5}},
+ {{.5, 0}, {1, .5}, {1, 0}}},
+
+ {{{0, .5}, {0, 1}, {.5, 1}},
+ {{0, .5}, {.5, 1}, {.5, .5}}},
+
+ {{{.5, .5}, {.5, 1}, {1, 1}},
+ {{.5, .5}, {1, 1}, {1, .5}}}
+ };
+ for(int i = 0; i < 2; i++) {
+ float *v = buffer + vertex_chunk_size*index + 3*3*i;
+ for(int j = 0; j < 3; j++) {
+ float *tc = buffer + vertices_size + normals_size + tex_coord_chunk_size*index + 6*i + 2*j;
+ int k = (fmodf(node->x, 2) == 0 ? 0 : 1) + (fmodf(node->y, 2) == 0 ? 0 : 2);
+ tc[0] = tex_coords[k][i][j][0];
+ tc[1] = tex_coords[k][i][j][1];
+ }
+
+ v[0] = node->vertex_array[0];
+ v[1] = node->vertex_array[1];
+ v[2] = node->vertex_array[2];
+
+ v[3] = node->vertex_array[i*3+3];
+ v[4] = node->vertex_array[i*3+4];
+ v[5] = node->vertex_array[i*3+5];
+
+ v[6] = node->vertex_array[i*3+6];
+ v[7] = node->vertex_array[i*3+7];
+ v[8] = node->vertex_array[i*3+8];
+ }
+
+ float *n = buffer + vertices_size + normal_chunk_size*index;
+
+ Vector3 bl = normals[(int)((node->x+1)*(h_height) + node->y)];
+ Vector3 br = normals[(int)((node->x)*(h_height) + node->y)];
+ Vector3 tr = normals[(int)((node->x)*(h_height) + node->y+1)];
+ Vector3 tl = normals[(int)((node->x+1)*(h_height) + node->y+1)];
+
+ n[0] = n[9] = br.x;
+ n[1] = n[10] = br.y;
+ n[2] = n[11] = br.z;
+
+ n[3] = tr.x;
+ n[4] = tr.y;
+ n[5] = tr.z;
+
+ n[6] = n[12] = tl.x;
+ n[7] = n[13] = tl.y;
+ n[8] = n[14] = tl.z;
+
+ n[15] = bl.x;
+ n[16] = bl.y;
+ n[17] = bl.z;
+ }
+
+ glUnmapBuffer(GL_ARRAY_BUFFER);
+}
+
+Terrain::Node* Terrain::Chunk::find(float x, float y) {
+ if(!(x >= this->x && x < this->x+width && y >= this->y && y < this->y+height)) {
+ return NULL;
+ }
+ return nodes[(int)(x-this->x)*(int)height + (int)(y - this->y)];
+}
+
+void Terrain::Chunk::calc_normals() {
+ float *right, *left, *up, *down;
+ right = left = up = down = NULL;
+ right = terrain->get_chunk(this->x - chunk_size, this->y, h_width, h_height);
+ left = terrain->get_chunk(this->x + chunk_size, this->y, h_width, h_height);
+ up = terrain->get_chunk(this->x, this->y + chunk_size, h_width, h_height);
+ down = terrain->get_chunk(this->x, this->y - chunk_size, h_width, h_height);
+
+ for(int x = 0; x < h_width; x++) {
+ for(int y = 0; y < h_height; y++) {
+ Vector3 p(x, heights[x*h_height + y], y);
+ Vector3 N, U, V;
+ float h;
+
+ // right
+ if(x == 0)
+ h = right[(chunk_size-1)*(int)(h_height) + y];
+ else
+ h = heights[(x-1)*h_height + y];
+ U = Vector3(x-1, h, y) - p;
+
+ // down
+ if(y == 0)
+ h = down[x*(int)(h_height) + chunk_size - 1];
+ else
+ h = heights[x*h_height + y - 1];
+ V = Vector3(x, h, y-1) - p;
+ N += V.cross(U);
+
+ // up
+ if(y == h_height-1)
+ h = up[x*(int)(h_height) + 1]; // y == 1
+ else
+ h = heights[x*h_height + y + 1];
+ V = Vector3(x, h, y+1) - p;
+ N += U.cross(V);
+
+ // left
+ if(x == h_width-1)
+ h = left[h_height + y]; // x == 1
+ else
+ h = heights[(x+1)*h_height + y];
+ U = Vector3(x+1, h, y) - p;
+
+ //down
+ if(y == 0)
+ h = down[x*(int)(h_height) + chunk_size - 1];
+ else
+ h = heights[x*h_height + y - 1];
+ V = Vector3(x, h, y-1) - p;
+ N += U.cross(V);
+
+ // up
+ if(y == h_height-1)
+ h = up[x*(int)(h_height) + 1]; // y == 1
+ else
+ h = heights[x*h_height + y + 1];
+ V = Vector3(x, h, y+1) - p;
+ N += V.cross(U);
+
+ N /= N.length();
+ normals[x*h_height + y] = N;
+ }
+ }
+
+ if(right)
+ delete[] right;
+ if(left)
+ delete[] left;
+ if(up)
+ delete[] up;
+ if(down)
+ delete[] down;
+}
+
+Terrain::Terrain() {
+}
+
+Terrain::~Terrain() {
+ for(std::list<Chunk*>::iterator it = chunks.begin(); it != chunks.end(); it++) {
+ delete *it;
+ }
+}
+
+float *Terrain::generate_heights(int x, int y, int width, int height) {
+ module::Perlin mod;
+ mod.SetSeed(0);
+
+ utils::NoiseMap heightmap;
+ utils::NoiseMapBuilderPlane heightmap_builder;
+
+ heightmap_builder.SetSourceModule(mod);
+ heightmap_builder.SetDestNoiseMap(heightmap);
+
+ heightmap_builder.SetDestSize(width, height);
+ heightmap_builder.SetBounds((double)x / 100, (double)(x+width) / 100, (double)y / 100, (double)(y+height) / 100);
+ heightmap_builder.Build();
+
+ float *heights = new float[width*height];
+ for(int i = 0; i < width; i++) {
+ for(int j = 0; j < height; j++) {
+ heights[i*height + j] = 10*(1+heightmap.GetValue(i, j));
+ }
+ }
+
+ chunk_indices.insert(std::pair<int, int>(x, y));
+ save_chunk(heights, x, y, width, height);
+
+ return heights;
+}
+
+float *Terrain::get_chunk(int x, int y, int width, int height) {
+ if(has_chunk(x, y))
+ return load_chunk(x, y, width, height);
+ else
+ return generate_heights(x, y, width, height);
+}
+
+bool Terrain::has_chunk(int x, int y) {
+ return fs::exists((boost::format("map/%d.%d.chunk") % x % y).str());
+ //return chunk_indices.find(std::pair<int, int>(x, y)) != chunk_indices.end();
+}
+
+void Terrain::save_chunk(float *chunk, int x, int y, int width, int height) {
+ fs::path p = (boost::format("map/%d.%d.chunk") % x % y).str();
+ fs::ofstream os(p);
+
+ os << width << std::endl;
+ os << height << std::endl;
+
+ for(int x = 0; x < width; x++)
+ for(int y = 0; y < height; y++)
+ os << chunk[x*height + y] << std::endl;
+ os.close();
+}
+
+// NOTE: assumes width <= chunk_size+, likewise for height
+float *Terrain::load_chunk(int x, int y, int width, int height) {
+ fs::path p = (boost::format("map/%d.%d.chunk") % x % y).str();
+ fs::ifstream is(p);
+
+ int w, h;
+ is >> w;
+ is >> h;
+
+ float *chunk = new float[width*height];
+ for(int x = 0; x < w; x++)
+ for(int y = 0; y < h; y++) {
+ float v;
+ is >> v;
+ if(x < width && y < height)
+ chunk[x*height + y] = v;
+ }
+ is.close();
+
+ return chunk;
+}
+
+void Terrain::raise(float x, float z, float radius, float focus, float strength, bool up) {
+ // TODO: fix setting heights on unloaded chunks
+
+ std::set<Chunk*> changed_chunks;
+
+ /* adjust heights */
+ for(int i = x-radius; i < x+radius; i++) {
+ for(int j = z-radius; j < z+radius; j++) {
+ float v = powf((radius - min((Vector2(x, z) - Vector2(i, j)).length(), radius)) * strength, 1+focus);
+
+ if(focus > 0)
+ /* Scale v with radius based on focus.
+ * Not 100% accurate, but close enough.
+ */
+ v /= radius * (focus/2);
+
+ Chunk *chunk = find_chunk(i, j);
+ changed_chunks.insert(chunk);
+ int index = (i-chunk->x)*(chunk->h_height) + j-chunk->y;
+ if(up)
+ chunk->heights[index] += v;
+ else
+ chunk->heights[index] -= v;
+
+ /* fill the "outer border" */
+ if(i % chunk_size == 0) {
+ Chunk *chunk = find_chunk(i-chunk_size, j);
+ if(chunk) {
+ changed_chunks.insert(chunk);
+ index = (chunk_size)*(chunk->h_height) + j-chunk->y;
+ if(up)
+ chunk->heights[index] += v;
+ else
+ chunk->heights[index] -= v;
+ }
+ }
+ if(j % chunk_size == 0) {
+ Chunk *chunk = find_chunk(i, j-chunk_size);
+ if(chunk) {
+ changed_chunks.insert(chunk);
+ index = (i-chunk->x)*(chunk->h_height) + chunk_size;
+ if(up)
+ chunk->heights[index] += v;
+ else
+ chunk->heights[index] -= v;
+ }
+ }
+ if(i % chunk_size == 0 && j % chunk_size == 0) {
+ Chunk *chunk = find_chunk(i-chunk_size, j-chunk_size);
+ if(chunk) {
+ changed_chunks.insert(chunk);
+ index = (chunk_size)*(chunk->h_height) + chunk_size;
+ if(up)
+ chunk->heights[index] += v;
+ else
+ chunk->heights[index] -= v;
+ }
+ }
+ }
+ }
+
+ // save chunks
+ for(std::set<Chunk*>::iterator it = changed_chunks.begin(); it != changed_chunks.end(); it++) {
+ Chunk *chunk = *it;
+ save_chunk(chunk->heights, chunk->x, chunk->y, chunk->h_width, chunk->h_height);
+ }
+
+ /* recalculate normals */
+ for(std::set<Chunk*>::iterator it = changed_chunks.begin(); it != changed_chunks.end(); it++) {
+ (*it)->calc_normals();
+ }
+
+ /* refill nodes and remake VBOs */
+ for(std::set<Chunk*>::iterator it = changed_chunks.begin(); it != changed_chunks.end(); it++) {
+ Chunk *chunk = *it;
+ for(unsigned int i = 0; i < chunk->node_count; i++) {
+ chunk->nodes[i]->fill();
+ }
+ chunk->make_vbo();
+ }
+}
+
+void Terrain::update(float x, float z) {
+ const int chunk_dist_threshold = chunk_size*4;
+ std::set<std::pair<int, int> > chunk_indices;
+ int i = x - chunk_dist_threshold;
+ i -= i % chunk_size;
+ for(; i < x + chunk_dist_threshold; i += chunk_size) {
+ int j = z - chunk_dist_threshold;
+ j -= j % chunk_size;
+ for(; j < z + chunk_dist_threshold; j += chunk_size) {
+ float a = i - x + chunk_size/2;
+ float b = j - z + chunk_size/2;
+ if(sqrtf(a*a + b*b) < chunk_dist_threshold)
+ chunk_indices.insert(std::pair<int, int>(i, j));
+ }
+ }
+ for(std::list<Chunk*>::iterator it = chunks.begin(); it != chunks.end(); it++) {
+ std::set<std::pair<int, int> >::iterator ind_it = chunk_indices.find(std::pair<int, int>((*it)->x, (*it)->y));
+ if(ind_it != chunk_indices.end()) {
+ chunk_indices.erase(ind_it);
+ }
+ if((*it)->distance(x, z) > chunk_dist_threshold) {
+ it = chunks.erase(it);
+ }
+ }
+ for(std::set<std::pair<int, int> >::iterator it = chunk_indices.begin(); it != chunk_indices.end(); it++) {
+ Chunk *chunk = new Chunk(this, it->first, it->second, chunk_size, chunk_size);
+ chunks.push_back(chunk);
+ }
+}
+
+Terrain::Chunk *Terrain::find_chunk(float x, float y) {
+ Node *node = find(x, y);
+ return node ? node->chunk : NULL;
+}
+
+Terrain::Node *Terrain::find(float x, float y) {
+ for(std::list<Chunk*>::iterator it = chunks.begin(); it != chunks.end(); it++) {
+ Node *node = (*it)->find(x, y);
+ if(node)
+ return node;
+ }
+ return NULL;
+}