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 <cmath>
4#include <algorithm>
5#include <array>
6
7#include "absl/strings/str_format.h"
10#include "zelda3/dungeon/room.h"
13
14namespace yaze::editor {
15
17 switch (index) {
18 case 0:
20 case 1:
22 case 2:
24 case 3:
26 case 4:
28 case 5:
30 case 6:
33 case 7:
35 case 8:
37 case 9:
39 case 10:
41 case 11:
43 case 12:
45 case 13:
47 case 14:
49 default:
50 return {};
51 }
52}
53
72
74 uint8_t tile, const std::vector<uint16_t>& track_tiles,
75 const std::vector<uint16_t>& switch_tiles) {
76 auto track_it = std::find(track_tiles.begin(), track_tiles.end(), tile);
77 if (track_it != track_tiles.end()) {
79 static_cast<size_t>(track_it - track_tiles.begin()));
80 }
81
82 auto switch_it = std::find(switch_tiles.begin(), switch_tiles.end(), tile);
83 if (switch_it != switch_tiles.end()) {
85 static_cast<size_t>(switch_it - switch_tiles.begin()));
86 }
87
88 return {};
89}
90
91void DungeonRenderingHelpers::DrawTrackArrowHead(ImDrawList* draw_list, const ImVec2& tip,
92 TrackDir dir, float size, ImU32 color) {
93 ImVec2 a, b;
94 switch (dir) {
95 case TrackDir::North:
96 a = ImVec2(tip.x - size, tip.y + size);
97 b = ImVec2(tip.x + size, tip.y + size);
98 break;
99 case TrackDir::East:
100 a = ImVec2(tip.x - size, tip.y - size);
101 b = ImVec2(tip.x - size, tip.y + size);
102 break;
103 case TrackDir::South:
104 a = ImVec2(tip.x - size, tip.y - size);
105 b = ImVec2(tip.x + size, tip.y - size);
106 break;
107 case TrackDir::West:
108 a = ImVec2(tip.x + size, tip.y - size);
109 b = ImVec2(tip.x + size, tip.y + size);
110 break;
111 }
112 draw_list->AddTriangleFilled(tip, a, b, color);
113}
114
115void DungeonRenderingHelpers::DrawTrackDirectionMask(ImDrawList* draw_list, const ImVec2& min,
116 float tile_size, uint8_t mask, ImU32 color) {
117 if (!mask) {
118 return;
119 }
120 const ImVec2 center(min.x + tile_size * 0.5f, min.y + tile_size * 0.5f);
121 const float line_len = tile_size * 0.32f;
122 const float head_size = std::max(2.0f, tile_size * 0.18f);
123 const float thickness = std::max(1.0f, tile_size * 0.08f);
124
125 auto draw_dir = [&](TrackDir dir, float dx, float dy) {
126 ImVec2 tip(center.x + dx * line_len, center.y + dy * line_len);
127 draw_list->AddLine(center, tip, color, thickness);
128 DrawTrackArrowHead(draw_list, tip, dir, head_size, color);
129 };
130
131 if (mask & kTrackDirNorth) {
132 draw_dir(TrackDir::North, 0.0f, -1.0f);
133 }
134 if (mask & kTrackDirEast) {
135 draw_dir(TrackDir::East, 1.0f, 0.0f);
136 }
137 if (mask & kTrackDirSouth) {
138 draw_dir(TrackDir::South, 0.0f, 1.0f);
139 }
140 if (mask & kTrackDirWest) {
141 draw_dir(TrackDir::West, -1.0f, 0.0f);
142 }
143}
144
145std::pair<int, int> DungeonRenderingHelpers::RoomToCanvasCoordinates(int room_x, int room_y) {
146 // Return unscaled relative coordinates (8 pixels per tile)
147 return {room_x * 8, room_y * 8};
148}
149
151 const ImVec2& screen_pos, const ImVec2& zero_point, float scale) {
152 if (scale <= 0.0f) return {0, 0};
153 float rel_x = (screen_pos.x - zero_point.x) / scale;
154 float rel_y = (screen_pos.y - zero_point.y) / scale;
155 return {static_cast<int>(rel_x / 8.0f), static_cast<int>(rel_y / 8.0f)};
156}
157
159 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
160 const CollisionOverlayCache& cache,
161 const TrackCollisionConfig& config,
162 bool direction_map_enabled,
163 const std::vector<uint16_t>& track_tile_order,
164 const std::vector<uint16_t>& switch_tile_order,
165 bool show_legend) {
166
167 const ImU32 track_color = ImGui::GetColorU32(ImVec4(0.2f, 0.8f, 0.4f, 0.35f));
168 const ImU32 stop_color = ImGui::GetColorU32(ImVec4(0.9f, 0.3f, 0.2f, 0.45f));
169 const ImU32 switch_color = ImGui::GetColorU32(ImVec4(0.95f, 0.8f, 0.2f, 0.45f));
170 const ImU32 outline_color = ImGui::GetColorU32(ImVec4(0, 0, 0, 0.4f));
171 const ImU32 arrow_color = ImGui::GetColorU32(ImVec4(0.05f, 0.05f, 0.05f, 0.75f));
172 const ImU32 arrow_color_dim = ImGui::GetColorU32(ImVec4(0.05f, 0.05f, 0.05f, 0.4f));
173
174 for (const auto& entry : cache.entries) {
175 const float px = static_cast<float>(entry.x * 8) * scale;
176 const float py = static_cast<float>(entry.y * 8) * scale;
177 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
178 const float tile_size = 8.0f * scale;
179 ImVec2 max(min.x + tile_size, min.y + tile_size);
180
181 ImU32 color = track_color;
182 if (config.stop_tiles[entry.tile]) {
183 color = stop_color;
184 } else if (config.switch_tiles[entry.tile]) {
185 color = switch_color;
186 }
187
188 draw_list->AddRectFilled(min, max, color);
189 draw_list->AddRect(min, max, outline_color);
190
191 if (config.stop_tiles[entry.tile]) {
192 const char* dir_label = nullptr;
193 if (entry.tile == 0xB7) dir_label = "U";
194 else if (entry.tile == 0xB8) dir_label = "D";
195 else if (entry.tile == 0xB9) dir_label = "L";
196 else if (entry.tile == 0xBA) dir_label = "R";
197
198 if (dir_label) {
199 const ImU32 label_color = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 0.9f));
200 ImVec2 text_size = ImGui::CalcTextSize(dir_label);
201 ImVec2 text_pos(min.x + (tile_size - text_size.x) * 0.5f,
202 min.y + (tile_size - text_size.y) * 0.5f);
203 draw_list->AddText(text_pos, label_color, dir_label);
204 }
205 }
206
207 if (direction_map_enabled) {
208 const auto masks = GetTrackDirectionMasksFromConfig(entry.tile, track_tile_order, switch_tile_order);
209 if (masks.primary != 0) {
210 DrawTrackDirectionMask(draw_list, min, tile_size, masks.primary, arrow_color);
211 if (masks.secondary != 0) {
212 DrawTrackDirectionMask(draw_list, min, tile_size, masks.secondary, arrow_color_dim);
213 }
214 }
215 }
216 }
217
218 if (show_legend) {
219 ImVec2 legend_pos(canvas_pos.x + 8.0f, canvas_pos.y + 8.0f);
220 const float swatch = 10.0f;
221 const float pad = 6.0f;
222 const ImU32 text_color = ImGui::GetColorU32(ImVec4(1, 1, 1, 0.85f));
223
224 struct LegendItem {
225 const char* label;
226 ImU32 color;
227 };
228 const LegendItem items[] = {
229 {"Track", track_color},
230 {"Stop", stop_color},
231 {"Switch", switch_color},
232 };
233
234 float y = legend_pos.y;
235 for (const auto& item : items) {
236 ImVec2 swatch_min(legend_pos.x, y);
237 ImVec2 swatch_max(legend_pos.x + swatch, y + swatch);
238 draw_list->AddRectFilled(swatch_min, swatch_max, item.color);
239 draw_list->AddRect(swatch_min, swatch_max, outline_color);
240 draw_list->AddText(ImVec2(legend_pos.x + swatch + pad, y - 1.0f), text_color, item.label);
241 y += swatch + 4.0f;
242 }
243 const char* arrow_label = direction_map_enabled ? "Arrows: track directions" : "Arrows: disabled";
244 draw_list->AddText(ImVec2(legend_pos.x, y + 2.0f), text_color, arrow_label);
245 }
246}
247
249 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
250 const zelda3::Room& room) {
251 const auto& theme = AgentUI::GetTheme();
252 const auto& custom = room.custom_collision();
253 if (!custom.has_data) return;
254
255 const float tile_size = 8.0f * scale;
256 auto apply_alpha = [](ImVec4 c, float a) { c.w = a; return c; };
257
258 for (int y = 0; y < 64; ++y) {
259 for (int x = 0; x < 64; ++x) {
260 uint8_t tile = custom.tiles[static_cast<size_t>(y * 64 + x)];
261 if (tile == 0) continue;
262
263 const float px = static_cast<float>(x * 8) * scale;
264 const float py = static_cast<float>(y * 8) * scale;
265 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
266 ImVec2 max(min.x + tile_size, min.y + tile_size);
267
268 ImVec4 fill = apply_alpha(theme.panel_border_color, 0.25f);
269 switch (tile) {
270 case 0x08: fill = apply_alpha(theme.status_active, 0.35f); break;
271 case 0x09: fill = apply_alpha(theme.status_active, 0.25f); break;
272 case 0x1B: fill = apply_alpha(theme.editor_background, 0.50f); break;
273 case 0x0E:
274 case 0x3C: fill = apply_alpha(theme.status_error, 0.30f); break;
275 case 0x1C: fill = apply_alpha(theme.status_warning, 0.30f); break;
276 case 0x21:
277 case 0x22:
278 case 0x23: fill = apply_alpha(theme.accent_color, 0.30f); break;
279 }
280
281 draw_list->AddRectFilled(min, max, ImGui::ColorConvertFloat4ToU32(fill));
282 draw_list->AddRect(min, max, ImGui::ColorConvertFloat4ToU32(apply_alpha(theme.editor_grid, 0.50f)));
283
284 if (scale >= 4.0f) {
285 std::string buf = absl::StrFormat("%02X", tile);
286 ImVec2 text_size = ImGui::CalcTextSize(buf.c_str());
287 if (text_size.x < tile_size * 0.9f) {
288 ImVec2 text_pos(min.x + (tile_size - text_size.x) * 0.5f, min.y + (tile_size - text_size.y) * 0.5f);
289 draw_list->AddText(text_pos, ImGui::ColorConvertFloat4ToU32(apply_alpha(theme.text_primary, 0.85f)), buf.c_str());
290 }
291 }
292 }
293 }
294}
295
297 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
298 const zelda3::Room& room) {
299 const auto& theme = AgentUI::GetTheme();
300 const auto& zone = room.water_fill_zone();
301 if (!zone.has_data) return;
302
303 const float tile_size = 8.0f * scale;
304 auto apply_alpha = [](ImVec4 c, float a) { c.w = a; return c; };
305 const ImU32 fill_u32 = ImGui::ColorConvertFloat4ToU32(apply_alpha(theme.status_active, 0.30f));
306 const ImU32 border_u32 = ImGui::ColorConvertFloat4ToU32(apply_alpha(theme.editor_grid, 0.40f));
307
308 for (int y = 0; y < 64; ++y) {
309 for (int x = 0; x < 64; ++x) {
310 if (zone.tiles[static_cast<size_t>(y * 64 + x)] == 0) continue;
311 const float px = static_cast<float>(x * 8) * scale;
312 const float py = static_cast<float>(y * 8) * scale;
313 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
314 ImVec2 max(min.x + tile_size, min.y + tile_size);
315 draw_list->AddRectFilled(min, max, fill_u32);
316 draw_list->AddRect(min, max, border_u32);
317 }
318 }
319}
320
322 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
323 const zelda3::Room& room) {
324 const auto& theme = AgentUI::GetTheme();
325 const float room_size_px = 512.0f * scale;
326 const float mid_px = 256.0f * scale;
327 const float thickness = std::max(1.0f, 1.0f * scale);
328 const ImU32 line_color = ImGui::GetColorU32(theme.editor_grid);
329
330 draw_list->AddLine(ImVec2(canvas_pos.x + mid_px, canvas_pos.y), ImVec2(canvas_pos.x + mid_px, canvas_pos.y + room_size_px), line_color, thickness);
331 draw_list->AddLine(ImVec2(canvas_pos.x, canvas_pos.y + mid_px), ImVec2(canvas_pos.x + room_size_px, canvas_pos.y + mid_px), line_color, thickness);
332
333 std::string label = absl::StrFormat("Layout %d", room.layout_id());
334 draw_list->AddText(ImVec2(canvas_pos.x + 6.0f, canvas_pos.y + 6.0f), ImGui::GetColorU32(theme.text_secondary_color), label.c_str());
335}
336
338 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
339 const zelda3::Room& room, const std::bitset<256>& minecart_sprite_ids,
340 const TrackCollisionConfig& config) {
341 const auto& theme = AgentUI::GetTheme();
342 auto map_or = zelda3::LoadCustomCollisionMap(room.rom(), room.id());
343 const bool has_collision = map_or.ok() && map_or.value().has_data;
344
345 const ImU32 ok_color = ImGui::GetColorU32(theme.status_success);
346 const ImU32 warn_color = ImGui::GetColorU32(theme.status_warning);
347 const ImU32 unknown_color = ImGui::GetColorU32(theme.status_inactive);
348
349 for (const auto& sprite : room.GetSprites()) {
350 if (sprite.id() >= 256 || !minecart_sprite_ids[sprite.id()]) continue;
351
352 bool on_stop_tile = false;
353 bool has_tile = false;
354 if (has_collision) {
355 int tile_x = sprite.x() * 2;
356 int tile_y = sprite.y() * 2;
357 if (tile_x >= 0 && tile_x < 64 && tile_y >= 0 && tile_y < 64) {
358 uint8_t tile = map_or.value().tiles[static_cast<size_t>(tile_y * 64 + tile_x)];
359 has_tile = true;
360 on_stop_tile = config.stop_tiles[tile];
361 }
362 }
363
364 ImU32 color = has_tile ? (on_stop_tile ? ok_color : warn_color) : unknown_color;
365 const float px = static_cast<float>(sprite.x() * 16) * scale;
366 const float py = static_cast<float>(sprite.y() * 16) * scale;
367 ImVec2 min(canvas_pos.x + px, canvas_pos.y + py);
368 ImVec2 max(min.x + (16.0f * scale), min.y + (16.0f * scale));
369 draw_list->AddRect(min, max, color, 0.0f, 0, 2.0f);
370 }
371}
372
374 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
375 const zelda3::Room& room, const CollisionOverlayCache& cache) {
376
377 std::array<bool, 64 * 64> object_grid{};
378 for (const auto& obj : room.GetTileObjects()) {
379 if (obj.id_ != 0x31) continue;
381 int base_x = obj.x() * 2;
382 int base_y = obj.y() * 2;
383 for (int dy = 0; dy < dim.height_tiles && (base_y + dy) < 64; ++dy) {
384 for (int dx = 0; dx < dim.width_tiles && (base_x + dx) < 64; ++dx) {
385 object_grid[static_cast<size_t>((base_y + dy) * 64 + (base_x + dx))] = true;
386 }
387 }
388 }
389
390 std::array<bool, 64 * 64> collision_grid{};
391 for (const auto& entry : cache.entries) {
392 collision_grid[static_cast<size_t>(entry.y * 64 + entry.x)] = true;
393 }
394
395 const auto& theme = AgentUI::GetTheme();
396 const ImU32 gap_color = ImGui::ColorConvertFloat4ToU32(theme.status_warning);
397 const ImU32 orphan_color = ImGui::ColorConvertFloat4ToU32(theme.status_inactive);
398 const float tile_size = 8.0f * scale;
399
400 for (int y = 0; y < 64; ++y) {
401 for (int x = 0; x < 64; ++x) {
402 size_t idx = static_cast<size_t>(y * 64 + x);
403 if (object_grid[idx] == collision_grid[idx]) continue;
404
405 const float px = static_cast<float>(x * 8) * scale;
406 const float py = static_cast<float>(y * 8) * scale;
407 ImVec2 min_pt(canvas_pos.x + px, canvas_pos.y + py);
408 ImVec2 max_pt(min_pt.x + tile_size, min_pt.y + tile_size);
409 draw_list->AddRectFilled(min_pt, max_pt, object_grid[idx] ? gap_color : orphan_color);
410 }
411 }
412}
413
415 ImDrawList* draw_list, const ImVec2& canvas_pos, float scale,
416 const CollisionOverlayCache& cache) {
417 const auto& theme = AgentUI::GetTheme();
418 std::array<bool, 64 * 64> occupied{};
419 for (const auto& entry : cache.entries) {
420 occupied[static_cast<size_t>(entry.y * 64 + entry.x)] = true;
421 }
422
423 const float tile_size = 8.0f * scale;
424 const float half_tile = tile_size * 0.5f;
425 const ImU32 route_color = ImGui::ColorConvertFloat4ToU32(theme.status_active);
426 const float thickness = std::max(1.0f, 1.5f * scale);
427
428 for (const auto& entry : cache.entries) {
429 float cx = canvas_pos.x + static_cast<float>(entry.x * 8) * scale + half_tile;
430 float cy = canvas_pos.y + static_cast<float>(entry.y * 8) * scale + half_tile;
431
432 if (entry.x + 1 < 64 && occupied[static_cast<size_t>(entry.y * 64 + (entry.x + 1))]) {
433 draw_list->AddLine(ImVec2(cx, cy), ImVec2(cx + tile_size, cy), route_color, thickness);
434 }
435 if (entry.y + 1 < 64 && occupied[static_cast<size_t>((entry.y + 1) * 64 + entry.x)]) {
436 draw_list->AddLine(ImVec2(cx, cy), ImVec2(cx, cy + tile_size), route_color, thickness);
437 }
438 }
439}
440
441} // 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 std::pair< int, int > ScreenToRoomCoordinates(const ImVec2 &screen_pos, const ImVec2 &zero_point, float scale)
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:384
const WaterFillZoneMap & water_fill_zone() const
Definition room.h:411
auto rom() const
Definition room.h:613
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:214
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:314
uint8_t layout_id() const
Definition room.h:572
int id() const
Definition room.h:566
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