3d77eb1ef4
- Adhesion actuators using contact normals as force transmission mechanism. - Related video: https://youtu.be/HdBue4MUZys Closes #229 PiperOrigin-RevId: 464389367 Change-Id: I9f69b3cd152d957e8f65870d208788463c036a6d
1944 lines
60 KiB
C
1944 lines
60 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_core_smooth.h"
|
|
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include <mujoco/mjdata.h>
|
|
#include <mujoco/mjmodel.h>
|
|
#include "engine/engine_callback.h"
|
|
#include "engine/engine_core_constraint.h"
|
|
#include "engine/engine_io.h"
|
|
#include "engine/engine_macro.h"
|
|
#include "engine/engine_support.h"
|
|
#include "engine/engine_util_blas.h"
|
|
#include "engine/engine_util_errmem.h"
|
|
#include "engine/engine_util_misc.h"
|
|
#include "engine/engine_util_sparse.h"
|
|
#include "engine/engine_util_spatial.h"
|
|
|
|
//--------------------------- position -------------------------------------------------------------
|
|
|
|
// forward kinematics
|
|
void mj_kinematics(const mjModel* m, mjData* d) {
|
|
mjtNum pos[3], quat[4], *bodypos, *bodyquat;
|
|
mjtNum qloc[4], qtmp[4], vec[3], vec1[3], xanchor[3], xaxis[3];
|
|
|
|
// set world position and orientation
|
|
mju_zero3(d->xpos);
|
|
mju_unit4(d->xquat);
|
|
mju_zero3(d->xipos);
|
|
mju_zero(d->xmat, 9);
|
|
mju_zero(d->ximat, 9);
|
|
d->xmat[0] = d->xmat[4] = d->xmat[8] = 1;
|
|
d->ximat[0] = d->ximat[4] = d->ximat[8] = 1;
|
|
|
|
// normalize all quaternions in qpos
|
|
mj_normalizeQuat(m, d->qpos);
|
|
|
|
// normalize mocap quaternions
|
|
for (int i=0; i<m->nmocap; i++) {
|
|
mju_normalize4(d->mocap_quat+4*i);
|
|
}
|
|
|
|
// compute global cartesian positions and orientations of all bodies
|
|
for (int i=1; i<m->nbody; i++) {
|
|
// free joint
|
|
if (m->body_jntnum[i]==1 && m->jnt_type[m->body_jntadr[i]]==mjJNT_FREE) {
|
|
// get addresses
|
|
int jid = m->body_jntadr[i];
|
|
int qadr = m->jnt_qposadr[jid];
|
|
|
|
// copy pos and quat from qpos
|
|
mju_copy3(pos, d->qpos+qadr);
|
|
mju_copy4(quat, d->qpos+qadr+3);
|
|
|
|
// set xanchor = 0, xaxis = (0,0,1)
|
|
mju_copy3(xanchor, pos);
|
|
xaxis[0] = xaxis[1] = 0;
|
|
xaxis[2] = 1;
|
|
|
|
// assign xanchor and xaxis
|
|
mju_copy3(d->xanchor+3*jid, xanchor);
|
|
mju_copy3(d->xaxis+3*jid, xaxis);
|
|
}
|
|
|
|
// regular or no joint
|
|
else {
|
|
int pid = m->body_parentid[i];
|
|
|
|
// get body pos and quat: from model or mocap
|
|
if (m->body_mocapid[i]>=0) {
|
|
bodypos = d->mocap_pos + 3*m->body_mocapid[i];
|
|
bodyquat = d->mocap_quat + 4*m->body_mocapid[i];
|
|
} else {
|
|
bodypos = m->body_pos+3*i;
|
|
bodyquat = m->body_quat+4*i;
|
|
}
|
|
|
|
// apply fixed translation and rotation relative to parent
|
|
mju_rotVecMat(vec, bodypos, d->xmat+9*pid);
|
|
mju_add3(pos, d->xpos+3*pid, vec);
|
|
mju_mulQuat(quat, d->xquat+4*pid, bodyquat);
|
|
|
|
// accumulate joints, compute pos and quat for this body
|
|
for (int j=0; j<m->body_jntnum[i]; j++) {
|
|
// get joint id, qpos address, joint type
|
|
int jid = m->body_jntadr[i] + j;
|
|
int qadr = m->jnt_qposadr[jid];
|
|
int jtype = m->jnt_type[jid];
|
|
|
|
// compute axis in global frame; ball jnt_axis is (0,0,1), set by compiler
|
|
mju_rotVecQuat(xaxis, m->jnt_axis+3*jid, quat);
|
|
|
|
// compute anchor in global frame
|
|
mju_rotVecQuat(xanchor, m->jnt_pos+3*jid, quat);
|
|
mju_addTo3(xanchor, pos);
|
|
|
|
// apply joint transformation
|
|
switch (jtype) {
|
|
case mjJNT_SLIDE:
|
|
mju_addToScl3(pos, xaxis, d->qpos[qadr] - m->qpos0[qadr]);
|
|
break;
|
|
|
|
case mjJNT_BALL:
|
|
case mjJNT_HINGE:
|
|
// compute local quaternion rotation (qloc)
|
|
if (jtype==mjJNT_BALL) {
|
|
mju_copy4(qloc, d->qpos+qadr);
|
|
} else {
|
|
mju_axisAngle2Quat(qloc, m->jnt_axis+3*jid, d->qpos[qadr] - m->qpos0[qadr]);
|
|
}
|
|
|
|
// apply rotation
|
|
mju_mulQuat(qtmp, quat, qloc);
|
|
mju_copy4(quat, qtmp);
|
|
|
|
// correct for off-center rotation
|
|
mju_sub3(vec, xanchor, pos);
|
|
mju_rotVecQuat(vec1, m->jnt_pos+3*jid, quat);
|
|
pos[0] += (vec[0] - vec1[0]);
|
|
pos[1] += (vec[1] - vec1[1]);
|
|
pos[2] += (vec[2] - vec1[2]);
|
|
break;
|
|
|
|
default:
|
|
mju_error_i("Unknown joint type %d", jtype); // SHOULD NOT OCCUR
|
|
}
|
|
|
|
// assign xanchor and xaxis
|
|
mju_copy3(d->xanchor+3*jid, xanchor);
|
|
mju_copy3(d->xaxis+3*jid, xaxis);
|
|
}
|
|
}
|
|
|
|
// assign xquat and xpos, construct xmat
|
|
mju_normalize4(quat);
|
|
mju_copy4(d->xquat+4*i, quat);
|
|
mju_copy3(d->xpos+3*i, pos);
|
|
mju_quat2Mat(d->xmat+9*i, quat);
|
|
}
|
|
|
|
// compute/copy Cartesian positions and orientations of body inertial frames
|
|
for (int i=1; i<m->nbody; i++) {
|
|
mj_local2Global(d, d->xipos+3*i, d->ximat+9*i,
|
|
m->body_ipos+3*i, m->body_iquat+4*i,
|
|
i, m->body_sameframe[i]);
|
|
}
|
|
|
|
// compute/copy Cartesian positions and orientations of geoms
|
|
for (int i=0; i<m->ngeom; i++) {
|
|
mj_local2Global(d, d->geom_xpos+3*i, d->geom_xmat+9*i,
|
|
m->geom_pos+3*i, m->geom_quat+4*i,
|
|
m->geom_bodyid[i], m->geom_sameframe[i]);
|
|
}
|
|
|
|
// compute/copy Cartesian positions and orientations of sites
|
|
for (int i=0; i<m->nsite; i++) {
|
|
mj_local2Global(d, d->site_xpos+3*i, d->site_xmat+9*i,
|
|
m->site_pos+3*i, m->site_quat+4*i,
|
|
m->site_bodyid[i], m->site_sameframe[i]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// map inertias and motion dofs to global frame centered at subtree-CoM
|
|
void mj_comPos(const mjModel* m, mjData* d) {
|
|
mjtNum offset[3], axis[3];
|
|
mjMARKSTACK;
|
|
mjtNum* mass_subtree = mj_stackAlloc(d, m->nbody);
|
|
|
|
// clear subtree
|
|
mju_zero(mass_subtree, m->nbody);
|
|
mju_zero(d->subtree_com, m->nbody*3);
|
|
|
|
// backwards pass over bodies: compute subtree_com and mass_subtree
|
|
for (int i=m->nbody-1; i>=0; i--) {
|
|
// add local info
|
|
mju_addToScl3(d->subtree_com+3*i, d->xipos+3*i, m->body_mass[i]);
|
|
mass_subtree[i] += m->body_mass[i];
|
|
|
|
// add to parent, except for world
|
|
if (i) {
|
|
int j = m->body_parentid[i];
|
|
mju_addTo3(d->subtree_com+3*j, d->subtree_com+3*i);
|
|
mass_subtree[j] += mass_subtree[i];
|
|
}
|
|
|
|
// compute local com
|
|
if (mass_subtree[i]<mjMINVAL) {
|
|
mju_copy3(d->subtree_com+3*i, d->xipos+3*i);
|
|
} else {
|
|
mju_scl3(d->subtree_com+3*i, d->subtree_com+3*i,
|
|
1.0/mjMAX(mjMINVAL, mass_subtree[i]));
|
|
}
|
|
}
|
|
|
|
// map inertias to frame centered at subtree_com
|
|
for (int i=1; i<m->nbody; i++) {
|
|
mju_sub3(offset, d->xipos+3*i, d->subtree_com+3*m->body_rootid[i]);
|
|
mju_inertCom(d->cinert+10*i, m->body_inertia+3*i, d->ximat+9*i,
|
|
offset, m->body_mass[i]);
|
|
}
|
|
|
|
// map motion dofs to global frame centered at subtree_com
|
|
for (int j=0; j<m->njnt; j++) {
|
|
// get dof address, body index
|
|
int da = 6*m->jnt_dofadr[j];
|
|
int bi = m->jnt_bodyid[j];
|
|
|
|
// compute com-anchor vector
|
|
mju_sub3(offset, d->subtree_com+3*m->body_rootid[bi], d->xanchor+3*j);
|
|
|
|
// create motion dof
|
|
int skip = 0;
|
|
switch (m->jnt_type[j]) {
|
|
case mjJNT_FREE:
|
|
// translation components: x, y, z in global frame
|
|
mju_zero(d->cdof+da, 18);
|
|
for (int i=0; i<3; i++) {
|
|
d->cdof[da+3+7*i] = 1;
|
|
}
|
|
|
|
// rotation components: same as ball
|
|
skip = 18;
|
|
|
|
case mjJNT_BALL:
|
|
for (int i=0; i<3; i++) {
|
|
// I_3 rotation in child frame (assume no subsequent rotations)
|
|
axis[0] = d->xmat[9*bi+i+0];
|
|
axis[1] = d->xmat[9*bi+i+3];
|
|
axis[2] = d->xmat[9*bi+i+6];
|
|
|
|
mju_dofCom(d->cdof+da+skip+6*i, axis, offset);
|
|
}
|
|
break;
|
|
|
|
case mjJNT_SLIDE:
|
|
mju_dofCom(d->cdof+da, d->xaxis+3*j, 0);
|
|
break;
|
|
|
|
case mjJNT_HINGE:
|
|
mju_dofCom(d->cdof+da, d->xaxis+3*j, offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mjFREESTACK;
|
|
}
|
|
|
|
|
|
|
|
// compute camera and light positions and orientations
|
|
void mj_camlight(const mjModel* m, mjData* d) {
|
|
mjtNum pos[3], matT[9];
|
|
|
|
// compute Cartesian positions and orientations of cameras
|
|
for (int i=0; i<m->ncam; i++) {
|
|
// default processing for fixed mode
|
|
mj_local2Global(d, d->cam_xpos+3*i, d->cam_xmat+9*i,
|
|
m->cam_pos+3*i, m->cam_quat+4*i, m->cam_bodyid[i], 0);
|
|
|
|
// get camera body id and target body id
|
|
int id = m->cam_bodyid[i];
|
|
int id1 = m->cam_targetbodyid[i];
|
|
|
|
// adjust for mode
|
|
switch (m->cam_mode[i]) {
|
|
case mjCAMLIGHT_TRACK:
|
|
case mjCAMLIGHT_TRACKCOM:
|
|
// fixed global orientation
|
|
mju_copy(d->cam_xmat+9*i, m->cam_mat0+9*i, 9);
|
|
|
|
// position: track camera body
|
|
if (m->cam_mode[i]==mjCAMLIGHT_TRACK) {
|
|
mju_add3(d->cam_xpos+3*i, d->xpos+3*id, m->cam_pos0+3*i);
|
|
}
|
|
|
|
// position: track subtree com
|
|
else {
|
|
mju_add3(d->cam_xpos+3*i, d->subtree_com+3*id, m->cam_poscom0+3*i);
|
|
}
|
|
break;
|
|
|
|
case mjCAMLIGHT_TARGETBODY:
|
|
case mjCAMLIGHT_TARGETBODYCOM:
|
|
// only if target body is specified
|
|
if (id1>=0) {
|
|
// get position to look at
|
|
if (m->cam_mode[i]==mjCAMLIGHT_TARGETBODY) {
|
|
mju_copy3(pos, d->xpos+3*id1);
|
|
} else {
|
|
mju_copy3(pos, d->subtree_com+3*id1);
|
|
}
|
|
|
|
// zaxis = -desired camera direction, in global frame
|
|
mju_sub3(matT+6, d->cam_xpos+3*i, pos);
|
|
mju_normalize3(matT+6);
|
|
|
|
// xaxis: orthogonal to zaxis and to (0,0,1)
|
|
matT[3] = 0;
|
|
matT[4] = 0;
|
|
matT[5] = 1;
|
|
mju_cross(matT, matT+3, matT+6);
|
|
mju_normalize3(matT);
|
|
|
|
// yaxis: orthogonal to xaxis and zaxis
|
|
mju_cross(matT+3, matT+6, matT);
|
|
mju_normalize3(matT+3);
|
|
|
|
// set camera frame
|
|
mju_transpose(d->cam_xmat+9*i, matT, 3, 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
// compute Cartesian positions and directions of lights
|
|
for (int i=0; i<m->nlight; i++) {
|
|
// default processing for fixed mode
|
|
mj_local2Global(d, d->light_xpos+3*i, 0, m->light_pos+3*i, 0, m->light_bodyid[i], 0);
|
|
mju_rotVecQuat(d->light_xdir+3*i, m->light_dir+3*i, d->xquat+4*m->light_bodyid[i]);
|
|
|
|
// get light body id and target body id
|
|
int id = m->light_bodyid[i];
|
|
int id1 = m->light_targetbodyid[i];
|
|
|
|
// adjust for mode
|
|
switch (m->light_mode[i]) {
|
|
case mjCAMLIGHT_TRACK:
|
|
case mjCAMLIGHT_TRACKCOM:
|
|
// fixed global orientation
|
|
mju_copy3(d->light_xdir+3*i, m->light_dir0+3*i);
|
|
|
|
// position: track light body
|
|
if (m->light_mode[i]==mjCAMLIGHT_TRACK) {
|
|
mju_add3(d->light_xpos+3*i, d->xpos+3*id, m->light_pos0+3*i);
|
|
}
|
|
|
|
// position: track subtree com
|
|
else {
|
|
mju_add3(d->light_xpos+3*i, d->subtree_com+3*id, m->light_poscom0+3*i);
|
|
}
|
|
break;
|
|
|
|
case mjCAMLIGHT_TARGETBODY:
|
|
case mjCAMLIGHT_TARGETBODYCOM:
|
|
// only if target body is specified
|
|
if (id1>=0) {
|
|
// get position to look at
|
|
if (m->light_mode[i]==mjCAMLIGHT_TARGETBODY) {
|
|
mju_copy3(pos, d->xpos+3*id1);
|
|
} else {
|
|
mju_copy3(pos, d->subtree_com+3*id1);
|
|
}
|
|
|
|
// set dir
|
|
mju_sub3(d->light_xdir+3*i, pos, d->light_xpos+3*i);
|
|
}
|
|
}
|
|
|
|
// normalize dir
|
|
mju_normalize3(d->light_xdir+3*i);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// compute tendon lengths and moments
|
|
void mj_tendon(const mjModel* m, mjData* d) {
|
|
int issparse = mj_isSparse(m), nv = m->nv, nten = m->ntendon;
|
|
int id0, id1, idw, adr, wcnt, wbody[4], sideid;
|
|
int tp0, tp1, tpw, NV, *chain = NULL, *buf_ind = NULL;
|
|
int *rownnz = d->ten_J_rownnz, *rowadr = d->ten_J_rowadr, *colind = d->ten_J_colind;
|
|
mjtNum dif[3], divisor, wpnt[12], wlen;
|
|
mjtNum *L = d->ten_length, *J = d->ten_J;
|
|
mjtNum *jac1, *jac2, *jacdif, *tmp, *sparse_buf = NULL;
|
|
mjMARKSTACK;
|
|
|
|
if (!nten) {
|
|
return;
|
|
}
|
|
|
|
// allocate space
|
|
jac1 = mj_stackAlloc(d, 3*nv);
|
|
jac2 = mj_stackAlloc(d, 3*nv);
|
|
jacdif = mj_stackAlloc(d, 3*nv);
|
|
tmp = mj_stackAlloc(d, nv);
|
|
if (issparse) {
|
|
chain = (int*)mj_stackAlloc(d, nv);
|
|
buf_ind = (int*)mj_stackAlloc(d, nv);
|
|
sparse_buf = mj_stackAlloc(d, nv);
|
|
}
|
|
|
|
// clear results
|
|
mju_zero(L, nten);
|
|
wcnt = 0;
|
|
|
|
// clear Jacobian: sparse or dense
|
|
if (issparse) {
|
|
memset(rownnz, 0, nten*sizeof(int));
|
|
} else {
|
|
mju_zero(J, nten*nv);
|
|
}
|
|
|
|
// loop over tendons
|
|
for (int i=0; i<nten; i++) {
|
|
// initialize tendon path
|
|
adr = m->tendon_adr[i];
|
|
d->ten_wrapadr[i] = wcnt;
|
|
d->ten_wrapnum[i] = 0;
|
|
|
|
// sparse Jacobian row init
|
|
if (issparse) {
|
|
rowadr[i] = (i>0 ? rowadr[i-1] + rownnz[i-1] : 0);
|
|
}
|
|
|
|
// process joint tendon
|
|
if (m->wrap_type[adr]==mjWRAP_JOINT) {
|
|
// process all defined joints
|
|
for (int j=0; j<m->tendon_num[i]; j++) {
|
|
// get joint id
|
|
int k = m->wrap_objid[adr+j];
|
|
|
|
// add to length
|
|
L[i] += m->wrap_prm[adr+j] * d->qpos[m->jnt_qposadr[k]];
|
|
|
|
// add to moment
|
|
if (issparse) {
|
|
J[rowadr[i] + rownnz[i]] = m->wrap_prm[adr+j];
|
|
colind[rowadr[i] + rownnz[i]] = m->jnt_dofadr[k];
|
|
rownnz[i]++;
|
|
}
|
|
|
|
// add to moment: dense
|
|
else {
|
|
J[i*nv + m->jnt_dofadr[k]] = m->wrap_prm[adr+j];
|
|
}
|
|
}
|
|
|
|
// sort on colind if sparse: custom insertion sort
|
|
if (issparse) {
|
|
int x, *list = colind+rowadr[i];
|
|
mjtNum y, *listy = J+rowadr[i];
|
|
|
|
for (int k=1; k<rownnz[i]; k++) {
|
|
x = list[k];
|
|
y = listy[k];
|
|
int j = k-1;
|
|
while (j>=0 && list[j]>x) {
|
|
list[j+1] = list[j];
|
|
listy[j+1] = listy[j];
|
|
j--;
|
|
}
|
|
list[j+1] = x;
|
|
listy[j+1] = y;
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// process spatial tendon
|
|
divisor = 1;
|
|
int j = 0;
|
|
while (j<m->tendon_num[i]-1) {
|
|
// get 1st and 2nd object
|
|
tp0 = m->wrap_type[adr+j];
|
|
id0 = m->wrap_objid[adr+j];
|
|
tp1 = m->wrap_type[adr+j+1];
|
|
id1 = m->wrap_objid[adr+j+1];
|
|
|
|
// pulley
|
|
if (tp0==mjWRAP_PULLEY || tp1==mjWRAP_PULLEY) {
|
|
// get divisor, insert obj=-2
|
|
if (tp0==mjWRAP_PULLEY) {
|
|
divisor = m->wrap_prm[adr+j];
|
|
mju_zero3(d->wrap_xpos+wcnt*3);
|
|
d->wrap_obj[wcnt] = -2;
|
|
d->ten_wrapnum[i]++;
|
|
wcnt++;
|
|
}
|
|
|
|
// move to next
|
|
j++;
|
|
continue;
|
|
}
|
|
|
|
// init sequence; assume it starts with site
|
|
wlen = -1;
|
|
mju_copy3(wpnt, d->site_xpos+3*id0);
|
|
wbody[0] = m->site_bodyid[id0];
|
|
|
|
// second object is geom: process site-geom-site
|
|
if (tp1==mjWRAP_SPHERE || tp1==mjWRAP_CYLINDER) {
|
|
// reassign, get 2nd site info
|
|
tpw = tp1;
|
|
idw = id1;
|
|
tp1 = m->wrap_type[adr+j+2];
|
|
id1 = m->wrap_objid[adr+j+2];
|
|
|
|
// do wrapping, possibly get 2 extra points (wlen>=0)
|
|
sideid = mju_round(m->wrap_prm[adr+j+1]);
|
|
if (sideid<-1 || sideid>=m->nsite) {
|
|
mju_error_i("Invalid sideid %d in wrap_prm", sideid); // SHOULD NOT OCCUR
|
|
}
|
|
|
|
wlen = mju_wrap(wpnt+3, d->site_xpos+3*id0, d->site_xpos+3*id1,
|
|
d->geom_xpos+3*idw, d->geom_xmat+9*idw, m->geom_size+3*idw, tpw,
|
|
(sideid>=0 ? d->site_xpos+3*sideid : 0));
|
|
} else {
|
|
tpw = mjWRAP_NONE;
|
|
}
|
|
|
|
// complete sequence, accumulate lengths
|
|
if (wlen<0) {
|
|
mju_copy3(wpnt+3, d->site_xpos+3*id1);
|
|
wbody[1] = m->site_bodyid[id1];
|
|
L[i] += mju_dist3(wpnt, wpnt+3)/divisor;
|
|
} else {
|
|
mju_copy3(wpnt+9, d->site_xpos+3*id1);
|
|
wbody[1] = wbody[2] = m->geom_bodyid[idw];
|
|
wbody[3] = m->site_bodyid[id1];
|
|
L[i] += (mju_dist3(wpnt, wpnt+3) + wlen + mju_dist3(wpnt+6, wpnt+9))/divisor;
|
|
}
|
|
|
|
// accumulate moments if consequtive points are in different bodies
|
|
for (int k=0; k<(wlen<0 ? 1 : 3); k++) {
|
|
if (wbody[k]!=wbody[k+1]) {
|
|
// get 3D position difference, normalize
|
|
mju_sub3(dif, wpnt+3*k+3, wpnt+3*k);
|
|
mju_normalize3(dif);
|
|
|
|
// sparse
|
|
if (issparse) {
|
|
// get endpoint Jacobians, subtract
|
|
NV = mj_jacDifPair(m, d, chain,
|
|
wbody[k], wbody[k+1], wpnt+3*k, wpnt+3*k+3,
|
|
jac1, jac2, jacdif, NULL, NULL, NULL);
|
|
|
|
// no dofs: skip
|
|
if (!NV) {
|
|
continue;
|
|
}
|
|
|
|
// apply chain rule to compute tendon Jacobian
|
|
mju_mulMatTVec(tmp, jacdif, dif, 3, NV);
|
|
|
|
// add to existing
|
|
rownnz[i] = mju_combineSparse(J+rowadr[i], tmp, nv, 1, 1/divisor,
|
|
rownnz[i], NV, colind+rowadr[i], chain,
|
|
sparse_buf, buf_ind);
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
// get endpoint Jacobians, subtract
|
|
mj_jac(m, d, jac1, 0, wpnt+3*k, wbody[k]);
|
|
mj_jac(m, d, jac2, 0, wpnt+3*k+3, wbody[k+1]);
|
|
mju_sub(jacdif, jac2, jac1, 3*nv);
|
|
|
|
// apply chain rule to compute tendon Jacobian
|
|
mju_mulMatTVec(tmp, jacdif, dif, 3, nv);
|
|
|
|
// add to existing
|
|
mju_addToScl(J + i*nv, tmp, 1/divisor, nv);
|
|
}
|
|
}
|
|
}
|
|
|
|
// assign to wrap
|
|
mju_copy(d->wrap_xpos+wcnt*3, wpnt, (wlen<0 ? 3:9));
|
|
d->wrap_obj[wcnt] = -1;
|
|
if (wlen>=0) {
|
|
d->wrap_obj[wcnt+1] = d->wrap_obj[wcnt+2] = idw;
|
|
}
|
|
d->ten_wrapnum[i] += (wlen<0 ? 1:3);
|
|
wcnt += (wlen<0 ? 1:3);
|
|
|
|
// advance
|
|
j += (tpw!=mjWRAP_NONE ? 2 : 1);
|
|
|
|
// assign last site before pulley or tendon end
|
|
if (j==m->tendon_num[i]-1 || m->wrap_type[adr+j+1]==mjWRAP_PULLEY) {
|
|
mju_copy3(d->wrap_xpos+wcnt*3, d->site_xpos+3*id1);
|
|
d->wrap_obj[wcnt] = -1;
|
|
d->ten_wrapnum[i]++;
|
|
wcnt++;
|
|
}
|
|
}
|
|
}
|
|
|
|
mjFREESTACK;
|
|
}
|
|
|
|
|
|
|
|
// compute actuator/transmission lengths and moments
|
|
void mj_transmission(const mjModel* m, mjData* d) {
|
|
int id, idslider, ok, nv = m->nv, nu = m->nu;
|
|
mjtNum det, sdet, av, rod, axis[3], vec[3], dlda[3], dldv[3], quat[4];
|
|
mjtNum wrench[6], gearAxis[3];
|
|
mjtNum *jac, *jacA, *jacS;
|
|
mjtNum *length = d->actuator_length, *moment = d->actuator_moment, *gear;
|
|
mjMARKSTACK;
|
|
|
|
if (!nu) {
|
|
return;
|
|
}
|
|
|
|
// allocate space, clear moments
|
|
jac = mj_stackAlloc(d, 3*nv);
|
|
jacA = mj_stackAlloc(d, 3*nv);
|
|
jacS = mj_stackAlloc(d, 3*nv);
|
|
mju_zero(moment, nu*nv);
|
|
|
|
// compute lengths and moments
|
|
for (int i=0; i<nu; i++) {
|
|
// extract info
|
|
id = m->actuator_trnid[2*i];
|
|
idslider = m->actuator_trnid[2*i+1]; // for slider-crank only
|
|
gear = m->actuator_gear+6*i;
|
|
|
|
// process according to transmission type
|
|
switch (m->actuator_trntype[i]) {
|
|
case mjTRN_JOINT: // joint
|
|
case mjTRN_JOINTINPARENT: // joint, force in parent frame
|
|
// slide and hinge joint: scalar gear
|
|
if (m->jnt_type[id]==mjJNT_SLIDE || m->jnt_type[id]==mjJNT_HINGE) {
|
|
length[i] = d->qpos[m->jnt_qposadr[id]]*gear[0];
|
|
moment[i*nv + m->jnt_dofadr[id]] = gear[0];
|
|
}
|
|
|
|
// ball joint: 3D wrench gear
|
|
else if (m->jnt_type[id]==mjJNT_BALL) {
|
|
// j: qpos start address
|
|
int j = m->jnt_qposadr[id];
|
|
|
|
// axis: expmap representation of quaternion
|
|
mju_quat2Vel(axis, d->qpos+j, 1);
|
|
|
|
// gearAxis: rotate to parent frame if necessary
|
|
if (m->actuator_trntype[i]==mjTRN_JOINT) {
|
|
mju_copy3(gearAxis, gear);
|
|
} else {
|
|
mju_negQuat(quat, d->qpos+j);
|
|
mju_rotVecQuat(gearAxis, gear, quat);
|
|
}
|
|
|
|
// length: axis*gearAxis
|
|
length[i] = mju_dot3(axis, gearAxis);
|
|
|
|
// j: dof start address
|
|
j = m->jnt_dofadr[id];
|
|
|
|
// moment: gearAxis
|
|
mju_copy3(moment+i*nv+j, gearAxis);
|
|
}
|
|
|
|
// free joint: 6D wrench gear
|
|
else {
|
|
// cannot compute meaningful length, set to 0
|
|
length[i] = 0;
|
|
|
|
// j: qpos start address
|
|
int j = m->jnt_qposadr[id];
|
|
|
|
// vec: translational components
|
|
mju_copy3(vec, d->qpos+j);
|
|
|
|
// axis: expmap representation of quaternion
|
|
mju_quat2Vel(axis, d->qpos+j+3, 1);
|
|
|
|
// gearAxis: rotate to world frame if necessary
|
|
if (m->actuator_trntype[i]==mjTRN_JOINT) {
|
|
mju_copy3(gearAxis, gear+3);
|
|
} else {
|
|
mju_negQuat(quat, d->qpos+j+3);
|
|
mju_rotVecQuat(gearAxis, gear+3, quat);
|
|
}
|
|
|
|
// j: dof start address
|
|
j = m->jnt_dofadr[id];
|
|
|
|
// moment: gear(tran), gearAxis
|
|
mju_copy3(moment+i*nv+j, gear);
|
|
mju_copy3(moment+i*nv+j+3, gearAxis);
|
|
}
|
|
break;
|
|
|
|
case mjTRN_SLIDERCRANK: // slider-crank
|
|
// get data
|
|
rod = m->actuator_cranklength[i];
|
|
axis[0] = d->site_xmat[9*idslider+2];
|
|
axis[1] = d->site_xmat[9*idslider+5];
|
|
axis[2] = d->site_xmat[9*idslider+8];
|
|
mju_sub3(vec, d->site_xpos+3*id, d->site_xpos+3*idslider);
|
|
|
|
// compute length and determinant
|
|
// length = a'*v - sqrt(det); det = (a'*v)^2 + r^2 - v'*v)
|
|
av = mju_dot3(vec, axis);
|
|
det = av*av + rod*rod - mju_dot3(vec, vec);
|
|
ok = 1;
|
|
if (det<=0) {
|
|
ok = 0;
|
|
sdet = 0;
|
|
length[i] = av;
|
|
} else {
|
|
sdet = mju_sqrt(det);
|
|
length[i] = av - sdet;
|
|
}
|
|
|
|
// compute derivatives of length w.r.t. vec and axis
|
|
if (ok) {
|
|
mju_scl3(dldv, axis, 1-av/sdet);
|
|
mju_scl3(dlda, vec, 1/sdet); // use dlda as temp
|
|
mju_addTo3(dldv, dlda);
|
|
|
|
mju_scl3(dlda, vec, 1-av/sdet);
|
|
} else {
|
|
mju_copy3(dlda, vec);
|
|
mju_copy3(dldv, axis);
|
|
}
|
|
|
|
// get Jacobians of axis(jacA) and vec(jac)
|
|
mj_jacPointAxis(m, d, jacS, jacA, d->site_xpos+3*idslider,
|
|
axis, m->site_bodyid[idslider]);
|
|
mj_jacSite(m, d, jac, 0, id);
|
|
mju_subFrom(jac, jacS, 3*nv);
|
|
|
|
// apply chain rule
|
|
for (int j=0; j<nv; j++) {
|
|
for (int k=0; k<3; k++) {
|
|
moment[i*nv+j] += dlda[k]*jacA[k*nv+j] + dldv[k]*jac[k*nv+j];
|
|
}
|
|
}
|
|
|
|
// scale by gear ratio
|
|
length[i] *= gear[0];
|
|
for (int j = 0; j<nv; j++) {
|
|
moment[i*nv + j] *= gear[0];
|
|
}
|
|
break;
|
|
|
|
case mjTRN_TENDON: // tendon
|
|
length[i] = d->ten_length[id]*gear[0];
|
|
|
|
// moment: dense or sparse
|
|
if (mj_isSparse(m)) {
|
|
int end = d->ten_J_rowadr[id] + d->ten_J_rownnz[id];
|
|
for (int j=d->ten_J_rowadr[id]; j<end; j++) {
|
|
moment[i*nv + d->ten_J_colind[j]] = d->ten_J[j] * gear[0];
|
|
}
|
|
} else {
|
|
mju_scl(moment + i*nv, d->ten_J + id*nv, gear[0], nv);
|
|
}
|
|
break;
|
|
|
|
case mjTRN_SITE: // site
|
|
// cannot compute meaningful length, set to 0
|
|
length[i] = 0;
|
|
|
|
// get site translation and rotation global Jacobians
|
|
mj_jacSite(m, d, jac, jacS, id);
|
|
|
|
// wrench: site gear vector in global coordinates
|
|
mju_mulMatVec(wrench, d->site_xmat+9*id, gear, 3, 3); // translation
|
|
mju_mulMatVec(wrench+3, d->site_xmat+9*id, gear+3, 3, 3); // rotation
|
|
|
|
// moment: global Jacobian projected on wrench
|
|
mju_mulMatTVec(moment+i*nv, jac, wrench, 3, nv); // translation
|
|
mju_mulMatTVec(jac, jacS, wrench+3, 3, nv); // rotation
|
|
mju_addTo(moment+i*nv, jac, nv); // add the two
|
|
break;
|
|
|
|
case mjTRN_BODY: // body (adhesive contacts)
|
|
// cannot compute meaningful length, set to 0
|
|
length[i] = 0;
|
|
|
|
// moment is average of all contact normal Jacobians
|
|
{
|
|
// find and count all relevant contacts, mark them in efc_force
|
|
int counter = 0;
|
|
mjtNum* efc_force = mj_stackAlloc(d, d->nefc);
|
|
mju_zero(efc_force, d->nefc);
|
|
for (int j=0; j<d->ncon; j++) {
|
|
const mjContact* con = d->contact+j;
|
|
if (m->geom_bodyid[con->geom1]==id || m->geom_bodyid[con->geom2]==id) {
|
|
if (!con->exclude) {
|
|
counter++;
|
|
|
|
// condim 1 or elliptic cones: normal is in the first row
|
|
if (con->dim == 1 || m->opt.cone==mjCONE_ELLIPTIC) {
|
|
efc_force[con->efc_address] = 1;
|
|
}
|
|
|
|
// pyramidal cones: average all pyramid directions
|
|
else {
|
|
int npyramid = con->dim-1; // number of frictional directions
|
|
for (int k=0; k<2*npyramid; k++) {
|
|
efc_force[con->efc_address+k] = 0.5/npyramid;
|
|
}
|
|
}
|
|
} else if (con->exclude == 1) {
|
|
// TODO(b/240848298): compute Jacobians for excluded contact (in gap)
|
|
}
|
|
}
|
|
}
|
|
|
|
// moment is average over contact normal Jacobians, make negative for adhesion
|
|
if (counter) {
|
|
mj_mulJacTVec(m, d, moment+i*nv, efc_force);
|
|
mju_scl(moment+i*nv, moment+i*nv, -1.0/counter, nv);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
mju_error_i("Unknown transmission type %d", m->actuator_trntype[i]); // SHOULD NOT OCCUR
|
|
}
|
|
}
|
|
|
|
mjFREESTACK;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------- inertia ---------------------------------------------------------------
|
|
|
|
// composite rigid body inertia algorithm, with skipsimple
|
|
void mj_crbSkip(const mjModel* m, mjData* d, int skipsimple) {
|
|
mjtNum tmp[6];
|
|
mjtNum* crb = d->crb;
|
|
|
|
// crb = cinert
|
|
mju_copy(crb, d->cinert, 10*m->nbody);
|
|
|
|
// backward pass over bodies, accumulate composite inertias
|
|
for (int i=m->nbody-1; i>0; i--) {
|
|
if (m->body_parentid[i]>0) {
|
|
mju_addTo(crb+10*m->body_parentid[i], crb+10*i, 10);
|
|
}
|
|
}
|
|
|
|
// clear qM
|
|
mju_zero(d->qM, m->nM);
|
|
|
|
// dense backward pass over dofs
|
|
for (int i=m->nv-1; i>=0; i--) {
|
|
// copy
|
|
if (skipsimple && m->dof_simplenum[i]) {
|
|
d->qM[m->dof_Madr[i]] = m->dof_M0[i];
|
|
}
|
|
|
|
// compute
|
|
else {
|
|
// init M(i,i) with armature inertia
|
|
int Madr_ij = m->dof_Madr[i];
|
|
d->qM[Madr_ij] = m->dof_armature[i];
|
|
|
|
// precompute tmp = crb * cdof
|
|
mju_mulInertVec(tmp, crb+10*m->dof_bodyid[i], d->cdof+6*i);
|
|
|
|
// sparse backward pass over ancestors
|
|
int j = i;
|
|
while (j>=0) {
|
|
// M(i,j) += cdof_j * crb_body(i) * cdof_i = cdof_j * tmp
|
|
d->qM[Madr_ij] += mju_dot(d->cdof+6*j, tmp, 6);
|
|
|
|
// advance to parent
|
|
j = m->dof_parentid[j];
|
|
Madr_ij++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// composite rigid body inertia algorithm
|
|
void mj_crb(const mjModel* m, mjData* d) {
|
|
mj_crbSkip(m, d, 1);
|
|
}
|
|
|
|
|
|
|
|
// sparse L'*D*L factorizaton of inertia-like matrix M, assumed spd
|
|
void mj_factorI(const mjModel* m, mjData* d, const mjtNum* M, mjtNum* qLD, mjtNum* qLDiagInv,
|
|
mjtNum* qLDiagSqrtInv) {
|
|
int cnt;
|
|
int Madr_kk, Madr_ki;
|
|
mjtNum tmp;
|
|
|
|
// local copies of key variables
|
|
int* dof_Madr = m->dof_Madr;
|
|
int* dof_parentid = m->dof_parentid;
|
|
int nv = m->nv;
|
|
|
|
// copy M into LD
|
|
mju_copy(qLD, M, m->nM);
|
|
|
|
// dense backward loop over dofs (regular only, simple diagonal already copied)
|
|
for (int k=nv-1; k>=0; k--) {
|
|
// get address of M(k,k)
|
|
Madr_kk = dof_Madr[k];
|
|
|
|
// check for small/negative numbers on diagonal
|
|
if (qLD[Madr_kk]<mjMINVAL) {
|
|
mj_warning(d, mjWARN_INERTIA, k);
|
|
qLD[Madr_kk] = mjMINVAL;
|
|
}
|
|
|
|
// skip the rest if simple
|
|
if (m->dof_simplenum[k]) {
|
|
continue;
|
|
}
|
|
|
|
// sparse backward loop over ancestors of k (excluding k)
|
|
Madr_ki = Madr_kk + 1;
|
|
int i = dof_parentid[k];
|
|
while (i>=0) {
|
|
tmp = qLD[Madr_ki] / qLD[Madr_kk]; // tmp = M(k,i) / M(k,k)
|
|
|
|
// get number of ancestors of i (including i)
|
|
if (i<nv-1) {
|
|
cnt = dof_Madr[i+1] - dof_Madr[i];
|
|
} else {
|
|
cnt = m->nM - dof_Madr[i+1];
|
|
}
|
|
|
|
// M(i,j) -= M(k,j) * tmp
|
|
mju_addToScl(qLD+dof_Madr[i], qLD+Madr_ki, -tmp, cnt);
|
|
|
|
qLD[Madr_ki] = tmp; // M(k,i) = tmp
|
|
|
|
// advance to i's parent
|
|
i = dof_parentid[i];
|
|
Madr_ki++;
|
|
}
|
|
}
|
|
|
|
// compute 1/diag(D), 1/sqrt(diag(D))
|
|
for (int i=0; i<nv; i++) {
|
|
mjtNum qLDi = qLD[dof_Madr[i]];
|
|
qLDiagInv[i] = 1.0/qLDi;
|
|
if (qLDiagSqrtInv) {
|
|
qLDiagSqrtInv[i] = 1.0/mju_sqrt(qLDi);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// sparse L'*D*L factorizaton of the inertia matrix M, assumed spd
|
|
void mj_factorM(const mjModel* m, mjData* d) {
|
|
mj_factorI(m, d, d->qM, d->qLD, d->qLDiagInv, d->qLDiagSqrtInv);
|
|
}
|
|
|
|
|
|
|
|
// sparse backsubstitution: x = inv(L'*D*L)*y
|
|
// L is in lower triangle of qLD; D is on diagonal of qLD
|
|
// handle n vectors at once
|
|
void mj_solveLD(const mjModel* m, mjtNum* x, const mjtNum* y, int n,
|
|
const mjtNum* qLD, const mjtNum* qLDiagInv) {
|
|
mjtNum tmp;
|
|
|
|
// local copies of key variables
|
|
int* dof_Madr = m->dof_Madr;
|
|
int* dof_parentid = m->dof_parentid;
|
|
int nv = m->nv;
|
|
|
|
// x = y
|
|
if (x != y) {
|
|
mju_copy(x, y, n*nv);
|
|
}
|
|
|
|
// single vector
|
|
if (n==1) {
|
|
// x <- inv(L') * x; skip simple, exploit sparsity of input vector
|
|
for (int i=nv-1; i>=0; i--) {
|
|
if (!m->dof_simplenum[i] && (tmp = x[i])) {
|
|
// init
|
|
int Madr_ij = dof_Madr[i]+1;
|
|
int j = dof_parentid[i];
|
|
|
|
// traverse ancestors backwards
|
|
while (j>=0) {
|
|
x[j] -= qLD[Madr_ij++]*tmp; // x(j) -= L(i,j) * x(i)
|
|
|
|
// advance to parent
|
|
j = dof_parentid[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
// x <- inv(D) * x
|
|
for (int i=0; i<nv; i++) {
|
|
x[i] *= qLDiagInv[i]; // x(i) /= L(i,i)
|
|
}
|
|
|
|
// x <- inv(L) * x; skip simple
|
|
for (int i=0; i<nv; i++) {
|
|
if (!m->dof_simplenum[i]) {
|
|
// init
|
|
int Madr_ij = dof_Madr[i]+1;
|
|
int j = dof_parentid[i];
|
|
|
|
// traverse ancestors backwards
|
|
tmp = x[i];
|
|
while (j>=0) {
|
|
tmp -= qLD[Madr_ij++]*x[j]; // x(i) -= L(i,j) * x(j)
|
|
|
|
// advance to parent
|
|
j = dof_parentid[j];
|
|
}
|
|
x[i] = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// multiple vectors
|
|
else {
|
|
int offset;
|
|
|
|
// x <- inv(L') * x; skip simple
|
|
for (int i=nv-1; i>=0; i--) {
|
|
if (!m->dof_simplenum[i]) {
|
|
// init
|
|
int Madr_ij = dof_Madr[i]+1;
|
|
int j = dof_parentid[i];
|
|
|
|
// traverse ancestors backwards
|
|
while (j>=0) {
|
|
// process all vectors, exploit sparsity
|
|
for (offset=0; offset<n*nv; offset+=nv)
|
|
if ((tmp = x[i+offset])) {
|
|
x[j+offset] -= qLD[Madr_ij]*tmp; // x(j) -= L(i,j) * x(i)
|
|
}
|
|
|
|
// advance to parent
|
|
Madr_ij++;
|
|
j = dof_parentid[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
// x <- inv(D) * x
|
|
for (int i=0; i<nv; i++) {
|
|
for (offset=0; offset<n*nv; offset+=nv) {
|
|
x[i+offset] *= qLDiagInv[i]; // x(i) /= L(i,i)
|
|
}
|
|
}
|
|
|
|
// x <- inv(L) * x; skip simple
|
|
for (int i=0; i<nv; i++) {
|
|
if (!m->dof_simplenum[i]) {
|
|
// init
|
|
int Madr_ij = dof_Madr[i]+1;
|
|
int j = dof_parentid[i];
|
|
|
|
// traverse ancestors backwards
|
|
tmp = x[i+offset];
|
|
while (j>=0) {
|
|
// process all vectors
|
|
for (offset=0; offset<n*nv; offset+=nv) {
|
|
x[i+offset] -= qLD[Madr_ij]*x[j+offset]; // x(i) -= L(i,j) * x(j)
|
|
}
|
|
|
|
// advance to parent
|
|
Madr_ij++;
|
|
j = dof_parentid[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// sparse backsubstitution: x = inv(L'*D*L)*y
|
|
// use factorization in d
|
|
void mj_solveM(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n) {
|
|
mj_solveLD(m, x, y, n, d->qLD, d->qLDiagInv);
|
|
}
|
|
|
|
|
|
|
|
// half of sparse backsubstitution: x = sqrt(inv(D))*inv(L')*y
|
|
void mj_solveM2(const mjModel* m, mjData* d, mjtNum* x, const mjtNum* y, int n) {
|
|
// local copies of key variables
|
|
mjtNum* qLD = d->qLD;
|
|
mjtNum* qLDiagSqrtInv = d->qLDiagSqrtInv;
|
|
int* dof_Madr = m->dof_Madr;
|
|
int* dof_parentid = m->dof_parentid;
|
|
int nv = m->nv;
|
|
|
|
// x = y
|
|
mju_copy(x, y, n * nv);
|
|
|
|
// loop over the n input vectors
|
|
for (int ivec=0; ivec<n; ivec++) {
|
|
int offset = ivec*nv;
|
|
|
|
// x <- inv(L') * x; skip simple, exploit sparsity of input vector
|
|
for (int i=nv-1; i>=0; i--) {
|
|
mjtNum tmp;
|
|
if (!m->dof_simplenum[i] && (tmp = x[i+offset])) {
|
|
// init
|
|
int Madr_ij = dof_Madr[i]+1;
|
|
int j = dof_parentid[i];
|
|
|
|
// traverse ancestors backwards
|
|
while (j>=0) {
|
|
x[j+offset] -= qLD[Madr_ij++] * tmp; // x(j) -= L(i,j) * x(i)
|
|
|
|
// advance to parent
|
|
j = dof_parentid[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
// x <- sqrt(inv(D)) * x
|
|
for (int i=0; i<nv; i++) {
|
|
x[i+offset] *= qLDiagSqrtInv[i]; // x(i) /= sqrt(L(i,i))
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------- velocity ------------------------------------------------------
|
|
|
|
// compute cvel, cdof_dot
|
|
void mj_comVel(const mjModel* m, mjData* d) {
|
|
mjtNum tmp[6], cvel[6], cdofdot[36];
|
|
|
|
// set world vel to 0
|
|
mju_zero(d->cvel, 6);
|
|
|
|
// forward pass over bodies
|
|
for (int i=1; i<m->nbody; i++) {
|
|
// get body's first dof address
|
|
int bda = m->body_dofadr[i];
|
|
|
|
// cvel = cvel_parent
|
|
mju_copy(cvel, d->cvel+6*m->body_parentid[i], 6);
|
|
|
|
// cvel = cvel_parent + cdof * qvel, cdofdot = cvel x cdof
|
|
for (int j=0; j<m->body_dofnum[i]; j++) {
|
|
// compute cvel and cdofdot
|
|
switch (m->jnt_type[m->dof_jntid[bda+j]]) {
|
|
case mjJNT_FREE:
|
|
// cdofdot = 0
|
|
mju_zero(cdofdot, 18);
|
|
|
|
// update velocity
|
|
mju_mulDofVec(tmp, d->cdof+6*bda, d->qvel+bda, 3);
|
|
mju_addTo(cvel, tmp, 6);
|
|
|
|
// continue with rotations
|
|
j += 3;
|
|
|
|
case mjJNT_BALL:
|
|
// compute all 3 cdofdots using parent velocity
|
|
for (int k=0; k<3; k++) {
|
|
mju_crossMotion(cdofdot+6*(j+k), cvel, d->cdof+6*(bda+j+k));
|
|
}
|
|
|
|
// update velocity
|
|
mju_mulDofVec(tmp, d->cdof+6*(bda+j), d->qvel+bda+j, 3);
|
|
mju_addTo(cvel, tmp, 6);
|
|
|
|
// adjust for 3-dof joint
|
|
j += 2;
|
|
break;
|
|
|
|
default:
|
|
// in principle we should use the new velocity to compute cdofdot,
|
|
// but it makes no difference becase crossMotion(cdof, cdof) = 0,
|
|
// and using the old velocity may be more accurate numerically
|
|
mju_crossMotion(cdofdot+6*j, cvel, d->cdof+6*(bda+j));
|
|
|
|
// update velocity
|
|
mju_mulDofVec(tmp, d->cdof+6*(bda+j), d->qvel+bda+j, 1);
|
|
mju_addTo(cvel, tmp, 6);
|
|
}
|
|
}
|
|
|
|
// assign cvel, cdofdot
|
|
mju_copy(d->cvel+6*i, cvel, 6);
|
|
mju_copy(d->cdof_dot+6*bda, cdofdot, 6*m->body_dofnum[i]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// passive forces
|
|
void mj_passive(const mjModel* m, mjData* d) {
|
|
int issparse = mj_isSparse(m);
|
|
int nv = m->nv;
|
|
mjtNum dif[3], frc, stiffness, damping;
|
|
|
|
// clear passive force
|
|
mju_zero(d->qfrc_passive, m->nv);
|
|
|
|
// disabled: return
|
|
if (mjDISABLED(mjDSBL_PASSIVE)) {
|
|
return;
|
|
}
|
|
|
|
// joint-level springs
|
|
for (int i=0; i<m->njnt; i++) {
|
|
stiffness = m->jnt_stiffness[i];
|
|
|
|
int padr = m->jnt_qposadr[i];
|
|
int dadr = m->jnt_dofadr[i];
|
|
|
|
switch (m->jnt_type[i]) {
|
|
case mjJNT_FREE:
|
|
// apply force
|
|
d->qfrc_passive[dadr+0] -= stiffness*(d->qpos[padr+0] - m->qpos_spring[padr+0]);
|
|
d->qfrc_passive[dadr+1] -= stiffness*(d->qpos[padr+1] - m->qpos_spring[padr+1]);
|
|
d->qfrc_passive[dadr+2] -= stiffness*(d->qpos[padr+2] - m->qpos_spring[padr+2]);
|
|
|
|
// continue with rotations
|
|
dadr += 3;
|
|
padr += 3;
|
|
|
|
case mjJNT_BALL:
|
|
// covert quatertion difference into angular "velocity"
|
|
mju_subQuat(dif, d->qpos + padr, m->qpos_spring + padr);
|
|
|
|
// apply torque
|
|
d->qfrc_passive[dadr+0] -= stiffness*dif[0];
|
|
d->qfrc_passive[dadr+1] -= stiffness*dif[1];
|
|
d->qfrc_passive[dadr+2] -= stiffness*dif[2];
|
|
break;
|
|
|
|
case mjJNT_SLIDE:
|
|
case mjJNT_HINGE:
|
|
// apply force or torque
|
|
d->qfrc_passive[dadr] -= stiffness*(d->qpos[padr] - m->qpos_spring[padr]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// dof-level dampers
|
|
for (int i=0; i<m->nv; i++) {
|
|
damping = m->dof_damping[i];
|
|
d->qfrc_passive[i] -= damping*d->qvel[i];
|
|
}
|
|
|
|
// tendon-level spring-dampers
|
|
for (int i=0; i<m->ntendon; i++) {
|
|
stiffness = m->tendon_stiffness[i];
|
|
damping = m->tendon_damping[i];
|
|
|
|
// compute spring-damper linear force along tendon
|
|
frc = -stiffness * (d->ten_length[i] - m->tendon_lengthspring[i])
|
|
-damping * d->ten_velocity[i];
|
|
|
|
// transform to joint torque, add to qfrc_passive: dense or sparse
|
|
if (issparse) {
|
|
int end = d->ten_J_rowadr[i] + d->ten_J_rownnz[i];
|
|
for (int j=d->ten_J_rowadr[i]; j<end; j++) {
|
|
d->qfrc_passive[d->ten_J_colind[j]] += d->ten_J[j] * frc;
|
|
}
|
|
} else {
|
|
mju_addToScl(d->qfrc_passive, d->ten_J+i*nv, frc, nv);
|
|
}
|
|
}
|
|
|
|
// body-level viscosity, lift and drag
|
|
if (m->opt.viscosity>0 || m->opt.density>0) {
|
|
for (int i=1; i<m->nbody; i++) {
|
|
if (m->body_mass[i]<mjMINVAL) {
|
|
continue;
|
|
}
|
|
|
|
int use_ellipsoid_model = 0;
|
|
// if any child geom uses the ellipsoid model, inertia-box model is disabled for parent body
|
|
for (int j=0; j<m->body_geomnum[i] && use_ellipsoid_model==0; j++) {
|
|
const int geomid = m->body_geomadr[i] + j;
|
|
use_ellipsoid_model += (m->geom_fluid[mjNFLUID*geomid] > 0);
|
|
}
|
|
if (use_ellipsoid_model) {
|
|
mj_ellipsoidFluidModel(m, d, i);
|
|
} else {
|
|
mj_inertiaBoxFluidModel(m, d, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// user callback: add custom passive forces
|
|
if (mjcb_passive) {
|
|
mjcb_passive(m, d);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// subtree linear velocity and angular momentum
|
|
void mj_subtreeVel(const mjModel* m, mjData* d) {
|
|
mjtNum dx[3], dv[3], dp[3], dL[3];
|
|
mjMARKSTACK;
|
|
mjtNum* body_vel = mj_stackAlloc(d, 6*m->nbody);
|
|
|
|
// bodywise quantities
|
|
for (int i=0; i<m->nbody; i++) {
|
|
// compute and save body velocity
|
|
mj_objectVelocity(m, d, mjOBJ_BODY, i, body_vel+6*i, 0);
|
|
|
|
// body linear momentum
|
|
mju_scl3(d->subtree_linvel+3*i, body_vel+6*i+3, m->body_mass[i]);
|
|
|
|
// body angular momentum
|
|
mju_rotVecMatT(dv, body_vel+6*i, d->ximat+9*i);
|
|
dv[0] *= m->body_inertia[3*i];
|
|
dv[1] *= m->body_inertia[3*i+1];
|
|
dv[2] *= m->body_inertia[3*i+2];
|
|
mju_rotVecMat(d->subtree_angmom+3*i, dv, d->ximat+9*i);
|
|
}
|
|
|
|
// subtree linvel
|
|
for (int i=m->nbody-1; i>=0; i--) {
|
|
// non-world: add linear momentum to parent
|
|
if (i) {
|
|
mju_addTo3(d->subtree_linvel+3*m->body_parentid[i], d->subtree_linvel+3*i);
|
|
}
|
|
|
|
// convert linear momentum to linear velocity
|
|
mju_scl3(d->subtree_linvel+3*i, d->subtree_linvel+3*i,
|
|
1/mjMAX(mjMINVAL, m->body_subtreemass[i]));
|
|
}
|
|
|
|
// subtree angmom
|
|
for (int i=m->nbody-1; i>0; i--) {
|
|
int parent = m->body_parentid[i];
|
|
|
|
// momentum wrt body i
|
|
mju_sub3(dx, d->xipos+3*i, d->subtree_com+3*i);
|
|
mju_sub3(dv, body_vel+6*i+3, d->subtree_linvel+3*i);
|
|
mju_scl3(dp, dv, m->body_mass[i]);
|
|
mju_cross(dL, dx, dp);
|
|
|
|
// add to subtree i
|
|
mju_addTo3(d->subtree_angmom+3*i, dL);
|
|
|
|
// add to parent
|
|
mju_addTo3(d->subtree_angmom+3*parent, d->subtree_angmom+3*i);
|
|
|
|
// momentum wrt parent
|
|
mju_sub3(dx, d->subtree_com+3*i, d->subtree_com+3*parent);
|
|
mju_sub3(dv, d->subtree_linvel+3*i, d->subtree_linvel+3*parent);
|
|
mju_scl3(dv, dv, m->body_subtreemass[i]);
|
|
mju_cross(dL, dx, dv);
|
|
|
|
// add to parent
|
|
mju_addTo3(d->subtree_angmom+3*parent, dL);
|
|
}
|
|
|
|
mjFREESTACK;
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------- fluid models --------------------------------------------------
|
|
|
|
// fluid forces based on inertia-box approximation
|
|
void mj_inertiaBoxFluidModel(const mjModel* m, mjData* d, int i) {
|
|
mjtNum lvel[6], wind[6], lwind[6], lfrc[6], bfrc[6], box[3], diam, *inertia;
|
|
inertia = m->body_inertia + 3*i;
|
|
box[0] = mju_sqrt(mju_max(mjMINVAL,
|
|
(inertia[1] + inertia[2] - inertia[0])) / m->body_mass[i] * 6.0);
|
|
box[1] = mju_sqrt(mju_max(mjMINVAL,
|
|
(inertia[0] + inertia[2] - inertia[1])) / m->body_mass[i] * 6.0);
|
|
box[2] = mju_sqrt(mju_max(mjMINVAL,
|
|
(inertia[0] + inertia[1] - inertia[2])) / m->body_mass[i] * 6.0);
|
|
|
|
// map from CoM-centered to local body-centered 6D velocity
|
|
mj_objectVelocity(m, d, mjOBJ_BODY, i, lvel, 1);
|
|
|
|
// compute wind in local coordinates
|
|
mju_zero(wind, 6);
|
|
mju_copy3(wind+3, m->opt.wind);
|
|
mju_transformSpatial(lwind, wind, 0, d->xipos+3*i,
|
|
d->subtree_com+3*m->body_rootid[i], d->ximat+9*i);
|
|
|
|
// subtract translational component from body velocity
|
|
mju_subFrom3(lvel+3, lwind+3);
|
|
mju_zero(lfrc, 6);
|
|
|
|
// set viscous force and torque
|
|
if (m->opt.viscosity>0) {
|
|
// diameter of sphere approximation
|
|
diam = (box[0] + box[1] + box[2])/3.0;
|
|
|
|
// angular viscosity
|
|
mju_scl3(lfrc, lvel, -mjPI*diam*diam*diam*m->opt.viscosity);
|
|
|
|
// linear viscosity
|
|
mju_scl3(lfrc+3, lvel+3, -3.0*mjPI*diam*m->opt.viscosity);
|
|
}
|
|
|
|
// add lift and drag force and torque
|
|
if (m->opt.density>0) {
|
|
// force
|
|
lfrc[3] -= 0.5*m->opt.density*box[1]*box[2]*mju_abs(lvel[3])*lvel[3];
|
|
lfrc[4] -= 0.5*m->opt.density*box[0]*box[2]*mju_abs(lvel[4])*lvel[4];
|
|
lfrc[5] -= 0.5*m->opt.density*box[0]*box[1]*mju_abs(lvel[5])*lvel[5];
|
|
|
|
// torque
|
|
lfrc[0] -= m->opt.density*box[0]*(box[1]*box[1]*box[1]*box[1]+box[2]*box[2]*box[2]*box[2])*
|
|
mju_abs(lvel[0])*lvel[0]/64.0;
|
|
lfrc[1] -= m->opt.density*box[1]*(box[0]*box[0]*box[0]*box[0]+box[2]*box[2]*box[2]*box[2])*
|
|
mju_abs(lvel[1])*lvel[1]/64.0;
|
|
lfrc[2] -= m->opt.density*box[2]*(box[0]*box[0]*box[0]*box[0]+box[1]*box[1]*box[1]*box[1])*
|
|
mju_abs(lvel[2])*lvel[2]/64.0;
|
|
}
|
|
// rotate to global orientation: lfrc -> bfrc
|
|
mju_rotVecMat(bfrc, lfrc, d->ximat+9*i);
|
|
mju_rotVecMat(bfrc+3, lfrc+3, d->ximat+9*i);
|
|
|
|
// apply force and torque to body com
|
|
mj_applyFT(m, d, bfrc+3, bfrc, d->xipos+3*i, i, d->qfrc_passive);
|
|
}
|
|
|
|
|
|
|
|
// fluid forces based on ellipsoid approximation
|
|
void mj_ellipsoidFluidModel(const mjModel* m, mjData* d, int bodyid) {
|
|
mjtNum lvel[6], wind[6], lwind[6], lfrc[6], bfrc[6];
|
|
mjtNum geom_interaction_coef, magnus_lift_coef, kutta_lift_coef;
|
|
mjtNum semiaxes[3], virtual_mass[3], virtual_inertia[3];
|
|
mjtNum blunt_drag_coef, slender_drag_coef, ang_drag_coef;
|
|
|
|
for (int j=0; j<m->body_geomnum[bodyid]; j++) {
|
|
const int geomid = m->body_geomadr[bodyid] + j;
|
|
|
|
mju_geomSemiAxes(m, geomid, semiaxes);
|
|
|
|
readFluidGeomInteraction(
|
|
m->geom_fluid + mjNFLUID*geomid, &geom_interaction_coef,
|
|
&blunt_drag_coef, &slender_drag_coef, &ang_drag_coef,
|
|
&kutta_lift_coef, &magnus_lift_coef,
|
|
virtual_mass, virtual_inertia);
|
|
|
|
// scales all forces, read from MJCF as boolean (0.0 or 1.0)
|
|
if (geom_interaction_coef == 0.0) {
|
|
continue;
|
|
}
|
|
|
|
// map from CoM-centered to local body-centered 6D velocity
|
|
mj_objectVelocity(m, d, mjOBJ_GEOM, geomid, lvel, 1);
|
|
|
|
// compute wind in local coordinates
|
|
mju_zero(wind, 6);
|
|
mju_copy3(wind+3, m->opt.wind);
|
|
mju_transformSpatial(lwind, wind, 0,
|
|
d->geom_xpos + 3*geomid, // Frame of ref's origin.
|
|
d->subtree_com + 3*m->body_rootid[bodyid],
|
|
d->geom_xmat + 9*geomid); // Frame of ref's orientation.
|
|
|
|
// subtract translational component from grom velocity
|
|
mju_subFrom3(lvel+3, lwind+3);
|
|
|
|
// initialize viscous force and torque
|
|
mju_zero(lfrc, 6);
|
|
|
|
// added-mass forces and torques
|
|
mj_addedMassForces(lvel, NULL, m->opt.density, virtual_mass, virtual_inertia, lfrc);
|
|
|
|
// lift force orthogonal to lvel from Kutta-Joukowski theorem
|
|
mj_viscousForces(lvel, m->opt.density, m->opt.viscosity, semiaxes, magnus_lift_coef,
|
|
kutta_lift_coef, blunt_drag_coef, slender_drag_coef, ang_drag_coef, lfrc);
|
|
|
|
// scale by geom_interaction_coef (1.0 by default)
|
|
mju_scl(lfrc, lfrc, geom_interaction_coef, 6);
|
|
|
|
// rotate to global orientation: lfrc -> bfrc
|
|
mju_rotVecMat(bfrc, lfrc, d->geom_xmat + 9*geomid);
|
|
mju_rotVecMat(bfrc+3, lfrc+3, d->geom_xmat + 9*geomid);
|
|
|
|
// apply force and torque to body com
|
|
mj_applyFT(m, d, bfrc+3, bfrc,
|
|
d->geom_xpos + 3*geomid, // point where FT is generated
|
|
bodyid, d->qfrc_passive);
|
|
}
|
|
}
|
|
|
|
|
|
// compute forces due to fluid mass moving with the body
|
|
void mj_addedMassForces(const mjtNum local_vels[6], const mjtNum local_accels[6],
|
|
const mjtNum fluid_density, const mjtNum virtual_mass[3],
|
|
const mjtNum virtual_inertia[3], mjtNum local_force[6])
|
|
{
|
|
const mjtNum lin_vel[3] = {local_vels[3], local_vels[4], local_vels[5]};
|
|
const mjtNum ang_vel[3] = {local_vels[0], local_vels[1], local_vels[2]};
|
|
const mjtNum virtual_lin_mom[3] = {
|
|
fluid_density * virtual_mass[0] * lin_vel[0],
|
|
fluid_density * virtual_mass[1] * lin_vel[1],
|
|
fluid_density * virtual_mass[2] * lin_vel[2]
|
|
};
|
|
const mjtNum virtual_ang_mom[3] = {
|
|
fluid_density * virtual_inertia[0] * ang_vel[0],
|
|
fluid_density * virtual_inertia[1] * ang_vel[1],
|
|
fluid_density * virtual_inertia[2] * ang_vel[2]
|
|
};
|
|
|
|
// disabled due to dependency on qacc but included for completeness
|
|
if (local_accels) {
|
|
local_force[0] -= fluid_density * virtual_inertia[0] * local_accels[0];
|
|
local_force[1] -= fluid_density * virtual_inertia[1] * local_accels[1];
|
|
local_force[2] -= fluid_density * virtual_inertia[2] * local_accels[2];
|
|
local_force[3] -= fluid_density * virtual_mass[0] * local_accels[3];
|
|
local_force[4] -= fluid_density * virtual_mass[1] * local_accels[4];
|
|
local_force[5] -= fluid_density * virtual_mass[2] * local_accels[5];
|
|
}
|
|
|
|
mjtNum added_mass_force[3], added_mass_torque1[3], added_mass_torque2[3];
|
|
mju_cross(added_mass_force, virtual_lin_mom, ang_vel);
|
|
mju_cross(added_mass_torque1, virtual_lin_mom, lin_vel);
|
|
mju_cross(added_mass_torque2, virtual_ang_mom, ang_vel);
|
|
|
|
mju_addTo3(local_force, added_mass_torque1);
|
|
mju_addTo3(local_force, added_mass_torque2);
|
|
mju_addTo3(local_force+3, added_mass_force);
|
|
}
|
|
|
|
|
|
// inlined helper functions
|
|
static inline mjtNum mji_pow4(const mjtNum val) {
|
|
return (val*val)*(val*val);
|
|
}
|
|
|
|
static inline mjtNum mji_pow2(const mjtNum val) {
|
|
return val*val;
|
|
}
|
|
|
|
static inline mjtNum mji_ellipsoid_max_moment(const mjtNum size[3], const int dir) {
|
|
const mjtNum d0 = size[dir], d1 = size[(dir+1) % 3], d2 = size[(dir+2) % 3];
|
|
return 8.0/15.0 * mjPI * d0 * mji_pow4(mju_max(d1, d2));
|
|
}
|
|
|
|
|
|
|
|
// lift and drag forces due to motion in the fluid
|
|
void mj_viscousForces(
|
|
const mjtNum local_vels[6], const mjtNum fluid_density,
|
|
const mjtNum fluid_viscosity, const mjtNum size[3],
|
|
const mjtNum magnus_lift_coef, const mjtNum kutta_lift_coef,
|
|
const mjtNum blunt_drag_coef, const mjtNum slender_drag_coef,
|
|
const mjtNum ang_drag_coef, mjtNum local_force[6])
|
|
{
|
|
const mjtNum lin_vel[3] = {local_vels[3], local_vels[4], local_vels[5]};
|
|
const mjtNum ang_vel[3] = {local_vels[0], local_vels[1], local_vels[2]};
|
|
const mjtNum volume = 4.0/3.0 * mjPI * size[0] * size[1] * size[2];
|
|
const mjtNum d_max = mju_max(mju_max(size[0], size[1]), size[2]);
|
|
const mjtNum d_min = mju_min(mju_min(size[0], size[1]), size[2]);
|
|
const mjtNum d_mid = size[0] + size[1] + size[2] - d_max - d_min;
|
|
const mjtNum A_max = mjPI * d_max * d_mid;
|
|
|
|
mjtNum magnus_force[3];
|
|
mju_cross(magnus_force, ang_vel, lin_vel);
|
|
magnus_force[0] *= magnus_lift_coef * fluid_density * volume;
|
|
magnus_force[1] *= magnus_lift_coef * fluid_density * volume;
|
|
magnus_force[2] *= magnus_lift_coef * fluid_density * volume;
|
|
|
|
// the dot product between velocity and the normal to the cross-section that
|
|
// defines the body's projection along velocity is proj_num/sqrt(proj_denom)
|
|
const mjtNum proj_denom = mji_pow4(size[1] * size[2]) * mji_pow2(lin_vel[0]) +
|
|
mji_pow4(size[2] * size[0]) * mji_pow2(lin_vel[1]) +
|
|
mji_pow4(size[0] * size[1]) * mji_pow2(lin_vel[2]);
|
|
const mjtNum proj_num = mji_pow2(size[1] * size[2] * lin_vel[0]) +
|
|
mji_pow2(size[2] * size[0] * lin_vel[1]) +
|
|
mji_pow2(size[0] * size[1] * lin_vel[2]);
|
|
|
|
// projected surface in the direction of the velocity
|
|
const mjtNum A_proj = mjPI * mju_sqrt(proj_denom/mju_max(mjMINVAL, proj_num));
|
|
|
|
// not-unit normal to ellipsoid's projected area in the direction of velocity
|
|
const mjtNum norm[3] = {
|
|
mji_pow2(size[1] * size[2]) * lin_vel[0],
|
|
mji_pow2(size[2] * size[0]) * lin_vel[1],
|
|
mji_pow2(size[0] * size[1]) * lin_vel[2]
|
|
};
|
|
|
|
// cosine between velocity and normal to the surface
|
|
// divided by proj_denom instead of sqrt(proj_denom) to account for skipped normalization in norm
|
|
const mjtNum cos_alpha = proj_num / mju_max(
|
|
mjMINVAL, mju_norm3(lin_vel) * proj_denom);
|
|
mjtNum kutta_circ[3];
|
|
mju_cross(kutta_circ, norm, lin_vel);
|
|
kutta_circ[0] *= kutta_lift_coef * fluid_density * cos_alpha * A_proj;
|
|
kutta_circ[1] *= kutta_lift_coef * fluid_density * cos_alpha * A_proj;
|
|
kutta_circ[2] *= kutta_lift_coef * fluid_density * cos_alpha * A_proj;
|
|
mjtNum kutta_force[3];
|
|
mju_cross(kutta_force, kutta_circ, lin_vel);
|
|
|
|
// viscous force and torque in Stokes flow, analytical for spherical bodies
|
|
const mjtNum eq_sphere_D = 2.0/3.0 * (size[0] + size[1] + size[2]);
|
|
const mjtNum lin_visc_force_coef = 3.0 * mjPI * eq_sphere_D;
|
|
const mjtNum lin_visc_torq_coef = mjPI * eq_sphere_D*eq_sphere_D*eq_sphere_D;
|
|
|
|
// moments of inertia used to compute angular quadratic drag
|
|
const mjtNum I_max = 8.0/15.0 * mjPI * d_mid * mji_pow4(d_max);
|
|
const mjtNum II[3] = {
|
|
mji_ellipsoid_max_moment(size, 0),
|
|
mji_ellipsoid_max_moment(size, 1),
|
|
mji_ellipsoid_max_moment(size, 2)
|
|
};
|
|
const mjtNum mom_visc[3] = {
|
|
ang_vel[0] * (ang_drag_coef*II[0] + slender_drag_coef*(I_max - II[0])),
|
|
ang_vel[1] * (ang_drag_coef*II[1] + slender_drag_coef*(I_max - II[1])),
|
|
ang_vel[2] * (ang_drag_coef*II[2] + slender_drag_coef*(I_max - II[2]))
|
|
};
|
|
|
|
const mjtNum drag_lin_coef = // linear plus quadratic
|
|
fluid_viscosity*lin_visc_force_coef + fluid_density*mju_norm3(lin_vel)*(
|
|
A_proj*blunt_drag_coef + slender_drag_coef*(A_max - A_proj));
|
|
const mjtNum drag_ang_coef = // linear plus quadratic
|
|
fluid_viscosity * lin_visc_torq_coef +
|
|
fluid_density * mju_norm3(mom_visc);
|
|
|
|
local_force[0] -= drag_ang_coef * ang_vel[0];
|
|
local_force[1] -= drag_ang_coef * ang_vel[1];
|
|
local_force[2] -= drag_ang_coef * ang_vel[2];
|
|
local_force[3] += magnus_force[0] + kutta_force[0] - drag_lin_coef*lin_vel[0];
|
|
local_force[4] += magnus_force[1] + kutta_force[1] - drag_lin_coef*lin_vel[1];
|
|
local_force[5] += magnus_force[2] + kutta_force[2] - drag_lin_coef*lin_vel[2];
|
|
}
|
|
|
|
|
|
|
|
// read the geom_fluid_coefs array into its constituent parts
|
|
void readFluidGeomInteraction(const mjtNum* geom_fluid_coefs,
|
|
mjtNum* geom_fluid_coef,
|
|
mjtNum* blunt_drag_coef,
|
|
mjtNum* slender_drag_coef,
|
|
mjtNum* ang_drag_coef,
|
|
mjtNum* kutta_lift_coef,
|
|
mjtNum* magnus_lift_coef,
|
|
mjtNum virtual_mass[3],
|
|
mjtNum virtual_inertia[3]) {
|
|
int i = 0;
|
|
geom_fluid_coef[0] = geom_fluid_coefs[i++];
|
|
blunt_drag_coef[0] = geom_fluid_coefs[i++];
|
|
slender_drag_coef[0] = geom_fluid_coefs[i++];
|
|
ang_drag_coef[0] = geom_fluid_coefs[i++];
|
|
kutta_lift_coef[0] = geom_fluid_coefs[i++];
|
|
magnus_lift_coef[0] = geom_fluid_coefs[i++];
|
|
virtual_mass[0] = geom_fluid_coefs[i++];
|
|
virtual_mass[1] = geom_fluid_coefs[i++];
|
|
virtual_mass[2] = geom_fluid_coefs[i++];
|
|
virtual_inertia[0] = geom_fluid_coefs[i++];
|
|
virtual_inertia[1] = geom_fluid_coefs[i++];
|
|
virtual_inertia[2] = geom_fluid_coefs[i++];
|
|
if (i != mjNFLUID) {
|
|
mju_error("Error in reading geom_fluid_coefs: wrong number of entries.");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// write components into geom_fluid_coefs array
|
|
void writeFluidGeomInteraction (mjtNum* geom_fluid_coefs,
|
|
const mjtNum* geom_fluid_coef,
|
|
const mjtNum* blunt_drag_coef,
|
|
const mjtNum* slender_drag_coef,
|
|
const mjtNum* ang_drag_coef,
|
|
const mjtNum* kutta_lift_coef,
|
|
const mjtNum* magnus_lift_coef,
|
|
const mjtNum virtual_mass[3],
|
|
const mjtNum virtual_inertia[3]) {
|
|
int i = 0;
|
|
geom_fluid_coefs[i++] = geom_fluid_coef[0];
|
|
geom_fluid_coefs[i++] = blunt_drag_coef[0];
|
|
geom_fluid_coefs[i++] = slender_drag_coef[0];
|
|
geom_fluid_coefs[i++] = ang_drag_coef[0];
|
|
geom_fluid_coefs[i++] = kutta_lift_coef[0];
|
|
geom_fluid_coefs[i++] = magnus_lift_coef[0];
|
|
geom_fluid_coefs[i++] = virtual_mass[0];
|
|
geom_fluid_coefs[i++] = virtual_mass[1];
|
|
geom_fluid_coefs[i++] = virtual_mass[2];
|
|
geom_fluid_coefs[i++] = virtual_inertia[0];
|
|
geom_fluid_coefs[i++] = virtual_inertia[1];
|
|
geom_fluid_coefs[i++] = virtual_inertia[2];
|
|
if (i != mjNFLUID) {
|
|
mju_error("Error in writing geom_fluid_coefs: wrong number of entries.");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------- RNE -----------------------------------------------------------
|
|
|
|
// RNE: compute M(qpos)*qacc + C(qpos,qvel); flg_acc=0 removes inertial term
|
|
void mj_rne(const mjModel* m, mjData* d, int flg_acc, mjtNum* result) {
|
|
mjtNum tmp[6], tmp1[6];
|
|
mjMARKSTACK;
|
|
mjtNum* loc_cacc = mj_stackAlloc(d, m->nbody*6);
|
|
mjtNum* loc_cfrc_body = mj_stackAlloc(d, m->nbody*6);
|
|
|
|
// set world acceleration to -gravity
|
|
mju_zero(loc_cacc, 6);
|
|
if (!mjDISABLED(mjDSBL_GRAVITY)) {
|
|
mju_scl3(loc_cacc+3, m->opt.gravity, -1);
|
|
}
|
|
|
|
// forward pass over bodies: accumulate cacc, set cfrc_body
|
|
for (int i=1; i<m->nbody; i++) {
|
|
// get body's first dof address
|
|
int bda = m->body_dofadr[i];
|
|
|
|
// cacc = cacc_parent + cdofdot * qvel
|
|
mju_mulDofVec(tmp, d->cdof_dot+6*bda, d->qvel+bda, m->body_dofnum[i]);
|
|
mju_add(loc_cacc+6*i, loc_cacc+6*m->body_parentid[i], tmp, 6);
|
|
|
|
// cacc += cdof * qacc
|
|
if (flg_acc) {
|
|
mju_mulDofVec(tmp, d->cdof+6*bda, d->qacc+bda, m->body_dofnum[i]);
|
|
mju_addTo(loc_cacc+6*i, tmp, 6);
|
|
}
|
|
|
|
// cfrc_body = cinert * cacc + cvel x (cinert * cvel)
|
|
mju_mulInertVec(loc_cfrc_body+6*i, d->cinert+10*i, loc_cacc+6*i);
|
|
mju_mulInertVec(tmp, d->cinert+10*i, d->cvel+6*i);
|
|
mju_crossForce(tmp1, d->cvel+6*i, tmp);
|
|
mju_addTo(loc_cfrc_body+6*i, tmp1, 6);
|
|
}
|
|
|
|
// clear world cfrc_body, for style
|
|
mju_zero(loc_cfrc_body, 6);
|
|
|
|
// backward pass over bodies: accumulate cfrc_body from children
|
|
for (int i=m->nbody-1; i>0; i--)
|
|
if (m->body_parentid[i]) {
|
|
mju_addTo(loc_cfrc_body+6*m->body_parentid[i], loc_cfrc_body+6*i, 6);
|
|
}
|
|
|
|
// result = cdof * cfrc_body
|
|
for (int i=0; i<m->nv; i++) {
|
|
result[i] = mju_dot(d->cdof+6*i, loc_cfrc_body+6*m->dof_bodyid[i], 6);
|
|
}
|
|
|
|
mjFREESTACK;
|
|
}
|
|
|
|
|
|
|
|
// RNE with complete data: compute cacc, cfrc_ext, cfrc_int
|
|
void mj_rnePostConstraint(const mjModel* m, mjData* d) {
|
|
int nbody=m->nbody;
|
|
mjtNum cfrc_body[6], tmp[6], tmp1[6];
|
|
mjContact* con;
|
|
|
|
// clear cacc, set world acceleration to -gravity
|
|
mju_zero(d->cacc, 6);
|
|
if (!mjDISABLED(mjDSBL_GRAVITY)) {
|
|
mju_scl3(d->cacc+3, m->opt.gravity, -1);
|
|
}
|
|
|
|
// cfrc_ext = perturb
|
|
mju_zero(d->cfrc_ext, 6*nbody);
|
|
for (int i=1; i<nbody; i++)
|
|
if (!mju_isZero(d->xfrc_applied+6*i, 6)) {
|
|
// rearrange as torque:force
|
|
mju_copy3(tmp1, d->xfrc_applied+6*i+3);
|
|
mju_copy3(tmp1+3, d->xfrc_applied+6*i);
|
|
|
|
// map force from application point to com; both world-oriented
|
|
mju_transformSpatial(tmp, tmp1, 1, d->subtree_com+3*m->body_rootid[i], d->xipos+3*i, 0);
|
|
|
|
// accumulate
|
|
mju_addTo(d->cfrc_ext+6*i, tmp, 6);
|
|
}
|
|
|
|
// cfrc_ext += contacts
|
|
for (int i=0; i<d->ncon; i++)
|
|
if (d->contact[i].efc_address>=0) {
|
|
// get contact pointer
|
|
con = d->contact+i;
|
|
|
|
// tmp = contact-local force:torque vector
|
|
mj_contactForce(m, d, i, tmp);
|
|
|
|
// tmp1 = world-oriented torque:force vector (swap in the process)
|
|
mju_rotVecMatT(tmp1, tmp+3, con->frame);
|
|
mju_rotVecMatT(tmp1+3, tmp, con->frame);
|
|
|
|
// body 1
|
|
int k;
|
|
if ((k = m->geom_bodyid[con->geom1])) {
|
|
// tmp = subtree CoM-based torque_force vector
|
|
mju_transformSpatial(tmp, tmp1, 1, d->subtree_com+3*m->body_rootid[k], con->pos, 0);
|
|
|
|
// apply (opposite for body 1)
|
|
mju_subFrom(d->cfrc_ext+6*k, tmp, 6);
|
|
}
|
|
|
|
// body 2
|
|
if ((k = m->geom_bodyid[con->geom2])) {
|
|
// tmp = subtree CoM-based torque_force vector
|
|
mju_transformSpatial(tmp, tmp1, 1, d->subtree_com+3*m->body_rootid[k], con->pos, 0);
|
|
|
|
// apply
|
|
mju_addTo(d->cfrc_ext+6*k, tmp, 6);
|
|
}
|
|
}
|
|
|
|
// cfrc_ext += connect and weld constraints
|
|
int i = 0;
|
|
while (i < d->ne) {
|
|
if (d->efc_type[i]!=mjCNSTR_EQUALITY)
|
|
mju_error_i("Row %d of efc is not an equality constraint", i); // SHOULD NOT OCCUR
|
|
|
|
int id = d->efc_id[i];
|
|
mjtNum* eq_data = m->eq_data + mjNEQDATA*id;
|
|
mjtNum pos[3];
|
|
int k;
|
|
switch (m->eq_type[id]) {
|
|
case mjEQ_CONNECT:
|
|
// tmp1 = world-oriented torque:force vector
|
|
mju_zero3(tmp1); // no torque from connect
|
|
mju_copy3(tmp1 + 3, d->efc_force + i);
|
|
|
|
// body 1
|
|
if ((k = m->eq_obj1id[id])) {
|
|
// transform connect point on body1: local -> global
|
|
mju_rotVecMat(pos, eq_data, d->xmat+9*k);
|
|
mju_addTo3(pos, d->xpos+3*k);
|
|
|
|
// tmp = subtree CoM-based torque_force vector
|
|
mju_transformSpatial(tmp, tmp1, 1, d->subtree_com+3*m->body_rootid[k], pos, 0);
|
|
|
|
// apply (opposite for body 1)
|
|
mju_addTo(d->cfrc_ext+6*k, tmp, 6);
|
|
}
|
|
|
|
// body 2
|
|
if ((k = m->eq_obj2id[id])) {
|
|
// transform connect point on body2: local -> global
|
|
mju_rotVecMat(pos, eq_data + 3, d->xmat+9*k);
|
|
mju_addTo3(pos, d->xpos+3*k);
|
|
|
|
// tmp = subtree CoM-based torque_force vector
|
|
mju_transformSpatial(tmp, tmp1, 1, d->subtree_com+3*m->body_rootid[k], pos, 0);
|
|
|
|
// apply
|
|
mju_subFrom(d->cfrc_ext+6*k, tmp, 6);
|
|
}
|
|
|
|
// increment 3 rows of connect
|
|
i += 3;
|
|
|
|
break;
|
|
|
|
case mjEQ_WELD:
|
|
// tmp1 = world-oriented torque:force vector (efc is f:t, so swap)
|
|
mju_copy3(tmp1, d->efc_force + i + 3);
|
|
mju_copy3(tmp1 + 3, d->efc_force + i);
|
|
|
|
// body 1
|
|
if ((k = m->eq_obj1id[id])) {
|
|
// transform weld point on body1: local -> global
|
|
mju_rotVecMat(pos, eq_data, d->xmat+9*k);
|
|
mju_addTo3(pos, d->xpos+3*k);
|
|
|
|
// tmp = subtree CoM-based torque_force vector
|
|
mju_transformSpatial(tmp, tmp1, 1, d->subtree_com+3*m->body_rootid[k], pos, 0);
|
|
|
|
// apply (opposite for body 1)
|
|
mju_addTo(d->cfrc_ext+6*k, tmp, 6);
|
|
}
|
|
|
|
// body 2
|
|
if ((k = m->eq_obj2id[id])) {
|
|
// weld force on body2 is always applied at body root
|
|
mju_copy3(pos, d->xpos+3*k);
|
|
|
|
// tmp = subtree CoM-based torque_force vector
|
|
mju_transformSpatial(tmp, tmp1, 1, d->subtree_com+3*m->body_rootid[k], pos, 0);
|
|
|
|
// apply
|
|
mju_subFrom(d->cfrc_ext+6*k, tmp, 6);
|
|
}
|
|
|
|
// increment 6 rows of weld
|
|
i += 6;
|
|
break;
|
|
|
|
case mjEQ_JOINT:
|
|
case mjEQ_TENDON:
|
|
case mjEQ_DISTANCE:
|
|
// increment 1 row
|
|
i++;
|
|
break;
|
|
|
|
default:
|
|
mju_error_i("Unknown constraint type type %d", m->eq_type[id]); // SHOULD NOT OCCUR
|
|
}
|
|
}
|
|
|
|
// forward pass over bodies: compute cacc, cfrc_int
|
|
mju_zero(d->cfrc_int, 6);
|
|
for (int i=1; i<m->nbody; i++) {
|
|
// get body's first dof address
|
|
int bda = m->body_dofadr[i];
|
|
|
|
// cacc = cacc_parent + cdofdot * qvel + cdof * qacc
|
|
mju_mulDofVec(tmp, d->cdof_dot+6*bda, d->qvel+bda, m->body_dofnum[i]);
|
|
mju_add(d->cacc+6*i, d->cacc+6*m->body_parentid[i], tmp, 6);
|
|
mju_mulDofVec(tmp, d->cdof+6*bda, d->qacc+bda, m->body_dofnum[i]);
|
|
mju_addTo(d->cacc+6*i, tmp, 6);
|
|
|
|
// cfrc_body = cinert * cacc + cvel x (cinert * cvel)
|
|
mju_mulInertVec(cfrc_body, d->cinert+10*i, d->cacc+6*i);
|
|
mju_mulInertVec(tmp, d->cinert+10*i, d->cvel+6*i);
|
|
mju_crossForce(tmp1, d->cvel+6*i, tmp);
|
|
mju_addTo(cfrc_body, tmp1, 6);
|
|
|
|
// set cfrc_int = cfrc_body - cfrc_ext
|
|
mju_sub(d->cfrc_int+6*i, cfrc_body, d->cfrc_ext+6*i, 6);
|
|
}
|
|
|
|
// backward pass over bodies: accumulate cfrc_int from children
|
|
for (int i=m->nbody-1; i>0; i--) {
|
|
mju_addTo(d->cfrc_int+6*m->body_parentid[i], d->cfrc_int+6*i, 6);
|
|
}
|
|
}
|