- Move C++ sources to core/ subdirectory (T05) - Add pnpm workspaces + Turborepo pipeline (T06) - Add Emscripten CMake target and build-wasm.sh (T07) - Implement Embind bindings via YmirBindings.cpp (T08) - Add packages/types with TypeBox schemas for VesselDTO, ScenarioDTO, SimulationStateDTO (T09) - Create apps/api: Fastify v4 + Drizzle ORM + SQLite with vessels/scenarios CRUD (T10-T13) - Create apps/web: Vite + React 18 + COOP/COEP headers (T14) - Add simulation.worker.ts for WASM loop in Web Worker (T15) - Add Zustand simulationStore (T16) - Add TelemetryPanel component (T17) - Verified: pnpm build 3/3 tasks, 21ms cached; ctest 211/211 pass (T18-T19) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
73 lines
2.1 KiB
C++
73 lines
2.1 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
|
|
#include <ymir/physics/RigidBody6DOF.h>
|
|
#include <ymir/common/math/LinearAlgebra.h>
|
|
|
|
using Catch::Matchers::WithinAbs;
|
|
using namespace ymir;
|
|
|
|
static Matrix6x6 diagonalMass(double m, double Ixx, double Iyy, double Izz)
|
|
{
|
|
Matrix6x6 M{};
|
|
M[0][0] = m;
|
|
M[1][1] = m;
|
|
M[2][2] = m;
|
|
M[3][3] = Ixx;
|
|
M[4][4] = Iyy;
|
|
M[5][5] = Izz;
|
|
return M;
|
|
}
|
|
|
|
TEST_CASE("RigidBody6DOF: invTotalMass * totalMass ≈ I", "[body]")
|
|
{
|
|
Matrix6x6 mass = diagonalMass(1000.0, 5e5, 4e5, 3e5);
|
|
Matrix6x6 added = diagonalMass(200.0, 1e5, 0.8e5, 0.6e5);
|
|
Vector6 q{}, qdot{};
|
|
|
|
RigidBody6DOF body{0, mass, added, q, qdot};
|
|
|
|
auto result = math::matMul(body.totalMass(), body.invTotalMass());
|
|
|
|
for (int i = 0; i < 6; ++i)
|
|
for (int j = 0; j < 6; ++j)
|
|
REQUIRE_THAT(result[i][j], WithinAbs(i == j ? 1.0 : 0.0, 1e-10));
|
|
}
|
|
|
|
TEST_CASE("RigidBody6DOF: computeAcceleration F/m for diagonal mass", "[body]")
|
|
{
|
|
Matrix6x6 mass = diagonalMass(1000.0, 1e5, 1e5, 1e5);
|
|
Matrix6x6 added{};
|
|
Vector6 q{}, qdot{};
|
|
|
|
RigidBody6DOF body{0, mass, added, q, qdot};
|
|
|
|
Forces F;
|
|
F.f[0] = 500.0; // 500 N in surge
|
|
|
|
Vector6 acc = body.computeAcceleration(F);
|
|
|
|
// a = F/m = 500/1000 = 0.5 m/s²
|
|
REQUIRE_THAT(acc[0], WithinAbs(0.5, 1e-12));
|
|
for (int i = 1; i < 6; ++i)
|
|
REQUIRE_THAT(acc[i], WithinAbs(0.0, 1e-12));
|
|
}
|
|
|
|
TEST_CASE("RigidBody6DOF: state() returns correct snapshot", "[body]")
|
|
{
|
|
Matrix6x6 mass = diagonalMass(1000.0, 1e5, 1e5, 1e5);
|
|
Matrix6x6 added{};
|
|
Vector6 q{1.0, 2.0, 3.0, 0.1, 0.2, 0.3};
|
|
Vector6 qdot{4.0, 5.0, 6.0, 0.4, 0.5, 0.6};
|
|
|
|
RigidBody6DOF body{0, mass, added, q, qdot};
|
|
BodyState s = body.state();
|
|
|
|
REQUIRE_THAT(s.x(), WithinAbs(1.0, 1e-15));
|
|
REQUIRE_THAT(s.y(), WithinAbs(2.0, 1e-15));
|
|
REQUIRE_THAT(s.yaw(), WithinAbs(0.3, 1e-15));
|
|
REQUIRE_THAT(s.u(), WithinAbs(4.0, 1e-15));
|
|
REQUIRE_THAT(s.r(), WithinAbs(0.6, 1e-15));
|
|
REQUIRE_THAT(s.time(), WithinAbs(0.0, 1e-15)); // time starts at 0
|
|
}
|