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