Changed procedural generation to use minimum spanning trees.
This commit is contained in:
parent
5b3020ad3d
commit
602cc605e8
3 changed files with 358 additions and 367 deletions
|
@ -1,305 +1,301 @@
|
|||
/*
|
||||
* Generator.cpp
|
||||
*
|
||||
* Created on: 07.04.2013
|
||||
* Author: Felix
|
||||
*/
|
||||
|
||||
#include "Generator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <SFML/System.hpp>
|
||||
|
||||
#include <Thor/Vectors.hpp>
|
||||
|
||||
#include "../Pathfinder.h"
|
||||
#include "../World.h"
|
||||
#include "../sprites/Enemy.h"
|
||||
|
||||
const int Generator::GENERATE_AREA_SIZE = 4;
|
||||
const float Generator::GENERATE_AREA_RANGE = 4.0f;
|
||||
|
||||
/**
|
||||
* Generates new random seed.
|
||||
*/
|
||||
Generator::Generator(World& world, Pathfinder& pathfinder) :
|
||||
mWorld(world),
|
||||
mPathfinder(pathfinder) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates tiles near player position (maximum distance is determined by
|
||||
* GENERATE_AREA_SIZE and GENERATE_AREA_RANGE).
|
||||
*/
|
||||
void
|
||||
Generator::generateCurrentAreaIfNeeded(const sf::Vector2f& playerPosition) {
|
||||
auto compare = [](const sf::Vector2i& a, const sf::Vector2i& b) {
|
||||
return a.x < b.x || (a.x == b.x && a.y < b.y);
|
||||
};
|
||||
std::map<sf::Vector2i, float, decltype(compare)> open(compare);
|
||||
std::set<sf::Vector2i, decltype(compare)> closed(compare);
|
||||
|
||||
sf::Vector2i start((int) floor(playerPosition.x / Tile::TILE_SIZE.x),
|
||||
(int) floor(playerPosition.y / Tile::TILE_SIZE.y));
|
||||
start /= GENERATE_AREA_SIZE;
|
||||
auto makePair = [&start](const sf::Vector2i& point) {
|
||||
return std::make_pair(point, thor::length(sf::Vector2f(point - start)));
|
||||
};
|
||||
|
||||
open.insert(makePair(start));
|
||||
while (!open.empty()) {
|
||||
auto intComp = [](const std::pair<sf::Vector2i, float>& left,
|
||||
const std::pair<sf::Vector2i, float>& right) {
|
||||
return left.second < right.second;
|
||||
};
|
||||
sf::Vector2i current =
|
||||
std::min_element(open.begin(), open.end(), intComp)->first;
|
||||
float distance = open[current];
|
||||
open.erase(current);
|
||||
closed.insert(current);
|
||||
if (!mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) {
|
||||
mGenerated[current.x][current.y] = true;
|
||||
sf::IntRect area(current * GENERATE_AREA_SIZE -
|
||||
sf::Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE) / 2,
|
||||
sf::Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE));
|
||||
generateTiles(area);
|
||||
for (const auto& enemyPosition : getEnemySpawns(area)) {
|
||||
float distance = thor::length(enemyPosition - playerPosition);
|
||||
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) {
|
||||
if (closed.find(sf::Vector2i(current.x + 1, current.y)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x + 1, current.y)));
|
||||
if (closed.find(sf::Vector2i(current.x, current.y + 1)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x, current.y + 1)));
|
||||
if (closed.find(sf::Vector2i(current.x - 1, current.y)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x - 1, current.y)));
|
||||
if (closed.find(sf::Vector2i(current.x, current.y - 1)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x, current.y - 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill world with procedurally generated tiles.
|
||||
*
|
||||
* @param area Size and position of area to generate tiles for. Width and
|
||||
* height must each be a power of two.
|
||||
*/
|
||||
void
|
||||
Generator::generateTiles(const sf::IntRect& area) {
|
||||
// Check if width and height are power of two.
|
||||
assert(area.width && !(area.width & (area.width - 1)));
|
||||
assert(area.height && !(area.height & (area.height - 1)));
|
||||
|
||||
array generatedTiles;
|
||||
fill(generatedTiles, area, type::FLOOR);
|
||||
|
||||
for (int x = area.left; x < area.left + area.width; x++) {
|
||||
for (int y = area.top; y < area.top + area.height; y++) {
|
||||
filterWalls(generatedTiles, x, y, 2, 1, 0);
|
||||
filterWalls(generatedTiles, x, y, 6, 1, 2);
|
||||
filterWalls(generatedTiles, x, y, 10, 1, 4);
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = area.left; x < area.left + area.width; x++)
|
||||
for (int y = area.top; y < area.top + area.height; y++) {
|
||||
// Merge map that we just generated with stored map.
|
||||
mTiles[x][y] = generatedTiles[x][y];
|
||||
// Actually generate physical tiles.
|
||||
mWorld.insert(std::shared_ptr<Sprite>(
|
||||
new Tile(generatedTiles.at(x).at(y), x, y)));
|
||||
}
|
||||
|
||||
generateAreas(area);
|
||||
mPathfinder.generatePortals();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns coordinates where enemies should spawn.
|
||||
*/
|
||||
std::vector<sf::Vector2f>
|
||||
Generator::getEnemySpawns(const sf::IntRect& area) {
|
||||
auto compare = [](const sf::Vector2f& a, const sf::Vector2f& b) {
|
||||
return a.x < b.x || (a.x == b.x && a.y < b.y);
|
||||
};
|
||||
std::set<sf::Vector2f, decltype(compare)> ret(compare);
|
||||
for (int x = area.left; x < area.left + area.width; x++) {
|
||||
for (int y = area.top; y < area.top + area.height; y++) {
|
||||
if (mCharacterNoise.getNoise(x, y) < -0.85f) {
|
||||
sf::Vector2i tilePosition = findClosestFloor(sf::Vector2i(x, y));
|
||||
ret.insert(sf::Vector2f(tilePosition.x * Tile::TILE_SIZE.x,
|
||||
tilePosition.y * Tile::TILE_SIZE.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::vector<sf::Vector2f>(ret.begin(), ret.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a rectangular area with the specified value.
|
||||
*
|
||||
* @param[in,out] Array to set values to.
|
||||
* @param area The area to fill.
|
||||
* @param value The value to set.
|
||||
*/
|
||||
void
|
||||
Generator::fill(array& tiles, const sf::IntRect& area, Tile::Type value) {
|
||||
for (int x = area.left; x < area.left + area.width; x++) {
|
||||
for (int y = area.top; y < area.top + area.height; y++)
|
||||
tiles[x][y] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts and returns the number of walls within the area.
|
||||
*
|
||||
* @param area The area to count in.
|
||||
*/
|
||||
int
|
||||
Generator::countWalls(const sf::IntRect& area) {
|
||||
int count = 0;
|
||||
for (int x = area.left; x < area.left + area.width; x++) {
|
||||
for (int y = area.top; y < area.top + area.height; y++)
|
||||
count += (int) (getTileType(mTileNoise.getNoise(x, y)) ==
|
||||
type::WALL);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds rectangles of specific size with mTileNoise and
|
||||
* puts them into vector out.
|
||||
*
|
||||
* @param[in,out] tiles Tiles to be placed. Does not explicitly set floor values
|
||||
* (keeps previous values).
|
||||
* @param x Position to check from (top left corner for rectangle).
|
||||
* @param y Position to check from (top left corner for rectangle).
|
||||
* @param longside Length of the longer side of the rectangle.
|
||||
* @param shortside Length of the shorter side of the rectangle.
|
||||
* @param subtract Still accepts rectangle if at least this amount of
|
||||
* tiles is not walls (tilecount >= longside * shortside - subtract).
|
||||
*/
|
||||
void
|
||||
Generator::filterWalls(array& tiles, int x, int y, int longside,
|
||||
int shortside, int subtract) {
|
||||
// Filter in horizontal direction.
|
||||
if (countWalls(sf::IntRect(x, y, longside, shortside)) >=
|
||||
shortside * longside - subtract)
|
||||
fill(tiles, sf::IntRect(x, y, longside, shortside), type::WALL);
|
||||
// Filter in vertical direction.
|
||||
if (countWalls(sf::IntRect(x, y, shortside, longside)) >=
|
||||
shortside * longside - subtract)
|
||||
fill(tiles, sf::IntRect(x, y, shortside, longside), type::WALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts floor tiles into path finder, using a quadtree approach to group
|
||||
* tiles where possible.
|
||||
*
|
||||
* @param area The area to generate areas for.
|
||||
*/
|
||||
void
|
||||
Generator::generateAreas(const sf::IntRect& area) {
|
||||
assert(area.width > 0 && area.height > 0);
|
||||
|
||||
int wallCount = 0;
|
||||
for (int x = area.left; x < area.left + area.width; x++)
|
||||
for (int y = area.top; y < area.top + area.height; y++)
|
||||
wallCount += (int) (mTiles[x][y] == type::WALL);
|
||||
|
||||
if (wallCount == 0)
|
||||
mPathfinder.insertArea(sf::FloatRect(area.left, area.top, area.width, area.height));
|
||||
else if (wallCount == area.width * area.height)
|
||||
return;
|
||||
else {
|
||||
int halfWidth = area.width / 2;
|
||||
int halfHeight = area.height / 2;
|
||||
generateAreas(sf::IntRect(area.left,
|
||||
area.top, halfWidth, halfHeight));
|
||||
generateAreas(sf::IntRect(area.left + halfWidth,
|
||||
area.top, halfWidth, halfHeight));
|
||||
generateAreas(sf::IntRect(area.left,
|
||||
area.top + halfHeight, halfWidth, halfHeight));
|
||||
generateAreas(sf::IntRect(area.left + halfWidth,
|
||||
area.top + halfHeight, halfWidth, halfHeight));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if a perlin noise result value is converted to a wall or floor tile.
|
||||
*
|
||||
* @param value Perlin noise value within [-1, 1]
|
||||
*/
|
||||
Generator::type
|
||||
Generator::getTileType(float value) {
|
||||
return (value < -0.2f)
|
||||
? type::WALL
|
||||
: type::FLOOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid position (floor) for the player to spawn at.
|
||||
*/
|
||||
sf::Vector2f
|
||||
Generator::getPlayerSpawn() const {
|
||||
sf::Vector2i spawn = findClosestFloor(sf::Vector2i(0, 0));
|
||||
return sf::Vector2f(spawn.x * Tile::TILE_SIZE.x,
|
||||
spawn.y * Tile::TILE_SIZE.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the point array index closest to position which has a floor tile.
|
||||
*
|
||||
* @warn Will fail if no floor tile has been generated yet.
|
||||
* @position Point to start search for a floor tile from.
|
||||
*/
|
||||
sf::Vector2i
|
||||
Generator::findClosestFloor(const sf::Vector2i& position) const {
|
||||
auto compare = [](const sf::Vector2i& a, const sf::Vector2i& b) {
|
||||
return a.x < b.x || (a.x == b.x && a.y < b.y);
|
||||
};
|
||||
std::map<sf::Vector2i, float, decltype(compare)> open(compare);
|
||||
std::set<sf::Vector2i, decltype(compare)> closed(compare);
|
||||
sf::Vector2i start = position;
|
||||
auto makePair = [&start](const sf::Vector2i& point) {
|
||||
return std::make_pair(point, thor::length(sf::Vector2f(point - start)));
|
||||
};
|
||||
|
||||
open.insert(makePair(start));
|
||||
while (!open.empty()) {
|
||||
auto intComp = [](const std::pair<sf::Vector2i, float>& left,
|
||||
const std::pair<sf::Vector2i, float>& right) {
|
||||
return left.second < right.second;
|
||||
};
|
||||
sf::Vector2i current = std::min_element(open.begin(), open.end(), intComp)->first;
|
||||
open.erase(current);
|
||||
closed.insert(current);
|
||||
if (mTiles.count(current.x) != 0 &&
|
||||
mTiles.at(current.x).count(current.y) != 0 &&
|
||||
mTiles.at(current.x).at(current.y) == type::FLOOR) {
|
||||
return current;
|
||||
}
|
||||
else {
|
||||
if (closed.find(sf::Vector2i(current.x + 1, current.y)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x + 1, current.y)));
|
||||
if (closed.find(sf::Vector2i(current.x, current.y + 1)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x, current.y + 1)));
|
||||
if (closed.find(sf::Vector2i(current.x - 1, current.y)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x - 1, current.y)));
|
||||
if (closed.find(sf::Vector2i(current.x, current.y - 1)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x, current.y - 1)));
|
||||
}
|
||||
}
|
||||
// No floor tile found in the entire world.
|
||||
assert(false);
|
||||
return sf::Vector2i();
|
||||
}
|
||||
/*
|
||||
* Generator.cpp
|
||||
*
|
||||
* Created on: 07.04.2013
|
||||
* Author: Felix
|
||||
*/
|
||||
|
||||
#include "Generator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <SFML/System.hpp>
|
||||
|
||||
#include <Thor/Vectors.hpp>
|
||||
|
||||
#include "../Pathfinder.h"
|
||||
#include "../World.h"
|
||||
#include "../sprites/Enemy.h"
|
||||
#include "../util/Log.h"
|
||||
|
||||
const int Generator::GENERATE_AREA_SIZE = 4;
|
||||
const float Generator::GENERATE_AREA_RANGE = 4.0f;
|
||||
|
||||
/**
|
||||
* Generates new random seed.
|
||||
*/
|
||||
Generator::Generator(World& world, Pathfinder& pathfinder) :
|
||||
mWorld(world),
|
||||
mPathfinder(pathfinder) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates tiles near player position (maximum distance is determined by
|
||||
* GENERATE_AREA_SIZE and GENERATE_AREA_RANGE).
|
||||
*/
|
||||
void
|
||||
Generator::generateCurrentAreaIfNeeded(const sf::Vector2f& playerPosition) {
|
||||
auto compare = [](const sf::Vector2i& a, const sf::Vector2i& b) {
|
||||
return a.x < b.x || (a.x == b.x && a.y < b.y);
|
||||
};
|
||||
std::map<sf::Vector2i, float, decltype(compare)> open(compare);
|
||||
std::set<sf::Vector2i, decltype(compare)> closed(compare);
|
||||
|
||||
sf::Vector2i start((int) floor(playerPosition.x / Tile::TILE_SIZE.x),
|
||||
(int) floor(playerPosition.y / Tile::TILE_SIZE.y));
|
||||
start /= GENERATE_AREA_SIZE;
|
||||
auto makePair = [&start](const sf::Vector2i& point) {
|
||||
return std::make_pair(point, thor::length(sf::Vector2f(point - start)));
|
||||
};
|
||||
|
||||
open.insert(makePair(start));
|
||||
while (!open.empty()) {
|
||||
auto intComp = [](const std::pair<sf::Vector2i, float>& left,
|
||||
const std::pair<sf::Vector2i, float>& right) {
|
||||
return left.second < right.second;
|
||||
};
|
||||
sf::Vector2i current =
|
||||
std::min_element(open.begin(), open.end(), intComp)->first;
|
||||
float distance = open[current];
|
||||
open.erase(current);
|
||||
closed.insert(current);
|
||||
if (!mGenerated[current.x][current.y] && distance <= GENERATE_AREA_RANGE) {
|
||||
mGenerated[current.x][current.y] = true;
|
||||
sf::IntRect area(current * GENERATE_AREA_SIZE -
|
||||
sf::Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE) / 2,
|
||||
sf::Vector2i(GENERATE_AREA_SIZE, GENERATE_AREA_SIZE));
|
||||
generateTiles(area);
|
||||
for (const auto& enemyPosition : getEnemySpawns(area)) {
|
||||
float distance = thor::length(enemyPosition - playerPosition);
|
||||
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) {
|
||||
if (closed.find(sf::Vector2i(current.x + 1, current.y)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x + 1, current.y)));
|
||||
if (closed.find(sf::Vector2i(current.x, current.y + 1)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x, current.y + 1)));
|
||||
if (closed.find(sf::Vector2i(current.x - 1, current.y)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x - 1, current.y)));
|
||||
if (closed.find(sf::Vector2i(current.x, current.y - 1)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x, current.y - 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<sf::Vector2i>
|
||||
Generator::createMinimalSpanningTree(const sf::Vector2i& start,
|
||||
const float limit) {
|
||||
std::vector<sf::Vector2i> open;
|
||||
std::vector<sf::Vector2i> selected;
|
||||
open.push_back(start);
|
||||
float totalWeight = 0.0f;
|
||||
|
||||
while (totalWeight < limit) {
|
||||
sf::Vector2i current;
|
||||
float minValue = std::numeric_limits<float>::max();
|
||||
for (auto& o : open) {
|
||||
if (mTileNoise.getNoise(o.x, o.y) + 1.0f < minValue) {
|
||||
current = o;
|
||||
minValue = mTileNoise.getNoise(o.x, o.y) + 1.0f;
|
||||
}
|
||||
}
|
||||
std::remove(open.begin(), open.end(), current);
|
||||
selected.push_back(current);
|
||||
totalWeight += minValue;
|
||||
|
||||
auto insertOnlyNew = [&open, &selected](const sf::Vector2i& v) {
|
||||
if (std::find(open.begin(), open.end(), v) == open.end()
|
||||
&& std::find(selected.begin(), selected.end(), v) == selected.end())
|
||||
open.push_back(v);
|
||||
};
|
||||
insertOnlyNew(sf::Vector2i(current.x + 1, current.y));
|
||||
insertOnlyNew(sf::Vector2i(current.x, current.y + 1));
|
||||
insertOnlyNew(sf::Vector2i(current.x - 1, current.y));
|
||||
insertOnlyNew(sf::Vector2i(current.x, current.y - 1));
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill world with procedurally generated tiles.
|
||||
*
|
||||
* This is done by generating random (simplex) noise, with a value mapped
|
||||
* to every integer point in area, selecting the lowest value point as start,
|
||||
* and building a minimum spanning tree from there.
|
||||
*
|
||||
* @param area Size and position of area to generate tiles for. Width and
|
||||
* height must each be a power of two.
|
||||
*/
|
||||
void
|
||||
Generator::generateTiles(const sf::IntRect& area) {
|
||||
// Width and height must be a power of two.
|
||||
assert(area.width && !(area.width & (area.width - 1)));
|
||||
assert(area.height && !(area.height & (area.height - 1)));
|
||||
|
||||
sf::Vector2i start;
|
||||
float minValue = std::numeric_limits<float>::max();
|
||||
|
||||
// Find lowest value for tree start.
|
||||
for (int x = area.left; x < area.left + area.width; x++)
|
||||
for (int y = area.top; y < area.top + area.height; y++) {
|
||||
if (mTileNoise.getNoise(x, y) + 1.0f < minValue) {
|
||||
start = sf::Vector2i(x, y);
|
||||
minValue = mTileNoise.getNoise(x, y) + 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<sf::Vector2i> selected = createMinimalSpanningTree(start, 12.0f);
|
||||
|
||||
// For rooms, take minimum bounding box of spanning tree.
|
||||
|
||||
int left = start.x;
|
||||
int right = start.x;
|
||||
int down = start.y;
|
||||
int up = start.y;
|
||||
|
||||
for (auto& s : selected) {
|
||||
if (s.x < left) left = s.x;
|
||||
if (s.x > right) right = s.x;
|
||||
if (s.y < down) down = s.y;
|
||||
if (s.y > up) up = s.y;
|
||||
}
|
||||
|
||||
// Merge new map into stored map and create tile sprites.
|
||||
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)));
|
||||
}
|
||||
generateAreas(area);
|
||||
mPathfinder.generatePortals();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns coordinates where enemies should spawn.
|
||||
*
|
||||
* @param area Area for which enemy spawns should be returned.
|
||||
*/
|
||||
std::vector<sf::Vector2f>
|
||||
Generator::getEnemySpawns(const sf::IntRect& area) {
|
||||
auto compare = [](const sf::Vector2f& a, const sf::Vector2f& b) {
|
||||
return a.x < b.x || (a.x == b.x && a.y < b.y);
|
||||
};
|
||||
std::set<sf::Vector2f, decltype(compare)> ret(compare);
|
||||
for (int x = area.left; x < area.left + area.width; x++) {
|
||||
for (int y = area.top; y < area.top + area.height; y++) {
|
||||
if (mCharacterNoise.getNoise(x, y) < -0.85f) {
|
||||
sf::Vector2i tilePosition = findClosestFloor(sf::Vector2i(x, y));
|
||||
ret.insert(sf::Vector2f(tilePosition.x * Tile::TILE_SIZE.x,
|
||||
tilePosition.y * Tile::TILE_SIZE.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::vector<sf::Vector2f>(ret.begin(), ret.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts floor tiles into path finder, using a quadtree approach to group
|
||||
* tiles where possible.
|
||||
*
|
||||
* @param area The area to generate areas for.
|
||||
*/
|
||||
void
|
||||
Generator::generateAreas(const sf::IntRect& area) {
|
||||
assert(area.width > 0 && area.height > 0);
|
||||
|
||||
int wallCount = 0;
|
||||
for (int x = area.left; x < area.left + area.width; x++)
|
||||
for (int y = area.top; y < area.top + area.height; y++)
|
||||
wallCount += (int) (mTiles[x][y] == Tile::Type::WALL);
|
||||
|
||||
if (wallCount == 0)
|
||||
mPathfinder.insertArea(sf::FloatRect(area.left, area.top, area.width, area.height));
|
||||
else if (wallCount == area.width * area.height)
|
||||
return;
|
||||
else {
|
||||
int halfWidth = area.width / 2;
|
||||
int halfHeight = area.height / 2;
|
||||
generateAreas(sf::IntRect(area.left,
|
||||
area.top, halfWidth, halfHeight));
|
||||
generateAreas(sf::IntRect(area.left + halfWidth,
|
||||
area.top, halfWidth, halfHeight));
|
||||
generateAreas(sf::IntRect(area.left,
|
||||
area.top + halfHeight, halfWidth, halfHeight));
|
||||
generateAreas(sf::IntRect(area.left + halfWidth,
|
||||
area.top + halfHeight, halfWidth, halfHeight));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid position (floor) for the player to spawn at.
|
||||
*/
|
||||
sf::Vector2f
|
||||
Generator::getPlayerSpawn() const {
|
||||
sf::Vector2i spawn = findClosestFloor(sf::Vector2i(0, 0));
|
||||
return sf::Vector2f(spawn.x * Tile::TILE_SIZE.x,
|
||||
spawn.y * Tile::TILE_SIZE.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the point array index closest to position which has a floor tile.
|
||||
*
|
||||
* @warn Will fail if no floor tile has been generated yet.
|
||||
* @position Point to start search for a floor tile from.
|
||||
*/
|
||||
sf::Vector2i
|
||||
Generator::findClosestFloor(const sf::Vector2i& position) const {
|
||||
auto compare = [](const sf::Vector2i& a, const sf::Vector2i& b) {
|
||||
return a.x < b.x || (a.x == b.x && a.y < b.y);
|
||||
};
|
||||
std::map<sf::Vector2i, float, decltype(compare)> open(compare);
|
||||
std::set<sf::Vector2i, decltype(compare)> closed(compare);
|
||||
sf::Vector2i start = position;
|
||||
auto makePair = [&start](const sf::Vector2i& point) {
|
||||
return std::make_pair(point, thor::length(sf::Vector2f(point - start)));
|
||||
};
|
||||
|
||||
open.insert(makePair(start));
|
||||
while (!open.empty()) {
|
||||
auto intComp = [](const std::pair<sf::Vector2i, float>& left,
|
||||
const std::pair<sf::Vector2i, float>& right) {
|
||||
return left.second < right.second;
|
||||
};
|
||||
sf::Vector2i current = std::min_element(open.begin(), open.end(), intComp)->first;
|
||||
open.erase(current);
|
||||
closed.insert(current);
|
||||
if (mTiles.count(current.x) != 0 &&
|
||||
mTiles.at(current.x).count(current.y) != 0 &&
|
||||
mTiles.at(current.x).at(current.y) == Tile::Type::FLOOR) {
|
||||
return current;
|
||||
}
|
||||
else {
|
||||
if (closed.find(sf::Vector2i(current.x + 1, current.y)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x + 1, current.y)));
|
||||
if (closed.find(sf::Vector2i(current.x, current.y + 1)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x, current.y + 1)));
|
||||
if (closed.find(sf::Vector2i(current.x - 1, current.y)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x - 1, current.y)));
|
||||
if (closed.find(sf::Vector2i(current.x, current.y - 1)) == closed.end())
|
||||
open.insert(makePair(sf::Vector2i(current.x, current.y - 1)));
|
||||
}
|
||||
}
|
||||
// No floor tile found in the entire world.
|
||||
assert(false);
|
||||
return sf::Vector2i();
|
||||
}
|
||||
|
|
|
@ -1,60 +1,55 @@
|
|||
/*
|
||||
* Generator.h
|
||||
*
|
||||
* Created on: 07.04.2013
|
||||
* Author: Felix
|
||||
*/
|
||||
|
||||
#ifndef DG_GENERATOR_H_
|
||||
#define DG_GENERATOR_H_
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
#include "../sprites/Tile.h"
|
||||
#include "SimplexNoise.h"
|
||||
|
||||
class World;
|
||||
class Pathfinder;
|
||||
|
||||
/**
|
||||
* Procedurally generates tiles, chooses player and enemy spawn positions.
|
||||
*/
|
||||
class Generator {
|
||||
public:
|
||||
explicit Generator(World& world, Pathfinder& pathfinder);
|
||||
void generateCurrentAreaIfNeeded(const sf::Vector2f& position);
|
||||
sf::Vector2f getPlayerSpawn() const;
|
||||
std::vector<sf::Vector2f> getEnemySpawns(const sf::IntRect& area);
|
||||
|
||||
private:
|
||||
typedef Tile::Type type;
|
||||
typedef std::map<int, std::map<int, type> > array;
|
||||
|
||||
private:
|
||||
void generateAreas(const sf::IntRect& area);
|
||||
void generateTiles(const sf::IntRect& area);
|
||||
sf::Vector2i findClosestFloor(const sf::Vector2i& position) const;
|
||||
|
||||
static void fill(array& tiles, const sf::IntRect& area, type value);
|
||||
void filterWalls(array& tiles, int x, int y, int longside,
|
||||
int shortside, int subtract);
|
||||
int countWalls(const sf::IntRect& area);
|
||||
static type getTileType(float value);
|
||||
|
||||
private:
|
||||
static const int GENERATE_AREA_SIZE;
|
||||
static const float GENERATE_AREA_RANGE;
|
||||
|
||||
World& mWorld;
|
||||
Pathfinder& mPathfinder;
|
||||
/// Contains values of all tiles that have yet been generated.
|
||||
array mTiles;
|
||||
/// Stores where tiles have already been generated.
|
||||
std::map<int, std::map<int, bool> > mGenerated;
|
||||
/// Perlin noise used for tile generation.
|
||||
SimplexNoise mTileNoise;
|
||||
/// Perlin noise used for character placement.
|
||||
SimplexNoise mCharacterNoise;
|
||||
};
|
||||
|
||||
#endif /* DG_GENERATOR_H_ */
|
||||
/*
|
||||
* Generator.h
|
||||
*
|
||||
* Created on: 07.04.2013
|
||||
* Author: Felix
|
||||
*/
|
||||
|
||||
#ifndef DG_GENERATOR_H_
|
||||
#define DG_GENERATOR_H_
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
#include "../sprites/Tile.h"
|
||||
#include "SimplexNoise.h"
|
||||
|
||||
class World;
|
||||
class Pathfinder;
|
||||
|
||||
/**
|
||||
* Procedurally generates tiles, chooses player and enemy spawn positions.
|
||||
*/
|
||||
class Generator {
|
||||
public:
|
||||
explicit Generator(World& world, Pathfinder& pathfinder);
|
||||
void generateCurrentAreaIfNeeded(const sf::Vector2f& position);
|
||||
sf::Vector2f getPlayerSpawn() const;
|
||||
std::vector<sf::Vector2f> getEnemySpawns(const sf::IntRect& area);
|
||||
|
||||
private:
|
||||
typedef std::map<int, std::map<int, Tile::Type> > array;
|
||||
|
||||
private:
|
||||
void generateAreas(const sf::IntRect& area);
|
||||
void generateTiles(const sf::IntRect& area);
|
||||
sf::Vector2i findClosestFloor(const sf::Vector2i& position) const;
|
||||
std::vector<sf::Vector2i> createMinimalSpanningTree(
|
||||
const sf::Vector2i& start, const float limit);
|
||||
|
||||
private:
|
||||
static const int GENERATE_AREA_SIZE;
|
||||
static const float GENERATE_AREA_RANGE;
|
||||
|
||||
World& mWorld;
|
||||
Pathfinder& mPathfinder;
|
||||
/// Contains values of all tiles that have yet been generated.
|
||||
array mTiles;
|
||||
/// Stores where tiles have already been generated.
|
||||
std::map<int, std::map<int, bool> > mGenerated;
|
||||
/// Perlin noise used for tile generation.
|
||||
SimplexNoise mTileNoise;
|
||||
/// Perlin noise used for character placement.
|
||||
SimplexNoise mCharacterNoise;
|
||||
};
|
||||
|
||||
#endif /* DG_GENERATOR_H_ */
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
class Tile : public Rectangle {
|
||||
public:
|
||||
enum class Type : char {
|
||||
FLOOR,
|
||||
WALL
|
||||
WALL,
|
||||
FLOOR
|
||||
};
|
||||
|
||||
public:
|
||||
|
|
Reference in a new issue