Skip to content

Commit 61421da

Browse files
committed
Implement radiated wave visualization
- Use finite depth dispersion relation - Use phase velocity for wave propagation with retarded time - Wave pattern emerges naturally from body motion at retarded time, ensuring frequency matches actual oscillation - Use 1/√(kr) cylindrical spreading decay
1 parent 4163c7c commit 61421da

12 files changed

Lines changed: 1280 additions & 216 deletions

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,13 +422,15 @@ if(HYDROCHRONO_ENABLE_VSG)
422422
src/gui/vsg_lighting.cpp
423423
src/gui/vsg_materials.cpp
424424
src/gui/vsg_water_surface.cpp
425+
src/gui/vsg_radiation_surface.cpp
425426
)
426427
set(HCGUI_HEADERS ${HCGUI_HEADERS}
427428
src/gui/vsg_config.h
428429
src/gui/vsg_gui_component.h
429430
src/gui/vsg_lighting.h
430431
src/gui/vsg_materials.h
431432
src/gui/vsg_water_surface.h
433+
src/gui/vsg_radiation_surface.h
432434
)
433435
endif()
434436

data/textures/water_grid.png

961 Bytes
Loading
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate a simple grid texture for the water surface visualization.
4+
Creates a seamless tileable grid pattern.
5+
"""
6+
7+
import sys
8+
9+
try:
10+
from PIL import Image, ImageDraw
11+
except ImportError:
12+
print("Pillow not installed. Installing...")
13+
import subprocess
14+
subprocess.check_call([sys.executable, "-m", "pip", "install", "Pillow"])
15+
from PIL import Image, ImageDraw
16+
17+
18+
def create_grid_texture(size=256, line_width=3, num_cells=4):
19+
"""
20+
Create a seamless tileable grid texture.
21+
22+
Args:
23+
size: Texture size in pixels (square)
24+
line_width: Width of grid lines in pixels
25+
num_cells: Number of grid cells per texture tile
26+
27+
Returns:
28+
PIL Image with grid pattern
29+
"""
30+
# Water color (light blue-gray, similar to the PBR material)
31+
water_color = (20, 80, 120, 180) # RGBA - semi-transparent blue
32+
33+
# Grid line color (darker, more visible)
34+
line_color = (5, 30, 50, 220) # RGBA - dark blue, mostly opaque
35+
36+
# Create image with alpha channel
37+
img = Image.new('RGBA', (size, size), water_color)
38+
draw = ImageDraw.Draw(img)
39+
40+
# Calculate cell size
41+
cell_size = size // num_cells
42+
43+
# Draw vertical lines
44+
for i in range(num_cells + 1):
45+
x = i * cell_size
46+
draw.rectangle([x - line_width//2, 0, x + line_width//2, size - 1], fill=line_color)
47+
48+
# Draw horizontal lines
49+
for i in range(num_cells + 1):
50+
y = i * cell_size
51+
draw.rectangle([0, y - line_width//2, size - 1, y + line_width//2], fill=line_color)
52+
53+
return img
54+
55+
56+
def main():
57+
# Generate the texture
58+
img = create_grid_texture(size=256, line_width=4, num_cells=4)
59+
60+
# Save to data/textures/water_grid.png
61+
output_path = "data/textures/water_grid.png"
62+
img.save(output_path)
63+
print(f"Created grid texture: {output_path}")
64+
print(f" Size: {img.size[0]}x{img.size[1]}")
65+
print(f" Format: RGBA PNG")
66+
67+
68+
if __name__ == "__main__":
69+
main()
70+
71+
72+
73+
74+
75+
76+

src/gui/guihelperVSG.cpp

Lines changed: 122 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@
1111
#include "vsg_gui_component.h"
1212
#include "vsg_lighting.h"
1313
#include "vsg_materials.h"
14+
#include "vsg_radiation_surface.h"
1415
#include "vsg_water_surface.h"
1516

17+
#include <hydroc/waves/regular_wave.h>
18+
#include <hydroc/waves/irregular_wave.h>
19+
1620
#include <iostream>
1721
#include <memory>
1822

@@ -124,49 +128,35 @@ void GUIImplVSG::EnsureWaterSurface() {
124128
return;
125129
}
126130

127-
// Check if we have a valid wave model (not NoWave).
128-
bool has_waves = wave_model_ && wave_model_->GetWaveMode() != WaveMode::noWaveCIC;
129-
130131
// If already initialized for this visual system, nothing to do.
131132
if (animated_water_->IsInitializedFor(pVis.get())) {
132133
return;
133134
}
134135

135136
if constexpr (kDebugWaveSurface) {
136-
std::cout << "[WaterSurface] EnsureWaterSurface: has_waves=" << has_waves
137-
<< " kForceWaterSurface=" << kForceWaterSurface << std::endl;
137+
bool has_waves = wave_model_ && wave_model_->GetWaveMode() != WaveMode::noWaveCIC;
138+
std::cout << "[WaterSurface] EnsureWaterSurface: has_waves=" << has_waves << std::endl;
138139
}
139140

140-
// Handle animated or static water surface.
141-
if (has_waves || kForceWaterSurface) {
142-
// Initialize animated water surface directly in VSG scene.
143-
// Must be done after pVis->Initialize() which happens in Init().
144-
// Use viewer_settings_ resolution if available.
145-
int resolution = (viewer_settings_) ? viewer_settings_->grid_resolution : 0;
146-
animated_water_->Initialize(pVis.get(), resolution);
147-
148-
if (animated_water_->IsInitialized()) {
149-
// Print status: wave pointer status.
150-
std::cout << "[WaterSurface] wave_ptr=" << (wave_model_ ? "ok" : "null");
151-
if (wave_model_) {
152-
std::cout << " mode=" << static_cast<int>(wave_model_->GetWaveMode());
153-
}
154-
std::cout << " " << animated_water_->GetStatusString() << std::endl;
155-
156-
// Initial update at current system time.
157-
double t = system_->GetChTime();
158-
animated_water_->Update(wave_model_, t);
159-
} else {
160-
if constexpr (kDebugWaveSurface) {
161-
std::cout << "[WaterSurface] Initialize() failed!" << std::endl;
162-
}
141+
// Always create animated water surface (supports waves + radiation viz).
142+
// Static plane fallback removed - animated water handles all cases.
143+
int resolution = (viewer_settings_) ? viewer_settings_->grid_resolution : 0;
144+
animated_water_->Initialize(pVis.get(), resolution);
145+
146+
if (animated_water_->IsInitialized()) {
147+
std::cout << "[WaterSurface] wave_ptr=" << (wave_model_ ? "ok" : "null");
148+
if (wave_model_) {
149+
std::cout << " mode=" << static_cast<int>(wave_model_->GetWaveMode());
150+
}
151+
std::cout << " " << animated_water_->GetStatusString() << std::endl;
152+
153+
// Initial update at current system time.
154+
double t = system_->GetChTime();
155+
animated_water_->Update(wave_model_, t);
156+
} else {
157+
if constexpr (kDebugWaveSurface) {
158+
std::cout << "[WaterSurface] Initialize() failed!" << std::endl;
163159
}
164-
} else if (!water_surface_created_) {
165-
// No waves and not forcing: add static water plane.
166-
auto water_plane = CreateStaticWaterPlane();
167-
system_->AddBody(water_plane);
168-
water_surface_created_ = true;
169-
std::cout << "[WaterSurface] static plane created (no wave model)" << std::endl;
170160
}
171161
}
172162

@@ -194,6 +184,14 @@ bool GUIImplVSG::IsRunning(double timestep) {
194184
// VSG vertex buffers are marked dirty() in Update() for GPU re-upload.
195185
if (animated_water_ && animated_water_->IsInitialized()) {
196186
double t = system_ ? system_->GetChTime() : 0.0;
187+
188+
// Update radiation source body state if radiation viz is enabled.
189+
// Chooses the first non-water body in the system as the source.
190+
// NOTE: This is visualization-only and does NOT affect physics.
191+
if (viewer_settings_ && viewer_settings_->show_radiation_viz && system_) {
192+
UpdateRadiationSourceBody(t);
193+
}
194+
197195
// Update() handles null wave_model_ gracefully (keeps surface flat).
198196
// Pass viewer_settings_ for scale multiplier and throttle.
199197
animated_water_->Update(wave_model_, t, viewer_settings_.get());
@@ -206,5 +204,95 @@ bool GUIImplVSG::IsRunning(double timestep) {
206204
return true;
207205
}
208206

207+
void GUIImplVSG::UpdateRadiationSourceBody(double t) {
208+
if (!animated_water_ || !system_) {
209+
return;
210+
}
211+
212+
// Update global params.
213+
if (viewer_settings_) {
214+
RadiationSurfaceViz::Params rad_params;
215+
rad_params.visual_scale = static_cast<double>(viewer_settings_->radiation_visual_scale);
216+
rad_params.wave_period = 8.0; // Default; should match body oscillation frequency
217+
218+
// Get wave properties from wave model if available.
219+
if (wave_model_) {
220+
// Water depth and gravity (available in all wave models).
221+
if (wave_model_->water_depth_ > 0.0) {
222+
rad_params.water_depth = wave_model_->water_depth_;
223+
}
224+
rad_params.gravity = wave_model_->g_;
225+
226+
// Wave period from RegularWave.
227+
if (wave_model_->GetWaveMode() == WaveMode::regular) {
228+
auto* reg_wave = dynamic_cast<RegularWave*>(wave_model_.get());
229+
if (reg_wave && reg_wave->regular_wave_omega_ > 0.0) {
230+
constexpr double kTwoPi = 2.0 * 3.14159265358979323846;
231+
rad_params.wave_period = kTwoPi / reg_wave->regular_wave_omega_;
232+
}
233+
}
234+
// Peak period from IrregularWaves (JONSWAP).
235+
else if (wave_model_->GetWaveMode() == WaveMode::irregular) {
236+
auto* irreg_wave = dynamic_cast<IrregularWaves*>(wave_model_.get());
237+
if (irreg_wave) {
238+
// Note: IrregularWaves doesn't expose params_ directly.
239+
// For now, keep default. Could add a GetPeakPeriod() accessor.
240+
}
241+
}
242+
// NoWave mode (decay tests): wave_period should ideally be set to the
243+
// body's natural period T_n ≈ 2π√((M+A_∞)/K_hs). Using default 8s as fallback.
244+
}
245+
246+
animated_water_->GetRadiationViz().SetParams(rad_params);
247+
}
248+
249+
// Log source bodies once.
250+
static bool logged_sources = false;
251+
252+
// Iterate over ALL moving bodies and update their radiation state.
253+
for (auto& body : system_->GetBodies()) {
254+
if (!body) {
255+
continue;
256+
}
257+
258+
const std::string& name = body->GetName();
259+
260+
// Skip water surfaces and ground bodies.
261+
if (name == "water_surface" || name == "animated_water_surface" ||
262+
name == "ground" || name == "floor" || name.empty()) {
263+
continue;
264+
}
265+
266+
// Skip fixed bodies.
267+
if (body->IsFixed()) {
268+
continue;
269+
}
270+
271+
// Get body motion state.
272+
const chrono::ChVector3d pos = body->GetPos();
273+
const chrono::ChVector3d vel = body->GetPosDt();
274+
const chrono::ChVector3d ang_vel = body->GetAngVelLocal();
275+
276+
// Estimate body radius from AABB (rough approximation).
277+
double radius = 5.0; // Default
278+
auto aabb = body->GetTotalAABB();
279+
if (aabb.max.x() > aabb.min.x()) {
280+
double dx = aabb.max.x() - aabb.min.x();
281+
double dy = aabb.max.y() - aabb.min.y();
282+
radius = std::max(dx, dy) / 2.0;
283+
radius = std::max(radius, 1.0); // Minimum 1m
284+
}
285+
286+
// Update radiation viz for this body.
287+
animated_water_->GetRadiationViz().SetSourceState(name, pos, vel, ang_vel, radius, t);
288+
289+
if (!logged_sources) {
290+
std::cout << "[RadiationViz] Source: " << name << " (r=" << radius << "m)" << std::endl;
291+
}
292+
}
293+
294+
logged_sources = true;
295+
}
296+
209297
} // namespace gui
210298
} // namespace hydroc

src/gui/guihelper_impl.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,16 @@ class GUIImplVSG : public GUIImpl {
8787
/// Ensure water surface is created (animated if wave model set, static otherwise).
8888
void EnsureWaterSurface();
8989

90+
/// Update radiation source bodies for visualization.
91+
/// Iterates over all moving bodies and updates their radiation state.
92+
void UpdateRadiationSourceBody(double t);
93+
9094
std::shared_ptr<chrono::vsg3d::ChVisualSystemVSG> pVis;
91-
std::shared_ptr<WaveBase> wave_model_; ///< Wave model for animated surface
92-
chrono::ChSystem* system_ = nullptr; ///< Cached system pointer for time access
93-
std::unique_ptr<AnimatedWaterSurface> animated_water_; ///< Animated water surface (owned)
94-
std::unique_ptr<ViewerSettings> viewer_settings_; ///< Runtime viewer settings (owned)
95-
bool water_surface_created_ = false; ///< True if any water surface was added
95+
std::shared_ptr<WaveBase> wave_model_;
96+
chrono::ChSystem* system_ = nullptr;
97+
std::unique_ptr<AnimatedWaterSurface> animated_water_;
98+
std::unique_ptr<ViewerSettings> viewer_settings_;
99+
bool water_surface_created_ = false;
96100
};
97101
#endif
98102

0 commit comments

Comments
 (0)