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