From 6e9627ca14e7f6378c62e26e6efd02e7b874dd28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:50:32 +0000 Subject: [PATCH 1/2] Initial plan From 86ec508cb56f506c7f0157f177d5427788676c39 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:56:30 +0000 Subject: [PATCH 2/2] Fix viz stub and migrate hinge to ChLinkRSDA + RsdaPtoFunctor Problem 1: Wire real SEA-Stack VSG GUI loop using seastack::viz::CreateUI gated by VGOSWEC_HAVE_SEASTACK_GUIHELPER. When the macro is defined, a single ui.IsRunning() loop handles both headless (CreateUI(false)) and visual (CreateUI(true)) runs. When the macro is absent, the existing headless-only path is preserved. Problem 2: Replace ChLinkMotorRotationTorque + per-step ChFunctionConst reallocation with ChLinkLockRevolute (5-DOF hinge constraint) + ChLinkRSDA whose TorqueFunctor is RsdaPtoFunctor. The functor calls controller->ComputeForce at every Chrono force-assembly sub-step, which is the correct sub-step-accurate approach documented in rsda_pto_functor.h and HIL_MIGRATION.md. Also adds rsda_pto_functor.cpp to vgoswec_chrono library in CMakeLists.txt and updates README prerequisites to list VSG/GUI dependencies. --- CMakeLists.txt | 1 + README.md | 15 ++++-- src/demo_vgoswec.cpp | 108 +++++++++++++++++++++++++------------------ 3 files changed, 74 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2249b8e..4ab8fc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ add_library(vgoswec_chrono STATIC src/active_pto.cpp src/excitation_force_provider.cpp src/impedance.cpp + src/rsda_pto_functor.cpp ) target_include_directories(vgoswec_chrono PUBLIC src) target_link_libraries(vgoswec_chrono diff --git a/README.md b/README.md index 6b8ced3..04e6337 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,17 @@ cpp-vgoswec/ - **Project Chrono** ≥ 10.0 with `CH_USE_SIMD=OFF` - **yaml-cpp** ≥ 0.7 - **Eigen3** ≥ 3.4 -- **For SEA-Stack/HIL visualization parity**: `libseastack_app_lib.a` available in one of: - - `$HOME/SEA-Stack/build/lib/Release/` - - `$HOME/SEA-Stack/build/lib/` - - `$HOME/SEA-Stack/install/lib/` +- **For GUI/visualization** (optional — headless builds work without these): + - **VulkanSceneGraph (VSG)** ≥ 1.1 (`vsg::vsg` CMake target) + - **vsgXchange** ≥ 1.0 (asset loading for VSG; `vsgXchange::vsgXchange`) + - **vsgImGui** ≥ 0.3 (in-scene UI overlay; `vsgImGui::vsgImGui`) + - **Chrono VSG module** built alongside Chrono (`Chrono::Chrono_vsg`) + - **SEA-Stack GUI helper header** present at `$HOME/SEA-Stack/apps/seastack/gui/guihelper.h` (from the SEA-Stack source tree) + - **`libseastack_app_lib`** available in one of: + - `$HOME/SEA-Stack/build/lib/Release/` + - `$HOME/SEA-Stack/build/lib/` + - `$HOME/SEA-Stack/install/lib/` + When any of the above GUI components are absent CMake automatically falls back to a headless-only build that still compiles and produces CSV output. ## Build diff --git a/src/demo_vgoswec.cpp b/src/demo_vgoswec.cpp index 694362a..21936be 100644 --- a/src/demo_vgoswec.cpp +++ b/src/demo_vgoswec.cpp @@ -5,6 +5,7 @@ #include "excitation_force_provider.h" #include "impedance.h" #include "pid_controller.h" +#include "rsda_pto_functor.h" #include #include @@ -19,9 +20,8 @@ #include #include #include +#include #include -#include -#include #include #include @@ -161,39 +161,6 @@ struct Record { double t, th, om, tau_pto, tau_exc, p; }; -static void RunHeadlessLoop( - ChSystemNSC& system, - seastack::chrono::HydroSystem& hydro_system, - const std::shared_ptr& flap_body, - const std::shared_ptr& motor, - const std::shared_ptr& controller, - const std::shared_ptr& exc_provider, - double sim_duration, - double dt, - std::vector& records) { - while (system.GetChTime() <= sim_duration) { - const double t = system.GetChTime(); - - const auto rpy = flap_body->GetRot().GetCardanAnglesXYZ(); - const double pitch_rad = rpy.y(); - const double pitch_vel = flap_body->GetAngVelParent().y(); - - const double pto_tau = controller->ComputeForce(pitch_rad, pitch_vel, t); - motor->SetTorqueFunction(chrono_types::make_shared(pto_tau)); - - system.DoStepDynamics(dt); - - const auto& per_comp = hydro_system.GetLastComponentForces(); - if (!per_comp.empty()) { - exc_provider->Update(per_comp, system.GetChTime()); - } - const double exc_tau = exc_provider->GetLatestExcitationTorque(); - - records.push_back( - {system.GetChTime(), pitch_rad, pitch_vel, pto_tau, exc_tau, -pto_tau * pitch_vel}); - } -} - } // namespace int main(int argc, char* argv[]) { @@ -245,11 +212,10 @@ int main(int argc, char* argv[]) { const ChVector3d hinge_pos(0.0, 0.0, cfg.hinge_z); const ChQuaternion<> hinge_rot = QuatFromAngleX(CH_PI / 2.0); - auto motor = chrono_types::make_shared(); - motor->Initialize(base_body, flap_body, ChFrame<>(hinge_pos, hinge_rot)); - auto tau_fun = chrono_types::make_shared(0.0); - motor->SetTorqueFunction(tau_fun); - system.AddLink(motor); + // Revolute constraint: 5-DOF hinge, free rotation about world Y-axis + auto revolute = chrono_types::make_shared(); + revolute->Initialize(base_body, flap_body, ChFrame<>(hinge_pos, hinge_rot)); + system.AddLink(revolute); auto waves = BuildWaveField(cfg); std::vector> bodies{flap_body, base_body}; @@ -259,22 +225,72 @@ int main(int argc, char* argv[]) { auto exc_provider = std::make_shared(0, 4); auto controller = BuildController(cfg, args.controller_override, hydro_data, exc_provider); + // RSDA: applies PTO torque at every force-assembly sub-step via RsdaPtoFunctor. + // This replaces the per-outer-step ChLinkMotorRotationTorque + ChFunctionConst pattern. + auto rsda = chrono_types::make_shared(); + rsda->Initialize(base_body, flap_body, false, + ChFramed(hinge_pos, hinge_rot), ChFramed(hinge_pos, hinge_rot)); + rsda->RegisterTorqueFunctor(std::make_shared(controller)); + system.AddLink(rsda); + std::vector records; records.reserve(static_cast(sim_duration / cfg.timestep) + 100); - if (args.visualization_on) { + const double dt = cfg.timestep; + #ifdef VGOSWEC_HAVE_SEASTACK_GUIHELPER - std::cout << "Visualization requested, but runtime GUI loop is not yet wired in this demo.\n" - << "Falling back to headless simulation.\n"; + // GUI path: CreateUI(true) -> real VSG renderer; CreateUI(false) -> headless no-op. + // A single loop driven by ui.IsRunning() works for both visual and headless runs. + auto pui = seastack::viz::CreateUI(args.visualization_on); + seastack::viz::UI& ui = *pui; + ui.Init(&system, "VGOSWEC-45"); + ui.SetCamera(0, -3.0, 0.5, 0, 0, -0.5); + ui.SetWaveModel(waves); + + while (system.GetChTime() <= sim_duration) { + if (!ui.IsRunning(dt)) break; + if (ui.simulationStarted) { + const double pitch_rad = rsda->GetAngle(); + const double pitch_vel = rsda->GetVelocity(); + + system.DoStepDynamics(dt); + + const auto& per_comp = hydro_system.GetLastComponentForces(); + if (!per_comp.empty()) { + exc_provider->Update(per_comp, system.GetChTime()); + } + const double exc_tau = exc_provider->GetLatestExcitationTorque(); + const double pto_tau = rsda->GetTorque(); + + records.push_back( + {system.GetChTime(), pitch_rad, pitch_vel, pto_tau, exc_tau, -pto_tau * pitch_vel}); + } + } #else + if (args.visualization_on) { std::cerr << "ERROR: Visualization requested but GUI support is not available in this build.\n" << "Rebuild with GUI support or use --no-viz.\n"; return 2; -#endif } - RunHeadlessLoop(system, hydro_system, flap_body, motor, controller, exc_provider, - sim_duration, cfg.timestep, records); + // Headless loop + while (system.GetChTime() <= sim_duration) { + const double pitch_rad = rsda->GetAngle(); + const double pitch_vel = rsda->GetVelocity(); + + system.DoStepDynamics(dt); + + const auto& per_comp = hydro_system.GetLastComponentForces(); + if (!per_comp.empty()) { + exc_provider->Update(per_comp, system.GetChTime()); + } + const double exc_tau = exc_provider->GetLatestExcitationTorque(); + const double pto_tau = rsda->GetTorque(); + + records.push_back( + {system.GetChTime(), pitch_rad, pitch_vel, pto_tau, exc_tau, -pto_tau * pitch_vel}); + } +#endif std::filesystem::create_directories("output"); std::ofstream csv("output/vgoswec_45_results.csv");