|
9 | 9 | #include "pinocchio/algorithm/crba.hpp" |
10 | 10 | #include "pinocchio/algorithm/jacobian.hpp" |
11 | 11 | #include "pinocchio/algorithm/compute-all-terms.hpp" |
| 12 | +#include "pinocchio/spatial/se3.hpp" |
| 13 | +#include "pinocchio/spatial/inertia.hpp" |
| 14 | +#include "pinocchio/multibody/model.hpp" |
| 15 | +#include "pinocchio/multibody/data.hpp" |
| 16 | +#include "pinocchio/algorithm/kinematics.hpp" |
| 17 | +#include "pinocchio/algorithm/frames.hpp" |
12 | 18 |
|
13 | 19 | #include <boost/test/unit_test.hpp> |
14 | 20 | #include <iostream> |
@@ -67,14 +73,290 @@ BOOST_AUTO_TEST_CASE(vsFreeFlyer) |
67 | 73 | // Calculer jdata.S().transpose() * data.f[i] |
68 | 74 | } |
69 | 75 |
|
70 | | -BOOST_AUTO_TEST_SUITE_END() |
71 | | - |
72 | | -// BEAU TEST |
73 | | -// Comparer spherical zyx avec ellipsoid |
74 | | -// base sur helical et universal |
75 | | -// vsPXRX ça teste les rotations |
| 76 | +// BOOST_AUTO_TEST_CASE(vsSphericalZYX) |
76 | 77 | // { |
77 | | - // TODO |
| 78 | +// using namespace pinocchio; |
| 79 | +// typedef SE3::Vector3 Vector3; |
| 80 | +// typedef Eigen::Matrix<double, 6, 1> Vector6; |
| 81 | +// typedef SE3::Matrix3 Matrix3; |
| 82 | + |
| 83 | +// // Build models using ModelGraph to enable configuration conversion |
| 84 | +// graph::ModelGraph g; |
| 85 | +// Inertia inertia(1., Vector3(0.5, 0., 0.0), Matrix3::Identity()); |
| 86 | +// SE3 pos(1); |
| 87 | +// pos.translation() = SE3::LinearType(1., 0., 0.); |
| 88 | + |
| 89 | +// g.addBody("root_body", inertia); |
| 90 | +// g.addBody("end_body", inertia); |
| 91 | + |
| 92 | +// // Create SphericalZYX joint (uses ZYX Euler angles) |
| 93 | +// g.addJoint( |
| 94 | +// "spherical_joint", |
| 95 | +// graph::JointSphericalZYX(), |
| 96 | +// "root_body", |
| 97 | +// SE3::Identity(), |
| 98 | +// "end_body", |
| 99 | +// pos); |
| 100 | + |
| 101 | +// // Build SphericalZYX model |
| 102 | +// const auto forward_build = graph::buildModelWithBuildInfo(g, "root_body", SE3::Identity()); |
| 103 | +// const Model & modelSphericalZYX = forward_build.model; |
| 104 | +// Data dataSphericalZYX(modelSphericalZYX); |
| 105 | + |
| 106 | +// Eigen::VectorXd q_zyx(3); |
| 107 | +// q_zyx << 0.5, 1.2, -0.8; |
| 108 | +// Eigen::VectorXd v_zyx(3); |
| 109 | +// v_zyx << 0.1, -0.3, 0.7; |
| 110 | +// forwardKinematics(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx); |
| 111 | +// updateFramePlacements(modelSphericalZYX, dataSphericalZYX); |
| 112 | + |
| 113 | +// auto end_index = modelSphericalZYX.getFrameId("end_body", BODY); |
| 114 | +// auto X_end = dataSphericalZYX.oMf[end_index]; |
| 115 | +// const auto backward_build = |
| 116 | +// graph::buildModelWithBuildInfo(g, "end_body", X_end); |
| 117 | + |
| 118 | +// const Model & XYZlacirehpSledom = backward_build.model; |
| 119 | + |
| 120 | +// // Create converter from SphericalZYX to Ellipsoid |
| 121 | +// auto converter = graph::createConverter( |
| 122 | +// modelSphericalZYX, XYZlacirehpSledom, forward_build.build_info, backward_build.build_info); |
| 123 | + |
| 124 | +// // Convert to Ellipsoid configuration |
| 125 | +// Eigen::VectorXd q_xyz = Eigen::VectorXd::Zero(XYZlacirehpSledom.nq); |
| 126 | +// Eigen::VectorXd v_xyz = Eigen::VectorXd::Zero(XYZlacirehpSledom.nv); |
| 127 | +// converter.convertConfigurationVector(q_zyx, q_xyz); |
| 128 | +// converter.convertTangentVector(q_zyx, v_zyx, v_xyz); |
| 129 | + |
| 130 | +// std::cout << "\n=== Configuration Conversion ===" << std::endl; |
| 131 | +// std::cout << "q_zyx (SphericalZYX): " << q_zyx.transpose() << std::endl; |
| 132 | +// std::cout << "q_xyz (converted): " << q_xyz.transpose() << std::endl; |
| 133 | +// std::cout << "v_zyx (SphericalZYX): " << v_zyx.transpose() << std::endl; |
| 134 | +// std::cout << "v_xyz (converted): " << v_xyz.transpose() << std::endl; |
| 135 | + |
| 136 | +// Model modelEllipsoid; |
| 137 | +// addJointAndBody(modelEllipsoid, JointModelEllipsoid(0, 0, 0), 0, pos, "ellipsoid", inertia); |
| 138 | +// Data dataEllipsoid(modelEllipsoid); |
| 139 | + |
| 140 | +// // Test forward kinematics with converted configurations |
| 141 | +// forwardKinematics(modelEllipsoid, dataEllipsoid, q_xyz, v_xyz); |
| 142 | +// forwardKinematics(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx); |
| 143 | + |
| 144 | +// // Check that the transformations are identical |
| 145 | +// std::cout << "\n=== Forward Kinematics Comparison ===" << std::endl; |
| 146 | +// std::cout << "oMi (Ellipsoid):\n" << dataEllipsoid.oMi[1] << std::endl; |
| 147 | +// std::cout << "oMi (SphericalZYX):\n" << dataSphericalZYX.oMi[1] << std::endl; |
| 148 | +// BOOST_CHECK(dataEllipsoid.oMi[1].isApprox(dataSphericalZYX.oMi[1])); |
| 149 | + |
| 150 | +// BOOST_CHECK(dataEllipsoid.liMi[1].isApprox(dataSphericalZYX.liMi[1])); |
| 151 | + |
| 152 | +// // Check that velocities match |
| 153 | +// BOOST_CHECK(dataEllipsoid.v[1].toVector().isApprox(dataSphericalZYX.v[1].toVector())); |
| 154 | + |
| 155 | +// // Test computeAllTerms |
| 156 | +// computeAllTerms(modelEllipsoid, dataEllipsoid, q_xyz, v_xyz); |
| 157 | +// computeAllTerms(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx); |
| 158 | + |
| 159 | +// BOOST_CHECK(dataEllipsoid.Ycrb[1].matrix().isApprox(dataSphericalZYX.Ycrb[1].matrix())); |
| 160 | + |
| 161 | +// BOOST_CHECK(dataEllipsoid.f[1].toVector().isApprox(dataSphericalZYX.f[1].toVector())); |
| 162 | + |
| 163 | +// BOOST_CHECK(dataEllipsoid.nle.isApprox(dataSphericalZYX.nle)); |
| 164 | + |
| 165 | +// BOOST_CHECK(dataEllipsoid.com[0].isApprox(dataSphericalZYX.com[0])); |
| 166 | + |
| 167 | +// // Test inverse dynamics (RNEA) |
| 168 | +// Eigen::VectorXd aEllipsoid = Eigen::VectorXd::Ones(modelEllipsoid.nv); |
| 169 | +// Eigen::VectorXd aSphericalZYX = Eigen::VectorXd::Ones(modelSphericalZYX.nv); |
| 170 | + |
| 171 | +// Eigen::VectorXd tauEllipsoid = rnea(modelEllipsoid, dataEllipsoid, q_xyz, v_xyz, aEllipsoid); |
| 172 | +// Eigen::VectorXd tauSphericalZYX = rnea(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx, aSphericalZYX); |
| 173 | + |
| 174 | +// BOOST_CHECK(tauEllipsoid.isApprox(tauSphericalZYX)); |
| 175 | + |
| 176 | +// // Test forward dynamics (ABA) |
| 177 | +// Eigen::VectorXd tau = Eigen::VectorXd::Ones(modelEllipsoid.nv); |
| 178 | + |
| 179 | +// Eigen::VectorXd aAbaEllipsoid = aba(modelEllipsoid, dataEllipsoid, q_xyz, v_xyz, tau, Convention::WORLD); |
| 180 | +// Eigen::VectorXd aAbaSphericalZYX = aba(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx, tau, Convention::WORLD); |
| 181 | + |
| 182 | +// BOOST_CHECK(aAbaEllipsoid.isApprox(aAbaSphericalZYX)); |
| 183 | + |
| 184 | +// // Test with LOCAL convention |
| 185 | +// aAbaEllipsoid = aba(modelEllipsoid, dataEllipsoid, q_xyz, v_xyz, tau, Convention::LOCAL); |
| 186 | +// aAbaSphericalZYX = aba(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx, tau, Convention::LOCAL); |
| 187 | + |
| 188 | +// BOOST_CHECK(aAbaEllipsoid.isApprox(aAbaSphericalZYX)); |
| 189 | + |
| 190 | +// // Test with different configurations |
| 191 | +// q_zyx << 0.2, -0.5, 1.1; |
| 192 | +// v_zyx << 0.3, 0.1, -0.2; |
| 193 | + |
| 194 | +// converter.convertConfigurationVector(q_zyx, q_xyz); |
| 195 | +// converter.convertTangentVector(q_zyx, v_zyx, v_xyz); |
| 196 | + |
| 197 | +// forwardKinematics(modelEllipsoid, dataEllipsoid, q_xyz, v_xyz); |
| 198 | +// forwardKinematics(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx); |
| 199 | + |
| 200 | +// BOOST_CHECK(dataEllipsoid.oMi[1].isApprox(dataSphericalZYX.oMi[1])); |
| 201 | + |
| 202 | +// BOOST_CHECK(dataEllipsoid.v[1].toVector().isApprox(dataSphericalZYX.v[1].toVector())); |
| 203 | + |
| 204 | +// tauEllipsoid = rnea(modelEllipsoid, dataEllipsoid, q_xyz, v_xyz, aEllipsoid); |
| 205 | +// tauSphericalZYX = rnea(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx, aSphericalZYX); |
| 206 | + |
| 207 | +// BOOST_CHECK(tauEllipsoid.isApprox(tauSphericalZYX)); |
| 208 | + |
| 209 | +// aAbaEllipsoid = aba(modelEllipsoid, dataEllipsoid, q_xyz, v_xyz, tau, Convention::WORLD); |
| 210 | +// aAbaSphericalZYX = aba(modelSphericalZYX, dataSphericalZYX, q_zyx, v_zyx, tau, Convention::WORLD); |
| 211 | + |
| 212 | +// BOOST_CHECK(aAbaEllipsoid.isApprox(aAbaSphericalZYX)); |
| 213 | + |
78 | 214 | // } |
79 | 215 |
|
80 | | -// Test translations |
| 216 | +BOOST_AUTO_TEST_CASE(vsSphericalZYX) |
| 217 | +{ |
| 218 | + using namespace pinocchio; |
| 219 | + typedef SE3::Vector3 Vector3; |
| 220 | + typedef SE3::Matrix3 Matrix3; |
| 221 | + |
| 222 | + Inertia inertia(1., Vector3(0.5, 0., 0.0), Matrix3::Identity()); |
| 223 | + SE3 pos(1); |
| 224 | + pos.translation() = SE3::LinearType(1., 0., 0.); |
| 225 | + |
| 226 | + // Create both models with the same structure |
| 227 | + Model modelEllipsoid, modelSphericalZYX; |
| 228 | + addJointAndBody(modelEllipsoid, JointModelEllipsoid(0, 0, 0), 0, pos, "ellipsoid", inertia); |
| 229 | + addJointAndBody(modelSphericalZYX, JointModelSphericalZYX(), 0, pos, "spherical", inertia); |
| 230 | + |
| 231 | + Data dataEllipsoid(modelEllipsoid); |
| 232 | + Data dataSphericalZYX(modelSphericalZYX); |
| 233 | + |
| 234 | + // Start with ZYX angles |
| 235 | + Eigen::VectorXd q_s(3); |
| 236 | + q_s << 0.5, 1.2, -0.8; // Z=0.5, Y=1.2, X=-0.8 |
| 237 | + Eigen::VectorXd qd_s(3); |
| 238 | + qd_s << 0.1, -0.3, 0.7; // ZYX angle velocities |
| 239 | + |
| 240 | + // Compute the rotation matrix from ZYX angles |
| 241 | + forwardKinematics(modelSphericalZYX, dataSphericalZYX, q_s, qd_s); |
| 242 | + const Matrix3 & R = dataSphericalZYX.oMi[1].rotation(); |
| 243 | + const Motion & spatial_vel_zyx = dataSphericalZYX.v[1]; |
| 244 | + |
| 245 | + std::cout << "\n=== Target Rotation Matrix (from ZYX) ===" << std::endl; |
| 246 | + std::cout << R << std::endl; |
| 247 | + |
| 248 | + // Extract XYZ Euler angles from the rotation matrix |
| 249 | + // For XYZ convention: R = Rx(x) * Ry(y) * Rz(z) |
| 250 | + // We need to solve for x, y, z |
| 251 | + |
| 252 | + Eigen::Vector3d q_e; |
| 253 | + |
| 254 | + // From the Ellipsoid rotation matrix structure: |
| 255 | + // R(0,2) = sin(y) |
| 256 | + // R(1,2) = -sin(x)*cos(y) |
| 257 | + // R(2,2) = cos(x)*cos(y) |
| 258 | + |
| 259 | + double sy = R(0, 2); |
| 260 | + q_e(1) = std::asin(sy); // y angle |
| 261 | + |
| 262 | + double cy = std::cos(q_e(1)); |
| 263 | + |
| 264 | + if (std::abs(cy) > 1e-6) { |
| 265 | + // Not at singularity |
| 266 | + q_e(0) = std::atan2(-R(1, 2) / cy, R(2, 2) / cy); // x angle |
| 267 | + q_e(2) = std::atan2(-R(0, 1) / cy, R(0, 0) / cy); // z angle |
| 268 | + } else { |
| 269 | + // Gimbal lock - choose x = 0 |
| 270 | + q_e(0) = 0.0; |
| 271 | + q_e(2) = std::atan2(R(1, 0), R(1, 1)); |
| 272 | + } |
| 273 | + |
| 274 | + std::cout << "\n=== Configuration ===" << std::endl; |
| 275 | + std::cout << "q_s (Z,Y,X): " << q_s.transpose() << std::endl; |
| 276 | + std::cout << "q_e (X,Y,Z): " << q_e.transpose() << std::endl; |
| 277 | + |
| 278 | + // Get the motion subspace matrices (which give us the Jacobians) |
| 279 | + JointModelSphericalZYX jmodel_s; |
| 280 | + JointDataSphericalZYX jdata_s = jmodel_s.createData(); |
| 281 | + jmodel_s.calc(jdata_s, q_s); |
| 282 | + |
| 283 | + JointModelEllipsoid jmodel_e(0, 0, 0); |
| 284 | + JointDataEllipsoid jdata_e = jmodel_e.createData(); |
| 285 | + jmodel_e.calc(jdata_e, q_e); |
| 286 | + |
| 287 | + |
| 288 | + // The motion subspace S gives us: omega = S * v |
| 289 | + Matrix3 S_s = jdata_s.S.matrix().bottomRows<3>(); // Angular part |
| 290 | + Matrix3 S_e = jdata_e.S.matrix().bottomRows<3>(); // Angular part |
| 291 | + |
| 292 | + Eigen::Vector3d qd_e = S_e.inverse() * S_s * qd_s; |
| 293 | + |
| 294 | + Eigen::Vector3d w_s = S_s * qd_s; |
| 295 | + Eigen::Vector3d w_e = S_e * qd_e; |
| 296 | + |
| 297 | + |
| 298 | + std::cout << "\n=== Joint Velocities ===" << std::endl; |
| 299 | + std::cout << "qd_s (joint velocities): " << qd_s.transpose() << std::endl; |
| 300 | + std::cout << "qd_e (joint velocities): " << qd_e.transpose() << std::endl; |
| 301 | + |
| 302 | + std::cout << "\n=== Motion Subspace Matrices ===" << std::endl; |
| 303 | + std::cout << "S_zyx (full 6x3):\n" << jdata_s.S.matrix() << std::endl; |
| 304 | + std::cout << "\nS_xyz (full 6x3):\n" << jdata_e.S.matrix() << std::endl; |
| 305 | + |
| 306 | + std::cout << "\n=== Angular Velocities from Joint Velocities ===" << std::endl; |
| 307 | + std::cout << "omega from ZYX (S_s * qd_s): " << w_s.transpose() << std::endl; |
| 308 | + std::cout << "omega from XYZ (S_e * qd_e): " << w_e.transpose() << std::endl; |
| 309 | + |
| 310 | + BOOST_CHECK(w_s.isApprox(w_e)); |
| 311 | + std::cout << "✓ Angular velocities from joint velocities match" << std::endl; |
| 312 | + |
| 313 | + // Compute forward kinematics with the converted configurations |
| 314 | + forwardKinematics(modelEllipsoid, dataEllipsoid, q_e, qd_e); |
| 315 | + |
| 316 | + // Also get the joint data to see body-frame velocities |
| 317 | + JointDataEllipsoid jdata_e_fk = jmodel_e.createData(); |
| 318 | + JointDataEllipsoid jdata_e_fk2 = jmodel_e.createData(); |
| 319 | + jmodel_e.calc(jdata_e_fk, q_e, qd_e); |
| 320 | + jmodel_e.calc(jdata_e_fk2, q_e); |
| 321 | + |
| 322 | + std::cout << "\n=== Motion Subspace Matrices after FK ===" << std::endl; |
| 323 | + std::cout << "S_e calc q:\n" << jdata_e_fk.S.matrix() << std::endl; |
| 324 | + std::cout << "\nS_e calc q v:\n" << jdata_e_fk2.S.matrix() << std::endl; |
| 325 | + |
| 326 | + BOOST_CHECK(jdata_e_fk.S.matrix().isApprox(jdata_e_fk2.S.matrix())); |
| 327 | + std::cout << "✓ Motion subspace matrices match" << std::endl; |
| 328 | + |
| 329 | + JointDataSphericalZYX jdata_s_fk = jmodel_s.createData(); |
| 330 | + jmodel_s.calc(jdata_s_fk, q_s, qd_s); |
| 331 | + |
| 332 | + std::cout << "\n=== Joint-frame velocities (S * v) ===" << std::endl; |
| 333 | + Eigen::Matrix<double, 6, 1> joint_vel_e = jdata_e_fk.S.matrix() * qd_e; |
| 334 | + std::cout << "joint_vel_e: " << joint_vel_e.transpose() << std::endl; |
| 335 | + std::cout << "jdata_e_fk.v : " << jdata_e_fk.v.toVector().transpose() << std::endl; |
| 336 | + |
| 337 | + |
| 338 | + |
| 339 | + Eigen::Matrix<double, 6, 1> joint_vel_s = jdata_s_fk.S.matrix() * qd_s; |
| 340 | + std::cout << "Ellipsoid (S_xyz * qd_e): " << joint_vel_e.transpose() << std::endl; |
| 341 | + std::cout << "SphericalZYX (S_zyx * qd_s): " << joint_vel_s.transpose() << std::endl; |
| 342 | + |
| 343 | + std::cout << "\n=== Rotation Matrices ===" << std::endl; |
| 344 | + std::cout << "Ellipsoid rotation:\n" << dataEllipsoid.oMi[1].rotation() << std::endl; |
| 345 | + std::cout << "\nSphericalZYX rotation:\n" << dataSphericalZYX.oMi[1].rotation() << std::endl; |
| 346 | + |
| 347 | + std::cout << "\n=== Translations ===" << std::endl; |
| 348 | + std::cout << "Ellipsoid translation: " << dataEllipsoid.oMi[1].translation().transpose() << std::endl; |
| 349 | + std::cout << "SphericalZYX translation: " << dataSphericalZYX.oMi[1].translation().transpose() << std::endl; |
| 350 | + |
| 351 | + std::cout << "\n=== Spatial Velocities ===" << std::endl; |
| 352 | + std::cout << "Ellipsoid v:\n" << dataEllipsoid.v[1].toVector().transpose() << std::endl; |
| 353 | + std::cout << "SphericalZYX v:\n" << dataSphericalZYX.v[1].toVector().transpose() << std::endl; |
| 354 | + |
| 355 | + BOOST_CHECK(dataEllipsoid.v[1].toVector().isApprox(dataSphericalZYX.v[1].toVector())); |
| 356 | + std::cout << "✓ Spatial velocities match" << std::endl; |
| 357 | + |
| 358 | + BOOST_CHECK(dataEllipsoid.oMi[1].isApprox(dataSphericalZYX.oMi[1])); |
| 359 | + std::cout << "✓ Full oMi[1] matches" << std::endl; |
| 360 | +} |
| 361 | + |
| 362 | +BOOST_AUTO_TEST_SUITE_END() |
0 commit comments