13 concept void_or_boolean = std::same_as<void, T> || std::convertible_to<T, bool>;
22 template <
typename FUNC>
23 concept tile_callback =
requires (
FUNC func) { {
func(glm::ivec2{ 0, 0 }) } -> void_or_boolean; };
24 template <
typename FUNC,
typename T>
25 concept query_tile_callback =
requires (
FUNC func, T
const&
td) { {
func(glm::ivec2{ 0, 0 },
td) } -> std::convertible_to<bool>; };
26 template <
typename FUNC,
typename T>
27 concept change_tile_callback =
requires (
FUNC func, T&
td) { {
func(glm::ivec2{ 0, 0 },
td) }; };
29 template <
typename TILE_DATA,
bool RESIZABLE = true>
34 static constexpr bool resizable =
RESIZABLE;
40 grid(
int w,
int h) { Reset(
w,
h); }
41 explicit grid(glm::ivec2 size) : grid(size.x, size.y) {}
47 void reset(glm::ivec2 size)
requires RESIZABLE { Reset(size.x, size.y); }
50 template <change_tile_callback<TILE_DATA> TILE_RESET_FUNC>
59 [[
nodiscard]]
TILE_DATA const& operator[](glm::ivec2 v)
const {
return mTiles[index(v)]; }
61 [[
nodiscard]]
bool is_valid(
int x,
int y)
const noexcept {
return x >= 0 && y >= 0 && x < mWidth && y < mHeight; }
62 [[
nodiscard]]
bool is_valid(glm::vec2 world_pos, glm::vec2 tile_size)
const noexcept {
return is_valid(world_pos_to_tile_pos(world_pos, tile_size)); }
63 [[
nodiscard]]
bool is_valid(glm::ivec2
pos)
const noexcept {
return is_valid(
pos.x,
pos.y); }
64 [[
nodiscard]]
bool is_index_valid(
int index)
const noexcept {
return index >= 0 && index < (
int)mTiles.size(); }
67 [[
nodiscard]]
bool is_valid(glm::vec2 world_pos, glm::vec2 tile_size,
int edge_width)
const noexcept {
return is_valid(world_pos_to_tile_pos(world_pos, tile_size),
edge_width); }
70 [[
nodiscard]]
inline int index(
int x,
int y)
const noexcept {
return x + y * mWidth; }
71 [[
nodiscard]]
inline int index(glm::ivec2
pos)
const noexcept {
return pos.x +
pos.y * mWidth; }
72 [[
nodiscard]]
inline int valid_index(
int x,
int y)
const noexcept {
return is_valid(x, y) ? x + y * mWidth : -1; }
79 [[
nodiscard]]
TILE_DATA const* at_index(
int index)
const noexcept {
return is_index_valid(index) ? &mTiles[index] :
nullptr; }
80 [[
nodiscard]]
TILE_DATA* at_index(
int index)
noexcept {
return is_index_valid(index) ? &mTiles[index] :
nullptr; }
87 [[
nodiscard]]
int width()
const noexcept {
return mWidth; }
88 [[
nodiscard]]
int height()
const noexcept {
return mHeight; }
89 [[
nodiscard]] glm::ivec2 size()
const noexcept {
return { mWidth, mHeight }; }
90 [[
nodiscard]] irec2 perimeter()
const noexcept {
return irec2::from_size({}, size()); }
91 [[
nodiscard]] irec2 bounds()
const noexcept {
return perimeter(); }
92 [[
nodiscard]] rec2 bounds(glm::vec2 tile_size)
const noexcept {
return perimeter() * tile_size; }
93 [[
nodiscard]] std::span<TILE_DATA const> tiles()
const {
return mTiles; }
94 [[
nodiscard]] std::span<TILE_DATA> tiles() {
return mTiles; }
95 [[
nodiscard]]
size_t tile_count()
const noexcept {
return mTiles.size(); }
103 enum class iteration_flags
112 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::with_self, iteration_flags::only_val
id },
typename FUNC >
113 auto for_each_neighbor(glm::ivec2
of,
FUNC&&
func)
const
115 static constexpr auto ONLY_VALID =
FLAGS.contain(iteration_flags::only_valid);
118 if constexpr (std::is_void_v<return_type>)
120 if constexpr (
FLAGS.contain(iteration_flags::with_self))
127 if constexpr (
FLAGS.contain(iteration_flags::diagonals))
137 if constexpr (
FLAGS.contain(iteration_flags::with_self))
144 if constexpr (
FLAGS.contain(iteration_flags::diagonals))
151 return return_type{};
155 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::with_self, iteration_flags::only_val
id },
typename FUNC >
158 static constexpr auto ONLY_VALID =
FLAGS.contain(iteration_flags::only_valid);
161 if constexpr (std::is_void_v<return_type>)
163 if constexpr (
FLAGS.contain(iteration_flags::with_self))
172 if constexpr (
FLAGS.contain(iteration_flags::with_self))
181 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename FUNC>
184 static constexpr auto ONLY_VALID =
FLAGS.contain(iteration_flags::only_valid);
191 for (
int y =
rect.top(); y <
rect.bottom(); y++)
192 for (
int x =
rect.left(); x <
rect.right(); x++)
194 if constexpr (std::is_void_v<return_type>)
203 return return_type{};
206 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename FUNC >
207 auto for_each_tile_in_perimeter(irec2
const& tile_rect, FUNC&& func)
const
209 static constexpr auto ONLY_VALID = FLAGS.contain(iteration_flags::only_valid);
210 using return_type =
decltype(this->apply<ONLY_VALID>(glm::ivec2{ 0, 0 }, func));
212 irec2 rect = tile_rect;
213 if constexpr (ONLY_VALID)
214 rect = tile_rect.clipped_to(bounds());
216 if constexpr (std::is_void_v<return_type>)
218 for (
int x = rect.left(); x < rect.right(); x++)
220 apply<ONLY_VALID>({ x, rect.top() }, func);
221 apply<ONLY_VALID>({ x, rect.bottom() - 1 }, func);
223 for (
int y = rect.top() + 1; y < rect.bottom() - 1; y++)
225 apply<ONLY_VALID>({ rect.left(), y }, func);
226 apply<ONLY_VALID>({ rect.right() - 1, y }, func);
231 for (
int x = rect.left(); x < rect.right(); x++)
233 if (
auto ret = apply<ONLY_VALID>({ x, rect.top() }, func))
return ret;
234 if (
auto ret = apply<ONLY_VALID>({ x, rect.bottom() - 1 }, func))
return ret;
236 for (
int y = rect.top() + 1; y < rect.bottom() - 1; y++)
238 if (
auto ret = apply<ONLY_VALID>({ rect.left(), y }, func))
return ret;
239 if (
auto ret = apply<ONLY_VALID>({ rect.right() - 1, y }, func))
return ret;
242 return return_type{};
246 template<enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename TILE_SET,
typename FUNC >
247 auto for_each_tile_in_set(TILE_SET&& tiles, FUNC&& func)
const
249 static constexpr auto ONLY_VALID = FLAGS.contain(iteration_flags::only_valid);
250 using return_type =
decltype(this->apply<ONLY_VALID>(glm::ivec2{ 0, 0 }, func));
252 if constexpr (std::is_void_v<return_type>)
254 for (
auto&& tile : tiles)
255 apply<ONLY_VALID>(tile, func);
259 for (
auto&& tile : tiles)
260 if (auto ret = apply<ONLY_VALID>(tile, func)) return ret;
261 return return_type{};
265 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename FUNC>
266 auto for_each_tile(
this auto&& self, FUNC&& func)
268 irec2 rect = { 0, 0, self.mWidth, self.mHeight };
269 return self.template for_each_tile_in_rect<FLAGS>(rect, std::forward<FUNC>(func));
272 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename FUNC>
273 auto for_each_tile_in_polygon(std::span<glm::vec2 const> poly_points, glm::vec2 tile_size, FUNC&& func)
const;
275 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename FUNC>
276 auto for_each_tile_in_row(
int row, FUNC&& func)
const;
278 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename FUNC>
279 auto for_each_tile_in_column(
int column, FUNC&& func)
const;
283 template <
typename FUNC>
284 bool line_cast(glm::ivec2 start, glm::ivec2 end, FUNC&& blocks_func,
bool ignore_start)
const
286 int delta_x{ end.x - start.x };
288 signed char const ix((delta_x > 0) - (delta_x < 0));
289 delta_x = std::abs(delta_x) << 1;
291 int delta_y(end.y - start.y);
293 signed char const iy((delta_y > 0) - (delta_y < 0));
294 delta_y = std::abs(delta_y) << 1;
296 if (!ignore_start && blocks_func(start))
299 if (delta_x >= delta_y)
302 int error(delta_y - (delta_x >> 1));
304 while (start.x != end.x)
307 if ((error > 0) || (!error && (ix > 0)))
317 if (blocks_func(start))
324 int error(delta_x - (delta_y >> 1));
326 while (start.y != end.y)
329 if ((error > 0) || (!error && (iy > 0)))
339 if (blocks_func(start))
349 template <
bool ONLY_VALID = true,
typename FUNC>
350 auto apply(
this auto&& self, glm::ivec2 to, FUNC&& func)
353 using self_type = std::remove_reference_t<
decltype(self)>;
354 using tile_data_type = std::conditional_t<std::is_const_v<self_type>, std::add_const_t<typename self_type::tile_data_type>,
typename self_type::tile_data_type>;
355 using invocable_type = std::remove_cvref_t<FUNC>;
356 if constexpr (std::invocable<invocable_type, glm::ivec2, tile_data_type&>)
357 return (ONLY_VALID && self.is_valid(to)) ? func(to, *self.at(to)) : decltype(func(
to, *self.
at(
to))){};
358 else if constexpr (std::invocable<invocable_type, tile_data_type&, glm::ivec2>)
359 return (ONLY_VALID && self.is_valid(to)) ? func(*self.at(to),
to) : decltype(func(*self.
at(
to),
to)){};
360 else if constexpr (std::invocable<invocable_type, tile_data_type&>)
361 return (ONLY_VALID && self.is_valid(to)) ? func(*self.at(to)) : decltype(func(*self.
at(
to))){};
363 return (ONLY_VALID && self.is_valid(to)) ? func(to) : decltype(func(
to)){};
366 void flip_row(
int row)
368 if (row >= 0 && row < mHeight)
369 std::reverse(GetRowStart(row), GetRowStart(row) + mWidth);
372 void flip_horizontal()
374 for (
int i = 0; i < mHeight; i++)
375 std::reverse(GetRowStart(i), GetRowStart(i) + mWidth);
380 for (
int i = 0; i < mHeight / 2; i++)
381 std::swap_ranges(GetRowStart(i), GetRowStart(i) + mWidth, GetRowStart(mHeight - i - 1));
384 void rotate_row(
int row,
int by);
385 void rotate_column(
int column,
int by);
387 void shift_row(
int row,
int by, TILE_DATA
const& add_tile);
388 void shift_column(
int column,
int by, TILE_DATA
const& add_tile);
390 void rotate_rows(
int by);
391 void rotate_columns(
int by);
393 void shift_rows(
int by, TILE_DATA
const& add_tile)
398 auto num_elems_to_shift = by * mWidth;
399 std::move_backward(mTiles.begin(), mTiles.begin() + (mTiles.size() - num_elems_to_shift), mTiles.end());
400 std::ranges::fill_n(mTiles.begin(), num_elems_to_shift, add_tile);
404 auto num_elems_to_shift = -by * mWidth;
405 std::move(mTiles.begin() + num_elems_to_shift, mTiles.end(), mTiles.begin());
406 std::ranges::fill_n(mTiles.begin() + (mTiles.size() - num_elems_to_shift), num_elems_to_shift, add_tile);
409 void shift_columns(
int by, TILE_DATA
const& add_tile);
411 void rotate_180()
requires RESIZABLE
413 for (
int i = 0; i < mHeight / 2; i++)
414 std::swap_ranges(std::make_reverse_iterator(GetRowStart(i) + mWidth), std::make_reverse_iterator(GetRowStart(i)), GetRowStart(mHeight - i - 1));
418 std::reverse(GetRowStart(mHeight / 2 + 1), GetRowStart(mHeight / 2 + 1) + mWidth);
421 void resize(glm::uvec2 new_size,
const TILE_DATA& new_element)
requires RESIZABLE
423 ResizeY(new_size.y, new_element);
424 ResizeX(new_size.x, new_element);
427 void clear() {
for (
auto& tile : mTiles) tile = {}; }
428 void clear(TILE_DATA
const& to) {
for (
auto& tile : mTiles) tile =
to; }
432 auto GetRowStart(
int row) {
return mTiles.begin() + row * mWidth; }
433 auto GetTileIterator(
int x,
int y) {
return mTiles.begin() + index(x, y); }
435 void Reset(
int w,
int h, TILE_DATA
const& default_tile)
437 if (w < 0)
throw std::invalid_argument{
"width cannot be negative" };
438 if (h < 0)
throw std::invalid_argument{
"height cannot be negative" };
443 mTiles.resize(w * h, default_tile);
446 template <change_tile_callback<TILE_DATA> TILE_RESET_FUNC>
447 void Reset(
int w,
int h, TILE_RESET_FUNC&& tile_reset)
449 if (w < 0)
throw std::invalid_argument{
"width cannot be negative" };
450 if (h < 0)
throw std::invalid_argument{
"height cannot be negative" };
455 mTiles.resize(w * h);
456 for_each_tile(tile_reset);
460 void Reset(
int w,
int h)
462 if (w < 0)
throw std::invalid_argument{
"width cannot be negative" };
463 if (h < 0)
throw std::invalid_argument{
"height cannot be negative" };
468 mTiles.resize(w * h);
471 void ResizeY(
int new_y,
const TILE_DATA& new_element)
473 if (new_y < 0)
throw std::invalid_argument(
"new_y cannot be negative");
475 const auto new_count = new_y * mWidth;
476 mTiles.resize(new_count, new_element);
480 void ResizeX(
int new_x,
const TILE_DATA& new_element)
482 if (new_x < 0)
throw std::invalid_argument(
"new_x cannot be negative");
500 const auto new_count = new_x * mHeight;
504 mTiles.resize(new_count, new_element);
506 for (
int yy = 0; yy < mHeight; ++yy)
508 auto y = mHeight - yy - 1;
509 const auto begin_range = y * mWidth;
510 const auto end_range = begin_range + mWidth;
511 const auto new_end_range = y * new_x + mWidth;
512 std::swap_ranges(std::make_reverse_iterator(mTiles.begin() + end_range), std::make_reverse_iterator(mTiles.begin() + begin_range), std::make_reverse_iterator(mTiles.begin() + new_end_range));
517 const auto dif = mWidth - new_x;
518 for (
int y = 1; y < mHeight; ++y)
520 const auto begin_range = y * mWidth;
521 const auto end_range = begin_range + new_x;
522 std::move(mTiles.begin() + begin_range, mTiles.begin() + end_range, mTiles.begin() + begin_range - (dif * y));
524 mTiles.resize(new_count);
532 std::vector<TILE_DATA> mTiles;
constexpr auto bit_count
Equal to the number of bits in the type.
constexpr decltype(auto) at(random_access_range auto &range, std::integral auto index)
Returns a reference to the value at index of range
constexpr bool valid_index(random_access_range auto &range, std::integral auto index)
Returns whether or not a given integer is a valid index to a random access range
constexpr CONTAINER to(RANGE &&range, TYPES &&... args)
to<container>();
A (constexpr) value struct that represents a set of bits mapped to an enum (implemented as a bitset)