yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_rendering_helpers.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6
7#include "absl/strings/str_format.h"
12#include "zelda3/dungeon/room.h"
13
14namespace yaze::editor {
15
16TrackDirectionMasks
18 switch (index) {
19 case 0:
21 case 1:
23 case 2:
25 case 3:
27 case 4:
29 case 5:
31 case 6:
34 case 7:
36 case 8:
38 case 9:
40 case 10:
42 case 11:
44 0};
45 case 12:
47 0};
48 case 13:
51 case 14:
54 default:
55 return {};
56 }
57}
58
78
80 uint8_t tile, const std::vector<uint16_t>& track_tiles,
81 const std::vector<uint16_t>& switch_tiles) {
82 auto track_it = std::find(track_tiles.begin(), track_tiles.end(), tile);
83 if (track_it != track_tiles.end()) {
85 static_cast<size_t>(track_it - track_tiles.begin()));
86 }
87
88 auto switch_it = std::find(switch_tiles.begin(), switch_tiles.end(), tile);
89 if (switch_it != switch_tiles.end()) {
91 static_cast<size_t>(switch_it - switch_tiles.begin()));
92 }
93
94 return {};
95}
96
98 const ImVec2& tip,
99 TrackDir dir, float size,
100 ImU32 color) {
101 ImVec2 a, b;
102 switch (dir) {
103 case TrackDir::North:
104 a = ImVec2(tip.x - size, tip.y + size);
105 b = ImVec2(tip.x + size, tip.y + size);
106 break;
107 case TrackDir::East:
108 a = ImVec2(tip.x - size, tip.y - size);
109 b = ImVec2(tip.x - size, tip.y + size);
110 break;
111 case TrackDir::South:
112 a = ImVec2(tip.x - size, tip.y - size);
113 b = ImVec2(tip.x + size, tip.y - size);
114 break;
115 case TrackDir::West:
116 a = ImVec2(tip.x + size, tip.y - size);
117 b = ImVec2(tip.x + size, tip.y + size);
118 break;
119 }
120 draw_list->AddTriangleFilled(tip, a, b, color);
121}
122
124 const ImVec2& min,
125 float tile_size,
126 uint8_t mask,
127 ImU32 color) {
128 if (!mask) {
129 return;
130 }
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);
135
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);
139 DrawTrackArrowHead(draw_list, tip, dir, head_size, color);
140 };
141
142 if (mask & kTrackDirNorth) {
143 draw_dir(TrackDir::North, 0.0f, -1.0f);
144 }
145 if (mask & kTrackDirEast) {
146 draw_dir(TrackDir::East, 1.0f, 0.0f);
147 }
148 if (mask & kTrackDirSouth) {
149 draw_dir(TrackDir::South, 0.0f, 1.0f);
150 }
151 if (mask & kTrackDirWest) {
152 draw_dir(TrackDir::West, -1.0f, 0.0f);
153 }
154}
155
157 int room_x, int room_y) {
158 // Return unscaled relative coordinates (8 pixels per tile)
159 return {room_x * 8, room_y * 8};
160}
161
163 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
164 const CollisionOverlayCache& cache, const TrackCollisionConfig& config,
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) {
167
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));
177
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);
184
185 ImU32 color = track_color;
186 if (config.stop_tiles[entry.tile]) {
187 color = stop_color;
188 } else if (config.switch_tiles[entry.tile]) {
189 color = switch_color;
190 }
191
192 draw_list->AddRectFilled(min, max, color);
193 draw_list->AddRect(min, max, outline_color);
194
195 if (config.stop_tiles[entry.tile]) {
196 const char* dir_label = nullptr;
197 if (entry.tile == 0xB7)
198 dir_label = "U";
199 else if (entry.tile == 0xB8)
200 dir_label = "D";
201 else if (entry.tile == 0xB9)
202 dir_label = "L";
203 else if (entry.tile == 0xBA)
204 dir_label = "R";
205
206 if (dir_label) {
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);
213 }
214 }
215
216 if (direction_map_enabled) {
217 const auto masks = GetTrackDirectionMasksFromConfig(
218 entry.tile, track_tile_order, switch_tile_order);
219 if (masks.primary != 0) {
220 DrawTrackDirectionMask(draw_list, min, tile_size, masks.primary,
221 arrow_color);
222 if (masks.secondary != 0) {
223 DrawTrackDirectionMask(draw_list, min, tile_size, masks.secondary,
224 arrow_color_dim);
225 }
226 }
227 }
228 }
229
230 if (show_legend) {
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));
235
236 struct LegendItem {
237 const char* label;
238 ImU32 color;
239 };
240 const LegendItem items[] = {
241 {"Track", track_color},
242 {"Stop", stop_color},
243 {"Switch", switch_color},
244 };
245
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);
254 y += swatch + 4.0f;
255 }
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);
259 }
260}
261
263 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
264 const zelda3::Room& room) {
265 const auto& theme = AgentUI::GetTheme();
266 const auto& custom = room.custom_collision();
267 if (!custom.has_data)
268 return;
269
270 const float tile_size = 8.0f * scale;
271 auto apply_alpha = [](ImVec4 c, float a) {
272 c.w = a;
273 return c;
274 };
275
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)];
279 if (tile == 0)
280 continue;
281
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);
286
287 ImVec4 fill = apply_alpha(theme.panel_border_color, 0.25f);
288 switch (tile) {
289 case 0x08:
290 fill = apply_alpha(theme.status_active, 0.35f);
291 break;
292 case 0x09:
293 fill = apply_alpha(theme.status_active, 0.25f);
294 break;
295 case 0x1B:
296 fill = apply_alpha(theme.editor_background, 0.50f);
297 break;
298 case 0x0E:
299 case 0x3C:
300 fill = apply_alpha(theme.status_error, 0.30f);
301 break;
302 case 0x1C:
303 fill = apply_alpha(theme.status_warning, 0.30f);
304 break;
305 case 0x21:
306 case 0x22:
307 case 0x23:
308 fill = apply_alpha(theme.accent_color, 0.30f);
309 break;
310 }
311
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)));
316
317 if (scale >= 4.0f) {
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)),
326 buf.c_str());
327 }
328 }
329 }
330 }
331}
332
334 const ImVec2& canvas_pos,
335 float scale,
336 const zelda3::Room& room) {
337 const auto& theme = AgentUI::GetTheme();
338 const auto& zone = room.water_fill_zone();
339 if (!zone.has_data)
340 return;
341
342 const float tile_size = 8.0f * scale;
343 auto apply_alpha = [](ImVec4 c, float a) {
344 c.w = a;
345 return c;
346 };
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));
351
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)
355 continue;
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);
362 }
363 }
364}
365
367 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
368 const zelda3::Room& room) {
369 const auto& theme = AgentUI::GetTheme();
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);
374
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);
381
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),
385 label.c_str());
386}
387
389 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
390 const zelda3::Room& room, const std::bitset<256>& minecart_sprite_ids,
391 const TrackCollisionConfig& config) {
392 const auto& theme = AgentUI::GetTheme();
393 auto map_or = zelda3::LoadCustomCollisionMap(room.rom(), room.id());
394 const bool has_collision = map_or.ok() && map_or.value().has_data;
395
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);
399
400 for (const auto& sprite : room.GetSprites()) {
401 if (sprite.id() >= 256 || !minecart_sprite_ids[sprite.id()])
402 continue;
403
404 bool on_stop_tile = false;
405 bool has_tile = false;
406 if (has_collision) {
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) {
410 uint8_t tile =
411 map_or.value().tiles[static_cast<size_t>(tile_y * 64 + tile_x)];
412 has_tile = true;
413 on_stop_tile = config.stop_tiles[tile];
414 }
415 }
416
417 ImU32 color =
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);
424 }
425}
426
428 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
429 const zelda3::Room& room, const CollisionOverlayCache& cache) {
430
431 std::array<bool, 64 * 64> object_grid{};
432 for (const auto& obj : room.GetTileObjects()) {
433 if (obj.id_ != 0x31)
434 continue;
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))] =
441 true;
442 }
443 }
444 }
445
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;
449 }
450
451 const auto& theme = AgentUI::GetTheme();
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;
456
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])
461 continue;
462
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);
469 }
470 }
471}
472
474 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
475 const CollisionOverlayCache& cache) {
476 const auto& theme = AgentUI::GetTheme();
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;
480 }
481
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);
486
487 for (const auto& entry : cache.entries) {
488 float cx =
489 canvas_pos.x + static_cast<float>(entry.x * 8) * scale + half_tile;
490 float cy =
491 canvas_pos.y + static_cast<float>(entry.y * 8) * scale + half_tile;
492
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);
497 }
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);
502 }
503 }
504}
505
506} // namespace yaze::editor
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 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 DimensionService & Get()
DimensionResult GetDimensions(const RoomObject &obj) const
const CustomCollisionMap & custom_collision() const
Definition room.h:496
const WaterFillZoneMap & water_fill_zone() const
Definition room.h:527
auto rom() const
Definition room.h:958
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:253
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:382
uint8_t layout_id() const
Definition room.h:912
int id() const
Definition room.h:899
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
absl::StatusOr< CustomCollisionMap > LoadCustomCollisionMap(Rom *rom, int room_id)
std::array< uint8_t, 64 *64 > tiles