Skip to content

Commit eed114b

Browse files
LucasJosephipuch
authored andcommitted
Added test for joint ellipsoid vs spherical
1 parent ded3caf commit eed114b

File tree

2 files changed

+291
-11
lines changed

2 files changed

+291
-11
lines changed

include/pinocchio/multibody/joint/joint-ellipsoid.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,6 @@ namespace pinocchio
449449
const typename Eigen::MatrixBase<ConfigVector> & qs,
450450
const typename Eigen::MatrixBase<TangentVector> & vs) const
451451
{
452-
// Configuration part
453452
data.joint_q = qs.template segment<NQ>(idx_q());
454453

455454
Scalar c0, s0;
@@ -467,15 +466,14 @@ namespace pinocchio
467466
data.M.rotation() << c1c2, -c1s2, s1,
468467
c0 * s2 + c2 * s0 * s1, c0 * c2 - s0 * s1 * s2, -c1 * s0,
469468
-c0 * c2 * s1 + s0 * s2, c0 * s1 * s2 + c2 * s0, c0 * c1;
470-
469+
471470
Scalar nx, ny, nz;
472471
nx = s1;
473472
ny = -s0 * c1;
474473
nz = c0 * c1;
475474

476475
data.M.translation() << radius_a * nx, radius_b * ny, radius_c * nz;
477476

478-
// First derivatives of n_dot with respect to q_dot
479477
Scalar dndotx_dqdot1, dndoty_dqdot0, dndoty_dqdot1, dndotz_dqdot0, dndotz_dqdot1;
480478
dndotx_dqdot1 = c1;
481479
dndoty_dqdot0 = - c0 * c1;

unittest/joint-ellipsoid.cpp

Lines changed: 290 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
#include "pinocchio/algorithm/crba.hpp"
1010
#include "pinocchio/algorithm/jacobian.hpp"
1111
#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"
1218

1319
#include <boost/test/unit_test.hpp>
1420
#include <iostream>
@@ -67,14 +73,290 @@ BOOST_AUTO_TEST_CASE(vsFreeFlyer)
6773
// Calculer jdata.S().transpose() * data.f[i]
6874
}
6975

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)
7677
// {
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+
78214
// }
79215

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

Comments
 (0)