Rooms are now automatically connected by pathfinding algorithm.

This commit is contained in:
Felix Ableitner 2013-08-20 21:46:59 +02:00
parent 9db73a7b93
commit 56da38792f
4 changed files with 125 additions and 28 deletions

View file

@ -67,9 +67,8 @@ Generator::generateCurrentAreaIfNeeded(const Vector2f& playerPosition) {
generateTiles(area);
for (const auto& enemyPosition : getEnemySpawns(area)) {
float distance = thor::length(enemyPosition - playerPosition);
if (distance > Character::VISION_DISTANCE) {
if (distance > Character::VISION_DISTANCE)
mWorld.insertCharacter(std::shared_ptr<Enemy>(new Enemy(mWorld, mPathfinder, enemyPosition)));
}
}
}
if (mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) {
@ -88,14 +87,12 @@ Generator::generateCurrentAreaIfNeeded(const Vector2f& playerPosition) {
/**
* Generates a minimum spanning tree on mTileNoise, starting from start with
* a maximum total node weight of limit.
*
* FIXME: Some nodes are selected more than once.
*/
*/
std::vector<Vector2i>
Generator::createMinimalSpanningTree(const Vector2i& start,
const float limit) {
std::vector<Vector2i> open;
std::vector<Vector2i> selected;
std::vector<Vector2i> selected;
open.push_back(start);
float totalWeight = 0.0f;
@ -103,9 +100,9 @@ Generator::createMinimalSpanningTree(const Vector2i& start,
Vector2i current;
float minValue = std::numeric_limits<float>::max();
for (auto& o : open) {
if (mTileNoise.getNoise(o.x, o.y) + 1.0f < minValue) {
if (mTileNoise.getNoise(o) + 1.0f < minValue) {
current = o;
minValue = mTileNoise.getNoise(o.x, o.y) + 1.0f;
minValue = mTileNoise.getNoise(o) + 1.0f;
}
}
std::remove(open.begin(), open.end(), current);
@ -125,6 +122,79 @@ Generator::createMinimalSpanningTree(const Vector2i& start,
return selected;
}
/**
* Generates paths that connect different rooms.
*
* Using basically Dijkstra on infinite graph/A* without destination node.
*
* @param start Tile to start path generation from (must be floor).
* @param limit Maximum weight of each path.
*/
void
Generator::connectRooms(const Vector2i& start, float limit) {
std::set<Vector2i> open;
std::set<Vector2i> closed;
std::map<Vector2i, Vector2i> previous;
std::set<Vector2i> destinations;
std::map<Vector2i, float> distance;
// Compares vectors using distance values.
auto comp = [&distance](const Vector2i& lhs, const Vector2i& rhs) {
return distance[lhs] < distance[rhs];
};
auto process = [&open, &closed, &previous, &distance, this](const Vector2i& point, const Vector2i& current) {
// Update previous nodes if shorter path is found.
if (distance.count(point) == 0) {
distance[point] = distance[current] + mTileNoise.getNoise(point) + 1;
previous[point] = current;
}
// Insert into open
if (closed.count(point) == 0)
open.insert(point);
};
open.insert(start);
distance[start] = 0;
while (!open.empty()) {
// Select node with lowest distance that is in open
Vector2i current = *std::min_element(open.begin(), open.end(), comp);
open.erase(current);
closed.insert(current);
// Take all floors right after wall tiles.
if (mTiles[previous[current].x][previous[current].y] == Tile::Type::WALL &&
mTiles[current.x][current.y] == Tile::Type::FLOOR) {
destinations.insert(current);
break;
}
else if (distance.at(current) < limit) {
process(Vector2i(current.x + 1, current.y), current);
process(Vector2i(current.x, current.y + 1), current);
process(Vector2i(current.x - 1, current.y), current);
process(Vector2i(current.x, current.y - 1), current);
}
}
// take min length paths and set tiles
float totalValue = 0.0f;
while (totalValue < limit && !destinations.empty()) {
std::vector<Vector2i> path;
float pathValue = 0;
Vector2i current = *destinations.begin();
destinations.erase(destinations.begin());
while (current != start) {
path.push_back(previous[current]);
pathValue += mTileNoise.getNoise(current);
current = previous[current];
};
path.push_back(start);
mPaths.push_back(path);
for (const auto& p : path) {
mTiles[p.x][p.y] = Tile::Type::FLOOR;
Tile::setTile(p, Tile::Type::FLOOR, mWorld);
}
}
}
/**
* Fill world with procedurally generated tiles.
*
@ -170,15 +240,16 @@ Generator::generateTiles(const sf::IntRect& area) {
}
// Merge new map into stored map and create tile sprites.
for (int x = left; x < right; x++)
for (int y = down; y < up; y++)
mTiles[x][y] = Tile::Type::FLOOR;
connectRooms(start, 5.0f);
for (int x = area.left; x < area.left + area.width; x++)
for (int y = area.top; y < area.top + area.height; y++) {
Tile::Type type = ((x >= left && x < right && y >= down && y < up)
|| (mTiles[x][y] == Tile::Type::FLOOR))
? Tile::Type::FLOOR
: Tile::Type::WALL;
mTiles[x][y] = type;
mWorld.insert(std::shared_ptr<Sprite>(new Tile(type, x, y)));
}
for (int y = area.top; y < area.top + area.height; y++)
mWorld.insert(std::shared_ptr<Sprite>(
new Tile(Vector2i(x, y), mTiles[x][y])));
generateAreas(area);
mPathfinder.generatePortals();
}
@ -254,23 +325,18 @@ Generator::getPlayerSpawn() const {
*
* @warn Will fail if no floor tile has been generated yet.
* @position Point to start search for a floor tile from.
*/
*/
Vector2i
Generator::findClosestFloor(const Vector2i& position) const {
Generator::findClosestFloor(const Vector2i& start) const {
std::map<Vector2i, float> open;
std::set<Vector2i> closed;
Vector2i start = position;
auto makePair = [&start](const Vector2i& point) {
return std::make_pair(point, thor::length(Vector2f(point - start)));
return std::make_pair(point, thor::length(Vector2f(point - start)));
};
open.insert(makePair(start));
while (!open.empty()) {
auto intComp = [](const std::pair<Vector2i, float>& left,
const std::pair<Vector2i, float>& right) {
return left.second < right.second;
};
Vector2i current = std::min_element(open.begin(), open.end(), intComp)->first;
Vector2i current = std::min_element(open.begin(), open.end())->first;
open.erase(current);
closed.insert(current);
if (mTiles.count(current.x) != 0 &&
@ -293,3 +359,22 @@ Generator::findClosestFloor(const Vector2i& position) const {
assert(false);
return Vector2i();
}
#ifndef NDEBUG
/**
* Debug only: Draws paths generated by connectRooms.
*
* mPaths is only required for this function.
*/
void
Generator::draw(sf::RenderTarget& target, sf::RenderStates states) const {
for (auto& p : mPaths) {
for (auto&q : p) {
sf::RectangleShape rect(Vector2f(Tile::TILE_SIZE));
rect.setPosition(Vector2f(q.x * Tile::TILE_SIZE.x, q.y * Tile::TILE_SIZE.y) - Vector2f(Tile::TILE_SIZE / 2));
rect.setFillColor(sf::Color(150, 127, 0, 96));
target.draw(rect);
}
}
}
#endif /* NDEBUG */

View file

@ -20,7 +20,7 @@ class Pathfinder;
/**
* Procedurally generates tiles, chooses player and enemy spawn positions.
*/
class Generator {
class Generator : public sf::Drawable {
public:
explicit Generator(World& world, Pathfinder& pathfinder);
void generateCurrentAreaIfNeeded(const Vector2f& position);
@ -32,10 +32,12 @@ private:
private:
void generateAreas(const sf::IntRect& area);
void generateTiles(const sf::IntRect& area);
Vector2i findClosestFloor(const Vector2i& position) const;
void generateTiles(const sf::IntRect& area);
Vector2i findClosestFloor(const Vector2i& start) const;
std::vector<Vector2i> createMinimalSpanningTree(
const Vector2i& start, const float limit);
void connectRooms(const Vector2i& start, float limit);
void draw(sf::RenderTarget& target, sf::RenderStates states) const;
private:
static const int GENERATE_AREA_SIZE;
@ -51,6 +53,8 @@ private:
SimplexNoise mTileNoise;
/// Perlin noise used for character placement.
SimplexNoise mCharacterNoise;
/// Used only for debug drawing.
std::vector<std::vector<Vector2i> > mPaths;
};
#endif /* DG_GENERATOR_H_ */

View file

@ -35,6 +35,11 @@ SimplexNoise::getNoise(int x, int y) {
return mCache.at(x).at(y);
}
float
SimplexNoise::getNoise(const Vector2i& v) {
return getNoise(v.x, v.y);
}
/**
* Floor implementation that is faster than std implementation by
* ignoring some checks and does not consider some border conditions.

View file

@ -11,6 +11,8 @@
#include <array>
#include <map>
#include "../util/Vector.h"
/**
* Caching simplex noise generator.
*
@ -22,6 +24,7 @@ class SimplexNoise {
public:
SimplexNoise();
float getNoise(int x, int y);
float getNoise(const Vector2i& v);
private: