104 a = ImVec2(tip.x - size, tip.y + size);
105 b = ImVec2(tip.x + size, tip.y + size);
108 a = ImVec2(tip.x - size, tip.y - size);
109 b = ImVec2(tip.x - size, tip.y + size);
112 a = ImVec2(tip.x - size, tip.y - size);
113 b = ImVec2(tip.x + size, tip.y - size);
116 a = ImVec2(tip.x + size, tip.y - size);
117 b = ImVec2(tip.x + size, tip.y + size);
120 draw_list->AddTriangleFilled(tip, a, b, color);
131 const ImVec2 center(min.x + tile_size * 0.5f, min.y + tile_size * 0.5f);
132 const float line_len = tile_size * 0.32f;
133 const float head_size = std::max(2.0f, tile_size * 0.18f);
134 const float thickness = std::max(1.0f, tile_size * 0.08f);
136 auto draw_dir = [&](
TrackDir dir,
float dx,
float dy) {
137 ImVec2 tip(center.x + dx * line_len, center.y + dy * line_len);
138 draw_list->AddLine(center, tip, color, thickness);
163 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
165 bool direction_map_enabled,
const std::vector<uint16_t>& track_tile_order,
166 const std::vector<uint16_t>& switch_tile_order,
bool show_legend) {
168 const ImU32 track_color = ImGui::GetColorU32(ImVec4(0.2f, 0.8f, 0.4f, 0.35f));
169 const ImU32 stop_color = ImGui::GetColorU32(ImVec4(0.9f, 0.3f, 0.2f, 0.45f));
170 const ImU32 switch_color =
171 ImGui::GetColorU32(ImVec4(0.95f, 0.8f, 0.2f, 0.45f));
172 const ImU32 outline_color = ImGui::GetColorU32(ImVec4(0, 0, 0, 0.4f));
173 const ImU32 arrow_color =
174 ImGui::GetColorU32(ImVec4(0.05f, 0.05f, 0.05f, 0.75f));
175 const ImU32 arrow_color_dim =
176 ImGui::GetColorU32(ImVec4(0.05f, 0.05f, 0.05f, 0.4f));
178 for (
const auto& entry : cache.
entries) {
179 const float px =
static_cast<float>(entry.x * 8) * scale;
180 const float py =
static_cast<float>(entry.y * 8) * scale;
181 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
182 const float tile_size = 8.0f * scale;
183 ImVec2 max(min.x + tile_size, min.y + tile_size);
185 ImU32 color = track_color;
189 color = switch_color;
192 draw_list->AddRectFilled(min, max, color);
193 draw_list->AddRect(min, max, outline_color);
196 const char* dir_label =
nullptr;
197 if (entry.tile == 0xB7)
199 else if (entry.tile == 0xB8)
201 else if (entry.tile == 0xB9)
203 else if (entry.tile == 0xBA)
207 const ImU32 label_color =
208 ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 0.9f));
209 ImVec2 text_size = ImGui::CalcTextSize(dir_label);
210 ImVec2 text_pos(min.x + (tile_size - text_size.x) * 0.5f,
211 min.y + (tile_size - text_size.y) * 0.5f);
212 draw_list->AddText(text_pos, label_color, dir_label);
216 if (direction_map_enabled) {
218 entry.tile, track_tile_order, switch_tile_order);
219 if (masks.primary != 0) {
222 if (masks.secondary != 0) {
231 ImVec2 legend_pos(canvas_pos.x + 8.0f, canvas_pos.y + 8.0f);
232 const float swatch = 10.0f;
233 const float pad = 6.0f;
234 const ImU32 text_color = ImGui::GetColorU32(ImVec4(1, 1, 1, 0.85f));
240 const LegendItem items[] = {
241 {
"Track", track_color},
242 {
"Stop", stop_color},
243 {
"Switch", switch_color},
246 float y = legend_pos.y;
247 for (
const auto& item : items) {
248 ImVec2 swatch_min(legend_pos.x, y);
249 ImVec2 swatch_max(legend_pos.x + swatch, y + swatch);
250 draw_list->AddRectFilled(swatch_min, swatch_max, item.color);
251 draw_list->AddRect(swatch_min, swatch_max, outline_color);
252 draw_list->AddText(ImVec2(legend_pos.x + swatch + pad, y - 1.0f),
253 text_color, item.label);
256 const char* arrow_label =
257 direction_map_enabled ?
"Arrows: track directions" :
"Arrows: disabled";
258 draw_list->AddText(ImVec2(legend_pos.x, y + 2.0f), text_color, arrow_label);
263 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
267 if (!custom.has_data)
270 const float tile_size = 8.0f * scale;
271 auto apply_alpha = [](ImVec4 c,
float a) {
276 for (
int y = 0; y < 64; ++y) {
277 for (
int x = 0; x < 64; ++x) {
278 uint8_t tile = custom.
tiles[
static_cast<size_t>(y * 64 + x)];
282 const float px =
static_cast<float>(x * 8) * scale;
283 const float py =
static_cast<float>(y * 8) * scale;
284 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
285 ImVec2 max(min.x + tile_size, min.y + tile_size);
287 ImVec4 fill = apply_alpha(theme.panel_border_color, 0.25f);
290 fill = apply_alpha(theme.status_active, 0.35f);
293 fill = apply_alpha(theme.status_active, 0.25f);
296 fill = apply_alpha(theme.editor_background, 0.50f);
300 fill = apply_alpha(theme.status_error, 0.30f);
303 fill = apply_alpha(theme.status_warning, 0.30f);
308 fill = apply_alpha(theme.accent_color, 0.30f);
312 draw_list->AddRectFilled(min, max, ImGui::ColorConvertFloat4ToU32(fill));
313 draw_list->AddRect(min, max,
314 ImGui::ColorConvertFloat4ToU32(
315 apply_alpha(theme.editor_grid, 0.50f)));
318 std::string buf = absl::StrFormat(
"%02X", tile);
319 ImVec2 text_size = ImGui::CalcTextSize(buf.c_str());
320 if (text_size.x < tile_size * 0.9f) {
321 ImVec2 text_pos(min.x + (tile_size - text_size.x) * 0.5f,
322 min.y + (tile_size - text_size.y) * 0.5f);
323 draw_list->AddText(text_pos,
324 ImGui::ColorConvertFloat4ToU32(
325 apply_alpha(theme.text_primary, 0.85f)),
334 const ImVec2& canvas_pos,
342 const float tile_size = 8.0f * scale;
343 auto apply_alpha = [](ImVec4 c,
float a) {
347 const ImU32 fill_u32 =
348 ImGui::ColorConvertFloat4ToU32(apply_alpha(theme.status_active, 0.30f));
349 const ImU32 border_u32 =
350 ImGui::ColorConvertFloat4ToU32(apply_alpha(theme.editor_grid, 0.40f));
352 for (
int y = 0; y < 64; ++y) {
353 for (
int x = 0; x < 64; ++x) {
354 if (zone.tiles[
static_cast<size_t>(y * 64 + x)] == 0)
356 const float px =
static_cast<float>(x * 8) * scale;
357 const float py =
static_cast<float>(y * 8) * scale;
358 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
359 ImVec2 max(min.x + tile_size, min.y + tile_size);
360 draw_list->AddRectFilled(min, max, fill_u32);
361 draw_list->AddRect(min, max, border_u32);
367 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
370 const float room_size_px = 512.0f * scale;
371 const float mid_px = 256.0f * scale;
372 const float thickness = std::max(1.0f, 1.0f * scale);
373 const ImU32 line_color = ImGui::GetColorU32(theme.editor_grid);
375 draw_list->AddLine(ImVec2(canvas_pos.x + mid_px, canvas_pos.y),
376 ImVec2(canvas_pos.x + mid_px, canvas_pos.y + room_size_px),
377 line_color, thickness);
378 draw_list->AddLine(ImVec2(canvas_pos.x, canvas_pos.y + mid_px),
379 ImVec2(canvas_pos.x + room_size_px, canvas_pos.y + mid_px),
380 line_color, thickness);
382 std::string label = absl::StrFormat(
"Layout %d", room.
layout_id());
383 draw_list->AddText(ImVec2(canvas_pos.x + 6.0f, canvas_pos.y + 6.0f),
384 ImGui::GetColorU32(theme.text_secondary_color),
389 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
390 const zelda3::Room& room,
const std::bitset<256>& minecart_sprite_ids,
394 const bool has_collision = map_or.ok() && map_or.value().has_data;
396 const ImU32 ok_color = ImGui::GetColorU32(theme.status_success);
397 const ImU32 warn_color = ImGui::GetColorU32(theme.status_warning);
398 const ImU32 unknown_color = ImGui::GetColorU32(theme.status_inactive);
400 for (
const auto& sprite : room.
GetSprites()) {
401 if (sprite.id() >= 256 || !minecart_sprite_ids[sprite.id()])
404 bool on_stop_tile =
false;
405 bool has_tile =
false;
407 int tile_x = sprite.x() * 2;
408 int tile_y = sprite.y() * 2;
409 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
411 map_or.value().tiles[
static_cast<size_t>(tile_y * 64 + tile_x)];
418 has_tile ? (on_stop_tile ? ok_color : warn_color) : unknown_color;
419 const float px =
static_cast<float>(sprite.x() * 16) * scale;
420 const float py =
static_cast<float>(sprite.y() * 16) * scale;
421 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
422 ImVec2 max(min.x + (16.0f * scale), min.y + (16.0f * scale));
423 draw_list->AddRect(min, max, color, 0.0f, 0, 2.0f);
428 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
431 std::array<bool, 64 * 64> object_grid{};
436 int base_x = obj.x() * 2;
437 int base_y = obj.y() * 2;
438 for (
int dy = 0; dy < dim.height_tiles && (base_y + dy) < 64; ++dy) {
439 for (
int dx = 0; dx < dim.width_tiles && (base_x + dx) < 64; ++dx) {
440 object_grid[
static_cast<size_t>((base_y + dy) * 64 + (base_x + dx))] =
446 std::array<bool, 64 * 64> collision_grid{};
447 for (
const auto& entry : cache.
entries) {
448 collision_grid[
static_cast<size_t>(entry.y * 64 + entry.x)] =
true;
452 const ImU32 gap_color = ImGui::ColorConvertFloat4ToU32(theme.status_warning);
453 const ImU32 orphan_color =
454 ImGui::ColorConvertFloat4ToU32(theme.status_inactive);
455 const float tile_size = 8.0f * scale;
457 for (
int y = 0; y < 64; ++y) {
458 for (
int x = 0; x < 64; ++x) {
459 size_t idx =
static_cast<size_t>(y * 64 + x);
460 if (object_grid[idx] == collision_grid[idx])
463 const float px =
static_cast<float>(x * 8) * scale;
464 const float py =
static_cast<float>(y * 8) * scale;
465 ImVec2 min_pt(canvas_pos.x + px, canvas_pos.y + py);
466 ImVec2 max_pt(min_pt.x + tile_size, min_pt.y + tile_size);
467 draw_list->AddRectFilled(min_pt, max_pt,
468 object_grid[idx] ? gap_color : orphan_color);
474 ImDrawList* draw_list,
const ImVec2& canvas_pos,
float scale,
477 std::array<bool, 64 * 64> occupied{};
478 for (
const auto& entry : cache.
entries) {
479 occupied[
static_cast<size_t>(entry.y * 64 + entry.x)] =
true;
482 const float tile_size = 8.0f * scale;
483 const float half_tile = tile_size * 0.5f;
484 const ImU32 route_color = ImGui::ColorConvertFloat4ToU32(theme.status_active);
485 const float thickness = std::max(1.0f, 1.5f * scale);
487 for (
const auto& entry : cache.
entries) {
489 canvas_pos.x +
static_cast<float>(entry.x * 8) * scale + half_tile;
491 canvas_pos.y +
static_cast<float>(entry.y * 8) * scale + half_tile;
493 if (entry.x + 1 < 64 &&
494 occupied[
static_cast<size_t>(entry.y * 64 + (entry.x + 1))]) {
495 draw_list->AddLine(ImVec2(cx, cy), ImVec2(cx + tile_size, cy),
496 route_color, thickness);
498 if (entry.y + 1 < 64 &&
499 occupied[
static_cast<size_t>((entry.y + 1) * 64 + entry.x)]) {
500 draw_list->AddLine(ImVec2(cx, cy), ImVec2(cx, cy + tile_size),
501 route_color, thickness);
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)