yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile_painting_manager.cc
Go to the documentation of this file.
1// Related header
3
4#include <algorithm>
5#include <cmath>
6#include <vector>
7
14#include "imgui/imgui.h"
15#include "util/log.h"
17
18namespace yaze::editor {
19
20namespace {
21
22int AllocatedRowsForWorld(int world) {
23 const int clamped_world = std::clamp(world, 0, 2);
24 const int world_start = clamped_world * 0x40;
25 const int maps_available =
26 std::clamp(zelda3::kNumOverworldMaps - world_start, 0, 0x40);
27 return (maps_available + 7) / 8;
28}
29
30bool IsValidMapGridPosition(int world, int map_x, int map_y) {
31 return map_x >= 0 && map_x < 8 && map_y >= 0 &&
32 map_y < AllocatedRowsForWorld(world);
33}
34
35int MapIndexForGridPosition(int world, int map_x, int map_y) {
36 return (std::clamp(world, 0, 2) * 0x40) + map_x + (map_y * 8);
37}
38
39} // namespace
40
42 const TilePaintingCallbacks& callbacks)
43 : deps_(deps), callbacks_(callbacks) {}
44
45// ---------------------------------------------------------------------------
46// DrawOverworldEdits - single-tile painting on left-click / drag
47// ---------------------------------------------------------------------------
49 // Determine which overworld map the user is currently editing.
50 // drawn_tile_position() returns scaled coordinates, need to unscale
51 auto scaled_position = deps_.ow_map_canvas->drawn_tile_position();
52 float scale = deps_.ow_map_canvas->global_scale();
53 if (scale <= 0.0f)
54 scale = 1.0f;
55
56 // Convert scaled position to world coordinates
57 ImVec2 mouse_position =
58 ImVec2(scaled_position.x / scale, scaled_position.y / scale);
59 if (mouse_position.x < 0.0f || mouse_position.y < 0.0f) {
60 return;
61 }
62
63 int map_x = static_cast<int>(mouse_position.x) / kOverworldMapSize;
64 int map_y = static_cast<int>(mouse_position.y) / kOverworldMapSize;
65 if (!IsValidMapGridPosition(*deps_.current_world, map_x, map_y)) {
66 return;
67 }
68
69 const int target_map =
70 MapIndexForGridPosition(*deps_.current_world, map_x, map_y);
71 // Bounds checking to prevent crashes
72 if (target_map < 0 ||
73 target_map >= static_cast<int>(deps_.maps_bmp->size())) {
74 return; // Invalid map index, skip drawing
75 }
76 *deps_.current_map = target_map;
77
78 // Validate tile16_blockset_ before calling GetTilemapData
80 deps_.tile16_blockset->atlas.vector().empty()) {
81 LOG_ERROR("TilePaintingManager",
82 "Error: tile16_blockset is not properly initialized (active: %s, "
83 "size: %zu)",
84 deps_.tile16_blockset->atlas.is_active() ? "true" : "false",
86 return; // Skip drawing if blockset is invalid
87 }
88
89 // Render the updated map bitmap.
90 auto tile_data =
92 RenderUpdatedMapBitmap(mouse_position, tile_data);
93
94 // Calculate the correct superX and superY values
95 const int world_offset = *deps_.current_world * 0x40;
96 const int local_map = *deps_.current_map - world_offset;
97 const int superY = local_map / 8;
98 const int superX = local_map % 8;
99 int mouse_x_i = static_cast<int>(mouse_position.x);
100 int mouse_y_i = static_cast<int>(mouse_position.y);
101 // Calculate the correct tile16_x and tile16_y positions
102 int tile16_x = (mouse_x_i % kOverworldMapSize) / (kOverworldMapSize / 32);
103 int tile16_y = (mouse_y_i % kOverworldMapSize) / (kOverworldMapSize / 32);
104
105 // Update the overworld map_tiles based on tile16 ID and current world
106 auto& selected_world =
107 (*deps_.current_world == 0)
108 ? deps_.overworld->mutable_map_tiles()->light_world
109 : (*deps_.current_world == 1)
110 ? deps_.overworld->mutable_map_tiles()->dark_world
111 : deps_.overworld->mutable_map_tiles()->special_world;
112
113 int index_x = superX * 32 + tile16_x;
114 int index_y = superY * 32 + tile16_y;
115 if (index_x < 0 || index_y < 0 ||
116 index_x >= static_cast<int>(selected_world.size()) ||
117 index_y >= static_cast<int>(selected_world[index_x].size())) {
118 return;
119 }
120
121 // Get old tile value for undo tracking
122 int old_tile_id = selected_world[index_x][index_y];
123
124 // Only record undo if tile is actually changing
125 if (old_tile_id != *deps_.current_tile16) {
127 index_x, index_y, old_tile_id);
128 deps_.rom->set_dirty(true);
129 }
130
131 selected_world[index_x][index_y] = *deps_.current_tile16;
132}
133
134// ---------------------------------------------------------------------------
135// RenderUpdatedMapBitmap - update bitmap pixels after tile paint
136// ---------------------------------------------------------------------------
138 const ImVec2& click_position, const std::vector<uint8_t>& tile_data) {
139 // Bounds checking to prevent crashes
140 if (*deps_.current_map < 0 ||
141 *deps_.current_map >= static_cast<int>(deps_.maps_bmp->size())) {
142 LOG_ERROR("TilePaintingManager",
143 "ERROR: RenderUpdatedMapBitmap - Invalid current_map %d "
144 "(maps_bmp size=%zu)",
145 *deps_.current_map, deps_.maps_bmp->size());
146 return; // Invalid map index, skip rendering
147 }
148
149 // Calculate the tile index for x and y based on the click_position
150 int tile_index_x =
151 (static_cast<int>(click_position.x) % kOverworldMapSize) / kTile16Size;
152 int tile_index_y =
153 (static_cast<int>(click_position.y) % kOverworldMapSize) / kTile16Size;
154
155 // Calculate the pixel start position based on tile index and tile size
156 ImVec2 start_position;
157 start_position.x = static_cast<float>(tile_index_x * kTile16Size);
158 start_position.y = static_cast<float>(tile_index_y * kTile16Size);
159
160 // Update the bitmap's pixel data based on the start_position and tile_data
161 gfx::Bitmap& current_bitmap = (*deps_.maps_bmp)[*deps_.current_map];
162
163 // Validate bitmap state before writing
164 if (!current_bitmap.is_active() || current_bitmap.size() == 0) {
165 LOG_ERROR(
166 "TilePaintingManager",
167 "ERROR: RenderUpdatedMapBitmap - Bitmap %d is not active or has no "
168 "data (active=%s, size=%zu)",
169 *deps_.current_map, current_bitmap.is_active() ? "true" : "false",
170 current_bitmap.size());
171 return;
172 }
173
174 for (int y = 0; y < kTile16Size; ++y) {
175 for (int x = 0; x < kTile16Size; ++x) {
176 int pixel_index =
177 (start_position.y + y) * kOverworldMapSize + (start_position.x + x);
178
179 // Bounds check for pixel index
180 if (pixel_index < 0 ||
181 pixel_index >= static_cast<int>(current_bitmap.size())) {
182 LOG_ERROR(
183 "TilePaintingManager",
184 "ERROR: RenderUpdatedMapBitmap - pixel_index %d out of bounds "
185 "(bitmap size=%zu)",
186 pixel_index, current_bitmap.size());
187 continue;
188 }
189
190 // Bounds check for tile data
191 int tile_data_index = y * kTile16Size + x;
192 if (tile_data_index < 0 ||
193 tile_data_index >= static_cast<int>(tile_data.size())) {
194 LOG_ERROR(
195 "TilePaintingManager",
196 "ERROR: RenderUpdatedMapBitmap - tile_data_index %d out of bounds "
197 "(tile_data size=%zu)",
198 tile_data_index, tile_data.size());
199 continue;
200 }
201
202 current_bitmap.WriteToPixel(pixel_index, tile_data[tile_data_index]);
203 }
204 }
205
206 current_bitmap.set_modified(true);
207
208 // Immediately update the texture to reflect changes
210 &current_bitmap);
211}
212
213// ---------------------------------------------------------------------------
214// CheckForOverworldEdits - main painting entry point
215// ---------------------------------------------------------------------------
217 LOG_DEBUG("TilePaintingManager", "CheckForOverworldEdits: Frame %d",
218 ImGui::GetFrameCount());
219
221
222 // User has selected a tile they want to draw from the blockset
223 // and clicked on the canvas.
225 *deps_.current_tile16 >= 0 &&
230 }
231
232 // Fill tool: fill the entire 32x32 tile16 screen under the cursor using the
233 // current selection pattern (if any) or the current tile16.
236 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
237 float scale = deps_.ow_map_canvas->global_scale();
238 if (scale <= 0.0f) {
239 scale = 1.0f;
240 }
241
242 const auto scaled_position = deps_.ow_map_canvas->hover_mouse_pos();
243 if (scaled_position.x < 0.0f || scaled_position.y < 0.0f) {
244 return;
245 }
246 const int map_x =
247 static_cast<int>(scaled_position.x / scale) / kOverworldMapSize;
248 const int map_y =
249 static_cast<int>(scaled_position.y / scale) / kOverworldMapSize;
250
251 // Bounds guard.
252 if (IsValidMapGridPosition(*deps_.current_world, map_x, map_y)) {
253 const int target_map =
254 MapIndexForGridPosition(*deps_.current_world, map_x, map_y);
255 if (target_map >= 0 && target_map < zelda3::kNumOverworldMaps) {
256 // Build pattern from active rectangle selection (if present).
257 std::vector<int> pattern_ids;
258 int pattern_w = 1;
259 int pattern_h = 1;
260
262 deps_.ow_map_canvas->selected_points().size() >= 2) {
263 const auto start = deps_.ow_map_canvas->selected_points()[0];
264 const auto end = deps_.ow_map_canvas->selected_points()[1];
265
266 const int start_x =
267 static_cast<int>(std::floor(std::min(start.x, end.x) / 16.0f));
268 const int end_x =
269 static_cast<int>(std::floor(std::max(start.x, end.x) / 16.0f));
270 const int start_y =
271 static_cast<int>(std::floor(std::min(start.y, end.y) / 16.0f));
272 const int end_y =
273 static_cast<int>(std::floor(std::max(start.y, end.y) / 16.0f));
274
275 pattern_w = std::max(1, end_x - start_x + 1);
276 pattern_h = std::max(1, end_y - start_y + 1);
277 pattern_ids.reserve(pattern_w * pattern_h);
278
280 deps_.overworld->set_current_map(target_map);
281 for (int y = start_y; y <= end_y; ++y) {
282 for (int x = start_x; x <= end_x; ++x) {
283 pattern_ids.push_back(deps_.overworld->GetTile(x, y));
284 }
285 }
286 } else {
287 pattern_ids = {*deps_.current_tile16};
288 }
289
290 auto& world_tiles =
291 (*deps_.current_world == 0)
292 ? deps_.overworld->mutable_map_tiles()->light_world
293 : (*deps_.current_world == 1)
294 ? deps_.overworld->mutable_map_tiles()->dark_world
295 : deps_.overworld->mutable_map_tiles()->special_world;
296
297 // Apply the fill (repeat pattern across 32x32).
298 for (int y = 0; y < 32; ++y) {
299 for (int x = 0; x < 32; ++x) {
300 const int pattern_x = x % pattern_w;
301 const int pattern_y = y % pattern_h;
302 const int new_tile_id =
303 pattern_ids[pattern_y * pattern_w + pattern_x];
304
305 const int global_x = map_x * 32 + x;
306 const int global_y = map_y * 32 + y;
307 if (global_x < 0 || global_y < 0 ||
308 global_x >= static_cast<int>(world_tiles.size()) ||
309 global_y >= static_cast<int>(world_tiles[global_x].size())) {
310 continue;
311 }
312
313 const int old_tile_id = world_tiles[global_x][global_y];
314 if (old_tile_id == new_tile_id) {
315 continue;
316 }
317
319 global_x, global_y, old_tile_id);
320 world_tiles[global_x][global_y] = new_tile_id;
321 }
322 }
323
324 deps_.rom->set_dirty(true);
326 *deps_.current_map = target_map;
328 }
329 }
330 }
331
332 // Rectangle selection stamping (brush mode only).
335 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
336 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
337 LOG_DEBUG("TilePaintingManager",
338 "CheckForOverworldEdits: About to apply rectangle selection");
339
340 auto& selected_world =
341 (*deps_.current_world == 0)
342 ? deps_.overworld->mutable_map_tiles()->light_world
343 : (*deps_.current_world == 1)
344 ? deps_.overworld->mutable_map_tiles()->dark_world
345 : deps_.overworld->mutable_map_tiles()->special_world;
346 // selected_points are now stored in world coordinates
347 auto start = deps_.ow_map_canvas->selected_points()[0];
348 auto end = deps_.ow_map_canvas->selected_points()[1];
349
350 // Calculate the bounds of the rectangle in terms of 16x16 tile indices
351 int start_x = std::floor(start.x / kTile16Size) * kTile16Size;
352 int start_y = std::floor(start.y / kTile16Size) * kTile16Size;
353 int end_x = std::floor(end.x / kTile16Size) * kTile16Size;
354 int end_y = std::floor(end.y / kTile16Size) * kTile16Size;
355
356 if (start_x > end_x)
357 std::swap(start_x, end_x);
358 if (start_y > end_y)
359 std::swap(start_y, end_y);
360
361 constexpr int local_map_size = 512; // Size of each local map
362 // Number of tiles per local map (since each tile is 16x16)
363 constexpr int tiles_per_local_map = local_map_size / kTile16Size;
364
365 LOG_DEBUG("TilePaintingManager",
366 "CheckForOverworldEdits: About to fill rectangle with "
367 "current_tile16=%d",
369
370 // Apply the selected tiles to each position in the rectangle
371 // CRITICAL FIX: Use pre-computed tile16_ids instead of recalculating
372 // from selected_tiles. This prevents wrapping issues when dragging near
373 // boundaries.
374 int i = 0;
375 for (int y = start_y;
376 y <= end_y &&
377 i < static_cast<int>(deps_.selected_tile16_ids->size());
378 y += kTile16Size) {
379 for (int x = start_x;
380 x <= end_x &&
381 i < static_cast<int>(deps_.selected_tile16_ids->size());
382 x += kTile16Size, ++i) {
383 // Determine which local map (512x512) the tile is in
384 int local_map_x = x / local_map_size;
385 int local_map_y = y / local_map_size;
386
387 // Calculate the tile's position within its local map
388 int tile16_x = (x % local_map_size) / kTile16Size;
389 int tile16_y = (y % local_map_size) / kTile16Size;
390
391 // Calculate the index within the overall map structure
392 int index_x = local_map_x * tiles_per_local_map + tile16_x;
393 int index_y = local_map_y * tiles_per_local_map + tile16_y;
394
395 // FIXED: Use pre-computed tile ID from the ORIGINAL selection
396 int tile16_id = (*deps_.selected_tile16_ids)[i];
397 // Bounds check for the selected world array
398 int rect_width = ((end_x - start_x) / kTile16Size) + 1;
399 int rect_height = ((end_y - start_y) / kTile16Size) + 1;
400
401 // Prevent painting from wrapping around at the edges of large maps
402 int start_local_map_x = start_x / local_map_size;
403 int start_local_map_y = start_y / local_map_size;
404 int end_local_map_x = end_x / local_map_size;
405 int end_local_map_y = end_y / local_map_size;
406
407 bool in_same_local_map = (start_local_map_x == end_local_map_x) &&
408 (start_local_map_y == end_local_map_y);
409
410 if (in_same_local_map && index_x >= 0 &&
411 (index_x + rect_width - 1) < 0x200 && index_y >= 0 &&
412 (index_y + rect_height - 1) < 0x200) {
413 // Get old tile value for undo tracking
414 int old_tile_id = selected_world[index_x][index_y];
415 if (old_tile_id != tile16_id) {
417 *deps_.current_world, index_x,
418 index_y, old_tile_id);
419 }
420
421 selected_world[index_x][index_y] = tile16_id;
422
423 // CRITICAL FIX: Also update the bitmap directly like single tile
424 // drawing
425 ImVec2 tile_position(x, y);
426 auto tile_data =
428 if (!tile_data.empty()) {
429 RenderUpdatedMapBitmap(tile_position, tile_data);
430 LOG_DEBUG(
431 "TilePaintingManager",
432 "CheckForOverworldEdits: Updated bitmap at position (%d,%d) "
433 "with tile16_id=%d",
434 x, y, tile16_id);
435 } else {
436 LOG_ERROR("TilePaintingManager",
437 "ERROR: Failed to get tile data for tile16_id=%d",
438 tile16_id);
439 }
440 }
441 }
442 }
443
444 // Finalize the undo batch operation after all tiles are placed
446
447 deps_.rom->set_dirty(true);
449 }
450 }
451}
452
453// ---------------------------------------------------------------------------
454// CheckForSelectRectangle - rectangle drag-to-select tiles
455// ---------------------------------------------------------------------------
457 // Pass the canvas scale for proper zoom handling
458 float scale = deps_.ow_map_canvas->global_scale();
459 if (scale <= 0.0f)
460 scale = 1.0f;
462
463 // Single tile case
464 if (deps_.ow_map_canvas->selected_tile_pos().x != -1) {
468
469 // Scroll blockset canvas to show the selected tile
471 }
472
473 // Rectangle selection case - use member variable instead of static local
475 // Get the tile16 IDs from the selected tile ID positions
476 deps_.selected_tile16_ids->clear();
477
478 if (deps_.ow_map_canvas->selected_tiles().size() > 0) {
479 // Set the current world and map in overworld for proper tile lookup
482 for (auto& each : deps_.ow_map_canvas->selected_tiles()) {
483 deps_.selected_tile16_ids->push_back(
485 }
486 }
487 }
488 // Create a composite image of all the tile16s selected
490 *deps_.tile16_blockset, 0x10,
492}
493
494// ---------------------------------------------------------------------------
495// PickTile16FromHoveredCanvas - eyedropper tool
496// ---------------------------------------------------------------------------
499 return false;
500 }
501
502 const ImVec2 scaled_position = deps_.ow_map_canvas->hover_mouse_pos();
503 float scale = deps_.ow_map_canvas->global_scale();
504 if (scale <= 0.0f) {
505 scale = 1.0f;
506 }
507 if (scaled_position.x < 0.0f || scaled_position.y < 0.0f) {
508 return false;
509 }
510
511 const int map_x =
512 static_cast<int>(scaled_position.x / scale) / kOverworldMapSize;
513 const int map_y =
514 static_cast<int>(scaled_position.y / scale) / kOverworldMapSize;
515 if (!IsValidMapGridPosition(*deps_.current_world, map_x, map_y)) {
516 return false;
517 }
518
519 const int local_tile_x =
520 (static_cast<int>(scaled_position.x / scale) % kOverworldMapSize) /
522 const int local_tile_y =
523 (static_cast<int>(scaled_position.y / scale) % kOverworldMapSize) /
525 if (local_tile_x < 0 || local_tile_x >= 32 || local_tile_y < 0 ||
526 local_tile_y >= 32) {
527 return false;
528 }
529
530 const int world_tile_x = map_x * 32 + local_tile_x;
531 const int world_tile_y = map_y * 32 + local_tile_y;
532 if (world_tile_x < 0 || world_tile_x >= 256 || world_tile_y < 0 ||
533 world_tile_y >= 256) {
534 return false;
535 }
536
537 const auto* map_tiles = deps_.overworld->mutable_map_tiles();
538 if (!map_tiles) {
539 return false;
540 }
541 const auto& world_tiles = (*deps_.current_world == 0) ? map_tiles->light_world
542 : (*deps_.current_world == 1)
543 ? map_tiles->dark_world
544 : map_tiles->special_world;
545 if (world_tile_x >= static_cast<int>(world_tiles.size()) ||
546 world_tile_y >= static_cast<int>(world_tiles[world_tile_x].size())) {
547 return false;
548 }
549 const int tile_id = world_tiles[world_tile_x][world_tile_y];
550 if (tile_id < 0) {
551 return false;
552 }
553
556 } else {
557 *deps_.current_tile16 = tile_id;
558 auto set_tile_status =
560 if (!set_tile_status.ok()) {
561 util::logf("Failed to sync Tile16 editor after eyedropper: %s",
562 set_tile_status.message().data());
563 }
564 }
565
567 return true;
568}
569
570// ---------------------------------------------------------------------------
571// ToggleBrushTool - switch between DRAW_TILE and MOUSE modes
572// ---------------------------------------------------------------------------
586
587// ---------------------------------------------------------------------------
588// ActivateFillTool - toggle FILL_TILE mode on/off
589// ---------------------------------------------------------------------------
600
601} // namespace yaze::editor
void set_dirty(bool dirty)
Definition rom.h:146
absl::Status SetCurrentTile(int id)
void CheckForSelectRectangle()
Draw and create the tile16 IDs that are currently selected.
void DrawOverworldEdits()
Handle the actual drawing of a single tile (called by CheckForOverworldEdits when DrawTilemapPainter ...
void CheckForOverworldEdits()
Main entry point: check for tile edits (paint, fill, stamp).
void RenderUpdatedMapBitmap(const ImVec2 &click_position, const std::vector< uint8_t > &tile_data)
Update bitmap pixels after a single tile paint.
bool PickTile16FromHoveredCanvas()
Eyedropper: pick the tile16 under the hovered canvas position.
void ActivateFillTool()
Toggle FILL_TILE mode on/off.
void ToggleBrushTool()
Toggle between DRAW_TILE and MOUSE modes.
TilePaintingManager(const TilePaintingDependencies &deps, const TilePaintingCallbacks &callbacks)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
static Arena & Get()
Definition arena.cc:21
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
Definition bitmap.cc:581
const std::vector< uint8_t > & vector() const
Definition bitmap.h:402
auto size() const
Definition bitmap.h:397
bool is_active() const
Definition bitmap.h:405
void set_modified(bool modified)
Definition bitmap.h:409
auto selected_tile_pos() const
Definition canvas.h:395
auto global_scale() const
Definition canvas.h:397
auto select_rect_active() const
Definition canvas.h:393
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:290
auto selected_tiles() const
Definition canvas.h:394
void DrawBitmapGroup(std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale=1.0f, int local_map_size=0x200, ImVec2 total_map_size=ImVec2(0x1000, 0x1000))
Draw group of bitmaps for multi-tile selection preview.
Definition canvas.cc:1241
auto hover_mouse_pos() const
Definition canvas.h:460
auto drawn_tile_position() const
Definition canvas.h:356
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
Definition canvas.cc:985
void set_selected_tile_pos(ImVec2 pos)
Definition canvas.h:396
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
Definition canvas.cc:1128
bool IsMouseHovering() const
Definition canvas.h:338
auto selected_points() const
Definition canvas.h:458
void set_current_world(int world)
Definition overworld.h:735
int GetTileFromPosition(ImVec2 position) const
Definition overworld.h:636
void set_current_map(int i)
Definition overworld.h:734
uint16_t GetTile(int x, int y) const
Definition overworld.h:736
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
Editors are the view controllers for the application.
constexpr unsigned int kOverworldMapSize
constexpr int kTile16Size
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:268
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:115
constexpr int kNumOverworldMaps
Definition common.h:85
Callbacks for undo integration and map refresh.
std::function< void(int tile_id)> request_tile16_selection
When set, eyedropper routes here (guarded RequestTileSwitch path).
std::function< void(int map_index)> refresh_overworld_map_on_demand
std::function< void()> scroll_blockset_to_current_tile
std::function< void()> finalize_paint_operation
std::function< void(int map_id, int world, int x, int y, int old_tile_id)> create_undo_point
Shared state for the tile painting system.
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119