#include "Terrain.hh"
#include "Graphics.hh"
#include "Engine.hh"
#include <cmath>
#include <cassert>
#include <algorithm>
#include <ClanLib/display.h>
const Vector DIRECTIONS[] = {
Vector(0,-1),
Vector(1,-1),
Vector(1,0),
Vector(1,1),
Vector(0,1),
Vector(-1,1),
Vector(-1,0),
Vector(-1,-1)
};
Terrain::Terrain (const TerrainConfig &config) :
terrain_buf(NULL),
width(config.dimensions.x), height(config.dimensions.y)
{
// allocate terrain_buf
terrain_buf = new TerrainPixel[width * height];
// fill with dirt
memset(terrain_buf, TERRAIN_DIRT, width * height);
// geneerate random map?
if (config.random_seed)
generateTerrain(config.random_seed);
// update pixel buffer
generatePixelBuffer();
}
Terrain::Terrain (PixelDimension width, PixelDimension height, TerrainPixel *terrain_buf) :
terrain_buf(terrain_buf),
width(width), height(height)
{
// just generate the pixel buffer
generatePixelBuffer();
}
Terrain::~Terrain (void) {
// free terrain data
delete[] terrain_buf;
}
void Terrain::generateTerrain (int seed) {
// set random number generator seed.
srand(seed);
// some constants to control random generation
const int min_range = 25;
const int max_range = 80;
const int num = 50;
const int rock_rarity = 4;
// generate \a num random circles
for (int i = 0; i < num; i++) {
// circle origin
PixelCoordinate mid (rand() % width, rand() % height);
// radius
int range = rand() % (max_range - min_range) + min_range;
// circle type
TerrainType type = TERRAIN_EMPTY;
// tweak to make sure that there's a circle in the midle of the cave
if (i == 0) {
mid.x = width / 2;
mid.y = height / 2;
range = 150;
} else if (rand() % rock_rarity == 0) {
// some rock
type = TERRAIN_ROCK;
}
// iterate over the area of the circle
for (
PixelDimension y = std::max((PixelDimension) 0, mid.y - range);
y < std::min(height, mid.y + range);
y++
) {
for (
PixelDimension x = std::max((PixelDimension) 0, mid.x - range);
x < std::min(width, mid.x + range);
x++
) {
// inside radius?
if ((x - mid.x) * (x - mid.x) + (y - mid.y) * (y - mid.y) < range * range)
terrain_buf[y * width + x] = (TerrainPixel) type;
}
}
}
}
void Terrain::generatePixelBuffer (void) {
// initialize textures
generateTexture();
// create the pixel buffer of the correct size
pixbuf = CL_PixelBuffer(width, height, 4 * width, CL_PixelFormat::rgba8888);
// iterate over each pixel
for (PixelDimension x = 0; x < width; x++) {
for (PixelDimension y = 0; y < height; y++) {
// draw textureized pixel color
pixbuf.draw_pixel(x, y, getTexturePixel(x, y));
}
}
}
/*
* Texture generation utility functions
*/
static void fractal_step (std::vector<double>& land, int size, double str, int dist) {
for (int i = 0; i < size; i += dist * 2) {
for (int j = dist; j < size; j += dist * 2) {
double sum = 0;
sum += land[(i + size - dist) % size + j * size];
sum += land[i + ((j + size - dist) % size) * size];
sum += land[(i + dist) % size + j * size];
sum += land[i + ((j + dist) % size) * size];
land[i + j * size] = sum / 4 + (rand() % 10000 - 5000) * str;
}
}
for (int i = dist; i < size; i += dist * 2) {
for (int j = 0; j < size; j += dist * 2) {
double sum = 0;
sum += land[(i + size - dist) % size + j * size];
sum += land[i + ((j + size - dist) % size) * size];
sum += land[(i + dist) % size + j * size];
sum += land[i + ((j + dist) % size) * size];
land[i + j * size] = sum / 4 + (rand() % 10000 - 5000) * str;
}
}
}
static void fractal_diamond (std::vector<double>& land, int size, double str, int dist) {
for (int i = dist; i < size; i += dist*2) {
for (int j = dist; j < size; j += dist*2) {
double sum = 0;
sum += land[(i + size - dist) % size + ((j + size - dist) % size) * size];
sum += land[(i + dist) % size + ((j + size - dist) % size) * size];
sum += land[(i + size - dist) % size + ((j + dist) % size) * size];
sum += land[(i + dist) % size + ((j + dist) % size) * size];
land[i + j * size] = sum / 4 + (rand() % 10000 - 5000) * str;
}
}
}
void Terrain::generateTexture (void) {
// texture is a 128x128 px pattern
int texturesize = 128;
// store the generated texture
texture = std::vector<std::vector<int> >(texturesize, std::vector<int>(texturesize));
// generate texture into this
std::vector<double> land(texture.size() * texture.size());
// XXX: magic constants with unintelligble names
double str = 0.8;
double H = 0.8;
// do some magic
for (int i = 512; i >= 1; i /= 2) {
fractal_diamond(land, texturesize, str, i);
fractal_step(land, texturesize, str, i);
str *= H;
}
double min = 100000;
double max = -100000;
// find the range of minimum and maximum values
for (int i = 0; i < texturesize * texturesize; i++) {
if (land[i] < min) min = land[i];
if (land[i] > max) max = land[i];
}
// normalize min down to zero
max -= min;
// normalize values to [0, max]
for (int i = 0; i < texturesize * texturesize; i++) {
land[i] -= min;
}
// copy+mangle land to texture as integers
for (int i = 0; i < texturesize * texturesize; i++) {
land[i] = land[i] * 255 / max;
texture[i % texturesize][i / texturesize] = (int) land[i];
}
}
static int normalizeRange (int min, int val, int max) {
if (val < min)
return min;
else if (val > max)
return max;
else
return val;
}
CL_Color Terrain::getTexturePixel (PixelDimension x, PixelDimension y) {
CL_Color color;
int texture_fade = 8;
// determine fade constant
switch (getType(x, y)) {
case TERRAIN_EMPTY:
color = COLOR_EMPTY;
break;
case TERRAIN_DIRT:
color = COLOR_DIRT;
texture_fade = 4;
break;
case TERRAIN_ROCK:
color = COLOR_ROCK;
texture_fade = 2;
break;
}
// calculate texture coordinate
int tx = (x + texture_fade * 37) % texture.size();
int ty = (y + texture_fade * 37) % texture[0].size();
// get color components
int red = color.get_red();
int green = color.get_green();
int blue = color.get_blue();
// apply noise from texture
red += (texture[tx][ty] - 128) / texture_fade;
green += (texture[tx][ty] - 128) / texture_fade;
blue += (texture[tx][ty] - 128) / texture_fade;
// normalize colors to within range and return new color
return CL_Color(
normalizeRange(0, red, 255),
normalizeRange(0, green, 255),
normalizeRange(0, blue, 255)
);
}
const TerrainPixel* Terrain::getTerrainBuffer (void) const {
return terrain_buf;
}
void Terrain::loadFromBuffer (const TerrainPixel *buf) {
// copy bytes
memcpy(terrain_buf, buf, width * height);
// regenerate pixbuf
generatePixelBuffer();
}
bool Terrain::collides (const Vector &point) const {
return (getType(point) != TERRAIN_EMPTY);
}
bool Terrain::collides (const Vector &begin, const Vector &end) const {
// TODO: Maybe there should be another function that also returns the point where we collided.
// use Bresenhams line algorithm to go trough all the "pixels" of the line.
PixelCoordinate b = getPixelCoordinate(begin);
PixelCoordinate e = getPixelCoordinate(end);
// steepness of line (large angle against horizontal)
bool steep = (abs(e.y - b.y) > abs(e.x - b.x)); // k > 1
// if the line is steep, swap the x and y coordinates
if (steep) {
std::swap(b.x, b.y);
std::swap(e.x, e.y);
}
// normalize so that the line goes upwards
if (b.x > e.x)
std::swap(b, e);
// line length
PixelDimension dx = e.x - b.x, dy = abs(e.y - b.y);
PixelDimension err = dx / 2;
PixelDimension y = b.y;
// ascending or descending line?
PixelDimension ystep = b.y < e.y ? 1 : -1;
// iterate through the pixels on the line
for (PixelDimension x = b.x; x <= e.x; x++) {
if (steep) {
// x and y coordinates must be switched if steep
if (getType(y, x) != TERRAIN_EMPTY)
return true;
} else {
if (getType(x, y) != TERRAIN_EMPTY)
return true;
}
err = err - dy;
if (err < 0) {
// check if we want to make an ystep
y = y + ystep;
err = err + dx;
}
}
// no collision
return false;
}
void Terrain::removeGround (const Vector &pos, float radius) {
// scale arguments to pixel units
PixelCoordinate mid = getPixelCoordinate(pos);
PixelDimension r = scale(radius);
// iterate through the circle's pixels
for (PixelDimension j = mid.y - r; j < mid.y + r; j++) {
for (PixelDimension i = mid.x - r; i < mid.x + r; i++) {
// getType also returns TERRAIN_ROCK if out-of-bounds
if (getType(i, j) != TERRAIN_ROCK) {
// within radius?
if ((i - mid.x) * (i - mid.x) + (j - mid.y) * (j - mid.y) < r * r) {
// update terrain buf
setType(i, j, TERRAIN_EMPTY);
}
}
}
}
}
/**
* Gets the index of the given coordinate direction referring to the DIRECTIONS table in Physics.hh
*
* XXX: ugly little "lookup table"
*/
static int getDirectionIndex (Vector direction) {
Vector dir = direction.roundToInt();
if (dir.x == 0 && dir.y == -1) {
return 0;
} else if (dir.x == 1 && dir.y == -1) {
return 1;
} else if (dir.x == 1 && dir.y == 0) {
return 2;
} else if (dir.x == 1 && dir.y == 1) {
return 3;
} else if (dir.x == 0 && dir.y == 1) {
return 4;
} else if (dir.x == -1 && dir.y == 1) {
return 5;
} else if (dir.x == -1 && dir.y == 0) {
return 6;
} else if (dir.x == -1 && dir.y == -1) {
return 7;
}
Engine::log(DEBUG, "Terrain.getDirectionIndex ") << "invalid direction: " << direction;
return 0;
}
Vector Terrain::getNormal(Vector point, Vector prevPoint) const {
// convert location to coordinate
PixelCoordinate p = getPixelCoordinate(point);
// sanity check
assert(point != prevPoint);
// round and subtract to get an integer direction vector, and turn this into a direction index
int dirIdx = getDirectionIndex(prevPoint.roundToInt() - point.roundToInt());
// always add our own direction to normal
Vector normal = DIRECTIONS[dirIdx];
// check the two pixels clockwise from the impact direction
for (int i = 1; i <= 2; i++) {
if (getType(point + DIRECTIONS[(dirIdx + i + 8) % 8]) == TERRAIN_EMPTY) {
normal += DIRECTIONS[(dirIdx + i + 8) % 8];
}
}
// check the two pixels counterclockwise from the impact direction
for (int i = 1; i <= 2; i++) {
if (getType(point + DIRECTIONS[(dirIdx - i + 8) % 8]) == TERRAIN_EMPTY) {
normal += DIRECTIONS[(dirIdx - i + 8) % 8];
}
}
// sanity check
if (getType(point) == TERRAIN_EMPTY || getType(prevPoint) != TERRAIN_EMPTY) {
Engine::log(DEBUG, "Physics.getNormal ") << "silly collision";
}
return normal;
}
void Terrain::draw (Graphics *g, PixelCoordinate camera) {
// XXX: can we optimize this somehow?
// load the terrain pixbuf as a surface
CL_Surface surf (pixbuf);
// draw it onto the graphics, offset by camera position
surf.draw(-camera.x, -camera.y, g->get_gc());
}