// 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_setconst.h" #include #include #include #include #include #include // IWYU pragma: keep #include "engine/engine_core_smooth.h" #include "engine/engine_core_util.h" #include "engine/engine_forward.h" #include "engine/engine_io.h" #include "engine/engine_memory.h" #include "engine/engine_util_blas.h" #include "engine/engine_util_errmem.h" #include "engine/engine_util_misc.h" #include "engine/engine_util_sparse.h" #include "engine/engine_util_spatial.h" // compute dof_M0 via composite rigid body algorithm static void mj_setM0(mjModel* m, mjData* d) { mjtNum buf[6]; mjtNum* crb = d->crb; int last_body = m->nbody - 1, nv = m->nv; // copy cinert into crb mju_copy(crb, d->cinert, 10*m->nbody); // backward pass over bodies, accumulate composite inertias for (int i=last_body; i > 0; i--) { if (m->body_parentid[i] > 0) { mju_addTo(crb+10*m->body_parentid[i], crb+10*i, 10); } } for (int i=0; i < nv; i++) { // precomute buf = crb_body_i * cdof_i mju_mulInertVec(buf, crb+10*m->dof_bodyid[i], d->cdof+6*i); // dof_M0(i) = armature inertia + cdof_i * (crb_body_i * cdof_i) mjtNum armature = m->dof_armature[i] + mj_actuatorArmature(m, mjOBJ_JOINT, m->dof_jntid[i]); m->dof_M0[i] = armature + mju_dot(d->cdof+6*i, buf, 6); } } // helper function to get the tree id of a wrap object static int GetWrapBodyTreeId(const mjModel* m, int wrap_index) { int bodyid = -1; int objid = m->wrap_objid[wrap_index]; switch ((mjtWrap)m->wrap_type[wrap_index]) { case mjWRAP_JOINT: bodyid = m->jnt_bodyid[objid]; break; case mjWRAP_SITE: bodyid = m->site_bodyid[objid]; break; case mjWRAP_SPHERE: case mjWRAP_CYLINDER: bodyid = m->geom_bodyid[objid]; break; case mjWRAP_PULLEY: case mjWRAP_NONE: break; } return (bodyid != -1) ? m->body_treeid[bodyid] : -1; } // set fixed quantities (do not depend on qpos0) static void setFixed(mjModel* m, mjData* d) { mj_markStack(d); // ----- general // compute subtreemass for (int i=0; i < m->nbody; i++) { m->body_subtreemass[i] = m->body_mass[i]; } for (int i=m->nbody-1; i > 0; i--) { m->body_subtreemass[m->body_parentid[i]] += m->body_subtreemass[i]; } // compute ngravcomp: number of bodies with gravity compensation int ngravcomp = 0; for (int i=0; i < m->nbody; i++) { ngravcomp += (m->body_gravcomp[i] > 0); } m->ngravcomp = ngravcomp; // set jnt_actuatorid and tendon_actuatorid mju_fillInt(m->jnt_actuatorid, -1, m->njnt); mju_fillInt(m->tendon_actuatorid, -1, m->ntendon); for (int i=0; i < m->nu; i++) { // skip actuator with no damping and no armature if (m->actuator_damping[i] == 0 && mju_isZero(m->actuator_dampingpoly+mjNPOLY*i, mjNPOLY) && m->actuator_armature[i] == 0) { continue; } // joint or jointinparent transmission if (m->actuator_trntype[i] == mjTRN_JOINT || m->actuator_trntype[i] == mjTRN_JOINTINPARENT) { int jntid = m->actuator_trnid[2*i]; // first actuator: set id to i if (m->jnt_actuatorid[jntid] == -1) { m->jnt_actuatorid[jntid] = i; } // multiple actuators acting on single transmission: use -2 sentinel else { m->jnt_actuatorid[jntid] = -2; } } // tendon transmission else if (m->actuator_trntype[i] == mjTRN_TENDON) { int tenid = m->actuator_trnid[2*i]; // first actuator: set id to i if (m->tendon_actuatorid[tenid] == -1) { m->tendon_actuatorid[tenid] = i; } // multiple actuators acting on single transmission: use -2 sentinel else { m->tendon_actuatorid[tenid] = -2; } } } // ----- tree related (body_treeid and dof_treeid already computed) // compute body_treeid for (int i=0; i < m->nbody; i++) { int weldid = m->body_weldid[i]; if (m->body_dofnum[weldid]) { m->body_treeid[i] = m->dof_treeid[m->body_dofadr[weldid]]; } else { m->body_treeid[i] = -1; } } // compute tree_bodyadr, tree_bodynum mju_zeroInt(m->tree_bodynum, m->ntree); int tree_current = -1; for (int i=1; i < m->nbody; i++) { int treeid = m->body_treeid[i]; if (treeid != -1) { if (treeid > tree_current) { m->tree_bodyadr[++tree_current] = i; } m->tree_bodynum[tree_current]++; } } // compute tree_dofadr, tree_dofnum mju_zeroInt(m->tree_dofnum, m->ntree); tree_current = -1; for (int i=0; i < m->nv; i++) { if (m->dof_treeid[i] > tree_current) { m->tree_dofadr[++tree_current] = i; } m->tree_dofnum[tree_current]++; } // compute tendon_treeid, tendon_treenum int* tree_marker = mjSTACKALLOC(d, m->ntree, int); // 1 if tree has been visited, 0 otherwise for (int i = 0; i < m->ntendon; i++) { mju_zeroInt(tree_marker, m->ntree); m->tendon_treenum[i] = 0; m->tendon_treeid[2*i] = -1; m->tendon_treeid[2*i+1] = -1; for (int j = m->tendon_adr[i]; j < m->tendon_adr[i] + m->tendon_num[i]; j++) { int wrap_treeid = GetWrapBodyTreeId(m, j); if (wrap_treeid != -1 && !tree_marker[wrap_treeid]) { tree_marker[wrap_treeid] = 1; if (m->tendon_treenum[i] == 0) { m->tendon_treeid[2*i] = wrap_treeid; } else if (m->tendon_treenum[i] == 1) { m->tendon_treeid[2*i+1] = wrap_treeid; } m->tendon_treenum[i]++; } } } // ----- apply compiler AUTO tree sleep policy // actuators: trees with any actuated joint, site, body, or tendon do not auto-sleep for (int i=0; i < m->nu; i++) { int bodyid = -1; int tid = m->actuator_trnid[2*i]; switch ((mjtTrn)m->actuator_trntype[i]) { case mjTRN_JOINT: case mjTRN_JOINTINPARENT: bodyid = m->jnt_bodyid[tid]; break; case mjTRN_SITE: case mjTRN_SLIDERCRANK: bodyid = m->site_bodyid[tid]; break; case mjTRN_BODY: bodyid = tid; break; case mjTRN_TENDON: // wake all trees connected by this actuated tendon for (int j = m->tendon_adr[tid]; j < m->tendon_adr[tid] + m->tendon_num[tid]; j++) { int treeid = GetWrapBodyTreeId(m, j); if (treeid != -1 && m->tree_sleep_policy[treeid] == mjSLEEP_AUTO) { m->tree_sleep_policy[treeid] = mjSLEEP_AUTO_NEVER; } } continue; // next actuator case mjTRN_UNDEFINED: continue; // next actuator } // wake tree containing bodyid, if any if (bodyid != -1) { int treeid = m->body_treeid[bodyid]; if (treeid != -1 && m->tree_sleep_policy[treeid] == mjSLEEP_AUTO) { m->tree_sleep_policy[treeid] = mjSLEEP_AUTO_NEVER; } } } // trees with inter-tree tendons that have non-zero stiffness or damping do not auto-sleep // if the tendon spans more than 2 trees. for (int i=0; i < m->ntendon; i++) { int treenum = m->tendon_treenum[i]; // tendon spans 1 or 0 trees: skip if (treenum < 2) { continue; } // tendon spans 2 trees and has no stiffness or damping: skip if (treenum == 2 && m->tendon_stiffness[i] == 0 && mju_isZero(m->tendon_stiffnesspoly+mjNPOLY*i, mjNPOLY) && m->tendon_damping[i] == 0 && mju_isZero(m->tendon_dampingpoly+mjNPOLY*i, mjNPOLY) && m->tendon_actuatorid[i] == -1) { continue; } // tendon spans two trees with stiffness or damping or more than two trees: wake all trees mju_zeroInt(tree_marker, m->ntree); for (int j = m->tendon_adr[i]; j < m->tendon_adr[i] + m->tendon_num[i]; j++) { int treeid = GetWrapBodyTreeId(m, j); // if the tree is not yet marked, mark it and wake it up if (treeid != -1 && !tree_marker[treeid]) { tree_marker[treeid] = 1; int policy = m->tree_sleep_policy[treeid]; // mark tree as never sleeping if (policy == mjSLEEP_AUTO) { m->tree_sleep_policy[treeid] = mjSLEEP_AUTO_NEVER; } // if the user marked it as sleepable, throw an error else if (policy == mjSLEEP_ALLOWED || policy == mjSLEEP_INIT) { mj_freeStack(d); if (treenum > 2) { mjERROR("tree %d connected to tendon %d which spans more than 2 trees, " "sleeping not allowed", treeid, i); } else { mjERROR("tree %d connected to tendon %d with non-zero stiffness or damping, " "sleeping not allowed", treeid, i); } } } } } // flexes: constraint-free trees are not allowed to sleep for (int i = 0; i < m->nflex; ++i) { // constrained flexes are allowed to sleep if (m->flex_edgeequality[i]) { continue; } // node-based flex if (m->flex_interp[i]) { int nodenum = m->flex_nodenum[i]; int* bodyid = m->flex_nodebodyid + m->flex_nodeadr[i]; for (int j = 0; j < nodenum; ++j) { int treeid = m->body_treeid[bodyid[j]]; if (treeid != -1 && m->tree_sleep_policy[treeid] == mjSLEEP_AUTO) { m->tree_sleep_policy[treeid] = mjSLEEP_AUTO_NEVER; } } } // vertex-based flex else { int vertnum = m->flex_vertnum[i]; int* bodyid = m->flex_vertbodyid + m->flex_vertadr[i]; for (int j = 0; j < vertnum; ++j) { int treeid = m->body_treeid[bodyid[j]]; if (treeid != -1 && m->tree_sleep_policy[treeid] == mjSLEEP_AUTO) { m->tree_sleep_policy[treeid] = mjSLEEP_AUTO_NEVER; } } } } // set remaining trees with mjSLEEP_AUTO policy to mjSLEEP_AUTO_ALLOWED for (int i = 0; i < m->ntree; i++) { if (m->tree_sleep_policy[i] == mjSLEEP_AUTO) { m->tree_sleep_policy[i] = mjSLEEP_AUTO_ALLOWED; } } mj_freeStack(d); } // compute tendon Jacobian sparsity static void makeTendonSparse(mjModel* m) { int ntendon = m->ntendon; int* rownnz = m->ten_J_rownnz; int* rowadr = m->ten_J_rowadr; int* colind = m->ten_J_colind; if (!ntendon) { return; } // clear mju_zeroInt(rownnz, ntendon); mju_zeroInt(rowadr, ntendon); // compute rownnz, rowadr, and colind for each tendon for (int i = 0; i < ntendon; i++) { rowadr[i] = (i > 0 ? rowadr[i-1] + rownnz[i-1] : 0); int adr = m->tendon_adr[i]; int num = m->tendon_num[i]; // joint tendon: each wrap object is a joint, colind is its dofadr if (m->wrap_type[adr] == mjWRAP_JOINT) { for (int j = 0; j < num; j++) { colind[rowadr[i] + j] = m->jnt_dofadr[m->wrap_objid[adr + j]]; } rownnz[i] = num; } else { // spatial tendon: collect used dofs from wrap object bodies int nnz = 0; for (int j = 0; j < num; j++) { int type = m->wrap_type[adr + j]; // get body id from site or geom wrap object int bodyid = -1; if (type == mjWRAP_SITE) { bodyid = m->site_bodyid[m->wrap_objid[adr + j]]; } else if (type == mjWRAP_SPHERE || type == mjWRAP_CYLINDER) { bodyid = m->geom_bodyid[m->wrap_objid[adr + j]]; } // walk up the body tree, collecting used dofs if (bodyid > 0) { int bid = bodyid; while (bid > 0) { int bdofadr = m->body_dofadr[bid]; int bdofnum = m->body_dofnum[bid]; for (int k = 0; k < bdofnum; k++) { int dof = bdofadr + k; // check if dof already in colind int found = 0; for (int l = 0; l < nnz; l++) { if (colind[rowadr[i] + l] == dof) { found = 1; break; } } // append new dof if (!found) { colind[rowadr[i] + nnz] = dof; nnz++; } } bid = m->body_parentid[bid]; } } } rownnz[i] = nnz; } // sort colind for this tendon int nnz = rownnz[i]; for (int j = 0; j < nnz - 1; j++) { for (int k = j + 1; k < nnz; k++) { // swap out-of-order entries if (colind[rowadr[i] + k] < colind[rowadr[i] + j]) { int tmp = colind[rowadr[i] + j]; colind[rowadr[i] + j] = colind[rowadr[i] + k]; colind[rowadr[i] + k] = tmp; } } } } } // compute flex sparsity: flexedge_J_{rowadr,rownnz,colind} and flexvert_J_{rowadr,rownnz} static void makeFlexSparse(mjModel* m, mjData* d) { int nv = m->nv; int* rowadr = m->flexedge_J_rowadr; int* rownnz = m->flexedge_J_rownnz; int* colind = m->flexedge_J_colind; int* vrowadr = m->flexvert_J_rowadr; int* vrownnz = m->flexvert_J_rownnz; if (!m->nflex) { return; } mj_markStack(d); int* chain = mjSTACKALLOC(d, nv, int); int* chain1 = mjSTACKALLOC(d, nv, int); int* chain2 = mjSTACKALLOC(d, nv, int); int* buf_ind = mjSTACKALLOC(d, nv, int); mjtNum* dummy_pos = mjSTACKALLOC(d, 3, mjtNum); mju_zero(dummy_pos, 3); // clear mju_zeroInt(rowadr, m->nflexedge); mju_zeroInt(rownnz, m->nflexedge); mju_zeroInt(vrowadr, 2 * m->nflexvert); mju_zeroInt(vrowadr, 2 * m->nflexvert); mju_zeroInt(vrownnz, 2 * m->nflexvert); mju_zeroInt(m->flex_vertedgeadr, m->nflexvert); mju_zeroInt(m->flex_vertedgenum, m->nflexvert); mju_zeroInt(m->flex_vertedge, 2 * m->nflexedge); mju_zeroInt(m->flex_vertedge, 2 * m->nflexedge); mju_zero(m->flex_vertmetric, 4 * m->nflexvert); mju_zeroInt(m->flexedge_J_colind, m->nJfe); mju_zeroInt(m->flexvert_J_colind, 2 * m->nJfv); int current_adj_offset = 0; // compute lengths and Jacobians of edges for (int f = 0; f < m->nflex; f++) { // skip if edges cannot generate forces if (m->flex_rigid[f] || m->flex_interp[f]) { continue; } // skip Jacobian if no built-in passive force is needed int skipjacobian = !m->flex_edgeequality[f] && !m->flex_edgedamping[f] && !m->flex_edgestiffness[f] && !m->flex_damping[f]; // process edges of this flex int vbase = m->flex_vertadr[f]; int ebase = m->flex_edgeadr[f]; for (int e = 0; e < m->flex_edgenum[f]; e++) { if (skipjacobian) { continue; } // set rowadr if (ebase + e > 0) { rowadr[ebase + e] = rowadr[ebase + e - 1] + rownnz[ebase + e - 1]; } int v1 = m->flex_edge[2 * (ebase + e)]; int v2 = m->flex_edge[2 * (ebase + e) + 1]; int b1 = m->flex_vertbodyid[vbase + v1]; int b2 = m->flex_vertbodyid[vbase + v2]; // get sparsity int NV = mj_jacDifPair(m, d, chain, b1, b2, dummy_pos, dummy_pos, NULL, NULL, NULL, NULL, NULL, NULL, /*issparse=*/1, /*skipcommon=*/0); // copy sparsity info rownnz[ebase + e] = NV; mju_copyInt(colind + rowadr[ebase + e], chain, NV); } // if dim=2 and constraints are active we use the vertex-based constraint if (m->flex_dim[f] == 2 && m->flex_edgeequality[f] == 2) { int nvert = m->flex_vertnum[f]; // populate global vertex adjacency list int* v_edge_cnt = m->flex_vertedgenum + vbase; int* v_edge_adr = m->flex_vertedgeadr + vbase; int* adj_edges = m->flex_vertedge; // global array for (int e = 0; e < m->flex_edgenum[f]; ++e) { v_edge_cnt[m->flex_edge[2 * (ebase + e) + 0]]++; v_edge_cnt[m->flex_edge[2 * (ebase + e) + 1]]++; } int total_adj_edges = 0; for (int v = 0; v < nvert; ++v) { v_edge_adr[v] = current_adj_offset + total_adj_edges; total_adj_edges += v_edge_cnt[v]; } int* v_edge_fill = mjSTACKALLOC(d, nvert, int); mju_zeroInt(v_edge_fill, nvert); for (int e = 0; e < m->flex_edgenum[f]; ++e) { int v1 = m->flex_edge[2 * (ebase + e) + 0]; int v2 = m->flex_edge[2 * (ebase + e) + 1]; adj_edges[v_edge_adr[v1] + v_edge_fill[v1]] = e; v_edge_fill[v1]++; adj_edges[v_edge_adr[v2] + v_edge_fill[v2]] = e; v_edge_fill[v2]++; } // precompute metric (Binv) for (int v = 0; v < nvert; ++v) { mjtNum B[4] = {0}; int v_global = vbase + v; for (int k = 0; k < v_edge_cnt[v]; ++k) { int e = adj_edges[v_edge_adr[v] + k]; // compute rest edge vector mjtNum dx[3]; int v1 = m->flex_edge[2 * (ebase + e)]; int v2 = m->flex_edge[2 * (ebase + e) + 1]; mju_sub3(dx, m->flex_vert0 + 3 * (vbase + v2), m->flex_vert0 + 3 * (vbase + v1)); // apply scaling since they are half sizes dx[0] *= 2 * m->flex_size[3 * f + 0]; dx[1] *= 2 * m->flex_size[3 * f + 1]; dx[2] *= 2 * m->flex_size[3 * f + 2]; if (mju_abs(dx[2]) > mjMINVAL) { mjERROR("flex vertices are not in the same plane"); } // get mass of neighbor vertex mjtNum weight = 1.0; int neighbor_v = (v == v1) ? v2 : v1; int b_neighbor = m->flex_vertbodyid[vbase + neighbor_v]; if (b_neighbor >= 0) { weight = m->body_mass[b_neighbor]; if (weight < mjMINVAL) weight = mjMINVAL; } // accumulate B += w * dx * dx' for (int row = 0; row < 2; row++) { for (int col = 0; col < 2; col++) { B[2 * row + col] += weight * dx[row] * dx[col]; } } } mjtNum* metric = m->flex_vertmetric + 4 * v_global; mjtNum det = B[0] * B[3] - B[1] * B[2]; if (mju_abs(det) < mjMINVAL) { mju_zero(metric, 4); } else { mjtNum invdet = 1.0 / det; metric[0] = B[3] * invdet; metric[1] = -B[1] * invdet; metric[2] = -B[2] * invdet; metric[3] = B[0] * invdet; } } // advance global offset current_adj_offset += total_adj_edges; // determine start address for this flex int v0_base = 2 * vbase; int current_adr = 0; if (v0_base > 0) { current_adr = vrowadr[v0_base - 1] + vrownnz[v0_base - 1]; } vrowadr[v0_base] = current_adr; for (int v = 0; v < nvert; ++v) { // clear buf_ind mju_zeroInt(buf_ind, nv); int current_nnz = 0; for (int i = 0; i < v_edge_cnt[v]; ++i) { int e = adj_edges[v_edge_adr[v] + i]; int v1 = m->flex_edge[2 * (ebase + e)]; int v2 = m->flex_edge[2 * (ebase + e) + 1]; // chains from edge e int b1 = m->flex_vertbodyid[vbase + v1]; int b2 = m->flex_vertbodyid[vbase + v2]; int NV1 = mj_bodyChain(m, b1, chain1); int NV2 = mj_bodyChain(m, b2, chain2); for (int j = 0; j < NV1; ++j) { if (!buf_ind[chain1[j]]) { buf_ind[chain1[j]] = 1; current_nnz++; } } for (int j = 0; j < NV2; ++j) { if (!buf_ind[chain2[j]]) { buf_ind[chain2[j]] = 1; current_nnz++; } } } int row0 = 2 * (vbase + v); int row1 = 2 * (vbase + v) + 1; vrownnz[row0] = vrownnz[row1] = current_nnz; // set rowadr for next rows vrowadr[row1] = vrowadr[row0] + current_nnz; if (row1 + 1 < 2 * m->nflexvert) { vrowadr[row1 + 1] = vrowadr[row1] + current_nnz; } // fill colind int count = 0; for (int j = 0; j < nv; j++) { if (buf_ind[j]) { m->flexvert_J_colind[vrowadr[row0] + count] = j; m->flexvert_J_colind[vrowadr[row1] + count] = j; count++; } } } } } mj_freeStack(d); } // align 2D flexes to the XY plane static void mj_alignFlex(mjModel* m, mjData* d) { for (int f = 0; f < m->nflex; f++) { // only for 2D flexes with vertex equality constraints if (m->flex_dim[f] == 2 && m->flex_edgeequality[f] == 2) { // get element data int t_adr = m->flex_elemdataadr[f]; int vbase = m->flex_vertadr[f]; int t0 = m->flex_elem[t_adr]; int t1 = m->flex_elem[t_adr + 1]; int t2 = m->flex_elem[t_adr + 2]; // compute normal from first element mjtNum edge1[3], edge2[3], normal[3]; mju_sub3(edge1, m->flex_vert0 + 3 * (vbase + t1), m->flex_vert0 + 3 * (vbase + t0)); mju_sub3(edge2, m->flex_vert0 + 3 * (vbase + t2), m->flex_vert0 + 3 * (vbase + t0)); mju_cross(normal, edge1, edge2); mju_normalize3(normal); // compute rotation to Z mjtNum quat[4], mat[9]; mju_quatZ2Vec(quat, normal); mju_quat2Mat(mat, quat); // rotate all vertices of this flex int nvert = m->flex_vertnum[f]; for (int v = 0; v < nvert; v++) { mjtNum* vert = m->flex_vert0 + 3 * (vbase + v); mjtNum res[3]; mju_mulMatTVec3(res, mat, vert); mju_copy3(vert, res); // check planarity (warning if not planar) if (mju_abs(vert[2] - m->flex_vert0[3 * (vbase + t0) + 2]) > 100 * mjMINVAL) { static int warned = 0; if (!warned) { warned = 1; mju_warning("flex %d is not planar", f); } } } } } } // set quantities that depend on qpos0 static void set0(mjModel* m, mjData* d) { makeTendonSparse(m); makeFlexSparse(m, d); mj_alignFlex(m, d); int nv = m->nv; mjtNum A[36] = {0}, pos[3], quat[4]; mj_markStack(d); mjtNum* jac = mjSTACKALLOC(d, 6*nv, mjtNum); mjtNum* tmp = mjSTACKALLOC(d, 6*nv, mjtNum); mjtNum* moment = mjSTACKALLOC(d, nv, mjtNum); int* cammode = 0; int* lightmode = 0; // save camera and light mode, set to fixed if (m->ncam) { cammode = mjSTACKALLOC(d, m->ncam, int); for (int i=0; i < m->ncam; i++) { cammode[i] = m->cam_mode[i]; m->cam_mode[i] = mjCAMLIGHT_FIXED; } } if (m->nlight) { lightmode = mjSTACKALLOC(d, m->nlight, int); for (int i=0; i < m->nlight; i++) { lightmode[i] = m->light_mode[i]; m->light_mode[i] = mjCAMLIGHT_FIXED; } } // run computations in qpos0 mju_copy(d->qpos, m->qpos0, m->nq); mj_kinematics(m, d); mj_comPos(m, d); mj_camlight(m, d); // compute dof_M0 for CRB algorithm mj_setM0(m, d); // save flex_rigid, temporarily make all flexes non-rigid mjtByte* rigid = NULL; if (m->nflex) { rigid = mjSTACKALLOC(d, m->nflex, mjtByte); memcpy(rigid, m->flex_rigid, m->nflex); memset(m->flex_rigid, 0, m->nflex); } // run remaining computations mj_tendon(m, d); mj_makeM(m, d); mj_factorM(m, d); mj_flex(m, d); mj_transmission(m, d); // restore flex rigidity if (m->nflex) { memcpy(m->flex_rigid, rigid, m->nflex); } // restore camera and light mode for (int i=0; i < m->ncam; i++) { m->cam_mode[i] = cammode[i]; } for (int i=0; i < m->nlight; i++) { m->light_mode[i] = lightmode[i]; } // copy fields mju_copy(m->flexedge_length0, d->flexedge_length, m->nflexedge); mju_copy(m->tendon_length0, d->ten_length, m->ntendon); mju_copy(m->actuator_length0, d->actuator_length, m->nu); // compute body_invweight0 m->body_invweight0[0] = m->body_invweight0[1] = 0.0; for (int i=1; i < m->nbody; i++) { // static bodies: zero invweight0 if (m->body_weldid[i] == 0) { m->body_invweight0[2*i] = m->body_invweight0[2*i+1] = 0; } // accelerate simple bodies with no rotations else if (m->body_simple[i] == 2) { mjtNum mass = m->body_mass[i]; if (!mass) { // SHOULD NOT OCCUR mjERROR("moving body %d has 0 mass", i); } m->body_invweight0[2*i+0] = 1/mju_max(mjMINVAL, mass); m->body_invweight0[2*i+1] = 0; } // general body: full inertia else { if (nv) { // inverse spatial inertia: A = J*inv(M)*J' mj_jacBodyCom(m, d, jac, jac+3*nv, i); mj_solveM(m, d, tmp, jac, 6); mju_mulMatMatT(A, jac, tmp, 6, nv, 6); } // average diagonal and assign m->body_invweight0[2*i] = (A[0] + A[7] + A[14])/3; m->body_invweight0[2*i+1] = (A[21] + A[28] + A[35])/3; } } // compute dof_invweight0 for (int i=0; i < m->njnt; i++) { // simple body with no rotations: no off-diagonal inertia if (m->body_simple[m->jnt_bodyid[i]] == 2) { int id = m->jnt_dofadr[i]; int bi = m->jnt_bodyid[i]; mjtNum mass = m->body_mass[bi]; if (!mass) { // SHOULD NOT OCCUR mjERROR("moving body %d has 0 mass", bi); } m->dof_invweight0[id] = 1/mju_max(mjMINVAL, mass); } // general joint: full inertia else { int dnum, id = m->jnt_dofadr[i]; // get number of components if (m->jnt_type[i] == mjJNT_FREE) { dnum = 6; } else if (m->jnt_type[i] == mjJNT_BALL) { dnum = 3; } else { dnum = 1; } // inverse joint inertia: A = J*inv(M)*J' if (nv) { mju_zero(jac, dnum*nv); for (int j=0; j < dnum; j++) { jac[j*(nv+1) + id] = 1; } mj_solveM(m, d, tmp, jac, dnum); mju_mulMatMatT(A, jac, tmp, dnum, nv, dnum); } // average diagonal and assign if (dnum == 6) { m->dof_invweight0[id] = m->dof_invweight0[id+1] = m->dof_invweight0[id+2] = (A[0] + A[7] + A[14])/3; m->dof_invweight0[id+3] = m->dof_invweight0[id+4] = m->dof_invweight0[id+5] = (A[21] + A[28] + A[35])/3; } else if (dnum == 3) { m->dof_invweight0[id] = m->dof_invweight0[id+1] = m->dof_invweight0[id+2] = (A[0] + A[4] + A[8])/3; } else { m->dof_invweight0[id] = A[0]; } } } // compute flexedge_invweight0, tendon_invweight0, actuator_acc0 if (nv) { // compute flexedge_invweight0 for (int f=0; f < m->nflex; f++) { if (m->flex_interp[f]) { continue; } for (int i=m->flex_edgeadr[f]; i < m->flex_edgeadr[f]+m->flex_edgenum[f]; i++) { // bodies connected by edge int b1 = m->flex_vertbodyid[m->flex_vertadr[f] + m->flex_edge[2*i]]; int b2 = m->flex_vertbodyid[m->flex_vertadr[f] + m->flex_edge[2*i+1]]; // rigid edge: set to 0 if (m->flexedge_rigid[i]) { m->flexedge_invweight0[i] = 0; } // accelerate edges that connect simple bodies with no rotations else if (m->body_simple[b1] == 2 && m->body_simple[b2] == 2) { m->flexedge_invweight0[i] = (1/m->body_mass[b1] + 1/m->body_mass[b2])/2; } // handle general edge else { // make dense vector into tmp mju_zero(tmp, nv); int end = m->flexedge_J_rowadr[i] + m->flexedge_J_rownnz[i]; for (int j=m->flexedge_J_rowadr[i]; j < end; j++) { tmp[m->flexedge_J_colind[j]] = d->flexedge_J[j]; } // solve into tmp+nv mj_solveM(m, d, tmp+nv, tmp, 1); m->flexedge_invweight0[i] = mju_dot(tmp, tmp+nv, nv); } } } // compute tendon_invweight0 for (int i=0; i < m->ntendon; i++) { mju_sparse2dense(tmp, d->ten_J, 1, nv, m->ten_J_rownnz+i, m->ten_J_rowadr+i, m->ten_J_colind); // solve into tmp+nv mj_solveM(m, d, tmp+nv, tmp, 1); m->tendon_invweight0[i] = mju_dot(tmp, tmp+nv, nv); } // compute actuator_acc0 for (int i=0; i < m->nu; i++) { mju_sparse2dense(moment, d->actuator_moment, 1, nv, d->moment_rownnz + i, d->moment_rowadr + i, d->moment_colind); mj_solveM(m, d, tmp, moment, 1); m->actuator_acc0[i] = mju_norm(tmp, nv); } } else { mju_zero(m->tendon_invweight0, m->ntendon); mju_zero(m->actuator_acc0, m->nu); } // compute missing eq_data for body constraints for (int i=0; i < m->neq; i++) { // get ids int id1 = m->eq_obj1id[i]; int id2 = m->eq_obj2id[i]; // connect constraint if (m->eq_type[i] == mjEQ_CONNECT) { switch ((mjtObj) m->eq_objtype[i]) { case mjOBJ_BODY: // pos = anchor position in global frame mj_local2Global(d, pos, 0, m->eq_data+mjNEQDATA*i, 0, id1, 0); // data[3-5] = anchor position in body2 local frame mju_subFrom3(pos, d->xpos+3*id2); mju_mulMatTVec3(m->eq_data+mjNEQDATA*i+3, d->xmat+9*id2, pos); break; case mjOBJ_SITE: // site-based connect, eq_data is unused mju_zero(m->eq_data+mjNEQDATA*i, mjNEQDATA); break; default: mjERROR("invalid objtype in connect constraint %d", i); } } // weld constraint else if (m->eq_type[i] == mjEQ_WELD) { switch ((mjtObj) m->eq_objtype[i]) { case mjOBJ_BODY: { // skip if user has set any quaternion data if (!mju_isZero(m->eq_data + mjNEQDATA*i + 6, 4)) { // normalize quaternion just in case mju_normalize4(m->eq_data+mjNEQDATA*i+6); continue; } // anchor position is in body2 local frame mj_local2Global(d, pos, 0, m->eq_data+mjNEQDATA*i, 0, id2, 0); // data[3-5] = anchor position in body1 local frame mju_subFrom3(pos, d->xpos+3*id1); mju_mulMatTVec3(m->eq_data+mjNEQDATA*i+3, d->xmat+9*id1, pos); // data[6-9] = neg(xquat1)*xquat2 = "xquat2-xquat1" in body1 local frame mju_negQuat(quat, d->xquat+4*id1); mju_mulQuat(m->eq_data+mjNEQDATA*i+6, quat, d->xquat+4*id2); break; } case mjOBJ_SITE: { break; } default: mjERROR("invalid objtype in weld constraint %d", i); } } } // camera compos0, pos0, mat0 for (int i=0; i < m->ncam; i++) { // get body ids int id = m->cam_bodyid[i]; // camera body int id1 = m->cam_targetbodyid[i]; // target body // compute positional offsets mju_sub3(m->cam_pos0+3*i, d->cam_xpos+3*i, d->xpos+3*id); mju_sub3(m->cam_poscom0+3*i, d->cam_xpos+3*i, d->subtree_com+ (id1 >= 0 ? 3*id1 : 3*id)); // copy mat mju_copy9(m->cam_mat0+9*i, d->cam_xmat+9*i); } // light compos0, pos0, dir0 for (int i=0; i < m->nlight; i++) { // get body ids int id = m->light_bodyid[i]; // light body int id1 = m->light_targetbodyid[i]; // target body // compute positional offsets mju_sub3(m->light_pos0+3*i, d->light_xpos+3*i, d->xpos+3*id); mju_sub3(m->light_poscom0+3*i, d->light_xpos+3*i, d->subtree_com + (id1 >= 0 ? 3*id1 : 3*id)); // copy dir mju_copy3(m->light_dir0+3*i, d->light_xdir+3*i); } // compute actuator damping from dampratio for (int i=0; i < m->nu; i++) { // get bias, gain parameters mjtNum* biasprm = m->actuator_biasprm + i*mjNBIAS; mjtNum* gainprm = m->actuator_gainprm + i*mjNGAIN; // not a position-like actuator: skip if (gainprm[0] != -biasprm[1]) { continue; } // damping is 0 or negative (interpreted as regular "kv"): skip if (biasprm[2] <= 0) { continue; } // === interpret biasprm[2] > 0 as dampratio for position-like actuators // "reflected" inertia (inversely scaled by transmission squared) int rownnz = d->moment_rownnz[i]; int rowadr = d->moment_rowadr[i]; mjtNum* transmission = d->actuator_moment + rowadr; mjtNum mass = 0; for (int j=0; j < rownnz; j++) { mjtNum trn = mju_abs(transmission[j]); mjtNum trn2 = trn*trn; // transmission squared if (trn2 > mjMINVAL) { int dof = d->moment_colind[rowadr + j]; mass += m->dof_M0[dof] / trn2; } } // damping = dampratio * 2 * sqrt(kp * mass) mjtNum damping = biasprm[2] * 2 * mju_sqrt(gainprm[0] * mass); // set biasprm[2] to negative damping biasprm[2] = -damping; } mj_freeStack(d); } // accumulate bounding box static void updateBox(mjtNum* xmin, mjtNum* xmax, mjtNum* pos, mjtNum radius) { for (int i=0; i < 3; i++) { xmin[i] = mjMIN(xmin[i], pos[i] - radius); xmax[i] = mjMAX(xmax[i], pos[i] + radius); } } // compute stat; assume computations already executed in qpos0 static void setStat(mjModel* m, mjData* d) { mjtNum xmin[3] = {1E+10, 1E+10, 1E+10}; mjtNum xmax[3] = {-1E+10, -1E+10, -1E+10}; mjtNum rbound; mj_markStack(d); // approximate length associated with each body mjtNum* body = mjSTACKALLOC(d, m->nbody, mjtNum); // compute bounding box of bodies, joint centers, geoms and sites for (int i=1; i < m->nbody; i++) { updateBox(xmin, xmax, d->xpos+3*i, 0); updateBox(xmin, xmax, d->xipos+3*i, 0); } for (int i=0; i < m->njnt; i++) { updateBox(xmin, xmax, d->xanchor+3*i, 0); } for (int i=0; i < m->nsite; i++) { updateBox(xmin, xmax, d->site_xpos+3*i, 0); } for (int i=0; i < m->ngeom; i++) { // set rbound: regular geom rbound, or 0.1 of plane or hfield max size rbound = 0; if (m->geom_rbound[i] > 0) { rbound = m->geom_rbound[i]; } else if (m->geom_type[i] == mjGEOM_PLANE) { // finite in at least one direction if (m->geom_size[3*i] || m->geom_size[3*i+1]) { rbound = mjMAX(m->geom_size[3*i], m->geom_size[3*i+1]) * 0.1; } // infinite in both directions else { rbound = 0.01; } } else if (m->geom_type[i] == mjGEOM_HFIELD) { int j = m->geom_dataid[i]; rbound = mjMAX(m->hfield_size[4*j], mjMAX(m->hfield_size[4*j+1], mjMAX(m->hfield_size[4*j+2], m->hfield_size[4*j+3]))) * 0.1; } updateBox(xmin, xmax, d->geom_xpos+3*i, rbound); } // compute center mju_add3(m->stat.center, xmin, xmax); mju_scl3(m->stat.center, m->stat.center, 0.5); // compute bounding box size if (xmax[0] > xmin[0]) m->stat.extent = mju_max(1E-5, mju_max(xmax[0]-xmin[0], mju_max(xmax[1]-xmin[1], xmax[2]-xmin[2]))); // set body size to max com-joint distance mju_zero(body, m->nbody); for (int i=0; i < m->njnt; i++) { // handle this body int id = m->jnt_bodyid[i]; body[id] = mju_max(body[id], mju_dist3(d->xipos+3*id, d->xanchor+3*i)); // handle parent body id = m->body_parentid[id]; body[id] = mju_max(body[id], mju_dist3(d->xipos+3*id, d->xanchor+3*i)); } body[0] = 0; // set body size to max of old value, and geom rbound + com-geom dist for (int i=1; i < m->nbody; i++) { for (int id=m->body_geomadr[i]; id < m->body_geomadr[i]+m->body_geomnum[i]; id++) { if (m->geom_rbound[id] > 0) { body[i] = mju_max(body[i], m->geom_rbound[id] + mju_dist3(d->xipos+3*i, d->geom_xpos+3*id)); } } } // adjust body size for flex edges involving body for (int f=0; f < m->nflex; f++) { if (m->flex_interp[f]) { for (int v1=m->flex_nodeadr[f]; v1 < m->flex_nodeadr[f]+m->flex_nodenum[f]; v1++) { for (int v2=m->flex_nodeadr[f]; v2 < m->flex_nodeadr[f]+m->flex_nodenum[f]; v2++) { mjtNum edge = mju_dist3(d->xpos+3*m->flex_nodebodyid[v1], d->xpos+3*m->flex_nodebodyid[v2]); body[m->flex_nodebodyid[v1]] = mju_max(body[m->flex_nodebodyid[v1]], edge); } } continue; } for (int e=m->flex_edgeadr[f]; e < m->flex_edgeadr[f]+m->flex_edgenum[f]; e++) { int b1 = m->flex_vertbodyid[m->flex_vertadr[f]+m->flex_edge[2*e]]; int b2 = m->flex_vertbodyid[m->flex_vertadr[f]+m->flex_edge[2*e+1]]; body[b1] = mju_max(body[b1], m->flexedge_length0[e]); body[b2] = mju_max(body[b2], m->flexedge_length0[e]); } } // compute meansize, make sure all sizes are above min if (m->nbody > 1) { m->stat.meansize = 0; for (int i=1; i < m->nbody; i++) { body[i] = mju_max(body[i], 1E-5); m->stat.meansize += body[i]/(m->nbody-1); } } // inherit dof length from parent body for (int i=0; i < m->nv; i++) { // default to linear dof, already has length units m->dof_length[i] = 1; // if rotational dof, inherit from body int jnt = m->dof_jntid[i]; mjtJoint type = m->jnt_type[jnt]; int offset = i - m->jnt_dofadr[jnt]; if (type == mjJNT_BALL || type == mjJNT_HINGE || (type == mjJNT_FREE && offset >= 3)) { m->dof_length[i] = body[m->dof_bodyid[i]]; } } // fix extent if too small compared to meanbody m->stat.extent = mju_max(m->stat.extent, 2 * m->stat.meansize); // compute meanmass if (m->nbody > 1) { m->stat.meanmass = 0; for (int i=1; i < m->nbody; i++) { m->stat.meanmass += m->body_mass[i]; } m->stat.meanmass /= (m->nbody-1); } // compute meaninertia if (m->nv) { m->stat.meaninertia = 0; for (int i=0; i < m->nv; i++) { m->stat.meaninertia += d->M[m->M_rowadr[i] + m->M_rownnz[i] - 1]; } m->stat.meaninertia /= m->nv; } mj_freeStack(d); } // set quantities that depend qpos_spring static void setSpring(mjModel* m, mjData* d) { // run computations in qpos_spring mju_copy(d->qpos, m->qpos_spring, m->nq); mj_kinematics(m, d); mj_comPos(m, d); mj_tendon(m, d); mj_transmission(m, d); // copy if model spring length is -1 for (int i=0; i < m->ntendon; i++) { if (m->tendon_lengthspring[2*i] == -1 && m->tendon_lengthspring[2*i+1] == -1) { // explicit springlength unused, set equal to ten_length m->tendon_lengthspring[2*i] = m->tendon_lengthspring[2*i+1] = d->ten_length[i]; } } } // entry point: set all remaining constant fields of mjModel, except for lengthrange void mj_setConst(mjModel* m, mjData* d) { // set fixed quantities setFixed(m, d); // set quantities that depend on qpos0 set0(m, d); // compute statistics setStat(m, d); // set quantities that depend qpos_spring setSpring(m, d); } //----------------------------- actuator length range computation ---------------------------------- // evaluate actuator length, advance special dynamics static mjtNum evalAct(const mjModel* m, mjData* d, int index, int side, const mjLROpt* opt) { int nv = m->nv; // reduce velocity mju_scl(d->qvel, d->qvel, mju_exp(-m->opt.timestep/mjMAX(0.01, opt->timeconst)), nv); // step1: compute inertia and actuator moments mj_step1(m, d); // dense actuator_moment row mj_markStack(d); mjtNum* moment = mjSTACKALLOC(d, nv, mjtNum); mju_sparse2dense(moment, d->actuator_moment, 1, nv, d->moment_rownnz + index, d->moment_rowadr + index, d->moment_colind); // set force to generate desired acceleration mj_solveM(m, d, d->qfrc_applied, moment, 1); mjtNum nrm = mju_norm(d->qfrc_applied, nv); mju_scl(d->qfrc_applied, moment, (2*side-1)*opt->accel/mjMAX(mjMINVAL, nrm), nv); // impose maxforce nrm = mju_norm(d->qfrc_applied, nv); if (opt->maxforce > 0 && nrm > opt->maxforce) { mju_scl(d->qfrc_applied, d->qfrc_applied, opt->maxforce/mjMAX(mjMINVAL, nrm), nv); } // step2: apply force mj_step2(m, d); mj_freeStack(d); // return actuator length return d->actuator_length[index]; } // Set length range for specified actuator, return 1 if ok, 0 if error. int mj_setLengthRange(mjModel* m, mjData* d, int index, const mjLROpt* opt, char* error, int error_sz) { // check index if (index < 0 || index >= m->nu) { mjERROR("invalid actuator index"); } // skip depending on mode and type int ismuscle = (m->actuator_gaintype[index] == mjGAIN_MUSCLE || m->actuator_biastype[index] == mjBIAS_MUSCLE); int isuser = (m->actuator_gaintype[index] == mjGAIN_USER || m->actuator_biastype[index] == mjBIAS_USER); if ((opt->mode == mjLRMODE_NONE) || (opt->mode == mjLRMODE_MUSCLE && !ismuscle) || (opt->mode == mjLRMODE_MUSCLEUSER && !ismuscle && !isuser)) { return 1; } // use existing length range if available if (opt->useexisting && (m->actuator_lengthrange[2*index] < m->actuator_lengthrange[2*index+1])) { return 1; } // get transmission id int threadid = m->actuator_trnid[index]; // use joint and tendon limits if available if (opt->uselimit) { // joint or jointinparent if (m->actuator_trntype[index] == mjTRN_JOINT || m->actuator_trntype[index] == mjTRN_JOINTINPARENT) { // make sure joint is limited if (m->jnt_limited[threadid]) { // copy range m->actuator_lengthrange[2*index] = m->jnt_range[2*threadid]; m->actuator_lengthrange[2*index+1] = m->jnt_range[2*threadid+1]; // skip optimization return 1; } } // tendon if (m->actuator_trntype[index] == mjTRN_TENDON) { // make sure tendon is limited if (m->tendon_limited[threadid]) { // copy range m->actuator_lengthrange[2*index] = m->tendon_range[2*threadid]; m->actuator_lengthrange[2*index+1] = m->tendon_range[2*threadid+1]; // skip optimization return 1; } } } // optimize in both directions mjtNum lmin[2] = {0, 0}, lmax[2] = {0, 0}; int side; for (side=0; side < 2; side++) { // init at qpos0 mj_resetData(m, d); // simulate int updated = 0; while (d->time < opt->inttotal) { // advance and get length mjtNum len = evalAct(m, d, index, side, opt); // reset: cannot proceed if (d->time == 0) { snprintf(error, error_sz, "Unstable lengthrange simulation in actuator %d", index); return 0; } // update limits if (d->time > opt->inttotal-opt->interval) { if (len < lmin[side] || !updated) { lmin[side] = len; } if (len > lmax[side] || !updated) { lmax[side] = len; } updated = 1; } } // assign m->actuator_lengthrange[2*index+side] = (side == 0 ? lmin[side] : lmax[side]); } // check range mjtNum dif = m->actuator_lengthrange[2*index+1] - m->actuator_lengthrange[2*index]; if (dif <= 0) { snprintf(error, error_sz, "Invalid lengthrange (%g, %g) in actuator %d", m->actuator_lengthrange[2*index], m->actuator_lengthrange[2*index+1], index); return 0; } // check convergence, side 0 if (lmax[0]-lmin[0] > opt->tolrange*dif) { snprintf(error, error_sz, "Lengthrange computation did not converge in actuator %d:\n" " eval (%g, %g)\n range (%g, %g)", index, lmin[0], lmax[0], m->actuator_lengthrange[2*index], m->actuator_lengthrange[2*index+1]); return 0; } // check convergence, side 1 if (lmax[1]-lmin[1] > opt->tolrange*dif) { snprintf(error, error_sz, "Lengthrange computation did not converge in actuator %d:\n" " eval (%g, %g)\n range (%g, %g)", index, lmin[1], lmax[1], m->actuator_lengthrange[2*index], m->actuator_lengthrange[2*index+1]); return 0; } return 1; }