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