Created
March 20, 2022 13:53
-
-
Save sppmacd/b2c54a84c0ce2a2f2a0a62b986f1afa3 to your computer and use it in GitHub Desktop.
Hexagon Tiling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <SFML/Graphics.hpp> | |
#include <SFML/Graphics/CircleShape.hpp> | |
#include <cmath> | |
#include <fmt/format.h> | |
#include <iostream> | |
#include <map> | |
int modulo_euclidean(int a, int b) | |
{ | |
int m = a % b; | |
if(m < 0) | |
{ | |
// m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN | |
m = (b < 0) ? m - b : m + b; | |
} | |
return m; | |
} | |
struct Block | |
{ | |
bool is_black { false }; | |
}; | |
class Chunk | |
{ | |
public: | |
Block& block(size_t index) | |
{ | |
if(index > 256) | |
std::cout << "oob " << index << std::endl; | |
return m_blocks[index]; | |
} | |
private: | |
Block m_blocks[256]; | |
}; | |
struct ChunkAndPosition | |
{ | |
Chunk& chunk; | |
size_t index; | |
}; | |
template<> | |
struct std::hash<sf::Vector2i> | |
{ | |
size_t operator()(sf::Vector2i const& vec) const | |
{ | |
return (size_t)vec.x << 32 | (size_t)vec.y; | |
} | |
}; | |
class World | |
{ | |
public: | |
sf::Vector2i calculate_chunk_coords(sf::Vector2i hexagon_coords) | |
{ | |
sf::Vector2i chunk_coords { hexagon_coords.x / 16, hexagon_coords.y / 16 }; | |
if(hexagon_coords.x < 0) | |
chunk_coords.x -= 1; | |
if(hexagon_coords.y < 0) | |
chunk_coords.y -= 1; | |
return chunk_coords; | |
} | |
ChunkAndPosition ensure_chunk_at_position(sf::Vector2i coords) | |
{ | |
sf::Vector2i chunk_coords = calculate_chunk_coords(coords); | |
sf::Vector2i in_chunk_coords { modulo_euclidean(coords.x, 16), modulo_euclidean(coords.y, 16) }; | |
size_t index = in_chunk_coords.y * 16 + in_chunk_coords.x; | |
auto chunk = m_chunks.find(chunk_coords); | |
if(chunk != m_chunks.end()) | |
return ChunkAndPosition { chunk->second, index }; | |
auto new_chunk = m_chunks.insert({ chunk_coords, Chunk() }); | |
return ChunkAndPosition { new_chunk.first->second, index }; | |
} | |
Block& ensure_block(sf::Vector2i coords) | |
{ | |
auto chunk = ensure_chunk_at_position(coords); | |
return chunk.chunk.block(chunk.index); | |
} | |
private: | |
std::unordered_map<sf::Vector2i, Chunk> m_chunks; | |
}; | |
constexpr float HEXAGON_SIZE = 60; | |
sf::Vector2f hexagon_to_cartesian(sf::Vector2i vec) | |
{ | |
return sf::Vector2f { static_cast<float>((vec.x + vec.y / 2.f) * std::sqrt(3.f)), static_cast<float>(vec.y) * 1.5f } * HEXAGON_SIZE; | |
} | |
sf::Vector2i cartesian_to_hexagon(sf::Vector2f cartesian) | |
{ | |
sf::Vector2f cartesian_normalized { cartesian.x / (HEXAGON_SIZE * std::sqrt(3.f)), cartesian.y / (HEXAGON_SIZE * 3 / 2.f) }; | |
auto remainder_y = std::remainder(cartesian_normalized.y, 1.f); | |
bool easy_case = std::abs(remainder_y) * 3 < 1; | |
if(easy_case) | |
{ | |
auto result_y = static_cast<int>(std::round(cartesian_normalized.y)); | |
sf::Vector2i result { static_cast<int>(std::round(cartesian_normalized.x - result_y / 2.f)), result_y }; | |
return result; | |
} | |
auto remainder_x = std::remainder(std::remainder(cartesian_normalized.y, 2.f) > 0 ? cartesian_normalized.x - 0.5f : cartesian_normalized.x, 1.f); | |
bool rising = remainder_x < 0; | |
auto remainder_y_transformed = remainder_y < 0 ? (remainder_y + 1) * 3.f - 1 : remainder_y * 3.f - 1; | |
auto cartesian_normalized_y_rounded_to_vertical_line = std::round(cartesian_normalized.y + 1 / .6f) - 1.f; | |
if(rising) | |
{ | |
bool above = 2 * (-remainder_x - 0.5) + 1 > remainder_y_transformed; | |
auto result_y = static_cast<int>(cartesian_normalized_y_rounded_to_vertical_line); | |
if(above) | |
result_y--; | |
sf::Vector2i result { static_cast<int>(std::round(cartesian_normalized.x - result_y / 2.f)), result_y }; | |
return result; | |
} | |
bool above = 2 * remainder_x > remainder_y_transformed; | |
auto result_y = static_cast<int>(cartesian_normalized_y_rounded_to_vertical_line); | |
if(above) | |
result_y--; | |
sf::Vector2i result { static_cast<int>(std::round(cartesian_normalized.x - result_y / 2.f)), result_y }; | |
return result; | |
} | |
int main() | |
{ | |
sf::RenderWindow wnd(sf::VideoMode(500, 500), "Hexagon Tiling"); | |
wnd.setView(sf::View(sf::Vector2f(), sf::Vector2f(wnd.getSize()))); | |
sf::Font font; | |
font.loadFromFile("res/font.ttf"); | |
World world; | |
for(int x = -16; x < 16; x++) | |
{ | |
for(int y = -16; y < 16; y++) | |
{ | |
world.ensure_block({ x, y }).is_black = y % 2; | |
} | |
} | |
while(wnd.isOpen()) | |
{ | |
sf::Event event; | |
while(wnd.pollEvent(event)) | |
{ | |
if(event.type == sf::Event::Resized) | |
{ | |
auto view = wnd.getView(); | |
view.setSize({ static_cast<float>(event.size.width), static_cast<float>(event.size.height) }); | |
wnd.setView(view); | |
} | |
} | |
wnd.clear(sf::Color::White); | |
auto world_view = wnd.getView(); | |
auto ccoords = cartesian_to_hexagon(wnd.mapPixelToCoords(sf::Mouse::getPosition(wnd), world_view)); | |
for(int x = -16; x < 16; x++) | |
{ | |
for(int y = -16; y < 16; y++) | |
{ | |
auto& block = world.ensure_block({ x, y }); | |
auto chunk_pos = world.calculate_chunk_coords({ x, y }); | |
auto pos = hexagon_to_cartesian({ x, y }); | |
sf::CircleShape cs(HEXAGON_SIZE, 6); | |
cs.setOrigin(cs.getRadius(), cs.getRadius()); | |
cs.setPosition(pos); | |
// FIXME: This hit test should be better | |
cs.setFillColor(ccoords == sf::Vector2i { x, y } ? sf::Color::Red : sf::Color::Transparent); | |
cs.setOutlineColor(block.is_black ? sf::Color::Black : sf::Color::Red); | |
cs.setOutlineThickness(-1.f); | |
wnd.draw(cs); | |
sf::Text text(fmt::format("{},{}\n({},{})", x, y, chunk_pos.x, chunk_pos.y), font, 15); | |
text.setPosition(pos); | |
text.setFillColor(sf::Color::Black); | |
auto bounds = text.getLocalBounds(); | |
text.setOrigin({ bounds.width / 2, bounds.height / 2 }); | |
wnd.draw(text); | |
} | |
} | |
// GUI | |
wnd.setView(sf::View(sf::FloatRect({}, world_view.getSize()))); | |
sf::Text text(fmt::format("{}, {}", ccoords.x, ccoords.y), font, 15); | |
text.setFillColor(sf::Color::Black); | |
text.setPosition(20, 20); | |
wnd.draw(text); | |
wnd.setView(world_view); | |
wnd.display(); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment