|
10 | 10 | #endif |
11 | 11 |
|
12 | 12 | #include "vsg_gui_component.h" |
| 13 | +#include "vsg_mooring_lines.h" |
13 | 14 |
|
14 | 15 | #include <vsgImGui/imgui.h> |
15 | 16 |
|
16 | 17 | #ifdef _MSC_VER |
17 | 18 | #pragma warning(pop) |
18 | 19 | #endif |
19 | 20 |
|
| 21 | +#include <algorithm> |
| 22 | +#include <cstdio> |
| 23 | + |
20 | 24 | namespace hydroc { |
21 | 25 | namespace gui { |
22 | 26 |
|
23 | 27 | HydroChronoGuiComponent::HydroChronoGuiComponent(chrono::vsg3d::ChVisualSystemVSG* vsys, bool& button_pressed, |
24 | | - ViewerSettings* settings) |
25 | | - : vsys_(vsys), pressed_(button_pressed), settings_(settings) {} |
| 28 | + ViewerSettings* settings, |
| 29 | + MooringLinesViz* mooring_viz) |
| 30 | + : vsys_(vsys), pressed_(button_pressed), settings_(settings), |
| 31 | + mooring_viz_(mooring_viz) {} |
26 | 32 |
|
27 | 33 | void HydroChronoGuiComponent::render(vsg::CommandBuffer& /*cb*/) { |
28 | 34 | // Play/Pause button (top center, no background). |
@@ -60,83 +66,177 @@ void HydroChronoGuiComponent::render(vsg::CommandBuffer& /*cb*/) { |
60 | 66 |
|
61 | 67 | ImGui::Begin("Viewer Settings", nullptr, settings_flags); |
62 | 68 |
|
63 | | - // Water visibility toggle. |
64 | | - ImGui::Checkbox("Show Water", &settings_->show_water); |
| 69 | + // ---- Waves ---- |
| 70 | + if (ImGui::CollapsingHeader("Waves", ImGuiTreeNodeFlags_DefaultOpen)) { |
| 71 | + ImGui::Checkbox("Show Water", &settings_->show_water); |
65 | 72 |
|
66 | | - // Wireframe overlay for better surface visibility. |
67 | | - ImGui::Checkbox("Show Wireframe", &settings_->show_water_grid); |
68 | | - if (ImGui::IsItemHovered()) { |
69 | | - ImGui::SetTooltip("Show mesh edges on water surface.\n" |
70 | | - "Helps visualize wave motion and ripples."); |
71 | | - } |
| 73 | + ImGui::Checkbox("Show Wireframe", &settings_->show_water_grid); |
| 74 | + if (ImGui::IsItemHovered()) { |
| 75 | + ImGui::SetTooltip("Show mesh edges on water surface.\n" |
| 76 | + "Helps visualize wave motion and ripples."); |
| 77 | + } |
72 | 78 |
|
73 | | - // Grid resolution combo box. |
74 | | - { |
75 | | - const char* resolution_labels[] = {"32x32", "64x64", "96x96", "128x128"}; |
76 | | - int current_idx = 0; |
77 | | - for (int i = 0; i < ViewerSettings::kResolutionCount; ++i) { |
78 | | - if (ViewerSettings::kResolutionOptions[i] == settings_->grid_resolution) { |
79 | | - current_idx = i; |
80 | | - break; |
| 79 | + { |
| 80 | + const char* resolution_labels[] = {"32x32", "64x64", "96x96", "128x128"}; |
| 81 | + int current_idx = 0; |
| 82 | + for (int i = 0; i < ViewerSettings::kResolutionCount; ++i) { |
| 83 | + if (ViewerSettings::kResolutionOptions[i] == settings_->grid_resolution) { |
| 84 | + current_idx = i; |
| 85 | + break; |
| 86 | + } |
| 87 | + } |
| 88 | + int prev_idx = current_idx; |
| 89 | + if (ImGui::Combo("Grid Resolution", ¤t_idx, resolution_labels, ViewerSettings::kResolutionCount)) { |
| 90 | + if (current_idx != prev_idx) { |
| 91 | + settings_->grid_resolution = ViewerSettings::kResolutionOptions[current_idx]; |
| 92 | + settings_->resolution_changed = true; |
| 93 | + } |
81 | 94 | } |
82 | 95 | } |
83 | | - int prev_idx = current_idx; |
84 | | - if (ImGui::Combo("Grid Resolution", ¤t_idx, resolution_labels, ViewerSettings::kResolutionCount)) { |
85 | | - if (current_idx != prev_idx) { |
86 | | - settings_->grid_resolution = ViewerSettings::kResolutionOptions[current_idx]; |
87 | | - settings_->resolution_changed = true; |
| 96 | + |
| 97 | + ImGui::Checkbox("Radiation (approx.)", &settings_->show_radiation_viz); |
| 98 | + if (ImGui::IsItemHovered()) { |
| 99 | + ImGui::SetTooltip("Visual approximation of radiated waves.\n" |
| 100 | + "Does NOT affect physics - for feedback only."); |
| 101 | + } |
| 102 | + |
| 103 | + if (settings_->show_radiation_viz) { |
| 104 | + ImGui::Indent(); |
| 105 | + ImGui::SliderFloat("Visual Scale##rad", &settings_->radiation_visual_scale, 0.1f, 5.0f, "%.1fx"); |
| 106 | + if (ImGui::IsItemHovered()) { |
| 107 | + ImGui::SetTooltip("Visual amplification for inspection.\n" |
| 108 | + "1.0x = baseline, increase to see small ripples.\n" |
| 109 | + "Does NOT affect physics."); |
88 | 110 | } |
| 111 | + ImGui::TextDisabled("Wavelength/speed derived from wave period"); |
| 112 | + ImGui::Unindent(); |
89 | 113 | } |
90 | 114 | } |
91 | 115 |
|
92 | | - // Update rate slider. |
93 | | - ImGui::SliderInt("Update Hz", &settings_->update_hz, 10, 60, "%d Hz"); |
| 116 | + // ---- Mooring ---- |
| 117 | + RenderMooringPanel(); |
94 | 118 |
|
95 | | - // Status line toggle. |
96 | | - ImGui::Checkbox("Show Status", &settings_->show_water_status); |
| 119 | + // ---- Display ---- |
| 120 | + if (ImGui::CollapsingHeader("Display")) { |
| 121 | + ImGui::SliderInt("Update Hz", &settings_->update_hz, 10, 60, "%d Hz"); |
97 | 122 |
|
98 | | - // Optional status display. |
99 | | - if (settings_->show_water_status) { |
100 | | - ImGui::Separator(); |
101 | | - ImGui::TextDisabled("Water: %s | Grid: %d | Hz: %d", |
102 | | - settings_->show_water ? "ON" : "OFF", |
103 | | - settings_->grid_resolution, |
104 | | - settings_->update_hz); |
| 123 | + ImGui::Checkbox("Show Status", &settings_->show_water_status); |
| 124 | + if (settings_->show_water_status) { |
| 125 | + ImGui::TextDisabled("Water: %s | Grid: %d | Hz: %d", |
| 126 | + settings_->show_water ? "ON" : "OFF", |
| 127 | + settings_->grid_resolution, |
| 128 | + settings_->update_hz); |
| 129 | + } |
105 | 130 | } |
106 | 131 |
|
107 | | - ImGui::Separator(); |
| 132 | + ImGui::End(); |
| 133 | + } |
108 | 134 |
|
109 | | - // Radiation visualization controls (Tier 0 - approximate). |
110 | | - // Clear labeling that this is a visual approximation, not physics. |
111 | | - ImGui::Checkbox("Radiation (viz, approximate)", &settings_->show_radiation_viz); |
112 | | - if (ImGui::IsItemHovered()) { |
113 | | - ImGui::SetTooltip("Visual approximation of radiated waves.\n" |
114 | | - "Does NOT affect physics - for feedback only."); |
115 | | - } |
| 135 | + RenderColorBar(); |
| 136 | +} |
116 | 137 |
|
117 | | - // Show Visual Scale slider when radiation is enabled. |
118 | | - // All other parameters are auto-derived from physics. |
119 | | - if (settings_->show_radiation_viz) { |
120 | | - ImGui::Indent(); |
| 138 | +// --------------------------------------------------------------------------- |
121 | 139 |
|
122 | | - // Visual scale slider - clearly labeled as visualization-only. |
123 | | - ImGui::SliderFloat("Visual Scale##rad", &settings_->radiation_visual_scale, 0.1f, 5.0f, "%.1fx"); |
124 | | - if (ImGui::IsItemHovered()) { |
125 | | - ImGui::SetTooltip("Visual amplification for inspection.\n" |
126 | | - "1.0x = baseline, increase to see small ripples.\n" |
127 | | - "Does NOT affect physics."); |
128 | | - } |
| 140 | +void HydroChronoGuiComponent::RenderMooringPanel() { |
| 141 | + if (!settings_ || !mooring_viz_ || !mooring_viz_->IsInitialized()) return; |
| 142 | + |
| 143 | + if (!ImGui::CollapsingHeader("Mooring", ImGuiTreeNodeFlags_DefaultOpen)) return; |
| 144 | + |
| 145 | + ImGui::Checkbox("Colour by Tension", &settings_->show_mooring_colors); |
| 146 | + if (ImGui::IsItemHovered()) { |
| 147 | + ImGui::SetTooltip("Map tension magnitude to colour\n" |
| 148 | + "along each mooring line."); |
| 149 | + } |
129 | 150 |
|
130 | | - // Info text about auto-derived parameters. |
131 | | - ImGui::TextDisabled("Wavelength/speed derived from wave period"); |
| 151 | + if (settings_->show_mooring_colors) { |
| 152 | + ImGui::Indent(); |
132 | 153 |
|
133 | | - ImGui::Unindent(); |
| 154 | + ImGui::Checkbox("Lock Range", &settings_->mooring_range_locked); |
| 155 | + if (ImGui::IsItemHovered()) { |
| 156 | + ImGui::SetTooltip("Freeze the colour range for stable comparison.\n" |
| 157 | + "Uncheck to auto-track."); |
134 | 158 | } |
135 | 159 |
|
136 | | - ImGui::End(); |
| 160 | + ImGui::TextDisabled("Range: %.3g .. %.3g N", |
| 161 | + mooring_viz_->ScalarMin(), |
| 162 | + mooring_viz_->ScalarMax()); |
| 163 | + |
| 164 | + ImGui::Unindent(); |
137 | 165 | } |
138 | 166 | } |
139 | 167 |
|
| 168 | +// --------------------------------------------------------------------------- |
| 169 | + |
| 170 | +void HydroChronoGuiComponent::RenderColorBar() { |
| 171 | + if (!settings_ || !settings_->show_mooring_colors) return; |
| 172 | + if (!mooring_viz_ || !mooring_viz_->IsInitialized()) return; |
| 173 | + |
| 174 | + const ImGuiViewport* vp = ImGui::GetMainViewport(); |
| 175 | + const float bar_w = 20.0f; |
| 176 | + const float bar_h = 200.0f; |
| 177 | + const float margin = 30.0f; |
| 178 | + const float label_gap = 6.0f; |
| 179 | + |
| 180 | + const float x0 = vp->WorkPos.x + vp->WorkSize.x - margin - bar_w; |
| 181 | + const float y0 = vp->WorkPos.y + vp->WorkSize.y * 0.5f - bar_h * 0.5f; |
| 182 | + |
| 183 | + ImDrawList* dl = ImGui::GetForegroundDrawList(); |
| 184 | + |
| 185 | + // Draw the gradient as a stack of thin horizontal strips. |
| 186 | + constexpr int kStrips = 64; |
| 187 | + const float strip_h = bar_h / kStrips; |
| 188 | + for (int i = 0; i < kStrips; ++i) { |
| 189 | + const float t_top = 1.0f - static_cast<float>(i) / kStrips; |
| 190 | + const float t_bot = 1.0f - static_cast<float>(i + 1) / kStrips; |
| 191 | + |
| 192 | + auto to_im = [](float t) -> ImU32 { |
| 193 | + t = std::clamp(t, 0.0f, 1.0f); |
| 194 | + const float r = std::clamp( |
| 195 | + 0.13572138f + t * (4.61539260f + t * (-42.66032258f + |
| 196 | + t * (132.13108234f + t * (-152.54895899f + t * 59.28637943f)))), |
| 197 | + 0.0f, 1.0f); |
| 198 | + const float g = std::clamp( |
| 199 | + 0.09140261f + t * (2.26418794f + t * (4.11868525f + |
| 200 | + t * (-44.58319668f + t * (70.41698018f - t * 33.26974748f)))), |
| 201 | + 0.0f, 1.0f); |
| 202 | + const float b = std::clamp( |
| 203 | + 0.10667330f + t * (12.75191895f + t * (-60.25290628f + |
| 204 | + t * (109.04872043f + t * (-89.38040853f + t * 27.13073700f)))), |
| 205 | + 0.0f, 1.0f); |
| 206 | + return IM_COL32(static_cast<int>(r * 255), |
| 207 | + static_cast<int>(g * 255), |
| 208 | + static_cast<int>(b * 255), 255); |
| 209 | + }; |
| 210 | + |
| 211 | + ImU32 col_top = to_im(t_top); |
| 212 | + ImU32 col_bot = to_im(t_bot); |
| 213 | + dl->AddRectFilledMultiColor( |
| 214 | + ImVec2(x0, y0 + i * strip_h), |
| 215 | + ImVec2(x0 + bar_w, y0 + (i + 1) * strip_h), |
| 216 | + col_top, col_top, col_bot, col_bot); |
| 217 | + } |
| 218 | + |
| 219 | + // Outline. |
| 220 | + dl->AddRect(ImVec2(x0, y0), ImVec2(x0 + bar_w, y0 + bar_h), |
| 221 | + IM_COL32(200, 200, 200, 180)); |
| 222 | + |
| 223 | + // Labels. |
| 224 | + char buf[64]; |
| 225 | + std::snprintf(buf, sizeof(buf), "%.3g N", mooring_viz_->ScalarMax()); |
| 226 | + dl->AddText(ImVec2(x0 - label_gap - ImGui::CalcTextSize(buf).x, y0 - 2.0f), |
| 227 | + IM_COL32(220, 220, 220, 255), buf); |
| 228 | + |
| 229 | + std::snprintf(buf, sizeof(buf), "%.3g N", mooring_viz_->ScalarMin()); |
| 230 | + dl->AddText(ImVec2(x0 - label_gap - ImGui::CalcTextSize(buf).x, |
| 231 | + y0 + bar_h - ImGui::GetTextLineHeight() + 2.0f), |
| 232 | + IM_COL32(220, 220, 220, 255), buf); |
| 233 | + |
| 234 | + const char* title = "Tension"; |
| 235 | + dl->AddText(ImVec2(x0 + bar_w * 0.5f - ImGui::CalcTextSize(title).x * 0.5f, |
| 236 | + y0 - ImGui::GetTextLineHeight() - 4.0f), |
| 237 | + IM_COL32(220, 220, 220, 255), title); |
| 238 | +} |
| 239 | + |
140 | 240 | } // namespace gui |
141 | 241 | } // namespace hydroc |
142 | 242 |
|
0 commit comments