feat(editor): implement overworld map editing features in ScreenEditor

- Added functionality for managing the overworld map, including loading, rendering, and saving map data for both Light and Dark Worlds.
- Introduced new canvas components for overworld map editing, allowing users to select and paint tiles directly onto the map.
- Enhanced the ScreenEditor with controls for tile flipping and palette selection, improving the user interface for overworld map management.

Benefits:
- Expands the capabilities of the ScreenEditor, providing users with tools to edit and manage the overworld map effectively.
- Improves user experience by enabling intuitive tile editing and visual feedback during map modifications.
This commit is contained in:
scawful
2025-10-13 16:51:55 -04:00
parent 965bb0946b
commit 1314d9daf9
7 changed files with 760 additions and 36 deletions

View File

@@ -3,7 +3,6 @@
#include <cstdint>
#include "app/gfx/core/bitmap.h"
#include "app/gfx/render/tilemap.h"
#include "app/gfx/resource/arena.h"
#include "app/rom.h"
#include "app/snes.h"
@@ -21,14 +20,45 @@ absl::Status TitleScreen::Create(Rom* rom) {
tiles_bg1_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
tiles_bg2_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
oam_bg_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
// Set metadata for title screen bitmaps
// Title screen uses 3BPP graphics with 8 palettes of 8 colors (64 total)
tiles8_bitmap_.metadata().source_bpp = 3;
tiles8_bitmap_.metadata().palette_format = 0; // Full 64-color palette
tiles8_bitmap_.metadata().source_type = "graphics_sheet";
tiles8_bitmap_.metadata().palette_colors = 64;
tiles_bg1_bitmap_.metadata().source_bpp = 3;
tiles_bg1_bitmap_.metadata().palette_format = 0; // Uses full palette with sub-palette indexing
tiles_bg1_bitmap_.metadata().source_type = "screen_buffer";
tiles_bg1_bitmap_.metadata().palette_colors = 64;
tiles_bg2_bitmap_.metadata().source_bpp = 3;
tiles_bg2_bitmap_.metadata().palette_format = 0;
tiles_bg2_bitmap_.metadata().source_type = "screen_buffer";
tiles_bg2_bitmap_.metadata().palette_colors = 64;
oam_bg_bitmap_.metadata().source_bpp = 3;
oam_bg_bitmap_.metadata().palette_format = 0;
oam_bg_bitmap_.metadata().source_type = "screen_buffer";
oam_bg_bitmap_.metadata().palette_colors = 64;
// Initialize tilemap buffers
tiles_bg1_buffer_.fill(0x492); // Default empty tile
tiles_bg2_buffer_.fill(0x492);
// Load palette (title screen uses sprite graphics)
// Load palette (title screen uses 3BPP graphics with 8 palettes of 8 colors each)
// Build a full 64-color palette from sprite palettes
auto sprite_pal_group = rom->palette_group().sprites_aux1;
palette_ = sprite_pal_group[0];
// Title screen needs 8 palettes (64 colors total for 3BPP mode)
// Each palette in sprites_aux1 has 8 colors (7 actual + 1 transparent)
for (int pal = 0; pal < 8 && pal < sprite_pal_group.size(); pal++) {
auto sub_palette = sprite_pal_group[pal];
for (int col = 0; col < sub_palette.size(); col++) {
palette_.AddColor(sub_palette[col]);
}
}
// Build tile16 blockset from graphics
RETURN_IF_ERROR(BuildTileset(rom));
@@ -179,10 +209,15 @@ absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
RETURN_IF_ERROR(RenderBG1Layer());
RETURN_IF_ERROR(RenderBG2Layer());
// Apply palettes to layer bitmaps
// Apply palettes to layer bitmaps AFTER rendering
tiles_bg1_bitmap_.SetPalette(palette_);
tiles_bg2_bitmap_.SetPalette(palette_);
oam_bg_bitmap_.SetPalette(palette_);
// Ensure bitmaps are marked as active
tiles_bg1_bitmap_.set_active(true);
tiles_bg2_bitmap_.set_active(true);
oam_bg_bitmap_.set_active(true);
// Queue texture creation for all layer bitmaps
gfx::Arena::Get().QueueTextureCommand(
@@ -302,9 +337,93 @@ absl::Status TitleScreen::RenderBG2Layer() {
}
absl::Status TitleScreen::Save(Rom* rom) {
// TODO: Implement saving title screen tilemap back to ROM
// This would involve compressing the tilemap data and writing it back
return absl::UnimplementedError("Title screen saving not yet implemented");
if (!rom || !rom->is_loaded()) {
return absl::InvalidArgumentError("ROM is not loaded");
}
// Title screen uses compressed tilemap format
// We'll write the data back in the same compressed format
std::vector<uint8_t> compressed_data;
// Helper to write word (little endian)
auto WriteWord = [&compressed_data](uint16_t value) {
compressed_data.push_back(value & 0xFF);
compressed_data.push_back((value >> 8) & 0xFF);
};
// Compress BG2 layer (dest < 0x1000)
uint16_t bg2_dest = 0x0000;
for (int i = 0; i < 1024; i++) {
if (i == 0 || tiles_bg2_buffer_[i] != tiles_bg2_buffer_[i - 1]) {
// Start a new run
WriteWord(bg2_dest + i); // Destination address
// Count consecutive identical tiles
int run_length = 1;
uint16_t tile_value = tiles_bg2_buffer_[i];
while (i + run_length < 1024 && tiles_bg2_buffer_[i + run_length] == tile_value) {
run_length++;
}
// Write length/flags (bit 14 = fixsource if run > 1)
uint16_t length_flags = (run_length - 1) * 2; // Length in bytes
if (run_length > 1) {
length_flags |= 0x4000; // fixsource flag
}
WriteWord(length_flags);
// Write tile data
WriteWord(tile_value);
i += run_length - 1; // Skip already processed tiles
}
}
// Compress BG1 layer (dest >= 0x1000)
uint16_t bg1_dest = 0x1000;
for (int i = 0; i < 1024; i++) {
if (i == 0 || tiles_bg1_buffer_[i] != tiles_bg1_buffer_[i - 1]) {
// Start a new run
WriteWord(bg1_dest + i); // Destination address
// Count consecutive identical tiles
int run_length = 1;
uint16_t tile_value = tiles_bg1_buffer_[i];
while (i + run_length < 1024 && tiles_bg1_buffer_[i + run_length] == tile_value) {
run_length++;
}
// Write length/flags (bit 14 = fixsource if run > 1)
uint16_t length_flags = (run_length - 1) * 2; // Length in bytes
if (run_length > 1) {
length_flags |= 0x4000; // fixsource flag
}
WriteWord(length_flags);
// Write tile data
WriteWord(tile_value);
i += run_length - 1; // Skip already processed tiles
}
}
// Write terminator byte
compressed_data.push_back(0x80);
// Calculate ROM address to write to
ASSIGN_OR_RETURN(uint8_t byte0, rom->ReadByte(0x137A + 3));
ASSIGN_OR_RETURN(uint8_t byte1, rom->ReadByte(0x1383 + 3));
ASSIGN_OR_RETURN(uint8_t byte2, rom->ReadByte(0x138C + 3));
int pos = (byte2 << 16) + (byte1 << 8) + byte0;
int write_pos = SnesToPc(pos);
// Write compressed data to ROM
for (size_t i = 0; i < compressed_data.size(); i++) {
RETURN_IF_ERROR(rom->WriteByte(write_pos + i, compressed_data[i]));
}
return absl::OkStatus();
}
} // namespace zelda3