Porytiles
Loading...
Searching...
No Matches
metatile.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4
11
12namespace porytiles {
13
14// Forward-declared to keep this widely-included header free of the Image header. metatile_count is a template, so the
15// definition only needs a complete Image template at the call site.
16template <typename PixelType>
17class Image;
18
19namespace metatile {
20
21inline constexpr std::size_t tiles_per_side = 2;
23inline constexpr std::size_t tiles_per_metatile = tiles_per_metatile_layer * 3;
24inline constexpr std::size_t side_length_pix = tiles_per_side * tile::side_length_pix;
25inline constexpr std::size_t entries_per_metatile_dual = 8;
26inline constexpr std::size_t entries_per_metatile_triple = 12;
27inline constexpr std::size_t metatiles_per_row = 8;
28
39template <typename PixelType>
40[[nodiscard]] std::size_t metatile_count(const Image<PixelType> &layer)
41{
42 return (layer.width() / side_length_pix) * (layer.height() / side_length_pix);
43}
44
45enum class Layer : std::uint8_t { bottom = 0, middle = 1, top = 2 };
46
47[[nodiscard]] inline std::string to_string(Layer layer)
48{
49 switch (layer) {
50 case Layer::bottom:
51 return "bottom";
52 case Layer::middle:
53 return "middle";
54 case Layer::top:
55 return "top";
56 }
57 panic("unhandled Layer value");
58}
59
60inline std::ostream &operator<<(std::ostream &os, const Layer &layer)
61{
62 os << to_string(layer);
63 return os;
64}
65
77{
78 switch (layer_type) {
80 return Layer::bottom;
82 return Layer::top;
84 return Layer::middle;
85 }
86 panic("unhandled LayerType value in dropped_layer_for");
87}
88
89enum class Subtile : std::uint8_t { northwest = 0, northeast = 1, southwest = 2, southeast = 3 };
90
91[[nodiscard]] inline Subtile subtile_from_index(std::size_t i)
92{
93 return static_cast<Subtile>(i);
94}
95
96[[nodiscard]] inline std::string to_string(Subtile layer)
97{
98 switch (layer) {
100 return "northwest(" + std::to_string(static_cast<std::uint8_t>(layer)) + ")";
102 return "northeast(" + std::to_string(static_cast<std::uint8_t>(layer)) + ")";
104 return "southwest(" + std::to_string(static_cast<std::uint8_t>(layer)) + ")";
106 return "southeast(" + std::to_string(static_cast<std::uint8_t>(layer)) + ")";
107 }
108 panic("unhandled Subtile value");
109}
110
111inline std::ostream &operator<<(std::ostream &os, const Subtile &subtile)
112{
113 os << to_string(subtile);
114 return os;
115}
116
132[[nodiscard]] inline std::tuple<std::size_t, Layer, Subtile> from_tile_index(std::size_t tile_index)
133{
134 const std::size_t metatile_index = tile_index / tiles_per_metatile;
135 const std::size_t local_index = tile_index % tiles_per_metatile;
136 const auto layer = static_cast<Layer>(local_index / tiles_per_metatile_layer);
137 const auto subtile = static_cast<Subtile>(local_index % tiles_per_metatile_layer);
138
139 return {metatile_index, layer, subtile};
140}
141
157[[nodiscard]] inline std::tuple<Layer, Subtile> from_internal_tile_index(std::size_t tile_index)
158{
159 if (tile_index >= tiles_per_metatile) {
160 panic("tile_index (" + std::to_string(tile_index) + ") >= tiles_per_metatile");
161 }
162
163 const std::size_t local_index = tile_index % tiles_per_metatile;
164 const auto layer = static_cast<Layer>(local_index / tiles_per_metatile_layer);
165 const auto subtile = static_cast<Subtile>(local_index % tiles_per_metatile_layer);
166
167 return {layer, subtile};
168}
169
170[[nodiscard]] inline std::string message_header(
171 const TextFormatter &format,
172 std::size_t index,
173 Layer layer,
174 Subtile subtile,
175 std::size_t subtile_row,
176 std::size_t subtile_col)
177{
178 return format.format(
179 "{} {}({})|{}|{}|{},{}",
180 FormatParam{"metatile"},
182 FormatParam{index},
183 FormatParam{to_string(layer)},
184 FormatParam{to_string(subtile)},
185 FormatParam{std::to_string(subtile_row)},
186 FormatParam{std::to_string(subtile_col)});
187}
188
189[[nodiscard]] inline std::string
190message_header(const TextFormatter &format, std::size_t index, Layer layer, Subtile subtile)
191{
192 return format.format(
193 "{} {}({})|{}|{}",
194 FormatParam{"metatile"},
196 FormatParam{index},
197 FormatParam{to_string(layer)},
198 FormatParam{to_string(subtile)});
199}
200
201[[nodiscard]] inline std::string message_header(const TextFormatter &format, std::size_t index, Subtile subtile)
202{
203 return format.format(
204 "{} {}({})|{}",
205 FormatParam{"metatile"},
207 FormatParam{index},
208 FormatParam{to_string(subtile)});
209}
210
211} // namespace metatile
212
223template <SupportsTransparency PixelType>
224class Metatile {
225 public:
226 Metatile() : id_{} {}
227
228 bool operator==(const Metatile &) const = default;
229
237 [[nodiscard]] bool is_transparent() const
238 requires requires(const PixelType &p) { p.is_transparent(); }
239 {
240 const bool bottom_transparent =
241 std::ranges::all_of(bottom(), [](const auto &tile) { return tile.is_transparent(); });
242 const bool middle_transparent =
243 std::ranges::all_of(middle(), [](const auto &tile) { return tile.is_transparent(); });
244 const bool top_transparent = std::ranges::all_of(top(), [](const auto &tile) { return tile.is_transparent(); });
245 return bottom_transparent && middle_transparent && top_transparent;
246 }
247
257 [[nodiscard]] bool is_transparent(const PixelType &extrinsic) const
258 requires requires(const PixelType &p) { p.is_transparent(p); }
259 {
260 const bool bottom_transparent =
261 std::ranges::all_of(bottom(), [=](const auto &tile) { return tile.is_transparent(extrinsic); });
262 const bool middle_transparent =
263 std::ranges::all_of(middle(), [=](const auto &tile) { return tile.is_transparent(extrinsic); });
264 const bool top_transparent =
265 std::ranges::all_of(top(), [=](const auto &tile) { return tile.is_transparent(extrinsic); });
266 return bottom_transparent && middle_transparent && top_transparent;
267 }
268
275 [[nodiscard]] std::array<PixelTile<PixelType>, metatile::tiles_per_metatile> decompose() const
276 {
277 std::array<PixelTile<PixelType>, metatile::tiles_per_metatile> tiles{};
278
279 auto out_it = tiles.begin();
280 out_it = std::ranges::copy(bottom(), out_it).out;
281 out_it = std::ranges::copy(middle(), out_it).out;
282 std::ranges::copy(top(), out_it);
283
284 return tiles;
285 }
286
294 [[nodiscard]] LayerMode infer_layer_mode() const
295 requires requires(const PixelType &p) { p.is_transparent(); }
296 {
297 return infer_layer_mode_impl([](const PixelType &pixel) { return pixel.is_transparent(); });
298 }
299
308 [[nodiscard]] LayerMode infer_layer_mode(const PixelType &extrinsic) const
309 requires requires(const PixelType &p) { p.is_transparent(p); }
310 {
311 return infer_layer_mode_impl([&extrinsic](const PixelType &pixel) { return pixel.is_transparent(extrinsic); });
312 }
313
326 [[nodiscard]] LayerType infer_layer_type() const
327 requires requires(const PixelType &p) { p.is_transparent(); }
328 {
329 return infer_layer_type_impl([](const PixelType &pixel) { return pixel.is_transparent(); });
330 }
331
345 [[nodiscard]] LayerType infer_layer_type(const PixelType &extrinsic) const
346 requires requires(const PixelType &p) { p.is_transparent(p); }
347 {
348 return infer_layer_type_impl([&extrinsic](const PixelType &pixel) { return pixel.is_transparent(extrinsic); });
349 }
350
358 [[nodiscard]] const PixelTile<PixelType> &bottom(std::size_t i) const
359 {
360 if (i > 3) {
361 panic(std::format("index {} out of bounds: must be [0,3]", i));
362 }
363 return bottom_[i];
364 }
365
372 [[nodiscard]] const std::array<PixelTile<PixelType>, metatile::tiles_per_metatile_layer> &bottom() const
373 {
374 return bottom_;
375 }
376
384 void set_bottom(std::size_t i, PixelTile<PixelType> tile)
385 {
386 if (i > 3) {
387 panic(std::format("index {} out of bounds: must be [0,3]", i));
388 }
389 bottom_[i] = std::move(tile);
390 }
391
399 [[nodiscard]] const PixelTile<PixelType> &middle(std::size_t i) const
400 {
401 if (i > 3) {
402 panic(std::format("index {} out of bounds: must be [0,3]", i));
403 }
404 return middle_[i];
405 }
406
413 [[nodiscard]] const std::array<PixelTile<PixelType>, metatile::tiles_per_metatile_layer> &middle() const
414 {
415 return middle_;
416 }
417
425 void set_middle(std::size_t i, PixelTile<PixelType> tile)
426 {
427 if (i > 3) {
428 panic(std::format("index {} out of bounds: must be [0,3]", i));
429 }
430 middle_[i] = std::move(tile);
431 }
432
440 [[nodiscard]] const PixelTile<PixelType> &top(std::size_t i) const
441 {
442 if (i > 3) {
443 panic(std::format("index {} out of bounds: must be [0,3]", i));
444 }
445 return top_[i];
446 }
447
454 [[nodiscard]] const std::array<PixelTile<PixelType>, metatile::tiles_per_metatile_layer> &top() const
455 {
456 return top_;
457 }
458
466 void set_top(std::size_t i, PixelTile<PixelType> tile)
467 {
468 if (i > 3) {
469 panic(std::format("index {} out of bounds: must be [0,3]", i));
470 }
471 top_[i] = std::move(tile);
472 }
473
474 private:
484 template <typename TransparencyPredicate>
485 [[nodiscard]] bool layer_has_content(
487 TransparencyPredicate is_transparent_pred) const
488 {
489 return std::ranges::any_of(layer, [&is_transparent_pred](const auto &tile) {
490 return std::ranges::any_of(
491 tile.pix(), [&is_transparent_pred](const auto &pixel) { return !is_transparent_pred(pixel); });
492 });
493 }
494
503 template <typename TransparencyPredicate>
504 [[nodiscard]] LayerMode infer_layer_mode_impl(TransparencyPredicate is_transparent_pred) const
505 {
506 const bool bottom_has_content = layer_has_content(bottom_, is_transparent_pred);
507 const bool middle_has_content = layer_has_content(middle_, is_transparent_pred);
508 const bool top_has_content = layer_has_content(top_, is_transparent_pred);
509
510 if (bottom_has_content && middle_has_content && top_has_content) {
511 return LayerMode::triple;
512 }
513 return LayerMode::dual;
514 }
515
524 template <typename TransparencyPredicate>
525 [[nodiscard]] LayerType infer_layer_type_impl(TransparencyPredicate is_transparent_pred) const
526 {
527 // If triple-layer mode, always return normal
528 const LayerMode mode = infer_layer_mode_impl(is_transparent_pred);
529 if (mode == LayerMode::triple) {
530 return LayerType::normal;
531 }
532
533 // For dual-layer mode, determine which layers have content
534 const bool bottom_has_content = layer_has_content(bottom_, is_transparent_pred);
535 const bool middle_has_content = layer_has_content(middle_, is_transparent_pred);
536 const bool top_has_content = layer_has_content(top_, is_transparent_pred);
537
538 // Apply the case logic
539 if (bottom_has_content && !middle_has_content && top_has_content) {
540 // Case 6: bottom/top content -> split
541 return LayerType::split;
542 }
543 if (bottom_has_content && (middle_has_content || !top_has_content)) {
544 // Case 1: bottom only -> covered
545 // Case 5: bottom/middle content -> covered
546 return LayerType::covered;
547 }
548 // All other cases (including no content) -> normal
549 // Case 0: completely transparent -> normal
550 // Case 2: middle only -> normal
551 // Case 3: top only -> normal
552 // Case 4: middle/top content -> normal
553 return LayerType::normal;
554 }
555
556 std::array<PixelTile<PixelType>, metatile::tiles_per_metatile_layer> bottom_;
557 std::array<PixelTile<PixelType>, metatile::tiles_per_metatile_layer> middle_;
558 std::array<PixelTile<PixelType>, metatile::tiles_per_metatile_layer> top_;
559 std::size_t id_;
560};
561
562namespace metatile {
563
564template <typename T>
565[[nodiscard]] std::vector<PixelTile<T>> decompose(const std::vector<Metatile<T>> &metatiles)
566{
567 std::vector<PixelTile<T>> tiles;
568 tiles.reserve(metatiles.size() * tiles_per_metatile);
569 for (const auto &mt : metatiles) {
570 tiles.append_range(mt.decompose());
571 }
572 return tiles;
573
574 // This is a less performant version that uses std::ranges
575 //
576 // auto tiles = metatiles
577 // | std::views::transform([](const auto& mt) { return mt.decompose(); })
578 // | std::views::join
579 // | std::ranges::to<std::vector>();
580 //
581 // This transforms each Metatile to a vector<Tile>, flattens (joins) all the vectors together, and collects into a
582 // final vector<Tile>.
583}
584
585} // namespace metatile
586
587} // namespace porytiles
A text parameter with associated styling for formatted output.
A template for two-dimensional images with arbitrarily typed pixel values.
Definition image.hpp:21
std::size_t width() const
Definition image.hpp:102
std::size_t height() const
Definition image.hpp:107
The core tileset entity - a 2x2 grid of PixelTile objects arranged into three layers.
Definition metatile.hpp:224
const std::array< PixelTile< PixelType >, metatile::tiles_per_metatile_layer > & top() const
Get a constant reference to the entire top layer array.
Definition metatile.hpp:454
bool operator==(const Metatile &) const =default
LayerType infer_layer_type() const
Infers the layer type based on which layers contain content (intrinsic transparency only).
Definition metatile.hpp:326
LayerMode infer_layer_mode() const
Infers the layer mode (dual or triple) based on metatile content (intrinsic transparency only).
Definition metatile.hpp:294
void set_bottom(std::size_t i, PixelTile< PixelType > tile)
Set a PixelTile in the bottom layer.
Definition metatile.hpp:384
void set_top(std::size_t i, PixelTile< PixelType > tile)
Set a Tile in the top layer.
Definition metatile.hpp:466
const PixelTile< PixelType > & top(std::size_t i) const
Get a constant reference to a PixelTile from the top layer.
Definition metatile.hpp:440
std::array< PixelTile< PixelType >, metatile::tiles_per_metatile > decompose() const
Decomposes this metatile into an array of PixelTiles in metatile order.
Definition metatile.hpp:275
const std::array< PixelTile< PixelType >, metatile::tiles_per_metatile_layer > & middle() const
Get a constant reference to the entire middle layer array.
Definition metatile.hpp:413
bool is_transparent(const PixelType &extrinsic) const
Checks if this entire metatile is transparent.
Definition metatile.hpp:257
LayerMode infer_layer_mode(const PixelType &extrinsic) const
Infers the layer mode (dual or triple) based on metatile content.
Definition metatile.hpp:308
const std::array< PixelTile< PixelType >, metatile::tiles_per_metatile_layer > & bottom() const
Get a constant reference to the entire bottom layer array.
Definition metatile.hpp:372
LayerType infer_layer_type(const PixelType &extrinsic) const
Infers the layer type based on which layers contain content.
Definition metatile.hpp:345
const PixelTile< PixelType > & bottom(std::size_t i) const
Get a constant reference to a PixelTile from the bottom layer.
Definition metatile.hpp:358
const PixelTile< PixelType > & middle(std::size_t i) const
Get a constant reference to a PixelTile from the middle layer.
Definition metatile.hpp:399
bool is_transparent() const
Checks if this entire metatile is transparent (intrinsic transparency only).
Definition metatile.hpp:237
void set_middle(std::size_t i, PixelTile< PixelType > tile)
Set a PixelTile in the middle layer.
Definition metatile.hpp:425
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
Abstract base class for applying text styling with context-aware formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
constexpr std::size_t entries_per_metatile_dual
Definition metatile.hpp:25
constexpr std::size_t entries_per_metatile_triple
Definition metatile.hpp:26
constexpr std::size_t tiles_per_metatile
Definition metatile.hpp:23
std::string to_string(Layer layer)
Definition metatile.hpp:47
std::ostream & operator<<(std::ostream &os, const Layer &layer)
Definition metatile.hpp:60
constexpr std::size_t side_length_pix
Definition metatile.hpp:24
constexpr std::size_t tiles_per_side
Definition metatile.hpp:21
Layer dropped_layer_for(LayerType layer_type)
Returns the layer that dual-layerization discards for a given inferred LayerType.
Definition metatile.hpp:76
std::tuple< std::size_t, Layer, Subtile > from_tile_index(std::size_t tile_index)
Decomposes a global tile index into its metatile index, layer, and subtile position.
Definition metatile.hpp:132
constexpr std::size_t metatiles_per_row
Definition metatile.hpp:27
std::tuple< Layer, Subtile > from_internal_tile_index(std::size_t tile_index)
Decomposes an internal tile index into its layer and subtile position within a metatile.
Definition metatile.hpp:157
constexpr std::size_t tiles_per_metatile_layer
Definition metatile.hpp:22
std::size_t metatile_count(const Image< PixelType > &layer)
Returns how many metatiles a single layer image holds.
Definition metatile.hpp:40
std::string message_header(const TextFormatter &format, std::size_t index, Layer layer, Subtile subtile, std::size_t subtile_row, std::size_t subtile_col)
Definition metatile.hpp:170
Subtile subtile_from_index(std::size_t i)
Definition metatile.hpp:91
constexpr std::size_t side_length_pix
LayerMode
Specifies whether a metatile uses dual-layer or triple-layer mode.
Definition layer.hpp:20
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.cpp:43
std::string int_to_hex_str(T t)
Converts an integer value to a hexadecimal string with "0x" prefix.
LayerType
Specifies which layers of a metatile are used for rendering.
Definition layer.hpp:86
Utility functions for string manipulation and formatting.