Skip to content

Commit b3207a0

Browse files
committed
Add dynamic tension colour mapping to mooring line visualization
Mooring lines now colour each tube segment by MoorDyn tension magnitude using a Turbo colormap, with an ImGui colour bar and real-time controls.
1 parent 4254f96 commit b3207a0

10 files changed

Lines changed: 353 additions & 79 deletions

File tree

include/hydroc/gui/mooring_viz_data.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ inline constexpr int kIntermediateNode = -99;
2828
/// plus the MoorDyn point type at each endpoint.
2929
struct MooringLineVizData {
3030
std::vector<std::array<double, 3>> node_positions;
31+
std::vector<double> node_tensions; ///< Per-node tension magnitude [N].
32+
double line_max_tension = 0.0; ///< Max tension magnitude along line [N].
3133
MooringPointType start_point_type = MooringPointType::kFree;
3234
MooringPointType end_point_type = MooringPointType::kFree;
3335
};
3436

3537
/// Callback the GUI invokes each frame to fetch current line geometry.
36-
using MooringVizProvider = std::function<std::vector<MooringLineVizData>()>;
38+
using MooringVizProvider =
39+
std::function<std::vector<MooringLineVizData>()>;
3740

3841
} // namespace gui
3942
} // namespace hydroc

include/hydroc/hydro_system.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,11 @@ class HydroSystem {
457457
}
458458

459459
/**
460-
* @brief Get current mooring line node positions for visualization.
460+
* @brief Get current mooring line node positions and tension for visualization.
461461
*
462462
* Returns one entry per MoorDyn line, each containing the ordered node
463-
* positions along the line. Returns an empty vector when MoorDyn is
464-
* not active or no lines exist.
463+
* positions along the line plus per-node tension magnitudes. Returns an
464+
* empty vector when MoorDyn is not active or no lines exist.
465465
*/
466466
std::vector<hydroc::gui::MooringLineVizData> GetMooringLineStates() const;
467467
#endif

src/gui/guihelperVSG.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,13 @@ void GUIImplVSG::Init(UI& ui, chrono::ChSystem* system, const char* title) {
9595
}
9696
}
9797

98-
pVis->AddGuiComponent(std::make_shared<HydroChronoGuiComponent>(pVis.get(), ui.simulationStarted,
99-
viewer_settings_.get()));
98+
// Create mooring viz early so the GUI component can read its adaptive range.
99+
if (!mooring_viz_)
100+
mooring_viz_ = std::make_unique<MooringLinesViz>();
101+
102+
pVis->AddGuiComponent(std::make_shared<HydroChronoGuiComponent>(
103+
pVis.get(), ui.simulationStarted, viewer_settings_.get(),
104+
mooring_viz_.get()));
100105

101106
pVis->Initialize();
102107

@@ -204,7 +209,10 @@ bool GUIImplVSG::IsRunning(double timestep) {
204209
mooring_viz_ = std::make_unique<MooringLinesViz>();
205210
if (!mooring_viz_->IsInitializedFor(pVis.get()))
206211
mooring_viz_->Initialize(pVis.get(), line_data, mooring_scene_group_);
207-
mooring_viz_->Update(line_data);
212+
213+
const bool color_on = viewer_settings_ && viewer_settings_->show_mooring_colors;
214+
const bool range_lock = viewer_settings_ && viewer_settings_->mooring_range_locked;
215+
mooring_viz_->Update(line_data, color_on, range_lock);
208216
}
209217
}
210218

src/gui/vsg_gui_component.cpp

Lines changed: 157 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,25 @@
1010
#endif
1111

1212
#include "vsg_gui_component.h"
13+
#include "vsg_mooring_lines.h"
1314

1415
#include <vsgImGui/imgui.h>
1516

1617
#ifdef _MSC_VER
1718
#pragma warning(pop)
1819
#endif
1920

21+
#include <algorithm>
22+
#include <cstdio>
23+
2024
namespace hydroc {
2125
namespace gui {
2226

2327
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) {}
2632

2733
void HydroChronoGuiComponent::render(vsg::CommandBuffer& /*cb*/) {
2834
// Play/Pause button (top center, no background).
@@ -60,83 +66,177 @@ void HydroChronoGuiComponent::render(vsg::CommandBuffer& /*cb*/) {
6066

6167
ImGui::Begin("Viewer Settings", nullptr, settings_flags);
6268

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);
6572

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+
}
7278

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", &current_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+
}
8194
}
8295
}
83-
int prev_idx = current_idx;
84-
if (ImGui::Combo("Grid Resolution", &current_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.");
88110
}
111+
ImGui::TextDisabled("Wavelength/speed derived from wave period");
112+
ImGui::Unindent();
89113
}
90114
}
91115

92-
// Update rate slider.
93-
ImGui::SliderInt("Update Hz", &settings_->update_hz, 10, 60, "%d Hz");
116+
// ---- Mooring ----
117+
RenderMooringPanel();
94118

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");
97122

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+
}
105130
}
106131

107-
ImGui::Separator();
132+
ImGui::End();
133+
}
108134

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+
}
116137

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+
// ---------------------------------------------------------------------------
121139

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+
}
129150

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();
132153

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.");
134158
}
135159

136-
ImGui::End();
160+
ImGui::TextDisabled("Range: %.3g .. %.3g N",
161+
mooring_viz_->ScalarMin(),
162+
mooring_viz_->ScalarMax());
163+
164+
ImGui::Unindent();
137165
}
138166
}
139167

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+
140240
} // namespace gui
141241
} // namespace hydroc
142242

src/gui/vsg_gui_component.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
namespace hydroc {
1313
namespace gui {
1414

15+
class MooringLinesViz; // forward declaration
16+
1517
/// Runtime-adjustable water visualization settings.
1618
///
1719
/// Allows tuning water appearance without recompiling. Owned by GUIImplVSG
@@ -47,6 +49,10 @@ struct ViewerSettings {
4749
// amplitude scales with body velocity and size.
4850
bool show_radiation_viz = false; ///< Enable radiated wave visualization
4951
float radiation_visual_scale = 1.0f; ///< Visual amplification [0.1x - 5x]
52+
53+
// --- Mooring Colour Mapping ---
54+
bool show_mooring_colors = true; ///< Colour lines by tension magnitude
55+
bool mooring_range_locked = false; ///< Freeze the adaptive min/max range
5056
};
5157

5258
/// ImGui component for HydroChrono visualization overlay.
@@ -57,17 +63,24 @@ class HydroChronoGuiComponent : public chrono::vsg3d::ChGuiComponentVSG {
5763
/// @param vsys Pointer to the VSG visual system (for potential future use).
5864
/// @param button_pressed Reference to the simulation running state (toggled by button).
5965
/// @param settings Pointer to viewer settings (owned by GUIImplVSG).
66+
/// @param mooring_viz Pointer to the mooring renderer (for reading the
67+
/// adaptive scalar range for the colour bar).
6068
HydroChronoGuiComponent(chrono::vsg3d::ChVisualSystemVSG* vsys, bool& button_pressed,
61-
ViewerSettings* settings = nullptr);
69+
ViewerSettings* settings = nullptr,
70+
MooringLinesViz* mooring_viz = nullptr);
6271

6372
/// Render the ImGui overlay.
6473
/// Called each frame by the VSG rendering loop.
6574
void render(vsg::CommandBuffer& cb) override;
6675

6776
private:
77+
void RenderMooringPanel();
78+
void RenderColorBar();
79+
6880
chrono::vsg3d::ChVisualSystemVSG* vsys_;
6981
bool& pressed_;
70-
ViewerSettings* settings_; ///< Viewer settings (may be null)
82+
ViewerSettings* settings_;
83+
MooringLinesViz* mooring_viz_;
7184
};
7285

7386
} // namespace gui

0 commit comments

Comments
 (0)