Files
Ymir/tests/naval/TestWindForces.cpp
T
vinihcrosaandClaude Sonnet 4.6 dd4e79cbd6 feat(naval): add naval domain module (M2)
NavalForceModel bridge pattern keeps core::ForceModel interface intact
while providing NavalContext (environment, speedToWater, speedToWind,
EMA q_avg) to all naval models via bindContext.

Force models: Inertial (Coriolis), Restoring (hydrostatic spring),
Damping (potential + linear/quadratic), Squat (ICORELS shallow-water),
Wind (REGULAR/ACSINKAGE), Current (OBOKATA section-integration and
REGULAR with Van der Pol VIM RK4), Thrust (open-water Kt), Rudder
(Prandtl lift), Tug (PUSH/PULL/ESCORTING with bilinear table).

Wave engine: JONSWAP/PIERSON/REGULAR spectrum, Mitsuyasu cos-2S
directional spreading, WAMIT RAO interpolation in WaveForces.

NavalSimulation wraps core::Simulation, rebuilds NavalContext each
step, maintains EMA (tau=16.5s). 63/63 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 01:14:23 -03:00

73 lines
2.0 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include <ymir/naval/forces/WindForces.h>
#include <ymir/naval/NavalContext.h>
#include <ymir/core/BodyState.h>
using Catch::Approx;
using namespace ymir::naval;
static WindForces::Config makeWindCfg()
{
WindForces::Config cfg{};
cfg.model = WindModel::REGULAR;
cfg.frontalArea = 200.0;
cfg.lateralArea = 1000.0;
cfg.frontalHeight = 5.0;
cfg.lateralHeight = 5.0;
cfg.length_BP = 100.0;
cfg.beam = 20.0;
cfg.draft = 8.0;
// Simple Cd table: 0 and 360 degrees both Cd=1.0
cfg.angles = {0.0, 90.0, 180.0, 270.0, 360.0};
cfg.cdx = {1.0, 0.0, -1.0, 0.0, 1.0};
cfg.cdy = {0.0, 1.0, 0.0, -1.0, 0.0};
cfg.cdz = {0.0, 0.0, 0.0, 0.0, 0.0};
return cfg;
}
static NavalContext makeCtx(double wind_u, double wind_v)
{
ymir::Vector6 q{};
ymir::Vector6 qdot{};
ymir::BodyState bs(q, qdot, 0.0, 0.1);
NavalContext ctx{};
ctx.state = bs;
ctx.speedToWind[0] = wind_u;
ctx.speedToWind[1] = wind_v;
return ctx;
}
TEST_CASE("WindForces zero wind produces no force")
{
WindForces model(makeWindCfg());
NavalContext ctx = makeCtx(0.0, 0.0);
model.bindContext(&ctx);
auto forces = model.compute(ctx.state);
for (int i = 0; i < 6; ++i)
REQUIRE(forces.f[i] == Approx(0.0).margin(1e-12));
}
TEST_CASE("WindForces head wind produces surge drag")
{
WindForces model(makeWindCfg());
NavalContext ctx = makeCtx(10.0, 0.0); // wind from ahead
model.bindContext(&ctx);
auto forces = model.compute(ctx.state);
REQUIRE(forces.f[0] != Approx(0.0).margin(1e-3));
REQUIRE(std::abs(forces.f[1]) < std::abs(forces.f[0])); // surge dominant
}
TEST_CASE("WindForces beam wind produces sway force")
{
WindForces model(makeWindCfg());
NavalContext ctx = makeCtx(0.0, 10.0); // wind from beam
model.bindContext(&ctx);
auto forces = model.compute(ctx.state);
REQUIRE(forces.f[1] != Approx(0.0).margin(1e-3));
}