#include <array>
#include <cmath>
#include <SFML/Graphics.hpp>
#include "SimplexNoise.h"
enum class Orientation
: std::size_t {
North,
East,
South,
West
};
template<std::size_t MapSize, std::size_t TileSize>
class IsometricTileMap final
: public sf::Drawable
, public sf::Transformable {
public:
IsometricTileMap() noexcept
: orientation_{ Orientation::North }
, vertices_{ sf::Quads, MapSize * MapSize * 12u }
, position_{ 1.f, 1.f }
, simplexNoise_{ 0.2f, 1.f, 2.f, 1.f / 2.f }
, heightMap_{}
{
static_assert(
0u < MapSize && (MapSize & (MapSize - 1u)) == 0u,
"MapSize must be greater than 0 and a power of 2"
);
static_assert(
0u < TileSize && (TileSize & (TileSize - 1u)) == 0u,
"TileSize must be greater than 0 and a power of 2"
);
update();
auto bounds = vertices_.getBounds();
setOrigin(bounds.width / 2.f + bounds.left, bounds.height / 2.f + bounds.top);
}
void update() noexcept {
for(auto y = 0u; y < MapSize; ++y) {
for(auto x = 0u; x < MapSize; ++x) {
auto *const height = &heightMap_[(x + y * MapSize) * 4u];
float f =
simplexNoise_.fractal(
static_cast<size_t>(std::log(MapSize)),
static_cast<float>(x) / static_cast<float>(MapSize) + (position_.x / static_cast<float>(MapSize)),
static_cast<float>(y) / static_cast<float>(MapSize) + (position_.y / static_cast<float>(MapSize))
);
for(auto i = 0u; i < 4u; ++i) {
height[i] = static_cast<unsigned>(64.f * std::pow((f + 1.f) / 2.f, 3.f));
}
}
}
for(auto y = 0u; y < MapSize; ++y) {
for(auto x = 0u; x < MapSize; ++x) {
renderBlock(static_cast<float>(x), static_cast<float>(y));
}
}
}
void renderBlock(float x, float y) noexcept {
unsigned *height = nullptr;
auto *const block = &vertices_[(x + y * MapSize) * 12u];;
switch(orientation_) {
case Orientation::North:
height = &heightMap_[(x + y * MapSize) * 4u];
break;
case Orientation::South:
height = &heightMap_[((MapSize - (x + 1u)) + (MapSize - (y + 1u)) * MapSize) * 4u];
break;
case Orientation::West:
height = &heightMap_[((MapSize - (y + 1u)) + x * MapSize) * 4u];
break;
case Orientation::East:
height = &heightMap_[(y + (MapSize - (x + 1u)) * MapSize) * 4u];
break;
}
block[0u].position = {
(static_cast<float>(x) - static_cast<float>(y)) * static_cast<float>(TileSize),
(static_cast<float>(x) + static_cast<float>(y) - static_cast<float>(height[(0u + static_cast<unsigned>(orientation_)) % 4u]) * 2.f) * static_cast<float>(TileSize) / 2.f
};
block[1u].position = {
block[0u].position.x + static_cast<float>(TileSize),
block[0u].position.y + static_cast<float>(TileSize) / 2.f
};
block[2u].position = {
block[0u].position.x,
block[0u].position.y + static_cast<float>(TileSize)
};
block[3u].position = {
block[0u].position.x - static_cast<float>(TileSize),
block[0u].position.y + static_cast<float>(TileSize) / 2.f
};
block[4u].position = block[3u].position;
block[5u].position = block[2u].position;
block[6u].position = {
block[5u].position.x,
block[5u].position.y + (static_cast<float>(height[(3u + static_cast<unsigned>(orientation_)) % 4u]) + 1.f) * static_cast<float>(TileSize)
};
block[7u].position = {
block[4u].position.x,
block[4u].position.y + (static_cast<float>(height[(2u + static_cast<unsigned>(orientation_)) % 4u]) + 1.f) * static_cast<float>(TileSize)
};
block[8u].position = block[2u].position;
block[9u].position = block[1u].position;
block[10u].position = {
block[9u].position.x,
block[9u].position.y + (static_cast<float>(height[(2u + static_cast<unsigned>(orientation_)) % 4u]) + 1.f) * static_cast<float>(TileSize)
};
block[11u].position = {
block[8u].position.x,
block[8u].position.y + (static_cast<float>(height[(1u + static_cast<unsigned>(orientation_)) % 4u]) + 1.f) * static_cast<float>(TileSize)
};
for(auto i = 0u; i < 4u; ++i) {
block[i].color = { 102, 176, 50, 255 };
block[4u + i].color = { 101, 67, 33, 255 };
block[8u + i].color = { 129, 97, 60, 255 };
}
}
void scroll(Orientation direction) noexcept {
switch(direction) {
case Orientation::North:
position_ += { 0.f, -1.f };
break;
case Orientation::South:
position_ += { 0.f, 1.f };
break;
case Orientation::West:
position_ += { -1.f, 0.f };
break;
case Orientation::East:
position_ += { 1.f, 0.f };
break;
}
}
void rotate(Orientation orientation) noexcept {
orientation_ = orientation;
}
Orientation getOrientation() const noexcept {
return orientation_;
}
private:
void draw(sf::RenderTarget& renderTarget, sf::RenderStates renderStates) const override {
renderStates.transform *= getTransform();
renderTarget.draw(vertices_, renderStates);
}
private:
Orientation orientation_;
sf::VertexArray vertices_;
sf::Vector2f position_;
SimplexNoise simplexNoise_;
std::array<unsigned, MapSize * MapSize * 4u> heightMap_;
};
int main() {
sf::RenderWindow window{ { 1280u, 720u }, "SFML Application" };
window.setFramerateLimit(60.f);
IsometricTileMap<128u, 4u> isoMap;
sf::RenderStates renderStates;
renderStates.transform.translate(
std::abs(static_cast<float>(1280u / 2u) - isoMap.getPosition().x),
std::abs(static_cast<float>(720u / 2u) - isoMap.getPosition().y)
);
while(window.isOpen()) {
sf::Event event;
while(window.pollEvent(event)) {
if(event.type == sf::Event::Closed) {
window.close();
}
if(event.type == sf::Event::KeyPressed) {
bool updated = false;
switch(event.key.code) {
case sf::Keyboard::W:
isoMap.scroll(Orientation::North);
updated = true;
break;
case sf::Keyboard::S:
isoMap.scroll(Orientation::South);
updated = true;
break;
case sf::Keyboard::A:
isoMap.scroll(Orientation::West);
updated = true;
break;
case sf::Keyboard::D:
isoMap.scroll(Orientation::East);
updated = true;
break;
case sf::Keyboard::R:
isoMap.rotate(static_cast<Orientation>((static_cast<std::size_t>(isoMap.getOrientation()) + 1u) % 4u));
updated = true;
break;
case sf::Keyboard::Escape:
window.close();
break;
default:
break;
}
if(updated) {
isoMap.update();
updated = false;
}
}
}
window.clear();
window.draw(isoMap, renderStates);
window.display();
}
}