// Copyright 2021 DeepMind Technologies Limited // // 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 "engine/engine_util_spatial.h" #include #include #include "engine/engine_util_blas.h" #include "engine/engine_util_errmem.h" //------------------------------ quaternion operations --------------------------------------------- // rotate vector by quaternion void mju_rotVecQuat(mjtNum res[3], const mjtNum vec[3], const mjtNum quat[4]) { // null quat: copy vec if (quat[0]==1 && quat[1]==0 && quat[2]==0 && quat[3]==0) { mju_copy3(res, vec); } // regular processing else { mjtNum tmp[3]; // tmp = q_w * v + cross(q_xyz, v) tmp[0] = quat[0]*vec[0] + quat[2]*vec[2] - quat[3]*vec[1]; tmp[1] = quat[0]*vec[1] + quat[3]*vec[0] - quat[1]*vec[2]; tmp[2] = quat[0]*vec[2] + quat[1]*vec[1] - quat[2]*vec[0]; // res = v + 2 * cross(q_xyz, t) res[0] = vec[0] + 2 * (quat[2]*tmp[2] - quat[3]*tmp[1]); res[1] = vec[1] + 2 * (quat[3]*tmp[0] - quat[1]*tmp[2]); res[2] = vec[2] + 2 * (quat[1]*tmp[1] - quat[2]*tmp[0]); } } // negate quaternion void mju_negQuat(mjtNum res[4], const mjtNum quat[4]) { res[0] = quat[0]; res[1] = -quat[1]; res[2] = -quat[2]; res[3] = -quat[3]; } // multiply quaternions void mju_mulQuat(mjtNum res[4], const mjtNum qa[4], const mjtNum qb[4]) { mjtNum tmp[4] = { qa[0]*qb[0] - qa[1]*qb[1] - qa[2]*qb[2] - qa[3]*qb[3], qa[0]*qb[1] + qa[1]*qb[0] + qa[2]*qb[3] - qa[3]*qb[2], qa[0]*qb[2] - qa[1]*qb[3] + qa[2]*qb[0] + qa[3]*qb[1], qa[0]*qb[3] + qa[1]*qb[2] - qa[2]*qb[1] + qa[3]*qb[0] }; res[0] = tmp[0]; res[1] = tmp[1]; res[2] = tmp[2]; res[3] = tmp[3]; } // multiply quaternion and axis void mju_mulQuatAxis(mjtNum res[4], const mjtNum quat[4], const mjtNum axis[3]) { mjtNum tmp[4] = { -quat[1]*axis[0] - quat[2]*axis[1] - quat[3]*axis[2], quat[0]*axis[0] + quat[2]*axis[2] - quat[3]*axis[1], quat[0]*axis[1] + quat[3]*axis[0] - quat[1]*axis[2], quat[0]*axis[2] + quat[1]*axis[1] - quat[2]*axis[0] }; res[0] = tmp[0]; res[1] = tmp[1]; res[2] = tmp[2]; res[3] = tmp[3]; } // convert axisAngle to quaternion void mju_axisAngle2Quat(mjtNum res[4], const mjtNum axis[3], mjtNum angle) { // zero angle: null quat if (angle==0) { res[0] = 1; res[1] = 0; res[2] = 0; res[3] = 0; } // regular processing else { mjtNum s = mju_sin(angle*0.5); res[0] = mju_cos(angle*0.5); res[1] = axis[0]*s; res[2] = axis[1]*s; res[3] = axis[2]*s; } } // convert quaternion (corresponding to orientation difference) to 3D velocity void mju_quat2Vel(mjtNum res[3], const mjtNum quat[4], mjtNum dt) { mjtNum axis[3] = {quat[1], quat[2], quat[3]}; mjtNum sin_a_2 = mju_normalize3(axis); mjtNum speed = 2 * mju_atan2(sin_a_2, quat[0]); // when axis-angle is larger than pi, rotation is in the opposite direction if (speed>mjPI) { speed -= 2*mjPI; } speed /= dt; mju_scl3(res, axis, speed); } // Subtract quaternions, express as 3D velocity: qb*quat(res) = qa. void mju_subQuat(mjtNum res[3], const mjtNum qa[4], const mjtNum qb[4]) { // qdif = neg(qb)*qa mjtNum qneg[4], qdif[4]; mju_negQuat(qneg, qb); mju_mulQuat(qdif, qneg, qa); // convert to 3D velocity mju_quat2Vel(res, qdif, 1); } // convert quaternion to 3D rotation matrix void mju_quat2Mat(mjtNum res[9], const mjtNum quat[4]) { // null quat: identity if (quat[0]==1 && quat[1]==0 && quat[2]==0 && quat[3]==0) { res[0] = 1; res[1] = 0; res[2] = 0; res[3] = 0; res[4] = 1; res[5] = 0; res[6] = 0; res[7] = 0; res[8] = 1; } // regular processing else { const mjtNum q00 = quat[0]*quat[0]; const mjtNum q01 = quat[0]*quat[1]; const mjtNum q02 = quat[0]*quat[2]; const mjtNum q03 = quat[0]*quat[3]; const mjtNum q11 = quat[1]*quat[1]; const mjtNum q12 = quat[1]*quat[2]; const mjtNum q13 = quat[1]*quat[3]; const mjtNum q22 = quat[2]*quat[2]; const mjtNum q23 = quat[2]*quat[3]; const mjtNum q33 = quat[3]*quat[3]; res[0] = q00 + q11 - q22 - q33; res[4] = q00 - q11 + q22 - q33; res[8] = q00 - q11 - q22 + q33; res[1] = 2*(q12 - q03); res[2] = 2*(q13 + q02); res[3] = 2*(q12 + q03); res[5] = 2*(q23 - q01); res[6] = 2*(q13 - q02); res[7] = 2*(q23 + q01); } } // convert 3D rotation matrix to quaternion void mju_mat2Quat(mjtNum quat[4], const mjtNum mat[9]) { // q0 largest if (mat[0]+mat[4]+mat[8]>0) { quat[0] = 0.5 * mju_sqrt(1 + mat[0] + mat[4] + mat[8]); quat[1] = 0.25 * (mat[7] - mat[5]) / quat[0]; quat[2] = 0.25 * (mat[2] - mat[6]) / quat[0]; quat[3] = 0.25 * (mat[3] - mat[1]) / quat[0]; } // q1 largest else if (mat[0]>mat[4] && mat[0]>mat[8]) { quat[1] = 0.5 * mju_sqrt(1 + mat[0] - mat[4] - mat[8]); quat[0] = 0.25 * (mat[7] - mat[5]) / quat[1]; quat[2] = 0.25 * (mat[1] + mat[3]) / quat[1]; quat[3] = 0.25 * (mat[2] + mat[6]) / quat[1]; } // q2 largest else if (mat[4]>mat[8]) { quat[2] = 0.5 * mju_sqrt(1 - mat[0] + mat[4] - mat[8]); quat[0] = 0.25 * (mat[2] - mat[6]) / quat[2]; quat[1] = 0.25 * (mat[1] + mat[3]) / quat[2]; quat[3] = 0.25 * (mat[5] + mat[7]) / quat[2]; } // q3 largest else { quat[3] = 0.5 * mju_sqrt(1 - mat[0] - mat[4] + mat[8]); quat[0] = 0.25 * (mat[3] - mat[1]) / quat[3]; quat[1] = 0.25 * (mat[2] + mat[6]) / quat[3]; quat[2] = 0.25 * (mat[5] + mat[7]) / quat[3]; } mju_normalize4(quat); } // time-derivative of quaternion, given 3D rotational velocity void mju_derivQuat(mjtNum res[4], const mjtNum quat[4], const mjtNum vel[3]) { res[0] = 0.5*(-vel[0]*quat[1] - vel[1]*quat[2] - vel[2]*quat[3]); res[1] = 0.5*( vel[0]*quat[0] + vel[1]*quat[3] - vel[2]*quat[2]); res[2] = 0.5*(-vel[0]*quat[3] + vel[1]*quat[0] + vel[2]*quat[1]); res[3] = 0.5*( vel[0]*quat[2] - vel[1]*quat[1] + vel[2]*quat[0]); } // integrate quaternion given 3D angular velocity void mju_quatIntegrate(mjtNum quat[4], const mjtNum vel[3], mjtNum scale) { mjtNum angle, tmp[4], qrot[4]; // form local rotation quaternion, apply mju_copy3(tmp, vel); angle = scale * mju_normalize3(tmp); mju_axisAngle2Quat(qrot, tmp, angle); mju_mulQuat(quat, quat, qrot); mju_normalize4(quat); } // compute quaternion performing rotation from z-axis to given vector void mju_quatZ2Vec(mjtNum quat[4], const mjtNum vec[3]) { mjtNum axis[3], a, vn[3] = {vec[0], vec[1], vec[2]}, z[3] = {0, 0, 1}; // set default result to no-rotation quaternion quat[0] = 1; mju_zero3(quat+1); // normalize vector; if too small, no rotation if (mju_normalize3(vn)-0.5) { frame[4] = 1; } else { frame[5] = 1; } } // make yaxis orthogonal to xaxis mju_scl3(tmp, frame, mju_dot3(frame, frame+3)); mju_subFrom3(frame+3, tmp); mju_normalize3(frame+3); // zaxis = cross(xaxis, yaxis) mju_cross(frame+6, frame, frame+3); }