Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
46f925a79This file is pinned to the selected release. Download the original to open it in your design tool.
// 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 <stdio.h>
#include <string.h>
#include <mujoco/mjdata.h>
#include <mujoco/mjmacro.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjsan.h> // IWYU pragma: keep
#include "engine/engine_core_smooth.h"
#include "engine/engine_core_util.h"
#include "engine/engine_derivative.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_solve.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;
}
// tolerance for frame comparison, must match compiler's kFrameEps
static const mjtNum kSameFrameEps = 1e-6;
// return 1 if all 3 elements are near zero
static int isNullVec3(const mjtNum v[3]) {
return mju_abs(v[0]) < kSameFrameEps &&
mju_abs(v[1]) < kSameFrameEps &&
mju_abs(v[2]) < kSameFrameEps;
}
// return 1 if quaternion is near identity (1,0,0,0), accounting for double-cover
static int isNullQuat(const mjtNum q[4]) {
int plus = mju_abs(q[0] - 1) < kSameFrameEps &&
mju_abs(q[1]) < kSameFrameEps &&
mju_abs(q[2]) < kSameFrameEps &&
mju_abs(q[3]) < kSameFrameEps;
int minus = mju_abs(q[0] + 1) < kSameFrameEps &&
mju_abs(q[1]) < kSameFrameEps &&
mju_abs(q[2]) < kSameFrameEps &&
mju_abs(q[3]) < kSameFrameEps;
return plus || minus;
}
// return 1 if two quaternions are near equal, accounting for double-cover
static int isSameQuat(const mjtNum q1[4], const mjtNum q2[4]) {
int plus = mju_abs(q1[0] - q2[0]) < kSameFrameEps &&
mju_abs(q1[1] - q2[1]) < kSameFrameEps &&
mju_abs(q1[2] - q2[2]) < kSameFrameEps &&
mju_abs(q1[3] - q2[3]) < kSameFrameEps;
int minus = mju_abs(q1[0] + q2[0]) < kSameFrameEps &&
mju_abs(q1[1] + q2[1]) < kSameFrameEps &&
mju_abs(q1[2] + q2[2]) < kSameFrameEps &&
mju_abs(q1[3] + q2[3]) < kSameFrameEps;
return plus || minus;
}
// return 1 if two 3-vectors are near equal
static int isSameVec3(const mjtNum v1[3], const mjtNum v2[3]) {
return mju_abs(v1[0] - v2[0]) < kSameFrameEps &&
mju_abs(v1[1] - v2[1]) < kSameFrameEps &&
mju_abs(v1[2] - v2[2]) < kSameFrameEps;
}
// recompute body_sameframe, geom_sameframe, site_sameframe from model geometry
static void setSameframe(mjModel* m) {
// body_sameframe: compare body inertial frame to body frame
for (int i=1; i < m->nbody; i++) {
mjtNum* ipos = m->body_ipos+3*i;
mjtNum* iquat = m->body_iquat+4*i;
if (isNullVec3(ipos) && isNullQuat(iquat)) {
m->body_sameframe[i] = mjSAMEFRAME_BODY;
} else if (isNullQuat(iquat)) {
m->body_sameframe[i] = mjSAMEFRAME_BODYROT;
} else {
m->body_sameframe[i] = mjSAMEFRAME_NONE;
}
}
// geom_sameframe: compare geom frame to body and inertial frames
for (int i=0; i < m->ngeom; i++) {
int b = m->geom_bodyid[i];
mjtNum* gpos = m->geom_pos+3*i;
mjtNum* gquat = m->geom_quat+4*i;
mjtNum* ipos = m->body_ipos+3*b;
mjtNum* iquat = m->body_iquat+4*b;
if (isNullVec3(gpos) && isNullQuat(gquat)) {
m->geom_sameframe[i] = mjSAMEFRAME_BODY;
} else if (isNullQuat(gquat)) {
m->geom_sameframe[i] = mjSAMEFRAME_BODYROT;
} else if (isSameVec3(gpos, ipos) && isSameQuat(gquat, iquat)) {
m->geom_sameframe[i] = mjSAMEFRAME_INERTIA;
} else if (isSameQuat(gquat, iquat)) {
m->geom_sameframe[i] = mjSAMEFRAME_INERTIAROT;
} else {
m->geom_sameframe[i] = mjSAMEFRAME_NONE;
}
}
// site_sameframe: compare site frame to body and inertial frames
for (int i=0; i < m->nsite; i++) {
int b = m->site_bodyid[i];
mjtNum* spos = m->site_pos+3*i;
mjtNum* squat = m->site_quat+4*i;
mjtNum* ipos = m->body_ipos+3*b;
mjtNum* iquat = m->body_iquat+4*b;
if (isNullVec3(spos) && isNullQuat(squat)) {
m->site_sameframe[i] = mjSAMEFRAME_BODY;
} else if (isNullQuat(squat)) {
m->site_sameframe[i] = mjSAMEFRAME_BODYROT;
} else if (isSameVec3(spos, ipos) && isSameQuat(squat, iquat)) {
m->site_sameframe[i] = mjSAMEFRAME_INERTIA;
} else if (isSameQuat(squat, iquat)) {
m->site_sameframe[i] = mjSAMEFRAME_INERTIAROT;
} else {
m->site_sameframe[i] = mjSAMEFRAME_NONE;
}
}
}
// 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;
m->flg_gravcomp = (ngravcomp > 0);
// compute flg_surfacevel: whether any geom has nonzero surfacevel
mjtBool flg_surfacevel = 0;
for (int i=0; i < m->ngeom; i++) {
const mjtNum* sv = m->geom_surfacevel + 6*i;
if (sv[0] || sv[1] || sv[2] || sv[3] || sv[4] || sv[5]) {
flg_surfacevel = 1;
break;
}
}
m->flg_surfacevel = flg_surfacevel;
// compute flg_adhesion: whether any geom or pair has nonzero adhesion
mjtBool flg_adhesion = 0;
for (int i=0; i < m->ngeom; i++) {
if (m->geom_adhesion[i]) {
flg_adhesion = 1;
break;
}
}
for (int i=0; i < m->npair && !flg_adhesion; i++) {
if (m->pair_adhesion[i]) {
flg_adhesion = 1;
}
}
m->flg_adhesion = flg_adhesion;
// 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->nactuator; 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->nactuator; i++) {
int bodyid = -1, bodyid2 = -1;
int tid = m->actuator_trnid[2*i];
int tid2 = m->actuator_trnid[2*i+1];
switch ((mjtTrn)m->actuator_trntype[i]) {
case mjTRN_JOINT:
case mjTRN_JOINTINPARENT:
bodyid = m->jnt_bodyid[tid];
break;
case mjTRN_SO3:
bodyid = tid2 >= 0 ? m->site_bodyid[tid] : m->jnt_bodyid[tid];
bodyid2 = tid2 >= 0 ? m->site_bodyid[tid2] : -1;
break;
case mjTRN_SITE:
case mjTRN_SLIDERCRANK:
bodyid = m->site_bodyid[tid];
bodyid2 = tid2 >= 0 ? m->site_bodyid[tid2] : -1;
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;
}
}
if (bodyid2 != -1) {
int treeid = m->body_treeid[bodyid2];
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 && !mj_tendonHasStiffness(m, i) && !mj_tendonHasDamping(m, i)) {
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->nout);
// compute body_invweight0
m->body_invweight0[0] = m->body_invweight0[1] = 0.0;
for (int i=1; i < m->nbody; i++) {
// bodies with no dofs (static and mocap): zero invweight0
if (m->body_dofnum[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, one per force output (moment row)
for (int i=0; i < m->nout; 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->nout);
}
// 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->nactuator; 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 (PID single-sources kp in biasprm[1])
int is_pid = m->actuator_gaintype[i] == mjGAIN_PID;
if (!is_pid && 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[m->actuator_outadr[i]];
int rowadr = d->moment_rowadr[m->actuator_outadr[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 kp = is_pid ? -biasprm[1] : gainprm[0];
mjtNum damping = biasprm[2] * 2 * mju_sqrt(kp * 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
// constant part of the implicit effective metric factor (currently dim-2 bending): sparse
// reverse-Cholesky of M + (h^2 + h*damping)*K_bend over
// the dofs of unpinned vertices of standard dim-2 flexes with bending. The matrix is constant
// (flat-rest bending stiffness, independent point masses), so it is computed here once and reused
// by the implicit-flex constraint solve every step. Bending couples full 3x3 blocks when vertex
// frames differ. Row order (body order, coordinate fastest) and the fill count must match the
// compiler's symbolic sizing (checked below).
static void setEfm0Factor(mjModel* m, mjData* d) {
int nbd = m->nefm0dof;
if (!nbd) {
return;
}
mj_markStack(d);
mjtNum h = m->opt.timestep;
// enumerate covered vertices: compact slot per unpinned vertex of qualifying flexes
// (filter matches the compiler's sizing and, for bending, flexStiff_active in
// engine_derivative.c: bending data exists only for dim-2 flexes)
int* bodyslot = mjSTACKALLOC(d, m->nbody, int);
for (int b=0; b < m->nbody; b++) {
bodyslot[b] = -1;
}
for (int f=0; f < m->nflex; f++) {
if (m->flex_interp[f] || m->flex_rigid[f] || m->flex_dim[f] != 2 ||
m->flex_bendingadr[f] < 0 || !mj_flexSimple(m, f)) {
continue;
}
for (int lv=0; lv < m->flex_vertnum[f]; lv++) {
int gv = m->flex_vertadr[f] + lv;
int wid = m->body_weldid[m->flex_vertbodyid[gv]];
if (m->body_dofnum[wid] == 3) {
bodyslot[wid] = 1;
}
}
}
int nfree = 0;
for (int b=0; b < m->nbody; b++) {
if (bodyslot[b] > 0) {
int s = nfree++;
bodyslot[b] = s;
int da = m->body_dofadr[b];
for (int k=0; k < 3; k++) {
m->efm0_dofid[3*s + k] = da + k;
}
}
}
if (3*nfree != nbd) {
mj_freeStack(d);
mjERROR("constant metric factor dof count mismatch: compiler sized %d, engine found %d",
nbd, 3*nfree);
}
// assemble the bending-only stiffness K = (h^2 + h*damping)*K_bend over all dofs with the
// shared stencil walker from engine_derivative. Only fixed-frame attachments participate;
// their orientations were computed by setSpring. As in the per-step metric, the h^2 and
// h*damping parts enter only when the spring and damper forces are enabled.
mjtNum s1 = mjDISABLED(mjDSBL_SPRING) ? 0 : h*h;
mjtNum s2 = mjDISABLED(mjDSBL_DAMPER) ? 0 : h;
int nv = m->nv;
int* K_rownnz = mjSTACKALLOC(d, nv, int);
int* K_rowadr = mjSTACKALLOC(d, nv, int);
int nK = mjd_flexStiff_assemble(m, d, K_rownnz, K_rowadr, NULL, NULL, s1, s2,
/*flg_bend=*/1, /*flg_stretch=*/0, NULL);
int* K_colind = mjSTACKALLOC(d, nK > 0 ? nK : 1, int);
mjtNum* K_val = mjSTACKALLOC(d, nK > 0 ? nK : 1, mjtNum);
mjd_flexStiff_assemble(m, d, K_rownnz, K_rowadr, K_colind, K_val, s1, s2, 1, 0, NULL);
// inverse map: dof address -> compact factor row (monotone: slots follow dof order)
int* dofrow = mjSTACKALLOC(d, nv, int);
for (int i=0; i < nv; i++) {
dofrow[i] = -1;
}
for (int r=0; r < nbd; r++) {
dofrow[m->efm0_dofid[r]] = r;
}
// Compact K_bend to covered rows, retaining full off-diagonal blocks Q_ij * R_i^T * R_j.
// Only same-body blocks are diagonal, even when several vertices share a weld parent.
// Keep structural zeros between bodies so the pattern is independent of their orientations.
// H = M + (h^2+h*d)*K_bend in compact dof indices: lower CSR (values) + upper CSR (pattern)
int nHl = 0, nHu = 0;
for (int r=0; r < nbd; r++) {
int dof = m->efm0_dofid[r];
for (int c=0; c < K_rownnz[dof]; c++) {
int rc = dofrow[K_colind[K_rowadr[dof] + c]];
if (rc < 0 || (rc != r && rc/3 == r/3)) continue;
if (rc < r) nHl++;
else if (rc > r) nHu++;
}
}
nHl += nbd; // diagonals: always present, also for rows without bending entries
int* Hl_rownnz = mjSTACKALLOC(d, nbd, int);
int* Hl_rowadr = mjSTACKALLOC(d, nbd, int);
int* Hl_colind = mjSTACKALLOC(d, nHl, int);
mjtNum* Hl_val = mjSTACKALLOC(d, nHl, mjtNum);
int* Hu_rownnz = mjSTACKALLOC(d, nbd, int);
int* Hu_rowadr = mjSTACKALLOC(d, nbd, int);
int* Hu_colind = mjSTACKALLOC(d, nHu > 0 ? nHu : 1, int);
int ladr = 0, uadr = 0;
for (int r=0; r < nbd; r++) {
int dof = m->efm0_dofid[r];
Hl_rowadr[r] = ladr;
Hu_rowadr[r] = uadr;
mjtNum diag = 0;
// B columns are dof-ascending, so filtered lower/upper columns stay ascending
for (int c=0; c < K_rownnz[dof]; c++) {
int adr = K_rowadr[dof] + c;
int rc = dofrow[K_colind[adr]];
if (rc < 0 || (rc != r && rc/3 == r/3)) continue;
if (rc < r) {
Hl_colind[ladr] = rc;
Hl_val[ladr++] = K_val[adr];
} else if (rc > r) {
Hu_colind[uadr++] = rc;
} else {
diag = K_val[adr];
}
}
// diagonal last: composite point mass + armature + bending diagonal
Hl_colind[ladr] = r;
Hl_val[ladr++] = m->dof_M0[dof] + diag;
Hl_rownnz[r] = ladr - Hl_rowadr[r];
Hu_rownnz[r] = uadr - Hu_rowadr[r];
}
// symbolic factorization: counting phase (from the upper-triangle pattern)
int* LT_rownnz = mjSTACKALLOC(d, nbd, int);
int* LT_rowadr = mjSTACKALLOC(d, nbd, int);
int nnz = mju_cholFactorSymbolic(NULL, m->efm0_L_rownnz, m->efm0_L_rowadr,
NULL, LT_rownnz, LT_rowadr, NULL,
Hu_rownnz, Hu_rowadr, Hu_colind, nbd, d);
if (nnz != m->nefm0L) {
mj_freeStack(d);
mjERROR("constant metric factor size mismatch: compiler sized %d, symbolic found %d",
(int)m->nefm0L, nnz);
}
// symbolic factorization: filling phase
int* LT_colind = mjSTACKALLOC(d, nnz, int);
int* LT_map = mjSTACKALLOC(d, nnz, int);
mju_cholFactorSymbolic(m->efm0_L_colind, m->efm0_L_rownnz, m->efm0_L_rowadr,
LT_colind, LT_rownnz, LT_rowadr, LT_map,
Hu_rownnz, Hu_rowadr, Hu_colind, nbd, d);
// numeric factorization
mjtNum* scratch = mjSTACKALLOC(d, nbd, mjtNum);
int rank = mju_cholFactorNumeric(m->efm0_L, nbd, mjMINVAL,
m->efm0_L_rownnz, m->efm0_L_rowadr, m->efm0_L_colind,
LT_rownnz, LT_rowadr, LT_colind, LT_map,
Hl_val, Hl_rownnz, Hl_rowadr, Hl_colind, scratch);
if (rank != nbd) {
mj_freeStack(d);
mjERROR("constant metric factor is rank-deficient (%d of %d)", rank, nbd);
}
mj_freeStack(d);
}
void mj_setConst(mjModel* m, mjData* d) {
// compute npolygonmax and nmeshdegmax
m->npolygonmax = 0;
for (int i=0; i < m->nmeshpoly; i++) {
m->npolygonmax = mjMAX(m->npolygonmax, m->mesh_polyvertnum[i]);
}
m->nmeshdegmax = 0;
for (int i=0; i < m->nmeshvert; i++) {
m->nmeshdegmax = mjMAX(m->nmeshdegmax, m->mesh_polymapnum[i]);
}
// recompute sameframe flags from current model geometry
setSameframe(m);
// error if simple body lost sameframe (user must set simple="false")
for (int i = 1; i < m->nbody; i++) {
if (m->body_simple[i] > 0 && m->body_sameframe[i] != mjSAMEFRAME_BODY) {
mjERROR("body %d is compiled as simple but sameframe no longer holds, "
"use body/simple='false'", i);
}
}
// 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);
// precompute the constant part of the implicit effective metric factor
setEfm0Factor(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;
int out = m->actuator_outadr[index];
// 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 + out,
d->moment_rowadr + out, 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[out];
}
// 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->nactuator) {
mjERROR("invalid actuator index");
}
int out = m->actuator_outadr[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*out] < m->actuator_lengthrange[2*out+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*out] = m->jnt_range[2*threadid];
m->actuator_lengthrange[2*out+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*out] = m->tendon_range[2*threadid];
m->actuator_lengthrange[2*out+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*out+1] - m->actuator_lengthrange[2*out];
if (dif <= 0) {
snprintf(error, error_sz,
"Invalid lengthrange (%g, %g) in actuator %d",
m->actuator_lengthrange[2*out],
m->actuator_lengthrange[2*out+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*out],
m->actuator_lengthrange[2*out+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*out],
m->actuator_lengthrange[2*out+1]);
return 0;
}
return 1;
}