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
28 if (room_id < 0 || room_id >= zelda3::kNumberOfRooms) {
29 return;
30 }
31
33 std::remove(recently_visited_rooms_.begin(),
34 recently_visited_rooms_.end(), room_id),
36 recently_visited_rooms_.insert(recently_visited_rooms_.begin(), room_id);
37 if (recently_visited_rooms_.size() > kRecentRoomLimit) {
38 recently_visited_rooms_.resize(kRecentRoomLimit);
39 }
40}
41
49
54
56 int room_id) {
57 auto& state = room_layer_managers_[room_id];
58 const auto* room = rooms_ ? rooms_->GetIfMaterialized(room_id) : nullptr;
59 // Initialize before controls can edit the manager. Reapply room defaults only
60 // when the header changes, not on every draw, so manual blend choices persist.
61 if (room && (!state.room_settings ||
62 state.room_settings->first != room->layer_merging() ||
63 state.room_settings->second != room->effect())) {
64 state.manager.ApplyLayerMerging(room->layer_merging());
65 state.manager.ApplyRoomEffect(room->effect());
66 state.room_settings = std::make_pair(room->layer_merging(), room->effect());
67 }
68 return state.manager;
69}
70
72 change_ping_rects_.clear();
73 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
74}
75
76void DungeonCanvasViewer::TriggerCanvasPingRect(int pixel_x, int pixel_y,
77 int pixel_w, int pixel_h) {
78 change_ping_rects_.clear();
79 const int x = std::clamp(pixel_x, 0, kDungeonCanvasPixelSize - 1);
80 const int y = std::clamp(pixel_y, 0, kDungeonCanvasPixelSize - 1);
81 const int w =
82 std::clamp(pixel_w, 1, std::max(1, kDungeonCanvasPixelSize - x));
83 const int h =
84 std::clamp(pixel_h, 1, std::max(1, kDungeonCanvasPixelSize - y));
85 change_ping_rects_.push_back(ChangePingRect{x, y, w, h});
86 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
87}
88
90 const std::vector<zelda3::RoomObject>& previous_objects,
91 const std::vector<zelda3::RoomObject>& next_objects) {
92 change_ping_rects_.clear();
93
94 auto append_object_rect = [this](const zelda3::RoomObject& object) {
95 const auto [x, y, w, h] =
97 change_ping_rects_.push_back(
98 ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
99 };
100
101 const size_t count = std::max(previous_objects.size(), next_objects.size());
102 for (size_t i = 0; i < count; ++i) {
103 const bool has_previous = i < previous_objects.size();
104 const bool has_next = i < next_objects.size();
105 if (has_previous && has_next &&
106 HasSameObjectIdentity(previous_objects[i], next_objects[i])) {
107 continue;
108 }
109 if (has_previous) {
110 append_object_rect(previous_objects[i]);
111 }
112 if (has_next) {
113 append_object_rect(next_objects[i]);
114 }
115 }
116
117 if (change_ping_rects_.empty()) {
119 return;
120 }
121
122 change_ping_start_time_ = ImGui::GetCurrentContext() ? ImGui::GetTime() : 0.0;
123}
124
126 auto apply_list = [](std::array<bool, 256>& dest,
127 const std::vector<uint16_t>& values) {
128 dest.fill(false);
129 for (uint16_t value : values) {
130 if (value < 256) {
131 dest[value] = true;
132 }
133 }
134 };
135 auto ids_valid = [](const std::vector<uint16_t>& values) {
136 std::array<bool, 256> seen{};
137 for (uint16_t value : values) {
138 if (value >= seen.size() || seen[value]) {
139 return false;
140 }
141 seen[value] = true;
142 }
143 return true;
144 };
145
146 std::vector<uint16_t> default_track_tile_list;
147 for (uint16_t tile = 0xB0; tile <= 0xBE; ++tile) {
148 default_track_tile_list.push_back(tile);
149 }
150 const std::vector<uint16_t> default_stop_tile_list = {0xB7, 0xB8, 0xB9, 0xBA};
151 const std::vector<uint16_t> default_switch_tile_list = {0xD0, 0xD1, 0xD2,
152 0xD3};
153
154 const auto& track_tiles =
157 : default_track_tile_list;
158 apply_list(track_collision_config_.track_tiles, track_tiles);
159 track_tile_order_ = track_tiles;
160
161 const auto& stop_tiles =
164 : default_stop_tile_list;
165 apply_list(track_collision_config_.stop_tiles, stop_tiles);
166
167 const auto& switch_tiles =
170 : default_switch_tile_list;
171 apply_list(track_collision_config_.switch_tiles, switch_tiles);
172 switch_tile_order_ = switch_tiles;
173
175 (track_tile_order_.size() == default_track_tile_list.size()) &&
176 (switch_tile_order_.size() == default_switch_tile_list.size()) &&
177 ids_valid(track_tile_order_) && ids_valid(switch_tile_order_);
178
179 minecart_sprite_ids_.reset();
180 std::vector<uint16_t> minecart_ids = {0xA3};
183 }
184 for (uint16_t id : minecart_ids) {
185 if (id < minecart_sprite_ids_.size()) {
186 minecart_sprite_ids_[id] = true;
187 }
188 }
189
191}
192
193void DungeonCanvasViewer::Draw(int room_id) {
194 DrawDungeonCanvas(room_id);
195}
196
198 zelda3::GameData* game_data,
199 DungeonRoomStore* rooms,
200 int room_id) {
201 // Refresh can replace Room values in place while all backing pointers stay
202 // identical. Discard presentation stamps before those replacements occur.
210
211 SetRom(rom);
214 current_room_id_ = room_id;
216
218 object_render_cache_.clear();
219 room_layer_managers_.clear();
221 prev_blockset_ = -1;
222 prev_palette_ = -1;
223 prev_layout_ = -1;
224 prev_spriteset_ = -1;
226 change_ping_rects_.clear();
228}
229
235
237 if (ImGui::GetCurrentContext() == nullptr) {
238 return;
239 }
240 const int frame = ImGui::GetFrameCount();
242 return;
243 }
245
246 // Keep one completed-frame grace period. This bounds the cache to the union
247 // of recently visible rooms without retiring a texture still referenced by
248 // another deferred ImGui draw in this frame.
249 std::erase_if(connected_composite_outputs_, [frame](const auto& item) {
250 return item.second.last_used_frame >= 0 &&
251 item.second.last_used_frame < frame - 1;
252 });
253}
254
256 current_room_id_ = room_id;
257 if (!ValidateRoomCanvasRequest(room_id)) {
258 return;
259 }
260 RecordVisitedRoom(room_id);
262
263 ImGui::BeginGroup();
264 const auto frame_opts = BuildRoomCanvasFrameOptions();
266 zelda3::Room* active_room = PrepareActiveRoomForCanvasFrame(room_id);
268 ImGui::EndGroup();
269
270 // BeginCanvas renders context-menu callbacks before the room draw pass. Bind
271 // this viewer's bitmap and palette first so issue capture cannot sample a
272 // compare viewer or auxiliary preview left active by the previous frame.
273 if (active_room != nullptr) {
275 }
276
278
279 auto canvas_rt = gui::BeginCanvas(canvas_, frame_opts);
280 const bool pointer_pressed = ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
281 ImGui::IsMouseClicked(ImGuiMouseButton_Right) ||
282 ImGui::IsMouseClicked(ImGuiMouseButton_Middle);
283 UpdateRoomCanvasShortcutFocus(canvas_rt.hovered, pointer_pressed,
284 ImGui::GetFrameCount());
285 SyncCanvasCaptureRegion(canvas_rt);
287 canvas_rt.scrolling = canvas_.scrolling();
288
289 touch_handler_.ProcessForCanvas(canvas_rt.canvas_p0, canvas_rt.canvas_sz,
290 canvas_rt.hovered);
292
295
296 if (active_room != nullptr) {
297 DrawRoomCanvasContent(canvas_rt, *active_room, room_id);
298 DrawRoomCanvasOverlays(canvas_rt, *active_room, room_id);
299 DrawChangePingOverlay(canvas_rt, *active_room);
300 }
301
303
304 gui::EndCanvas(canvas_, canvas_rt, frame_opts);
306}
307
309 bool pointer_pressed,
310 int frame_index) {
311 room_canvas_last_draw_frame_ = frame_index;
312 if (pointer_pressed) {
314 }
315}
316
322
324 const gui::CanvasRuntime& canvas_rt, const zelda3::Room& room) {
325 if (!canvas_rt.draw_list || change_ping_start_time_ < 0.0 ||
326 !ImGui::GetCurrentContext()) {
327 return;
328 }
329
330 const double elapsed = ImGui::GetTime() - change_ping_start_time_;
331 if (elapsed >= kChangePingDurationSeconds) {
333 change_ping_rects_.clear();
334 return;
335 }
336
337 std::vector<ChangePingRect> rects = change_ping_rects_;
338 if (rects.empty()) {
339 const auto& objects = room.GetTileObjects();
340 for (size_t index : object_interaction_.GetSelectedObjectIndices()) {
341 if (index >= objects.size()) {
342 continue;
343 }
344 const auto [x, y, w, h] =
346 objects[index]);
347 rects.push_back(ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
348 }
349
350 auto selected_entities =
352 if (selected_entities.empty() && object_interaction_.HasEntitySelection()) {
353 selected_entities.push_back(object_interaction_.GetSelectedEntity());
354 }
355 for (const SelectedEntity entity : selected_entities) {
356 switch (entity.type) {
357 case EntityType::Door: {
358 const auto& doors = room.GetDoors();
359 if (entity.index < doors.size()) {
360 const auto [x, y, w, h] = doors[entity.index].GetEditorBounds();
361 rects.push_back(
362 ChangePingRect{x, y, std::max(w, 8), std::max(h, 8)});
363 }
364 break;
365 }
366 case EntityType::Sprite: {
367 const auto& sprites = room.GetSprites();
368 if (entity.index < sprites.size()) {
369 rects.push_back(ChangePingRect{sprites[entity.index].x() * 16,
370 sprites[entity.index].y() * 16, 16,
371 16});
372 }
373 break;
374 }
375 case EntityType::Item: {
376 const auto& pot_items = room.GetPotItems();
377 if (entity.index < pot_items.size()) {
378 rects.push_back(ChangePingRect{pot_items[entity.index].GetPixelX(),
379 pot_items[entity.index].GetPixelY(),
380 16, 16});
381 }
382 break;
383 }
385 case EntityType::None:
386 break;
387 }
388 }
389 }
390
391 if (rects.empty()) {
392 rects.push_back(
393 ChangePingRect{0, 0, kDungeonCanvasPixelSize, kDungeonCanvasPixelSize});
394 }
395
396 const float progress =
397 static_cast<float>(elapsed / kChangePingDurationSeconds);
398 const float remaining = std::clamp(1.0f - progress, 0.0f, 1.0f);
399 const float expand = 4.0f + 12.0f * progress;
400 const auto& theme = AgentUI::GetTheme();
401 ImVec4 fill = theme.dungeon_selection_pulsing;
402 fill.w = 0.18f * remaining;
403 ImVec4 border = theme.dungeon_selection_primary;
404 border.w = 0.95f * remaining;
405 const ImU32 fill_color = ImGui::GetColorU32(fill);
406 const ImU32 border_color = ImGui::GetColorU32(border);
407
408 const ImVec2 viewport_min = canvas_rt.canvas_p0;
409 const ImVec2 viewport_max(canvas_rt.canvas_p0.x + canvas_rt.canvas_sz.x,
410 canvas_rt.canvas_p0.y + canvas_rt.canvas_sz.y);
411
412 for (const ChangePingRect& rect : rects) {
413 const ImVec2 min(canvas_rt.canvas_p0.x + canvas_rt.scrolling.x +
414 static_cast<float>(rect.x) * canvas_rt.scale - expand,
415 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y +
416 static_cast<float>(rect.y) * canvas_rt.scale - expand);
417 const ImVec2 max(
418 min.x + static_cast<float>(rect.w) * canvas_rt.scale + expand * 2.0f,
419 min.y + static_cast<float>(rect.h) * canvas_rt.scale + expand * 2.0f);
420
421 const bool visible = max.x >= viewport_min.x && min.x <= viewport_max.x &&
422 max.y >= viewport_min.y && min.y <= viewport_max.y;
423 if (visible) {
424 canvas_rt.draw_list->AddRectFilled(min, max, fill_color, 2.0f);
425 canvas_rt.draw_list->AddRect(min, max, border_color, 2.0f, 0,
426 2.0f + 2.0f * remaining);
427 continue;
428 }
429
430 const ImVec2 center(
431 canvas_rt.canvas_p0.x + canvas_rt.scrolling.x +
432 (static_cast<float>(rect.x) + static_cast<float>(rect.w) * 0.5f) *
433 canvas_rt.scale,
434 canvas_rt.canvas_p0.y + canvas_rt.scrolling.y +
435 (static_cast<float>(rect.y) + static_cast<float>(rect.h) * 0.5f) *
436 canvas_rt.scale);
437 const float marker_min_x = viewport_min.x + 8.0f;
438 const float marker_min_y = viewport_min.y + 8.0f;
439 const float marker_max_x = std::max(marker_min_x, viewport_max.x - 8.0f);
440 const float marker_max_y = std::max(marker_min_y, viewport_max.y - 8.0f);
441 const ImVec2 clamped(std::clamp(center.x, marker_min_x, marker_max_x),
442 std::clamp(center.y, marker_min_y, marker_max_y));
443 canvas_rt.draw_list->AddCircleFilled(clamped, 5.0f + 4.0f * remaining,
444 fill_color);
445 canvas_rt.draw_list->AddCircle(clamped, 7.0f + 6.0f * remaining,
446 border_color, 18, 2.0f);
447 }
448}
449
450} // 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 UpdateRoomCanvasShortcutFocus(bool hovered, bool pointer_pressed, int frame_index)
zelda3::SpritePreviewResourceCache sprite_preview_resources_
void SetProject(const project::YazeProject *project)
std::map< int, ConnectedRoomCompositeEntry > connected_composite_outputs_
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_
gfx::Bitmap * PrepareRoomCompositeBitmap(int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
void TriggerObjectChangePing(const std::vector< zelda3::RoomObject > &previous_objects, const std::vector< zelda3::RoomObject > &next_objects)
gui::CanvasFrameOptions BuildRoomCanvasFrameOptions() const
util::LruCache< SpritePreviewKey, CachedSpritePreview > sprite_preview_cache_
bool HasRoomCanvasShortcutFocusForFrame(int frame_index) const
const project::YazeProject * project() const
void ConsumePendingCanvasScroll(const gui::CanvasRuntime &canvas_rt)
std::vector< ObjectRenderCache > object_render_cache_
DungeonRenderingHelpers::TrackCollisionConfig track_collision_config_
void SetRooms(DungeonRoomStore *rooms)
std::vector< ChangePingRect > change_ping_rects_
std::map< int, RoomLayerState > room_layer_managers_
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.
zelda3::Room * GetIfMaterialized(int room_id)
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:356
static DimensionService & Get()
std::tuple< int, int, int, int > GetSelectionBoundsPixels(const RoomObject &obj) const
RoomLayerManager - Manages layer visibility and compositing.
uint8_t size() const
Definition room_object.h:89
uint8_t GetLayerValue() const
const std::vector< Door > & GetDoors() const
Definition room.h:373
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:276
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:405
const std::vector< PotItem > & GetPotItems() const
Definition room.h:389
bool SetContext(std::string_view project_path, std::string_view assets_path, std::string_view hack_name)
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