diff --git a/src/app/core/common.h b/src/app/core/common.h index 05c15a79..002b904e 100644 --- a/src/app/core/common.h +++ b/src/app/core/common.h @@ -49,7 +49,11 @@ class ExperimentFlags { // only supports macOS. bool kLoadSystemFonts = true; + // Uses texture streaming from SDL for my dynamic updates. bool kLoadTexturesAsStreaming = false; + + // Save dungeon map edits to the ROM. + bool kSaveDungeonMaps = false; }; ExperimentFlags() = default; diff --git a/src/app/core/constants.h b/src/app/core/constants.h index 97ff6e4a..2851eef0 100644 --- a/src/app/core/constants.h +++ b/src/app/core/constants.h @@ -135,6 +135,7 @@ namespace yaze { namespace app { namespace core { +constexpr uint32_t kRedPen = 0xFF0000FF; constexpr float kYazeVersion = 0.05; // ============================================================================ diff --git a/src/app/editor/screen_editor.cc b/src/app/editor/screen_editor.cc index a261ba8f..84740fe1 100644 --- a/src/app/editor/screen_editor.cc +++ b/src/app/editor/screen_editor.cc @@ -15,9 +15,11 @@ #include "app/core/constants.h" #include "app/gfx/bitmap.h" #include "app/gfx/snes_tile.h" +#include "app/gfx/tilesheet.h" #include "app/gui/canvas.h" #include "app/gui/icons.h" #include "app/gui/input.h" +#include "app/zelda3/dungeon/room.h" namespace yaze { namespace app { @@ -27,11 +29,11 @@ ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); } void ScreenEditor::Update() { TAB_BAR("##TabBar") + DrawDungeonMapsEditor(); DrawInventoryMenuEditor(); + DrawOverworldMapEditor(); DrawTitleScreenEditor(); DrawNamingScreenEditor(); - DrawOverworldMapEditor(); - DrawDungeonMapsEditor(); END_TAB_BAR() } @@ -39,7 +41,7 @@ void ScreenEditor::DrawInventoryMenuEditor() { TAB_ITEM("Inventory Menu") static bool create = false; - if (!create && rom()->isLoaded()) { + if (!create && rom()->is_loaded()) { inventory_.Create(); palette_ = inventory_.Palette(); create = true; @@ -76,43 +78,6 @@ void ScreenEditor::DrawInventoryMenuEditor() { END_TAB_ITEM() } -void ScreenEditor::DrawTitleScreenEditor() { - TAB_ITEM("Title Screen") - END_TAB_ITEM() -} -void ScreenEditor::DrawNamingScreenEditor() { - TAB_ITEM("Naming Screen") - END_TAB_ITEM() -} -void ScreenEditor::DrawOverworldMapEditor() { - TAB_ITEM("Overworld Map") - END_TAB_ITEM() -} -void ScreenEditor::DrawDungeonMapsEditor() { - TAB_ITEM("Dungeon Maps") - END_TAB_ITEM() -} - -void ScreenEditor::DrawToolset() { - static bool show_bg1 = true; - static bool show_bg2 = true; - static bool show_bg3 = true; - - static bool drawing_bg1 = true; - static bool drawing_bg2 = false; - static bool drawing_bg3 = false; - - ImGui::Checkbox("Show BG1", &show_bg1); - ImGui::SameLine(); - ImGui::Checkbox("Show BG2", &show_bg2); - - ImGui::Checkbox("Draw BG1", &drawing_bg1); - ImGui::SameLine(); - ImGui::Checkbox("Draw BG2", &drawing_bg2); - ImGui::SameLine(); - ImGui::Checkbox("Draw BG3", &drawing_bg3); -} - void ScreenEditor::DrawInventoryToolset() { if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0))) { @@ -138,6 +103,322 @@ void ScreenEditor::DrawInventoryToolset() { } } +absl::Status ScreenEditor::LoadDungeonMaps() { + std::vector> current_floor_rooms_d; + std::vector> current_floor_gfx_d; + int total_floors_d; + uint8_t nbr_floor_d; + uint8_t nbr_basement_d; + + for (int d = 0; d < 14; d++) { + current_floor_rooms_d.clear(); + current_floor_gfx_d.clear(); + ASSIGN_OR_RETURN(int ptr, + rom()->ReadWord(zelda3::kDungeonMapRoomsPtr + (d * 2))); + ASSIGN_OR_RETURN(int ptrGFX, + rom()->ReadWord(zelda3::kDungeonMapRoomsPtr + (d * 2))); + ptr |= 0x0A0000; // Add bank to the short ptr + ptrGFX |= 0x0A0000; // Add bank to the short ptr + int pcPtr = core::SnesToPc(ptr); // Contains data for the next 25 rooms + int pcPtrGFX = + core::SnesToPc(ptrGFX); // Contains data for the next 25 rooms + + ASSIGN_OR_RETURN(ushort bossRoomD, + rom()->ReadWord(zelda3::kDungeonMapBossRooms + (d * 2))); + + ASSIGN_OR_RETURN(nbr_basement_d, + rom()->ReadByte(zelda3::kDungeonMapFloors + (d * 2))); + nbr_basement_d &= 0x0F; + + ASSIGN_OR_RETURN(nbr_floor_d, + rom()->ReadByte(zelda3::kDungeonMapFloors + (d * 2))); + nbr_floor_d &= 0xF0; + nbr_floor_d = nbr_floor_d >> 4; + + total_floors_d = nbr_basement_d + nbr_floor_d; + + dungeon_map_labels_.emplace_back(); + + // for each floor in the dungeon + for (int i = 0; i < total_floors_d; i++) { + dungeon_map_labels_[d].emplace_back(); + + std::array rdata; + std::array gdata; + + // for each room on the floor + for (int j = 0; j < 25; j++) { + // rdata[j] = 0x0F; + gdata[j] = 0xFF; + rdata[j] = rom()->data()[pcPtr + j + (i * 25)]; // Set the rooms + + if (rdata[j] == 0x0F) { + gdata[j] = 0xFF; + } else { + gdata[j] = rom()->data()[pcPtrGFX++]; + } + + std::string label = core::UppercaseHexByte(rdata[j]); + dungeon_map_labels_[d][i][j] = label; + } + + current_floor_gfx_d.push_back(gdata); // Add new floor gfx data + current_floor_rooms_d.push_back(rdata); // Add new floor data + } + + dungeon_maps_.emplace_back(bossRoomD, nbr_floor_d, nbr_basement_d, + current_floor_rooms_d, current_floor_gfx_d); + } + + return absl::OkStatus(); +} + +absl::Status ScreenEditor::SaveDungeonMaps() { + for (int d = 0; d < 14; d++) { + int ptr = zelda3::kDungeonMapRoomsPtr + (d * 2); + int ptrGFX = zelda3::kDungeonMapGfxPtr + (d * 2); + int pcPtr = core::SnesToPc(ptr); + int pcPtrGFX = core::SnesToPc(ptrGFX); + + const int nbr_floors = dungeon_maps_[d].nbr_of_floor; + const int nbr_basements = dungeon_maps_[d].nbr_of_basement; + for (int i = 0; i < nbr_floors + nbr_basements; i++) { + for (int j = 0; j < 25; j++) { + // rom()->data()[pcPtr + j + (i * 25)] = + // dungeon_maps_[d].floor_rooms[i][j]; + // rom()->data()[pcPtrGFX++] = dungeon_maps_[d].floor_gfx[i][j]; + + RETURN_IF_ERROR(rom()->WriteByte(ptr + j + (i * 25), + dungeon_maps_[d].floor_rooms[i][j])); + RETURN_IF_ERROR(rom()->WriteByte(ptrGFX + j + (i * 25), + dungeon_maps_[d].floor_gfx[i][j])); + pcPtrGFX++; + } + } + } + + return absl::OkStatus(); +} + +absl::Status ScreenEditor::LoadDungeonMapTile16() { + tile16_sheet_.Init(256, 192, gfx::TileType::Tile16); + + for (int i = 0; i < 186; i++) { + int addr = zelda3::kDungeonMapTile16; + if (rom()->data()[zelda3::kDungeonMapExpCheck] != 0xB9) { + addr = zelda3::kDungeonMapTile16Expanded; + } + + ASSIGN_OR_RETURN(auto tl, rom()->ReadWord(addr + (i * 8))); + gfx::TileInfo t1 = gfx::WordToTileInfo(tl); // Top left + + ASSIGN_OR_RETURN(auto tr, rom()->ReadWord(addr + 2 + (i * 8))); + gfx::TileInfo t2 = gfx::WordToTileInfo(tr); // Top right + + ASSIGN_OR_RETURN(auto bl, rom()->ReadWord(addr + 4 + (i * 8))); + gfx::TileInfo t3 = gfx::WordToTileInfo(bl); // Bottom left + + ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8))); + gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right + + tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4); + } + + tile16_sheet_.mutable_bitmap()->ApplyPalette( + *rom()->mutable_dungeon_palette(3)); + rom()->RenderBitmap(&*tile16_sheet_.mutable_bitmap().get()); + + return absl::OkStatus(); +} + +void ScreenEditor::DrawDungeonMapsTabs() { + auto current_dungeon = dungeon_maps_[selected_dungeon]; + if (ImGui::BeginTabBar("##DungeonMapTabs")) { + auto nbr_floors = + current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement; + for (int i = 0; i < nbr_floors; i++) { + std::string tab_name = absl::StrFormat("Floor %d", i + 1); + if (i >= current_dungeon.nbr_of_floor) { + tab_name = absl::StrFormat("Basement %d", + i - current_dungeon.nbr_of_floor + 1); + } + + if (ImGui::BeginTabItem(tab_name.c_str())) { + floor_number = i; + // screen_canvas_.LoadCustomLabels(dungeon_map_labels_[selected_dungeon]); + // screen_canvas_.set_current_labels(floor_number); + screen_canvas_.DrawBackground(ImVec2(325, 325)); + screen_canvas_.DrawTileSelector(64.f); + + auto boss_room = current_dungeon.boss_room; + for (int i = 0; i < 25; i++) { + if (current_dungeon.floor_rooms[floor_number][i] != 0x0F) { + int tile16_id = current_dungeon.floor_gfx[floor_number][i]; + int tile_x = (tile16_id % 16) * 16; + int tile_y = (tile16_id / 16) * 16; + int posX = ((i % 5) * 32); + int posY = ((i / 5) * 32); + + if (tile16_individual_.count(tile16_id) == 0) { + auto tile = tile16_sheet_.GetTile16(tile16_id); + rom()->RenderBitmap(&tile); + tile16_individual_[tile16_id] = tile; + } + auto bmp = tile16_individual_[tile16_id]; + screen_canvas_.DrawBitmap(bmp, (posX * 2), (posY * 2), 4.0f); + + if (current_dungeon.floor_rooms[floor_number][i] == boss_room) { + screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64, + 64, core::kRedPen); + } + + std::string label = + dungeon_map_labels_[selected_dungeon][floor_number][i]; + screen_canvas_.DrawText(label, (posX * 2), (posY * 2)); + // GFX.drawText( + // e.Graphics, 16 + ((i % 5) * 32), 20 + ((i / 5) * 32), + } + + // if (dungmapSelectedTile == i) + // Constants.AzurePen2, + // 10 + ((i % 5) * 32), 12 + ((i / 5) * 32), 32, 32)); + } + + screen_canvas_.DrawGrid(64.f, 5); + screen_canvas_.DrawOverlay(); + + if (!screen_canvas_.Points().empty()) { + int x = screen_canvas_.Points().front().x / 64; + int y = screen_canvas_.Points().front().y / 64; + selected_room = x + (y * 5); + } + ImGui::EndTabItem(); + } + } + ImGui::EndTabBar(); + } + + gui::InputHexByte( + "Selected Room", + ¤t_dungeon.floor_rooms[floor_number].at(selected_room)); + + gui::InputHexWord("Boss Room", ¤t_dungeon.boss_room); + + if (ImGui::Button("Copy Floor", ImVec2(100, 0))) { + copy_button_pressed = true; + } + ImGui::SameLine(); + if (ImGui::Button("Paste Floor", ImVec2(100, 0))) { + paste_button_pressed = true; + } +} + +void ScreenEditor::DrawDungeonMapsEditor() { + if (rom()->is_loaded() && !dungeon_maps_loaded_) { + if (LoadDungeonMaps().ok()) { + if (LoadDungeonMapTile16().ok()) { + auto bitmap_manager = rom()->mutable_bitmap_manager(); + sheets_.emplace(0, *bitmap_manager->mutable_bitmap(212)); + sheets_.emplace(1, *bitmap_manager->mutable_bitmap(213)); + sheets_.emplace(2, *bitmap_manager->mutable_bitmap(214)); + sheets_.emplace(3, *bitmap_manager->mutable_bitmap(215)); + dungeon_maps_loaded_ = true; + } else { + throw std::runtime_error("Failed to load dungeon map tile16"); + } + } else { + throw std::runtime_error("Failed to load dungeon maps"); + } + } + + static std::vector dungeon_names = { + "Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace", + "Desert Palace", "Tower of Hera", "Agahnim's Tower", + "Palace of Darkness", "Swamp Palace", "Skull Woods", + "Thieves' Town", "Ice Palace", "Misery Mire", + "Turtle Rock", "Ganon's Tower"}; + + TAB_ITEM("Dungeon Maps") + + if (ImGui::BeginTable("DungeonMapsTable", 4, ImGuiTableFlags_Resizable)) { + ImGui::TableSetupColumn("Dungeon"); + ImGui::TableSetupColumn("Map"); + ImGui::TableSetupColumn("Rooms Gfx"); + ImGui::TableSetupColumn("Tiles Gfx"); + ImGui::TableHeadersRow(); + + // Dungeon column + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().y - 20); + gui::ListBox("##DungeonList", &selected_dungeon, dungeon_names, + dungeon_names.size()); + + // Map column + ImGui::TableNextColumn(); + DrawDungeonMapsTabs(); + + ImGui::TableNextColumn(); + if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) { + tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4)); + tilesheet_canvas_.DrawContextMenu(); + tilesheet_canvas_.DrawTileSelector(32.f); + tilesheet_canvas_.DrawBitmap(*tile16_sheet_.bitmap(), 2, true); + tilesheet_canvas_.DrawGrid(32.f); + tilesheet_canvas_.DrawOverlay(); + + if (!tilesheet_canvas_.Points().empty()) { + selected_tile16_ = tilesheet_canvas_.Points().front().x / 32 + + (tilesheet_canvas_.Points().front().y / 32) * 16; + } + } + ImGui::EndChild(); + + ImGui::TableNextColumn(); + tilemap_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4)); + tilemap_canvas_.DrawContextMenu(); + tilemap_canvas_.DrawBitmapTable(sheets_); + tilemap_canvas_.DrawGrid(); + tilemap_canvas_.DrawOverlay(); + + ImGui::EndTable(); + } + + END_TAB_ITEM() +} + +void ScreenEditor::DrawTitleScreenEditor() { + TAB_ITEM("Title Screen") + END_TAB_ITEM() +} +void ScreenEditor::DrawNamingScreenEditor() { + TAB_ITEM("Naming Screen") + END_TAB_ITEM() +} +void ScreenEditor::DrawOverworldMapEditor() { + TAB_ITEM("Overworld Map") + END_TAB_ITEM() +} + +void ScreenEditor::DrawToolset() { + static bool show_bg1 = true; + static bool show_bg2 = true; + static bool show_bg3 = true; + + static bool drawing_bg1 = true; + static bool drawing_bg2 = false; + static bool drawing_bg3 = false; + + ImGui::Checkbox("Show BG1", &show_bg1); + ImGui::SameLine(); + ImGui::Checkbox("Show BG2", &show_bg2); + + ImGui::Checkbox("Draw BG1", &drawing_bg1); + ImGui::SameLine(); + ImGui::Checkbox("Draw BG2", &drawing_bg2); + ImGui::SameLine(); + ImGui::Checkbox("Draw BG3", &drawing_bg3); +} + } // namespace editor } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/editor/screen_editor.h b/src/app/editor/screen_editor.h index a70c566c..a46f8fdf 100644 --- a/src/app/editor/screen_editor.h +++ b/src/app/editor/screen_editor.h @@ -5,14 +5,17 @@ #include +#include "absl/status/status.h" #include "app/core/constants.h" #include "app/gfx/bitmap.h" #include "app/gfx/snes_palette.h" #include "app/gfx/snes_tile.h" +#include "app/gfx/tilesheet.h" #include "app/gui/canvas.h" #include "app/gui/color.h" #include "app/gui/icons.h" #include "app/rom.h" +#include "app/zelda3/screen/dungeon_map.h" #include "app/zelda3/screen/inventory.h" namespace yaze { @@ -24,21 +27,48 @@ class ScreenEditor : public SharedROM { ScreenEditor(); void Update(); + absl::Status SaveDungeonMaps(); + private: void DrawTitleScreenEditor(); void DrawNamingScreenEditor(); void DrawOverworldMapEditor(); - void DrawDungeonMapsEditor(); - void DrawInventoryMenuEditor(); + void DrawInventoryMenuEditor(); void DrawToolset(); void DrawInventoryToolset(); + absl::Status LoadDungeonMaps(); + absl::Status LoadDungeonMapTile16(); + void DrawDungeonMapsTabs(); + void DrawDungeonMapsEditor(); + + std::vector dungeon_maps_; + std::vector>> dungeon_map_labels_; + + std::unordered_map tile16_individual_; + + bool dungeon_maps_loaded_ = false; + + int selected_tile16_ = 0; + int selected_dungeon = 0; + uint8_t selected_room = 0; + uint8_t boss_room = 0; + int floor_number = 1; + + bool copy_button_pressed = false; + bool paste_button_pressed = false; + Bytes all_gfx_; zelda3::Inventory inventory_; gfx::SNESPalette palette_; gui::Canvas screen_canvas_; gui::Canvas tilesheet_canvas_; + gui::Canvas tilemap_canvas_; + + gfx::BitmapTable sheets_; + + gfx::Tilesheet tile16_sheet_; }; } // namespace editor diff --git a/src/app/gfx/bitmap.h b/src/app/gfx/bitmap.h index 17232f2a..2d80519c 100644 --- a/src/app/gfx/bitmap.h +++ b/src/app/gfx/bitmap.h @@ -81,6 +81,41 @@ class Bitmap { } } + void Get16x16Tile(int tile_index, int x, int y, + std::vector &tile_data, int &tile_data_offset) { + int tile_offset = tile_index * (width_ * height_); + int tile_x = x * 16; + int tile_y = y * 16; + for (int i = 0; i < 16; i++) { + int row_offset = tile_offset + ((i / 8) * (width_ * 8)); + for (int j = 0; j < 16; j++) { + int pixel_offset = + row_offset + ((j / 8) * 8) + ((i % 8) * width_) + (j % 8); + int pixel_value = data_[pixel_offset]; + tile_data[tile_data_offset] = pixel_value; + tile_data_offset++; + } + } + } + + void Get16x16Tile(int tile_x, int tile_y, std::vector &tile_data, + int &tile_data_offset) { + // Assuming 'width_' and 'height_' are the dimensions of the bitmap + // and 'data_' is the bitmap data. + for (int ty = 0; ty < 16; ty++) { + for (int tx = 0; tx < 16; tx++) { + // Calculate the pixel position in the bitmap + int pixel_x = tile_x + tx; + int pixel_y = tile_y + ty; + int pixel_offset = pixel_y * width_ + pixel_x; + int pixel_value = data_[pixel_offset]; + + // Store the pixel value in the tile data + tile_data[tile_data_offset++] = pixel_value; + } + } + } + void WriteColor(int position, const ImVec4 &color) { // Convert ImVec4 (RGBA) to SDL_Color (RGBA) SDL_Color sdl_color; diff --git a/src/app/gfx/tilesheet.cc b/src/app/gfx/tilesheet.cc new file mode 100644 index 00000000..e69de29b diff --git a/src/app/gfx/tilesheet.h b/src/app/gfx/tilesheet.h new file mode 100644 index 00000000..9fa5b15f --- /dev/null +++ b/src/app/gfx/tilesheet.h @@ -0,0 +1,237 @@ +#ifndef YAZE_APP_GFX_TILESHEET_H +#define YAZE_APP_GFX_TILESHEET_H +#include +#include + +#include "app/gfx/bitmap.h" +#include "app/gfx/snes_palette.h" + +namespace yaze { +namespace app { +namespace gfx { + +enum class TileType { Tile8, Tile16 }; + +class Tilesheet { + public: + Tilesheet() = default; + Tilesheet(std::shared_ptr bitmap, int tileWidth, int tileHeight, + TileType tile_type) + : bitmap_(std::move(bitmap)), + tile_width_(tileWidth), + tile_height_(tileHeight), + tile_type_(tile_type) {} + + void Init(int width, int height, TileType tile_type) { + bitmap_ = std::make_shared(width, height, 8, 0x20000); + internal_data_.resize(0x20000); + tile_type_ = tile_type; + if (tile_type_ == TileType::Tile8) { + tile_width_ = 8; + tile_height_ = 8; + } else { + tile_width_ = 16; + tile_height_ = 16; + } + } + + void ComposeTile16(const std::vector& graphics_buffer, + const TileInfo& top_left, const TileInfo& top_right, + const TileInfo& bottom_left, + const TileInfo& bottom_right) { + // Calculate the base position for this Tile16 in the full-size bitmap + int tiles_per_row = bitmap_->width() / tile_width_; + int tile16_row = num_tiles_ / tiles_per_row; + int tile16_column = num_tiles_ % tiles_per_row; + int baseX = tile16_column * tile_width_; + int baseY = tile16_row * tile_height_; + + // Compose and place each part of the Tile16 + ComposeAndPlaceTilePart(graphics_buffer, top_left, baseX, baseY); + ComposeAndPlaceTilePart(graphics_buffer, top_right, baseX + 8, baseY); + ComposeAndPlaceTilePart(graphics_buffer, bottom_left, baseX, baseY + 8); + ComposeAndPlaceTilePart(graphics_buffer, bottom_right, baseX + 8, + baseY + 8); + + tile_info_.push_back({top_left, top_right, bottom_left, bottom_right}); + + num_tiles_++; + } + + void ComposeAndPlaceTilePart(const std::vector& graphics_buffer, + const TileInfo& tile_info, int baseX, + int baseY) { + std::vector tile_data = + FetchTileDataFromGraphicsBuffer(graphics_buffer, tile_info.id_); + + if (tile_info.vertical_mirror_) { + MirrorTileDataVertically(tile_data); + } + if (tile_info.horizontal_mirror_) { + MirrorTileDataHorizontally(tile_data); + } + + // Place the tile data into the full-size bitmap at the calculated position + for (int y = 0; y < 8; ++y) { + for (int x = 0; x < 8; ++x) { + int srcIndex = y * 8 + x; + int destX = baseX + x; + int destY = baseY + y; + int destIndex = (destY * bitmap_->width()) + destX; + internal_data_[destIndex] = tile_data[srcIndex]; + } + } + + bitmap_->set_data(internal_data_); + } + + // Extracts a tile from the tilesheet + Bitmap GetTile(int tileX, int tileY, int bmp_width, int bmp_height) { + std::vector tileData(tile_width_ * tile_height_); + int tileDataOffset = 0; + bitmap_->Get8x8Tile(CalculateTileIndex(tileX, tileY), tileX, tileY, + tileData, tileDataOffset); + return Bitmap(bmp_width, bmp_height, bitmap_->depth(), tileData); + } + + Bitmap GetTile16(int tile_x, int tile_y) { + std::vector tile_data(tile_width_ * tile_height_, 0x00); + int tileDataOffset = 0; + bitmap_->Get16x16Tile(tile_x, tile_y, tile_data, tileDataOffset); + return Bitmap(16, 16, bitmap_->depth(), tile_data); + } + + Bitmap GetTile16(int tile_id) { + int tiles_per_row = bitmap_->width() / tile_width_; + int tile_x = (tile_id % tiles_per_row) * tile_width_; + int tile_y = (tile_id / tiles_per_row) * tile_height_; + return GetTile16(tile_x, tile_y); + } + + // Copy a tile within the tilesheet + void CopyTile(int srcX, int srcY, int destX, int destY, bool mirrorX = false, + bool mirrorY = false) { + auto srcTile = GetTile(srcX, srcY, tile_width_, tile_height_); + auto destTileData = srcTile.vector(); + MirrorTileData(destTileData, mirrorX, mirrorY); + WriteTile(destX, destY, destTileData); + } + + // Other methods and properties + auto bitmap() const { return bitmap_; } + auto mutable_bitmap() { return bitmap_; } + auto num_tiles() const { return num_tiles_; } + auto tile_width() const { return tile_width_; } + auto tile_height() const { return tile_height_; } + auto set_palette(gfx::SNESPalette& palette) { palette_ = palette; } + auto palette() const { return palette_; } + auto tile_type() const { return tile_type_; } + auto tile_info() const { return tile_info_; } + auto mutable_tile_info() { return tile_info_; } + + private: + int CalculateTileIndex(int x, int y) { + return y * (bitmap_->width() / tile_width_) + x; + } + + std::vector FetchTileDataFromGraphicsBuffer( + const std::vector& graphics_buffer, int tile_id) { + const int tileWidth = 8; + const int tileHeight = 8; + const int bufferWidth = 128; + const int sheetHeight = 32; + + const int tilesPerRow = bufferWidth / tileWidth; + const int rowsPerSheet = sheetHeight / tileHeight; + const int tilesPerSheet = tilesPerRow * rowsPerSheet; + + // Calculate the position in the graphics_buffer_ based on tile_id + std::vector tile_data(0x40, 0x00); + int sheet = (tile_id / tilesPerSheet) % 4 + 212; + int positionInSheet = tile_id % tilesPerSheet; + int rowInSheet = positionInSheet / tilesPerRow; + int columnInSheet = positionInSheet % tilesPerRow; + + // Ensure that the sheet ID is between 212 and 215 + assert(sheet >= 212 && sheet <= 215); + + // Copy the tile data from the graphics_buffer_ to tile_data + for (int y = 0; y < 8; ++y) { + for (int x = 0; x < 8; ++x) { + // Calculate the position in the graphics_buffer_ based on tile_id + + int srcX = columnInSheet * tileWidth + x; + int srcY = (sheet * sheetHeight) + (rowInSheet * tileHeight) + y; + + int src_index = (srcY * bufferWidth) + srcX; + int dest_index = y * tileWidth + x; + + tile_data[dest_index] = graphics_buffer[src_index]; + } + } + + return tile_data; + } + + void MirrorTileDataVertically(std::vector& tileData) { + std::vector tile_data_copy = tileData; + for (int i = 0; i < 8; ++i) { // For each row + for (int j = 0; j < 8; ++j) { // For each column + int src_index = i * 8 + j; + int dest_index = (7 - i) * 8 + j; // Calculate the mirrored row + tile_data_copy[dest_index] = tileData[src_index]; + } + } + tileData = tile_data_copy; + } + + void MirrorTileDataHorizontally(std::vector& tileData) { + std::vector tile_data_copy = tileData; + for (int i = 0; i < 8; ++i) { // For each row + for (int j = 0; j < 8; ++j) { // For each column + int src_index = i * 8 + j; + int dest_index = i * 8 + (7 - j); // Calculate the mirrored column + tile_data_copy[dest_index] = tileData[src_index]; + } + } + tileData = tile_data_copy; + } + + void MirrorTileData(std::vector& tileData, bool mirrorX, + bool mirrorY) { + // Implement logic to mirror tile data horizontally and/or vertically + std::vector tile_data_copy = tileData; + if (mirrorX) { + MirrorTileDataHorizontally(tile_data_copy); + } + if (mirrorY) { + MirrorTileDataVertically(tile_data_copy); + } + tileData = tile_data_copy; + } + + void WriteTile(int x, int y, const std::vector& tileData) { + int tileDataOffset = 0; + bitmap_->Get8x8Tile(CalculateTileIndex(x, y), x, y, + const_cast&>(tileData), + tileDataOffset); + } + + gfx::SNESPalette palette_; + std::vector internal_data_; + std::shared_ptr bitmap_; + struct InternalTile16 { + std::array tiles; + }; + std::vector tile_info_; + int num_tiles_ = 0; + int tile_width_ = 0; + int tile_height_ = 0; + TileType tile_type_; +}; + +} // namespace gfx +} // namespace app +} // namespace yaze + +#endif // YAZE_APP_GFX_TILESHEET_H \ No newline at end of file diff --git a/src/app/gui/canvas.cc b/src/app/gui/canvas.cc index bd927063..d037c41c 100644 --- a/src/app/gui/canvas.cc +++ b/src/app/gui/canvas.cc @@ -63,7 +63,7 @@ void Canvas::UpdateInfoGrid(ImVec2 bg_size, int tile_size, float scale, DrawOverlay(); } -void Canvas::DrawBackground(ImVec2 canvas_size) { +void Canvas::DrawBackground(ImVec2 canvas_size, bool can_drag) { canvas_p0_ = ImGui::GetCursorScreenPos(); if (!custom_canvas_size_) canvas_sz_ = ImGui::GetContentRegionAvail(); if (canvas_size.x != 0) canvas_sz_ = canvas_size; @@ -72,26 +72,37 @@ void Canvas::DrawBackground(ImVec2 canvas_size) { draw_list_ = ImGui::GetWindowDrawList(); // Draw border and background color draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor); draw_list_->AddRect(canvas_p0_, canvas_p1_, kRectangleBorder); + + const ImGuiIO &io = ImGui::GetIO(); + auto scaled_sz = + ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_); + ImGui::InvisibleButton("canvas", scaled_sz, kMouseFlags); + + if (can_drag) { + const bool is_active = ImGui::IsItemActive(); // Held + const ImVec2 origin(canvas_p0_.x + scrolling_.x, + canvas_p0_.y + scrolling_.y); // Lock scrolled origin + const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); + + // Pan (we use a zero mouse threshold when there's no context menu) + if (const float mouse_threshold_for_pan = + enable_context_menu_ ? -1.0f : 0.0f; + is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, + mouse_threshold_for_pan)) { + scrolling_.x += io.MouseDelta.x; + scrolling_.y += io.MouseDelta.y; + } + } } void Canvas::DrawContextMenu() { const ImGuiIO &io = ImGui::GetIO(); auto scaled_sz = ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_); - ImGui::InvisibleButton("canvas", scaled_sz, kMouseFlags); - const bool is_active = ImGui::IsItemActive(); // Held const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); // Lock scrolled origin const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); - // Pan (we use a zero mouse threshold when there's no context menu) - if (const float mouse_threshold_for_pan = enable_context_menu_ ? -1.0f : 0.0f; - is_active && - ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) { - scrolling_.x += io.MouseDelta.x; - scrolling_.y += io.MouseDelta.y; - } - // Context menu (under default mouse threshold) if (ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right); enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f) @@ -117,18 +128,22 @@ void Canvas::DrawContextMenu() { ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::MenuItem("8x8", nullptr, custom_step_ == 8.0f)) { - custom_step_ = 8.0f; - } - if (ImGui::MenuItem("16x16", nullptr, custom_step_ == 16.0f)) { - custom_step_ = 16.0f; - } - if (ImGui::MenuItem("32x32", nullptr, custom_step_ == 32.0f)) { - custom_step_ = 32.0f; - } - if (ImGui::MenuItem("64x64", nullptr, custom_step_ == 64.0f)) { - custom_step_ = 64.0f; + if (ImGui::BeginMenu("Grid Tile Size")) { + if (ImGui::MenuItem("8x8", nullptr, custom_step_ == 8.0f)) { + custom_step_ = 8.0f; + } + if (ImGui::MenuItem("16x16", nullptr, custom_step_ == 16.0f)) { + custom_step_ = 16.0f; + } + if (ImGui::MenuItem("32x32", nullptr, custom_step_ == 32.0f)) { + custom_step_ = 32.0f; + } + if (ImGui::MenuItem("64x64", nullptr, custom_step_ == 64.0f)) { + custom_step_ = 64.0f; + } + ImGui::EndMenu(); } + ImGui::EndPopup(); } } @@ -374,6 +389,23 @@ void Canvas::DrawOutline(int x, int y, int w, int h) { draw_list_->AddRect(origin, size, IM_COL32(255, 255, 255, 255)); } +void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) { + ImVec2 origin(canvas_p0_.x + scrolling_.x + x, + canvas_p0_.y + scrolling_.y + y); + ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, + canvas_p0_.y + scrolling_.y + y + h); + draw_list_->AddRect(origin, size, + IM_COL32(color.x, color.y, color.z, color.w)); +} + +void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) { + ImVec2 origin(canvas_p0_.x + scrolling_.x + x, + canvas_p0_.y + scrolling_.y + y); + ImVec2 size(canvas_p0_.x + scrolling_.x + x + w, + canvas_p0_.y + scrolling_.y + y + h); + draw_list_->AddRect(origin, size, color); +} + void Canvas::DrawSelectRect(int tile_size, float scale) { const ImGuiIO &io = ImGui::GetIO(); static ImVec2 drag_start_pos; @@ -445,23 +477,27 @@ void Canvas::DrawText(std::string text, int x, int y) { IM_COL32(255, 255, 255, 255), text.data()); } -void Canvas::DrawGrid(float grid_step) { +void Canvas::DrawGridLines(float grid_step) { + for (float x = fmodf(scrolling_.x, grid_step); + x < canvas_sz_.x * global_scale_; x += grid_step) + draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y), + ImVec2(canvas_p0_.x + x, canvas_p1_.y), + IM_COL32(200, 200, 200, 50), 0.5f); + for (float y = fmodf(scrolling_.y, grid_step); + y < canvas_sz_.y * global_scale_; y += grid_step) + draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y), + ImVec2(canvas_p1_.x, canvas_p0_.y + y), + IM_COL32(200, 200, 200, 50), 0.5f); +} + +void Canvas::DrawGrid(float grid_step, int tile_id_offset) { // Draw grid + all lines in the canvas draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true); if (enable_grid_) { if (custom_step_ != 0.f) grid_step = custom_step_; - grid_step *= global_scale_; // Apply global scale to grid step - for (float x = fmodf(scrolling_.x, grid_step); - x < canvas_sz_.x * global_scale_; x += grid_step) - draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y), - ImVec2(canvas_p0_.x + x, canvas_p1_.y), - IM_COL32(200, 200, 200, 50), 0.5f); - for (float y = fmodf(scrolling_.y, grid_step); - y < canvas_sz_.y * global_scale_; y += grid_step) - draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y), - ImVec2(canvas_p1_.x, canvas_p0_.y + y), - IM_COL32(200, 200, 200, 50), 0.5f); + + DrawGridLines(grid_step); if (highlight_tile_id != -1) { int tile_x = highlight_tile_id % 8; @@ -499,15 +535,16 @@ void Canvas::DrawGrid(float grid_step) { y < canvas_sz_.y * global_scale_; y += grid_step) { int tile_x = (x - scrolling_.x) / grid_step; int tile_y = (y - scrolling_.y) / grid_step; - int tile_id = tile_x + (tile_y * 8); + int tile_id = tile_x + (tile_y * tile_id_offset); if (tile_id >= labels_[current_labels_].size()) { break; } std::string label = labels_[current_labels_][tile_id]; - draw_list_->AddText(ImVec2(canvas_p0_.x + x + (grid_step / 2) - 8, - canvas_p0_.y + y + (grid_step / 2) - 8), - IM_COL32(255, 255, 255, 255), label.data()); + draw_list_->AddText( + ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset, + canvas_p0_.y + y + (grid_step / 2) - tile_id_offset), + IM_COL32(255, 255, 255, 255), label.data()); } } } diff --git a/src/app/gui/canvas.h b/src/app/gui/canvas.h index 3e181069..51757d26 100644 --- a/src/app/gui/canvas.h +++ b/src/app/gui/canvas.h @@ -38,7 +38,7 @@ class Canvas { // Background for the Canvas represents region without any content drawn to // it, but can be controlled by the user. - void DrawBackground(ImVec2 canvas_size = ImVec2(0, 0)); + void DrawBackground(ImVec2 canvas_size = ImVec2(0, 0), bool drag = false); // Context Menu refers to what happens when the right mouse button is pressed // This routine also handles the scrolling for the canvas. @@ -75,10 +75,14 @@ class Canvas { void DrawBitmapTable(const BitmapTable& gfx_bin); void DrawOutline(int x, int y, int w, int h); + void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color); + void DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color); void DrawSelectRect(int tile_size, float scale = 1.0f); void DrawRect(int x, int y, int w, int h, ImVec4 color); void DrawText(std::string text, int x, int y); - void DrawGrid(float grid_step = 64.0f); + void DrawGridLines(float grid_step); + void DrawGrid(float grid_step = 64.0f, int tile_id_offset = 8); + void DrawOverlay(); // last auto Points() const { return points_; } @@ -97,6 +101,21 @@ class Canvas { void set_global_scale(float scale) { global_scale_ = scale; } auto global_scale() const { return global_scale_; } + auto custom_labels_enabled() const { return enable_custom_labels_; } + + void LoadCustomLabels( + std::vector> custom_labels) { + // Initialize labels + for (int i = 0; i < custom_labels.size(); i++) { + labels_.push_back(ImVector()); + } + for (int i = 0; i < custom_labels.size(); i++) { + for (int j = 0; j < custom_labels[i].size(); j++) { + labels_[i].push_back(custom_labels[i][j]); + } + } + enable_custom_labels_ = true; + } auto labels(int i) { if (i >= labels_.size()) { diff --git a/src/app/gui/input.cc b/src/app/gui/input.cc index 7c2dd82b..5be5d6e5 100644 --- a/src/app/gui/input.cc +++ b/src/app/gui/input.cc @@ -2,6 +2,7 @@ #include #include +#include #include "absl/strings/string_view.h" @@ -176,6 +177,18 @@ void ItemLabel(absl::string_view title, ItemLabelFlags flags) { ImGui::SetCursorScreenPos(lineStart); } +bool ListBox(const char* label, int* current_item, + const std::vector& items, int height_in_items) { + std::vector items_ptr; + items_ptr.reserve(items.size()); + for (const auto& item : items) { + items_ptr.push_back(item.c_str()); + } + int items_count = static_cast(items.size()); + return ImGui::ListBox(label, current_item, items_ptr.data(), items_count, + height_in_items); +} + } // namespace gui } // namespace app } // namespace yaze \ No newline at end of file diff --git a/src/app/gui/input.h b/src/app/gui/input.h index 1542a702..6761c812 100644 --- a/src/app/gui/input.h +++ b/src/app/gui/input.h @@ -5,6 +5,7 @@ #include #include +#include #include "absl/strings/string_view.h" @@ -19,8 +20,12 @@ IMGUI_API bool InputHex(const char* label, uint64_t* data); IMGUI_API bool InputHexShort(const char* label, uint32_t* data); IMGUI_API bool InputHexWord(const char* label, uint16_t* data, float input_width = 50.f); -IMGUI_API bool InputHexByte(const char* label, uint8_t* data, uint8_t step = 0x01, - float input_width = 50.f); +IMGUI_API bool InputHexByte(const char* label, uint8_t* data, + uint8_t step = 0x01, float input_width = 50.f); + +IMGUI_API bool ListBox(const char* label, int* current_item, + const std::vector& items, + int height_in_items = -1); using ItemLabelFlags = enum ItemLabelFlag { Left = 1u << 0u, diff --git a/src/app/rom.h b/src/app/rom.h index aadfb156..b2788562 100644 --- a/src/app/rom.h +++ b/src/app/rom.h @@ -418,7 +418,12 @@ class ROM : public core::ExperimentFlags { auto mutable_palette_group(const std::string& group) { return &palette_groups_[group]; } + auto dungeon_palette(int i) { return palette_groups_["dungeon_main"][i]; } + auto mutable_dungeon_palette(int i) { + return palette_groups_["dungeon_main"].mutable_palette(i); + } + // Full graphical data for the game Bytes graphics_buffer() const { return graphics_buffer_; } gfx::BitmapTable graphics_bin() const { return graphics_bin_; }