yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tilemap.cc
Go to the documentation of this file.
2
3#include <vector>
4
10
11namespace yaze {
12namespace gfx {
13
14Tilemap CreateTilemap(IRenderer* renderer, std::vector<uint8_t>& data,
15 int width, int height, int tile_size, int num_tiles,
16 SnesPalette& palette) {
17 Tilemap tilemap;
18 tilemap.tile_size.x = tile_size;
19 tilemap.tile_size.y = tile_size;
20 tilemap.map_size.x = num_tiles;
21 tilemap.map_size.y = num_tiles;
22 tilemap.atlas = Bitmap(width, height, 8, data);
23 tilemap.atlas.SetPalette(palette);
24
25 // Queue texture creation directly via Arena
26 if (tilemap.atlas.is_active() && tilemap.atlas.surface()) {
28 &tilemap.atlas);
29 }
30
31 return tilemap;
32}
33
34void UpdateTilemap(IRenderer* renderer, Tilemap& tilemap,
35 const std::vector<uint8_t>& data) {
36 tilemap.atlas.set_data(data);
37
38 // Queue texture update directly via Arena
39 if (tilemap.atlas.texture() && tilemap.atlas.is_active() &&
40 tilemap.atlas.surface()) {
42 &tilemap.atlas);
43 } else if (!tilemap.atlas.texture() && tilemap.atlas.is_active() &&
44 tilemap.atlas.surface()) {
45 // Create if doesn't exist yet
47 &tilemap.atlas);
48 }
49}
50
51void RenderTile(IRenderer* renderer, Tilemap& tilemap, int tile_id) {
52 // Validate tilemap state before proceeding
53 if (!tilemap.atlas.is_active() || tilemap.atlas.vector().empty()) {
54 return;
55 }
56
57 if (tile_id < 0) {
58 return;
59 }
60
61 // Try cache first, then fall back to fresh render
62 Bitmap* cached_tile = tilemap.tile_cache.GetTile(tile_id);
63 if (!cached_tile) {
64 auto tile_data = GetTilemapData(tilemap, tile_id);
65 if (tile_data.empty()) {
66 return;
67 }
68 // Cache uses copy semantics - safe to use
69 gfx::Bitmap new_tile = gfx::Bitmap(
70 tilemap.tile_size.x, tilemap.tile_size.y, 8,
71 tile_data, tilemap.atlas.palette());
72 tilemap.tile_cache.CacheTile(tile_id, new_tile);
73 }
74}
75
76void RenderTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id) {
77 // Validate tilemap state before proceeding
78 if (!tilemap.atlas.is_active() || tilemap.atlas.vector().empty()) {
79 return;
80 }
81
82 if (tile_id < 0) {
83 return;
84 }
85
86 int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
87 if (tiles_per_row <= 0) {
88 return;
89 }
90
91 int tile_x = (tile_id % tiles_per_row) * tilemap.tile_size.x;
92 int tile_y = (tile_id / tiles_per_row) * tilemap.tile_size.y;
93
94 // Validate tile position
95 if (tile_x < 0 || tile_x >= tilemap.atlas.width() || tile_y < 0 ||
96 tile_y >= tilemap.atlas.height()) {
97 return;
98 }
99
100 // Try cache first, then fall back to fresh render
101 Bitmap* cached_tile = tilemap.tile_cache.GetTile(tile_id);
102 if (!cached_tile) {
103 auto tile_data = GetTilemapData(tilemap, tile_id);
104 if (tile_data.empty()) {
105 return;
106 }
107 // Cache uses copy semantics - safe to use
108 gfx::Bitmap new_tile = gfx::Bitmap(
109 tilemap.tile_size.x, tilemap.tile_size.y, 8,
110 tile_data, tilemap.atlas.palette());
111 tilemap.tile_cache.CacheTile(tile_id, new_tile);
112 }
113}
114
115void UpdateTile16(IRenderer* renderer, Tilemap& tilemap, int tile_id) {
116 // Check if tile is cached
117 Bitmap* cached_tile = tilemap.tile_cache.GetTile(tile_id);
118 if (cached_tile) {
119 // Update cached tile data
120 int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
121 int tile_x = (tile_id % tiles_per_row) * tilemap.tile_size.x;
122 int tile_y = (tile_id / tiles_per_row) * tilemap.tile_size.y;
123 std::vector<uint8_t> tile_data(tilemap.tile_size.x * tilemap.tile_size.y,
124 0x00);
125 int tile_data_offset = 0;
126 tilemap.atlas.Get16x16Tile(tile_x, tile_y, tile_data, tile_data_offset);
127 cached_tile->set_data(tile_data);
128
129 // Queue texture update directly via Arena
130 if (cached_tile->texture() && cached_tile->is_active()) {
132 cached_tile);
133 }
134 } else {
135 // Tile not cached, render it fresh
136 RenderTile16(renderer, tilemap, tile_id);
137 }
138}
139
141 const std::vector<uint8_t>& data, int tile_id, int sheet_offset) {
142 const int tile_width = 8;
143 const int tile_height = 8;
144 const int buffer_width = 128;
145 const int sheet_height = 32;
146
147 const int tiles_per_row = buffer_width / tile_width;
148 const int rows_per_sheet = sheet_height / tile_height;
149 const int tiles_per_sheet = tiles_per_row * rows_per_sheet;
150
151 int sheet = (tile_id / tiles_per_sheet) % 4 + sheet_offset;
152 int position_in_sheet = tile_id % tiles_per_sheet;
153 int row_in_sheet = position_in_sheet / tiles_per_row;
154 int column_in_sheet = position_in_sheet % tiles_per_row;
155
156 // Bounds check for sheet range
157 if (sheet < sheet_offset || sheet > sheet_offset + 3) {
158 return std::vector<uint8_t>(tile_width * tile_height, 0);
159 }
160
161 const int data_size = static_cast<int>(data.size());
162 std::vector<uint8_t> tile_data(tile_width * tile_height, 0);
163 for (int y = 0; y < tile_height; ++y) {
164 for (int x = 0; x < tile_width; ++x) {
165 int src_x = column_in_sheet * tile_width + x;
166 int src_y = (sheet * sheet_height) + (row_in_sheet * tile_height) + y;
167
168 int src_index = (src_y * buffer_width) + src_x;
169 int dest_index = y * tile_width + x;
170
171 // Bounds check before access
172 if (src_index >= 0 && src_index < data_size) {
173 tile_data[dest_index] = data[src_index];
174 }
175 }
176 }
177 return tile_data;
178}
179
180namespace {
181
182void MirrorTileDataVertically(std::vector<uint8_t>& tile_data) {
183 for (int y = 0; y < 4; ++y) {
184 for (int x = 0; x < 8; ++x) {
185 std::swap(tile_data[y * 8 + x], tile_data[(7 - y) * 8 + x]);
186 }
187 }
188}
189
190void MirrorTileDataHorizontally(std::vector<uint8_t>& tile_data) {
191 for (int y = 0; y < 8; ++y) {
192 for (int x = 0; x < 4; ++x) {
193 std::swap(tile_data[y * 8 + x], tile_data[y * 8 + (7 - x)]);
194 }
195 }
196}
197
198void ComposeAndPlaceTilePart(Tilemap& tilemap, const std::vector<uint8_t>& data,
199 const TileInfo& tile_info, int base_x, int base_y,
200 int sheet_offset) {
201 std::vector<uint8_t> tile_data =
202 FetchTileDataFromGraphicsBuffer(data, tile_info.id_, sheet_offset);
203
204 if (tile_info.vertical_mirror_) {
205 MirrorTileDataVertically(tile_data);
206 }
207 if (tile_info.horizontal_mirror_) {
209 }
210
211 for (int y = 0; y < 8; ++y) {
212 for (int x = 0; x < 8; ++x) {
213 int src_index = y * 8 + x;
214 int dest_x = base_x + x;
215 int dest_y = base_y + y;
216 int dest_index = (dest_y * tilemap.atlas.width()) + dest_x;
217 tilemap.atlas.WriteToPixel(dest_index, tile_data[src_index]);
218 }
219 };
220}
221} // namespace
222
223void ModifyTile16(Tilemap& tilemap, const std::vector<uint8_t>& data,
224 const TileInfo& top_left, const TileInfo& top_right,
225 const TileInfo& bottom_left, const TileInfo& bottom_right,
226 int sheet_offset, int tile_id) {
227 // Calculate the base position for this Tile16 in the full-size bitmap
228 int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
229 int tile16_row = tile_id / tiles_per_row;
230 int tile16_column = tile_id % tiles_per_row;
231 int base_x = tile16_column * tilemap.tile_size.x;
232 int base_y = tile16_row * tilemap.tile_size.y;
233
234 // Compose and place each part of the Tile16
235 ComposeAndPlaceTilePart(tilemap, data, top_left, base_x, base_y,
236 sheet_offset);
237 ComposeAndPlaceTilePart(tilemap, data, top_right, base_x + 8, base_y,
238 sheet_offset);
239 ComposeAndPlaceTilePart(tilemap, data, bottom_left, base_x, base_y + 8,
240 sheet_offset);
241 ComposeAndPlaceTilePart(tilemap, data, bottom_right, base_x + 8, base_y + 8,
242 sheet_offset);
243
244 tilemap.tile_info[tile_id] = {top_left, top_right, bottom_left, bottom_right};
245}
246
247void ComposeTile16(Tilemap& tilemap, const std::vector<uint8_t>& data,
248 const TileInfo& top_left, const TileInfo& top_right,
249 const TileInfo& bottom_left, const TileInfo& bottom_right,
250 int sheet_offset) {
251 int num_tiles = tilemap.tile_info.size();
252 int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
253 int tile16_row = num_tiles / tiles_per_row;
254 int tile16_column = num_tiles % tiles_per_row;
255 int base_x = tile16_column * tilemap.tile_size.x;
256 int base_y = tile16_row * tilemap.tile_size.y;
257
258 ComposeAndPlaceTilePart(tilemap, data, top_left, base_x, base_y,
259 sheet_offset);
260 ComposeAndPlaceTilePart(tilemap, data, top_right, base_x + 8, base_y,
261 sheet_offset);
262 ComposeAndPlaceTilePart(tilemap, data, bottom_left, base_x, base_y + 8,
263 sheet_offset);
264 ComposeAndPlaceTilePart(tilemap, data, bottom_right, base_x + 8, base_y + 8,
265 sheet_offset);
266
267 tilemap.tile_info.push_back({top_left, top_right, bottom_left, bottom_right});
268}
269
270std::vector<uint8_t> GetTilemapData(Tilemap& tilemap, int tile_id) {
271 // Comprehensive validation to prevent crashes
272 if (tile_id < 0) {
273 SDL_Log("GetTilemapData: Invalid tile_id %d (negative)", tile_id);
274 return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
275 }
276
277 if (!tilemap.atlas.is_active()) {
278 SDL_Log("GetTilemapData: Atlas is not active for tile_id %d", tile_id);
279 return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
280 }
281
282 if (tilemap.atlas.vector().empty()) {
283 SDL_Log("GetTilemapData: Atlas vector is empty for tile_id %d", tile_id);
284 return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
285 }
286
287 if (tilemap.tile_size.x <= 0 || tilemap.tile_size.y <= 0) {
288 SDL_Log("GetTilemapData: Invalid tile size (%d, %d) for tile_id %d",
289 tilemap.tile_size.x, tilemap.tile_size.y, tile_id);
290 return std::vector<uint8_t>(256, 0); // Return empty 16x16 tile data
291 }
292
293 int tile_size = tilemap.tile_size.x;
294 int width = tilemap.atlas.width();
295 int height = tilemap.atlas.height();
296
297 // Validate atlas dimensions
298 if (width <= 0 || height <= 0) {
299 SDL_Log("GetTilemapData: Invalid atlas dimensions (%d, %d) for tile_id %d",
300 width, height, tile_id);
301 return std::vector<uint8_t>(tile_size * tile_size, 0);
302 }
303
304 // Calculate maximum possible tile_id based on atlas size
305 int tiles_per_row = width / tile_size;
306 int tiles_per_column = height / tile_size;
307 int max_tile_id = tiles_per_row * tiles_per_column - 1;
308
309 if (tile_id > max_tile_id) {
310 SDL_Log(
311 "GetTilemapData: tile_id %d exceeds maximum %d (atlas: %dx%d, "
312 "tile_size: %d)",
313 tile_id, max_tile_id, width, height, tile_size);
314 return std::vector<uint8_t>(tile_size * tile_size, 0);
315 }
316
317 std::vector<uint8_t> data(tile_size * tile_size);
318
319 for (int ty = 0; ty < tile_size; ty++) {
320 for (int tx = 0; tx < tile_size; tx++) {
321 // Calculate atlas position more safely
322 int tile_row = tile_id / tiles_per_row;
323 int tile_col = tile_id % tiles_per_row;
324 int atlas_x = tile_col * tile_size + tx;
325 int atlas_y = tile_row * tile_size + ty;
326 int atlas_index = atlas_y * width + atlas_x;
327
328 // Comprehensive bounds checking
329 if (atlas_x >= 0 && atlas_x < width && atlas_y >= 0 && atlas_y < height &&
330 atlas_index >= 0 &&
331 atlas_index < static_cast<int>(tilemap.atlas.vector().size())) {
332 uint8_t value = tilemap.atlas.vector()[atlas_index];
333 data[ty * tile_size + tx] = value;
334 } else {
335 SDL_Log(
336 "GetTilemapData: Atlas position (%d, %d) or index %d out of bounds "
337 "(atlas: %dx%d, size: %zu)",
338 atlas_x, atlas_y, atlas_index, width, height,
339 tilemap.atlas.vector().size());
340 data[ty * tile_size + tx] = 0; // Default to 0 if out of bounds
341 }
342 }
343 }
344
345 return data;
346}
347
348void RenderTilesBatch(IRenderer* renderer, Tilemap& tilemap,
349 const std::vector<int>& tile_ids,
350 const std::vector<std::pair<float, float>>& positions,
351 const std::vector<std::pair<float, float>>& scales) {
352 if (tile_ids.empty() || positions.empty() ||
353 tile_ids.size() != positions.size()) {
354 return;
355 }
356
357 ScopedTimer timer("tilemap_batch_render");
358
359 // Initialize atlas renderer if not already done
360 auto& atlas_renderer = AtlasRenderer::Get();
361 if (!renderer) {
362 // For now, we'll use the existing rendering approach
363 // In a full implementation, we'd get the renderer from the core system
364 return;
365 }
366
367 // Prepare render commands
368 std::vector<RenderCommand> render_commands;
369 render_commands.reserve(tile_ids.size());
370
371 for (size_t i = 0; i < tile_ids.size(); ++i) {
372 int tile_id = tile_ids[i];
373 float x = positions[i].first;
374 float y = positions[i].second;
375
376 // Get scale factors (default to 1.0 if not provided)
377 float scale_x = 1.0F;
378 float scale_y = 1.0F;
379 if (i < scales.size()) {
380 scale_x = scales[i].first;
381 scale_y = scales[i].second;
382 }
383
384 // Try to get tile from cache first
385 Bitmap* cached_tile = tilemap.tile_cache.GetTile(tile_id);
386 if (!cached_tile) {
387 // Create and cache the tile if not found (copy semantics for safety)
388 gfx::Bitmap new_tile = gfx::Bitmap(
389 tilemap.tile_size.x, tilemap.tile_size.y, 8,
390 gfx::GetTilemapData(tilemap, tile_id), tilemap.atlas.palette());
391 tilemap.tile_cache.CacheTile(tile_id, new_tile); // Copies bitmap
392 cached_tile = tilemap.tile_cache.GetTile(tile_id);
393 if (cached_tile) {
394 cached_tile->CreateTexture();
395 }
396 }
397
398 if (cached_tile && cached_tile->is_active()) {
399 // Queue texture creation if needed
400 if (!cached_tile->texture() && cached_tile->surface()) {
402 cached_tile);
403 }
404
405 // Add to atlas renderer
406 int atlas_id = atlas_renderer.AddBitmap(*cached_tile);
407 if (atlas_id >= 0) {
408 render_commands.emplace_back(atlas_id, x, y, scale_x, scale_y);
409 }
410 }
411 }
412
413 // Render all commands in batch
414 if (!render_commands.empty()) {
415 atlas_renderer.RenderBatch(render_commands);
416 }
417}
418
419} // namespace gfx
420} // namespace yaze
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:34
static Arena & Get()
Definition arena.cc:19
static AtlasRenderer & Get()
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const SnesPalette & palette() const
Definition bitmap.h:368
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
Definition bitmap.cc:579
TextureHandle texture() const
Definition bitmap.h:380
const std::vector< uint8_t > & vector() const
Definition bitmap.h:381
void CreateTexture()
Creates the underlying SDL_Texture to be displayed.
Definition bitmap.cc:291
bool is_active() const
Definition bitmap.h:384
int height() const
Definition bitmap.h:374
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:851
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:382
int width() const
Definition bitmap.h:373
SDL_Surface * surface() const
Definition bitmap.h:379
void Get16x16Tile(int tile_x, int tile_y, std::vector< uint8_t > &tile_data, int &tile_data_offset)
Extract a 16x16 tile from the bitmap (SNES metatile size)
Definition bitmap.cc:690
Defines an abstract interface for all rendering operations.
Definition irenderer.h:40
RAII timer for automatic timing management.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
SNES 16-bit tile metadata container.
Definition snes_tile.h:50
void ComposeAndPlaceTilePart(Tilemap &tilemap, const std::vector< uint8_t > &data, const TileInfo &tile_info, int base_x, int base_y, int sheet_offset)
Definition tilemap.cc:198
void MirrorTileDataVertically(std::vector< uint8_t > &tile_data)
Definition tilemap.cc:182
void MirrorTileDataHorizontally(std::vector< uint8_t > &tile_data)
Definition tilemap.cc:190
void RenderTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:76
void ModifyTile16(Tilemap &tilemap, const std::vector< uint8_t > &data, const TileInfo &top_left, const TileInfo &top_right, const TileInfo &bottom_left, const TileInfo &bottom_right, int sheet_offset, int tile_id)
Definition tilemap.cc:223
void UpdateTilemap(IRenderer *renderer, Tilemap &tilemap, const std::vector< uint8_t > &data)
Definition tilemap.cc:34
void UpdateTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:115
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:270
Tilemap CreateTilemap(IRenderer *renderer, std::vector< uint8_t > &data, int width, int height, int tile_size, int num_tiles, SnesPalette &palette)
Definition tilemap.cc:14
void RenderTilesBatch(IRenderer *renderer, Tilemap &tilemap, const std::vector< int > &tile_ids, const std::vector< std::pair< float, float > > &positions, const std::vector< std::pair< float, float > > &scales)
Render multiple tiles using atlas rendering for improved performance.
Definition tilemap.cc:348
std::vector< uint8_t > FetchTileDataFromGraphicsBuffer(const std::vector< uint8_t > &data, int tile_id, int sheet_offset)
Definition tilemap.cc:140
void RenderTile(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:51
void ComposeTile16(Tilemap &tilemap, const std::vector< uint8_t > &data, const TileInfo &top_left, const TileInfo &top_right, const TileInfo &bottom_left, const TileInfo &bottom_right, int sheet_offset)
Definition tilemap.cc:247
int y
Y coordinate or height.
Definition tilemap.h:21
int x
X coordinate or width.
Definition tilemap.h:20
void CacheTile(int tile_id, const Bitmap &bitmap)
Cache a tile bitmap by copying it.
Definition tilemap.h:67
Bitmap * GetTile(int tile_id)
Get a cached tile by ID.
Definition tilemap.h:50
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:118
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:123
TileCache tile_cache
Smart tile cache with LRU eviction.
Definition tilemap.h:120
Pair map_size
Size of tilemap in tiles.
Definition tilemap.h:124
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
Definition tilemap.h:122