Files
Mujoco_WASM/src/engine/engine_setconst.c
T
Alessio 2a3554c8a3 Integrate passive flex contact implicitly
Contact of a flex with `passive` collisions enabled was applied as an
explicit spring of fixed stiffness 1e4, which the timestep bounds: any
stiffness worth having oscillates faster than the step can resolve, so
the force was too soft to keep sheets apart and interpenetration was
routine.

Carry its curvature in the effective metric M + K instead, alongside the
flex's own stretch and bending stiffness. The contact block k*J^T*J is
appended to the per-vertex candidate list already assembled for the flex
stencils, so it costs additional entries in an existing matrix rather
than a new one, and the accompanying shift -h*K*v is what damps the
stiff modes. At a 2 ms timestep this holds roughly 50x the stiffness an
explicit force of the same step could.

With the timestep no longer setting the bound, the stiffness is chosen
as a natural frequency scaled by the participating vertex mass rather
than left at a fixed 1e4, so one value suits models of any scale.

Passive handling is scoped to contacts whose every dof is a flex vertex
carried by the metric: flex against flex, flex against itself, and flex
against static geometry, which contributes no dofs of its own. For those
the Hessian is assembled in full. Contact with a body that can move
would have that body's dofs dropped from it, and is left on the
constraint solver.

The feature now requires an integrator whose constraint solve runs in
that metric, and is rejected with an error otherwise.

Add model/flex/drape.xml as the example model, replacing sphere_passive,
whose contacts no longer demonstrated the feature.
2026-08-06 15:38:05 +01:00

1726 lines
55 KiB
C

// 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;
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_SO3:
bodyid = m->actuator_trnid[2*i+1] >= 0 ? m->site_bodyid[tid] : 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->nout);
// 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, 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, point masses), so the factor is computed here once and reused
// by the implicit-flex constraint solve every step. Bending couples only same-coordinate dofs,
// so the pattern is three interleaved copies of the vertex flap adjacency. Row order (flex
// order, vertex order, coordinate fastest) and the resulting 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* vslot = mjSTACKALLOC(d, m->nflexvert > 0 ? m->nflexvert : 1, int);
for (int i=0; i < m->nflexvert; i++) {
vslot[i] = -1;
}
int nfree = 0;
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) {
continue;
}
for (int lv=0; lv < m->flex_vertnum[f]; lv++) {
int gv = m->flex_vertadr[f] + lv;
if (m->body_dofnum[m->flex_vertbodyid[gv]] == 3) {
vslot[gv] = nfree;
nfree++;
}
}
}
if (3*nfree != nbd) {
mj_freeStack(d);
mjERROR("constant metric factor dof count mismatch: compiler sized %d, engine found %d",
nbd, 3*nfree);
}
// fill row -> dof address (row 3*slot + k, coordinate fastest)
for (int gv=0; gv < m->nflexvert; gv++) {
if (vslot[gv] >= 0) {
int da = m->body_dofadr[m->flex_vertbodyid[gv]];
for (int k=0; k < 3; k++) {
m->efm0_dofid[3*vslot[gv] + k] = da + k;
}
}
}
// assemble the bending-only stiffness K = (h^2 + h*damping)*K_bend over all dofs with the
// shared stencil walker from engine_derivative: bending values are configuration-independent
// and stretch/interp are gated off, so the call is valid at set-constants time (d is used
// for stack scratch only)
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, h*h, h,
/*flg_bend=*/1, /*flg_stretch=*/0, /*flg_contact=*/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, h*h, h, 1, 0, 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 B to covered rows, keeping same-coordinate entries only: bending blocks are
// isotropic (q * I3), so the off-coordinate entries of assemble's 3x3 block pattern are
// structurally zero and dropping them preserves the pattern the compiler sized.
// 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) % 3 != 0) continue; // uncovered or off-coordinate
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) % 3 != 0) 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: point mass + armature + bending diagonal
Hl_colind[ladr] = r;
Hl_val[ladr++] = m->body_mass[m->dof_bodyid[dof]] + m->dof_armature[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
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, d);
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;
}