46 static constexpr bool resizable = RESIZABLE;
47 using tile_data_type = TILE_DATA;
49 grid()
requires RESIZABLE =
default;
50 grid(
int w,
int h, TILE_DATA
const& default_tile) { Reset(w, h, default_tile); }
51 grid(glm::ivec2 size, TILE_DATA
const& default_tile) : grid(size.x, size.y, default_tile) {}
52 grid(
int w,
int h) { Reset(w, h); }
53 explicit grid(glm::ivec2 size) : grid(size.x, size.y) {}
55 void reset(
int w,
int h, TILE_DATA
const& default_tile)
requires RESIZABLE { Reset(w, h, default_tile); }
56 void reset(
int w,
int h, std::vector<TILE_DATA> tiles)
requires RESIZABLE { Reset(w, h, tiles); }
57 void reset(
int w,
int h)
requires RESIZABLE { Reset(w, h); }
59 void reset(glm::ivec2 size)
requires RESIZABLE { Reset(size.x, size.y); }
60 void reset(glm::ivec2 size, TILE_DATA
const& default_tile)
requires RESIZABLE { Reset(size.x, size.y, default_tile); }
62 template <change_tile_callback<TILE_DATA> TILE_RESET_FUNC>
63 void reset_from(glm::ivec2 size, TILE_RESET_FUNC&& tile_reset)
requires RESIZABLE { Reset(size.x, size.y, std::forward<TILE_RESET_FUNC>(tile_reset)); }
67 [[nodiscard]] TILE_DATA&
operator[](
int i) {
return mTiles[i]; }
68 [[nodiscard]] TILE_DATA
const&
operator[](
int i)
const {
return mTiles[i]; }
70#if defined(__cpp_multidimensional_subscript)
71 [[nodiscard]] TILE_DATA&
operator[](
int x,
int y) {
return mTiles[index(x, y)]; }
72 [[nodiscard]] TILE_DATA
const&
operator[](
int x,
int y)
const {
return mTiles[index(x, y)]; }
75 [[nodiscard]] TILE_DATA&
operator[](glm::ivec2 v) {
return mTiles[index(v)]; }
76 [[nodiscard]] TILE_DATA
const&
operator[](glm::ivec2 v)
const {
return mTiles[index(v)]; }
78 [[nodiscard]]
bool is_valid(
int x,
int y)
const noexcept {
return x >= 0 && y >= 0 && x < mWidth && y < mHeight; }
79 [[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)); }
80 [[nodiscard]]
bool is_valid(glm::ivec2 pos)
const noexcept {
return is_valid(pos.x, pos.y); }
81 [[nodiscard]]
bool is_index_valid(
int index)
const noexcept {
return index >= 0 && index < (int)mTiles.size(); }
83 [[nodiscard]]
bool is_valid(
int x,
int y,
int edge_width)
const noexcept {
return x >= edge_width && y >= edge_width && x < mWidth - edge_width && y < mHeight - edge_width; }
84 [[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); }
85 [[nodiscard]]
bool is_valid(glm::ivec2 pos,
int edge_width)
const noexcept {
return is_valid(pos.x, pos.y, edge_width); }
87 [[nodiscard]]
inline int index(
int x,
int y)
const noexcept {
return x + y * mWidth; }
88 [[nodiscard]]
inline int index(glm::ivec2 pos)
const noexcept {
return pos.x + pos.y * mWidth; }
89 [[nodiscard]]
inline int valid_index(
int x,
int y)
const noexcept {
return is_valid(x, y) ? x + y * mWidth : -1; }
90 [[nodiscard]]
inline int valid_index(glm::ivec2 pos)
const noexcept {
return is_valid(pos) ? pos.x + pos.y * mWidth : -1; }
92 [[nodiscard]] TILE_DATA
const* at(glm::ivec2 pos)
const noexcept {
if (!is_valid(pos))
return nullptr;
return &mTiles[index(pos)]; }
93 [[nodiscard]] TILE_DATA
const* at(
int x,
int y)
const noexcept {
return at(glm::ivec2{ x, y }); }
94 [[nodiscard]] TILE_DATA* at(glm::ivec2 pos)
noexcept {
if (!is_valid(pos))
return nullptr;
return &mTiles[index(pos)]; }
95 [[nodiscard]] TILE_DATA* at(
int x,
int y)
noexcept {
return at(glm::ivec2{ x, y }); }
96 [[nodiscard]] TILE_DATA
const* at_index(
int index)
const noexcept {
return is_index_valid(index) ? &mTiles[index] :
nullptr; }
97 [[nodiscard]] TILE_DATA* at_index(
int index)
noexcept {
return is_index_valid(index) ? &mTiles[index] :
nullptr; }
99 [[nodiscard]] TILE_DATA
const& safe_at(glm::ivec2 pos, TILE_DATA
const& outside)
const noexcept {
if (
auto at = this->at(pos))
return *at;
return outside; }
100 [[nodiscard]] TILE_DATA
const& safe_at(
int x,
int y, TILE_DATA
const& outside)
const noexcept {
return safe_at(glm::ivec2{ x, y }, outside); }
101 [[nodiscard]] TILE_DATA& safe_at(glm::ivec2 pos, TILE_DATA& outside)
noexcept {
if (
auto at = this->at(pos))
return *at;
return outside; }
102 [[nodiscard]] TILE_DATA& safe_at(
int x,
int y, TILE_DATA& outside)
noexcept {
return safe_at(glm::ivec2{ x, y }, outside); }
104 [[nodiscard]]
int width() const noexcept {
return mWidth; }
105 [[nodiscard]]
int height() const noexcept {
return mHeight; }
106 [[nodiscard]] glm::ivec2 size() const noexcept {
return { mWidth, mHeight }; }
107 [[nodiscard]] irec2 perimeter() const noexcept {
return irec2::from_size({}, size()); }
108 [[nodiscard]] irec2 bounds() const noexcept {
return perimeter(); }
109 [[nodiscard]] rec2 bounds(glm::vec2 tile_size)
const noexcept {
return perimeter() * tile_size; }
110 [[nodiscard]] std::span<TILE_DATA const> tiles()
const {
return mTiles; }
111 [[nodiscard]] std::span<TILE_DATA> tiles() {
return mTiles; }
112 [[nodiscard]]
size_t tile_count() const noexcept {
return mTiles.size(); }
117 bool IsReachable(glm::ivec2 src_tile, glm::ivec2 dest_tile);
120 enum class iteration_flags
129 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::with_self, iteration_flags::only_val
id }>
132 static constexpr auto ONLY_VALID = FLAGS.contain(iteration_flags::only_valid);
135 if constexpr (std::is_void_v<return_type>)
137 if constexpr (FLAGS.contain(iteration_flags::with_self))
144 if constexpr (FLAGS.contain(iteration_flags::diagonals))
154 if constexpr (FLAGS.contain(iteration_flags::with_self))
161 if constexpr (FLAGS.contain(iteration_flags::diagonals))
168 return return_type{};
172 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::with_self, iteration_flags::only_val
id }>
173 auto for_each_selected_neighbor(glm::ivec2 of, direction_set neighbor_set, change_tile_callback<TILE_DATA>
auto&& func)
175 static constexpr auto ONLY_VALID = FLAGS.contain(iteration_flags::only_valid);
178 if constexpr (std::is_void_v<return_type>)
180 if constexpr (FLAGS.contain(iteration_flags::with_self))
183 neighbor_set.
for_each([
this, of, &func](direction d) {
189 if constexpr (FLAGS.contain(iteration_flags::with_self))
192 return neighbor_set.
for_each([
this, of, &func](direction d) {
199 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id }>
202 static constexpr auto ONLY_VALID = FLAGS.contain(iteration_flags::only_valid);
203 using return_type =
decltype(this->
template apply<ONLY_VALID>(glm::ivec2{ 0, 0 }, func));
205 irec2 rect = tile_rect;
206 if constexpr (ONLY_VALID)
207 rect = tile_rect.clipped_to(this->bounds());
209 for (
int y = rect.top(); y < rect.bottom(); y++)
210 for (
int x = rect.left(); x < rect.right(); x++)
212 if constexpr (std::is_void_v<return_type>)
221 return default_value<return_type>();
224 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id }>
225 auto for_each_tile_in_perimeter(irec2
const& tile_rect, change_tile_callback<TILE_DATA>
auto&& func)
227 static constexpr auto ONLY_VALID = FLAGS.contain(iteration_flags::only_valid);
230 irec2 rect = tile_rect;
231 if constexpr (ONLY_VALID)
232 rect = tile_rect.clipped_to(bounds());
234 if constexpr (std::is_void_v<return_type>)
236 for (
int x = rect.left(); x < rect.right(); x++)
241 for (
int y = rect.top() + 1; y < rect.bottom() - 1; y++)
249 for (
int x = rect.left(); x < rect.right(); x++)
254 for (
int y = rect.top() + 1; y < rect.bottom() - 1; y++)
260 return return_type{};
264 template<enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename TILE_SET>
265 auto for_each_tile_in_set(TILE_SET&& tiles, change_tile_callback<TILE_DATA>
auto&& func)
267 static constexpr auto ONLY_VALID = FLAGS.contain(iteration_flags::only_valid);
270 if constexpr (std::is_void_v<return_type>)
272 for (
auto&& tile : tiles)
277 for (
auto&& tile : tiles)
279 return return_type{};
283 template <change_tile_callback<TILE_DATA> FUNC>
284 auto for_each_tile(FUNC&& func)
286 irec2 rect = { 0, 0, this->mWidth, this->mHeight };
290 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id }>
291 auto for_each_tile_in_polygon(std::span<glm::vec2 const> poly_points, glm::vec2 tile_size, change_tile_callback<TILE_DATA>
auto&& func);
293 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename FUNC>
294 auto for_each_tile_in_row(
int row, FUNC&& func);
296 template <enum_flags<iteration_flags> FLAGS = enum_flags<iteration_flags>{ iteration_flags::only_val
id },
typename FUNC>
297 auto for_each_tile_in_column(
int column, FUNC&& func);
300 template <query_tile_callback<TILE_DATA> FUNC>
301 bool line_cast(glm::ivec2 start, glm::ivec2 end, FUNC&& blocks_func,
bool ignore_start)
const
303 int delta_x{ end.x - start.x };
305 signed char const ix((delta_x > 0) - (delta_x < 0));
306 delta_x = std::abs(delta_x) << 1;
308 int delta_y(end.y - start.y);
310 signed char const iy((delta_y > 0) - (delta_y < 0));
311 delta_y = std::abs(delta_y) << 1;
313 if (!ignore_start &&
apply(start, blocks_func))
316 if (delta_x >= delta_y)
319 int error(delta_y - (delta_x >> 1));
321 while (start.x != end.x)
324 if ((error > 0) || (!error && (ix > 0)))
334 if (
apply(start, blocks_func))
341 int error(delta_x - (delta_y >> 1));
343 while (start.y != end.y)
346 if ((error > 0) || (!error && (iy > 0)))
356 if (
apply(start, blocks_func))
366 template <
bool ONLY_VALID = true, change_tile_callback<TILE_DATA> FUNC>
370 using invocable_type = std::remove_cvref_t<FUNC>;
371 if constexpr (std::invocable<invocable_type, glm::ivec2, tile_data_type&>)
372 return (!ONLY_VALID || this->is_valid(
to)) ? func(
to, *this->at(
to)) : default_value<
decltype(func(
to, *this->at(
to)))>();
373 else if constexpr (std::invocable<invocable_type, tile_data_type&, glm::ivec2>)
374 return (!ONLY_VALID || this->is_valid(
to)) ? func(*this->at(
to),
to) : default_value<
decltype(func(*this->at(
to),
to))>();
375 else if constexpr (std::invocable<invocable_type, tile_data_type&>)
376 return (!ONLY_VALID || this->is_valid(
to)) ? func(*this->at(
to)) : default_value<
decltype(func(*this->at(
to)))>();
378 return (!ONLY_VALID || this->is_valid(
to)) ? func(
to) : default_value<decltype(func(to))>();
381 template <
bool ONLY_VALID = true, tile_callback<TILE_DATA> FUNC>
382 auto apply(glm::ivec2
to, FUNC&& func)
const
384 using invocable_type = std::remove_cvref_t<FUNC>;
385 if constexpr (std::invocable<invocable_type, glm::ivec2, tile_data_type const&>)
386 return (!ONLY_VALID || this->is_valid(
to)) ? func(
to, *this->
at(
to)) : default_value<decltype(func(
to, *this->
at(
to)))>();
387 else if constexpr (std::invocable<invocable_type, tile_data_type const&, glm::ivec2>)
388 return (!ONLY_VALID || this->is_valid(
to)) ? func(*this->
at(
to),
to) : default_value<
decltype(func(*this->
at(
to),
to))>();
389 else if constexpr (std::invocable<invocable_type, tile_data_type const&>)
390 return (!ONLY_VALID || this->is_valid(
to)) ? func(*this->
at(
to)) : default_value<
decltype(func(*this->
at(
to)))>();
392 return (!ONLY_VALID || this->is_valid(
to)) ? func(
to) : default_value<
decltype(func(
to))>();
395 void flip_row(
int row)
397 if (row >= 0 && row < mHeight)
398 std::reverse(GetRowStart(row), GetRowStart(row) + mWidth);
401 void flip_horizontal()
403 for (
int i = 0; i < mHeight; i++)
404 std::reverse(GetRowStart(i), GetRowStart(i) + mWidth);
409 for (
int i = 0; i < mHeight / 2; i++)
410 std::swap_ranges(GetRowStart(i), GetRowStart(i) + mWidth, GetRowStart(mHeight - i - 1));
413 void rotate_row(
int row,
int by);
414 void rotate_column(
int column,
int by);
416 void shift_row(
int row,
int by, TILE_DATA
const& add_tile);
417 void shift_column(
int column,
int by, TILE_DATA
const& add_tile);
419 void rotate_rows(
int by);
420 void rotate_columns(
int by);
422 void shift_rows(
int by, TILE_DATA
const& add_tile)
427 auto num_elems_to_shift = by * mWidth;
428 std::move_backward(mTiles.begin(), mTiles.begin() + (mTiles.size() - num_elems_to_shift), mTiles.end());
429 std::ranges::fill_n(mTiles.begin(), num_elems_to_shift, add_tile);
433 auto num_elems_to_shift = -by * mWidth;
434 std::move(mTiles.begin() + num_elems_to_shift, mTiles.end(), mTiles.begin());
435 std::ranges::fill_n(mTiles.begin() + (mTiles.size() - num_elems_to_shift), num_elems_to_shift, add_tile);
438 void shift_columns(
int by, TILE_DATA
const& add_tile);
440 void rotate_180()
requires RESIZABLE
442 for (
int i = 0; i < mHeight / 2; i++)
443 std::swap_ranges(std::make_reverse_iterator(GetRowStart(i) + mWidth), std::make_reverse_iterator(GetRowStart(i)), GetRowStart(mHeight - i - 1));
447 std::reverse(GetRowStart(mHeight / 2 + 1), GetRowStart(mHeight / 2 + 1) + mWidth);
450 void resize(glm::uvec2 new_size,
const TILE_DATA& new_element)
requires RESIZABLE
452 ResizeY(new_size.y, new_element);
453 ResizeX(new_size.x, new_element);
456 void resize(glm::uvec2 new_size)
requires RESIZABLE
458 const TILE_DATA new_element{};
459 resize(new_size, new_element);
462 void resize(
int w,
int h)
requires RESIZABLE
464 if (w < 0)
throw std::invalid_argument{
"width cannot be negative" };
465 if (h < 0)
throw std::invalid_argument{
"height cannot be negative" };
469 void clear() {
for (
auto& tile : mTiles) tile = {}; }
470 void clear(TILE_DATA
const&
to) {
for (
auto& tile : mTiles) tile =
to; }
474 auto GetRowStart(
int row) {
return mTiles.begin() + row * mWidth; }
475 auto GetTileIterator(
int x,
int y) {
return mTiles.begin() + index(x, y); }
477 void Reset(
int w,
int h, TILE_DATA
const& default_tile)
479 if (w < 0)
throw std::invalid_argument{
"width cannot be negative" };
480 if (h < 0)
throw std::invalid_argument{
"height cannot be negative" };
485 mTiles.resize(w * h, default_tile);
488 template <change_tile_callback<TILE_DATA> TILE_RESET_FUNC>
489 void Reset(
int w,
int h, TILE_RESET_FUNC&& tile_reset)
491 if (w < 0)
throw std::invalid_argument{
"width cannot be negative" };
492 if (h < 0)
throw std::invalid_argument{
"height cannot be negative" };
497 mTiles.resize(w * h);
498 for_each_tile(tile_reset);
502 void Reset(
int w,
int h)
504 if (w < 0)
throw std::invalid_argument{
"width cannot be negative" };
505 if (h < 0)
throw std::invalid_argument{
"height cannot be negative" };
510 mTiles.resize(w * h);
513 void Reset(
int w,
int h, std::vector<TILE_DATA> tiles)
515 if (w < 0)
throw std::invalid_argument{
"width cannot be negative" };
516 if (h < 0)
throw std::invalid_argument{
"height cannot be negative" };
517 if (
size_t(w * h) > tiles.size())
throw std::invalid_argument{
"not enough tiles in vector" };
520 mTiles = std::move(tiles);
525 void ResizeY(
int new_y,
const TILE_DATA& new_element)
527 if (new_y < 0)
throw std::invalid_argument(
"new_y cannot be negative");
529 const auto new_count = new_y * mWidth;
530 mTiles.resize(new_count, new_element);
534 void ResizeX(
int new_x,
const TILE_DATA& new_element)
536 if (new_x < 0)
throw std::invalid_argument(
"new_x cannot be negative");
554 const auto new_count = new_x * mHeight;
558 mTiles.resize(new_count, new_element);
560 for (
int yy = 0; yy < mHeight; ++yy)
562 auto y = mHeight - yy - 1;
563 const auto begin_range = y * mWidth;
564 const auto end_range = begin_range + mWidth;
565 const auto new_end_range = y * new_x + mWidth;
566 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));
571 const auto dif = mWidth - new_x;
572 for (
int y = 1; y < mHeight; ++y)
574 const auto begin_range = y * mWidth;
575 const auto end_range = begin_range + new_x;
576 std::move(mTiles.begin() + begin_range, mTiles.begin() + end_range, mTiles.begin() + begin_range - (dif * y));
578 mTiles.resize(new_count);
586 std::vector<TILE_DATA> mTiles;