yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_usage_tracker.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <vector>
6
8#include "imgui/imgui.h"
9
10namespace yaze::editor {
11
12namespace {
13
14// Returns a heat-map color from green (low) to yellow (mid) to red (high).
15// |t| is clamped to [0, 1].
16ImU32 HeatColor(float t) {
17 t = std::clamp(t, 0.0f, 1.0f);
18 float r, g, b;
19 if (t < 0.5f) {
20 // Green -> Yellow
21 float s = t * 2.0f;
22 r = s;
23 g = 1.0f;
24 b = 0.0f;
25 } else {
26 // Yellow -> Red
27 float s = (t - 0.5f) * 2.0f;
28 r = 1.0f;
29 g = 1.0f - s;
30 b = 0.0f;
31 }
32 return IM_COL32(static_cast<int>(r * 255), static_cast<int>(g * 255),
33 static_cast<int>(b * 255), 200);
34}
35
36// Find the maximum count value in a usage map.
37int MaxCount(const absl::flat_hash_map<uint16_t, int>& usage_map) {
38 int max_val = 0;
39 for (const auto& [key, count] : usage_map) {
40 if (count > max_val)
41 max_val = count;
42 }
43 return max_val;
44}
45
46} // namespace
47
49 blockset_usage_.clear();
50 spriteset_usage_.clear();
51 palette_usage_.clear();
52
53 for (int room_id = 0; room_id < static_cast<int>(rooms.size()); ++room_id) {
54 const auto& room = rooms[room_id];
55 if (blockset_usage_.find(room.blockset()) == blockset_usage_.end()) {
56 blockset_usage_[room.blockset()] = 1;
57 } else {
58 blockset_usage_[room.blockset()] += 1;
59 }
60
61 if (spriteset_usage_.find(room.spriteset()) == spriteset_usage_.end()) {
62 spriteset_usage_[room.spriteset()] = 1;
63 } else {
64 spriteset_usage_[room.spriteset()] += 1;
65 }
66
67 if (palette_usage_.find(room.palette()) == palette_usage_.end()) {
68 palette_usage_[room.palette()] = 1;
69 } else {
70 palette_usage_[room.palette()] += 1;
71 }
72 }
73}
74
76 if (ImGui::Button(tr("Refresh"))) {
78 }
79
80 ImGui::Text(tr("Usage Statistics"));
81 ImGui::Separator();
82
83 ImGui::Text(tr("Blocksets: %zu used"), blockset_usage_.size());
84 ImGui::Text(tr("Spritesets: %zu used"), spriteset_usage_.size());
85 ImGui::Text(tr("Palettes: %zu used"), palette_usage_.size());
86
87 ImGui::Separator();
88
89 // Detailed usage breakdown
90 if (ImGui::CollapsingHeader(tr("Blockset Usage"))) {
91 for (const auto& [blockset, count] : blockset_usage_) {
92 ImGui::Text(tr("Blockset 0x%02X: %d rooms"), blockset, count);
93 }
94 }
95
96 if (ImGui::CollapsingHeader(tr("Spriteset Usage"))) {
97 for (const auto& [spriteset, count] : spriteset_usage_) {
98 ImGui::Text(tr("Spriteset 0x%02X: %d rooms"), spriteset, count);
99 }
100 }
101
102 if (ImGui::CollapsingHeader(tr("Palette Usage"))) {
103 for (const auto& [palette, count] : palette_usage_) {
104 ImGui::Text(tr("Palette 0x%02X: %d rooms"), palette, count);
105 }
106 }
107}
108
110 if (blockset_usage_.empty() && spriteset_usage_.empty() &&
111 palette_usage_.empty()) {
112 ImGui::TextDisabled(tr("No usage data. Load rooms and click Refresh."));
113 return;
114 }
115
116 ImGui::Text(tr("Blockset Usage Grid"));
117 ImGui::SameLine();
118 ImGui::TextDisabled("(?)");
119 if (ImGui::IsItemHovered()) {
120 ImGui::SetTooltip(
121 tr("Color intensity indicates room count.\n"
122 "Green = few rooms, Red = many rooms.\n"
123 "Click a cell to select that blockset."));
124 }
125 ImGui::Separator();
126
127 // Collect blockset IDs and sort them for a stable layout.
128 std::vector<uint16_t> blockset_ids;
129 blockset_ids.reserve(blockset_usage_.size());
130 for (const auto& [id, count] : blockset_usage_) {
131 blockset_ids.push_back(id);
132 }
133 std::sort(blockset_ids.begin(), blockset_ids.end());
134
135 int max_val = MaxCount(blockset_usage_);
136 if (max_val == 0)
137 max_val = 1; // Avoid division by zero.
138
139 // Render as a grid table. Use 8 columns to fit a reasonable width.
140 constexpr int kGridCols = 8;
141 if (ImGui::BeginTable(
142 "BlocksetGrid", kGridCols,
143 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
144 for (size_t i = 0; i < blockset_ids.size(); ++i) {
145 if (i % kGridCols == 0)
146 ImGui::TableNextRow();
147 ImGui::TableNextColumn();
148
149 uint16_t bs_id = blockset_ids[i];
150 int count = blockset_usage_[bs_id];
151 float t = static_cast<float>(count) / static_cast<float>(max_val);
152
153 ImU32 bg_color = HeatColor(t);
154 bool is_selected = (selected_blockset_ == bs_id);
155
156 // Draw colored cell background.
157 ImVec2 cell_min = ImGui::GetCursorScreenPos();
158 ImVec2 cell_size(48.0f, 36.0f);
159 ImVec2 cell_max(cell_min.x + cell_size.x, cell_min.y + cell_size.y);
160 ImDrawList* draw_list = ImGui::GetWindowDrawList();
161 draw_list->AddRectFilled(cell_min, cell_max, bg_color);
162
163 if (is_selected) {
164 draw_list->AddRect(cell_min, cell_max, IM_COL32(255, 255, 255, 255),
165 0.0f, 0, 2.0f);
166 }
167
168 // Invisible button to handle selection.
169 char btn_id[32];
170 snprintf(btn_id, sizeof(btn_id), "##bs%d", bs_id);
171 if (ImGui::InvisibleButton(btn_id, cell_size)) {
172 selected_blockset_ = bs_id;
173 }
174
175 // Overlay text: blockset ID and count.
176 char label[16];
177 snprintf(label, sizeof(label), "%02X", bs_id);
178 ImVec2 text_pos(cell_min.x + 2.0f, cell_min.y + 2.0f);
179 draw_list->AddText(text_pos, IM_COL32(0, 0, 0, 255), label);
180
181 char count_label[16];
182 snprintf(count_label, sizeof(count_label), "%d", count);
183 ImVec2 count_pos(cell_min.x + 2.0f, cell_min.y + 18.0f);
184 draw_list->AddText(count_pos, IM_COL32(0, 0, 0, 200), count_label);
185
186 // Tooltip on hover.
187 if (ImGui::IsItemHovered()) {
188 ImGui::BeginTooltip();
189 ImGui::Text(tr("Blockset 0x%02X"), bs_id);
190 ImGui::Text(tr("Used by %d rooms"), count);
191 ImGui::EndTooltip();
192 }
193 }
194 ImGui::EndTable();
195 }
196
197 // Also show spriteset and palette grids in collapsible sections.
198 if (ImGui::CollapsingHeader(tr("Spriteset Usage Grid"))) {
200 }
201
202 if (ImGui::CollapsingHeader(tr("Palette Usage Grid"))) {
204 }
205}
206
208 const absl::flat_hash_map<uint16_t, int>& usage_map, uint16_t& selected_set,
209 int spriteset_offset) {
210 if (usage_map.empty()) {
211 ImGui::TextDisabled(tr("No data available."));
212 return;
213 }
214
215 // Collect and sort IDs for stable ordering.
216 std::vector<uint16_t> ids;
217 ids.reserve(usage_map.size());
218 for (const auto& [id, count] : usage_map) {
219 ids.push_back(id);
220 }
221 std::sort(ids.begin(), ids.end());
222
223 int max_val = MaxCount(usage_map);
224 if (max_val == 0)
225 max_val = 1;
226
227 // Summary bar showing distribution.
228 ImGui::Text(tr("%zu unique sets, max usage: %d rooms"), ids.size(), max_val);
229 ImGui::Separator();
230
231 // Table with ID, usage count, and visual bar.
232 // Scope table IDs by usage_map address so multiple calls in one frame
233 // (spriteset + palette sections) do not collide in ImGui state.
234 ImGui::PushID(static_cast<const void*>(&usage_map));
235 if (ImGui::BeginTable("SetUsageTable", 3,
236 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
237 ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable,
238 ImVec2(0, 200.0f))) {
239 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 50.0f);
240 ImGui::TableSetupColumn("Rooms", ImGuiTableColumnFlags_WidthFixed, 50.0f);
241 ImGui::TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthStretch);
242 ImGui::TableHeadersRow();
243
244 ImGuiListClipper clipper;
245 clipper.Begin(static_cast<int>(ids.size()));
246
247 while (clipper.Step()) {
248 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
249 uint16_t set_id = ids[row];
250 auto it = usage_map.find(set_id);
251 int count = (it != usage_map.end()) ? it->second : 0;
252
253 ImGui::TableNextRow();
254 ImGui::TableNextColumn();
255
256 // Display ID with optional offset (for spritesets).
257 uint16_t display_id = static_cast<uint16_t>(set_id + spriteset_offset);
258 char id_label[32];
259 snprintf(id_label, sizeof(id_label), "0x%02X##set%d", display_id,
260 set_id);
261 if (ImGui::Selectable(id_label, selected_set == set_id,
262 ImGuiSelectableFlags_SpanAllColumns)) {
263 selected_set = set_id;
264 }
265
266 ImGui::TableNextColumn();
267 ImGui::Text("%d", count);
268
269 ImGui::TableNextColumn();
270 // Visual usage bar.
271 float fraction =
272 static_cast<float>(count) / static_cast<float>(max_val);
273 ImU32 bar_color = HeatColor(fraction);
274
275 ImVec2 bar_pos = ImGui::GetCursorScreenPos();
276 float bar_width = ImGui::GetContentRegionAvail().x;
277 float bar_height = ImGui::GetTextLineHeight();
278 ImVec2 bar_end(bar_pos.x + bar_width * fraction,
279 bar_pos.y + bar_height);
280 ImVec2 bar_bg_end(bar_pos.x + bar_width, bar_pos.y + bar_height);
281
282 ImDrawList* draw_list = ImGui::GetWindowDrawList();
283 draw_list->AddRectFilled(bar_pos, bar_bg_end,
284 IM_COL32(40, 40, 40, 100));
285 draw_list->AddRectFilled(bar_pos, bar_end, bar_color);
286
287 // Advance cursor past the bar.
288 ImGui::Dummy(ImVec2(bar_width, bar_height));
289 }
290 }
291
292 ImGui::EndTable();
293 }
294 ImGui::PopID();
295}
296
298 selected_blockset_ = 0xFFFF;
299 selected_spriteset_ = 0xFFFF;
300 selected_palette_ = 0xFFFF;
301 spriteset_usage_.clear();
302 blockset_usage_.clear();
303 palette_usage_.clear();
304}
305
306} // namespace yaze::editor
absl::flat_hash_map< uint16_t, int > palette_usage_
void RenderSetUsage(const absl::flat_hash_map< uint16_t, int > &usage_map, uint16_t &selected_set, int spriteset_offset=0x00)
absl::flat_hash_map< uint16_t, int > spriteset_usage_
void CalculateUsageStats(const DungeonRoomStore &rooms)
absl::flat_hash_map< uint16_t, int > blockset_usage_
int MaxCount(const absl::flat_hash_map< uint16_t, int > &usage_map)
Editors are the view controllers for the application.