#include <array>
#include <bitset>
#include <cmath>
#include <type_traits>
#include <SFML/Graphics.hpp>
#include "SimplexNoise.h"
enum class Direction
: std::size_t
{
North = 0x0,
East = 0x1,
South = 0x2,
West = 0x3
};
struct Tile {
unsigned char height;
enum class State
: unsigned char {
// tile corners --
TopLeft = 0x1,
TopRight = 0x2,
BottomRight = 0x4,
BottomLeft = 0x8,
// tile states ------------------
Zero = 0x0,
One = TopLeft,
Two = TopLeft | TopRight,
Three = TopRight,
Four = TopRight | BottomRight,
Five = BottomRight,
Six = BottomRight | BottomLeft,
Seven = BottomLeft,
Eight = BottomLeft | TopLeft,
Nine = TopLeft | TopRight | BottomRight,
Ten = TopRight | BottomRight | BottomLeft,
Eleven = BottomRight | BottomLeft | TopLeft,
Twelve = BottomLeft | TopLeft | TopRight,
Thirteen = TopLeft | TopRight | BottomRight | BottomLeft
} state;
};
constexpr bool operator&(Tile::State lhs, Tile::State rhs) noexcept {
return static_cast<bool>(
static_cast<std::underlying_type_t<Tile::State>>(lhs) &
static_cast<std::underlying_type_t<Tile::State>>(rhs)
);
}
template<std::size_t MapSize, std::size_t TileSize>
class IsometricTilemap final
: public sf::Drawable
, public sf::Transformable {
public:
IsometricTilemap() noexcept
: tiles_{}
, position_{ 0.f, 0.f }
, vertices_{ sf::Quads, MapSize * MapSize * 12u }
, orientation_{ Direction::North }
, simplexNoise_{ 0.2f, 1.f, 2.f, 0.5f } {
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"
);
static_assert(static_cast<Tile::State>(15) == Tile::State::Thirteen);
update();
auto bounds = vertices_.getBounds();
setOrigin(bounds.width / 2.f + bounds.left, bounds.height / 2.f + bounds.top);
}
public:
void update() noexcept {
for(auto y = 0u; y < MapSize; ++y) {
for(auto x = 0u; x < MapSize; ++x) {
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))
);
auto& tile = tiles_[(x + y * MapSize)];
tile.height = static_cast<unsigned>(32.f * std::pow((f + 1.f) / 2.f, 3.f));
tile.state = Tile::State::Zero;
}
}
for(auto y = 0; y < static_cast<int>(MapSize); ++y) {
for(auto x = 0; x < static_cast<int>(MapSize); ++x) {
auto& tile = tiles_[x + y * MapSize];
unsigned char state = 0x0u;
for(auto j = -1; j < 1; ++j) {
for(auto i = -1; i < 1; ++i) {
if(i == 0 && j == 0) continue;
if(auto neighbour = (x + i + (y + j) * static_cast<int>(MapSize));
0 <= neighbour && neighbour < static_cast<int>(MapSize * MapSize)) {
if(tiles_[neighbour].height == tile.height + 1u) {
int k = i + j * 3;
state |= 1u << static_cast<unsigned>(k < 0 ? k + 4 : k + 3);
}
}
}
}
tile.state = stateTable_[state];
}
}
for(auto y = 0u; y < MapSize; ++y) {
for(auto x = 0u; x < MapSize; ++x) {
renderBlock(x, y);
}
}
}
void renderBlock(unsigned x, unsigned y) noexcept {
auto& tile = tiles_[x + y * MapSize];
auto *const block = &vertices_[(x + y * MapSize) * 12u];
// orientation_ == Direction::North --------------------------------------------------------------------------------------
block[0u].position = cartesianToIsometric(x, y, tile.state & Tile::State::TopLeft ? tile.height + 1u : tile.height);
block[1u].position = cartesianToIsometric(x + 1u, y, tile.state & Tile::State::TopRight ? tile.height + 1u : tile.height);
block[2u].position = cartesianToIsometric(x + 1u, y + 1u, tile.state & Tile::State::BottomRight ? tile.height + 1u : tile.height);
block[3u].position = cartesianToIsometric(x, y + 1u, tile.state & Tile::State::BottomLeft ? tile.height + 1u : tile.height);
block[4u].position = cartesianToIsometric(x, y + 1u, tile.state & Tile::State::BottomLeft ? tile.height + 1u : tile.height);
block[5u].position = cartesianToIsometric(x + 1u, y + 1u, tile.state & Tile::State::BottomRight ? tile.height + 1u : tile.height);
block[6u].position = cartesianToIsometric(x + 1u, y + 1u, 0u);
block[7u].position = cartesianToIsometric(x, y + 1u, 0u);
block[8u].position = cartesianToIsometric(x + 1u, y + 1u, tile.state & Tile::State::BottomRight ? tile.height + 1u : tile.height);
block[9u].position = cartesianToIsometric(x + 1u, y, tile.state & Tile::State::TopRight ? tile.height + 1u : tile.height);
block[10u].position = cartesianToIsometric(x + 1u, y, 0u);
block[11u].position = cartesianToIsometric(x + 1u, y + 1u, 0u);
sf::Color color;
switch(tile.state) {
case Tile::State::Zero: color = { 0x73, 0xA3, 0x4D, 0xFF }; break;
case Tile::State::One: color = { 0x81, 0xAD, 0x5E, 0xFF }; break;
case Tile::State::Two: color = { 0x76, 0xA6, 0x4E, 0xFF }; break;
case Tile::State::Three: color = { 0x67, 0x9B, 0x3C, 0xFF }; break;
case Tile::State::Four: color = { 0x58, 0x8F, 0x2A, 0xFF }; break;
case Tile::State::Five: color = { 0x67, 0x9B, 0x3C, 0xFF }; break;
case Tile::State::Six: color = { 0x76, 0xA6, 0x4E, 0xFF }; break;
case Tile::State::Seven: color = { 0x81, 0xAD, 0x5E, 0xFF }; break;
case Tile::State::Eight: color = { 0x8C, 0xB4, 0x6E, 0xFF }; break;
default: color = { 0x81, 0xAD, 0x5E, 0xFF }; break;
}
for(auto i = 0u; i < 4u; ++i) {
block[i].color = color;
block[i + 4u].color = { 0x65, 0x43, 0x21, 0xFF };
block[i + 8u].color = { 0x81, 0x61, 0x3C, 0xFF };
}
}
void scroll(Direction direction) noexcept {
switch(direction) {
case Direction::North:
position_ += { 0.f, -1.f };
break;
case Direction::South:
position_ += { 0.f, 1.f };
break;
case Direction::West:
position_ += { -1.f, 0.f };
break;
case Direction::East:
position_ += { 1.f, 0.f };
break;
default: break;
}
}
private:
static inline sf::Vector2f cartesianToIsometric(unsigned x, unsigned y, unsigned z) noexcept {
return {
(static_cast<float>(x) - static_cast<float>(y)) * static_cast<float>(TileSize),
(static_cast<float>(x + y) - static_cast<float>(z) * 2.f) * (static_cast<float>(TileSize) / 2.f)
};
}
void draw(sf::RenderTarget& target, sf::RenderStates states) const override {
states.transform *= getTransform();
states.texture = nullptr;
target.draw(vertices_, states);
}
private:
std::array<Tile, MapSize * MapSize> tiles_;
sf::Vector2f position_;
sf::VertexArray vertices_;
Direction orientation_;
SimplexNoise simplexNoise_;
static constexpr std::array<Tile::State, 256u> stateTable_{
static_cast<Tile::State>(0),
static_cast<Tile::State>(1),
static_cast<Tile::State>(3),
static_cast<Tile::State>(3),
static_cast<Tile::State>(2),
static_cast<Tile::State>(3),
static_cast<Tile::State>(3),
static_cast<Tile::State>(3),
static_cast<Tile::State>(9),
static_cast<Tile::State>(9),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(6),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(6),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(8),
static_cast<Tile::State>(9),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(10),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(9),
static_cast<Tile::State>(9),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(11),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(12),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(13),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(12),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(13),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(4),
static_cast<Tile::State>(5),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(6),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(13),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(6),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(6),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(7),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(12),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(13),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(12),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(13),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(12),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(13),
static_cast<Tile::State>(13),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(14),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
static_cast<Tile::State>(15),
};
};
constexpr auto windowHeight = 720u,
windowWidth = 1280u;
int main() {
sf::RenderWindow window{ { windowWidth, windowHeight }, "SFML Application"};
window.setFramerateLimit(60.f);
IsometricTilemap<32u, 16u> tileMap;
tileMap.move(
std::abs(static_cast<float>(windowWidth / 2u) - tileMap.getPosition().x),
std::abs(static_cast<float>(windowHeight / 2u) - tileMap.getPosition().y)
);
bool updated = false;
while(window.isOpen()) {
sf::Event event;
while(window.pollEvent(event)) {
if(event.type == sf::Event::Closed) window.close();
if(event.type == sf::Event::KeyPressed) {
switch(event.key.code) {
case sf::Keyboard::W:
tileMap.scroll(Direction::North);
updated = true;
break;
case sf::Keyboard::S:
tileMap.scroll(Direction::South);
updated = true;
break;
case sf::Keyboard::A:
tileMap.scroll(Direction::West);
updated = true;
break;
case sf::Keyboard::D:
tileMap.scroll(Direction::East);
updated = true;
break;
case sf::Keyboard::Escape:
window.close();
break;
default:
break;
}
}
}
if(updated) {
tileMap.update();
updated = false;
}
window.clear();
window.draw(tileMap);
window.display();
}
}