df59e7d0f1
The coordinates for flex interpolation are now computed using the absolute values of the vertex weights. The sign of the first vertex weight is then applied to the resulting barycentric weights. This correctly handles cases where the flex is both the first and the second entity in the contact pair. PiperOrigin-RevId: 913590928 Change-Id: I970b35fba3d209e13b5b33bb5f945e3c6a43d487
3193 lines
95 KiB
C
3193 lines
95 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_constraint.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
|
|
#include <mujoco/mjdata.h>
|
|
#include <mujoco/mjmacro.h>
|
|
#include <mujoco/mjmodel.h>
|
|
#include <mujoco/mjsan.h> // IWYU pragma: keep
|
|
#include <mujoco/mjxmacro.h>
|
|
#include "engine/engine_init.h"
|
|
#include "engine/engine_core_util.h"
|
|
#include "engine/engine_core_smooth.h"
|
|
#include "engine/engine_memory.h"
|
|
#include "engine/engine_sleep.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"
|
|
|
|
#ifdef MEMORY_SANITIZER
|
|
#include <sanitizer/msan_interface.h>
|
|
#endif
|
|
|
|
#ifdef mjUSEPLATFORMSIMD
|
|
#if defined(__AVX__) && !defined(mjUSESINGLE)
|
|
#define mjUSEAVX
|
|
#endif // defined(__AVX__) && !defined(mjUSESINGLE)
|
|
#endif // mjUSEPLATFORMSIMD
|
|
|
|
|
|
//-------------------------- utility functions -----------------------------------------------------
|
|
|
|
|
|
// compute cell node Jacobians and combined chain for flex strain constraints
|
|
// npc: number of nodes per cell
|
|
// gindices: global indices of cell nodes in flex
|
|
// cell_node_jac: output array of size 3*npc*cell_nnz (allocated on stack)
|
|
// mj_{mark/free}Stack in calling function
|
|
static mjtNum* cell_pos_and_jac(const mjModel* m, mjData* d, int flex_id, int npc, const int* gindices,
|
|
int nv, const mjtNum* xpos_c, int* cell_chain, int* cell_nnz) {
|
|
int* nstart = m->flex_nodeadr + flex_id;
|
|
int* bodyid = m->flex_nodebodyid + *nstart;
|
|
|
|
// build per-cell sparse chain: union of bodyChain for npc nodes
|
|
*cell_nnz = 0;
|
|
int* dof_used = mjSTACKALLOC(d, nv, int);
|
|
int* temp_chain = mjSTACKALLOC(d, nv, int);
|
|
mju_zeroInt(dof_used, nv);
|
|
for (int n = 0; n < npc; n++) {
|
|
int temp_nnz = mj_bodyChain(m, bodyid[gindices[n]], temp_chain);
|
|
for (int k = 0; k < temp_nnz; k++) {
|
|
dof_used[temp_chain[k]] = 1;
|
|
}
|
|
}
|
|
for (int q = 0; q < nv; q++) {
|
|
if (dof_used[q]) {
|
|
cell_chain[(*cell_nnz)++] = q;
|
|
}
|
|
}
|
|
|
|
// build per-cell node Jacobians: 3*npc x cell_nnz
|
|
mjtNum* cell_node_jac = mjSTACKALLOC(d, 3*npc*(*cell_nnz), mjtNum);
|
|
mju_zero(cell_node_jac, 3*npc*(*cell_nnz));
|
|
int* chain_col = mjSTACKALLOC(d, nv, int);
|
|
mjtNum* blk_jac = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
for (int n = 0; n < npc; n++) {
|
|
int body = bodyid[gindices[n]];
|
|
int chain_n = mj_bodyChain(m, body, chain_col);
|
|
mju_zero(blk_jac, 3*chain_n);
|
|
mj_jacSparse(m, d, blk_jac, NULL, xpos_c + 3*n,
|
|
body, chain_n, chain_col, 0);
|
|
// map node's sparse chain into cell_chain indexing
|
|
for (int r = 0; r < 3; r++) {
|
|
for (int k = 0; k < chain_n; k++) {
|
|
// find chain_col[k] in cell_chain via linear scan (chain is short)
|
|
for (int cc = 0; cc < *cell_nnz; cc++) {
|
|
if (cell_chain[cc] == chain_col[k]) {
|
|
cell_node_jac[(3*n + r)*(*cell_nnz) + cc] = blk_jac[r*chain_n + k];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return cell_node_jac;
|
|
}
|
|
|
|
|
|
|
|
// compute strain Jacobian from strain derivative w.r.t. cell-local node positions
|
|
// dSdx_local: input array of size 3*npc (dStrain/dNodePosition for cell nodes)
|
|
// cell_node_jac: input array of size 3*npc*cell_nnz (sparse Jacobians)
|
|
// strain_jac: output array of size cell_nnz (dStrain/dq)
|
|
static void cell_strain_jacobian(int npc, int cell_nnz,
|
|
const mjtNum* dSdx_local,
|
|
const mjtNum* cell_node_jac,
|
|
mjtNum* strain_jac) {
|
|
mju_zero(strain_jac, cell_nnz);
|
|
for (int n = 0; n < npc; n++) {
|
|
for (int c = 0; c < 3; c++) {
|
|
mjtNum w = dSdx_local[3*n + c];
|
|
if (w == 0) continue;
|
|
int row = 3*n + c;
|
|
for (int k = 0; k < cell_nnz; k++) {
|
|
strain_jac[k] += w * cell_node_jac[row*cell_nnz + k];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// allocate efc arrays on arena, return 1 on success, 0 on failure
|
|
static int arenaAllocEfc(const mjModel* m, mjData* d) {
|
|
#undef MJ_M
|
|
#define MJ_M(n) m->n
|
|
#undef MJ_D
|
|
#define MJ_D(n) d->n
|
|
|
|
// move arena pointer to end of contact array
|
|
d->parena = d->ncon * sizeof(mjContact);
|
|
|
|
// poison remaining memory
|
|
#ifdef ADDRESS_SANITIZER
|
|
ASAN_POISON_MEMORY_REGION(
|
|
(char*)d->arena + d->parena, d->narena - d->pstack - d->parena);
|
|
#endif
|
|
|
|
#define X(type, name, nr, nc) \
|
|
d->name = mj_arenaAllocByte(d, sizeof(type) * (nr) * (nc), _Alignof(type)); \
|
|
if (!d->name) { \
|
|
mj_warning(d, mjWARN_CNSTRFULL, d->narena); \
|
|
mj_clearEfc(d); \
|
|
d->parena = d->ncon * sizeof(mjContact); \
|
|
return 0; \
|
|
}
|
|
|
|
MJDATA_ARENA_POINTERS_SOLVER
|
|
#undef X
|
|
|
|
#undef MJ_M
|
|
#define MJ_M(n) n
|
|
#undef MJ_D
|
|
#define MJ_D(n) n
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// determine type of solver
|
|
int mj_isDual(const mjModel* m) {
|
|
if (m->opt.solver == mjSOL_PGS || m->opt.noslip_iterations > 0) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
// assign/clamp contact friction parameters
|
|
void mj_assignFriction(const mjModel* m, mjtNum* target, const mjtNum* source) {
|
|
if (mjENABLED(mjENBL_OVERRIDE)) {
|
|
for (int i=0; i < 5; i++) {
|
|
target[i] = mju_max(mjMINMU, m->opt.o_friction[i]);
|
|
}
|
|
} else {
|
|
for (int i=0; i < 5; i++) {
|
|
target[i] = mju_max(mjMINMU, source[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// assign/override contact reference parameters
|
|
void mj_assignRef(const mjModel* m, mjtNum* target, const mjtNum* source) {
|
|
if (mjENABLED(mjENBL_OVERRIDE)) {
|
|
mju_copy(target, m->opt.o_solref, mjNREF);
|
|
} else {
|
|
mju_copy(target, source, mjNREF);
|
|
}
|
|
}
|
|
|
|
|
|
// assign/override contact impedance parameters
|
|
void mj_assignImp(const mjModel* m, mjtNum* target, const mjtNum* source) {
|
|
if (mjENABLED(mjENBL_OVERRIDE)) {
|
|
mju_copy(target, m->opt.o_solimp, mjNIMP);
|
|
} else {
|
|
mju_copy(target, source, mjNIMP);
|
|
}
|
|
}
|
|
|
|
|
|
// assign/override contact margin
|
|
mjtNum mj_assignMargin(const mjModel* m, mjtNum source) {
|
|
if (mjENABLED(mjENBL_OVERRIDE)) {
|
|
return m->opt.o_margin;
|
|
} else {
|
|
return source;
|
|
}
|
|
}
|
|
|
|
|
|
// compute element bodies and weights for given contact point, return #bodies
|
|
// if v is one of the element vertices, reduce element to fragment
|
|
static int mj_elemBodyWeight(const mjModel* m, const mjData* d, int f, int e, int v,
|
|
const mjtNum point[3], int* body, mjtNum* weight) {
|
|
// get flex info
|
|
int dim = m->flex_dim[f];
|
|
const int* edata = m->flex_elem + m->flex_elemdataadr[f] + e*(dim+1);
|
|
const mjtNum* vert = d->flexvert_xpos + 3*m->flex_vertadr[f];
|
|
|
|
// compute inverse distances from contact point to element vertices
|
|
// save body ids, find vertex v in element
|
|
int vid = -1;
|
|
for (int i=0; i <= dim; i++) {
|
|
mjtNum dist = mju_dist3(point, vert+3*edata[i]);
|
|
weight[i] = 1.0/(mju_max(mjMINVAL, dist));
|
|
body[i] = m->flex_vertadr[f] + edata[i];
|
|
|
|
// check if element vertex matches v
|
|
if (edata[i] == v) {
|
|
vid = i;
|
|
}
|
|
}
|
|
|
|
// v found in e: skip and shift remaining
|
|
if (vid >= 0) {
|
|
while (vid < dim) {
|
|
weight[vid] = weight[vid+1];
|
|
body[vid] = body[vid+1];
|
|
vid++;
|
|
}
|
|
dim--;
|
|
}
|
|
|
|
// normalize weights
|
|
mjtNum sum = mju_sum(weight, dim+1);
|
|
if (sum < mjMINVAL) {
|
|
mjERROR("element body weight sum < mjMINVAL");
|
|
}
|
|
mju_scl(weight, weight, 1.0/sum, dim+1);
|
|
return dim+1;
|
|
}
|
|
|
|
|
|
// compute body weights for a given contact vertex, return #bodies
|
|
static int mj_vertBodyWeight(const mjModel* m, const mjData* d, int f, int* v,
|
|
int* body, mjtNum* bweight, const mjtNum* vweight, int nw) {
|
|
if (nw == 0) {
|
|
return 0;
|
|
}
|
|
|
|
// determine sign: vweight may be negative for side-0 of a contact pair
|
|
mjtNum sign = vweight[0] < 0 ? -1 : 1;
|
|
|
|
// compute parametric coordinates using absolute weights
|
|
mjtNum coord[3] = {0, 0, 0};
|
|
for (int i = 0; i < nw; i++) {
|
|
mju_addToScl3(coord, m->flex_vert0 + 3*v[i], mju_abs(vweight[i]));
|
|
}
|
|
|
|
int order = m->flex_interp[f];
|
|
order = order < 0 ? -order : order;
|
|
int npc = (order+1)*(order+1)*(order+1); // number of nodes per cell
|
|
|
|
// cell lookup: get local coords and node indices
|
|
mjtNum local[3];
|
|
int nodeindices[27]; // max npc for quadratic: 3^3 = 27
|
|
mju_cellLookup(coord, m->flex_cellnum+3*f, order, local, nodeindices);
|
|
|
|
// evaluate basis functions for this cell's local nodes
|
|
int nstart = m->flex_nodeadr[f];
|
|
int nb = 0;
|
|
|
|
for (int j = 0; j < npc; j++) {
|
|
mjtNum w = mju_evalBasis(local, j, order);
|
|
if (w < 1e-5) {
|
|
continue;
|
|
}
|
|
if (bweight) bweight[nb] = sign * w;
|
|
body[nb++] = m->flex_nodebodyid[nstart + nodeindices[j]];
|
|
}
|
|
|
|
return nb;
|
|
}
|
|
|
|
|
|
// add contact to d->contact list; return 0 if success; 1 if buffer full
|
|
int mj_addContact(const mjModel* m, mjData* d, const mjContact* con) {
|
|
// move arena pointer back to the end of the existing contact array and invalidate efc_ arrays
|
|
d->parena = d->ncon * sizeof(mjContact);
|
|
#ifdef ADDRESS_SANITIZER
|
|
ASAN_POISON_MEMORY_REGION(
|
|
(char*)d->arena + d->parena, d->narena - d->pstack - d->parena);
|
|
#endif
|
|
mj_clearEfc(d);
|
|
|
|
// copy contact
|
|
mjContact* dst = mj_arenaAllocByte(d, sizeof(mjContact), _Alignof(mjContact));
|
|
if (!dst) {
|
|
mj_warning(d, mjWARN_CONTACTFULL, d->ncon);
|
|
return 1;
|
|
}
|
|
*dst = *con;
|
|
|
|
// increase counter, return success
|
|
d->ncon++;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// add #size rows to constraint Jacobian; set pos, margin, frictionloss, type, id
|
|
static void mj_addConstraint(const mjModel* m, mjData* d,
|
|
const mjtNum* jac, const mjtNum* pos,
|
|
const mjtNum* margin, mjtNum frictionloss,
|
|
int size, int type, int id, int NV, const int* chain) {
|
|
int empty, nv = m->nv, nefc = d->nefc;
|
|
int *nnz = d->efc_J_rownnz, *adr = d->efc_J_rowadr, *ind = d->efc_J_colind;
|
|
mjtNum *J = d->efc_J;
|
|
|
|
// init empty guard for constraints other than contact
|
|
if (type == mjCNSTR_CONTACT_FRICTIONLESS ||
|
|
type == mjCNSTR_CONTACT_PYRAMIDAL ||
|
|
type == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
empty = 0;
|
|
} else {
|
|
empty = 1;
|
|
}
|
|
|
|
// dense: copy entire Jacobian
|
|
if (!mj_isSparse(m)) {
|
|
// make sure jac is not empty
|
|
if (empty) {
|
|
for (int i=0; i < size*nv; i++) {
|
|
if (jac[i]) {
|
|
empty = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy if not empty
|
|
if (!empty) {
|
|
mju_copy(J + nefc*nv, jac, size*nv);
|
|
}
|
|
}
|
|
|
|
// sparse: copy chain
|
|
else {
|
|
// clamp NV (in case -1 was used in constraint construction)
|
|
NV = mjMAX(0, NV);
|
|
|
|
if (NV) {
|
|
empty = 0;
|
|
} else if (empty) {
|
|
// all rows are empty, return early
|
|
return;
|
|
}
|
|
|
|
// chain required in sparse mode
|
|
if (NV && !chain) {
|
|
mjERROR("called with dense arguments");
|
|
}
|
|
|
|
// process size elements
|
|
for (int i=0; i < size; i++) {
|
|
// set row address
|
|
adr[nefc+i] = (nefc+i ? adr[nefc+i-1]+nnz[nefc+i-1] : 0);
|
|
|
|
// set row descriptor
|
|
nnz[nefc+i] = NV;
|
|
|
|
// copy if not empty
|
|
if (NV) {
|
|
mju_copyInt(ind + adr[nefc+i], chain, NV);
|
|
mju_copy(J + adr[nefc+i], jac + i*NV, NV);
|
|
}
|
|
}
|
|
|
|
// set J row supernodes; 1: next row has same pattern, 0: different pattern
|
|
|
|
// cross-boundary: does previous row have same pattern?
|
|
if (nefc > 0 && NV == nnz[nefc-1] &&
|
|
(NV == 0 || mju_compare(ind + adr[nefc], ind + adr[nefc-1], NV))) {
|
|
d->efc_J_rowsuper[nefc-1] = 1;
|
|
}
|
|
|
|
// within-constraint: consecutive rows always share same pattern
|
|
mju_fillInt(d->efc_J_rowsuper + nefc, 1, size-1);
|
|
d->efc_J_rowsuper[nefc+size-1] = 0;
|
|
}
|
|
|
|
// all rows empty: skip constraint
|
|
if (empty) {
|
|
return;
|
|
}
|
|
|
|
// set constraint pos, margin, frictionloss, type, id
|
|
for (int i=0; i < size; i++) {
|
|
d->efc_pos[nefc+i] = (pos ? pos[i] : 0);
|
|
d->efc_margin[nefc+i] = (margin ? margin[i] : 0);
|
|
d->efc_frictionloss[nefc+i] = frictionloss;
|
|
d->efc_type[nefc+i] = type;
|
|
d->efc_id[nefc+i] = id;
|
|
}
|
|
|
|
// increase counters
|
|
d->nefc += size;
|
|
if (type == mjCNSTR_EQUALITY) {
|
|
d->ne += size;
|
|
} else if (type == mjCNSTR_FRICTION_DOF || type == mjCNSTR_FRICTION_TENDON) {
|
|
d->nf += size;
|
|
} else if (type == mjCNSTR_LIMIT_JOINT || type == mjCNSTR_LIMIT_TENDON) {
|
|
d->nl += size;
|
|
}
|
|
}
|
|
|
|
|
|
// multiply Jacobian by vector
|
|
void mj_mulJacVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec) {
|
|
// exit if no constraints
|
|
if (!d->nefc) {
|
|
return;
|
|
}
|
|
|
|
// sparse Jacobian
|
|
if (mj_isSparse(m))
|
|
mju_mulMatVecSparse(res, d->efc_J, vec, d->nefc,
|
|
d->efc_J_rownnz, d->efc_J_rowadr,
|
|
d->efc_J_colind, d->efc_J_rowsuper);
|
|
|
|
// dense Jacobian
|
|
else {
|
|
mju_mulMatVec(res, d->efc_J, vec, d->nefc, m->nv);
|
|
}
|
|
}
|
|
|
|
|
|
// multiply JacobianT by vector
|
|
void mj_mulJacTVec(const mjModel* m, const mjData* d, mjtNum* res, const mjtNum* vec) {
|
|
// exit if no constraints
|
|
if (!d->nefc) {
|
|
return;
|
|
}
|
|
|
|
// sparse Jacobian
|
|
if (mj_isSparse(m)) {
|
|
mju_mulMatTVecSparse(res, d->efc_J, vec, d->nefc, m->nv,
|
|
d->efc_J_rownnz, d->efc_J_rowadr, d->efc_J_colind);
|
|
}
|
|
|
|
// dense Jacobian
|
|
else {
|
|
mju_mulMatTVec(res, d->efc_J, vec, d->nefc, m->nv);
|
|
}
|
|
}
|
|
|
|
|
|
// compute global anchor points for connect/weld equality constraints
|
|
static void mj_equalityAnchors(const mjModel* m, const mjData* d, int eq_id,
|
|
mjtNum pos1[3], mjtNum pos2[3],
|
|
int* body1, int* body2) {
|
|
mjtEq type = (mjtEq) m->eq_type[eq_id];
|
|
int obj1 = m->eq_obj1id[eq_id];
|
|
int obj2 = m->eq_obj2id[eq_id];
|
|
|
|
if (m->eq_objtype[eq_id] == mjOBJ_BODY) {
|
|
const mjtNum* data = m->eq_data + mjNEQDATA*eq_id;
|
|
if (type == mjEQ_CONNECT) {
|
|
mju_mulMatVec3(pos1, d->xmat + 9*obj1, data);
|
|
mju_addTo3(pos1, d->xpos + 3*obj1);
|
|
mju_mulMatVec3(pos2, d->xmat + 9*obj2, data + 3);
|
|
mju_addTo3(pos2, d->xpos + 3*obj2);
|
|
} else {
|
|
// weld uses data+3*(1-j) for anchor
|
|
mju_mulMatVec3(pos1, d->xmat + 9*obj1, data + 3);
|
|
mju_addTo3(pos1, d->xpos + 3*obj1);
|
|
mju_mulMatVec3(pos2, d->xmat + 9*obj2, data);
|
|
mju_addTo3(pos2, d->xpos + 3*obj2);
|
|
}
|
|
*body1 = obj1;
|
|
*body2 = obj2;
|
|
} else {
|
|
mju_copy3(pos1, d->site_xpos + 3*obj1);
|
|
mju_copy3(pos2, d->site_xpos + 3*obj2);
|
|
*body1 = m->site_bodyid[obj1];
|
|
*body2 = m->site_bodyid[obj2];
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------- instantiate constraints by type --------------------------------------------
|
|
|
|
// equality constraints
|
|
void mj_instantiateEquality(const mjModel* m, mjData* d) {
|
|
int issparse = mj_isSparse(m), nv = m->nv;
|
|
int id[2], size, NV, NV2, *chain = NULL, *chain2 = NULL;
|
|
int flex_edgeadr, flex_edgenum;
|
|
int flex_vertadr, flex_vertnum;
|
|
mjtNum cpos[6], pos[2][3], ref[2], dif, deriv;
|
|
mjtNum quat[4], quat1[4], quat2[4], quat3[4], axis[3];
|
|
mjtNum *jac[2], *jacdif, *data;
|
|
|
|
// disabled or no equality constraints: return
|
|
if (mjDISABLED(mjDSBL_EQUALITY) || m->nemax == 0) {
|
|
return;
|
|
}
|
|
|
|
// sleep filtering
|
|
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->ntree_awake < m->ntree;
|
|
|
|
mj_markStack(d);
|
|
|
|
// allocate space
|
|
jac[0] = mjSTACKALLOC(d, 6*nv, mjtNum);
|
|
jac[1] = mjSTACKALLOC(d, 6*nv, mjtNum);
|
|
jacdif = mjSTACKALLOC(d, 6*nv, mjtNum);
|
|
if (issparse) {
|
|
chain = mjSTACKALLOC(d, nv, int);
|
|
chain2 = mjSTACKALLOC(d, nv, int);
|
|
}
|
|
|
|
// find active equality constraints
|
|
for (int i=0; i < m->neq; i++) {
|
|
// skip inactive
|
|
if (!d->eq_active[i]) {
|
|
continue;
|
|
}
|
|
|
|
// skip sleeping
|
|
if (sleep_filter && mj_sleepState(m, d, mjOBJ_EQUALITY, i) == mjS_ASLEEP) {
|
|
continue;
|
|
}
|
|
|
|
// get constraint data
|
|
data = m->eq_data + mjNEQDATA*i;
|
|
id[0] = m->eq_obj1id[i];
|
|
id[1] = m->eq_obj2id[i];
|
|
size = 0;
|
|
NV = 0;
|
|
NV2 = 0;
|
|
int body_id[2];
|
|
|
|
// process according to type
|
|
switch ((mjtEq) m->eq_type[i]) {
|
|
case mjEQ_CONNECT: // connect bodies with ball joint
|
|
// find global points, body semantic
|
|
mj_equalityAnchors(m, d, i, pos[0], pos[1], body_id, body_id + 1);
|
|
|
|
// compute position error
|
|
mju_sub3(cpos, pos[0], pos[1]);
|
|
|
|
// compute Jacobian difference (opposite of contact: 0 - 1)
|
|
NV = mj_jacDifPair(m, d, chain, body_id[1], body_id[0], pos[1], pos[0],
|
|
jac[1], jac[0], jacdif, NULL, NULL, NULL, issparse,
|
|
/*flg_skipcommon=*/0);
|
|
|
|
// copy difference into jac[0]
|
|
mju_copy(jac[0], jacdif, 3*NV);
|
|
|
|
size = 3;
|
|
break;
|
|
|
|
case mjEQ_WELD: // fix relative position and orientation
|
|
// find global points, body semantic
|
|
mj_equalityAnchors(m, d, i, pos[0], pos[1], body_id, body_id + 1);
|
|
|
|
// compute position error
|
|
mju_sub3(cpos, pos[0], pos[1]);
|
|
|
|
// get torquescale coefficient
|
|
mjtNum torquescale = data[10];
|
|
|
|
// compute error Jacobian (opposite of contact: 0 - 1)
|
|
NV = mj_jacDifPair(m, d, chain, body_id[1], body_id[0], pos[1], pos[0],
|
|
jac[1], jac[0], jacdif,
|
|
jac[1]+3*nv, jac[0]+3*nv, jacdif+3*nv, issparse,
|
|
/*flg_skipcommon=*/0);
|
|
|
|
// copy difference into jac[0], compress translation:rotation if sparse
|
|
mju_copy(jac[0], jacdif, 3*NV);
|
|
mju_copy(jac[0]+3*NV, jacdif+3*nv, 3*NV);
|
|
|
|
// orientation, body semantic
|
|
if (m->eq_objtype[i] == mjOBJ_BODY) {
|
|
// compute orientation error: neg(q1) * q0 * relpose (axis components only)
|
|
mjtNum* relpose = data+6;
|
|
mju_mulQuat(quat, d->xquat+4*id[0], relpose); // quat = q0*relpose
|
|
mju_negQuat(quat1, d->xquat+4*id[1]); // quat1 = neg(q1)
|
|
}
|
|
|
|
// orientation, site semantic
|
|
else {
|
|
mjtNum quat_site1[4];
|
|
mju_mulQuat(quat, d->xquat+4*body_id[0], m->site_quat+4*id[0]);
|
|
mju_mulQuat(quat_site1, d->xquat+4*body_id[1], m->site_quat+4*id[1]);
|
|
mju_negQuat(quat1, quat_site1);
|
|
}
|
|
|
|
mju_mulQuat(quat2, quat1, quat);
|
|
mju_scl3(cpos+3, quat2+1, torquescale); // scale axis components by torquescale
|
|
|
|
// correct rotation Jacobian: 0.5 * neg(q1) * (jac0-jac1) * q0 * relpose
|
|
for (int j=0; j < NV; j++) {
|
|
// axis = [jac0-jac1]_col(j)
|
|
axis[0] = jac[0][3*NV+j];
|
|
axis[1] = jac[0][4*NV+j];
|
|
axis[2] = jac[0][5*NV+j];
|
|
|
|
// apply formula
|
|
mju_mulQuatAxis(quat2, quat1, axis); // quat2 = neg(q1)*(jac0-jac1)
|
|
mju_mulQuat(quat3, quat2, quat); // quat3 = neg(q1)*(jac0-jac1)*q0*relpose
|
|
|
|
// correct Jacobian
|
|
jac[0][3*NV+j] = 0.5*quat3[1];
|
|
jac[0][4*NV+j] = 0.5*quat3[2];
|
|
jac[0][5*NV+j] = 0.5*quat3[3];
|
|
}
|
|
|
|
// scale rotational jacobian by torquescale
|
|
mju_scl(jac[0]+3*NV, jac[0]+3*NV, torquescale, 3*NV);
|
|
|
|
size = 6;
|
|
break;
|
|
|
|
case mjEQ_JOINT: // couple joint values with cubic
|
|
case mjEQ_TENDON: // couple tendon lengths with cubic
|
|
// get scalar positions and their Jacobians
|
|
for (int j=0; j < 1+(id[1] >= 0); j++) {
|
|
if (m->eq_type[i] == mjEQ_JOINT) { // joint object
|
|
pos[j][0] = d->qpos[m->jnt_qposadr[id[j]]];
|
|
ref[j] = m->qpos0[m->jnt_qposadr[id[j]]];
|
|
|
|
// make Jacobian: sparse or dense
|
|
if (issparse) {
|
|
// add first or second joint
|
|
if (j == 0) {
|
|
NV = 1;
|
|
chain[0] = m->jnt_dofadr[id[j]];
|
|
jac[j][0] = 1;
|
|
} else {
|
|
NV2 = 1;
|
|
chain2[0] = m->jnt_dofadr[id[j]];
|
|
jac[j][0] = 1;
|
|
}
|
|
} else {
|
|
mju_zero(jac[j], nv);
|
|
jac[j][m->jnt_dofadr[id[j]]] = 1;
|
|
}
|
|
} else { // tendon object
|
|
pos[j][0] = d->ten_length[id[j]];
|
|
ref[j] = m->tendon_length0[id[j]];
|
|
|
|
// set tendon_efcadr
|
|
if (d->tendon_efcadr[id[j]] == -1) {
|
|
d->tendon_efcadr[id[j]] = i;
|
|
}
|
|
|
|
// copy Jacobian: sparse or dense
|
|
if (issparse) {
|
|
if (j == 0) {
|
|
NV = m->ten_J_rownnz[id[j]];
|
|
mju_copyInt(chain, m->ten_J_colind+m->ten_J_rowadr[id[j]], NV);
|
|
mju_copy(jac[j], d->ten_J+m->ten_J_rowadr[id[j]], NV);
|
|
} else {
|
|
NV2 = m->ten_J_rownnz[id[j]];
|
|
mju_copyInt(chain2, m->ten_J_colind+m->ten_J_rowadr[id[j]], NV2);
|
|
mju_copy(jac[j], d->ten_J+m->ten_J_rowadr[id[j]], NV2);
|
|
}
|
|
} else {
|
|
mju_sparse2dense(jac[j], d->ten_J, 1, nv, m->ten_J_rownnz+id[j], m->ten_J_rowadr+id[j], m->ten_J_colind);
|
|
}
|
|
}
|
|
}
|
|
|
|
// both objects defined
|
|
if (id[1] >= 0) {
|
|
// compute position error
|
|
dif = pos[1][0] - ref[1];
|
|
cpos[0] = pos[0][0] - ref[0] - data[0] -
|
|
(data[1]*dif + data[2]*dif*dif + data[3]*dif*dif*dif + data[4]*dif*dif*dif*dif);
|
|
|
|
// compute derivative
|
|
deriv = data[1] + 2*data[2]*dif + 3*data[3]*dif*dif + 4*data[4]*dif*dif*dif;
|
|
|
|
// compute Jacobian: sparse or dense
|
|
if (issparse) {
|
|
NV = mju_combineSparse(jac[0], jac[1], 1, -deriv, NV, NV2, chain, chain2);
|
|
} else {
|
|
mju_addToScl(jac[0], jac[1], -deriv, nv);
|
|
}
|
|
}
|
|
|
|
// only one object defined
|
|
else {
|
|
// compute position error
|
|
cpos[0] = pos[0][0] - ref[0] - data[0];
|
|
|
|
// jac[0] already has the correct Jacobian
|
|
}
|
|
|
|
size = 1;
|
|
break;
|
|
|
|
case mjEQ_FLEXSTRAIN: {
|
|
// each constraint represents a single element (3D cell or 2D face)
|
|
int f = id[0];
|
|
int nodenum = m->flex_nodenum[f];
|
|
int interp = m->flex_interp[f];
|
|
int order = interp < 0 ? -interp : interp;
|
|
int shell_mode = (interp < 0);
|
|
|
|
// skip if not interpolated (order == 0 or no nodes)
|
|
if (!order || !nodenum) {
|
|
break;
|
|
}
|
|
|
|
// only order 1 (trilinear) and 2 (quadratic) are supported
|
|
if (order > 2) {
|
|
mjERROR("flex strain constraints only support order 1 and 2, got %d", order);
|
|
}
|
|
|
|
int cx = m->flex_cellnum[3*f+0];
|
|
int cy = m->flex_cellnum[3*f+1];
|
|
int cz = m->flex_cellnum[3*f+2];
|
|
int nstart = m->flex_nodeadr[f];
|
|
int* bodyid = m->flex_nodebodyid + nstart;
|
|
|
|
// nodes per element and element index
|
|
int npe;
|
|
int elem_idx;
|
|
if (shell_mode) {
|
|
npe = (order+1) * (order+1);
|
|
elem_idx = (int)data[0]; // face element index
|
|
} else {
|
|
npe = (order+1) * (order+1) * (order+1);
|
|
int ci = (int)data[0];
|
|
int cj = (int)data[1];
|
|
int ck = (int)data[2];
|
|
elem_idx = ci * cy * cz + cj * cz + ck;
|
|
}
|
|
|
|
mj_markStack(d);
|
|
|
|
// get element node indices
|
|
int gindices[125]; // max npc = 125 for quadratic
|
|
if (shell_mode) {
|
|
mju_flexGatherFaceState(order, cx, cy, cz, elem_idx,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, gindices, NULL);
|
|
} else {
|
|
int ci = (int)data[0], cj = (int)data[1], ck = (int)data[2];
|
|
mju_flexGatherCellState(order, cy, cz, ci, cj, ck,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, gindices, NULL);
|
|
}
|
|
|
|
// compute positions only for element nodes (npe << nodenum)
|
|
mjtNum* xpos_e = mjSTACKALLOC(d, 3*npe, mjtNum);
|
|
mjtNum* refpos_e = mjSTACKALLOC(d, 3*npe, mjtNum);
|
|
for (int n = 0; n < npe; n++) {
|
|
int gn = gindices[n];
|
|
if (m->flex_centered[f] ||
|
|
(m->flex_node[3*(gn + nstart)+0] == 0 &&
|
|
m->flex_node[3*(gn + nstart)+1] == 0 &&
|
|
m->flex_node[3*(gn + nstart)+2] == 0)) {
|
|
mju_copy3(xpos_e + 3*n, d->xpos + 3*bodyid[gn]);
|
|
} else {
|
|
mju_mulMatVec3(xpos_e + 3*n, d->xmat + 9*bodyid[gn], m->flex_node + 3*(gn + nstart));
|
|
mju_addTo3(xpos_e + 3*n, d->xpos + 3*bodyid[gn]);
|
|
}
|
|
mju_copy3(refpos_e + 3*n, m->flex_node0 + 3*(gn + nstart));
|
|
}
|
|
|
|
// compute corotational quaternion
|
|
mjtNum elem_quat[4] = {1, 0, 0, 0};
|
|
if (shell_mode) {
|
|
// determine face normal axis from elem_idx
|
|
int face_sizes[6] = {cy*cz, cy*cz, cx*cz, cx*cz, cx*cy, cx*cy};
|
|
int face_normals[6] = {0, 0, 1, 1, 2, 2};
|
|
int cumul = 0, normal_axis = 0;
|
|
for (int ff = 0; ff < 6; ff++) {
|
|
if (elem_idx < cumul + face_sizes[ff]) {
|
|
normal_axis = face_normals[ff];
|
|
break;
|
|
}
|
|
cumul += face_sizes[ff];
|
|
}
|
|
int na0 = (normal_axis + 1) % 3;
|
|
int na1 = (normal_axis + 2) % 3;
|
|
|
|
// compute corotational rotation from 2D deformation gradient at face center
|
|
mjtNum p[2] = {.5, .5};
|
|
mju_flexInterpRotation2D(order, xpos_e, npe, na0, na1, normal_axis, p, elem_quat);
|
|
} else {
|
|
mjtNum center[3] = {0.5, 0.5, 0.5};
|
|
mjtNum mat[9];
|
|
mju_defGradient(mat, center, xpos_e, order);
|
|
mju_mat2Rot(elem_quat, mat);
|
|
mju_negQuat(elem_quat, elem_quat);
|
|
}
|
|
|
|
// build per-element sparse chain and node Jacobians
|
|
int* elem_chain = mjSTACKALLOC(d, nv, int);
|
|
int elem_nnz = 0;
|
|
mjtNum* elem_node_jac = cell_pos_and_jac(m, d, f, npe, gindices, nv, xpos_e, elem_chain,
|
|
&elem_nnz);
|
|
|
|
|
|
mjtNum* strain_jac = mjSTACKALLOC(d, elem_nnz, mjtNum);
|
|
mjtNum* dSdx_local = mjSTACKALLOC(d, 3*npe, mjtNum);
|
|
|
|
// for dense mode: allocate and zero a dense Jacobian buffer once
|
|
mjtNum* dense_jac = NULL;
|
|
if (!issparse) {
|
|
dense_jac = mjSTACKALLOC(d, nv, mjtNum);
|
|
mju_zero(dense_jac, nv);
|
|
}
|
|
|
|
// read eigenmode data from flex_stiffness
|
|
int ndof_elem = 3 * npe;
|
|
int stiffnessadr = m->flex_stiffnessadr[f];
|
|
int neig = 0;
|
|
const mjtNum* k_elem = NULL;
|
|
if (stiffnessadr >= 0) {
|
|
k_elem = m->flex_stiffness + stiffnessadr
|
|
+ elem_idx * ndof_elem * ndof_elem;
|
|
neig = (int)k_elem[0];
|
|
}
|
|
|
|
// compute displacement in corotational frame
|
|
mjtNum* displ_e = mjSTACKALLOC(d, ndof_elem, mjtNum);
|
|
for (int n = 0; n < npe; n++) {
|
|
// rotate xpos_e to corotational frame
|
|
mjtNum xrot[3];
|
|
mju_rotVecQuat(xrot, xpos_e + 3*n, elem_quat);
|
|
displ_e[3*n + 0] = xrot[0] - refpos_e[3*n + 0];
|
|
displ_e[3*n + 1] = xrot[1] - refpos_e[3*n + 1];
|
|
displ_e[3*n + 2] = xrot[2] - refpos_e[3*n + 2];
|
|
}
|
|
|
|
// compute inverse quaternion for rotating eigenvectors to world frame
|
|
mjtNum elem_quat_inv[4];
|
|
mju_negQuat(elem_quat_inv, elem_quat);
|
|
|
|
// loop over eigenmodes
|
|
for (int eig = 0; eig < neig; eig++) {
|
|
const mjtNum* eigvec = k_elem + 1 + eig * ndof_elem;
|
|
|
|
// constraint residual: dot product of scaled eigenvector with displacement
|
|
mjtNum residual = 0;
|
|
for (int j = 0; j < ndof_elem; j++) {
|
|
residual += eigvec[j] * displ_e[j];
|
|
}
|
|
cpos[0] = residual;
|
|
|
|
// rotate eigenvector to world frame for Jacobian
|
|
for (int n = 0; n < npe; n++) {
|
|
mju_rotVecQuat(dSdx_local + 3*n, eigvec + 3*n, elem_quat_inv);
|
|
}
|
|
|
|
// contract with elem_node_jac to get sparse Jacobian
|
|
cell_strain_jacobian(npe, elem_nnz, dSdx_local, elem_node_jac, strain_jac);
|
|
|
|
if (issparse) {
|
|
mj_addConstraint(m, d, strain_jac, cpos, 0, 0, 1, mjCNSTR_EQUALITY, i,
|
|
elem_nnz, elem_chain);
|
|
} else {
|
|
for (int k = 0; k < elem_nnz; k++) {
|
|
dense_jac[elem_chain[k]] = strain_jac[k];
|
|
}
|
|
mj_addConstraint(m, d, dense_jac, cpos, 0, 0, 1, mjCNSTR_EQUALITY, i, 0, NULL);
|
|
for (int k = 0; k < elem_nnz; k++) {
|
|
dense_jac[elem_chain[k]] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
break;
|
|
}
|
|
|
|
case mjEQ_FLEX:
|
|
// edge constraint mode: add one constraint per non-rigid edge
|
|
flex_edgeadr = m->flex_edgeadr[id[0]];
|
|
flex_edgenum = m->flex_edgenum[id[0]];
|
|
for (int e=flex_edgeadr; e < flex_edgeadr+flex_edgenum; e++) {
|
|
// skip rigid
|
|
if (m->flexedge_rigid[e]) {
|
|
continue;
|
|
}
|
|
|
|
// position error
|
|
cpos[0] = d->flexedge_length[e] - m->flexedge_length0[e];
|
|
|
|
// add constraint: sparse or dense
|
|
if (issparse) {
|
|
mj_addConstraint(m, d, d->flexedge_J+m->flexedge_J_rowadr[e], cpos, 0, 0,
|
|
1, mjCNSTR_EQUALITY, i,
|
|
m->flexedge_J_rownnz[e],
|
|
m->flexedge_J_colind+m->flexedge_J_rowadr[e]);
|
|
} else {
|
|
mju_zero(jac[0], nv); // reuse first row of jac[0]
|
|
int rowadr = m->flexedge_J_rowadr[e];
|
|
int rownnz = m->flexedge_J_rownnz[e];
|
|
for (int k=0; k<rownnz; k++) {
|
|
jac[0][m->flexedge_J_colind[rowadr+k]] = d->flexedge_J[rowadr+k];
|
|
}
|
|
mj_addConstraint(m, d, jac[0], cpos, 0, 0, 1, mjCNSTR_EQUALITY, i, 0, NULL);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case mjEQ_FLEXVERT:
|
|
// add two constraints per vertex
|
|
flex_vertadr = m->flex_vertadr[id[0]];
|
|
flex_vertnum = m->flex_vertnum[id[0]];
|
|
for (int v=flex_vertadr; v < flex_vertadr+flex_vertnum; v++) {
|
|
for (int j=0; j < 2; j++) {
|
|
cpos[0] = d->flexvert_length[2*v+j];
|
|
int row = 2*v+j;
|
|
if (issparse) {
|
|
mj_addConstraint(m, d, d->flexvert_J + m->flexvert_J_rowadr[row],
|
|
cpos, 0, 0, 1, mjCNSTR_EQUALITY, i,
|
|
m->flexvert_J_rownnz[row],
|
|
m->flexvert_J_colind + m->flexvert_J_rowadr[row]);
|
|
} else {
|
|
mju_zero(jac[0], nv); // reuse first row of jac[0]
|
|
int rowadr = m->flexvert_J_rowadr[row];
|
|
int rownnz = m->flexvert_J_rownnz[row];
|
|
for (int k=0; k<rownnz; k++) {
|
|
jac[0][m->flexvert_J_colind[rowadr+k]] = d->flexvert_J[rowadr+k];
|
|
}
|
|
mj_addConstraint(m, d, jac[0], cpos, 0, 0, 1, mjCNSTR_EQUALITY, i, 0, NULL);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: // SHOULD NOT OCCUR
|
|
mjERROR("invalid equality constraint type %d", m->eq_type[i]);
|
|
}
|
|
|
|
// add constraint
|
|
if (size) {
|
|
mj_addConstraint(m, d, jac[0], cpos, 0, 0,
|
|
size, mjCNSTR_EQUALITY, i,
|
|
issparse ? NV : 0,
|
|
issparse ? chain : NULL);
|
|
}
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
// subtract Jdot*v correction from result vector for equality constraints
|
|
void mj_Jdotv(const mjModel* m, mjData* d, mjtNum* result) {
|
|
int nv = m->nv, ne = d->ne;
|
|
|
|
// nothing to do
|
|
if (!ne || !nv) {
|
|
return;
|
|
}
|
|
|
|
int issparse = mj_isSparse(m);
|
|
|
|
mj_markStack(d);
|
|
|
|
// allocate scratch for jacDot matrices (translational and rotational)
|
|
int* chain = issparse ? mjSTACKALLOC(d, nv, int) : NULL;
|
|
mjtNum* jacdot1 = NULL;
|
|
mjtNum* jacdot2 = NULL;
|
|
mjtNum* jacrdot1 = NULL;
|
|
mjtNum* jacrdot2 = NULL;
|
|
|
|
// iterate over equality constraint efc rows
|
|
int row = 0;
|
|
while (row < ne) {
|
|
int eq_id = d->efc_id[row];
|
|
mjtEq type = (mjtEq) m->eq_type[eq_id];
|
|
|
|
// connect or weld: compute Jdot*v for translational part
|
|
if (type == mjEQ_CONNECT || type == mjEQ_WELD) {
|
|
mjtNum* data = m->eq_data + mjNEQDATA*eq_id;
|
|
|
|
// allocate translational scratch on first connect or weld
|
|
if (!jacdot1) {
|
|
jacdot1 = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
jacdot2 = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
}
|
|
|
|
// allocate rotational scratch on first weld
|
|
if (type == mjEQ_WELD && !jacrdot1) {
|
|
jacrdot1 = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
jacrdot2 = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
}
|
|
|
|
// compute global anchor points and body ids
|
|
int obj1 = m->eq_obj1id[eq_id];
|
|
int obj2 = m->eq_obj2id[eq_id];
|
|
mjtNum pos1[3], pos2[3];
|
|
int body1, body2;
|
|
mj_equalityAnchors(m, d, eq_id, pos1, pos2, &body1, &body2);
|
|
|
|
// compute jacDot*v for each body point
|
|
mjtNum jdv1[3], jdv2[3];
|
|
mjtNum jrdv1[3] = {0}, jrdv2[3] = {0};
|
|
if (issparse) {
|
|
// get merged chain for the two bodies
|
|
int NV = mj_mergeChain(m, chain, body1, body2, /*flg_skipcommon=*/0);
|
|
|
|
if (NV) {
|
|
// sparse: translational and rotational
|
|
mjtNum* jacr1 = (type == mjEQ_WELD) ? jacrdot1 : NULL;
|
|
mjtNum* jacr2 = (type == mjEQ_WELD) ? jacrdot2 : NULL;
|
|
mj_jacDotSparse(m, d, jacdot1, jacr1, pos1, body1, NV, chain);
|
|
mj_jacDotSparse(m, d, jacdot2, jacr2, pos2, body2, NV, chain);
|
|
|
|
// translational jdv = jacDot * qvel
|
|
mju_dotSparseX3(jdv1, jdv1+1, jdv1+2, jacdot1, jacdot1+NV, jacdot1+2*NV,
|
|
d->qvel, NV, chain);
|
|
mju_dotSparseX3(jdv2, jdv2+1, jdv2+2, jacdot2, jacdot2+NV, jacdot2+2*NV,
|
|
d->qvel, NV, chain);
|
|
|
|
// rotational jdv for welds
|
|
if (type == mjEQ_WELD) {
|
|
mju_dotSparseX3(jrdv1, jrdv1+1, jrdv1+2, jacrdot1, jacrdot1+NV, jacrdot1+2*NV,
|
|
d->qvel, NV, chain);
|
|
mju_dotSparseX3(jrdv2, jrdv2+1, jrdv2+2, jacrdot2, jacrdot2+NV, jacrdot2+2*NV,
|
|
d->qvel, NV, chain);
|
|
}
|
|
} else {
|
|
mju_zero3(jdv1);
|
|
mju_zero3(jdv2);
|
|
}
|
|
} else {
|
|
// dense: translational and rotational
|
|
mjtNum* jacr1 = (type == mjEQ_WELD) ? jacrdot1 : NULL;
|
|
mjtNum* jacr2 = (type == mjEQ_WELD) ? jacrdot2 : NULL;
|
|
mj_jacDot(m, d, jacdot1, jacr1, pos1, body1);
|
|
mj_jacDot(m, d, jacdot2, jacr2, pos2, body2);
|
|
|
|
// translational jdv = jacDot * qvel
|
|
mju_mulMatVec(jdv1, jacdot1, d->qvel, 3, nv);
|
|
mju_mulMatVec(jdv2, jacdot2, d->qvel, 3, nv);
|
|
|
|
// rotational jdv for welds
|
|
if (type == mjEQ_WELD) {
|
|
mju_mulMatVec(jrdv1, jacrdot1, d->qvel, 3, nv);
|
|
mju_mulMatVec(jrdv2, jacrdot2, d->qvel, 3, nv);
|
|
}
|
|
}
|
|
|
|
// subtract translational Jdot*v
|
|
result[row+0] -= jdv1[0] - jdv2[0];
|
|
result[row+1] -= jdv1[1] - jdv2[1];
|
|
result[row+2] -= jdv1[2] - jdv2[2];
|
|
|
|
// advance past translational rows
|
|
row += 3;
|
|
|
|
// weld: compute rotational Jdot*v
|
|
if (type == mjEQ_WELD) {
|
|
mjtNum torquescale = data[10];
|
|
|
|
// get body quaternions and relpose, following mj_instantiateEquality
|
|
mjtNum q0r[4], negq1[4]; // q0r = q0*relpose, negq1 = neg(q1)
|
|
if (m->eq_objtype[eq_id] == mjOBJ_BODY) {
|
|
mjtNum* relpose = data+6;
|
|
mju_mulQuat(q0r, d->xquat+4*body1, relpose);
|
|
mju_negQuat(negq1, d->xquat+4*body2);
|
|
} else {
|
|
mju_mulQuat(q0r, d->xquat+4*body1, m->site_quat+4*obj1);
|
|
mjtNum qsite1[4];
|
|
mju_mulQuat(qsite1, d->xquat+4*body2, m->site_quat+4*obj2);
|
|
mju_negQuat(negq1, qsite1);
|
|
}
|
|
|
|
// angular velocities from cvel (first 3 components are angular)
|
|
const mjtNum* omega1 = d->cvel+6*body1;
|
|
const mjtNum* omega2 = d->cvel+6*body2;
|
|
|
|
// relative angular velocity: domega = omega1 - omega2
|
|
mjtNum domega[3];
|
|
mju_sub3(domega, omega1, omega2);
|
|
|
|
// quaternion derivatives: qdot = 0.5 * q * (0, omega)
|
|
mjtNum qdot0[4];
|
|
if (m->eq_objtype[eq_id] == mjOBJ_BODY) {
|
|
mju_derivQuat(qdot0, d->xquat+4*body1, omega1);
|
|
} else {
|
|
mjtNum qfull0[4];
|
|
mju_mulQuat(qfull0, d->xquat+4*body1, m->site_quat+4*obj1);
|
|
mju_derivQuat(qdot0, qfull0, omega1);
|
|
}
|
|
mjtNum qdot0r[4]; // d/dt(q0 * relpose) = qdot0 * relpose
|
|
if (m->eq_objtype[eq_id] == mjOBJ_BODY) {
|
|
mju_mulQuat(qdot0r, qdot0, data+6);
|
|
} else {
|
|
mju_copy4(qdot0r, qdot0);
|
|
}
|
|
|
|
// neg(qdot1): d/dt(neg(q1)) = neg(qdot1)
|
|
mjtNum negqdot1[4];
|
|
if (m->eq_objtype[eq_id] == mjOBJ_BODY) {
|
|
mjtNum qdot1[4];
|
|
mju_derivQuat(qdot1, d->xquat+4*body2, omega2);
|
|
mju_negQuat(negqdot1, qdot1);
|
|
} else {
|
|
mjtNum qfull1[4], qdot1[4];
|
|
mju_mulQuat(qfull1, d->xquat+4*body2, m->site_quat+4*obj2);
|
|
mju_derivQuat(qdot1, qfull1, omega2);
|
|
mju_negQuat(negqdot1, qdot1);
|
|
}
|
|
|
|
// Jdot_rot * v differentiates: 0.5 * neg(q1) * (J0-J1)*v * q0*relpose
|
|
// three terms from product rule:
|
|
|
|
// djrdv = Jrdot0*v - Jrdot1*v (rotational jacDot difference * v)
|
|
mjtNum djrdv[3];
|
|
mju_sub3(djrdv, jrdv1, jrdv2);
|
|
|
|
// term1: neg(qdot1) * domega * q0r
|
|
mjtNum t1a[4], t1[4];
|
|
mju_mulQuatAxis(t1a, negqdot1, domega);
|
|
mju_mulQuat(t1, t1a, q0r);
|
|
|
|
// term2: neg(q1) * djrdv * q0r
|
|
mjtNum t2a[4], t2[4];
|
|
mju_mulQuatAxis(t2a, negq1, djrdv);
|
|
mju_mulQuat(t2, t2a, q0r);
|
|
|
|
// term3: neg(q1) * domega * qdot0r
|
|
mjtNum t3a[4], t3[4];
|
|
mju_mulQuatAxis(t3a, negq1, domega);
|
|
mju_mulQuat(t3, t3a, qdot0r);
|
|
|
|
// combine: 0.5 * (term1 + term2 + term3), take vector part, scale
|
|
result[row+0] -= 0.5 * (t1[1] + t2[1] + t3[1]) * torquescale;
|
|
result[row+1] -= 0.5 * (t1[2] + t2[2] + t3[2]) * torquescale;
|
|
result[row+2] -= 0.5 * (t1[3] + t2[3] + t3[3]) * torquescale;
|
|
|
|
row += 3;
|
|
}
|
|
}
|
|
|
|
// other types: advance past all rows with this efc_id
|
|
else {
|
|
while (row < ne && d->efc_id[row] == eq_id) {
|
|
row++;
|
|
}
|
|
}
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
|
|
// return number of constraint non-zeros, handle dense and dof-less cases
|
|
static inline int mj_addConstraintCount(const mjModel* m, int size, int NV) {
|
|
// over count for dense allocation
|
|
if (!mj_isSparse(m)) {
|
|
return m->nv ? size : 0;
|
|
}
|
|
return mjMAX(0, NV) ? size : 0;
|
|
}
|
|
|
|
|
|
// frictional DOFs and tendons
|
|
// count_only: count constraints and Jacobian nonzeros without instantiating
|
|
static int mj_instantiateFriction(const mjModel* m, mjData* d, int count_only, int* nnz) {
|
|
int nv = m->nv, issparse = mj_isSparse(m);
|
|
int nf = 0;
|
|
mjtNum* jac = NULL;
|
|
|
|
// disabled: return
|
|
if (mjDISABLED(mjDSBL_FRICTIONLOSS)) {
|
|
return 0;
|
|
}
|
|
|
|
// sleep filtering
|
|
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->ntree_awake < m->ntree;
|
|
|
|
if (!count_only) {
|
|
mj_markStack(d);
|
|
|
|
// allocate Jacobian
|
|
jac = mjSTACKALLOC(d, nv, mjtNum);
|
|
}
|
|
|
|
// find frictional DOFs
|
|
for (int i=0; i < nv; i++) {
|
|
// no friction loss: skip
|
|
if (!m->dof_frictionloss[i]) {
|
|
continue;
|
|
}
|
|
|
|
// sleeping tree: skip
|
|
if (sleep_filter && mj_sleepState(m, d, mjOBJ_DOF, i) == mjS_ASLEEP) {
|
|
continue;
|
|
}
|
|
|
|
if (count_only) {
|
|
nf += mj_addConstraintCount(m, 1, 1);
|
|
if (nnz) *nnz += 1;
|
|
} else {
|
|
// prepare Jacobian: sparse or dense
|
|
if (issparse) {
|
|
jac[0] = 1;
|
|
} else {
|
|
mju_zero(jac, nv);
|
|
jac[i] = 1;
|
|
}
|
|
|
|
// add constraint
|
|
mj_addConstraint(m, d, jac, 0, 0, m->dof_frictionloss[i],
|
|
1, mjCNSTR_FRICTION_DOF, i,
|
|
issparse ? 1 : 0,
|
|
issparse ? &i : NULL);
|
|
}
|
|
}
|
|
|
|
// find frictional tendons
|
|
for (int i=0; i < m->ntendon; i++) {
|
|
if (m->tendon_frictionloss[i] > 0) {
|
|
if (count_only) {
|
|
nf += mj_addConstraintCount(m, 1, m->ten_J_rownnz[i]);
|
|
if (nnz) *nnz += m->ten_J_rownnz[i];
|
|
} else {
|
|
int efcadr = d->nefc;
|
|
// add constraint
|
|
if (issparse) {
|
|
mj_addConstraint(m, d, d->ten_J + m->ten_J_rowadr[i],
|
|
0, 0, m->tendon_frictionloss[i],
|
|
1, mjCNSTR_FRICTION_TENDON, i,
|
|
m->ten_J_rownnz[i],
|
|
m->ten_J_colind+m->ten_J_rowadr[i]);
|
|
} else {
|
|
mju_sparse2dense(jac, d->ten_J, 1, nv, m->ten_J_rownnz+i, m->ten_J_rowadr+i, m->ten_J_colind);
|
|
mj_addConstraint(m, d, jac, 0, 0, m->tendon_frictionloss[i],
|
|
1, mjCNSTR_FRICTION_TENDON, i, 0, NULL);
|
|
}
|
|
// set tendon_efcadr
|
|
if (d->tendon_efcadr[i] == -1) {
|
|
d->tendon_efcadr[i] = efcadr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!count_only) {
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
return nf;
|
|
}
|
|
|
|
|
|
// joint and tendon limits
|
|
// count_only: count constraints and Jacobian nonzeros without instantiating
|
|
static int mj_instantiateLimit(const mjModel* m, mjData* d, int count_only, int* nnz) {
|
|
int nv = m->nv, issparse = mj_isSparse(m);
|
|
int nl = 0;
|
|
mjtNum margin, value, dist, angleAxis[3];
|
|
mjtNum *jac = NULL;
|
|
|
|
// disabled: return
|
|
if (mjDISABLED(mjDSBL_LIMIT)) {
|
|
return 0;
|
|
}
|
|
|
|
// sleep filtering
|
|
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->ntree_awake < m->ntree;
|
|
|
|
if (!count_only) {
|
|
mj_markStack(d);
|
|
|
|
// allocate Jacobian
|
|
jac = mjSTACKALLOC(d, nv, mjtNum);
|
|
}
|
|
|
|
// find joint limits
|
|
for (int i=0; i < m->njnt; i++) {
|
|
// no limit: skip
|
|
if (!m->jnt_limited[i]) {
|
|
continue;
|
|
}
|
|
|
|
// sleeping tree: skip
|
|
if (sleep_filter && mj_sleepState(m, d, mjOBJ_JOINT, i) == mjS_ASLEEP) {
|
|
continue;
|
|
}
|
|
|
|
// get margin
|
|
margin = m->jnt_margin[i];
|
|
|
|
// HINGE or SLIDE joint
|
|
if (m->jnt_type[i] == mjJNT_SLIDE || m->jnt_type[i] == mjJNT_HINGE) {
|
|
// get joint value
|
|
value = d->qpos[m->jnt_qposadr[i]];
|
|
|
|
// process lower and upper limits
|
|
for (int side=-1; side <= 1; side+=2) {
|
|
// compute distance (negative: penetration)
|
|
dist = side * (m->jnt_range[2*i+(side+1)/2] - value);
|
|
|
|
// detect joint limit
|
|
if (dist < margin) {
|
|
if (count_only) {
|
|
nl += mj_addConstraintCount(m, 1, 1);
|
|
if (nnz) *nnz += 1;
|
|
} else {
|
|
// prepare Jacobian: sparse or dense
|
|
if (issparse) {
|
|
jac[0] = -(mjtNum)side;
|
|
} else {
|
|
mju_zero(jac, nv);
|
|
jac[m->jnt_dofadr[i]] = -(mjtNum)side;
|
|
}
|
|
|
|
// add constraint
|
|
mj_addConstraint(m, d, jac, &dist, &margin, 0,
|
|
1, mjCNSTR_LIMIT_JOINT, i,
|
|
issparse ? 1 : 0,
|
|
issparse ? m->jnt_dofadr+i : NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// BALL joint
|
|
else if (m->jnt_type[i] == mjJNT_BALL) {
|
|
// convert joint quaternion to axis-angle
|
|
int adr = m->jnt_qposadr[i];
|
|
mjtNum quat[4] = {d->qpos[adr], d->qpos[adr+1], d->qpos[adr+2], d->qpos[adr+3]};
|
|
mju_normalize4(quat);
|
|
mju_quat2Vel(angleAxis, quat, 1);
|
|
|
|
// get rotation angle, normalize
|
|
value = mju_normalize3(angleAxis);
|
|
|
|
// compute distance, using max of range (negative: penetration)
|
|
dist = mju_max(m->jnt_range[2*i], m->jnt_range[2*i+1]) - value;
|
|
|
|
// detect joint limit
|
|
if (dist < margin) {
|
|
if (count_only) {
|
|
nl += mj_addConstraintCount(m, 1, 3);
|
|
if (nnz) *nnz += 3;
|
|
}
|
|
|
|
// sparse
|
|
else if (issparse) {
|
|
// prepare dof index array
|
|
int chain[3] = {
|
|
m->jnt_dofadr[i] + 0,
|
|
m->jnt_dofadr[i] + 1,
|
|
m->jnt_dofadr[i] + 2
|
|
};
|
|
|
|
// prepare Jacobian
|
|
mju_scl3(jac, angleAxis, -1);
|
|
|
|
// add constraint
|
|
mj_addConstraint(m, d, jac, &dist, &margin, 0,
|
|
1, mjCNSTR_LIMIT_JOINT, i, 3, chain);
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
// prepare Jacobian
|
|
mju_zero(jac, nv);
|
|
mju_scl3(jac + m->jnt_dofadr[i], angleAxis, -1);
|
|
|
|
// add constraint
|
|
mj_addConstraint(m, d, jac, &dist, &margin, 0,
|
|
1, mjCNSTR_LIMIT_JOINT, i, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// find tendon limits
|
|
for (int i=0; i < m->ntendon; i++) {
|
|
if (!m->tendon_limited[i]) {
|
|
continue;
|
|
}
|
|
|
|
// get value = length, margin
|
|
value = d->ten_length[i];
|
|
margin = m->tendon_margin[i];
|
|
|
|
// process lower and upper limits
|
|
for (int side=-1; side <= 1; side+=2) {
|
|
// compute distance (negative: penetration)
|
|
dist = side * (m->tendon_range[2*i+(side+1)/2] - value);
|
|
|
|
// detect tendon limit
|
|
if (dist < margin) {
|
|
if (count_only) {
|
|
nl += mj_addConstraintCount(m, 1, m->ten_J_rownnz[i]);
|
|
if (nnz) *nnz += m->ten_J_rownnz[i];
|
|
} else {
|
|
// prepare Jacobian
|
|
int efcadr = d->nefc;
|
|
if (issparse) {
|
|
mju_scl(jac, d->ten_J+m->ten_J_rowadr[i], -side, m->ten_J_rownnz[i]);
|
|
mj_addConstraint(m, d, jac, &dist, &margin, 0,
|
|
1, mjCNSTR_LIMIT_TENDON, i,
|
|
m->ten_J_rownnz[i],
|
|
m->ten_J_colind+m->ten_J_rowadr[i]);
|
|
} else {
|
|
mju_sparse2dense(jac, d->ten_J, 1, nv, m->ten_J_rownnz+i, m->ten_J_rowadr+i, m->ten_J_colind);
|
|
mju_scl(jac, jac, -side, nv);
|
|
mj_addConstraint(m, d, jac, &dist, &margin, 0,
|
|
1, mjCNSTR_LIMIT_TENDON, i, 0, NULL);
|
|
}
|
|
// set tendon_efcadr
|
|
if (d->tendon_efcadr[i] == -1) {
|
|
d->tendon_efcadr[i] = efcadr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!count_only) {
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
return nl;
|
|
}
|
|
|
|
|
|
// compute Jacobian for contact, return number of DOFs affected
|
|
int mj_contactJacobian(const mjModel* m, mjData* d, const mjContact* con, int dim,
|
|
mjtNum* jac, mjtNum* jacdif, mjtNum* jacdifp,
|
|
mjtNum* jacdifr, mjtNum* jac1p, mjtNum* jac2p,
|
|
mjtNum* jac1r, mjtNum* jac2r, int* chain) {
|
|
// special case: single body on each side
|
|
if ((con->geom[0] >= 0 || (con->vert[0] >= 0 && m->flex_interp[con->flex[0]] == 0)) &&
|
|
(con->geom[1] >= 0 || (con->vert[1] >= 0 && m->flex_interp[con->flex[1]] == 0))) {
|
|
// get bodies
|
|
int bid[2];
|
|
for (int side=0; side < 2; side++) {
|
|
bid[side] = (con->geom[side] >= 0) ?
|
|
m->geom_bodyid[con->geom[side]] :
|
|
m->flex_vertbodyid[m->flex_vertadr[con->flex[side]] + con->vert[side]];
|
|
}
|
|
// compute Jacobian differences, skipping common dofs
|
|
if (dim > 3) {
|
|
return mj_jacDifPair(m, d, chain, bid[0], bid[1], con->pos, con->pos,
|
|
jac1p, jac2p, jacdifp, jac1r, jac2r, jacdifr, mj_isSparse(m), 1);
|
|
} else {
|
|
return mj_jacDifPair(m, d, chain, bid[0], bid[1], con->pos, con->pos,
|
|
jac1p, jac2p, jacdifp, NULL, NULL, NULL, mj_isSparse(m), 1);
|
|
}
|
|
}
|
|
|
|
// general case: flex elements involved
|
|
else {
|
|
// get bodies and weights
|
|
int nb = 0;
|
|
int bid[729]; // 729 = 27*27
|
|
mjtNum bweight[729];
|
|
for (int side=0; side < 2; side++) {
|
|
// geom
|
|
if (con->geom[side] >= 0) {
|
|
bid[nb] = m->geom_bodyid[con->geom[side]];
|
|
bweight[nb] = side ? +1 : -1;
|
|
nb++;
|
|
}
|
|
|
|
// flex
|
|
else {
|
|
int nw = 0;
|
|
int vid[4];
|
|
mjtNum vweight[4];
|
|
|
|
// vert
|
|
if (con->vert[side] >= 0) {
|
|
vid[0] = m->flex_vertadr[con->flex[side]] + con->vert[side];
|
|
vweight[0] = side ? +1 : -1;
|
|
nw = 1;
|
|
}
|
|
|
|
// elem
|
|
else {
|
|
nw = mj_elemBodyWeight(m, d, con->flex[side], con->elem[side],
|
|
con->vert[1-side], con->pos, vid, vweight);
|
|
|
|
// negative sign for first side of contact
|
|
if (side == 0) {
|
|
mju_scl(vweight, vweight, -1, nw);
|
|
}
|
|
}
|
|
|
|
// get body or node ids and weights
|
|
if (m->flex_interp[con->flex[side]] == 0) {
|
|
for (int k=0; k < nw; k++) {
|
|
bid[nb] = m->flex_vertbodyid[vid[k]];
|
|
bweight[nb] = vweight[k];
|
|
nb++;
|
|
}
|
|
} else {
|
|
nb += mj_vertBodyWeight(m, d, con->flex[side], vid, bid+nb, bweight+nb, vweight, nw);
|
|
}
|
|
}
|
|
}
|
|
|
|
// combine weighted Jacobians
|
|
return mj_jacSum(m, d, chain, nb, bid, bweight, con->pos, jacdif, dim > 3);
|
|
}
|
|
}
|
|
|
|
|
|
// frictionless and frictional contacts
|
|
void mj_instantiateContact(const mjModel* m, mjData* d) {
|
|
int ispyramid = mj_isPyramidal(m), issparse = mj_isSparse(m), ncon = d->ncon;
|
|
int dim, NV, nv = m->nv, *chain = NULL;
|
|
mjContact* con;
|
|
mjtNum cpos[6], cmargin[6], *jac, *jacdif, *jacdifp, *jacdifr, *jac1p, *jac2p, *jac1r, *jac2r;
|
|
|
|
if (mjDISABLED(mjDSBL_CONTACT) || ncon == 0 || nv == 0) {
|
|
return;
|
|
}
|
|
|
|
mj_markStack(d);
|
|
|
|
// allocate Jacobian
|
|
jac = mjSTACKALLOC(d, 6*nv, mjtNum);
|
|
jacdif = mjSTACKALLOC(d, 6*nv, mjtNum);
|
|
jacdifp = jacdif;
|
|
jacdifr = jacdif + 3*nv;
|
|
jac1p = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
jac2p = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
jac1r = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
jac2r = mjSTACKALLOC(d, 3*nv, mjtNum);
|
|
if (issparse) {
|
|
chain = mjSTACKALLOC(d, nv, int);
|
|
}
|
|
|
|
// find contacts to be included
|
|
for (int i=0; i < ncon; i++) {
|
|
if (d->contact[i].exclude) {
|
|
continue;
|
|
}
|
|
|
|
// get contact info, save efc_address
|
|
con = d->contact + i;
|
|
dim = con->dim;
|
|
con->efc_address = d->nefc;
|
|
NV = mj_contactJacobian(m, d, con, dim, jac, jacdif, jacdifp, jacdifr,
|
|
jac1p, jac2p, jac1r, jac2r, chain);
|
|
|
|
// skip contact if no DOFs affected
|
|
if (NV == 0) {
|
|
con->efc_address = -1;
|
|
con->exclude = 3;
|
|
continue;
|
|
}
|
|
|
|
// rotate Jacobian differences to contact frame
|
|
mju_mulMatMat(jac, con->frame, jacdifp, dim > 1 ? 3 : 1, 3, NV);
|
|
if (dim > 3) {
|
|
mju_mulMatMat(jac + 3*NV, con->frame, jacdifr, dim-3, 3, NV);
|
|
}
|
|
|
|
// make frictionless contact
|
|
if (dim == 1) {
|
|
// add constraint
|
|
mj_addConstraint(m, d, jac, &(con->dist), &(con->includemargin), 0,
|
|
1, mjCNSTR_CONTACT_FRICTIONLESS, i,
|
|
issparse ? NV : 0,
|
|
issparse ? chain : NULL);
|
|
}
|
|
|
|
// make pyramidal friction cone
|
|
else if (ispyramid) {
|
|
// pos = dist
|
|
cpos[0] = cpos[1] = con->dist;
|
|
cmargin[0] = cmargin[1] = con->includemargin;
|
|
|
|
// one pair per friction dimension
|
|
for (int k=1; k < con->dim; k++) {
|
|
// Jacobian for pair of opposing pyramid edges
|
|
mju_addScl(jacdifp, jac, jac + k*NV, con->friction[k-1], NV);
|
|
mju_addScl(jacdifp + NV, jac, jac + k*NV, -con->friction[k-1], NV);
|
|
|
|
// add constraint
|
|
mj_addConstraint(m, d, jacdifp, cpos, cmargin, 0,
|
|
2, mjCNSTR_CONTACT_PYRAMIDAL, i,
|
|
issparse ? NV : 0,
|
|
issparse ? chain : NULL);
|
|
}
|
|
}
|
|
|
|
// make elliptic friction cone
|
|
else {
|
|
// normal pos = dist, all others 0
|
|
mju_zero(cpos, con->dim);
|
|
mju_zero(cmargin, con->dim);
|
|
cpos[0] = con->dist;
|
|
cmargin[0] = con->includemargin;
|
|
|
|
// add constraint
|
|
mj_addConstraint(m, d, jac, cpos, cmargin, 0,
|
|
con->dim, mjCNSTR_CONTACT_ELLIPTIC, i,
|
|
issparse ? NV : 0,
|
|
issparse ? chain : NULL);
|
|
}
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
|
|
//------------------------ compute constraint parameters -------------------------------------------
|
|
|
|
// compute diagApprox
|
|
void mj_diagApprox(const mjModel* m, mjData* d) {
|
|
int id, dim, b1, b2, f, weldcnt = 0;
|
|
int nefc = d->nefc;
|
|
mjtNum tran, rot, fri, *dA = d->efc_diagApprox;
|
|
mjContact* con = NULL;
|
|
|
|
// loop over all constraints, compute approximate inverse inertia
|
|
for (int i=0; i < nefc; i++) {
|
|
// get constraint id
|
|
id = d->efc_id[i];
|
|
|
|
// process according to constraint type
|
|
switch ((mjtConstraint) d->efc_type[i]) {
|
|
case mjCNSTR_EQUALITY:
|
|
// process according to equality-constraint type
|
|
switch (m->eq_type[id]) {
|
|
case mjEQ_CONNECT:
|
|
b1 = m->eq_obj1id[id];
|
|
b2 = m->eq_obj2id[id];
|
|
|
|
// get body ids if using site semantics
|
|
if (m->eq_objtype[id] == mjOBJ_SITE) {
|
|
b1 = m->site_bodyid[b1];
|
|
b2 = m->site_bodyid[b2];
|
|
}
|
|
|
|
// body translation
|
|
dA[i] = m->body_invweight0[2*b1] + m->body_invweight0[2*b2];
|
|
break;
|
|
|
|
case mjEQ_WELD: // distinguish translation and rotation inertia
|
|
b1 = m->eq_obj1id[id];
|
|
b2 = m->eq_obj2id[id];
|
|
|
|
// get body ids if using site semantics
|
|
if (m->eq_objtype[id] == mjOBJ_SITE) {
|
|
b1 = m->site_bodyid[b1];
|
|
b2 = m->site_bodyid[b2];
|
|
}
|
|
|
|
// body translation or rotation depending on weldcnt
|
|
dA[i] = m->body_invweight0[2*b1 + (weldcnt > 2)] +
|
|
m->body_invweight0[2*b2 + (weldcnt > 2)];
|
|
weldcnt = (weldcnt + 1) % 6;
|
|
break;
|
|
|
|
case mjEQ_JOINT:
|
|
case mjEQ_TENDON:
|
|
// object 1 contribution
|
|
dA[i] = (m->eq_type[id] == mjEQ_JOINT ?
|
|
m->dof_invweight0[m->jnt_dofadr[m->eq_obj1id[id]]] :
|
|
m->tendon_invweight0[m->eq_obj1id[id]]);
|
|
|
|
// add object 2 contribution if present
|
|
if (m->eq_obj2id[id] >= 0)
|
|
dA[i] += (m->eq_type[id] == mjEQ_JOINT ?
|
|
m->dof_invweight0[m->jnt_dofadr[m->eq_obj2id[id]]] :
|
|
m->tendon_invweight0[m->eq_obj2id[id]]);
|
|
break;
|
|
|
|
case mjEQ_FLEX:
|
|
// process all non-rigid edges for this flex
|
|
f = m->eq_obj1id[id];
|
|
int flex_edgeadr = m->flex_edgeadr[f];
|
|
int flex_edgenum = m->flex_edgenum[f];
|
|
for (int e=flex_edgeadr; e<flex_edgeadr+flex_edgenum; e++) {
|
|
if (!m->flexedge_rigid[e]) {
|
|
dA[i++] = m->flexedge_invweight0[e];
|
|
}
|
|
}
|
|
|
|
// adjust constraint counter
|
|
i--;
|
|
break;
|
|
|
|
case mjEQ_FLEXVERT:
|
|
// process all vertices for this flex
|
|
f = m->eq_obj1id[id];
|
|
int vertadr = m->flex_vertadr[f];
|
|
int vertnum = m->flex_vertnum[f];
|
|
for (int v=vertadr; v<vertadr+vertnum; v++) {
|
|
int bodyid = m->flex_vertbodyid[v];
|
|
dA[i++] = m->body_invweight0[2*bodyid];
|
|
dA[i++] = m->body_invweight0[2*bodyid];
|
|
}
|
|
|
|
// adjust constraint counter
|
|
i--;
|
|
break;
|
|
|
|
case mjEQ_FLEXSTRAIN: {
|
|
// strain constraints: use avg inv weight of element's nodes
|
|
int flex_id = m->eq_obj1id[id];
|
|
int nstart = m->flex_nodeadr[flex_id];
|
|
int interp = m->flex_interp[flex_id];
|
|
int order = interp < 0 ? -interp : interp;
|
|
int is_shell = (interp < 0);
|
|
|
|
int cx = m->flex_cellnum[3*flex_id+0];
|
|
int cy = m->flex_cellnum[3*flex_id+1];
|
|
int cz = m->flex_cellnum[3*flex_id+2];
|
|
|
|
// nodes per element
|
|
int npe;
|
|
int elem_idx;
|
|
if (is_shell) {
|
|
npe = (order+1) * (order+1);
|
|
elem_idx = (int)m->eq_data[mjNEQDATA*id + 0];
|
|
} else {
|
|
npe = (order+1) * (order+1) * (order+1);
|
|
int ci_cell = (int)m->eq_data[mjNEQDATA*id + 0];
|
|
int cj_cell = (int)m->eq_data[mjNEQDATA*id + 1];
|
|
int ck_cell = (int)m->eq_data[mjNEQDATA*id + 2];
|
|
elem_idx = ci_cell * cy * cz + cj_cell * cz + ck_cell;
|
|
}
|
|
|
|
// read neig from flex_stiffness
|
|
int ndof_elem = 3 * npe;
|
|
const mjtNum* k_elem = m->flex_stiffness + m->flex_stiffnessadr[flex_id]
|
|
+ elem_idx * ndof_elem * ndof_elem;
|
|
int nconstraint = (int)k_elem[0];
|
|
|
|
// get element node indices
|
|
int gindices[125];
|
|
if (is_shell) {
|
|
mju_flexGatherFaceState(order, cx, cy, cz, elem_idx,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, gindices, NULL);
|
|
} else {
|
|
int ci_cell = (int)m->eq_data[mjNEQDATA*id + 0];
|
|
int cj_cell = (int)m->eq_data[mjNEQDATA*id + 1];
|
|
int ck_cell = (int)m->eq_data[mjNEQDATA*id + 2];
|
|
mju_flexGatherCellState(order, cy, cz, ci_cell, cj_cell, ck_cell,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, gindices, NULL);
|
|
}
|
|
|
|
mjtNum avg_invweight = 0;
|
|
for (int n = 0; n < npe; n++) {
|
|
int bodyid = m->flex_nodebodyid[nstart + gindices[n]];
|
|
avg_invweight += m->body_invweight0[2*bodyid];
|
|
}
|
|
avg_invweight /= npe;
|
|
for (int c = 0; c < nconstraint; c++) {
|
|
dA[i++] = avg_invweight;
|
|
}
|
|
|
|
// adjust constraint counter
|
|
i--;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
mjERROR("unknown constraint type %d", d->efc_type[i]); // SHOULD NOT OCCUR
|
|
}
|
|
break;
|
|
|
|
case mjCNSTR_FRICTION_DOF:
|
|
dA[i] = m->dof_invweight0[id];
|
|
break;
|
|
|
|
case mjCNSTR_LIMIT_JOINT:
|
|
dA[i] = m->dof_invweight0[m->jnt_dofadr[id]];
|
|
break;
|
|
|
|
case mjCNSTR_FRICTION_TENDON:
|
|
case mjCNSTR_LIMIT_TENDON:
|
|
dA[i] = m->tendon_invweight0[id];
|
|
break;
|
|
|
|
case mjCNSTR_CONTACT_FRICTIONLESS:
|
|
case mjCNSTR_CONTACT_PYRAMIDAL:
|
|
case mjCNSTR_CONTACT_ELLIPTIC:
|
|
// get contact info
|
|
con = d->contact + id;
|
|
dim = con->dim;
|
|
|
|
// add the average translation and rotation components from both sides
|
|
tran = rot = 0;
|
|
for (int side=0; side < 2; side++) {
|
|
// get bodies and weights
|
|
int nb = 0, bid[729];
|
|
mjtNum bweight[729];
|
|
|
|
// geom
|
|
if (con->geom[side] >= 0) {
|
|
bid[0] = m->geom_bodyid[con->geom[side]];
|
|
bweight[0] = 1;
|
|
nb = 1;
|
|
}
|
|
|
|
// flex
|
|
else {
|
|
int nw = 0;
|
|
int vid[4];
|
|
mjtNum vweight[4];
|
|
|
|
// vert
|
|
if (con->vert[side] >= 0) {
|
|
vid[0] = m->flex_vertadr[con->flex[side]] + con->vert[side];
|
|
vweight[0] = 1;
|
|
nw = 1;
|
|
}
|
|
|
|
// elem
|
|
else {
|
|
nw = mj_elemBodyWeight(m, d, con->flex[side], con->elem[side],
|
|
con->vert[1-side], con->pos, vid, vweight);
|
|
}
|
|
|
|
// convert verted ids and weights to body ids and weights
|
|
if (m->flex_interp[con->flex[side]] == 0) {
|
|
for (int k=0; k < nw; k++) {
|
|
bid[k] = m->flex_vertbodyid[vid[k]];
|
|
bweight[k] = vweight[k];
|
|
nb++;
|
|
}
|
|
} else {
|
|
nb += mj_vertBodyWeight(m, d, con->flex[side], vid, bid, bweight, vweight, nw);
|
|
}
|
|
}
|
|
|
|
// add weighted average over bodies
|
|
for (int k=0; k < nb; k++) {
|
|
tran += m->body_invweight0[2*bid[k]] * bweight[k];
|
|
rot += m->body_invweight0[2*bid[k]+1] * bweight[k];
|
|
}
|
|
}
|
|
|
|
// set frictionless
|
|
if (d->efc_type[i] == mjCNSTR_CONTACT_FRICTIONLESS) {
|
|
dA[i] = tran;
|
|
}
|
|
|
|
// set elliptical
|
|
else if (d->efc_type[i] == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
for (int j=0; j < dim; j++) {
|
|
dA[i+j] = (j < 3 ? tran : rot);
|
|
}
|
|
|
|
// processed dim elements in one i-loop iteration; advance counter
|
|
i += (dim-1);
|
|
}
|
|
|
|
// set pyramidal
|
|
else {
|
|
for (int j=0; j < dim-1; j++) {
|
|
fri = con->friction[j];
|
|
dA[i+2*j] = dA[i+2*j+1] = tran + fri*fri*(j < 2 ? tran : rot);
|
|
}
|
|
|
|
// processed 2*dim-2 elements in one i-loop iteration; advance counter
|
|
i += (2*dim-3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// get solref, solimp for specified constraint
|
|
static void getsolparam(const mjModel* m, const mjData* d, int i,
|
|
mjtNum* solref, mjtNum* solreffriction, mjtNum* solimp) {
|
|
// get constraint id
|
|
int id = d->efc_id[i];
|
|
|
|
// clear solreffriction (applies only to contacts)
|
|
mju_zero(solreffriction, mjNREF);
|
|
|
|
// extract solver parameters from corresponding model element
|
|
switch ((mjtConstraint) d->efc_type[i]) {
|
|
case mjCNSTR_EQUALITY:
|
|
mju_copy(solref, m->eq_solref+mjNREF*id, mjNREF);
|
|
mju_copy(solimp, m->eq_solimp+mjNIMP*id, mjNIMP);
|
|
break;
|
|
|
|
case mjCNSTR_LIMIT_JOINT:
|
|
mju_copy(solref, m->jnt_solref+mjNREF*id, mjNREF);
|
|
mju_copy(solimp, m->jnt_solimp+mjNIMP*id, mjNIMP);
|
|
break;
|
|
|
|
case mjCNSTR_FRICTION_DOF:
|
|
mju_copy(solref, m->dof_solref+mjNREF*id, mjNREF);
|
|
mju_copy(solimp, m->dof_solimp+mjNIMP*id, mjNIMP);
|
|
break;
|
|
|
|
case mjCNSTR_LIMIT_TENDON:
|
|
mju_copy(solref, m->tendon_solref_lim+mjNREF*id, mjNREF);
|
|
mju_copy(solimp, m->tendon_solimp_lim+mjNIMP*id, mjNIMP);
|
|
break;
|
|
|
|
case mjCNSTR_FRICTION_TENDON:
|
|
mju_copy(solref, m->tendon_solref_fri+mjNREF*id, mjNREF);
|
|
mju_copy(solimp, m->tendon_solimp_fri+mjNIMP*id, mjNIMP);
|
|
break;
|
|
|
|
case mjCNSTR_CONTACT_FRICTIONLESS:
|
|
case mjCNSTR_CONTACT_PYRAMIDAL:
|
|
case mjCNSTR_CONTACT_ELLIPTIC:
|
|
mju_copy(solref, d->contact[id].solref, mjNREF);
|
|
mju_copy(solreffriction, d->contact[id].solreffriction, mjNREF);
|
|
mju_copy(solimp, d->contact[id].solimp, mjNIMP);
|
|
}
|
|
|
|
// check reference format: standard or direct, cannot be mixed
|
|
if ((solref[0] > 0) ^ (solref[1] > 0)) {
|
|
mju_warning("mixed solref format, replacing with default");
|
|
mj_defaultSolRefImp(solref, NULL);
|
|
}
|
|
|
|
// integrator safety: impose ref[0]>=2*timestep for standard format
|
|
if (!mjDISABLED(mjDSBL_REFSAFE) && solref[0] > 0) {
|
|
solref[0] = mju_max(solref[0], 2*m->opt.timestep);
|
|
}
|
|
|
|
// check reference format: standard or direct, cannot be mixed
|
|
if ((solreffriction[0] > 0) ^ (solreffriction[1] > 0)) {
|
|
mju_warning("solreffriction values should have the same sign, replacing with default");
|
|
mju_zero(solreffriction, mjNREF); // default solreffriction is (0, 0)
|
|
}
|
|
|
|
// integrator safety: impose ref[0]>=2*timestep for standard format
|
|
if (!mjDISABLED(mjDSBL_REFSAFE) && solreffriction[0] > 0) {
|
|
solreffriction[0] = mju_max(solreffriction[0], 2*m->opt.timestep);
|
|
}
|
|
|
|
// enforce constraints on solimp
|
|
solimp[0] = mju_min(mjMAXIMP, mju_max(mjMINIMP, solimp[0]));
|
|
solimp[1] = mju_min(mjMAXIMP, mju_max(mjMINIMP, solimp[1]));
|
|
solimp[2] = mju_max(0, solimp[2]);
|
|
solimp[3] = mju_min(mjMAXIMP, mju_max(mjMINIMP, solimp[3]));
|
|
solimp[4] = mju_max(1, solimp[4]);
|
|
}
|
|
|
|
|
|
// get pos and dim for specified constraint
|
|
static void getposdim(const mjModel* m, const mjData* d, int i, mjtNum* pos, int* dim) {
|
|
// get id of constraint-related object
|
|
int id = d->efc_id[i];
|
|
|
|
// set (dim, pos) for common case
|
|
*dim = 1;
|
|
*pos = d->efc_pos[i];
|
|
|
|
// change (dim, distance) for special cases
|
|
switch ((mjtConstraint) d->efc_type[i]) {
|
|
case mjCNSTR_CONTACT_ELLIPTIC:
|
|
*dim = d->contact[id].dim;
|
|
break;
|
|
|
|
case mjCNSTR_CONTACT_PYRAMIDAL:
|
|
*dim = 2*(d->contact[id].dim-1);
|
|
break;
|
|
|
|
case mjCNSTR_EQUALITY:
|
|
if (m->eq_type[id] == mjEQ_WELD) {
|
|
*dim = 6;
|
|
*pos = mju_norm(d->efc_pos+i, 6);
|
|
} else if (m->eq_type[id] == mjEQ_CONNECT) {
|
|
*dim = 3;
|
|
*pos = mju_norm(d->efc_pos+i, 3);
|
|
}
|
|
break;
|
|
default:
|
|
// already handled
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// return a to the power of b, quick return for powers 1 and 2
|
|
// solimp[4] == 2 is the default, so these branches are common
|
|
static mjtNum power(mjtNum a, mjtNum b) {
|
|
if (b == 1) {
|
|
return a;
|
|
} else if (b == 2) {
|
|
return a*a;
|
|
}
|
|
return mju_pow(a, b);
|
|
}
|
|
|
|
|
|
// compute impedance and derivative for one constraint
|
|
static void getimpedance(const mjtNum* solimp, mjtNum pos, mjtNum margin,
|
|
mjtNum* imp, mjtNum* impP) {
|
|
// flat function
|
|
if (solimp[0] == solimp[1] || solimp[2] <= mjMINVAL) {
|
|
*imp = 0.5*(solimp[0] + solimp[1]);
|
|
*impP = 0;
|
|
return;
|
|
}
|
|
|
|
// x = abs((pos-margin) / width)
|
|
mjtNum x = (pos-margin) / solimp[2];
|
|
mjtNum sgn = 1;
|
|
if (x < 0) {
|
|
x = -x;
|
|
sgn = -1;
|
|
}
|
|
|
|
// fully saturated
|
|
if (x >= 1 || x <= 0) {
|
|
*imp = (x >= 1 ? solimp[1] : solimp[0]);
|
|
*impP = 0;
|
|
return;
|
|
}
|
|
|
|
// linear
|
|
mjtNum y, yP;
|
|
if (solimp[4] == 1) {
|
|
y = x;
|
|
yP = 1;
|
|
}
|
|
|
|
// y(x) = a*x^p if x<=midpoint
|
|
else if (x <= solimp[3]) {
|
|
mjtNum a = 1/power(solimp[3], solimp[4]-1);
|
|
y = a*power(x, solimp[4]);
|
|
yP = solimp[4] * a*power(x, solimp[4]-1);
|
|
}
|
|
|
|
// y(x) = 1-b*(1-x)^p if x>midpoint
|
|
else {
|
|
mjtNum b = 1/power(1-solimp[3], solimp[4]-1);
|
|
y = 1-b*power(1-x, solimp[4]);
|
|
yP = solimp[4] * b*power(1-x, solimp[4]-1);
|
|
}
|
|
|
|
// scale
|
|
*imp = solimp[0] + y*(solimp[1]-solimp[0]);
|
|
*impP = yP * sgn * (solimp[1]-solimp[0]) / solimp[2];
|
|
}
|
|
|
|
|
|
// compute efc_R, efc_D, efc_KBIP, adjust efc_diagApprox
|
|
void mj_makeImpedance(const mjModel* m, mjData* d) {
|
|
int dim, nefc = d->nefc;
|
|
mjtNum *R = d->efc_R, *KBIP = d->efc_KBIP;
|
|
mjtNum pos, imp, impP, Rpy, solref[mjNREF], solreffriction[mjNREF], solimp[mjNIMP];
|
|
|
|
// set efc_R, efc_KBIP
|
|
for (int i=0; i < nefc; i++) {
|
|
// get solref and solimp
|
|
getsolparam(m, d, i, solref, solreffriction, solimp);
|
|
|
|
// get pos and dim
|
|
getposdim(m, d, i, &pos, &dim);
|
|
|
|
// get imp and impP
|
|
getimpedance(solimp, pos, d->efc_margin[i], &imp, &impP);
|
|
|
|
// set R and KBIP for all constraint dimensions
|
|
for (int j=0; j < dim; j++) {
|
|
// R = (1-imp)/imp * diagApprox
|
|
R[i+j] = mju_max(mjMINVAL, (1-imp)*d->efc_diagApprox[i+j]/imp);
|
|
|
|
// constraint type
|
|
int tp = d->efc_type[i+j];
|
|
|
|
// elliptic contacts use solreffriction in non-normal directions, if non-zero
|
|
int elliptic_friction = (tp == mjCNSTR_CONTACT_ELLIPTIC) && (j > 0);
|
|
mjtNum* ref = elliptic_friction && (solreffriction[0] || solreffriction[1]) ?
|
|
solreffriction : solref;
|
|
|
|
// friction: K = 0
|
|
if (tp == mjCNSTR_FRICTION_DOF || tp == mjCNSTR_FRICTION_TENDON || elliptic_friction) {
|
|
KBIP[4*(i+j)] = 0;
|
|
}
|
|
|
|
// standard: K = 1 / (d_width^2 * timeconst^2 * dampratio^2)
|
|
else if (ref[0] > 0)
|
|
KBIP[4*(i+j)] = 1 / mju_max(mjMINVAL, solimp[1]*solimp[1] * ref[0]*ref[0] * ref[1]*ref[1]);
|
|
|
|
// direct: K = -solref[0] / d_width^2
|
|
else {
|
|
KBIP[4*(i+j)] = -ref[0] / mju_max(mjMINVAL, solimp[1]*solimp[1]);
|
|
}
|
|
|
|
// standard: B = 2 / (d_width*timeconst)
|
|
if (ref[1] > 0) {
|
|
KBIP[4*(i+j)+1] = 2 / mju_max(mjMINVAL, solimp[1]*ref[0]);
|
|
}
|
|
|
|
// direct: B = -solref[1] / d_width
|
|
else {
|
|
KBIP[4*(i+j)+1] = -ref[1] / mju_max(mjMINVAL, solimp[1]);
|
|
}
|
|
|
|
// I = imp, P = imp'
|
|
KBIP[4*(i+j)+2] = imp;
|
|
KBIP[4*(i+j)+3] = impP;
|
|
}
|
|
|
|
// skip the rest of this constraint
|
|
i += (dim-1);
|
|
}
|
|
|
|
// frictional contacts: adjust R in friction dimensions, set contact master mu
|
|
for (int i=d->ne+d->nf; i < nefc; i++) {
|
|
if (d->efc_type[i] == mjCNSTR_CONTACT_PYRAMIDAL ||
|
|
d->efc_type[i] == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
// extract id, dim, mu
|
|
int id = d->efc_id[i];
|
|
dim = d->contact[id].dim;
|
|
mjtNum* friction = d->contact[id].friction;
|
|
|
|
// set R[1] = R[0]/impratio
|
|
R[i+1] = R[i]/mju_max(mjMINVAL, m->opt.impratio);
|
|
|
|
// set mu of regularized cone = mu[1]*sqrt(R[1]/R[0])
|
|
d->contact[id].mu = friction[0] * mju_sqrt(R[i+1]/R[i]);
|
|
|
|
// elliptic
|
|
if (d->efc_type[i] == mjCNSTR_CONTACT_ELLIPTIC) {
|
|
// set remaining R's such that R[j]*mu[j]^2 = R[1]*mu[1]^2
|
|
for (int j=1; j < dim-1; j++) {
|
|
R[i+j+1] = R[i+1]*friction[0]*friction[0]/(friction[j]*friction[j]);
|
|
}
|
|
|
|
// skip the rest of this contact
|
|
i += (dim-1);
|
|
}
|
|
|
|
// pyramidal: common R matching friction impedance of elliptic model
|
|
else {
|
|
// D0_el = 2*(dim-1)*D_py : normal match
|
|
// D0_el = 2*mu^2*D_py : friction match
|
|
Rpy = 2*d->contact[id].mu*d->contact[id].mu*R[i];
|
|
|
|
// assign Rpy to all pyramidal R
|
|
for (int j=0; j < 2*(dim-1); j++) {
|
|
R[i+j] = Rpy;
|
|
}
|
|
|
|
// skip the rest of this contact
|
|
i += 2*(dim-1) - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set D = 1 / R
|
|
for (int i=0; i < nefc; i++) {
|
|
d->efc_D[i] = 1 / R[i];
|
|
}
|
|
|
|
// adjust diagApprox so that R = (1-imp)/imp * diagApprox
|
|
for (int i=0; i < nefc; i++) {
|
|
d->efc_diagApprox[i] = R[i] * KBIP[4*i+2] / (1-KBIP[4*i+2]);
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------- constraint counting ----------------------------------------
|
|
|
|
// count the non-zero columns of the Jacobian returned by mj_jacSum
|
|
static int mj_jacSumCount(const mjModel* m, mjData* d, int* chain,
|
|
int n, const int* body) {
|
|
int nv = m->nv, NV;
|
|
|
|
mj_markStack(d);
|
|
int* bodychain = mjSTACKALLOC(d, nv, int);
|
|
int* tempchain = mjSTACKALLOC(d, nv, int);
|
|
|
|
// set first
|
|
NV = mj_bodyChain(m, body[0], chain);
|
|
|
|
// accumulate remaining
|
|
for (int i=1; i < n; i++) {
|
|
// get body chain
|
|
int bodyNV = mj_bodyChain(m, body[i], bodychain);
|
|
if (!bodyNV) {
|
|
continue;
|
|
}
|
|
|
|
// accumulate chains
|
|
NV = mju_addChains(tempchain, nv, NV, bodyNV, chain, bodychain);
|
|
if (NV) {
|
|
mju_copyInt(chain, tempchain, NV);
|
|
}
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
return NV;
|
|
}
|
|
|
|
// count equality constraints, count Jacobian nonzeros if nnz is not NULL
|
|
static int mj_ne(const mjModel* m, mjData* d, int* nnz) {
|
|
int ne = 0, nnze = 0;
|
|
int nv = m->nv, neq = m->neq;
|
|
int id[2], size, NV, NV2, *chain = NULL, *chain2 = NULL;
|
|
int issparse = (nnz != NULL);
|
|
int flex_edgeadr, flex_edgenum, flex_vertadr, flex_vertnum;
|
|
|
|
// disabled or no equality constraints: return
|
|
if (mjDISABLED(mjDSBL_EQUALITY) || m->nemax == 0) {
|
|
return 0;
|
|
}
|
|
|
|
// sleep filtering
|
|
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->ntree_awake < m->ntree;
|
|
|
|
mj_markStack(d);
|
|
|
|
if (nnz) {
|
|
chain = mjSTACKALLOC(d, nv, int);
|
|
chain2 = mjSTACKALLOC(d, nv, int);
|
|
}
|
|
|
|
// pre-allocate buffer for cell body IDs (max npc = 125 for order=2)
|
|
int* cell_bodies = nnz ? mjSTACKALLOC(d, 125, int) : NULL;
|
|
|
|
// find active equality constraints
|
|
for (int i=0; i < neq; i++) {
|
|
// skip inactive
|
|
if (!d->eq_active[i]) {
|
|
continue;
|
|
}
|
|
|
|
// skip sleeping
|
|
if (sleep_filter && mj_sleepState(m, d, mjOBJ_EQUALITY, i) == mjS_ASLEEP) {
|
|
continue;
|
|
}
|
|
|
|
id[0] = m->eq_obj1id[i];
|
|
id[1] = m->eq_obj2id[i];
|
|
size = 0;
|
|
NV = 0;
|
|
NV2 = 0;
|
|
|
|
// process according to type
|
|
switch ((mjtEq) m->eq_type[i]) {
|
|
case mjEQ_CONNECT:
|
|
size = 3;
|
|
if (!nnz) {
|
|
break;
|
|
}
|
|
|
|
// get body ids if using site semantics
|
|
if (m->eq_objtype[i] == mjOBJ_SITE) {
|
|
id[0] = m->site_bodyid[id[0]];
|
|
id[1] = m->site_bodyid[id[1]];
|
|
}
|
|
|
|
NV = mj_jacDifPair(m, NULL, chain, id[1], id[0], NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, issparse,
|
|
/*flg_skipcommon=*/0);
|
|
break;
|
|
|
|
case mjEQ_WELD:
|
|
size = 6;
|
|
if (!nnz) {
|
|
break;
|
|
}
|
|
|
|
// get body ids if using site semantics
|
|
if (m->eq_objtype[i] == mjOBJ_SITE) {
|
|
id[0] = m->site_bodyid[id[0]];
|
|
id[1] = m->site_bodyid[id[1]];
|
|
}
|
|
|
|
NV = mj_jacDifPair(m, NULL, chain, id[1], id[0], NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, issparse,
|
|
/*flg_skipcommon=*/0);
|
|
break;
|
|
|
|
case mjEQ_JOINT:
|
|
case mjEQ_TENDON:
|
|
size = 1;
|
|
if (!nnz) {
|
|
break;
|
|
}
|
|
|
|
for (int j=0; j < 1+(id[1] >= 0); j++) {
|
|
if (m->eq_type[i] == mjEQ_JOINT) {
|
|
if (!j) {
|
|
NV = 1;
|
|
chain[0] = m->jnt_dofadr[id[j]];
|
|
} else {
|
|
NV2 = 1;
|
|
chain2[0] = m->jnt_dofadr[id[j]];
|
|
}
|
|
} else {
|
|
if (!j) {
|
|
NV = m->ten_J_rownnz[id[j]];
|
|
mju_copyInt(chain, m->ten_J_colind+m->ten_J_rowadr[id[j]], NV);
|
|
} else {
|
|
NV2 = m->ten_J_rownnz[id[j]];
|
|
mju_copyInt(chain2, m->ten_J_colind+m->ten_J_rowadr[id[j]], NV2);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (id[1] >= 0) {
|
|
NV = mju_combineSparseCount(NV, NV2, chain, chain2);
|
|
}
|
|
break;
|
|
|
|
case mjEQ_FLEX:
|
|
flex_edgeadr = m->flex_edgeadr[id[0]];
|
|
flex_edgenum = m->flex_edgenum[id[0]];
|
|
|
|
// init with all edges, subtract rigid later
|
|
size = flex_edgenum;
|
|
|
|
// process edges of this flex
|
|
for (int e=flex_edgeadr; e < flex_edgeadr+flex_edgenum; e++) {
|
|
// rigid: reduce size and skip
|
|
if (m->flexedge_rigid[e]) {
|
|
size--;
|
|
continue;
|
|
}
|
|
|
|
// accumulate NV if needed
|
|
if (nnz) {
|
|
int b1 = m->flex_vertbodyid[m->flex_vertadr[id[0]] + m->flex_edge[2*e]];
|
|
int b2 = m->flex_vertbodyid[m->flex_vertadr[id[0]] + m->flex_edge[2*e+1]];
|
|
NV += mj_jacDifPair(m, NULL, chain, b1, b2, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, issparse,
|
|
/*flg_skipcommon=*/0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case mjEQ_FLEXVERT:
|
|
flex_vertadr = m->flex_vertadr[id[0]];
|
|
flex_vertnum = m->flex_vertnum[id[0]];
|
|
size = 2 * flex_vertnum;
|
|
if (nnz) {
|
|
for (int v=flex_vertadr; v < flex_vertadr+flex_vertnum; v++) {
|
|
NV += m->flexvert_J_rownnz[2*v+0];
|
|
NV += m->flexvert_J_rownnz[2*v+1];
|
|
}
|
|
}
|
|
break;
|
|
|
|
case mjEQ_FLEXSTRAIN: {
|
|
// per-element strain constraints: each equality is one cell or face
|
|
int f = id[0];
|
|
int interp = m->flex_interp[f];
|
|
int order = interp < 0 ? -interp : interp;
|
|
int is_shell = (interp < 0);
|
|
if (!order || !m->flex_nodenum[f]) {
|
|
break;
|
|
}
|
|
|
|
int cx = m->flex_cellnum[3*f+0];
|
|
int cy = m->flex_cellnum[3*f+1];
|
|
int cz = m->flex_cellnum[3*f+2];
|
|
|
|
int npe;
|
|
int elem_idx;
|
|
if (is_shell) {
|
|
npe = (order+1) * (order+1);
|
|
elem_idx = (int)m->eq_data[mjNEQDATA*i + 0];
|
|
} else {
|
|
npe = (order+1) * (order+1) * (order+1);
|
|
int ci_cell = (int)m->eq_data[mjNEQDATA*i + 0];
|
|
int cj_cell = (int)m->eq_data[mjNEQDATA*i + 1];
|
|
int ck_cell = (int)m->eq_data[mjNEQDATA*i + 2];
|
|
elem_idx = ci_cell * cy * cz + cj_cell * cz + ck_cell;
|
|
}
|
|
|
|
// read eigenmode count from flex_stiffness
|
|
int ndof_elem = 3 * npe;
|
|
size = 0;
|
|
if (m->flex_stiffnessadr[f] >= 0) {
|
|
const mjtNum* k_elem = m->flex_stiffness + m->flex_stiffnessadr[f]
|
|
+ elem_idx * ndof_elem * ndof_elem;
|
|
size = (int)k_elem[0]; // neig stored as first element
|
|
}
|
|
|
|
if (nnz) {
|
|
// get element node body IDs
|
|
int gindices[125];
|
|
if (is_shell) {
|
|
mju_flexGatherFaceState(order, cx, cy, cz, elem_idx,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, gindices, NULL);
|
|
} else {
|
|
int ci_cell = (int)m->eq_data[mjNEQDATA*i + 0];
|
|
int cj_cell = (int)m->eq_data[mjNEQDATA*i + 1];
|
|
int ck_cell = (int)m->eq_data[mjNEQDATA*i + 2];
|
|
mju_flexGatherCellState(order, cy, cz, ci_cell, cj_cell, ck_cell,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, gindices, NULL);
|
|
}
|
|
int nstart = m->flex_nodeadr[f];
|
|
for (int n = 0; n < npe; n++) {
|
|
cell_bodies[n] = m->flex_nodebodyid[nstart + gindices[n]];
|
|
}
|
|
NV = mj_jacSumCount(m, d, chain, npe, cell_bodies);
|
|
NV = size * NV;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
// might occur in case of the now-removed distance equality constraint
|
|
mjERROR("unknown constraint type %d", m->eq_type[i]); // SHOULD NOT OCCUR
|
|
}
|
|
|
|
// accumulate counts; flex NV already accumulated
|
|
ne += mj_addConstraintCount(m, size, NV);
|
|
if (m->eq_type[i] == mjEQ_FLEX || m->eq_type[i] == mjEQ_FLEXVERT ||
|
|
m->eq_type[i] == mjEQ_FLEXSTRAIN) {
|
|
nnze += NV;
|
|
} else {
|
|
nnze += size*NV;
|
|
}
|
|
}
|
|
|
|
if (nnz) {
|
|
*nnz += nnze;
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
return ne;
|
|
}
|
|
|
|
|
|
// count contact constraints, count Jacobian nonzeros if nnz is not NULL
|
|
static int mj_nc(const mjModel* m, mjData* d, int* nnz) {
|
|
int nnzc = 0, nc = 0;
|
|
int ispyramid = mj_isPyramidal(m), ncon = d->ncon;
|
|
|
|
if (mjDISABLED(mjDSBL_CONTACT) || !ncon) {
|
|
return 0;
|
|
}
|
|
|
|
// sleep filtering
|
|
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->ntree_awake < m->ntree;
|
|
|
|
mj_markStack(d);
|
|
int *chain = mjSTACKALLOC(d, m->nv, int);
|
|
|
|
for (int i=0; i < ncon; i++) {
|
|
mjContact* con = d->contact + i;
|
|
|
|
// skip if passive
|
|
if ((con->flex[0] > -1 && m->flex_passive[con->flex[0]]) ||
|
|
(con->flex[1] > -1 && m->flex_passive[con->flex[1]])) {
|
|
con->efc_address = -1;
|
|
con->exclude = 4;
|
|
}
|
|
|
|
// skip if excluded
|
|
if (con->exclude) {
|
|
continue;
|
|
}
|
|
|
|
// check for contact with sleeping tree; SHOULD NOT OCCUR
|
|
if (sleep_filter) {
|
|
int g1 = con->geom[0];
|
|
int g2 = con->geom[1];
|
|
if (g1 >= 0 && g2 >= 0) {
|
|
int b1 = m->body_weldid[m->geom_bodyid[g1]];
|
|
int b2 = m->body_weldid[m->geom_bodyid[g2]];
|
|
int asleep1 = d->body_awake[b1] == mjS_ASLEEP;
|
|
int asleep2 = d->body_awake[b2] == mjS_ASLEEP;
|
|
if (asleep1 || asleep2) {
|
|
mjERROR("contact %d involves sleeping geom %d", i, asleep1 ? g1 : g2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// compute NV only if nnz requested
|
|
int NV = 0;
|
|
if (nnz) {
|
|
// single body on each side (geom-geom or flex vert-vert): skip common dofs
|
|
if ((con->geom[0] >= 0 || (con->vert[0] >= 0 && m->flex_interp[con->flex[0]] == 0)) &&
|
|
(con->geom[1] >= 0 || (con->vert[1] >= 0 && m->flex_interp[con->flex[1]] == 0))) {
|
|
// get bodies
|
|
int bid[2];
|
|
for (int side=0; side < 2; side++) {
|
|
bid[side] = (con->geom[side] >= 0) ?
|
|
m->geom_bodyid[con->geom[side]] :
|
|
m->flex_vertbodyid[m->flex_vertadr[con->flex[side]] + con->vert[side]];
|
|
}
|
|
NV = mj_jacDifPair(m, NULL, chain, bid[0], bid[1], NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, mj_isSparse(m), 1);
|
|
}
|
|
|
|
// general case: flex elements involved
|
|
else {
|
|
// get bodies
|
|
int nb = 0, bid[729];
|
|
for (int side=0; side < 2; side++) {
|
|
// geom
|
|
if (con->geom[side] >= 0) {
|
|
bid[nb++] = m->geom_bodyid[con->geom[side]];
|
|
}
|
|
|
|
// flex
|
|
else {
|
|
int nw = 0;
|
|
int vid[4];
|
|
mjtNum vweight[4];
|
|
|
|
// flex vert
|
|
if (con->vert[side] >= 0) {
|
|
vid[nw++] = m->flex_vertadr[con->flex[side]] + con->vert[side];
|
|
vweight[0] = 1;
|
|
}
|
|
|
|
// flex elem
|
|
else {
|
|
int f = con->flex[side];
|
|
int fdim = m->flex_dim[f];
|
|
const int* edata = m->flex_elem + m->flex_elemdataadr[f] + con->elem[side]*(fdim+1);
|
|
for (int k=0; k <= fdim; k++) {
|
|
vid[nw++] = m->flex_vertadr[f] + edata[k];
|
|
}
|
|
|
|
if (m->flex_interp[f]) {
|
|
nw = mj_elemBodyWeight(m, d, con->flex[side], con->elem[side],
|
|
con->vert[1-side], con->pos, vid, vweight);
|
|
}
|
|
}
|
|
|
|
// get body or node ids and weights
|
|
if (m->flex_interp[con->flex[side]] == 0) {
|
|
for (int k=0; k < nw; k++) {
|
|
bid[nb] = m->flex_vertbodyid[vid[k]];
|
|
nb++;
|
|
}
|
|
} else {
|
|
nb += mj_vertBodyWeight(m, d, con->flex[side], vid, bid+nb, NULL, vweight, nw);
|
|
}
|
|
}
|
|
}
|
|
|
|
// count non-zeros in merged chain
|
|
NV = mj_jacSumCount(m, d, chain, nb, bid);
|
|
}
|
|
if (!NV) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// count according to friction type
|
|
int dim = con->dim;
|
|
if (dim == 1) {
|
|
nc++;
|
|
nnzc += NV;
|
|
} else if (ispyramid) {
|
|
nc += 2*(dim-1);
|
|
nnzc += 2*(dim-1)*NV;
|
|
} else {
|
|
nc += dim;
|
|
nnzc += dim*NV;
|
|
}
|
|
}
|
|
|
|
if (nnz) {
|
|
*nnz += nnzc;
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
return nc;
|
|
}
|
|
|
|
|
|
//---------------------------- top-level API for constraint construction ---------------------------
|
|
|
|
// driver: call all functions above
|
|
void mj_makeConstraint(const mjModel* m, mjData* d) {
|
|
// clear sizes
|
|
d->ne = d->nf = d->nl = d->nefc = d->nJ = d->nA = 0;
|
|
|
|
// disabled or Jacobian not allocated: return
|
|
if (mjDISABLED(mjDSBL_CONSTRAINT)) {
|
|
return;
|
|
}
|
|
|
|
// precount sizes for constraint Jacobian matrices
|
|
int *nnz = mj_isSparse(m) ? &(d->nJ) : NULL;
|
|
int ne_allocated = mj_ne(m, d, nnz);
|
|
int nf_allocated = mj_instantiateFriction(m, d, 1, nnz);
|
|
int nl_allocated = mj_instantiateLimit(m, d, 1, nnz);
|
|
int nc_allocated = mj_nc(m, d, nnz);
|
|
int nefc_allocated = ne_allocated + nf_allocated + nl_allocated + nc_allocated;
|
|
if (!mj_isSparse(m)) {
|
|
d->nJ = nefc_allocated * m->nv;
|
|
}
|
|
d->nefc = nefc_allocated;
|
|
|
|
// allocate efc arrays on arena
|
|
if (!arenaAllocEfc(m, d)) {
|
|
return;
|
|
}
|
|
|
|
// clear tendon_efcadr
|
|
mju_fillInt(d->tendon_efcadr, -1, m->ntendon);
|
|
|
|
// reset nefc for the instantiation functions, instantiate all elements of Jacobian
|
|
d->nefc = 0;
|
|
mj_instantiateEquality(m, d);
|
|
mj_instantiateFriction(m, d, 0, NULL);
|
|
mj_instantiateLimit(m, d, 0, NULL);
|
|
mj_instantiateContact(m, d);
|
|
|
|
// check sparse allocation
|
|
if (mj_isSparse(m)) {
|
|
if (d->ne != ne_allocated) {
|
|
mjERROR("ne mis-allocation: found ne=%d but allocated %d", d->ne, ne_allocated);
|
|
}
|
|
|
|
if (d->nf != nf_allocated) {
|
|
mjERROR("nf mis-allocation: found nf=%d but allocated %d", d->nf, nf_allocated);
|
|
}
|
|
|
|
if (d->nl != nl_allocated) {
|
|
mjERROR("nl mis-allocation: found nl=%d but allocated %d", d->nl, nl_allocated);
|
|
}
|
|
|
|
// check that nefc was computed correctly
|
|
if (d->nefc != nefc_allocated) {
|
|
mjERROR("nefc mis-allocation: found nefc=%d but allocated %d", d->nefc, nefc_allocated);
|
|
}
|
|
|
|
// check that nJ was computed correctly
|
|
if (d->nefc > 0) {
|
|
int nJ = d->efc_J_rownnz[d->nefc - 1] + d->efc_J_rowadr[d->nefc - 1];
|
|
if (d->nJ != nJ) {
|
|
mjERROR("constraint Jacobian mis-allocation: found nJ=%d but allocated %d", nJ, d->nJ);
|
|
}
|
|
}
|
|
} else if (d->nefc > nefc_allocated) {
|
|
mjERROR("nefc under-allocation: found nefc=%d but allocated only %d",
|
|
d->nefc, nefc_allocated);
|
|
}
|
|
|
|
// collect memory use statistics
|
|
d->maxuse_con = mjMAX(d->maxuse_con, d->ncon);
|
|
d->maxuse_efc = mjMAX(d->maxuse_efc, d->nefc);
|
|
|
|
// no constraints: return
|
|
if (!d->nefc) {
|
|
return;
|
|
}
|
|
|
|
// accumulate J row supernodes (reverse cumsum of 0/1 flags set at assembly time)
|
|
if (mj_isSparse(m) && d->nefc) {
|
|
for (int r=d->nefc-2; r >= 0; r--) {
|
|
if (d->efc_J_rowsuper[r]) {
|
|
d->efc_J_rowsuper[r] += d->efc_J_rowsuper[r+1];
|
|
}
|
|
}
|
|
}
|
|
|
|
// compute diagApprox
|
|
mj_diagApprox(m, d);
|
|
|
|
// compute KBIP, D, R, adjust diagApprox
|
|
mj_makeImpedance(m, d);
|
|
}
|
|
|
|
|
|
// compute efc_AR
|
|
void mj_projectConstraint(const mjModel* m, mjData* d) {
|
|
int nefc = d->nefc, nv = m->nv;
|
|
|
|
// nothing to do
|
|
if (nefc == 0 || !mj_isDual(m)) {
|
|
return;
|
|
}
|
|
|
|
mj_markStack(d);
|
|
|
|
// inverse square root of D from inertia LDL decomposition
|
|
mjtNum* sqrtInvD = mjSTACKALLOC(d, nv, mjtNum);
|
|
for (int i=0; i < nv; i++) {
|
|
int diag = m->M_rowadr[i] + m->M_rownnz[i] - 1;
|
|
sqrtInvD[i] = 1 / mju_sqrt(d->qLD[diag]);
|
|
}
|
|
|
|
// sparse
|
|
if (mj_isSparse(m)) {
|
|
// compute B = backsubM2(J')' and its transpose
|
|
|
|
|
|
// === pre-count B_rownnz, B_rowadr, nB (total nonzeros)
|
|
|
|
// allocate B rownnz and rowadr
|
|
int* B_rownnz = mjSTACKALLOC(d, nefc, int);
|
|
int* B_rowadr = mjSTACKALLOC(d, nefc, int);
|
|
|
|
// markers for merged dofs, initialized to -1
|
|
int* marker = mjSTACKALLOC(d, nv, int);
|
|
mju_fillInt(marker, -1, nv);
|
|
|
|
B_rowadr[0] = 0;
|
|
for (int r=0; r < nefc; r++) {
|
|
// supernode: same sparsity as previous row
|
|
if (r > 0 && d->efc_J_rowsuper[r-1] > 0) {
|
|
B_rownnz[r] = B_rownnz[r-1];
|
|
}
|
|
|
|
// first row in supernode block: full chain traversal
|
|
else {
|
|
int nnz = 0;
|
|
|
|
// traverse row r of J in reverse, count unique nonzeros
|
|
int start = d->efc_J_rowadr[r];
|
|
int end = start + d->efc_J_rownnz[r];
|
|
for (int i=end-1; i >= start; i--) {
|
|
int j = d->efc_J_colind[i];
|
|
|
|
// if dof j is marked, it was already counted by a child dof: skip it
|
|
if (marker[j] == r) {
|
|
continue;
|
|
}
|
|
|
|
// traverse row j of M, marking new unique nonzeros
|
|
int nnzM = m->M_rownnz[j];
|
|
int adrM = m->M_rowadr[j];
|
|
for (int k=0; k < nnzM; k++) {
|
|
int c = m->M_colind[adrM + k];
|
|
if (marker[c] != r) {
|
|
marker[c] = r;
|
|
nnz++;
|
|
}
|
|
}
|
|
}
|
|
B_rownnz[r] = nnz;
|
|
}
|
|
|
|
// update rowadr
|
|
if (r < nefc - 1) {
|
|
B_rowadr[r+1] = B_rowadr[r] + B_rownnz[r];
|
|
}
|
|
}
|
|
|
|
// total non-zeros in B
|
|
int nB = B_rowadr[nefc-1] + B_rownnz[nefc-1];
|
|
|
|
|
|
// === fill in B column indices, copy values from J
|
|
|
|
// allocate values and column indices
|
|
mjtNum* B = mjSTACKALLOC(d, nB, mjtNum);
|
|
int* B_colind = mjSTACKALLOC(d, nB, int);
|
|
|
|
for (int r=0; r < nefc; r++) {
|
|
// supernode: copy column indices, only update values from J
|
|
if (r > 0 && d->efc_J_rowsuper[r-1] > 0) {
|
|
int prevAdr = B_rowadr[r-1];
|
|
int adrB = B_rowadr[r];
|
|
int nnzB = B_rownnz[r];
|
|
mju_copyInt(B_colind + adrB, B_colind + prevAdr, nnzB);
|
|
mju_zero(B + adrB, nnzB);
|
|
|
|
// copy J values into correct positions
|
|
int adrJ = d->efc_J_rowadr[r];
|
|
int jnnz = d->efc_J_rownnz[r];
|
|
int bi = 0, ji = 0;
|
|
while (ji < jnnz && bi < nnzB) {
|
|
if (B_colind[adrB+bi] == d->efc_J_colind[adrJ+ji]) {
|
|
B[adrB+bi] = d->efc_J[adrJ+ji];
|
|
bi++;
|
|
ji++;
|
|
} else {
|
|
bi++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// first row in supernode block: full chain completion
|
|
else {
|
|
int end = B_rowadr[r] + B_rownnz[r];
|
|
int adrJ = d->efc_J_rowadr[r];
|
|
int remainJ = d->efc_J_rownnz[r];
|
|
int nnzB = 0;
|
|
|
|
// complete chain in reverse
|
|
while (1) {
|
|
// get previous dof in src and dst
|
|
int prev_src = (remainJ > 0 ? d->efc_J_colind[adrJ + remainJ - 1] : -1);
|
|
int prev_dst = (nnzB > 0 ? m->dof_parentid[B_colind[end - nnzB]] : -1);
|
|
|
|
// both finished: break
|
|
if (prev_src < 0 && prev_dst < 0) {
|
|
break;
|
|
}
|
|
|
|
// add src
|
|
else if (prev_src >= prev_dst) {
|
|
nnzB++;
|
|
remainJ--;
|
|
B_colind[end - nnzB] = prev_src;
|
|
B[end - nnzB] = d->efc_J[adrJ + remainJ];
|
|
}
|
|
|
|
// add dst
|
|
else {
|
|
nnzB++;
|
|
B_colind[end - nnzB] = prev_dst;
|
|
B[end - nnzB] = 0;
|
|
}
|
|
}
|
|
|
|
// compare with B_rownnz: SHOULD NOT OCCUR
|
|
if (nnzB != B_rownnz[r]) {
|
|
mjERROR("pre and post-count of B_rownnz are not equal on row %d", r);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// === in-place sparse back-substitution: B <- B * M^-1/2
|
|
|
|
// sparse backsubM2 (half of LD back-substitution)
|
|
for (int r=0; r < nefc; r++) {
|
|
int nnzB = B_rownnz[r];
|
|
int adrB = B_rowadr[r];
|
|
|
|
// B(r,:) <- inv(L') * B(r,:), exploit sparsity of input vector
|
|
for (int i=adrB + nnzB-1; i >= adrB; i--) {
|
|
mjtNum b = B[i];
|
|
if (b == 0) {
|
|
continue;
|
|
}
|
|
int j = B_colind[i];
|
|
int adrC = m->M_rowadr[j];
|
|
mju_addToSclSparseInc(B + adrB, d->qLD + adrC,
|
|
nnzB, B_colind + adrB,
|
|
m->M_rownnz[j]-1, m->M_colind + adrC, -b);
|
|
}
|
|
|
|
// B(r,:) <- sqrt(inv(D)) * B(r,:)
|
|
for (int i=adrB; i < adrB + nnzB; i++) {
|
|
int j = B_colind[i];
|
|
B[i] *= sqrtInvD[j];
|
|
}
|
|
}
|
|
|
|
// B supernodes are identical to J supernodes
|
|
const int* B_rowsuper = d->efc_J_rowsuper;
|
|
|
|
// construct B transposed
|
|
int* BT_rownnz = mjSTACKALLOC(d, nv, int);
|
|
int* BT_rowadr = mjSTACKALLOC(d, nv, int);
|
|
int* BT_colind = mjSTACKALLOC(d, nB, int);
|
|
mjtNum* BT = mjSTACKALLOC(d, nB, mjtNum);
|
|
mju_transposeSparse(BT, B, nefc, nv,
|
|
BT_rownnz, BT_rowadr, BT_colind, NULL,
|
|
B_rownnz, B_rowadr, B_colind);
|
|
|
|
// allocate AR row nonzeros and addresses on arena
|
|
d->efc_AR_rownnz = mj_arenaAllocByte(d, sizeof(int) * nefc, _Alignof(int));
|
|
d->efc_AR_rowadr = mj_arenaAllocByte(d, sizeof(int) * nefc, _Alignof(int));
|
|
if (!d->efc_AR_rownnz || !d->efc_AR_rowadr) {
|
|
mj_warning(d, mjWARN_CNSTRFULL, d->narena);
|
|
mj_clearEfc(d);
|
|
d->parena = d->ncon * sizeof(mjContact);
|
|
mj_freeStack(d);
|
|
return;
|
|
}
|
|
|
|
int* diagind = mjSTACKALLOC(d, nefc, int);
|
|
d->nA = mju_sqrMatTDSparseSymbolic(
|
|
d->efc_AR_rownnz, d->efc_AR_rowadr, NULL, diagind,
|
|
nv, nefc, BT_rownnz, BT_rowadr, BT_colind,
|
|
B_rownnz, B_rowadr, B_colind, B_rowsuper, d);
|
|
|
|
// allocate A values and column indices on arena
|
|
d->efc_AR = mj_arenaAllocByte(d, sizeof(mjtNum) * d->nA, _Alignof(mjtNum));
|
|
d->efc_AR_colind = mj_arenaAllocByte(d, sizeof(int) * d->nA, _Alignof(int));
|
|
if (!d->efc_AR || !d->efc_AR_colind) {
|
|
mj_warning(d, mjWARN_CNSTRFULL, d->narena);
|
|
mj_clearEfc(d);
|
|
d->parena = d->ncon * sizeof(mjContact);
|
|
mj_freeStack(d);
|
|
return;
|
|
}
|
|
|
|
// A = B * B': symbolic phase
|
|
mju_sqrMatTDSparseSymbolic(
|
|
d->efc_AR_rownnz, d->efc_AR_rowadr, d->efc_AR_colind, diagind,
|
|
nv, nefc, BT_rownnz, BT_rowadr, BT_colind,
|
|
B_rownnz, B_rowadr, B_colind, B_rowsuper, d);
|
|
|
|
// A = B * B': numeric phase
|
|
mju_sqrMatTDSparseNumeric(
|
|
d->efc_AR, nefc, d->efc_AR_rownnz, d->efc_AR_rowadr,
|
|
d->efc_AR_colind, diagind, BT, BT_rownnz, BT_rowadr,
|
|
BT_colind, B, B_rownnz, B_rowadr, B_colind, B_rowsuper, NULL, d);
|
|
|
|
// AR = A + diag(R)
|
|
for (int i=0; i < nefc; i++) {
|
|
d->efc_AR[diagind[i]] += d->efc_R[i];
|
|
}
|
|
}
|
|
|
|
// dense
|
|
else {
|
|
d->nA = nefc * nefc;
|
|
|
|
// arena-allocate efc_AR
|
|
d->efc_AR = mj_arenaAllocByte(d, sizeof(mjtNum) * d->nA, _Alignof(mjtNum));
|
|
if (!d->efc_AR) {
|
|
mj_warning(d, mjWARN_CNSTRFULL, d->narena);
|
|
mj_clearEfc(d);
|
|
d->parena = d->ncon * sizeof(mjContact);
|
|
mj_freeStack(d);
|
|
return;
|
|
}
|
|
|
|
// space for B = backsubM2(J')' and its transpose
|
|
mjtNum* B = mjSTACKALLOC(d, nefc*nv, mjtNum);
|
|
mjtNum* BT = mjSTACKALLOC(d, nv*nefc, mjtNum);
|
|
|
|
// B = backsubM2(J')'
|
|
mj_solveM2(m, d, B, d->efc_J, sqrtInvD, nefc);
|
|
|
|
// construct BT
|
|
mju_transpose(BT, B, nefc, nv);
|
|
|
|
// AR = B * B'
|
|
mju_sqrMatTD(d->efc_AR, BT, NULL, nv, nefc);
|
|
|
|
// add R to diagonal of AR
|
|
for (int r=0; r < nefc; r++) {
|
|
d->efc_AR[r*(nefc+1)] += d->efc_R[r];
|
|
}
|
|
}
|
|
|
|
mj_freeStack(d);
|
|
}
|
|
|
|
|
|
// compute efc_vel, efc_aref
|
|
void mj_referenceConstraint(const mjModel* m, mjData* d) {
|
|
int nefc = d->nefc;
|
|
mjtNum* KBIP = d->efc_KBIP;
|
|
|
|
// compute efc_vel
|
|
mj_mulJacVec(m, d, d->efc_vel, d->qvel);
|
|
|
|
// compute aref = -B*vel - K*I*(pos-margin)
|
|
for (int i=0; i < nefc; i++) {
|
|
d->efc_aref[i] = -KBIP[4*i+1]*d->efc_vel[i]
|
|
-KBIP[4*i]*KBIP[4*i+2]*(d->efc_pos[i]-d->efc_margin[i]);
|
|
}
|
|
|
|
// subtract Jdot*v correction for connect/weld equality constraints
|
|
if (d->ne > 0) {
|
|
mj_Jdotv(m, d, d->efc_aref);
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------- update constraint state ---------------------------------------------
|
|
|
|
// compute efc_state, efc_force
|
|
// optional: cost(qacc) = s_hat(jar); cone Hessians
|
|
void mj_constraintUpdate_impl(int ne, int nf, int nefc,
|
|
const mjtNum* D, const mjtNum* R, const mjtNum* floss,
|
|
const mjtNum* jar, const int* type, const int* id,
|
|
mjContact* contact, int* state, mjtNum* force, mjtNum cost[1],
|
|
int flg_coneHessian) {
|
|
mjtNum s = 0;
|
|
|
|
// no constraints: clear cost, return
|
|
if (!nefc) {
|
|
if (cost) {
|
|
*cost = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// compute unconstrained efc_force
|
|
for (int i=0; i < nefc; i++) {
|
|
force[i] = -D[i]*jar[i];
|
|
}
|
|
|
|
// update constraints
|
|
for (int i=0; i < nefc; i++) {
|
|
// ==== equality
|
|
if (i < ne) {
|
|
if (cost) {
|
|
s += 0.5*D[i]*jar[i]*jar[i];
|
|
}
|
|
state[i] = mjCNSTRSTATE_QUADRATIC;
|
|
continue;
|
|
}
|
|
|
|
// ==== friction
|
|
if (i < ne + nf) {
|
|
// linear negative
|
|
if (jar[i] <= -R[i]*floss[i]) {
|
|
if (cost) {
|
|
s += -0.5*R[i]*floss[i]*floss[i] - floss[i]*jar[i];
|
|
}
|
|
|
|
force[i] = floss[i];
|
|
state[i] = mjCNSTRSTATE_LINEARNEG;
|
|
}
|
|
|
|
// linear positive
|
|
else if (jar[i] >= R[i]*floss[i]) {
|
|
if (cost) {
|
|
s += -0.5*R[i]*floss[i]*floss[i] + floss[i]*jar[i];
|
|
}
|
|
|
|
force[i] = -floss[i];
|
|
state[i] = mjCNSTRSTATE_LINEARPOS;
|
|
}
|
|
|
|
// quadratic
|
|
else {
|
|
if (cost) {
|
|
s += 0.5*D[i]*jar[i]*jar[i];
|
|
}
|
|
state[i] = mjCNSTRSTATE_QUADRATIC;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// ==== contact
|
|
|
|
// non-negative constraint
|
|
if (type[i] != mjCNSTR_CONTACT_ELLIPTIC) {
|
|
// constraint is satisfied: no cost
|
|
if (jar[i] >= 0) {
|
|
force[i] = 0;
|
|
|
|
state[i] = mjCNSTRSTATE_SATISFIED;
|
|
}
|
|
|
|
// quadratic
|
|
else {
|
|
if (cost) {
|
|
s += 0.5*D[i]*jar[i]*jar[i];
|
|
}
|
|
state[i] = mjCNSTRSTATE_QUADRATIC;
|
|
}
|
|
}
|
|
|
|
// contact with elliptic cone
|
|
else {
|
|
// get contact
|
|
mjContact* con = contact + id[i];
|
|
mjtNum mu = con->mu, *friction = con->friction;
|
|
int dim = con->dim;
|
|
|
|
// map to regular dual cone space
|
|
mjtNum U[6];
|
|
U[0] = jar[i]*mu;
|
|
for (int j=1; j < dim; j++) {
|
|
U[j] = jar[i+j]*friction[j-1];
|
|
}
|
|
|
|
// decompose into normal and tangent
|
|
mjtNum N = U[0];
|
|
mjtNum T = mju_norm(U+1, dim-1);
|
|
|
|
// top zone
|
|
if (N >= mu*T || (T <= 0 && N >= 0)) {
|
|
mju_zero(force+i, dim);
|
|
state[i] = mjCNSTRSTATE_SATISFIED;
|
|
}
|
|
|
|
// bottom zone
|
|
else if (mu*N+T <= 0 || (T <= 0 && N < 0)) {
|
|
if (cost) {
|
|
for (int j=0; j < dim; j++) {
|
|
s += 0.5*D[i+j]*jar[i+j]*jar[i+j];
|
|
}
|
|
}
|
|
state[i] = mjCNSTRSTATE_QUADRATIC;
|
|
}
|
|
|
|
// middle zone
|
|
else {
|
|
// cost: 0.5*D0/(mu*mu*(1+mu*mu))*(N-mu*T)^2
|
|
mjtNum Dm = D[i]/(mu*mu*(1+mu*mu));
|
|
mjtNum NmT = N - mu*T;
|
|
|
|
if (cost) {
|
|
s += 0.5*Dm*NmT*NmT;
|
|
}
|
|
|
|
// force: - ds/djar = dU/djar * ds/dU (dU/djar = diag(mu, friction))
|
|
force[i] = -Dm*NmT*mu;
|
|
for (int j=1; j < dim; j++) {
|
|
force[i+j] = -force[i]/T*U[j]*friction[j-1];
|
|
}
|
|
|
|
// set state
|
|
state[i] = mjCNSTRSTATE_CONE;
|
|
|
|
// cone Hessian
|
|
if (flg_coneHessian) {
|
|
// get Hessian pointer
|
|
mjtNum* H = contact[id[i]].H;
|
|
|
|
// set first row: (1, -mu/T * U)
|
|
mjtNum scl = -mu/T;
|
|
H[0] = 1;
|
|
for (int j=1; j < dim; j++) {
|
|
H[j] = scl*U[j];
|
|
}
|
|
|
|
// set upper block: mu*N/T^3 * U*U'
|
|
scl = mu*N/(T*T*T);
|
|
for (int k=1; k < dim; k++) {
|
|
for (int j=k; j < dim; j++) {
|
|
H[k*dim+j] = scl*U[j]*U[k];
|
|
}
|
|
}
|
|
|
|
// add to diagonal: (mu^2 - mu*N/T) * I
|
|
scl = mu*mu - mu*N/T;
|
|
for (int j=1; j < dim; j++) {
|
|
H[j*(dim+1)] += scl;
|
|
}
|
|
|
|
// pre and post multiply by diag(mu, friction), scale by Dm
|
|
for (int k=0; k < dim; k++) {
|
|
scl = Dm * (k == 0 ? mu : friction[k-1]);
|
|
for (int j=k; j < dim; j++) {
|
|
H[k*dim+j] *= scl * (j == 0 ? mu : friction[j-1]);
|
|
}
|
|
}
|
|
|
|
// make symmetric: copy upper into lower
|
|
for (int k=0; k < dim; k++) {
|
|
for (int j=k+1; j < dim; j++) {
|
|
H[j*dim+k] = H[k*dim+j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// replicate state in all cone dimensions
|
|
for (int j=1; j < dim; j++) {
|
|
state[i+j] = state[i];
|
|
}
|
|
|
|
// advance to end of contact
|
|
i += (dim-1);
|
|
}
|
|
}
|
|
|
|
// assign cost
|
|
if (cost) {
|
|
*cost = s;
|
|
}
|
|
}
|
|
|
|
|
|
// compute efc_state, efc_force, qfrc_constraint
|
|
// optional: cost(qacc) = s_hat(jar) where jar = Jac*qacc-aref; cone Hessians
|
|
void mj_constraintUpdate(const mjModel* m, mjData* d, const mjtNum* jar,
|
|
mjtNum cost[1], int flg_coneHessian) {
|
|
mj_constraintUpdate_impl(d->ne, d->nf, d->nefc, d->efc_D, d->efc_R, d->efc_frictionloss,
|
|
jar, d->efc_type, d->efc_id, d->contact, d->efc_state, d->efc_force,
|
|
cost, flg_coneHessian);
|
|
mj_mulJacTVec(m, d, d->qfrc_constraint, d->efc_force);
|
|
}
|