Skip to content

Instantly share code, notes, and snippets.

@sppmacd
Created March 20, 2022 13:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sppmacd/b2c54a84c0ce2a2f2a0a62b986f1afa3 to your computer and use it in GitHub Desktop.
Save sppmacd/b2c54a84c0ce2a2f2a0a62b986f1afa3 to your computer and use it in GitHub Desktop.
Hexagon Tiling
#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