// 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 "user/user_util.h" #include #include #include #include #include #include #include #include #include #include "engine/engine_crossplatform.h" #include "engine/engine_util_misc.h" #include "engine/engine_util_spatial.h" using std::isnan; using std::string; using std::numeric_limits; // set value of NAN here; needs const double mjNAN = numeric_limits::quiet_NaN(); // check if numeric variable is defined bool mjuu_defined(const double num) { return !isnan(num); } // compute address of M[g1][g2] where M is triangular n-by-n int mjuu_matadr(int g1, int g2, const int n) { if (g1<0 || g2<0 || g1>=n || g2>=n) { return -1; } if (g1>g2) { int tmp = g1; g1 = g2; g2 = tmp; } return g1*n + g2; } // set 4D vector void mjuu_setvec(double* dest, const double x, const double y, const double z, const double w) { dest[0] = x; dest[1] = y; dest[2] = z; dest[3] = w; } void mjuu_setvec(float* dest, const double x, const double y, const double z, const double w) { dest[0] = (float)x; dest[1] = (float)y; dest[2] = (float)z; dest[3] = (float)w; } // set 3D vector void mjuu_setvec(double* dest, const double x, const double y, const double z) { dest[0] = x; dest[1] = y; dest[2] = z; } void mjuu_setvec(float* dest, const double x, const double y, const double z) { dest[0] = (float)x; dest[1] = (float)y; dest[2] = (float)z; } // set 2D vector void mjuu_setvec(double* dest, const double x, const double y) { dest[0] = x; dest[1] = y; } // copy double array void mjuu_copyvec(double* dest, const double* src, const int n) { for (int i=0; i0) { quat[0] = 0.5 * sqrt(1 + mat[0][0] + mat[1][1] + mat[2][2]); quat[1] = 0.25 * (mat[1][2] - mat[2][1]) / quat[0]; quat[2] = 0.25 * (mat[2][0] - mat[0][2]) / quat[0]; quat[3] = 0.25 * (mat[0][1] - mat[1][0]) / quat[0]; } // q1 largest else if (mat[0][0]>mat[1][1] && mat[0][0]>mat[2][2]) { quat[1] = 0.5 * sqrt(1 + mat[0][0] - mat[1][1] - mat[2][2]); quat[0] = 0.25 * (mat[1][2] - mat[2][1]) / quat[1]; quat[2] = 0.25 * (mat[1][0] + mat[0][1]) / quat[1]; quat[3] = 0.25 * (mat[2][0] + mat[0][2]) / quat[1]; } // q2 largest else if (mat[1][1]>mat[2][2]) { quat[2] = 0.5 * sqrt(1 - mat[0][0] + mat[1][1] - mat[2][2]); quat[0] = 0.25 * (mat[2][0] - mat[0][2]) / quat[2]; quat[1] = 0.25 * (mat[1][0] + mat[0][1]) / quat[2]; quat[3] = 0.25 * (mat[2][1] + mat[1][2]) / quat[2]; } // q3 largest else { quat[3] = 0.5 * sqrt(1 - mat[0][0] - mat[1][1] + mat[2][2]); quat[0] = 0.25 * (mat[0][1] - mat[1][0]) / quat[3]; quat[1] = 0.25 * (mat[2][0] + mat[0][2]) / quat[3]; quat[2] = 0.25 * (mat[2][1] + mat[1][2]) / quat[3]; } mjuu_normvec(quat, 4); } // invert frame transformation void mjuu_frameinvert(double newpos[3], double newquat[4], const double oldpos[3], const double oldquat[4]) { // position mjuu_localaxis(newpos, oldpos, oldquat); newpos[0] = -newpos[0]; newpos[1] = -newpos[1]; newpos[2] = -newpos[2]; // orientation newquat[0] = oldquat[0]; newquat[1] = -oldquat[1]; newquat[2] = -oldquat[2]; newquat[3] = -oldquat[3]; } // accumulate frame transformations (forward kinematics) void mjuu_frameaccum(double pos[3], double quat[4], const double childpos[3], const double childquat[4]) { double mat[9], vec[3], qtmp[4]; mjuu_quat2mat(mat, quat); mjuu_mulvecmat(vec, childpos, mat); pos[0] += vec[0]; pos[1] += vec[1]; pos[2] += vec[2]; mjuu_mulquat(qtmp, quat, childquat); mjuu_copyvec(quat, qtmp, 4); } // accumulate frame transformation in second frame void mjuu_frameaccumChild(const double pos[3], const double quat[4], double childpos[3], double childquat[4]) { double p[] = {pos[0], pos[1], pos[2]}; double q[] = {quat[0], quat[1], quat[2], quat[3]}; mjuu_frameaccum(p, q, childpos, childquat); mjuu_copyvec(childpos, p, 3); mjuu_copyvec(childquat, q, 4); } // invert frame accumulation void mjuu_frameaccuminv(double pos[3], double quat[4], const double childpos[3], const double childquat[4]) { double mat[9], vec[3], qtmp[4]; double qneg[4] = {childquat[0], -childquat[1], -childquat[2], -childquat[3]}; mjuu_mulquat(qtmp, quat, qneg); mjuu_copyvec(quat, qtmp, 4); mjuu_quat2mat(mat, quat); mjuu_mulvecmat(vec, childpos, mat); pos[0] -= vec[0]; pos[1] -= vec[1]; pos[2] -= vec[2]; } // convert local_inertia[3] to global_inertia[6] void mjuu_globalinertia(double* global, const double* local, const double* quat) { double mat[9]; mjuu_quat2mat(mat, quat); double tmp[9] = { mat[0]*local[0], mat[3]*local[0], mat[6]*local[0], mat[1]*local[1], mat[4]*local[1], mat[7]*local[1], mat[2]*local[2], mat[5]*local[2], mat[8]*local[2] }; global[0] = mat[0]*tmp[0] + mat[1]*tmp[3] + mat[2]*tmp[6]; global[1] = mat[3]*tmp[1] + mat[4]*tmp[4] + mat[5]*tmp[7]; global[2] = mat[6]*tmp[2] + mat[7]*tmp[5] + mat[8]*tmp[8]; global[3] = mat[0]*tmp[1] + mat[1]*tmp[4] + mat[2]*tmp[7]; global[4] = mat[0]*tmp[2] + mat[1]*tmp[5] + mat[2]*tmp[8]; global[5] = mat[3]*tmp[2] + mat[4]*tmp[5] + mat[5]*tmp[8]; } // compute off-center correction to inertia matrix // mass * [y^2+z^2, -x*y, -x*z; -x*y, x^2+z^2, -y*z; -x*z, -y*z, x^2+y^2] void mjuu_offcenter(double* res, const double mass, const double* vec) { res[0] = mass*(vec[1]*vec[1] + vec[2]*vec[2]); res[1] = mass*(vec[0]*vec[0] + vec[2]*vec[2]); res[2] = mass*(vec[0]*vec[0] + vec[1]*vec[1]); res[3] = -mass*vec[0]*vec[1]; res[4] = -mass*vec[0]*vec[2]; res[5] = -mass*vec[1]*vec[2]; } // compute viscosity coefficients from mass and inertia void mjuu_visccoef(double* visccoef, double mass, const double* inertia, double scl) { // compute equivalent box double equivbox[3]; equivbox[0] = sqrt(mju_max(mjMINVAL, (inertia[1] + inertia[2] - inertia[0])) / mass * 6.0); equivbox[1] = sqrt(mju_max(mjMINVAL, (inertia[0] + inertia[2] - inertia[1])) / mass * 6.0); equivbox[2] = sqrt(mju_max(mjMINVAL, (inertia[0] + inertia[1] - inertia[2])) / mass * 6.0); // apply formula for box (or rather cross) viscosity // torque components visccoef[0] = scl * 4.0 / 3.0 * equivbox[0] * (equivbox[1]*equivbox[1]*equivbox[1] + equivbox[2]*equivbox[2]*equivbox[2]); visccoef[1] = scl * 4.0 / 3.0 * equivbox[1] * (equivbox[0]*equivbox[0]*equivbox[0] + equivbox[2]*equivbox[2]*equivbox[2]); visccoef[2] = scl * 4.0 / 3.0 * equivbox[2] * (equivbox[0]*equivbox[0]*equivbox[0] + equivbox[1]*equivbox[1]*equivbox[1]); // force components visccoef[3] = scl * 4*equivbox[1]*equivbox[2]; visccoef[4] = scl * 4*equivbox[0]*equivbox[2]; visccoef[5] = scl * 4*equivbox[0]*equivbox[1]; } // update moving frame along a curve or initialize it, returns edge length // inputs: // normal - normal vector computed by a previous call to the function // edge - edge vector (non-unit tangent vector) // tprv - unit tangent vector of previous body // tnxt - unit tangent vector of next body // first - 1 if the frame requires initialization // outputs: // quat - frame orientation // normal - unit normal vector mjtNum mju_updateFrame(mjtNum quat[4], mjtNum normal[3], const mjtNum edge[3], const mjtNum tprv[3], const mjtNum tnxt[3], int first) { mjtNum tangent[3], binormal[3]; // normalize tangent mjuu_copyvec(tangent, edge, 3); mjuu_normvec(tangent, 3); // compute moving frame if (first) { // use the first vertex binormal for the first edge mjuu_crossvec(binormal, tangent, tnxt); mjuu_normvec(binormal, 3); // compute edge normal given tangent and binormal mjuu_crossvec(normal, binormal, tangent); mjuu_normvec(normal, 3); } else { mjtNum darboux[4]; // rotate edge normal about the vertex binormal mjuu_crossvec(binormal, tprv, tangent); mjtNum angle = atan2(mjuu_normvec(binormal, 3), mjuu_dot3(tprv, tangent)); mju_axisAngle2Quat(darboux, binormal, angle); mju_rotVecQuat(normal, normal, darboux); mjuu_normvec(normal, 3); // compute edge binormal given tangent and normal mjuu_crossvec(binormal, tangent, normal); mjuu_normvec(binormal, 3); } // global orientation of the frame mjuu_frame2quat(quat, tangent, normal, binormal); // return edge length return sqrt(mjuu_dot3(edge, edge)); } // strip directory from filename string mjuu_strippath(string filename) { // find last pathsymbol size_t start = filename.find_last_of("/\\"); // no path found: return original if (start==string::npos) { return filename; } // return name without path else { return filename.substr(start+1, filename.size()-start-1); } } // strip extension string mjuu_stripext(string filename) { // find last dot size_t end = filename.find_last_of('.'); // no path found: return original if (end==string::npos) { return filename; } // return name without extension return filename.substr(0, end); } string mjuu_getext(std::string_view filename) { size_t dot = filename.find_last_of('.'); if (dot==string::npos) { return ""; } return string(filename.substr(dot, filename.size() - dot)); } // is directory path absolute bool mjuu_isabspath(string path) { // empty: not absolute if (path.empty()) { return false; } // path is scheme:filename which we consider an absolute path // e.g. file URI's are always absolute paths if (mjp_getResourceProvider(path.c_str()) != nullptr) { return true; } // check first char const char* str = path.c_str(); if (str[0]=='\\' || str[0]=='/') { return true; } // find ":/" or ":\" if (path.find(":/")!=string::npos || path.find(":\\")!=string::npos) { return true; } return false; } // assemble full filename string mjuu_makefullname(string filedir, string meshdir, string filename) { // filename has absolute path: filename if (mjuu_isabspath(filename)) { return filename; } // meshdir has absolute path: meshdir + filename if (mjuu_isabspath(meshdir)) { return meshdir + filename; } // default return filedir + meshdir + filename; } // return true if the text is in a valid content type format: // {type}/{subtype}[;{parameter}={value}] static bool mjuu_isValidContentType(std::string_view text) { // find a forward slash that's not the last character size_t n = text.find('/'); if (n == std::string::npos || n == text.size() - 1) { return false; } size_t m = text.find(';'); if (m == std::string::npos) { return true; } if (m + 1 <= n) { return false; } // just check if there's an equal sign; this isn't robust enough for general // validation, but works for our scope, hence this is a private helper // function size_t s = text.find('='); if (s == std::string::npos || s + 1 <= m) { return false; } return true; } // return type from content_type format {type}/{subtype}[;{parameter}={value}] // return empty string on invalid format std::optional mjuu_parseContentTypeAttrType(std::string_view text) { if (!mjuu_isValidContentType(text)) { return std::nullopt; } return { text.substr(0, text.find('/')) }; } // return subtype from content_type format {type}/{subtype}[;{parameter}={value}] // return empty string on invalid format std::optional mjuu_parseContentTypeAttrSubtype(std::string_view text) { if (!mjuu_isValidContentType(text)) { return std::nullopt; } size_t n = text.find('/'); size_t m = text.find(';', n + 1); if (m == std::string::npos) { return { text.substr(n+1) }; } return { text.substr(n + 1, m - n - 1) }; } // convert filename extension to content type; return empty string if not found std::string mjuu_extToContentType(std::string_view filename) { std::string ext = mjuu_getext(filename); if (!strcasecmp(ext.c_str(), ".stl")) { return "model/stl"; } else if (!strcasecmp(ext.c_str(), ".obj")) { return "model/obj"; } else if (!strcasecmp(ext.c_str(), ".ply")) { return "model/ply"; } else if (!strcasecmp(ext.c_str(), ".msh")) { return "model/vnd.mujoco.msh"; } else if (!strcasecmp(ext.c_str(), ".png")) { return "image/png"; } else { return ""; } }