src/Terrain.cc
author terom
Mon, 15 Dec 2008 14:24:38 +0000
changeset 370 39e59dd36b6e
parent 327 09a3b5055862
child 377 01d3c340b372
permissions -rw-r--r--
clean up Vector a bit, remove unused Terrain -> direction function
#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 (void) :
    map_width(0), map_height(0)
{
}

Terrain::Terrain (PixelDimension map_width, PixelDimension map_height, int seed) :
    terrain(map_width, std::vector<TerrainType>(map_height, TERRAIN_DIRT)),
    map_width(map_width), 
    map_height(map_height)
{
    generateTerrain(seed);
}

Terrain::Terrain (const Terrain &t) 
{
    map_width = t.map_width;
    map_height = t.map_height;
    terrain = t.terrain;

    generatePixelBuffer();
}

/*
 * Texture generation util 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::generate_texture (void) {
    int texturesize = 128;
    texture = std::vector<std::vector<int> >(texturesize, std::vector<int>(texturesize));
    std::vector<double> land(texture.size()*texture.size());
    double str = 0.8;
    double H = 0.8;
    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;
    for(int i = 0; i < texturesize*texturesize; i++) {
        if(land[i] < min) min = land[i];
        if(land[i] > max) max = land[i];
    }
    max -= min;
    for(int i = 0; i < texturesize*texturesize; i++) {
        land[i] -= min;
    }
    for(int i = 0; i < texturesize*texturesize; i++) {
        land[i] = land[i]*255/max;
        texture[i%texturesize][i/texturesize] = (int)(land[i]);
    }
}

/**
 * Changes color depending on x and y values
 * x and y should be valid coordinates (not outside)
 */
void Terrain::noisifyPixel(CL_Color& color, PixelCoordinate pc) {
    int texture_fade = 8;
    switch (terrain[pc.x][pc.y]) {
    case TERRAIN_EMPTY:
        break;
    case TERRAIN_DIRT:
        texture_fade = 4;
        break;
    case TERRAIN_ROCK:
        texture_fade = 2;
        break;
    }
    int tx = (pc.x + texture_fade * 37) % texture.size();
    int ty = (pc.y + texture_fade * 37) % texture[0].size();
    int red = color.get_red();
    int green = color.get_green();
    int blue = color.get_blue();

    red += (texture[tx][ty] - 128) / texture_fade;
    green += (texture[tx][ty] - 128) / texture_fade;
    blue += (texture[tx][ty] - 128) / texture_fade;

    if (red < 0)
        red = 0;
    else if (red >= 256)
        red = 255;

    if (green < 0)
        green = 0;
    else if (green >= 256)
        green = 255;

    if (blue < 0)
        blue = 0;
    else if (blue >= 256)
        blue = 255;

    color = CL_Color(red, green, blue);
}

/**
 * Sets to color the correct color of pixel in (x,y)
 */
void Terrain::loadPixelColor(CL_Color& color, PixelCoordinate pc) {
    if ((pc.x < 0) || (pc.y < 0) || (pc.x >= map_width) || (pc.y >= map_height)) {
        color = CL_Color(0, 0, 0);
        return;
    }

    switch (terrain[pc.x][pc.y]) {
    case TERRAIN_EMPTY:
        color = COLOR_EMPTY;
        break;

    case TERRAIN_DIRT:
        color = COLOR_DIRT;
        break;

    case TERRAIN_ROCK:
        color = COLOR_ROCK;
        break;
    }
        
    noisifyPixel(color, pc);
}

void Terrain::generatePixelBuffer (void) {
    // initialze texture
    generate_texture();

    // create pixel buffer
    pixbuf = CL_PixelBuffer(map_width, map_height, 4 * map_width, CL_PixelFormat::rgba8888);

    CL_Color color;

    for (PixelDimension x = 0; x < map_width; x++) {
        for (PixelDimension y = 0; y < map_height; y++) {
            PixelCoordinate pc(x, y);

            loadPixelColor(color, pc);

            pixbuf.draw_pixel(pc.x, pc.y, color);
        }
    }
}

PixelCoordinate Terrain::getPixelCoordinate (Vector point) const {
    // XXX: assume 1:1
    return PixelCoordinate((int) point.x, (int) point.y);
}

PixelCoordinate Terrain::getDimensions (void) const {
    return PixelCoordinate(map_width, map_height);
}

TerrainType Terrain::getType (PixelDimension px, PixelDimension py) const {
    if ((px < 0) || (py < 0) ||(px >= map_width) || (py >= map_height))
        return TERRAIN_ROCK;

    return terrain[px][py];
}

TerrainType Terrain::getType (PixelCoordinate pc) const {
    return getType(pc.x, pc.y);
}

TerrainType Terrain::getType (Vector point) const {
    return getType((PixelDimension) point.x, (PixelDimension) point.y);
}

bool Terrain::collides (const Vector &point) const {
    return (getType(point) != TERRAIN_EMPTY);
}

bool Terrain::collides (const Vector &begin, const Vector &end) const {
    // TODO: Maybe we should have another function prototype that also
    // returns the point where we collided.

    // We'll use Bresenhams line algorithm to go trough all the
    // "pixels" of the line.
    PixelCoordinate b = getPixelCoordinate(begin);
    PixelCoordinate e = getPixelCoordinate(end);

    bool steep = (abs(e.y - b.y) > abs(e.x - b.x)); // k > 1

    if (steep) { 
        // Line is steep -> swap x and y coordinates
        std::swap(b.x, b.y);
        std::swap(e.x, e.y);
    }

    if (b.x > e.x) { 
        // Line goes down -> make it go up
        std::swap(b, e);
    }

    PixelDimension dx = e.x - b.x, dy = abs(e.y - b.y);
    PixelDimension err = dx / 2;
    PixelDimension ystep, y = b.y;

    // Is the line ascending or descending
    if (b.y < e.y) 
        ystep = 1;

    else 
        ystep = -1;

    // Go trough 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) { 
                // Collision!
                return true;
            }
        } else {
            if (getType(x, y) != TERRAIN_EMPTY) { 
                // Collision!
                return true;
            }
        }

        err = err - dy;

        if (err < 0) { 
            // Check if we want to make an ystep
            y = y + ystep;
            err = err + dx;
        }
    }

    return false; // No Collision
}

void Terrain::removeGround (const Vector &pos, float radius) {
    // TODO: Implement. Some circle algoritmh should be usefull here,
    // though the current impelementation doesn't seem too bad either.

    PixelCoordinate mid = getPixelCoordinate(pos);
    PixelDimension r = (unsigned int) radius; // XXX: scale

    for (PixelDimension i = mid.x - r; i < mid.x + r; i++) {
        for (PixelDimension j = mid.y-r; j < mid.y+r; j++) {
            PixelCoordinate pc(i, j);

            if (getType(pc) != TERRAIN_ROCK) { 
                // getType returns ROCK if out of bounds

                if ((i - mid.x) * (i - mid.x) + (j - mid.y) * (j - mid.y) < r * r) {
                    terrain[i][j] = TERRAIN_EMPTY;

                    CL_Color color;
                    loadPixelColor(color, pc);
                    pixbuf.draw_pixel(pc.x, pc.y, color);
                }
            }
        }
    }
}

/**
 * Gets the index of the given coordinate direction
 * referring to the DIRECTIONS table in Physics.hh
 */
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;
}

/**
 * point should be ground and prevPoint air, but it's easy to assure that
 * @param point - pixel on ground to which was collided
 * @param prevPoint - pixel where we are when we collide
 */
Vector Terrain::getNormal(Vector point, Vector prevPoint) const {
    PixelCoordinate p = getPixelCoordinate(point);

    assert(point != prevPoint);

    Vector normal(0,0);

    // These two must be rounded separately
    int dirIdx = getDirectionIndex(prevPoint.roundToInt() - point.roundToInt());

    normal += DIRECTIONS[dirIdx];

    for (int i = 1; i <= 2; i++) {
        if (getType(point + DIRECTIONS[(dirIdx+i+8)%8]) == TERRAIN_EMPTY) {
            normal += DIRECTIONS[(dirIdx+i+8)%8];
        }
    }

    for (int i = 1; i <= 2; i++) {
        if (getType(point + DIRECTIONS[(dirIdx-i+8)%8]) == TERRAIN_EMPTY) {
            normal += DIRECTIONS[(dirIdx-i+8)%8];
        }
    }

    if (getType(point) == TERRAIN_EMPTY || getType(prevPoint) != TERRAIN_EMPTY) {
        Engine::log(DEBUG, "Physics.getNormal ") << "logic ground error";
    }
    
    return normal;
}

// TODO: This could better :)
// TODO: And this need some cleaning :)
void Terrain::generateTerrain (int seed) {
    srand(seed); // Set random number generator 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 circles (or whatever)
    for (int i = 0; i < num; i++) {
        // Random generate circle attributes
        PixelCoordinate mid(rand() % map_width, rand() % map_width);

        int range = rand()%(max_range-min_range)+min_range;

        // Make sure that there's a circle in the midle of the cave
        if (i == 0) {
            mid.x = map_width / 2;
            mid.y = map_height / 2;
            range = 150;
        }

        TerrainType type = TERRAIN_EMPTY;

        if (rand() % rock_rarity == 0) {
            type = TERRAIN_ROCK;
        }

        // Loops for every pixel of the cirlcle (or square as it seems
        // now)
        for (
            PixelDimension x = std::max((PixelDimension) 0, mid.x - range); 
            x < std::min(map_width, mid.x + range); 
            x++
        ) {
            for (
                PixelDimension y = std::max((PixelDimension) 0, mid.y - range);
                y < std::min(map_height, mid.y + range);
                y++
            ) {
                if ((x - mid.x) * (x - mid.x) + (y - mid.y) * (y - mid.y) < range * range) {
                    terrain[x][y] = type;
                }
            }
        } 
    }
    
    // regenerate pixel buffer
    this->generatePixelBuffer();
}

void Terrain::draw (Graphics *g, PixelCoordinate camera) {
    CL_Surface surf (pixbuf);
    surf.draw(-camera.x, -camera.y, g->get_gc());
}

std::vector<std::vector<TerrainType> > Terrain::getTerrain() const {
    return terrain;
}