src/Terrain.cc
author saiam
Thu, 04 Dec 2008 22:59:13 +0000
changeset 204 4c386e9c950f
parent 185 25becd2cb026
child 223 2fcaf54ed37b
permissions -rw-r--r--
Removed one memory leak.
#include "Terrain.hh"
#include "Engine.hh"

#include <cmath>
#include <cassert>
#include <algorithm>
#include <ClanLib/display.h>

Terrain::Terrain() {}
Terrain::Terrain(const int &seed) 
    : terrain(MAP_WIDTH, std::vector<TerrainType>(MAP_HEIGHT, DIRT)){
    this->generateTerrain(seed);
}
Terrain::Terrain(const Terrain &t) {
    this->terrain = t.getTerrain();
    this->generatePixelBuffer();
}

void Terrain::generatePixelBuffer() {
    this->pixbuf = CL_PixelBuffer(MAP_WIDTH, MAP_HEIGHT, 4*MAP_WIDTH, 
                                  CL_PixelFormat::rgba8888);

    CL_Color color;
    for (uint16_t i = 0; i < MAP_WIDTH; i++) {
        for (uint16_t j = 0; j < MAP_HEIGHT; j++) {
            switch(terrain[i][j]) {
            case EMPTY:
                color = COLOR_EMPTY;
                break;
            case DIRT:
                color = COLOR_DIRT;
                break;
            case ROCK:
                color = COLOR_ROCK;
                break;
            default: // TODO: Shouldn't be here.
                break; 
            }
            this->pixbuf.draw_pixel(i, j, color);
        }
    }
}

Vector Terrain::getPixelLocation(Vector point) const{
    return Vector(scale(point.x), 
                  scale(point.y));
}

uint16_t Terrain::scale(float x) const {
    return (uint16_t)(x/MAP_SCALE);
}

TerrainType Terrain::getType(int32_t x, int32_t y) const {
    if ((x < 0) || (y < 0) ||(x >= MAP_WIDTH) || (y >= MAP_HEIGHT)) {
        return ROCK;
    }
    return terrain[x][y];
}
TerrainType Terrain::getType(Vector point) const {
    return getType((int32_t)point.x, (int32_t)point.y);
}

bool Terrain::collides(const Vector &point) const {
    return (getType(point) != 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.
    Vector b = getPixelLocation(begin);
    Vector e = getPixelLocation(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);
    }
    uint16_t dx = e.x - b.x;
    uint16_t dy = abs(e.y - b.y);
    int32_t err = dx/2;
    uint16_t ystep;
    uint16_t y = b.y;
    // Is the line ascending or descending
    if (b.y < e.y) ystep = 1;
    else ystep = -1;
    // Go trough the line
    for (uint16_t x =  b.x; x <= e.x; x++) {
        if (steep) { // X and Y coordinates must be switched if steep
            if (getType(y,x) != EMPTY) { // Collision!
                return true;
            }
        } else {
            if (getType(x,y) != 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, const float &radius) {
    // TODO: Implement. Some circle algoritmh should be usefull here,
    // though the current impelementation doesn't seem too bad either.

    Vector mid = getPixelLocation(pos);
    uint16_t r = scale(radius);
    for (uint16_t i = mid.x-r; i < mid.x+r; i++) {
        for (uint16_t j = mid.y-r; j < mid.y+r; j++) {
            if (getType(i, j) != 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] = EMPTY;
                    pixbuf.draw_pixel(i, j, COLOR_EMPTY);
                }
            }
        }
    }
}

/**
 * Gets the index of the given coordinate direction
 * referring to the DIRECTIONS table in Physics.hh
 */
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 {
    Vector p = getPixelLocation(point);

    assert(point != prevPoint);

    Vector normal(0,0);

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

    std::cout << (prevPoint.roundToInt()) - (point.roundToInt()) << prevPoint-point << std::endl;

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

    Engine::log(DEBUG, "Physics.getNormal ") << "normal: " << normal << "   dirIdx: " << dirIdx;

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

//    for (int i = 0; i < 8; i++) {
//        if (getType(p.x+DIRECTIONS[i].x, p.y+DIRECTIONS[i].y) == EMPTY) {
//            normal += DIRECTIONS[i];
//        }
//    }

   
    // Special cases
    /*    Vector tmp = direction(direction(prevPoint-point) + direction(normal));
    Engine::log(DEBUG, "Terrain.getNormal") << "tmp: " << tmp;
    if (normal.length() == 0 || (tmp.x != 0 && tmp.y != 0 && getType(tmp.x, tmp.y) != EMPTY)) 
        normal = prevPoint - point; // Direct hit
    */
//    Engine::log(DEBUG, "Terrain.getNormal") << "Normal: " << normal;
    return normal;
    return Vector(0,-1);
}

Vector direction(const Vector &v) {
    Vector tmp(v);
    if (tmp.length() > 0) tmp /= tmp.length();
    tmp.x = (uint16_t)(tmp.x);
    tmp.y = (uint16_t)(tmp.y);
    return tmp;
}

// 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
        int midx = rand()%MAP_WIDTH;
        int midy = rand()%MAP_HEIGHT;
        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) {
            midx = MAP_WIDTH/2;
            midy = MAP_WIDTH/2;
            range = 150;
        }

        TerrainType type = EMPTY;
        if (rand()%rock_rarity == 0) {
            type = ROCK;
        }

        // Loops for every pixel of the cirlcle (or square as it seems
        // now)
        for (int x = std::max(0, midx-range); 
             x < std::min((int32_t)MAP_WIDTH, midx+range); 
             x++) {
            for (int y = std::max(0, midy-range);
                    y < std::min((int32_t)MAP_HEIGHT, midy+range);
                    y++) {
                
                //terrain[x][y] = type;

                if ((x-midx)*(x-midx)+(y-midy)*(y-midy) < range*range) {
                    terrain[x][y] = type;
                }
            }
            
        } 
        
    }
    
    this->generatePixelBuffer();
}

void Terrain::draw(CL_GraphicContext *gc) {
    CL_Surface surf(this->pixbuf);
    surf.draw(0,0,gc);
}

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