Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed fabs() use in quaternion interpolation #1479

Merged
merged 9 commits into from
Aug 9, 2022
25 changes: 13 additions & 12 deletions moveit_core/robot_model/src/floating_joint_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,19 +136,20 @@ void FloatingJointModel::interpolate(const double* from, const double* to, const
state[1] = from[1] + (to[1] - from[1]) * t;
state[2] = from[2] + (to[2] - from[2]) * t;

double dq = fabs(from[3] * to[3] + from[4] * to[4] + from[5] * to[5] + from[6] * to[6]);
double theta = (dq + std::numeric_limits<double>::epsilon() >= 1.0) ? 0.0 : acos(dq);
if (theta > std::numeric_limits<double>::epsilon())
// Check if the quaternions are significantly different
if (abs(from[3] - to[3]) + abs(from[4] - to[4]) + abs(from[5] - to[5]) + abs(from[6] - to[6]) >
AndyZe marked this conversation as resolved.
Show resolved Hide resolved
std::numeric_limits<double>::epsilon())
{
double d = 1.0 / sin(theta);
double s0 = sin((1.0 - t) * theta);
double s1 = sin(t * theta);
if (dq < 0) // Take care of long angle case see http://en.wikipedia.org/wiki/Slerp
s1 = -s1;
state[3] = (from[3] * s0 + to[3] * s1) * d;
state[4] = (from[4] * s0 + to[4] * s1) * d;
state[5] = (from[5] * s0 + to[5] * s1) * d;
state[6] = (from[6] * s0 + to[6] * s1) * d;
// Note the ordering: Eigen takes w first!
Eigen::Quaterniond q1(from[6], from[3], from[4], from[5]);
Eigen::Quaterniond q2(to[6], to[3], to[4], to[5]);

Eigen::Quaterniond q = q1.slerp(t, q2);

state[3] = q.x();
state[4] = q.y();
state[5] = q.z();
state[6] = q.w();
}
else
{
Expand Down
44 changes: 44 additions & 0 deletions moveit_core/robot_model/test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,50 @@ TEST(SiblingAssociateLinks, SimpleYRobot)
}
}

TEST(FloatingJointTest, interpolation_test)
{
// Create a simple floating joint model with some dummy parameters (these are not used by the test)
moveit::core::FloatingJointModel fjm("joint", 0, 0);

// We set some bounds where the joint position's translation component is bounded between -1 and 1 in all
// dimensions. This is necessary, otherwise we just get (0,0,0) translations.
moveit::core::JointModel::Bounds bounds;
bounds = fjm.getVariableBounds();
bounds[0].min_position_ = -1.0;
bounds[0].max_position_ = 1.0;
bounds[1].min_position_ = -1.0;
bounds[1].max_position_ = 1.0;
bounds[2].min_position_ = -1.0;
bounds[2].max_position_ = 1.0;

double jv1[7];
double jv2[7];
double intp[7];
random_numbers::RandomNumberGenerator rng;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is currently some ongoing refactoring around RNG, see #1267. @sjahr any thoughts if we should merge this and refactor later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not half-theta at all, that really is the angle between the two quaternions. The halving only shows up when you when you convert between Quaternions and axis-angle stuff to my knowledge

Copy link
Contributor Author

@werner291 werner291 Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agree that just calling into Eigen would be way easier (and probably better/safer) than DIY-ing the math ourselves; I just didn't feel confident suggesting a larger rewrite than was necessary to fix the bug.

Do note that Eigen's Quaternion parameters are in a different order than MoveIt's.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can merge this now b/c it's an obvious improvement. Thanks! especially for the new test

I'm still not sure I agree on the factor of 2 though. We can make an issue and figure it out later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I think I misread your website. Lemme take a look at what they're doing, Quaternions are tricky

Copy link
Contributor Author

@werner291 werner291 Aug 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, found a source here that features the *2 as well https://nl.mathworks.com/matlabcentral/answers/415936-angle-between-2-quaternions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a fantastic introduction to quaternions that only slightly breaks my brain: https://eater.net/quaternions

Copy link
Contributor Author

@werner291 werner291 Aug 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://math.stackexchange.com/questions/4053346/the-angle-between-two-quaternion-vectors-is-equal-to-half-the-angle-between-thei

that given two quaternions q1,q2∈R4, the angle between the 4D quaternion vectors is equal to half the angle between their corresponding 3D orientations.

Oh my god the angle is half the angle... I think this is a definitions issue. Do we want the angle between rotations, or do we want the angle between the quaternions?

On the referenced Wikipedia article: "Compute Ω as the angle subtended by the arc, so that cos Ω = p0 ∙ p1, the n-dimensional dot product of the unit vectors from the origin to the ends."

So (if I read this correctly) it's the angle between the vectors (quaternions are basically 4D vectors), not the angle between the rotations that the quaternions represent. Therefore, it's theta and not half-theta.

This also reinforces my belief that we really should just let Eigen handle this...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In light of that explanation, this code here (https://github.com/ros-planning/moveit2/blob/main/moveit_core/robot_model/src/floating_joint_model.cpp#L122) is also wrong (the method name explicitly mentions "rotation" and not "quaternion angle"), and is missing the factor 2.

(Doesn't change anything for our case since the slerp operation explicitly requires the angle between quaternions, not rotations)


for (size_t i = 0; i < 1000; ++i)
{
// Randomize the joint settings.
fjm.getVariableRandomPositions(rng, jv1, bounds);
fjm.getVariableRandomPositions(rng, jv2, bounds);

// Pick a random interpolation value
double t = rng.uniformReal(0.0, 1.0);

// Apply the interpolation
fjm.interpolate(jv1, jv2, t, intp);

// Get the distances between the two joint configurations
double d1 = fjm.distance(jv1, intp);
double d2 = fjm.distance(jv2, intp);
double t_total = fjm.distance(jv1, jv2);

// Check that the resulting distances match with the interpolation value
EXPECT_NEAR(d1, t_total * t, 1e-6);
EXPECT_NEAR(d2, t_total * (1.0 - t), 1e-6);
}
}

int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
Expand Down