/* * Copyright (C) 2012 Open Source Robotics Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include "gazebo/math/Helpers.hh" #include "gazebo/math/Quaternion.hh" using namespace gazebo; class QuaternionTest : public ::testing::Test { }; ////////////////////////////////////////////////// TEST_F(QuaternionTest, Quaternion) { { math::Quaternion q; EXPECT_TRUE(math::equal(q.w, 1.0)); EXPECT_TRUE(math::equal(q.x, 0.0)); EXPECT_TRUE(math::equal(q.y, 0.0)); EXPECT_TRUE(math::equal(q.z, 0.0)); } { math::Quaternion q(1, 2, 3, 4); EXPECT_TRUE(math::equal(q.w, 1.0)); EXPECT_TRUE(math::equal(q.x, 2.0)); EXPECT_TRUE(math::equal(q.y, 3.0)); EXPECT_TRUE(math::equal(q.z, 4.0)); } { math::Quaternion q(0, 1, 2); EXPECT_TRUE(q == math::Quaternion(math::Vector3(0, 1, 2))); } math::Quaternion q1(math::Vector3(0, 0, 1), M_PI); EXPECT_TRUE(math::equal(q1.x, 0.0)); EXPECT_TRUE(math::equal(q1.y, 0.0)); EXPECT_TRUE(math::equal(q1.z, 1.0)); EXPECT_TRUE(math::equal(q1.w, 0.0)); math::Quaternion q(q1); EXPECT_TRUE(q == q1); q.SetToIdentity(); EXPECT_TRUE(math::equal(q.w, 1.0)); EXPECT_TRUE(math::equal(q.x, 0.0)); EXPECT_TRUE(math::equal(q.y, 0.0)); EXPECT_TRUE(math::equal(q.z, 0.0)); q = math::Quaternion(M_PI*0.1, M_PI*0.5, M_PI); EXPECT_TRUE(q == math::Quaternion(0.110616, -0.698401, 0.110616, 0.698401)); EXPECT_TRUE(q.GetLog() == math::Quaternion(0, -1.02593, 0.162491, 1.02593)); EXPECT_TRUE(q.GetExp() == math::Quaternion(0.545456, -0.588972, 0.093284, 0.588972)); q1 = q; q1.w = 2.0; EXPECT_TRUE(q1.GetLog() == math::Quaternion(0, -0.698401, 0.110616, 0.698401)); q1.x = 0.000000001; q1.y = 0.0; q1.z = 0.0; q1.w = 0.0; EXPECT_TRUE(q1.GetExp() == math::Quaternion(1, 0, 0, 0)); q.Invert(); EXPECT_TRUE(q == math::Quaternion(0.110616, 0.698401, -0.110616, -0.698401)); q.SetFromAxis(0, 1, 0, M_PI); EXPECT_TRUE(q == math::Quaternion(6.12303e-17, 0, 1, 0)); q.SetFromAxis(math::Vector3(1, 0, 0), M_PI); EXPECT_TRUE(q == math::Quaternion(0, 1, 0, 0)); q.Set(1, 2, 3, 4); EXPECT_TRUE(math::equal(q.w, 1.0)); EXPECT_TRUE(math::equal(q.x, 2.0)); EXPECT_TRUE(math::equal(q.y, 3.0)); EXPECT_TRUE(math::equal(q.z, 4.0)); q.Normalize(); EXPECT_TRUE(q == math::Quaternion(0.182574, 0.365148, 0.547723, 0.730297)); EXPECT_TRUE(math::equal(q.GetRoll(), 1.4289, 1e-3)); EXPECT_TRUE(math::equal(q.GetPitch(), -0.339837, 1e-3)); EXPECT_TRUE(math::equal(q.GetYaw(), 2.35619, 1e-3)); math::Vector3 axis; double angle; q.GetAsAxis(axis, angle); EXPECT_TRUE(axis == math::Vector3(0.371391, 0.557086, 0.742781)); EXPECT_TRUE(math::equal(angle, 2.77438, 1e-3)); q.Scale(0.1); EXPECT_TRUE(q == math::Quaternion(0.990394, 0.051354, 0.0770309, 0.102708)); q = q + math::Quaternion(0, 1, 2); EXPECT_TRUE(q == math::Quaternion(1.46455, -0.352069, 0.336066, 0.841168)); q += q; EXPECT_TRUE(q == math::Quaternion(2.92911, -0.704137, 0.672131, 1.68234)); q -= math::Quaternion(.4, .2, .1); EXPECT_TRUE(q == math::Quaternion(1.95416, -0.896677, 0.56453, 1.65341)); q = q - math::Quaternion(0, 1, 2); EXPECT_TRUE(q == math::Quaternion(1.48, -0.493254, 0.305496, 0.914947)); q *= math::Quaternion(.4, .1, .01); EXPECT_TRUE(q == math::Quaternion(1.53584, -0.236801, 0.551841, 0.802979)); q = q * 5.0; EXPECT_TRUE(q == math::Quaternion(7.67918, -1.184, 2.7592, 4.0149)); std::cerr << "[" << q.w << ", " << q.x << ", " << q.y << ", " << q.z << "]\n"; std::cerr << q.RotateVectorReverse(math::Vector3(1, 2, 3)) << "\n"; EXPECT_TRUE(q.RotateVectorReverse(math::Vector3(1, 2, 3)) == math::Vector3(-0.104115, 0.4975, 3.70697)); EXPECT_TRUE(math::equal(q.Dot(math::Quaternion(.4, .2, .1)), 7.67183, 1e-3)); EXPECT_TRUE(math::Quaternion::Squad(1.1, math::Quaternion(.1, 0, .2), math::Quaternion(0, .3, .4), math::Quaternion(.5, .2, 1), math::Quaternion(0, 0, 2), true) == math::Quaternion(0.346807, -0.0511734, -0.0494723, 0.935232)); EXPECT_TRUE(math::Quaternion::EulerToQuaternion(math::Vector3(.1, .2, .3)) == math::Quaternion(0.983347, 0.0342708, 0.106021, 0.143572)); q.Round(2); EXPECT_TRUE(math::equal(-1.18, q.x)); EXPECT_TRUE(math::equal(2.76, q.y)); EXPECT_TRUE(math::equal(4.01, q.z)); EXPECT_TRUE(math::equal(7.68, q.w)); q.x = q.y = q.z = q.w = 0.0; q.Normalize(); EXPECT_TRUE(q == math::Quaternion()); q.SetFromAxis(0, 0, 0, 0); EXPECT_TRUE(q == math::Quaternion()); EXPECT_TRUE(math::Quaternion::EulerToQuaternion(0.1, 0.2, 0.3) == math::Quaternion(0.983347, 0.0342708, 0.106021, 0.143572)); q.x = q.y = q.z = q.w = 0.0; q.GetAsAxis(axis, angle); EXPECT_TRUE(axis == math::Vector3(1, 0, 0)); EXPECT_TRUE(math::equal(angle, 0.0, 1e-3)); { // simple 180 rotation about yaw, should result in x and y flipping signs q = math::Quaternion(0, 0, M_PI); math::Vector3 v = math::Vector3(1, 2, 3); math::Vector3 r1 = q.RotateVector(v); math::Vector3 r2 = q.RotateVectorReverse(v); std::cout << "[" << q.w << ", " << q.x << ", " << q.y << ", " << q.z << "]\n"; std::cout << " forward turns [" << v << "] to [" << r1 << "]\n"; std::cout << " reverse turns [" << v << "] to [" << r2 << "]\n"; EXPECT_TRUE(r1 == math::Vector3(-1, -2, 3)); EXPECT_TRUE(r2 == math::Vector3(-1, -2, 3)); } { // simple 90 rotation about yaw, should map x to y, y to -x // simple -90 rotation about yaw, should map x to -y, y to x q = math::Quaternion(0, 0, 0.5*M_PI); math::Vector3 v = math::Vector3(1, 2, 3); math::Vector3 r1 = q.RotateVector(v); math::Vector3 r2 = q.RotateVectorReverse(v); std::cout << "[" << q.w << ", " << q.x << ", " << q.y << ", " << q.z << "]\n"; std::cout << " forward turns [" << v << "] to [" << r1 << "]\n"; std::cout << " reverse turns [" << v << "] to [" << r2 << "]\n"; std::cout << " x axis [" << q.GetXAxis() << "]\n"; std::cout << " y axis [" << q.GetYAxis() << "]\n"; std::cout << " z axis [" << q.GetZAxis() << "]\n"; EXPECT_TRUE(r1 == math::Vector3(-2, 1, 3)); EXPECT_TRUE(r2 == math::Vector3(2, -1, 3)); EXPECT_TRUE(q.GetInverse().GetXAxis() == math::Vector3(0, -1, 0)); EXPECT_TRUE(q.GetInverse().GetYAxis() == math::Vector3(1, 0, 0)); EXPECT_TRUE(q.GetInverse().GetZAxis() == math::Vector3(0, 0, 1)); } // Test RPY fixed-body-frame convention: // Rotate each unit vector in roll and pitch { q = math::Quaternion(M_PI/2.0, M_PI/2.0, 0); math::Vector3 v1(1, 0, 0); math::Vector3 r1 = q.RotateVector(v1); // 90 degrees about X does nothing, // 90 degrees about Y sends point down to -Z EXPECT_EQ(r1, math::Vector3(0, 0, -1)); math::Vector3 v2(0, 1, 0); math::Vector3 r2 = q.RotateVector(v2); // 90 degrees about X sends point to +Z // 90 degrees about Y sends point to +X EXPECT_EQ(r2, math::Vector3(1, 0, 0)); math::Vector3 v3(0, 0, 1); math::Vector3 r3 = q.RotateVector(v3); // 90 degrees about X sends point to -Y // 90 degrees about Y does nothing EXPECT_EQ(r3, math::Vector3(0, -1, 0)); } { // now try a harder case (axis[1,2,3], rotation[0.3*pi]) // verified with octave q.SetFromAxis(math::Vector3(1, 2, 3), 0.3*M_PI); std::cout << "[" << q.w << ", " << q.x << ", " << q.y << ", " << q.z << "]\n"; std::cout << " x [" << q.GetInverse().GetXAxis() << "]\n"; std::cout << " y [" << q.GetInverse().GetYAxis() << "]\n"; std::cout << " z [" << q.GetInverse().GetZAxis() << "]\n"; EXPECT_TRUE(q.GetInverse().GetXAxis() == math::Vector3(0.617229, -0.589769, 0.520770)); EXPECT_TRUE(q.GetInverse().GetYAxis() == math::Vector3(0.707544, 0.705561, -0.039555)); EXPECT_TRUE(q.GetInverse().GetZAxis() == math::Vector3(-0.344106, 0.392882, 0.852780)); // rotate about the axis of rotation should not change axis math::Vector3 v = math::Vector3(1, 2, 3); math::Vector3 r1 = q.RotateVector(v); math::Vector3 r2 = q.RotateVectorReverse(v); EXPECT_TRUE(r1 == math::Vector3(1, 2, 3)); EXPECT_TRUE(r2 == math::Vector3(1, 2, 3)); // rotate unit vectors v = math::Vector3(0, 0, 1); r1 = q.RotateVector(v); r2 = q.RotateVectorReverse(v); EXPECT_TRUE(r1 == math::Vector3(0.520770, -0.039555, 0.852780)); EXPECT_TRUE(r2 == math::Vector3(-0.34411, 0.39288, 0.85278)); v = math::Vector3(0, 1, 0); r1 = q.RotateVector(v); r2 = q.RotateVectorReverse(v); EXPECT_TRUE(r1 == math::Vector3(-0.58977, 0.70556, 0.39288)); EXPECT_TRUE(r2 == math::Vector3(0.707544, 0.705561, -0.039555)); v = math::Vector3(1, 0, 0); r1 = q.RotateVector(v); r2 = q.RotateVectorReverse(v); EXPECT_TRUE(r1 == math::Vector3(0.61723, 0.70754, -0.34411)); EXPECT_TRUE(r2 == math::Vector3(0.61723, -0.58977, 0.52077)); EXPECT_TRUE(-q == math::Quaternion(-0.891007, -0.121334, -0.242668, -0.364002)); EXPECT_TRUE(q.GetAsMatrix3() == math::Matrix3( 0.617229, -0.589769, 0.52077, 0.707544, 0.705561, -0.0395554, -0.344106, 0.392882, 0.85278)); EXPECT_TRUE(q.GetAsMatrix4() == math::Matrix4( 0.617229, -0.589769, 0.52077, 0, 0.707544, 0.705561, -0.0395554, 0, -0.344106, 0.392882, 0.85278, 0, 0, 0, 0, 1)); } // Test quaternion multiplication (rotation) order of application // if qa rotates frame o to p // qb rotates frame p to q // qc rotates frame q to r // qd rotates frame r to s // then qd * qc * qb * qa rotates frame o to s EXPECT_EQ(math::Quaternion(0, 0, 0), math::Quaternion(0, -0.5*M_PI, 0)* math::Quaternion(-0.5*M_PI, 0, 0)* math::Quaternion(0, 0.5*M_PI, 0)* math::Quaternion(0, 0, 0.5*M_PI)); EXPECT_EQ(math::Quaternion(0, 0, M_PI), math::Quaternion(0, 0, 0.5*M_PI)* math::Quaternion(0, 0.5*M_PI, 0)* math::Quaternion(-0.5*M_PI, 0, 0)* math::Quaternion(0, -0.5*M_PI, 0)); } ////////////////////////////////////////////////// TEST_F(QuaternionTest, Integrate) { // Integrate by zero, expect no change { const math::Quaternion q(0.5, 0.5, 0.5, 0.5); EXPECT_EQ(q, q.Integrate(math::Vector3::Zero, 1.0)); EXPECT_EQ(q, q.Integrate(math::Vector3::UnitX, 0.0)); EXPECT_EQ(q, q.Integrate(math::Vector3::UnitY, 0.0)); EXPECT_EQ(q, q.Integrate(math::Vector3::UnitZ, 0.0)); } // Integrate along single axes, // expect linear change in roll, pitch, yaw { const math::Quaternion q(1, 0, 0, 0); math::Quaternion qRoll = q.Integrate(math::Vector3::UnitX, 1.0); math::Quaternion qPitch = q.Integrate(math::Vector3::UnitY, 1.0); math::Quaternion qYaw = q.Integrate(math::Vector3::UnitZ, 1.0); EXPECT_EQ(qRoll.GetAsEuler(), math::Vector3::UnitX); EXPECT_EQ(qPitch.GetAsEuler(), math::Vector3::UnitY); EXPECT_EQ(qYaw.GetAsEuler(), math::Vector3::UnitZ); } // Integrate sequentially along single axes in order XYZ, // expect rotations to match Euler Angles { const math::Quaternion q(1, 0, 0, 0); const double angle = 0.5; math::Quaternion qX = q.Integrate(math::Vector3::UnitX, angle); math::Quaternion qXY = qX.Integrate(math::Vector3::UnitY, angle); EXPECT_EQ(qXY.GetAsEuler(), angle*math::Vector3(1, 1, 0)); } { const math::Quaternion q(1, 0, 0, 0); const double angle = 0.5; math::Quaternion qX = q.Integrate(math::Vector3::UnitX, angle); math::Quaternion qXZ = qX.Integrate(math::Vector3::UnitZ, angle); EXPECT_EQ(qXZ.GetAsEuler(), angle*math::Vector3(1, 0, 1)); } { const math::Quaternion q(1, 0, 0, 0); const double angle = 0.5; math::Quaternion qY = q.Integrate(math::Vector3::UnitY, angle); math::Quaternion qYZ = qY.Integrate(math::Vector3::UnitZ, angle); EXPECT_EQ(qYZ.GetAsEuler(), angle*math::Vector3(0, 1, 1)); } { const math::Quaternion q(1, 0, 0, 0); const double angle = 0.5; math::Quaternion qX = q.Integrate(math::Vector3::UnitX, angle); math::Quaternion qXY = qX.Integrate(math::Vector3::UnitY, angle); math::Quaternion qXYZ = qXY.Integrate(math::Vector3::UnitZ, angle); EXPECT_EQ(qXYZ.GetAsEuler(), angle*math::Vector3::One); } // Integrate sequentially along single axes in order ZYX, // expect rotations to not match Euler Angles { const math::Quaternion q(1, 0, 0, 0); const double angle = 0.5; math::Quaternion qZ = q.Integrate(math::Vector3::UnitZ, angle); math::Quaternion qZY = qZ.Integrate(math::Vector3::UnitY, angle); EXPECT_NE(qZY.GetAsEuler(), angle*math::Vector3(0, 1, 1)); } { const math::Quaternion q(1, 0, 0, 0); const double angle = 0.5; math::Quaternion qZ = q.Integrate(math::Vector3::UnitZ, angle); math::Quaternion qZX = qZ.Integrate(math::Vector3::UnitX, angle); EXPECT_NE(qZX.GetAsEuler(), angle*math::Vector3(1, 0, 1)); } { const math::Quaternion q(1, 0, 0, 0); const double angle = 0.5; math::Quaternion qZ = q.Integrate(math::Vector3::UnitZ, angle); math::Quaternion qZY = qZ.Integrate(math::Vector3::UnitY, angle); math::Quaternion qZYX = qZY.Integrate(math::Vector3::UnitX, angle); EXPECT_NE(qZYX.GetAsEuler(), angle*math::Vector3(1, 1, 1)); } { const math::Quaternion q(1, 0, 0, 0); const double angle = 0.5; math::Quaternion qY = q.Integrate(math::Vector3::UnitY, angle); math::Quaternion qYX = qY.Integrate(math::Vector3::UnitX, angle); EXPECT_NE(qYX.GetAsEuler(), angle*math::Vector3(1, 1, 0)); } // Integrate a full rotation about different axes, // expect no change. { const math::Quaternion q(0.5, 0.5, 0.5, 0.5); const double fourPi = 4 * M_PI; math::Quaternion qX = q.Integrate(math::Vector3::UnitX, fourPi); math::Quaternion qY = q.Integrate(math::Vector3::UnitY, fourPi); math::Quaternion qZ = q.Integrate(math::Vector3::UnitZ, fourPi); EXPECT_EQ(q, qX); EXPECT_EQ(q, qY); EXPECT_EQ(q, qZ); } }