139 lines
5.6 KiB
C++
139 lines
5.6 KiB
C++
#include "game/map/tilemap.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
#include "core/rendering/screen.hpp"
|
|
#include "core/rendering/surface.hpp"
|
|
#include "core/resources/resource_cache.hpp"
|
|
#include "external/fkyaml_node.hpp"
|
|
|
|
namespace {
|
|
|
|
// Cada símbol del grid es mapeja a (col, fila) dins del tileset.
|
|
using TileCoord = std::pair<int, int>;
|
|
using Legend = std::unordered_map<char, TileCoord>;
|
|
|
|
auto splitLines(const std::string& text) -> std::vector<std::string> {
|
|
std::vector<std::string> lines;
|
|
std::istringstream iss(text);
|
|
std::string line;
|
|
while (std::getline(iss, line)) {
|
|
if (!line.empty()) { lines.push_back(line); }
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
// La llegenda usa índexs lineals del tileset (esq→dre, dalt→baix).
|
|
// Els convertim a (col, fila) amb el nombre de tiles per fila del tileset.
|
|
auto parseLegend(const fkyaml::node& node, int tileset_cols) -> Legend {
|
|
Legend legend;
|
|
for (auto it = node.begin(); it != node.end(); ++it) {
|
|
const auto KEY_STR = it.key().get_value<std::string>();
|
|
if (KEY_STR.empty()) { continue; }
|
|
const auto INDEX = it.value().get_value<int>();
|
|
const int COL = INDEX % tileset_cols;
|
|
const int ROW = INDEX / tileset_cols;
|
|
legend[KEY_STR.front()] = {COL, ROW};
|
|
}
|
|
return legend;
|
|
}
|
|
|
|
// 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<std::uint32_t>& 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<std::size_t>(dest_y + y) * static_cast<std::size_t>(dest_w)) + static_cast<std::size_t>(dest_x + x);
|
|
dest[DST_OFFSET] = ARGB;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Tilemap::Tilemap(const std::string& yaml_path) {
|
|
std::ifstream file(yaml_path);
|
|
if (!file.is_open()) {
|
|
throw std::runtime_error("Tilemap: no s'ha pogut obrir " + yaml_path);
|
|
}
|
|
|
|
auto yaml = fkyaml::node::deserialize(file);
|
|
|
|
const auto TILESET_PATH = yaml["tileset"].get_value<std::string>();
|
|
const auto TILE_SIZE = yaml["tile_size"].get_value<int>();
|
|
if (yaml.contains("origin")) {
|
|
origin_x_ = yaml["origin"][0].get_value<int>();
|
|
origin_y_ = yaml["origin"][1].get_value<int>();
|
|
}
|
|
|
|
// 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 la llegenda per saber quants tiles per fila té.
|
|
const auto& cache_name = TILESET_PATH;
|
|
Resource::Cache::get().loadSurface(cache_name, TILESET_PATH);
|
|
const auto TILESET = Resource::Cache::get().getSurface(cache_name);
|
|
const int TILESET_COLS = TILESET->getWidth() / TILE_SIZE;
|
|
|
|
const auto LEGEND = parseLegend(yaml["legend"], TILESET_COLS);
|
|
const auto LINES = splitLines(yaml["data"].get_value<std::string>());
|
|
|
|
const int ROWS = static_cast<int>(LINES.size());
|
|
const int COLS = ROWS > 0 ? static_cast<int>(LINES.front().size()) : 0;
|
|
width_px_ = COLS * TILE_SIZE;
|
|
height_px_ = ROWS * TILE_SIZE;
|
|
pixels_.assign(
|
|
static_cast<std::size_t>(width_px_) * static_cast<std::size_t>(height_px_),
|
|
0U);
|
|
|
|
for (int row = 0; row < ROWS; ++row) {
|
|
const auto& line = LINES[row];
|
|
for (int col = 0; col < COLS && col < static_cast<int>(line.size()); ++col) {
|
|
const char SYMBOL = line[col];
|
|
const auto IT = LEGEND.find(SYMBOL);
|
|
if (IT == LEGEND.end()) { continue; }
|
|
blitTile(*TILESET, TILE_SIZE, IT->second.first, IT->second.second, pixels_, width_px_, col * TILE_SIZE, row * TILE_SIZE);
|
|
}
|
|
}
|
|
|
|
std::cout << "Tilemap: " << COLS << "x" << ROWS << " tiles ("
|
|
<< width_px_ << "x" << height_px_ << " px) des de " << yaml_path << '\n';
|
|
}
|
|
|
|
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_);
|
|
|
|
for (int y = Y_START; y < Y_END; ++y) {
|
|
const auto* src_row = &pixels_[static_cast<std::size_t>(y) * static_cast<std::size_t>(width_px_)];
|
|
auto* dst_row =
|
|
&canvas[(static_cast<std::size_t>(origin_y_ + y) * static_cast<std::size_t>(CANVAS_W)) + static_cast<std::size_t>(origin_x_)];
|
|
for (int x = X_START; x < X_END; ++x) {
|
|
const auto PIXEL = src_row[x];
|
|
if ((PIXEL & 0xFF000000U) != 0U) { dst_row[x] = PIXEL; }
|
|
}
|
|
}
|
|
}
|