#include "game/map/tilemap.hpp" #include #include #include #include #include #include "core/rendering/screen.hpp" #include "core/rendering/surface.hpp" #include "core/resources/resource_cache.hpp" #include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" #include "utils/log.hpp" #ifdef _DEBUG #include "game/map/tilemap_writer.hpp" #endif namespace { // Copia un tile (col, fila) del tileset al buffer ARGB a la posició de // destí (dest_x, dest_y). Els píxels amb l'índex de transparència del // tileset es deixen com estan al buffer (alpha=0 per defecte). void blitTile(const Surface& tileset, int tile_size, int src_col, int src_row, std::vector& dest, int dest_w, int dest_x, int dest_y) { const int SRC_X0 = src_col * tile_size; const int SRC_Y0 = src_row * tile_size; const auto TRANSPARENT = tileset.getTransparentColor(); for (int y = 0; y < tile_size; ++y) { for (int x = 0; x < tile_size; ++x) { const auto IDX = tileset.getPixel(SRC_X0 + x, SRC_Y0 + y); if (IDX == TRANSPARENT) { continue; } const auto ARGB = tileset.getPaletteColor(IDX); const auto DST_OFFSET = (static_cast(dest_y + y) * static_cast(dest_w)) + static_cast(dest_x + x); dest[DST_OFFSET] = ARGB; } } } } // namespace Tilemap::Tilemap(const std::string& yaml_path) { auto bytes = Resource::Helper::loadFile(yaml_path); if (bytes.empty()) { throw std::runtime_error("Tilemap: no s'ha pogut obrir " + yaml_path); } const auto* begin = reinterpret_cast(bytes.data()); const auto* end = begin + bytes.size(); auto yaml = fkyaml::node::deserialize(begin, end); tileset_path_ = yaml["tileset"].get_value(); tile_size_ = yaml["tile_size"].get_value(); if (yaml.contains("origin")) { origin_x_ = yaml["origin"][0].get_value(); origin_y_ = yaml["origin"][1].get_value(); } // Carreguem el tileset al cache (idempotent) i el pillem per accedir // als seus píxels i paleta. La paleta del tileset ve del GIF mateix. // Ens cal abans de llegir `data` per saber quants tiles per fila té. Resource::Cache::get().loadSurface(tileset_path_, tileset_path_); const auto TILESET = Resource::Cache::get().getSurface(tileset_path_); tileset_cols_ = TILESET->getWidth() / tile_size_; // `data` és una seqüència de seqüències d'enters: cada fila és una // llista d'índexs lineals al tileset (esq→dre, dalt→baix). Un valor // negatiu (per convenció -1) deixa la cel·la buida (alpha=0). const auto& data = yaml["data"]; if (!data.is_sequence() || data.empty()) { throw std::runtime_error("Tilemap: `data` buit o amb format incorrecte a " + yaml_path); } rows_ = static_cast(data.size()); cols_ = static_cast(data[0].size()); width_px_ = cols_ * tile_size_; height_px_ = rows_ * tile_size_; pixels_.assign( static_cast(width_px_) * static_cast(height_px_), 0U); #ifdef _DEBUG data_.assign(static_cast(rows_), std::vector(static_cast(cols_), -1)); #endif for (int row = 0; row < rows_; ++row) { const auto& line = data[row]; const int LINE_COLS = static_cast(line.size()); for (int col = 0; col < cols_ && col < LINE_COLS; ++col) { const int INDEX = line[col].get_value(); #ifdef _DEBUG data_[static_cast(row)][static_cast(col)] = INDEX; #endif if (INDEX < 0) { continue; } const int SRC_COL = INDEX % tileset_cols_; const int SRC_ROW = INDEX / tileset_cols_; blitTile(*TILESET, tile_size_, SRC_COL, SRC_ROW, pixels_, width_px_, col * tile_size_, row * tile_size_); } } Log::info("Tilemap", "{}x{} tiles ({}x{} px) des de {}", cols_, rows_, width_px_, height_px_, yaml_path); } #ifdef _DEBUG void Tilemap::setTileAt(int col, int row, int index) { if (col < 0 || col >= cols_ || row < 0 || row >= rows_) { return; } if (index < 0) { return; } // El mapa no admet cel·les buides (política de projecte). // No validem l'índex contra el total de tiles del tileset: la // invariant la garanteix el TilePicker, que només ofereix índexs en // rang. data_[static_cast(row)][static_cast(col)] = index; // Netegem el rectangle del tile al buffer de píxels i re-blitegem. const int DX = col * tile_size_; const int DY = row * tile_size_; for (int y = 0; y < tile_size_; ++y) { auto* dst = &pixels_[(static_cast(DY + y) * static_cast(width_px_)) + static_cast(DX)]; for (int x = 0; x < tile_size_; ++x) { dst[x] = 0U; } } const auto TILESET = Resource::Cache::get().getSurface(tileset_path_); const int SRC_COL = index % tileset_cols_; const int SRC_ROW = index / tileset_cols_; blitTile(*TILESET, tile_size_, SRC_COL, SRC_ROW, pixels_, width_px_, DX, DY); } auto Tilemap::save(const std::string& yaml_path) const -> bool { return TilemapWriter::saveDataBlock(yaml_path, data_); } void Tilemap::renderTilePreview(int canvas_x, int canvas_y, int tile_index) const { if (tile_index < 0) { return; } const auto TILESET = Resource::Cache::get().getSurface(tileset_path_); if (TILESET == nullptr) { return; } auto& screen = Screen::get(); auto* buf = screen.getCanvasArgb(); const int CW = screen.getCanvasWidth(); const int CH = screen.getCanvasHeight(); const auto TRANSPARENT = TILESET->getTransparentColor(); const int SX0 = (tile_index % tileset_cols_) * tile_size_; const int SY0 = (tile_index / tileset_cols_) * tile_size_; for (int y = 0; y < tile_size_; ++y) { const int DY = canvas_y + y; if (DY < 0 || DY >= CH) { continue; } for (int x = 0; x < tile_size_; ++x) { const int DX = canvas_x + x; if (DX < 0 || DX >= CW) { continue; } const auto IDX = TILESET->getPixel(SX0 + x, SY0 + y); if (IDX == TRANSPARENT) { continue; } buf[(static_cast(DY) * static_cast(CW)) + static_cast(DX)] = TILESET->getPaletteColor(IDX); } } } #endif void Tilemap::render() const { auto& screen = Screen::get(); auto* canvas = screen.getCanvasArgb(); const int CANVAS_W = screen.getCanvasWidth(); const int CANVAS_H = screen.getCanvasHeight(); // Clipping de files: només pintem les que cauen dins del canvas. const int Y_START = std::max(0, -origin_y_); const int Y_END = std::min(height_px_, CANVAS_H - origin_y_); const int X_START = std::max(0, -origin_x_); const int X_END = std::min(width_px_, CANVAS_W - origin_x_); // Màscara del canal alfa (els píxels transparents del tilemap queden // amb alpha=0 i cal mantenir el canvas sota visible). constexpr std::uint32_t ALPHA_MASK = 0xFF000000U; for (int y = Y_START; y < Y_END; ++y) { const auto* src_row = &pixels_[static_cast(y) * static_cast(width_px_)]; auto* dst_row = &canvas[(static_cast(origin_y_ + y) * static_cast(CANVAS_W)) + static_cast(origin_x_)]; for (int x = X_START; x < X_END; ++x) { const auto PIXEL = src_row[x]; if ((PIXEL & ALPHA_MASK) != 0U) { dst_row[x] = PIXEL; } } } }