yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_viewer.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <vector>
5
8
9namespace yaze::editor {
10
11namespace {
12
13constexpr double kChangePingDurationSeconds = 0.85;
14constexpr int kDungeonCanvasPixelSize = 512;
15constexpr size_t kRecentRoomLimit = 5;
16
18 const zelda3::RoomObject& rhs) {
19 return lhs.id_ == rhs.id_ && lhs.x() == rhs.x() && lhs.y() == rhs.y() &&
20 lhs.size() == rhs.size() && lhs.GetLayerValue() == rhs.GetLayerValue();
21}
22
23} // namespace
24
26 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
27 return;
28 }
29
31 std::remove(recently_visited_rooms_.begin(),
32 recently_visited_rooms_.end(), room_id),
34 recently_visited_rooms_.insert(recently_visited_rooms_.begin(), room_id);
35 if (recently_visited_rooms_.size() > kRecentRoomLimit) {
36 recently_visited_rooms_.resize(kRecentRoomLimit);
37 }
38}
39
44
46 change_ping_rects_.clear();
47 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
48}
49
50void DungeonCanvasViewer::TriggerCanvasPingRect(int pixel_x, int pixel_y,
51 int pixel_w, int pixel_h) {
52 change_ping_rects_.clear();
53 const int x = std::clamp(pixel_x, 0, kDungeonCanvasPixelSize - 1);
54 const int y = std::clamp(pixel_y, 0, kDungeonCanvasPixelSize - 1);
55 const int w =
56 std::clamp(pixel_w, 1, std::max(1, kDungeonCanvasPixelSize - x));
57 const int h =
58 std::clamp(pixel_h, 1, std::max(1, kDungeonCanvasPixelSize - y));
59 change_ping_rects_.push_back(ChangePingRect{x, y, w, h});
60 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
61}
62
64 const std::vector<zelda3::RoomObject>& previous_objects,
65 const std::vector<zelda3::RoomObject>& next_objects) {
66 change_ping_rects_.clear();
67
68 auto append_object_rect = [this](const zelda3::RoomObject& object) {
69 const auto [x, y, w, h] =
71 change_ping_rects_.push_back(
72 ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
73 };
74
75 const size_t count = std::max(previous_objects.size(), next_objects.size());
76 for (size_t i = 0; i < count; ++i) {
77 const bool has_previous = i < previous_objects.size();
78 const bool has_next = i < next_objects.size();
79 if (has_previous && has_next &&
80 HasSameObjectIdentity(previous_objects[i], next_objects[i])) {
81 continue;
82 }
83 if (has_previous) {
84 append_object_rect(previous_objects[i]);
85 }
86 if (has_next) {
87 append_object_rect(next_objects[i]);
88 }
89 }
90
91 if (change_ping_rects_.empty()) {
93 return;
94 }
95
96 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
97}
98
100 auto apply_list = [](std::array<bool, 256>& dest,
101 const std::vector<uint16_t>& values) {
102 dest.fill(false);
103 for (uint16_t value : values) {
104 if (value < 256) {
105 dest[value] = true;
106 }
107 }
108 };
109 auto ids_valid = [](const std::vector<uint16_t>& values) {
110 std::array<bool, 256> seen{};
111 for (uint16_t value : values) {
112 if (value >= seen.size() || seen[value]) {
113 return false;
114 }
115 seen[value] = true;
116 }
117 return true;
118 };
119
120 std::vector<uint16_t> default_track_tile_list;
121 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
122 default_track_tile_list.push_back(tile);
123 }
124 const std::vector<uint16_t> default_stop_tile_list = {0xB7, 0xB8, 0xB9, 0xBA};
125 const std::vector<uint16_t> default_switch_tile_list = {0xD0, 0xD1, 0xD2,
126 0xD3};
127
128 const auto& track_tiles =
131 : default_track_tile_list;
132 apply_list(track_collision_config_.track_tiles, track_tiles);
133 track_tile_order_ = track_tiles;
134
135 const auto& stop_tiles =
138 : default_stop_tile_list;
139 apply_list(track_collision_config_.stop_tiles, stop_tiles);
140
141 const auto& switch_tiles =
144 : default_switch_tile_list;
145 apply_list(track_collision_config_.switch_tiles, switch_tiles);
146 switch_tile_order_ = switch_tiles;
147
149 (track_tile_order_.size() == default_track_tile_list.size()) &&
150 (switch_tile_order_.size() == default_switch_tile_list.size()) &&
151 ids_valid(track_tile_order_) && ids_valid(switch_tile_order_);
152
153 minecart_sprite_ids_.reset();
154 std::vector<uint16_t> minecart_ids = {0xA3};
157 }
158 for (uint16_t id : minecart_ids) {
159 if (id < minecart_sprite_ids_.size()) {
160 minecart_sprite_ids_[id] = true;
161 }
162 }
163
165}
166
167void DungeonCanvasViewer::Draw(int room_id) {
168 DrawDungeonCanvas(room_id);
169}
170
199
201 current_room_id_ = room_id;
202 if (!ValidateRoomCanvasRequest(room_id)) {
203 return;
204 }
205 RecordVisitedRoom(room_id);
206
207 ImGui::BeginGroup();
208 const auto frame_opts = BuildRoomCanvasFrameOptions();
210 zelda3::Room* active_room = PrepareActiveRoomForCanvasFrame(room_id);
212 ImGui::EndGroup();
213
215
216 auto canvas_rt = gui::BeginCanvas(canvas_, frame_opts);
217 SyncCanvasCaptureRegion(canvas_rt);
219 canvas_rt.scrolling = canvas_.scrolling();
220
221 touch_handler_.ProcessForCanvas(canvas_rt.canvas_p0, canvas_rt.canvas_sz,
222 canvas_rt.hovered);
224
227
228 if (active_room != nullptr) {
229 DrawRoomCanvasContent(canvas_rt, *active_room, room_id);
230 DrawRoomCanvasOverlays(canvas_rt, *active_room, room_id);
231 DrawChangePingOverlay(canvas_rt, *active_room);
232 }
233
235
236 gui::EndCanvas(canvas_, canvas_rt, frame_opts);
238}
239
241 const gui::CanvasRuntime& canvas_rt, const zelda3::Room& room) {
242 if (!canvas_rt.draw_list || change_ping_start_time_ < 0.0 ||
243 !ImGui::GetCurrentContext()) {
244 return;
245 }
246
247 const double elapsed = ImGui::GetTime() - change_ping_start_time_;
248 if (elapsed >= kChangePingDurationSeconds) {
250 change_ping_rects_.clear();
251 return;
252 }
253
254 std::vector<ChangePingRect> rects = change_ping_rects_;
255 if (rects.empty()) {
256 const auto& objects = room.GetTileObjects();
257 for (size_t index : object_interaction_.GetSelectedObjectIndices()) {
258 if (index >= objects.size()) {
259 continue;
260 }
261 const auto [x, y, w, h] =
263 objects[index]);
264 rects.push_back(ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
265 }
266
267 auto selected_entities =
269 if (selected_entities.empty() && object_interaction_.HasEntitySelection()) {
270 selected_entities.push_back(object_interaction_.GetSelectedEntity());
271 }
272 for (const SelectedEntity entity : selected_entities) {
273 switch (entity.type) {
274 case EntityType::Door: {
275 const auto& doors = room.GetDoors();
276 if (entity.index < doors.size()) {
277 const auto [x, y, w, h] = doors[entity.index].GetEditorBounds();
278 rects.push_back(
279 ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
280 }
281 break;
282 }
283 case EntityType::Sprite: {
284 const auto& sprites = room.GetSprites();
285 if (entity.index < sprites.size()) {
286 rects.push_back(ChangePingRect{sprites[entity.index].x() * 16,
287 sprites[entity.index].y() * 16, 16,
288 16});
289 }
290 break;
291 }
292 case EntityType::Item: {
293 const auto& pot_items = room.GetPotItems();
294 if (entity.index < pot_items.size()) {
295 rects.push_back(ChangePingRect{pot_items[entity.index].GetPixelX(),
296 pot_items[entity.index].GetPixelY(),
297 16, 16});
298 }
299 break;
300 }
302 case EntityType::None:
303 break;
304 }
305 }
306 }
307
308 if (rects.empty()) {
309 rects.push_back(
310 ChangePingRect{0, 0, kDungeonCanvasPixelSize, kDungeonCanvasPixelSize});
311 }
312
313 const float progress =
314 static_cast<float>(elapsed / kChangePingDurationSeconds);
315 const float remaining = std::clamp(1.0f - progress, 0.0f, 1.0f);
316 const float expand = 4.0f + 12.0f * progress;
317 const auto& theme = AgentUI::GetTheme();
318 ImVec4 fill = theme.dungeon_selection_pulsing;
319 fill.w = 0.18f * remaining;
320 ImVec4 border = theme.dungeon_selection_primary;
321 border.w = 0.95f * remaining;
322 const ImU32 fill_color = ImGui::GetColorU32(fill);
323 const ImU32 border_color = ImGui::GetColorU32(border);
324
325 const ImVec2 viewport_min = canvas_rt.canvas_p0;
326 const ImVec2 viewport_max(canvas_rt.canvas_p0.x + canvas_rt.canvas_sz.x,
327 canvas_rt.canvas_p0.y + canvas_rt.canvas_sz.y);
328
329 for (const ChangePingRect& rect : rects) {
330 const ImVec2 min(canvas_rt.canvas_p0.x + canvas_rt.scrolling.x +
331 static_cast<float>(rect.x) * canvas_rt.scale - expand,
332 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y +
333 static_cast<float>(rect.y) * canvas_rt.scale - expand);
334 const ImVec2 max(
335 min.x + static_cast<float>(rect.w) * canvas_rt.scale + expand * 2.0f,
336 min.y + static_cast<float>(rect.h) * canvas_rt.scale + expand * 2.0f);
337
338 const bool visible = max.x >= viewport_min.x && min.x <= viewport_max.x &&
339 max.y >= viewport_min.y && min.y <= viewport_max.y;
340 if (visible) {
341 canvas_rt.draw_list->AddRectFilled(min, max, fill_color, 2.0f);
342 canvas_rt.draw_list->AddRect(min, max, border_color, 2.0f, 0,
343 2.0f + 2.0f * remaining);
344 continue;
345 }
346
347 const ImVec2 center(
348 canvas_rt.canvas_p0.x + canvas_rt.scrolling.x +
349 (static_cast<float>(rect.x) + static_cast<float>(rect.w) * 0.5f) *
350 canvas_rt.scale,
351 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y +
352 (static_cast<float>(rect.y) + static_cast<float>(rect.h) * 0.5f) *
353 canvas_rt.scale);
354 const float marker_min_x = viewport_min.x + 8.0f;
355 const float marker_min_y = viewport_min.y + 8.0f;
356 const float marker_max_x = std::max(marker_min_x, viewport_max.x - 8.0f);
357 const float marker_max_y = std::max(marker_min_y, viewport_max.y - 8.0f);
358 const ImVec2 clamped(std::clamp(center.x, marker_min_x, marker_max_x),
359 std::clamp(center.y, marker_min_y, marker_max_y));
360 canvas_rt.draw_list->AddCircleFilled(clamped, 5.0f + 4.0f * remaining,
361 fill_color);
362 canvas_rt.draw_list->AddCircle(clamped, 7.0f + 6.0f * remaining,
363 border_color, 18, 2.0f);
364 }
365}
366
367} // namespace yaze::editor
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
void DrawChangePingOverlay(const gui::CanvasRuntime &canvas_rt, const zelda3::Room &room)
void SyncCanvasCaptureRegion(const gui::CanvasRuntime &canvas_rt)
void SetProject(const project::YazeProject *project)
zelda3::Room * PrepareActiveRoomForCanvasFrame(int room_id)
void RefreshRomBackedState(Rom *rom, zelda3::GameData *game_data, DungeonRoomStore *rooms, int room_id)
DungeonObjectInteraction object_interaction_
void DrawRoomCanvasContent(const gui::CanvasRuntime &canvas_rt, zelda3::Room &room, int room_id)
void TriggerCanvasPingRect(int pixel_x, int pixel_y, int pixel_w, int pixel_h)
const project::YazeProject * project_
std::unordered_map< int, DungeonRenderingHelpers::CollisionOverlayCache > collision_overlay_cache_
void TriggerObjectChangePing(const std::vector< zelda3::RoomObject > &previous_objects, const std::vector< zelda3::RoomObject > &next_objects)
gui::CanvasFrameOptions BuildRoomCanvasFrameOptions() const
const project::YazeProject * project() const
void ConsumePendingCanvasScroll(const gui::CanvasRuntime &canvas_rt)
std::map< int, zelda3::RoomLayerManager > room_layer_managers_
std::vector< ObjectRenderCache > object_render_cache_
DungeonRenderingHelpers::TrackCollisionConfig track_collision_config_
void SetRooms(DungeonRoomStore *rooms)
std::vector< ChangePingRect > change_ping_rects_
void DrawRoomCanvasOverlays(const gui::CanvasRuntime &rt, zelda3::Room &room, int room_id)
void SetGameData(zelda3::GameData *game_data)
void SetCurrentRoom(DungeonRoomStore *rooms, int room_id)
std::vector< size_t > GetSelectedObjectIndices() const
InteractionCoordinator & entity_coordinator()
Get the interaction coordinator for entity handling.
const std::vector< SelectedEntity > & GetSelectedEntities() const
void ClearAllEntitySelections()
Clear all entity selections.
void CancelCurrentMode()
Cancel current mode and return to Select.
void Update()
Update touch state each frame.
void ProcessForCanvas(ImVec2 canvas_p0, ImVec2 canvas_sz, bool is_hovered)
Process touch gestures for the current canvas bounds.
auto scrolling() const
Definition canvas.h:350
static DimensionService & Get()
std::tuple< int, int, int, int > GetSelectionBoundsPixels(const RoomObject &obj) const
uint8_t size() const
Definition room_object.h:90
uint8_t GetLayerValue() const
const std::vector< Door > & GetDoors() const
Definition room.h:350
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:253
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:382
const std::vector< PotItem > & GetPotItems() const
Definition room.h:366
const AgentUITheme & GetTheme()
bool HasSameObjectIdentity(const zelda3::RoomObject &lhs, const zelda3::RoomObject &rhs)
Editors are the view controllers for the application.
void EndCanvas(Canvas &canvas)
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
constexpr int kNumberOfRooms
Represents a selected entity in the dungeon editor.
std::vector< uint16_t > minecart_sprite_ids
Definition project.h:101
std::vector< uint16_t > track_stop_tiles
Definition project.h:96
std::vector< uint16_t > track_tiles
Definition project.h:95
std::vector< uint16_t > track_switch_tiles
Definition project.h:97
Modern project structure with comprehensive settings consolidation.
Definition project.h:172
DungeonOverlaySettings dungeon_overlay
Definition project.h:202