7#include "absl/strings/str_format.h"
82 uint8_t tile,
const std::vector<uint16_t>& track_tiles,
83 const std::vector<uint16_t>& switch_tiles) {
84 auto track_it = std::find(track_tiles.begin(), track_tiles.end(), tile);
85 if (track_it != track_tiles.end()) {
87 static_cast<size_t>(track_it - track_tiles.begin()));
90 auto switch_it = std::find(switch_tiles.begin(), switch_tiles.end(), tile);
91 if (switch_it != switch_tiles.end()) {
93 static_cast<size_t>(switch_it - switch_tiles.begin()));
106 a = ImVec2(tip.x - size, tip.y + size);
107 b = ImVec2(tip.x + size, tip.y + size);
110 a = ImVec2(tip.x - size, tip.y - size);
111 b = ImVec2(tip.x - size, tip.y + size);
114 a = ImVec2(tip.x - size, tip.y - size);
115 b = ImVec2(tip.x + size, tip.y - size);
118 a = ImVec2(tip.x + size, tip.y - size);
119 b = ImVec2(tip.x + size, tip.y + size);
122 draw_list->AddTriangleFilled(tip, a, b, color);
133 const ImVec2 center(min.x + tile_size * 0.5f, min.y + tile_size * 0.5f);
134 const float line_len = tile_size * 0.32f;
135 const float head_size = std::max(2.0f, tile_size * 0.18f);
136 const float thickness = std::max(1.0f, tile_size * 0.08f);
138 auto draw_dir = [&](
TrackDir dir,
float dx,
float dy) {
139 ImVec2 tip(center.x + dx * line_len, center.y + dy * line_len);
140 draw_list->AddLine(center, tip, color, thickness);
159 int room_x,
int room_y) {
161 return {room_x * 8, room_y * 8};
165 ImDrawList* draw_list,
const std::vector<uint8_t>& preview,
166 const SDL_Rect& bounds,
const ImVec2& box_min,
const ImVec2& box_max,
167 const std::array<SDL_Color, 256>& color_table, uint8_t transparent_index) {
168 if (!draw_list || bounds.w <= 0 || bounds.h <= 0) {
171 const size_t row_stride =
static_cast<size_t>(bounds.w);
172 if (
static_cast<size_t>(bounds.h) > preview.size() / row_stride) {
176 const float avail_w = box_max.x - box_min.x;
177 const float avail_h = box_max.y - box_min.y;
178 if (avail_w <= 0.0f || avail_h <= 0.0f) {
182 const float scale_x = avail_w /
static_cast<float>(bounds.w);
183 const float scale_y = avail_h /
static_cast<float>(bounds.h);
184 const float scale = std::min(scale_x, scale_y);
186 const float draw_w = bounds.w * scale;
187 const float draw_h = bounds.h * scale;
188 const float origin_x = box_min.x + (avail_w - draw_w) * 0.5f;
189 const float origin_y = box_min.y + (avail_h - draw_h) * 0.5f;
191 for (
int y = 0; y < bounds.h; ++y) {
193 while (x < bounds.w) {
194 const uint8_t palette_index =
195 preview[
static_cast<size_t>(y) * row_stride + x];
196 const SDL_Color& palette_color = color_table[palette_index];
197 if (palette_index == transparent_index || palette_color.a == 0) {
201 const ImU32 color = IM_COL32(palette_color.r, palette_color.g,
202 palette_color.b, palette_color.a);
204 const int start_x = x;
205 while (x < bounds.w && preview[
static_cast<size_t>(y) * row_stride + x] ==
210 const ImVec2 p_min(origin_x + start_x * scale, origin_y + y * scale);
211 const ImVec2 p_max(origin_x + x * scale, origin_y + (y + 1) * scale);
212 draw_list->AddRectFilled(p_min, p_max, color);
219 uint8_t item_id, uint8_t* out_sprite_id) {
221 uint8_t sprite_id = 0;
270 if (out_sprite_id !=
nullptr) {
271 *out_sprite_id = sprite_id;
277 uint8_t item_id,
const zelda3::Room& room, std::vector<uint8_t>& out_pixels,
278 SDL_Rect& out_bounds) {
279 uint8_t sprite_id = 0;
287 const std::span<const uint8_t> room_gfx_span(room_gfx.data(),
300 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
302 bool direction_map_enabled,
const std::vector<uint16_t>& track_tile_order,
303 const std::vector<uint16_t>& switch_tile_order,
bool show_legend) {
305 const ImU32 track_color = ImGui::GetColorU32(ImVec4(0.2f, 0.8f, 0.4f, 0.35f));
306 const ImU32 stop_color = ImGui::GetColorU32(ImVec4(0.9f, 0.3f, 0.2f, 0.45f));
307 const ImU32 switch_color =
308 ImGui::GetColorU32(ImVec4(0.95f, 0.8f, 0.2f, 0.45f));
309 const ImU32 outline_color = ImGui::GetColorU32(ImVec4(0, 0, 0, 0.4f));
310 const ImU32 arrow_color =
311 ImGui::GetColorU32(ImVec4(0.05f, 0.05f, 0.05f, 0.75f));
312 const ImU32 arrow_color_dim =
313 ImGui::GetColorU32(ImVec4(0.05f, 0.05f, 0.05f, 0.4f));
315 for (
const auto& entry : cache.
entries) {
316 const float px =
static_cast<float>(entry.x * 8) * scale;
317 const float py =
static_cast<float>(entry.y * 8) * scale;
318 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
319 const float tile_size = 8.0f * scale;
320 ImVec2 max(min.x + tile_size, min.y + tile_size);
322 ImU32 color = track_color;
326 color = switch_color;
329 draw_list->AddRectFilled(min, max, color);
330 draw_list->AddRect(min, max, outline_color);
333 const char* dir_label =
nullptr;
334 if (entry.tile == 0xB7)
336 else if (entry.tile == 0xB8)
338 else if (entry.tile == 0xB9)
340 else if (entry.tile == 0xBA)
344 const ImU32 label_color =
345 ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 0.9f));
346 ImVec2 text_size = ImGui::CalcTextSize(dir_label);
347 ImVec2 text_pos(min.x + (tile_size - text_size.x) * 0.5f,
348 min.y + (tile_size - text_size.y) * 0.5f);
349 draw_list->AddText(text_pos, label_color, dir_label);
353 if (direction_map_enabled) {
355 entry.tile, track_tile_order, switch_tile_order);
356 if (masks.primary != 0) {
359 if (masks.secondary != 0) {
368 ImVec2 legend_pos(canvas_pos.x + 8.0f, canvas_pos.y + 8.0f);
369 const float swatch = 10.0f;
370 const float pad = 6.0f;
371 const ImU32 text_color = ImGui::GetColorU32(ImVec4(1, 1, 1, 0.85f));
377 const LegendItem items[] = {
378 {
"Track", track_color},
379 {
"Stop", stop_color},
380 {
"Switch", switch_color},
383 float y = legend_pos.y;
384 for (
const auto& item : items) {
385 ImVec2 swatch_min(legend_pos.x, y);
386 ImVec2 swatch_max(legend_pos.x + swatch, y + swatch);
387 draw_list->AddRectFilled(swatch_min, swatch_max, item.color);
388 draw_list->AddRect(swatch_min, swatch_max, outline_color);
389 draw_list->AddText(ImVec2(legend_pos.x + swatch + pad, y - 1.0f),
390 text_color, item.label);
393 const char* arrow_label =
394 direction_map_enabled ?
"Arrows: track directions" :
"Arrows: disabled";
395 draw_list->AddText(ImVec2(legend_pos.x, y + 2.0f), text_color, arrow_label);
400 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
404 if (!custom.has_data)
407 const float tile_size = 8.0f * scale;
408 auto apply_alpha = [](ImVec4 c,
float a) {
413 for (
int y = 0; y < 64; ++y) {
414 for (
int x = 0; x < 64; ++x) {
415 uint8_t tile = custom.
tiles[
static_cast<size_t>(y * 64 + x)];
416 if (!custom.HasOverride(
static_cast<size_t>(y * 64 + x)))
419 const float px =
static_cast<float>(x * 8) * scale;
420 const float py =
static_cast<float>(y * 8) * scale;
421 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
422 ImVec2 max(min.x + tile_size, min.y + tile_size);
424 ImVec4 fill = apply_alpha(theme.panel_border_color, 0.25f);
427 fill = apply_alpha(theme.status_success, 0.28f);
430 fill = apply_alpha(theme.status_active, 0.35f);
433 fill = apply_alpha(theme.status_active, 0.25f);
436 fill = apply_alpha(theme.editor_background, 0.50f);
440 fill = apply_alpha(theme.status_error, 0.30f);
443 fill = apply_alpha(theme.status_warning, 0.30f);
448 fill = apply_alpha(theme.accent_color, 0.30f);
452 draw_list->AddRectFilled(min, max, ImGui::ColorConvertFloat4ToU32(fill));
453 draw_list->AddRect(min, max,
454 ImGui::ColorConvertFloat4ToU32(
455 apply_alpha(theme.editor_grid, 0.50f)));
458 std::string buf = absl::StrFormat(
"%02X", tile);
459 ImVec2 text_size = ImGui::CalcTextSize(buf.c_str());
460 if (text_size.x < tile_size * 0.9f) {
461 ImVec2 text_pos(min.x + (tile_size - text_size.x) * 0.5f,
462 min.y + (tile_size - text_size.y) * 0.5f);
463 draw_list->AddText(text_pos,
464 ImGui::ColorConvertFloat4ToU32(
465 apply_alpha(theme.text_primary, 0.85f)),
474 const ImVec2& canvas_pos,
482 const float tile_size = 8.0f * scale;
483 auto apply_alpha = [](ImVec4 c,
float a) {
487 const ImU32 fill_u32 =
488 ImGui::ColorConvertFloat4ToU32(apply_alpha(theme.status_active, 0.30f));
489 const ImU32 border_u32 =
490 ImGui::ColorConvertFloat4ToU32(apply_alpha(theme.editor_grid, 0.40f));
492 for (
int y = 0; y < 64; ++y) {
493 for (
int x = 0; x < 64; ++x) {
494 if (zone.tiles[
static_cast<size_t>(y * 64 + x)] == 0)
496 const float px =
static_cast<float>(x * 8) * scale;
497 const float py =
static_cast<float>(y * 8) * scale;
498 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
499 ImVec2 max(min.x + tile_size, min.y + tile_size);
500 draw_list->AddRectFilled(min, max, fill_u32);
501 draw_list->AddRect(min, max, border_u32);
507 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
510 const float room_size_px = 512.0f * scale;
511 const float mid_px = 256.0f * scale;
512 const float thickness = std::max(1.0f, 1.0f * scale);
513 const ImU32 line_color = ImGui::GetColorU32(theme.editor_grid);
515 draw_list->AddLineV(canvas_pos.x + mid_px, canvas_pos.y,
516 canvas_pos.y + room_size_px, line_color, thickness);
517 draw_list->AddLineH(canvas_pos.x, canvas_pos.x + room_size_px,
518 canvas_pos.y + mid_px, line_color, thickness);
520 std::string label = absl::StrFormat(
"Layout %d", room.
layout_id());
521 draw_list->AddText(ImVec2(canvas_pos.x + 6.0f, canvas_pos.y + 6.0f),
522 ImGui::GetColorU32(theme.text_secondary_color),
527 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
528 const zelda3::Room& room,
const std::bitset<256>& minecart_sprite_ids,
532 const bool has_collision = map_or.ok() && map_or.value().has_data;
534 const ImU32 ok_color = ImGui::GetColorU32(theme.status_success);
535 const ImU32 warn_color = ImGui::GetColorU32(theme.status_warning);
536 const ImU32 unknown_color = ImGui::GetColorU32(theme.status_inactive);
538 for (
const auto& sprite : room.
GetSprites()) {
539 if (sprite.id() >= 256 || !minecart_sprite_ids[sprite.id()])
542 bool on_stop_tile =
false;
543 bool has_tile =
false;
545 int tile_x = sprite.x() * 2;
546 int tile_y = sprite.y() * 2;
547 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
549 map_or.value().tiles[
static_cast<size_t>(tile_y * 64 + tile_x)];
556 has_tile ? (on_stop_tile ? ok_color : warn_color) : unknown_color;
557 const float px =
static_cast<float>(sprite.x() * 16) * scale;
558 const float py =
static_cast<float>(sprite.y() * 16) * scale;
559 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
560 ImVec2 max(min.x + (16.0f * scale), min.y + (16.0f * scale));
561 draw_list->AddRect(min, max, color, 0.0f, 2.0f);
566 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
569 std::array<bool, 64 * 64> object_grid{};
574 int base_x = obj.x() * 2;
575 int base_y = obj.y() * 2;
576 for (
int dy = 0; dy < dim.height_tiles && (base_y + dy) < 64; ++dy) {
577 for (
int dx = 0; dx < dim.width_tiles && (base_x + dx) < 64; ++dx) {
578 object_grid[
static_cast<size_t>((base_y + dy) * 64 + (base_x + dx))] =
584 std::array<bool, 64 * 64> collision_grid{};
585 for (
const auto& entry : cache.
entries) {
586 collision_grid[
static_cast<size_t>(entry.y * 64 + entry.x)] =
true;
590 const ImU32 gap_color = ImGui::ColorConvertFloat4ToU32(theme.status_warning);
591 const ImU32 orphan_color =
592 ImGui::ColorConvertFloat4ToU32(theme.status_inactive);
593 const float tile_size = 8.0f * scale;
595 for (
int y = 0; y < 64; ++y) {
596 for (
int x = 0; x < 64; ++x) {
597 size_t idx =
static_cast<size_t>(y * 64 + x);
598 if (object_grid[idx] == collision_grid[idx])
601 const float px =
static_cast<float>(x * 8) * scale;
602 const float py =
static_cast<float>(y * 8) * scale;
603 ImVec2 min_pt(canvas_pos.x + px, canvas_pos.y + py);
604 ImVec2 max_pt(min_pt.x + tile_size, min_pt.y + tile_size);
605 draw_list->AddRectFilled(min_pt, max_pt,
606 object_grid[idx] ? gap_color : orphan_color);
612 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
615 std::array<bool, 64 * 64> occupied{};
616 for (
const auto& entry : cache.
entries) {
617 occupied[
static_cast<size_t>(entry.y * 64 + entry.x)] =
true;
620 const float tile_size = 8.0f * scale;
621 const float half_tile = tile_size * 0.5f;
622 const ImU32 route_color = ImGui::ColorConvertFloat4ToU32(theme.status_active);
623 const float thickness = std::max(1.0f, 1.5f * scale);
625 for (
const auto& entry : cache.
entries) {
627 canvas_pos.x +
static_cast<float>(entry.x * 8) * scale + half_tile;
629 canvas_pos.y +
static_cast<float>(entry.y * 8) * scale + half_tile;
631 if (entry.x + 1 < 64 &&
632 occupied[
static_cast<size_t>(entry.y * 64 + (entry.x + 1))]) {
633 draw_list->AddLineH(cx, cx + tile_size, cy, route_color, thickness);
635 if (entry.y + 1 < 64 &&
636 occupied[
static_cast<size_t>((entry.y + 1) * 64 + entry.x)]) {
637 draw_list->AddLineV(cx, cy, cy + tile_size, route_color, thickness);
static void DrawCustomCollisionOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room)
static void DrawTrackGapOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room, const CollisionOverlayCache &cache)
static void DrawTrackCollisionOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const CollisionOverlayCache &cache, const TrackCollisionConfig &config, bool direction_map_enabled, const std::vector< uint16_t > &track_tile_order, const std::vector< uint16_t > &switch_tile_order, bool show_legend)
static void DrawCameraQuadrantOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room)
static std::pair< int, int > RoomToCanvasCoordinates(int room_x, int room_y)
static void DrawTrackRouteOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const CollisionOverlayCache &cache)
static TrackDirectionMasks GetTrackDirectionMasksForTrackIndex(size_t index)
static void DrawMinecartSpriteOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room, const std::bitset< 256 > &minecart_sprite_ids, const TrackCollisionConfig &config)
static TrackDirectionMasks GetTrackDirectionMasksForSwitchIndex(size_t index)
static void DrawWaterFillOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room)
static void DrawTrackDirectionMask(ImDrawList *draw_list, const ImVec2 &min, float tile_size, uint8_t mask, ImU32 color)
static bool BuildPotItemPreview(uint8_t item_id, const zelda3::Room &room, std::vector< uint8_t > &out_pixels, SDL_Rect &out_bounds)
static void DrawTrackArrowHead(ImDrawList *draw_list, const ImVec2 &tip, TrackDir dir, float size, ImU32 color)
static TrackDirectionMasks GetTrackDirectionMasksFromConfig(uint8_t tile, const std::vector< uint16_t > &track_tiles, const std::vector< uint16_t > &track_switches)
static bool DrawSpritePreview(ImDrawList *draw_list, const std::vector< uint8_t > &preview, const SDL_Rect &bounds, const ImVec2 &box_min, const ImVec2 &box_max, const std::array< SDL_Color, 256 > &color_table, uint8_t transparent_index=kSpritePreviewTransparentIndex)
static const zelda3::SpriteOamLayout * GetPotItemSpriteLayout(uint8_t item_id, uint8_t *out_sprite_id=nullptr)
static DimensionService & Get()
DimensionResult GetDimensions(const RoomObject &obj) const
const std::array< uint8_t, 0x10000 > & get_gfx_buffer() const
const CustomCollisionMap & custom_collision() const
const WaterFillZoneMap & water_fill_zone() const
const std::vector< zelda3::Sprite > & GetSprites() const
const std::vector< RoomObject > & GetTileObjects() const
uint8_t layout_id() const
static const SpriteOamLayout * GetLayout(uint8_t sprite_id)
Get the OAM layout for a sprite ID.
A class for managing sprites in the overworld and underworld.
void RenderPreviewGraphics(std::span< const uint8_t > graphics, const SpriteOamLayout *layout_override=nullptr, std::span< const uint8_t > graphics_resource={})
auto preview_graphics() const
SDL_Rect preview_bounds() const
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
constexpr uint8_t kTrackDirSouth
constexpr uint8_t kTrackDirEast
constexpr uint8_t kTrackDirNorth
constexpr uint8_t kTrackDirWest
const SpriteOamLayout kLargeMagicLayout
const SpriteOamLayout kBlueSoldierLayout
const SpriteOamLayout kBlueRupeeLayout
const SpriteOamLayout kGreenSoldierLayout
const SpriteOamLayout kSmallMagicLayout
const SpriteOamLayout kGreenRupeeLayout
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
const SpriteOamLayout kChickenLayout
const SpriteOamLayout kKeyLayout
const SpriteOamLayout kSmallHeartLayout
std::vector< CollisionOverlayEntry > entries
std::array< bool, 256 > stop_tiles
std::array< bool, 256 > switch_tiles
std::array< uint8_t, 64 *64 > tiles
Complete OAM layout for a vanilla sprite.