yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
screen_editor.cc
Go to the documentation of this file.
1#include "screen_editor.h"
2
3#include <fstream>
4#include <iostream>
5#include <string>
6
7#include "absl/strings/str_format.h"
8#include "absl/strings/string_view.h"
10#include "util/file_util.h"
11#include "app/core/window.h"
12#include "app/gfx/arena.h"
14#include "app/gfx/bitmap.h"
16#include "app/gfx/snes_tile.h"
17#include "app/gui/canvas.h"
18#include "app/gui/color.h"
19#include "app/gui/icons.h"
20#include "app/gui/input.h"
21#include "app/gui/ui_helpers.h"
22#include "imgui/imgui.h"
23#include "util/hex.h"
24#include "util/macro.h"
25
26namespace yaze {
27namespace editor {
28
29
30constexpr uint32_t kRedPen = 0xFF0000FF;
31
33 // Register cards with EditorCardManager during initialization (once)
34 auto& card_manager = gui::EditorCardManager::Get();
35
36 card_manager.RegisterCard({
37 .card_id = "screen.dungeon_maps",
38 .display_name = "Dungeon Maps",
39 .icon = ICON_MD_MAP,
40 .category = "Screen",
41 .shortcut_hint = "Alt+1",
42 .visibility_flag = &show_dungeon_maps_,
43 .priority = 10
44 });
45
46 card_manager.RegisterCard({
47 .card_id = "screen.inventory_menu",
48 .display_name = "Inventory Menu",
49 .icon = ICON_MD_INVENTORY,
50 .category = "Screen",
51 .shortcut_hint = "Alt+2",
52 .visibility_flag = &show_inventory_menu_,
53 .priority = 20
54 });
55
56 card_manager.RegisterCard({
57 .card_id = "screen.overworld_map",
58 .display_name = "Overworld Map",
59 .icon = ICON_MD_PUBLIC,
60 .category = "Screen",
61 .shortcut_hint = "Alt+3",
62 .visibility_flag = &show_overworld_map_,
63 .priority = 30
64 });
65
66 card_manager.RegisterCard({
67 .card_id = "screen.title_screen",
68 .display_name = "Title Screen",
69 .icon = ICON_MD_TITLE,
70 .category = "Screen",
71 .shortcut_hint = "Alt+4",
72 .visibility_flag = &show_title_screen_,
73 .priority = 40
74 });
75
76 card_manager.RegisterCard({
77 .card_id = "screen.naming_screen",
78 .display_name = "Naming Screen",
79 .icon = ICON_MD_EDIT,
80 .category = "Screen",
81 .shortcut_hint = "Alt+5",
82 .visibility_flag = &show_naming_screen_,
83 .priority = 50
84 });
85}
86
87absl::Status ScreenEditor::Load() {
88 gfx::ScopedTimer timer("ScreenEditor::Load");
89
93 tile16_blockset_, *rom(), rom()->graphics_buffer(), false));
94 // TODO: Load roomset gfx based on dungeon ID
95 sheets_.try_emplace(0, gfx::Arena::Get().gfx_sheets()[212]);
96 sheets_.try_emplace(1, gfx::Arena::Get().gfx_sheets()[213]);
97 sheets_.try_emplace(2, gfx::Arena::Get().gfx_sheets()[214]);
98 sheets_.try_emplace(3, gfx::Arena::Get().gfx_sheets()[215]);
118 return absl::OkStatus();
119}
120
121absl::Status ScreenEditor::Update() {
122 DrawToolset();
124
125 // Create session-aware cards (non-static for multi-session support)
126 gui::EditorCard dungeon_maps_card(MakeCardTitle("Dungeon Maps").c_str(), ICON_MD_MAP);
127 gui::EditorCard inventory_menu_card(MakeCardTitle("Inventory Menu").c_str(), ICON_MD_INVENTORY);
128 gui::EditorCard overworld_map_card(MakeCardTitle("Overworld Map").c_str(), ICON_MD_PUBLIC);
129 gui::EditorCard title_screen_card(MakeCardTitle("Title Screen").c_str(), ICON_MD_TITLE);
130 gui::EditorCard naming_screen_card(MakeCardTitle("Naming Screen").c_str(), ICON_MD_EDIT_ATTRIBUTES);
131
132 if (show_dungeon_maps_) {
133 if (dungeon_maps_card.Begin(&show_dungeon_maps_)) {
135 }
136 dungeon_maps_card.End(); // ALWAYS call End after Begin
137 }
139 if (inventory_menu_card.Begin(&show_inventory_menu_)) {
141 }
142 inventory_menu_card.End(); // ALWAYS call End after Begin
143 }
145 if (overworld_map_card.Begin(&show_overworld_map_)) {
147 }
148 overworld_map_card.End(); // ALWAYS call End after Begin
149 }
150 if (show_title_screen_) {
151 if (title_screen_card.Begin(&show_title_screen_)) {
153 }
154 title_screen_card.End(); // ALWAYS call End after Begin
155 }
157 if (naming_screen_card.Begin(&show_naming_screen_)) {
159 }
160 naming_screen_card.End(); // ALWAYS call End after Begin
161 }
162
163 return status_;
164}
165
167 static gui::Toolset toolbar;
168 toolbar.Begin();
169
170 if (toolbar.AddAction(ICON_MD_MAP, "Dungeon Maps")) {
172 }
173 if (toolbar.AddAction(ICON_MD_INVENTORY, "Inventory Menu")) {
175 }
176 if (toolbar.AddAction(ICON_MD_PUBLIC, "Overworld Map")) {
178 }
179 if (toolbar.AddAction(ICON_MD_TITLE, "Title Screen")) {
181 }
182 if (toolbar.AddAction(ICON_MD_EDIT_ATTRIBUTES, "Naming Screen")) {
184 }
185
186 toolbar.End();
187}
188
190 static bool create = false;
191 if (!create && rom()->is_loaded()) {
194 create = true;
195 }
196
198
199 if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
200 ImGui::TableSetupColumn("Canvas");
201 ImGui::TableSetupColumn("Tilesheet");
202 ImGui::TableSetupColumn("Palette");
203 ImGui::TableHeadersRow();
204
205 ImGui::TableNextColumn();
211
212 ImGui::TableNextColumn();
213 tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
218
219 ImGui::TableNextColumn();
221
222 ImGui::EndTable();
223 }
224 ImGui::Separator();
225}
226
228 if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
229 ImVec2(0, 0))) {
230 ImGui::TableSetupColumn("#drawTool");
231 ImGui::TableSetupColumn("#sep1");
232 ImGui::TableSetupColumn("#zoomOut");
233 ImGui::TableSetupColumn("#zoomIN");
234 ImGui::TableSetupColumn("#sep2");
235 ImGui::TableSetupColumn("#bg2Tool");
236 ImGui::TableSetupColumn("#bg3Tool");
237 ImGui::TableSetupColumn("#itemTool");
238
239 ImGui::TableNextColumn();
240 if (ImGui::Button(ICON_MD_UNDO)) {
241 // status_ = inventory_.Undo();
242 }
243 ImGui::TableNextColumn();
244 if (ImGui::Button(ICON_MD_REDO)) {
245 // status_ = inventory_.Redo();
246 }
247 ImGui::TableNextColumn();
248 ImGui::Text(ICON_MD_MORE_VERT);
249 ImGui::TableNextColumn();
250 if (ImGui::Button(ICON_MD_ZOOM_OUT)) {
252 }
253 ImGui::TableNextColumn();
254 if (ImGui::Button(ICON_MD_ZOOM_IN)) {
256 }
257 ImGui::TableNextColumn();
258 ImGui::Text(ICON_MD_MORE_VERT);
259 ImGui::TableNextColumn();
260 if (ImGui::Button(ICON_MD_DRAW)) {
262 }
263 ImGui::TableNextColumn();
264 if (ImGui::Button(ICON_MD_BUILD)) {
265 // current_mode_ = EditingMode::BUILD;
266 }
267
268 ImGui::EndTable();
269 }
270}
271
273 gfx::ScopedTimer timer("screen_editor_draw_dungeon_map_screen");
274
275 auto& current_dungeon = dungeon_maps_[selected_dungeon];
276
277 floor_number = i;
278 screen_canvas_.DrawBackground(ImVec2(325, 325));
280
281 auto boss_room = current_dungeon.boss_room;
282
283 // Pre-allocate vectors for batch operations
284 std::vector<int> tile_ids_to_render;
285 std::vector<ImVec2> tile_positions;
286 tile_ids_to_render.reserve(zelda3::kNumRooms);
287 tile_positions.reserve(zelda3::kNumRooms);
288
289 for (int j = 0; j < zelda3::kNumRooms; j++) {
290 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
291 int tile16_id = current_dungeon.floor_gfx[floor_number][j];
292 int posX = ((j % 5) * 32);
293 int posY = ((j / 5) * 32);
294
295 // Batch tile rendering
296 tile_ids_to_render.push_back(tile16_id);
297 tile_positions.emplace_back(posX * 2, posY * 2);
298 }
299 }
300
301 // Batch render all tiles
302 for (size_t idx = 0; idx < tile_ids_to_render.size(); ++idx) {
303 int tile16_id = tile_ids_to_render[idx];
304 ImVec2 pos = tile_positions[idx];
305
306 gfx::RenderTile16(nullptr, tile16_blockset_, tile16_id);
307 // Get tile from cache after rendering
308 auto* cached_tile = tile16_blockset_.tile_cache.GetTile(tile16_id);
309 if (cached_tile && cached_tile->is_active()) {
310 // Ensure the cached tile has a valid texture
311 if (!cached_tile->texture()) {
312 // Queue texture creation via Arena's deferred system
315 }
316 screen_canvas_.DrawBitmap(*cached_tile, pos.x, pos.y, 4.0F, 255);
317 }
318 }
319
320 // Draw overlays and labels
321 for (int j = 0; j < zelda3::kNumRooms; j++) {
322 if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
323 int posX = ((j % 5) * 32);
324 int posY = ((j / 5) * 32);
325
326 if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
327 screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64, 64,
328 kRedPen);
329 }
330
331 std::string label =
333 screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
334 std::string gfx_id =
335 util::HexByte(current_dungeon.floor_gfx[floor_number][j]);
336 screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
337 }
338 }
339
340 screen_canvas_.DrawGrid(64.f, 5);
342
343 if (!screen_canvas_.points().empty()) {
344 int x = screen_canvas_.points().front().x / 64;
345 int y = screen_canvas_.points().front().y / 64;
346 selected_room = x + (y * 5);
347 }
348}
349
351 auto& current_dungeon = dungeon_maps_[selected_dungeon];
352 if (ImGui::BeginTabBar("##DungeonMapTabs")) {
353 auto nbr_floors =
354 current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
355 for (int i = 0; i < nbr_floors; i++) {
356 int basement_num = current_dungeon.nbr_of_basement - i;
357 std::string tab_name = absl::StrFormat("Basement %d", basement_num);
358 if (i >= current_dungeon.nbr_of_basement) {
359 tab_name = absl::StrFormat("Floor %d",
360 i - current_dungeon.nbr_of_basement + 1);
361 }
362 if (ImGui::BeginTabItem(tab_name.data())) {
364 ImGui::EndTabItem();
365 }
366 }
367 ImGui::EndTabBar();
368 }
369
371 "Selected Room",
372 &current_dungeon.floor_rooms[floor_number].at(selected_room));
373
374 gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
375
376 const auto button_size = ImVec2(130, 0);
377
378 if (ImGui::Button("Add Floor", button_size) &&
379 current_dungeon.nbr_of_floor < 8) {
380 current_dungeon.nbr_of_floor++;
382 }
383 ImGui::SameLine();
384 if (ImGui::Button("Remove Floor", button_size) &&
385 current_dungeon.nbr_of_floor > 0) {
386 current_dungeon.nbr_of_floor--;
388 }
389
390 if (ImGui::Button("Add Basement", button_size) &&
391 current_dungeon.nbr_of_basement < 8) {
392 current_dungeon.nbr_of_basement++;
394 }
395 ImGui::SameLine();
396 if (ImGui::Button("Remove Basement", button_size) &&
397 current_dungeon.nbr_of_basement > 0) {
398 current_dungeon.nbr_of_basement--;
400 }
401
402 if (ImGui::Button("Copy Floor", button_size)) {
403 copy_button_pressed = true;
404 }
405 ImGui::SameLine();
406 if (ImGui::Button("Paste Floor", button_size)) {
408 }
409}
410
428 gfx::ScopedTimer timer("screen_editor_draw_dungeon_maps_room_gfx");
429
430 if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
431 // Enhanced tilesheet canvas with improved tile selection
432 tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
434
435 // Interactive tile16 selector with grid snapping
437 selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
438 (tilesheet_canvas_.points().front().y / 32) * 16;
439
440 // Render selected tile16 and cache tile metadata
443 current_tile16_info.begin());
444 }
445 // Use direct bitmap rendering for tilesheet
449
450 if (!tilesheet_canvas_.points().empty() &&
451 !screen_canvas_.points().empty()) {
455 }
456
457 ImGui::Separator();
458 current_tile_canvas_.DrawBackground(); // ImVec2(64 * 2 + 2, 64 * 2 + 4));
461 16)) {
462 // Modify the tile16 based on the selected tile and current_tile16_info
463 gfx::ModifyTile16(tile16_blockset_, rom()->graphics_buffer(),
468 }
469 // Get selected tile from cache
471 if (selected_tile && selected_tile->is_active()) {
472 // Ensure the selected tile has a valid texture
473 if (!selected_tile->texture()) {
474 // Queue texture creation via Arena's deferred system
477 }
478 current_tile_canvas_.DrawBitmap(*selected_tile, 2, 2, 4.0f, 255);
479 }
482
484 ImGui::SameLine();
487 ImGui::SameLine();
489
490 if (ImGui::Button("Modify Tile16")) {
491 gfx::ModifyTile16(tile16_blockset_, rom()->graphics_buffer(),
496 }
497 }
498 ImGui::EndChild();
499}
500
518 // Enhanced editing mode controls with visual feedback
519 if (ImGui::Button(ICON_MD_DRAW)) {
521 }
522 ImGui::SameLine();
523 if (ImGui::Button(ICON_MD_EDIT)) {
525 }
526 ImGui::SameLine();
527 if (ImGui::Button(ICON_MD_SAVE)) {
529 }
530
531 static std::vector<std::string> dungeon_names = {
532 "Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
533 "Desert Palace", "Tower of Hera", "Agahnim's Tower",
534 "Palace of Darkness", "Swamp Palace", "Skull Woods",
535 "Thieves' Town", "Ice Palace", "Misery Mire",
536 "Turtle Rock", "Ganon's Tower"};
537
538 if (ImGui::BeginTable("DungeonMapsTable", 4,
539 ImGuiTableFlags_Resizable |
540 ImGuiTableFlags_Reorderable |
541 ImGuiTableFlags_Hideable)) {
542 ImGui::TableSetupColumn("Dungeon");
543 ImGui::TableSetupColumn("Map");
544 ImGui::TableSetupColumn("Rooms Gfx");
545 ImGui::TableSetupColumn("Tiles Gfx");
546 ImGui::TableHeadersRow();
547
548 ImGui::TableNextColumn();
549 for (int i = 0; i < dungeon_names.size(); i++) {
551 selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
552 dungeon_names[i]);
553 if (ImGui::IsItemClicked()) {
555 }
556 }
557
558 ImGui::TableNextColumn();
560
561 ImGui::TableNextColumn();
563
564 ImGui::TableNextColumn();
568 // Get the tile8 ID to use for the tile16 drawing above
570 }
574
575 ImGui::Text("Selected tile8: %d", selected_tile8_);
576 ImGui::Separator();
577 ImGui::Text("For use with custom inserted graphics assembly patches.");
578 if (ImGui::Button("Load GFX from BIN file"))
580
581 ImGui::EndTable();
582 }
583}
584
586 std::string bin_file = util::FileDialogWrapper::ShowOpenFileDialog();
587 if (!bin_file.empty()) {
588 std::ifstream file(bin_file, std::ios::binary);
589 if (file.is_open()) {
590 // Read the gfx data into a buffer
591 std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
592 std::istreambuf_iterator<char>());
593 if (auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
595 true)
596 .ok()) {
597 sheets_.clear();
598 std::vector<std::vector<uint8_t>> gfx_sheets;
599 for (int i = 0; i < 4; i++) {
600 gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
601 converted_bin.begin() + ((i + 1) * 0x1000));
602 sheets_.emplace(i, gfx::Bitmap(128, 32, 8, gfx_sheets[i]));
603 sheets_[i].SetPalette(*rom()->mutable_dungeon_palette(3));
604 // Queue texture creation via Arena's deferred system
607 }
608 binary_gfx_loaded_ = true;
609 } else {
610 status_ = absl::InternalError("Failed to load dungeon map tile16");
611 }
612 file.close();
613 }
614 }
615}
616
619
622
625
627 static bool show_bg1 = true;
628 static bool show_bg2 = true;
629 static bool show_bg3 = true;
630
631 static bool drawing_bg1 = true;
632 static bool drawing_bg2 = false;
633 static bool drawing_bg3 = false;
634
635 ImGui::Checkbox("Show BG1", &show_bg1);
636 ImGui::SameLine();
637 ImGui::Checkbox("Show BG2", &show_bg2);
638
639 ImGui::Checkbox("Draw BG1", &drawing_bg1);
640 ImGui::SameLine();
641 ImGui::Checkbox("Draw BG2", &drawing_bg2);
642 ImGui::SameLine();
643 ImGui::Checkbox("Draw BG3", &drawing_bg3);
644}
645
646} // namespace editor
647} // namespace yaze
auto mutable_dungeon_palette(int i)
Definition rom.h:216
core::ResourceLabelManager * resource_label()
Definition rom.h:220
std::string MakeCardTitle(const std::string &base_title) const
Definition editor.h:127
void DrawDungeonMapsRoomGfx()
Draw dungeon room graphics editor with enhanced tile16 editing.
std::vector< gfx::Bitmap > tile8_individual_
std::array< gfx::TileInfo, 4 > current_tile16_info
absl::Status Load() override
absl::Status Update() override
void DrawDungeonMapsEditor()
Draw dungeon maps editor with enhanced ROM hacking features.
zelda3::Inventory inventory_
zelda3::DungeonMapLabels dungeon_map_labels_
std::vector< zelda3::DungeonMap > dungeon_maps_
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:32
static Arena & Get()
Definition arena.cc:15
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1062
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1147
ImVector< ImVec2 > * mutable_points()
Definition canvas.h:307
void DrawContextMenu()
Definition canvas.cc:441
int GetTileIdFromMousePos()
Definition canvas.h:288
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:920
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
Definition canvas.cc:728
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:1129
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:381
const ImVector< ImVec2 > & points() const
Definition canvas.h:306
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1386
void DrawText(const std::string &text, int x, int y)
Definition canvas.cc:1334
static EditorCardManager & Get()
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
Ultra-compact toolbar that merges mode buttons with settings.
bool AddAction(const char *icon, const char *tooltip)
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
absl::Status Create()
Definition inventory.cc:12
#define ICON_MD_TITLE
Definition icons.h:1988
#define ICON_MD_MORE_VERT
Definition icons.h:1241
#define ICON_MD_EDIT_ATTRIBUTES
Definition icons.h:644
#define ICON_MD_DRAW
Definition icons.h:623
#define ICON_MD_ZOOM_OUT
Definition icons.h:2194
#define ICON_MD_MAP
Definition icons.h:1171
#define ICON_MD_REDO
Definition icons.h:1568
#define ICON_MD_EDIT
Definition icons.h:643
#define ICON_MD_PUBLIC
Definition icons.h:1522
#define ICON_MD_INVENTORY
Definition icons.h:1009
#define ICON_MD_BUILD
Definition icons.h:326
#define ICON_MD_ZOOM_IN
Definition icons.h:2192
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_UNDO
Definition icons.h:2037
#define PRINT_IF_ERROR(expression)
Definition macro.h:27
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
constexpr uint32_t kRedPen
void RenderTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:63
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:188
void UpdateTile16(IRenderer *renderer, Tilemap &tilemap, int tile_id)
Definition tilemap.cc:90
std::vector< uint8_t > SnesTo8bppSheet(std::span< uint8_t > sheet, int bpp, int num_sheets)
Definition snes_tile.cc:129
void VerticalSpacing(float pixels)
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:175
bool InputTileInfo(const char *label, gfx::TileInfo *tile_info)
Definition input.cc:325
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
Definition color.cc:50
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:189
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:30
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom, const std::vector< uint8_t > &gfx_data, bool bin_mode)
Load the dungeon map tile16 from the ROM.
constexpr int kNumRooms
Definition dungeon_map.h:35
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom)
Save the dungeon map tile16 to the ROM.
absl::StatusOr< std::vector< DungeonMap > > LoadDungeonMaps(Rom &rom, DungeonMapLabels &dungeon_map_labels)
Load the dungeon maps from the ROM.
Main namespace for the application.
void SelectableLabelWithNameEdit(bool selected, const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:855
Bitmap * GetTile(int tile_id)
Get a cached tile by ID.
Definition tilemap.h:42
TileCache tile_cache
Smart tile cache with LRU eviction.
Definition tilemap.h:111
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:110
std::vector< std::array< gfx::TileInfo, 4 > > tile_info
Tile metadata (4 tiles per 16x16)
Definition tilemap.h:112