093b92acc1
The global node indices stored in face_gidx are now checked to ensure they are within the valid range of node numbers, raising an error if any index is out of bounds. PiperOrigin-RevId: 916025763 Change-Id: I1d2dcb11603137c337493541066b62b2ed2854c0
1372 lines
48 KiB
C
1372 lines
48 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_passive.h"
|
||
|
||
#include <stddef.h>
|
||
|
||
#include <mujoco/mjdata.h>
|
||
#include <mujoco/mjmacro.h>
|
||
#include <mujoco/mjmodel.h>
|
||
#include "engine/engine_callback.h"
|
||
#include "engine/engine_core_constraint.h"
|
||
#include "engine/engine_core_util.h"
|
||
#include "engine/engine_crossplatform.h"
|
||
#include "engine/engine_inline.h"
|
||
#include "engine/engine_memory.h"
|
||
#include "engine/engine_plugin.h"
|
||
#include "engine/engine_sleep.h"
|
||
#include "engine/engine_support.h"
|
||
#include "engine/engine_util_blas.h"
|
||
#include "engine/engine_util_errmem.h"
|
||
#include "engine/engine_util_misc.h"
|
||
#include "engine/engine_util_spatial.h"
|
||
|
||
|
||
//----------------------------- passive forces -----------------------------------------------------
|
||
|
||
// stiffness for passive contacts
|
||
static const mjtNum kContactStiffness = 1e4;
|
||
|
||
// local edge-based vertex indexing for 2D and 3D elements, 2D and 3D elements
|
||
// have 3 and 6 edges, respectively so the missing indexes are set to 0
|
||
static const int edges[2][6][2] = {{{1, 2}, {2, 0}, {0, 1}, {0, 0}, {0, 0}, {0, 0}},
|
||
{{0, 1}, {1, 2}, {2, 0}, {2, 3}, {0, 3}, {1, 3}}};
|
||
|
||
// compute gradient of squared lengths of edges belonging to a given element
|
||
static void inline GradSquaredLengths(mjtNum gradient[6][2][3],
|
||
const mjtNum* xpos,
|
||
const int vert[4],
|
||
const int edge[6][2],
|
||
int nedge) {
|
||
for (int e = 0; e < nedge; e++) {
|
||
for (int d = 0; d < 3; d++) {
|
||
gradient[e][0][d] = xpos[3*vert[edge[e][0]]+d] - xpos[3*vert[edge[e][1]]+d];
|
||
gradient[e][1][d] = xpos[3*vert[edge[e][1]]+d] - xpos[3*vert[edge[e][0]]+d];
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// passive forces for interpolated flex (stretch + bending)
|
||
static void mj_flexPassiveInterp(const mjModel* m, mjData* d, int f,
|
||
int enbl_spring, int enbl_damper) {
|
||
int stiffnessadr = m->flex_stiffnessadr[f];
|
||
if (stiffnessadr < 0) {
|
||
return;
|
||
}
|
||
|
||
mjtNum* k = m->flex_stiffness + stiffnessadr;
|
||
int nodenum = m->flex_nodenum[f];
|
||
|
||
int order = m->flex_interp[f];
|
||
int shell_mode = order < 0;
|
||
order = order < 0 ? -order : 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];
|
||
|
||
// determine element type: 2D boundary quads (shell) or 3D cells (volume)
|
||
int npe; // nodes per element
|
||
int nelem_fe; // total finite elements
|
||
|
||
if (shell_mode) {
|
||
npe = (order+1)*(order+1);
|
||
nelem_fe = 2*(cy*cz + cx*cz + cx*cy);
|
||
} else {
|
||
npe = (order+1)*(order+1)*(order+1);
|
||
nelem_fe = cx * cy * cz;
|
||
}
|
||
|
||
// check if we have any work to do
|
||
int has_stretch = k[0] != 0 && m->flex_edgeequality[f] != 3;
|
||
if (!has_stretch) {
|
||
return;
|
||
}
|
||
|
||
mj_markStack(d);
|
||
|
||
// allocate global arrays
|
||
mjtNum* xpos_g = mjSTACKALLOC(d, 3*nodenum, mjtNum);
|
||
mjtNum* vel_g = mjSTACKALLOC(d, 3*nodenum, mjtNum);
|
||
mjtNum* frc_g = mjSTACKALLOC(d, 3*nodenum, mjtNum);
|
||
mjtNum* dmp_g = mjSTACKALLOC(d, 3*nodenum, mjtNum);
|
||
mjtNum* xpos0 = m->flex_node0 + 3*m->flex_nodeadr[f];
|
||
int* bodyid = m->flex_nodebodyid + m->flex_nodeadr[f];
|
||
|
||
// gather global node positions and velocities (unrotated)
|
||
mju_flexGatherState(m, d, f, xpos_g, vel_g);
|
||
|
||
// zero global force accumulators
|
||
mju_zero(frc_g, 3*nodenum);
|
||
mju_zero(dmp_g, 3*nodenum);
|
||
|
||
// per-element arrays (sized for npe)
|
||
mjtNum* xpos_e = mjSTACKALLOC(d, 3*npe, mjtNum);
|
||
mjtNum* vel_e = mjSTACKALLOC(d, 3*npe, mjtNum);
|
||
mjtNum* xpos0_e = mjSTACKALLOC(d, 3*npe, mjtNum);
|
||
mjtNum* displ_e = mjSTACKALLOC(d, 3*npe, mjtNum);
|
||
mjtNum* frc_e = mjSTACKALLOC(d, 3*npe, mjtNum);
|
||
mjtNum* dmp_e = mjSTACKALLOC(d, 3*npe, mjtNum);
|
||
int* gindices = mjSTACKALLOC(d, npe, int);
|
||
|
||
// -------------------- stretch forces --------------------
|
||
if (has_stretch) {
|
||
for (int fe = 0; fe < nelem_fe; fe++) {
|
||
// get element stiffness matrix
|
||
mjtNum* k_elem = k + fe * 3*npe * 3*npe;
|
||
|
||
// skip empty elements (zero stiffness)
|
||
if (k_elem[0] == 0) {
|
||
continue;
|
||
}
|
||
|
||
// gather element-local node data and compute corotational rotation
|
||
mjtNum quat[4];
|
||
if (shell_mode) {
|
||
mju_flexGatherFaceState(order, cx, cy, cz, fe, xpos_g, vel_g, xpos0,
|
||
xpos_e, vel_e, xpos0_e, gindices, quat);
|
||
} else {
|
||
int ci = fe / (cy * cz);
|
||
int cj = (fe / cz) % cy;
|
||
int ck = fe % cz;
|
||
mju_flexGatherCellState(order, cy, cz, ci, cj, ck, xpos_g, vel_g,
|
||
xpos0, xpos_e, vel_e, xpos0_e, gindices,
|
||
quat);
|
||
}
|
||
|
||
// rotate to corotational frame
|
||
for (int n = 0; n < npe; n++) {
|
||
mju_rotVecQuat(xpos_e+3*n, xpos_e+3*n, quat);
|
||
mju_rotVecQuat(vel_e+3*n, vel_e+3*n, quat);
|
||
}
|
||
|
||
// compute displacement
|
||
for (int n = 0; n < npe; n++) {
|
||
mji_addScl3(displ_e+3*n, xpos_e+3*n, xpos0_e+3*n, -1);
|
||
}
|
||
|
||
// compute force in corotational frame
|
||
if (enbl_spring) {
|
||
mju_mulMatVec(frc_e, k_elem, displ_e, 3*npe, 3*npe);
|
||
}
|
||
if (enbl_damper) {
|
||
mju_mulMatVec(dmp_e, k_elem, vel_e, 3*npe, 3*npe);
|
||
}
|
||
|
||
// rotate back to global frame and scatter using node indices
|
||
mju_negQuat(quat, quat);
|
||
for (int n = 0; n < npe; n++) {
|
||
mjtNum qfrc[3], qdmp[3];
|
||
mji_rotVecQuat(qfrc, frc_e+3*n, quat);
|
||
mji_rotVecQuat(qdmp, dmp_e+3*n, quat);
|
||
int gidx = gindices[n];
|
||
if (enbl_spring) {
|
||
mji_addTo3(frc_g + 3*gidx, qfrc);
|
||
}
|
||
if (enbl_damper) {
|
||
mji_addTo3(dmp_g + 3*gidx, qdmp);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// apply accumulated forces to bodies
|
||
for (int i = 0; i < nodenum; i++) {
|
||
mju_scl3(dmp_g+3*i, dmp_g+3*i, m->flex_damping[f]);
|
||
int bid = bodyid[i];
|
||
int nidx = i + m->flex_nodeadr[f];
|
||
|
||
// fast path: node at body origin (not pinned), direct DOF write
|
||
if (m->body_dofnum[bid] > 0 &&
|
||
(m->flex_centered[f] ||
|
||
(m->flex_node[3*nidx+0] == 0 &&
|
||
m->flex_node[3*nidx+1] == 0 &&
|
||
m->flex_node[3*nidx+2] == 0))) {
|
||
if (enbl_spring) mji_addTo3(d->qfrc_spring + m->body_dofadr[bid], frc_g+3*i);
|
||
if (enbl_damper) mji_addTo3(d->qfrc_damper + m->body_dofadr[bid], dmp_g+3*i);
|
||
} else {
|
||
if (enbl_spring) mj_applyFT(m, d, frc_g+3*i, 0, xpos_g+3*i, bid, d->qfrc_spring);
|
||
if (enbl_damper) mj_applyFT(m, d, dmp_g+3*i, 0, xpos_g+3*i, bid, d->qfrc_damper);
|
||
}
|
||
}
|
||
|
||
mj_freeStack(d);
|
||
}
|
||
|
||
|
||
// 2D shape function gradient: dir=0 returns dphi(s0,l0)*phi(s1,l1),
|
||
// dir=1 returns phi(s0,l0)*dphi(s1,l1)
|
||
static inline mjtNum mju_dphi2D(mjtNum s0, int l0, mjtNum s1, int l1,
|
||
int order, int dir) {
|
||
if (dir == 0) {
|
||
return mju_flexDphi(s0, l0, order) * mju_flexPhi(s1, l1, order);
|
||
} else {
|
||
return mju_flexPhi(s0, l0, order) * mju_flexDphi(s1, l1, order);
|
||
}
|
||
}
|
||
|
||
|
||
// per-edge data layout in flex_bending for interpolated shell bending
|
||
#define BEND_EDGE_SIZE 10
|
||
|
||
|
||
// passive bending forces for interpolated flex shell
|
||
//
|
||
// Current approach: discrete Crouzeix-Raviart — point evaluation of the
|
||
// normal jump at each edge midpoint, with energy E = D/(2h) * |Δn - Δn₀|² * l.
|
||
//
|
||
// TODO(quaglino): upgrade to a Galerkin formulation by precomputing a bending
|
||
// stiffness matrix K_bend in the corotated frame using edge normal-jump residuals
|
||
// as DOFs and Gauss integration over each face. At runtime, rotate the residual
|
||
// vector into the corotated frame, multiply by K_bend, and rotate back. This
|
||
// would give implicit derivatives for free (K_bend is constant) and better
|
||
// accuracy for elements with varying curvature.
|
||
static void mj_flexPassiveBendInterp(const mjModel* m, mjData* d, int f,
|
||
int enbl_spring, int enbl_damper) {
|
||
if (m->flex_interp[f] >= 0) {
|
||
return;
|
||
}
|
||
|
||
int bendingadr = m->flex_bendingadr[f];
|
||
if (bendingadr < 0) {
|
||
return;
|
||
}
|
||
|
||
// read bending edge data
|
||
const mjtNum* bdata = m->flex_bending + bendingadr;
|
||
int nedge = (int)bdata[0];
|
||
if (nedge == 0) return;
|
||
|
||
int order = -m->flex_interp[f]; // shell_mode: interp < 0
|
||
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 = (order+1)*(order+1);
|
||
int nodenum = m->flex_nodenum[f];
|
||
|
||
mj_markStack(d);
|
||
|
||
// gather global state
|
||
mjtNum* xpos_g = mjSTACKALLOC(d, 3*nodenum, mjtNum);
|
||
mjtNum* vel_g = mjSTACKALLOC(d, 3*nodenum, mjtNum);
|
||
mjtNum* frc_g = mjSTACKALLOC(d, 3*nodenum, mjtNum);
|
||
mjtNum* dmp_g = mjSTACKALLOC(d, 3*nodenum, mjtNum);
|
||
mju_flexGatherState(m, d, f, xpos_g, vel_g);
|
||
mju_zero(frc_g, 3*nodenum);
|
||
mju_zero(dmp_g, 3*nodenum);
|
||
|
||
// precompute per-face cache: each face appears in multiple bending edges,
|
||
// so caching avoids redundant position gathering and quaternion computation
|
||
int nfaces = 2*(cy*cz + cx*cz + cx*cy);
|
||
mjtNum* face_xpos = mjSTACKALLOC(d, nfaces * 3*npe, mjtNum);
|
||
int* face_gidx = mjSTACKALLOC(d, nfaces * npe, int);
|
||
mjtNum* face_quat = mjSTACKALLOC(d, nfaces * 4, mjtNum);
|
||
for (int fi = 0; fi < nfaces; fi++) {
|
||
mju_flexGatherFaceState(order, cx, cy, cz, fi, xpos_g,
|
||
NULL, NULL,
|
||
face_xpos + fi * 3*npe, NULL, NULL,
|
||
face_gidx + fi * npe, face_quat + fi * 4);
|
||
}
|
||
|
||
// check cached node indices are in bounds
|
||
for (int i = 0; i < nfaces * npe; i++) {
|
||
if (face_gidx[i] < 0 || face_gidx[i] >= nodenum) {
|
||
mjERROR("cached node index out of range: face_gidx[%d]=%d, nodenum=%d",
|
||
i, face_gidx[i], nodenum);
|
||
}
|
||
}
|
||
|
||
// per-edge temporaries
|
||
mjtNum* xpos_A = mjSTACKALLOC(d, 3*npe, mjtNum);
|
||
mjtNum* xpos_B = mjSTACKALLOC(d, 3*npe, mjtNum);
|
||
int* gidx_A = mjSTACKALLOC(d, npe, int);
|
||
int* gidx_B = mjSTACKALLOC(d, npe, int);
|
||
|
||
mjtNum kD = m->opt.timestep > 0 ? m->flex_damping[f] / m->opt.timestep : 0;
|
||
if (enbl_damper && kD > 0) {
|
||
mju_warning("Bending damping is not yet supported for interpolated flex shells.");
|
||
}
|
||
|
||
for (int e = 0; e < nedge; e++) {
|
||
const mjtNum* edata = bdata + 1 + e * BEND_EDGE_SIZE;
|
||
int fe_A = (int)edata[0];
|
||
int fe_B = (int)edata[1];
|
||
mjtNum local_A[2] = {edata[2], edata[3]};
|
||
mjtNum local_B[2] = {edata[4], edata[5]};
|
||
mjtNum stiffness = edata[6];
|
||
mjtNum dn0[3] = {edata[7], edata[8], edata[9]};
|
||
|
||
if (stiffness == 0) continue;
|
||
|
||
// look up cached face data instead of recomputing
|
||
mju_copy(xpos_A, face_xpos + fe_A * 3*npe, 3*npe);
|
||
mju_copy(xpos_B, face_xpos + fe_B * 3*npe, 3*npe);
|
||
mju_copyInt(gidx_A, face_gidx + fe_A * npe, npe);
|
||
mju_copyInt(gidx_B, face_gidx + fe_B * npe, npe);
|
||
mjtNum quat_A[4], quat_B[4];
|
||
mju_copy(quat_A, face_quat + fe_A * 4, 4);
|
||
mju_copy(quat_B, face_quat + fe_B * 4, 4);
|
||
|
||
|
||
// compute deformed normals at edge midpoint
|
||
mjtNum n_A[3], t1_A[3], t2_A[3];
|
||
mjtNum n_B[3], t1_B[3], t2_B[3];
|
||
mju_flexFaceNormal2D(n_A, t1_A, t2_A, order, xpos_A, local_A);
|
||
mju_flexFaceNormal2D(n_B, t1_B, t2_B, order, xpos_B, local_B);
|
||
|
||
// normalize normals
|
||
mjtNum len_A = mju_norm3(n_A);
|
||
mjtNum len_B = mju_norm3(n_B);
|
||
if (len_A < mjMINVAL || len_B < mjMINVAL) continue;
|
||
mjtNum inv_A = 1.0 / len_A;
|
||
mjtNum inv_B = 1.0 / len_B;
|
||
n_A[0] *= inv_A; n_A[1] *= inv_A; n_A[2] *= inv_A;
|
||
n_B[0] *= inv_B; n_B[1] *= inv_B; n_B[2] *= inv_B;
|
||
|
||
// average corotational frame: symmetric under face swap
|
||
// quat_A and quat_B encode R^{-1}; average them, then negate to get R_avg
|
||
// ensure quaternions are in the same hemisphere before averaging
|
||
if (mju_dot(quat_A, quat_B, 4) < 0) {
|
||
mju_scl(quat_B, quat_B, -1, 4);
|
||
}
|
||
mjtNum quat_avg[4];
|
||
mju_add(quat_avg, quat_A, quat_B, 4);
|
||
mju_normalize(quat_avg, 4); // NLERP = SLERP at t=0.5 for two quaternions
|
||
// negate to get R_avg (from rest frame to current frame)
|
||
mju_negQuat(quat_avg, quat_avg);
|
||
|
||
// rotate dn0 from rest frame to current frame using average corotational R
|
||
mjtNum dn0_rot[3];
|
||
mju_rotVecQuat(dn0_rot, dn0, quat_avg);
|
||
|
||
// normal jump residual: r = (n_A - n_B) - R_avg * dn0
|
||
mjtNum r[3];
|
||
mji_sub3(r, n_A, n_B);
|
||
r[0] -= dn0_rot[0]; r[1] -= dn0_rot[1]; r[2] -= dn0_rot[2];
|
||
|
||
// --- spring force ---
|
||
if (enbl_spring) {
|
||
// w_A = P_A * r = (r - n_A*(n_A.r)) / |c_A|
|
||
mjtNum dot_A = mju_dot3(n_A, r);
|
||
mjtNum w_A[3];
|
||
w_A[0] = (r[0] - n_A[0]*dot_A) * inv_A;
|
||
w_A[1] = (r[1] - n_A[1]*dot_A) * inv_A;
|
||
w_A[2] = (r[2] - n_A[2]*dot_A) * inv_A;
|
||
|
||
mjtNum dot_B = mju_dot3(n_B, r);
|
||
mjtNum w_B[3];
|
||
w_B[0] = (r[0] - n_B[0]*dot_B) * inv_B;
|
||
w_B[1] = (r[1] - n_B[1]*dot_B) * inv_B;
|
||
w_B[2] = (r[2] - n_B[2]*dot_B) * inv_B;
|
||
|
||
// precompute cross products: wA x t2_A, wA x t1_A
|
||
mjtNum wAt2[3], wAt1[3], wBt2[3], wBt1[3];
|
||
mji_cross(wAt2, w_A, t2_A);
|
||
mji_cross(wAt1, w_A, t1_A);
|
||
mji_cross(wBt2, w_B, t2_B);
|
||
mji_cross(wBt1, w_B, t1_B);
|
||
|
||
// force on face A nodes: f_k = stiffness * [g0_k * (wA x t2_A) -
|
||
// g1_k * (wA x t1_A)]
|
||
int idx = 0;
|
||
for (int l0 = 0; l0 <= order; l0++) {
|
||
for (int l1 = 0; l1 <= order; l1++) {
|
||
mjtNum g0 = mju_dphi2D(local_A[0], l0, local_A[1], l1, order, 0);
|
||
mjtNum g1 = mju_dphi2D(local_A[0], l0, local_A[1], l1, order, 1);
|
||
int gi = gidx_A[idx];
|
||
for (int j = 0; j < 3; j++) {
|
||
frc_g[3*gi + j] += stiffness * (g0 * wAt2[j] - g1 * wAt1[j]);
|
||
}
|
||
idx++;
|
||
}
|
||
}
|
||
|
||
// force on face B nodes (negative sign: ∂Δn/∂x = -∂n_B/∂x)
|
||
idx = 0;
|
||
for (int l0 = 0; l0 <= order; l0++) {
|
||
for (int l1 = 0; l1 <= order; l1++) {
|
||
mjtNum g0 = mju_dphi2D(local_B[0], l0, local_B[1], l1, order, 0);
|
||
mjtNum g1 = mju_dphi2D(local_B[0], l0, local_B[1], l1, order, 1);
|
||
int gi = gidx_B[idx];
|
||
for (int j = 0; j < 3; j++) {
|
||
frc_g[3*gi + j] -= stiffness * (g0 * wBt2[j] - g1 * wBt1[j]);
|
||
}
|
||
idx++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- damping force ---
|
||
// TODO(quaglino): bending damping is disabled because at corner edges
|
||
// with nonzero rest normal jump, dΔn/dt = ω × Δn₀ ≠ 0 under rigid
|
||
// rotation, producing anti-conservative forces. A correct implementation
|
||
// would subtract the rigid-body velocity component before computing the
|
||
// damping residual.
|
||
}
|
||
|
||
// apply accumulated forces to bodies
|
||
int* bodyid = m->flex_nodebodyid + m->flex_nodeadr[f];
|
||
for (int i = 0; i < nodenum; i++) {
|
||
int bid = bodyid[i];
|
||
int nidx = i + m->flex_nodeadr[f];
|
||
|
||
// fast path: node at body origin, direct DOF write
|
||
if (m->body_dofnum[bid] > 0 &&
|
||
(m->flex_centered[f] ||
|
||
(m->flex_node[3*nidx+0] == 0 &&
|
||
m->flex_node[3*nidx+1] == 0 &&
|
||
m->flex_node[3*nidx+2] == 0))) {
|
||
if (enbl_spring) mji_addTo3(d->qfrc_spring + m->body_dofadr[bid], frc_g+3*i);
|
||
if (enbl_damper) mji_addTo3(d->qfrc_damper + m->body_dofadr[bid], dmp_g+3*i);
|
||
} else {
|
||
if (enbl_spring) mj_applyFT(m, d, frc_g+3*i, 0, xpos_g+3*i, bid, d->qfrc_spring);
|
||
if (enbl_damper) mj_applyFT(m, d, dmp_g+3*i, 0, xpos_g+3*i, bid, d->qfrc_damper);
|
||
}
|
||
}
|
||
|
||
mj_freeStack(d);
|
||
}
|
||
|
||
|
||
// passive forces for flex bending
|
||
static void mj_flexPassiveBend(const mjModel* m, mjData* d, int f,
|
||
int enbl_spring, int enbl_damper) {
|
||
if (m->flex_dim[f] != 2) {
|
||
return;
|
||
}
|
||
|
||
int bendingadr = m->flex_bendingadr[f];
|
||
if (bendingadr < 0) {
|
||
return;
|
||
}
|
||
|
||
int edgenum = m->flex_edgenum[f];
|
||
mjtNum* xpos = d->flexvert_xpos + 3*m->flex_vertadr[f];
|
||
int* bodyid = m->flex_vertbodyid + m->flex_vertadr[f];
|
||
mjtNum* b = m->flex_bending + bendingadr;
|
||
|
||
for (int e = 0; e < edgenum; e++) {
|
||
const int* edge = m->flex_edge + 2*(e+m->flex_edgeadr[f]);
|
||
const int* flap = m->flex_edgeflap + 2*(e+m->flex_edgeadr[f]);
|
||
int v[4] = {edge[0], edge[1], flap[0], flap[1]};
|
||
if (v[3] == -1) {
|
||
// skip boundary edges
|
||
continue;
|
||
}
|
||
|
||
// flap edges
|
||
mjtNum ed[3][3];
|
||
mji_sub3(ed[0], xpos + 3*v[1], xpos + 3*v[0]);
|
||
mji_sub3(ed[1], xpos + 3*v[2], xpos + 3*v[0]);
|
||
mji_sub3(ed[2], xpos + 3*v[3], xpos + 3*v[0]);
|
||
|
||
// forces at the vertices due to curved reference
|
||
mjtNum frc[4][3];
|
||
mji_cross(frc[1], ed[1], ed[2]);
|
||
mji_cross(frc[2], ed[2], ed[0]);
|
||
mji_cross(frc[3], ed[0], ed[1]);
|
||
frc[0][0] = -(frc[1][0] + frc[2][0] + frc[3][0]);
|
||
frc[0][1] = -(frc[1][1] + frc[2][1] + frc[3][1]);
|
||
frc[0][2] = -(frc[1][2] + frc[2][2] + frc[3][2]);
|
||
|
||
// velocities
|
||
mjtNum* vel[4];
|
||
for (int i = 0; i < 4; i++) {
|
||
vel[i] = d->qvel + m->body_dofadr[bodyid[v[i]]];
|
||
}
|
||
|
||
// force
|
||
mjtNum spring[12] = {0};
|
||
mjtNum damper[12] = {0};
|
||
for (int i = 0; i < 4; i++) {
|
||
for (int x = 0; x < 3; x++) {
|
||
for (int j = 0; j < 4; j++) {
|
||
// thin plate bending force
|
||
if (enbl_spring) spring[3*i+x] += b[17*e+4*i+j] * xpos[3*v[j]+x];
|
||
|
||
// thin plate damping force
|
||
// TODO: do not assume DOFs are in the world frame
|
||
if (enbl_damper) damper[3*i+x] += b[17*e+4*i+j] * vel[j][x];
|
||
}
|
||
|
||
// curved reference contribution
|
||
if (enbl_spring) spring[3*i+x] += b[17*e+16] * frc[i][x];
|
||
}
|
||
}
|
||
|
||
// insert into global force
|
||
for (int i = 0; i < 4; i++) {
|
||
int bid = bodyid[v[i]];
|
||
int body_dofnum = m->body_dofnum[bid];
|
||
int body_dofadr = m->body_dofadr[bid];
|
||
for (int x = 0; x < body_dofnum; x++) {
|
||
if (enbl_spring) d->qfrc_spring[body_dofadr+x] -= spring[3*i+x];
|
||
if (enbl_damper) d->qfrc_damper[body_dofadr+x] -= damper[3*i+x] * m->flex_damping[f];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// passive forces for flex stretch
|
||
static void mj_flexPassiveStretch(const mjModel* m, mjData* d, int f,
|
||
int enbl_spring, int enbl_damper) {
|
||
int stiffnessadr = m->flex_stiffnessadr[f];
|
||
if (stiffnessadr < 0) {
|
||
return;
|
||
}
|
||
mjtNum* k = m->flex_stiffness + stiffnessadr;
|
||
if (k[0] == 0) {
|
||
return;
|
||
}
|
||
|
||
int dim = m->flex_dim[f];
|
||
int nedge = (dim == 2) ? 3 : 6;
|
||
int nvert = (dim == 2) ? 3 : 4;
|
||
const int* elem = m->flex_elem + m->flex_elemdataadr[f];
|
||
const int* edgeelem = m->flex_elemedge + m->flex_elemedgeadr[f];
|
||
mjtNum* xpos = d->flexvert_xpos + 3*m->flex_vertadr[f];
|
||
mjtNum* vel = d->flexedge_velocity + m->flex_edgeadr[f];
|
||
mjtNum* deformed = d->flexedge_length + m->flex_edgeadr[f];
|
||
mjtNum* reference = m->flexedge_length0 + m->flex_edgeadr[f];
|
||
int* bodyid = m->flex_vertbodyid + m->flex_vertadr[f];
|
||
mjtNum kD = m->opt.timestep > 0 ? m->flex_damping[f] / m->opt.timestep : 0;
|
||
|
||
mj_markStack(d);
|
||
mjtNum* qfrc = mjSTACKALLOC(d, 3*m->flex_vertnum[f], mjtNum);
|
||
mju_zero(qfrc, 3*m->flex_vertnum[f]);
|
||
|
||
// compute force element-by-element
|
||
int elemnum = m->flex_elemnum[f];
|
||
for (int t = 0; t < elemnum; t++) {
|
||
const int* vert = elem + (dim+1) * t;
|
||
|
||
// compute length gradient with respect to dofs
|
||
mjtNum gradient[6][2][3];
|
||
GradSquaredLengths(gradient, xpos, vert, edges[dim-2], nedge);
|
||
|
||
// we add generalized Rayleigh damping as described in Section 5.2 of
|
||
// Kharevych et al., "Geometric, Variational Integrators for Computer
|
||
// Animation" http://multires.caltech.edu/pubs/DiscreteLagrangian.pdf
|
||
|
||
// extract elongation of edges belonging to this element
|
||
mjtNum elongation[6];
|
||
for (int e = 0; e < nedge; e++) {
|
||
int idx = edgeelem[t * nedge + e];
|
||
mjtNum previous = deformed[idx] - vel[idx] * m->opt.timestep;
|
||
elongation[e] = deformed[idx]*deformed[idx] - reference[idx]*reference[idx] +
|
||
(deformed[idx]*deformed[idx] - previous*previous) * kD;
|
||
}
|
||
|
||
// unpack triangular representation
|
||
mjtNum metric[36];
|
||
int id = 0;
|
||
for (int ed1 = 0; ed1 < nedge; ed1++) {
|
||
for (int ed2 = ed1; ed2 < nedge; ed2++) {
|
||
metric[nedge*ed1 + ed2] = k[21*t + id];
|
||
metric[nedge*ed2 + ed1] = k[21*t + id++];
|
||
}
|
||
}
|
||
|
||
// compute local force
|
||
mjtNum force[12] = {0};
|
||
for (int ed1 = 0; ed1 < nedge; ed1++) {
|
||
for (int ed2 = 0; ed2 < nedge; ed2++) {
|
||
for (int i = 0; i < 2; i++) {
|
||
for (int x = 0; x < 3; x++) {
|
||
force[3 * edges[dim-2][ed2][i] + x] -=
|
||
elongation[ed1] * gradient[ed2][i][x] *
|
||
metric[nedge * ed1 + ed2];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// insert into global force
|
||
for (int i = 0; i < nvert; i++) {
|
||
for (int x = 0; x < 3; x++) {
|
||
qfrc[3*vert[i]+x] += force[3*i+x];
|
||
}
|
||
}
|
||
}
|
||
|
||
// insert force into qfrc_passive, straightforward for simple bodies,
|
||
// need to distribute the force in case of pinned vertices
|
||
for (int v = 0; v < m->flex_vertnum[f]; v++) {
|
||
int bid = bodyid[v];
|
||
if (m->body_simple[bid] != 2) {
|
||
// this should only occur for pinned flex vertices
|
||
mj_applyFT(m, d, qfrc + 3*v, 0, xpos + 3*v, bid, d->qfrc_spring);
|
||
} else {
|
||
int body_dofnum = m->body_dofnum[bid];
|
||
int body_dofadr = m->body_dofadr[bid];
|
||
for (int x = 0; x < body_dofnum; x++) {
|
||
d->qfrc_spring[body_dofadr+x] += qfrc[3*v+x];
|
||
}
|
||
}
|
||
}
|
||
|
||
mj_freeStack(d);
|
||
}
|
||
|
||
|
||
// spring and damper forces
|
||
static void mj_springdamper(const mjModel* m, mjData* d) {
|
||
int nv = m->nv, ntendon = m->ntendon;
|
||
int enbl_spring = !mjDISABLED(mjDSBL_SPRING);
|
||
int enbl_damper = !mjDISABLED(mjDSBL_DAMPER);
|
||
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->ntree_awake < m->ntree;
|
||
int nbody = sleep_filter ? d->nbody_awake : m->nbody;
|
||
|
||
// joint-level springs
|
||
if (enbl_spring) {
|
||
for (int b=0; b < nbody; b++) {
|
||
int i = sleep_filter ? d->body_awake_ind[b] : b;
|
||
int jnt_start = m->body_jntadr[i];
|
||
int jnt_end = jnt_start + m->body_jntnum[i];
|
||
for (int j=jnt_start; j < jnt_end; j++) {
|
||
mjtNum stiffness = m->jnt_stiffness[j];
|
||
const mjtNum* spoly = m->jnt_stiffnesspoly + mjNPOLY*j;
|
||
|
||
if (stiffness == 0 && mju_isZero(spoly, mjNPOLY)) {
|
||
continue;
|
||
}
|
||
|
||
int padr = m->jnt_qposadr[j];
|
||
int dadr = m->jnt_dofadr[j];
|
||
|
||
switch ((mjtJoint) m->jnt_type[j]) {
|
||
case mjJNT_FREE:
|
||
// apply force
|
||
{
|
||
mjtNum dif[3];
|
||
mji_sub3(dif, d->qpos+padr, m->qpos_spring+padr);
|
||
mjtNum r = mju_norm3(dif);
|
||
mjtNum k = mju_polyForce(stiffness, spoly, r, mjNPOLY, 0);
|
||
mji_addToScl3(d->qfrc_spring + dadr, dif, -k);
|
||
}
|
||
|
||
// continue with rotations
|
||
dadr += 3;
|
||
padr += 3;
|
||
mjFALLTHROUGH;
|
||
|
||
case mjJNT_BALL:
|
||
{
|
||
// convert quaternion difference into angular "velocity"
|
||
mjtNum dif[3], quat[4];
|
||
mji_copy4(quat, d->qpos+padr);
|
||
mju_normalize4(quat);
|
||
mji_subQuat(dif, quat, m->qpos_spring + padr);
|
||
mjtNum r = mju_norm3(dif);
|
||
mjtNum k = mju_polyForce(stiffness, spoly, r, mjNPOLY, 0);
|
||
|
||
// apply torque
|
||
mji_addToScl3(d->qfrc_spring + dadr, dif, -k);
|
||
}
|
||
break;
|
||
|
||
case mjJNT_SLIDE:
|
||
case mjJNT_HINGE:
|
||
{
|
||
// apply force or torque
|
||
mjtNum x = d->qpos[padr] - m->qpos_spring[padr];
|
||
d->qfrc_spring[dadr] = -x * mju_polyForce(stiffness, spoly, x, mjNPOLY, 0);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// dof-level dampers
|
||
if (enbl_damper) {
|
||
int nv_awake = sleep_filter ? d->nv_awake : nv;
|
||
for (int j = 0; j < nv_awake; j++) {
|
||
int i = sleep_filter ? d->dof_awake_ind[j] : j;
|
||
mjtNum poly[mjNPOLY];
|
||
mju_copy(poly, m->dof_dampingpoly + mjNPOLY*i, mjNPOLY);
|
||
mjtNum damping = m->dof_damping[i]
|
||
+ mj_actuatorDamping(m, mjOBJ_JOINT, m->dof_jntid[i], poly);
|
||
if (damping != 0 || !mju_isZero(poly, mjNPOLY)) {
|
||
mjtNum v = d->qvel[i];
|
||
d->qfrc_damper[i] = -v * mju_polyForce(damping, poly, v, mjNPOLY, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
// flex elasticity
|
||
for (int f=0; f < m->nflex; f++) {
|
||
if (m->flex_dim[f] == 1 || m->flex_rigid[f]) {
|
||
continue;
|
||
}
|
||
|
||
if (m->flex_interp[f]) {
|
||
// interpolated flex: stretch forces
|
||
mj_flexPassiveInterp(m, d, f, enbl_spring, enbl_damper);
|
||
|
||
// interpolated shell bending forces
|
||
mj_flexPassiveBendInterp(m, d, f, enbl_spring, enbl_damper);
|
||
} else {
|
||
// add bending forces
|
||
mj_flexPassiveBend(m, d, f, enbl_spring, enbl_damper);
|
||
|
||
// stretch forces
|
||
mj_flexPassiveStretch(m, d, f, enbl_spring, enbl_damper);
|
||
}
|
||
}
|
||
|
||
// flexedge-level spring-dampers
|
||
for (int f=0; f < m->nflex; f++) {
|
||
mjtNum stiffness = enbl_spring ? m->flex_edgestiffness[f] : 0;
|
||
mjtNum damping = enbl_damper ? m->flex_edgedamping[f] : 0;
|
||
|
||
// disabled or rigid: nothing to do
|
||
if (m->flex_rigid[f] || (stiffness == 0 && damping == 0)) {
|
||
continue;
|
||
}
|
||
|
||
// process non-rigid edges of this flex (global edge index)
|
||
int edgeend = m->flex_edgeadr[f] + m->flex_edgenum[f];
|
||
for (int e=m->flex_edgeadr[f]; e < edgeend; e++) {
|
||
// skip rigid
|
||
if (m->flexedge_rigid[e]) {
|
||
continue;
|
||
}
|
||
|
||
// compute spring-damper force along edge
|
||
mjtNum frc_spring = stiffness * (m->flexedge_length0[e] - d->flexedge_length[e]);
|
||
mjtNum frc_damper = -damping * d->flexedge_velocity[e];
|
||
|
||
// transform to joint torque, add to qfrc_{spring, damper}: always sparse
|
||
int end = m->flexedge_J_rowadr[e] + m->flexedge_J_rownnz[e];
|
||
for (int j=m->flexedge_J_rowadr[e]; j < end; j++) {
|
||
int colind = m->flexedge_J_colind[j];
|
||
mjtNum J = d->flexedge_J[j];
|
||
d->qfrc_spring[colind] += J * frc_spring;
|
||
d->qfrc_damper[colind] += J * frc_damper;
|
||
}
|
||
}
|
||
}
|
||
|
||
// tendon-level spring-dampers
|
||
for (int i=0; i < ntendon; i++) {
|
||
// skip sleeping or static tendon
|
||
if (sleep_filter && mj_sleepState(m, d, mjOBJ_TENDON, i) != mjS_AWAKE) {
|
||
continue;
|
||
}
|
||
|
||
mjtNum stiffness = 0;
|
||
const mjtNum* spoly = NULL;
|
||
if (enbl_spring) {
|
||
stiffness = m->tendon_stiffness[i];
|
||
spoly = m->tendon_stiffnesspoly + mjNPOLY*i;
|
||
}
|
||
|
||
mjtNum damping = 0;
|
||
mjtNum dpoly[mjNPOLY] = {0};
|
||
if (enbl_damper) {
|
||
mju_copy(dpoly, m->tendon_dampingpoly + mjNPOLY*i, mjNPOLY);
|
||
damping = m->tendon_damping[i] + mj_actuatorDamping(m, mjOBJ_TENDON, i, dpoly);
|
||
}
|
||
|
||
// both zero: nothing to do
|
||
if (stiffness == 0 && (!enbl_spring || mju_isZero(spoly, mjNPOLY)) &&
|
||
damping == 0 && mju_isZero(dpoly, mjNPOLY)) {
|
||
continue;
|
||
}
|
||
|
||
// compute spring force along tendon
|
||
mjtNum length = d->ten_length[i];
|
||
mjtNum lower = m->tendon_lengthspring[2*i];
|
||
mjtNum upper = m->tendon_lengthspring[2*i+1];
|
||
mjtNum x = (length > upper) ? length - upper : (length < lower) ? length - lower : 0;
|
||
mjtNum frc_spring = enbl_spring ? -x * mju_polyForce(stiffness, spoly, x, mjNPOLY, 0) : 0;
|
||
|
||
// compute damper force along tendon
|
||
mjtNum v = d->ten_velocity[i];
|
||
mjtNum frc_damper = enbl_damper ? -v * mju_polyForce(damping, dpoly, v, mjNPOLY, 1) : 0;
|
||
|
||
// transform to joint torque, add to qfrc_{spring, damper}
|
||
if (frc_spring || frc_damper) {
|
||
int end = m->ten_J_rowadr[i] + m->ten_J_rownnz[i];
|
||
for (int j=m->ten_J_rowadr[i]; j < end; j++) {
|
||
int k = m->ten_J_colind[j];
|
||
mjtNum J = d->ten_J[j];
|
||
d->qfrc_spring[k] += J * frc_spring;
|
||
d->qfrc_damper[k] += J * frc_damper;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// body-level gravity compensation, return 1 if any, 0 otherwise
|
||
static int mj_gravcomp(const mjModel* m, mjData* d) {
|
||
if (!m->ngravcomp || mjDISABLED(mjDSBL_GRAVITY) || mju_norm3(m->opt.gravity) == 0) {
|
||
return 0;
|
||
}
|
||
|
||
int has_gravcomp = 0;
|
||
mjtNum force[3], torque[3]={0};
|
||
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->nbody_awake < m->nbody;
|
||
int nbody = sleep_filter ? d->nbody_awake : m->nbody;
|
||
|
||
// apply per-body gravity compensation
|
||
for (int b=1; b < nbody; b++) {
|
||
int i = sleep_filter ? d->body_awake_ind[b] : b;
|
||
if (m->body_gravcomp[i]) {
|
||
has_gravcomp = 1;
|
||
mji_scl3(force, m->opt.gravity, -(m->body_mass[i]*m->body_gravcomp[i]));
|
||
mj_applyFT(m, d, force, torque, d->xipos+3*i, i, d->qfrc_gravcomp);
|
||
}
|
||
}
|
||
|
||
return has_gravcomp;
|
||
}
|
||
|
||
|
||
// fluid forces
|
||
static int mj_fluid(const mjModel* m, mjData* d) {
|
||
// no fluid forces: early return
|
||
if (!m->opt.viscosity && !m->opt.density) {
|
||
return 0;
|
||
}
|
||
|
||
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->nbody_awake < m->nbody;
|
||
int nbody = sleep_filter ? d->nbody_awake : m->nbody;
|
||
|
||
for (int b=0; b < nbody; b++) {
|
||
int i = sleep_filter ? d->body_awake_ind[b] : b;
|
||
|
||
if (m->body_mass[i] < mjMINVAL) {
|
||
continue;
|
||
}
|
||
|
||
// if any child geom uses the ellipsoid model, inertia-box model is disabled for parent body
|
||
int use_ellipsoid_model = 0;
|
||
int geomnum = m->body_geomnum[i];
|
||
for (int j=0; j < geomnum && use_ellipsoid_model == 0; j++) {
|
||
const int geomid = m->body_geomadr[i] + j;
|
||
use_ellipsoid_model += (m->geom_fluid[mjNFLUID*geomid] > 0);
|
||
}
|
||
|
||
if (use_ellipsoid_model) {
|
||
mj_ellipsoidFluidModel(m, d, i);
|
||
} else {
|
||
mj_inertiaBoxFluidModel(m, d, i);
|
||
}
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
|
||
// passive contact forces
|
||
int mj_contactPassive(const mjModel* m, mjData* d) {
|
||
int ncon = d->ncon, issparse = mj_isSparse(m);
|
||
int dim, NV, nv = m->nv, *chain = NULL;
|
||
mjtNum *jac, *jacdif, *jacdifp, *jacdifr, *jac1p, *jac2p, *jac1r, *jac2r, *qfrc;
|
||
mjContact* con;
|
||
int has_contact = 0;
|
||
|
||
if (mjDISABLED(mjDSBL_CONTACT) || ncon == 0 || nv == 0) {
|
||
return 0;
|
||
}
|
||
|
||
// early return if no contact to be included
|
||
for (int i=0; i < ncon; i++) {
|
||
if (d->contact[i].exclude != 4) {
|
||
continue;
|
||
}
|
||
has_contact = 1;
|
||
}
|
||
|
||
if (!has_contact) {
|
||
return 0;
|
||
}
|
||
|
||
// allocate Jacobian
|
||
mj_markStack(d);
|
||
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);
|
||
qfrc = mjSTACKALLOC(d, 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 != 4) {
|
||
continue;
|
||
}
|
||
|
||
// get contact info, safe efc_address
|
||
con = d->contact + i;
|
||
dim = con->dim;
|
||
con->efc_address = -1;
|
||
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);
|
||
}
|
||
|
||
// compute passive contact force (dim = 1)
|
||
mjtNum scl = -kContactStiffness*con->dist;
|
||
if (!issparse) {
|
||
mju_addToScl(d->qfrc_spring, jac, scl, nv);
|
||
} else {
|
||
mju_scl(qfrc, jac, scl, NV);
|
||
for (int j=0; j < NV; j++) {
|
||
d->qfrc_spring[chain[j]] += qfrc[j];
|
||
}
|
||
}
|
||
}
|
||
|
||
mj_freeStack(d);
|
||
return has_contact;
|
||
}
|
||
|
||
|
||
// all passive forces
|
||
void mj_passive(const mjModel* m, mjData* d) {
|
||
int sleep_filter = mjENABLED(mjENBL_SLEEP) && d->nv_awake < m->nv;
|
||
int nv = sleep_filter ? d->nv_awake : m->nv;
|
||
const int* dof_awake_ind = sleep_filter ? d->dof_awake_ind : NULL;
|
||
|
||
// clear passive force vectors for awake dofs
|
||
if (sleep_filter) {
|
||
mju_zeroInd(d->qfrc_spring, nv, dof_awake_ind);
|
||
mju_zeroInd(d->qfrc_damper, nv, dof_awake_ind);
|
||
mju_zeroInd(d->qfrc_gravcomp, nv, dof_awake_ind);
|
||
mju_zeroInd(d->qfrc_fluid, nv, dof_awake_ind);
|
||
mju_zeroInd(d->qfrc_passive, nv, dof_awake_ind);
|
||
} else {
|
||
mju_zero(d->qfrc_spring, nv);
|
||
mju_zero(d->qfrc_damper, nv);
|
||
mju_zero(d->qfrc_gravcomp, nv);
|
||
mju_zero(d->qfrc_fluid, nv);
|
||
mju_zero(d->qfrc_passive, nv);
|
||
}
|
||
|
||
// both spring and damping disabled: skip all passive forces
|
||
if (mjDISABLED(mjDSBL_SPRING) && mjDISABLED(mjDSBL_DAMPER)) {
|
||
return;
|
||
}
|
||
|
||
// springs and dampers
|
||
mj_springdamper(m, d);
|
||
|
||
// gravity compensation
|
||
int has_gravcomp = mj_gravcomp(m, d);
|
||
|
||
// fluid forces
|
||
int has_fluid = mj_fluid(m, d);
|
||
|
||
// contact forces
|
||
mj_contactPassive(m, d);
|
||
|
||
// add passive forces into qfrc_passive
|
||
if (sleep_filter) {
|
||
mju_addInd(d->qfrc_passive, d->qfrc_spring, d->qfrc_damper, dof_awake_ind, nv);
|
||
} else {
|
||
mju_add(d->qfrc_passive, d->qfrc_spring, d->qfrc_damper, nv);
|
||
}
|
||
|
||
if (has_fluid) {
|
||
if (sleep_filter) {
|
||
mju_addToInd(d->qfrc_passive, d->qfrc_fluid, dof_awake_ind, nv);
|
||
} else {
|
||
mju_addTo(d->qfrc_passive, d->qfrc_fluid, nv);
|
||
}
|
||
}
|
||
|
||
if (has_gravcomp) {
|
||
int ndof = sleep_filter ? d->nv_awake : nv;
|
||
for (int v=0; v < ndof; v++) {
|
||
int dof = sleep_filter ? d->dof_awake_ind[v] : v;
|
||
|
||
// add gravity compensation force unless added via actuators
|
||
if (!m->jnt_actgravcomp[m->dof_jntid[dof]]) {
|
||
d->qfrc_passive[dof] += d->qfrc_gravcomp[dof];
|
||
}
|
||
}
|
||
}
|
||
|
||
// user callback: add custom passive forces
|
||
if (mjcb_passive) {
|
||
mjcb_passive(m, d);
|
||
}
|
||
|
||
// plugin: add custom passive forces
|
||
if (m->nplugin) {
|
||
const int nslot = mjp_pluginCount();
|
||
|
||
// iterate over plugins, call compute if type is mjPLUGIN_PASSIVE
|
||
for (int i=0; i < m->nplugin; i++) {
|
||
const int slot = m->plugin[i];
|
||
const mjpPlugin* plugin = mjp_getPluginAtSlotUnsafe(slot, nslot);
|
||
if (!plugin) {
|
||
mjERROR("invalid plugin slot: %d", slot);
|
||
}
|
||
if (plugin->capabilityflags & mjPLUGIN_PASSIVE) {
|
||
if (!plugin->compute) {
|
||
mjERROR("`compute` is a null function pointer for plugin at slot %d", slot);
|
||
}
|
||
plugin->compute(m, d, i, mjPLUGIN_PASSIVE);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//---------------------------------- fluid models --------------------------------------------------
|
||
|
||
// fluid forces based on inertia-box approximation
|
||
void mj_inertiaBoxFluidModel(const mjModel* m, mjData* d, int i) {
|
||
mjtNum lvel[6], wind[6], lwind[6], lfrc[6], bfrc[6], box[3], diam, *inertia;
|
||
inertia = m->body_inertia + 3*i;
|
||
box[0] = mju_sqrt(mju_max(mjMINVAL,
|
||
(inertia[1] + inertia[2] - inertia[0])) / m->body_mass[i] * 6.0);
|
||
box[1] = mju_sqrt(mju_max(mjMINVAL,
|
||
(inertia[0] + inertia[2] - inertia[1])) / m->body_mass[i] * 6.0);
|
||
box[2] = mju_sqrt(mju_max(mjMINVAL,
|
||
(inertia[0] + inertia[1] - inertia[2])) / m->body_mass[i] * 6.0);
|
||
|
||
// map from CoM-centered to local body-centered 6D velocity
|
||
mj_objectVelocity(m, d, mjOBJ_BODY, i, lvel, 1);
|
||
|
||
// compute wind in local coordinates
|
||
mju_zero(wind, 6);
|
||
mji_copy3(wind+3, m->opt.wind);
|
||
mju_transformSpatial(lwind, wind, 0, d->xipos+3*i,
|
||
d->subtree_com+3*m->body_rootid[i], d->ximat+9*i);
|
||
|
||
// subtract translational component from body velocity
|
||
mji_subFrom3(lvel+3, lwind+3);
|
||
mju_zero(lfrc, 6);
|
||
|
||
// set viscous force and torque
|
||
if (m->opt.viscosity > 0) {
|
||
// diameter of sphere approximation
|
||
diam = (box[0] + box[1] + box[2])/3.0;
|
||
|
||
// angular viscosity
|
||
mji_scl3(lfrc, lvel, -mjPI*diam*diam*diam*m->opt.viscosity);
|
||
|
||
// linear viscosity
|
||
mji_scl3(lfrc+3, lvel+3, -3.0*mjPI*diam*m->opt.viscosity);
|
||
}
|
||
|
||
// add lift and drag force and torque
|
||
if (m->opt.density > 0) {
|
||
// force
|
||
lfrc[3] -= 0.5*m->opt.density*box[1]*box[2]*mju_abs(lvel[3])*lvel[3];
|
||
lfrc[4] -= 0.5*m->opt.density*box[0]*box[2]*mju_abs(lvel[4])*lvel[4];
|
||
lfrc[5] -= 0.5*m->opt.density*box[0]*box[1]*mju_abs(lvel[5])*lvel[5];
|
||
|
||
// torque
|
||
lfrc[0] -= m->opt.density*box[0]*(box[1]*box[1]*box[1]*box[1]+box[2]*box[2]*box[2]*box[2])*
|
||
mju_abs(lvel[0])*lvel[0]/64.0;
|
||
lfrc[1] -= m->opt.density*box[1]*(box[0]*box[0]*box[0]*box[0]+box[2]*box[2]*box[2]*box[2])*
|
||
mju_abs(lvel[1])*lvel[1]/64.0;
|
||
lfrc[2] -= m->opt.density*box[2]*(box[0]*box[0]*box[0]*box[0]+box[1]*box[1]*box[1]*box[1])*
|
||
mju_abs(lvel[2])*lvel[2]/64.0;
|
||
}
|
||
// rotate to global orientation: lfrc -> bfrc
|
||
mji_mulMatVec3(bfrc, d->ximat+9*i, lfrc);
|
||
mji_mulMatVec3(bfrc+3, d->ximat+9*i, lfrc+3);
|
||
|
||
// apply force and torque to body com
|
||
mj_applyFT(m, d, bfrc+3, bfrc, d->xipos+3*i, i, d->qfrc_fluid);
|
||
}
|
||
|
||
|
||
// fluid forces based on ellipsoid approximation
|
||
void mj_ellipsoidFluidModel(const mjModel* m, mjData* d, int bodyid) {
|
||
mjtNum lvel[6], wind[6], lwind[6], lfrc[6], bfrc[6];
|
||
mjtNum geom_interaction_coef, magnus_lift_coef, kutta_lift_coef;
|
||
mjtNum semiaxes[3], virtual_mass[3], virtual_inertia[3];
|
||
mjtNum blunt_drag_coef, slender_drag_coef, ang_drag_coef;
|
||
|
||
for (int j=0; j < m->body_geomnum[bodyid]; j++) {
|
||
const int geomid = m->body_geomadr[bodyid] + j;
|
||
|
||
mju_geomSemiAxes(semiaxes, m->geom_size + 3*geomid, m->geom_type[geomid]);
|
||
|
||
readFluidGeomInteraction(
|
||
m->geom_fluid + mjNFLUID*geomid, &geom_interaction_coef,
|
||
&blunt_drag_coef, &slender_drag_coef, &ang_drag_coef,
|
||
&kutta_lift_coef, &magnus_lift_coef,
|
||
virtual_mass, virtual_inertia);
|
||
|
||
// scales all forces, read from MJCF as boolean (0.0 or 1.0)
|
||
if (geom_interaction_coef == 0.0) {
|
||
continue;
|
||
}
|
||
|
||
// map from CoM-centered to local body-centered 6D velocity
|
||
mj_objectVelocity(m, d, mjOBJ_GEOM, geomid, lvel, 1);
|
||
|
||
// compute wind in local coordinates
|
||
mju_zero(wind, 6);
|
||
mji_copy3(wind+3, m->opt.wind);
|
||
mju_transformSpatial(lwind, wind, 0,
|
||
d->geom_xpos + 3*geomid, // Frame of ref's origin.
|
||
d->subtree_com + 3*m->body_rootid[bodyid],
|
||
d->geom_xmat + 9*geomid); // Frame of ref's orientation.
|
||
|
||
// subtract translational component from grom velocity
|
||
mji_subFrom3(lvel+3, lwind+3);
|
||
|
||
// initialize viscous force and torque
|
||
mju_zero(lfrc, 6);
|
||
|
||
// added-mass forces and torques
|
||
mj_addedMassForces(lvel, NULL, m->opt.density, virtual_mass, virtual_inertia, lfrc);
|
||
|
||
// lift force orthogonal to lvel from Kutta-Joukowski theorem
|
||
mj_viscousForces(lvel, m->opt.density, m->opt.viscosity, semiaxes, magnus_lift_coef,
|
||
kutta_lift_coef, blunt_drag_coef, slender_drag_coef, ang_drag_coef, lfrc);
|
||
|
||
// scale by geom_interaction_coef (1.0 by default)
|
||
mju_scl(lfrc, lfrc, geom_interaction_coef, 6);
|
||
|
||
// rotate to global orientation: lfrc -> bfrc
|
||
mji_mulMatVec3(bfrc, d->geom_xmat + 9*geomid, lfrc);
|
||
mji_mulMatVec3(bfrc+3, d->geom_xmat + 9*geomid, lfrc+3);
|
||
|
||
// apply force and torque to body com
|
||
mj_applyFT(m, d, bfrc+3, bfrc,
|
||
d->geom_xpos + 3*geomid, // point where FT is generated
|
||
bodyid, d->qfrc_fluid);
|
||
}
|
||
}
|
||
|
||
|
||
// compute forces due to fluid mass moving with the body
|
||
void mj_addedMassForces(const mjtNum local_vels[6], const mjtNum local_accels[6],
|
||
const mjtNum fluid_density, const mjtNum virtual_mass[3],
|
||
const mjtNum virtual_inertia[3], mjtNum local_force[6])
|
||
{
|
||
const mjtNum lin_vel[3] = {local_vels[3], local_vels[4], local_vels[5]};
|
||
const mjtNum ang_vel[3] = {local_vels[0], local_vels[1], local_vels[2]};
|
||
const mjtNum virtual_lin_mom[3] = {
|
||
fluid_density * virtual_mass[0] * lin_vel[0],
|
||
fluid_density * virtual_mass[1] * lin_vel[1],
|
||
fluid_density * virtual_mass[2] * lin_vel[2]
|
||
};
|
||
const mjtNum virtual_ang_mom[3] = {
|
||
fluid_density * virtual_inertia[0] * ang_vel[0],
|
||
fluid_density * virtual_inertia[1] * ang_vel[1],
|
||
fluid_density * virtual_inertia[2] * ang_vel[2]
|
||
};
|
||
|
||
// disabled due to dependency on qacc but included for completeness
|
||
if (local_accels) {
|
||
local_force[0] -= fluid_density * virtual_inertia[0] * local_accels[0];
|
||
local_force[1] -= fluid_density * virtual_inertia[1] * local_accels[1];
|
||
local_force[2] -= fluid_density * virtual_inertia[2] * local_accels[2];
|
||
local_force[3] -= fluid_density * virtual_mass[0] * local_accels[3];
|
||
local_force[4] -= fluid_density * virtual_mass[1] * local_accels[4];
|
||
local_force[5] -= fluid_density * virtual_mass[2] * local_accels[5];
|
||
}
|
||
|
||
mjtNum added_mass_force[3], added_mass_torque1[3], added_mass_torque2[3];
|
||
mji_cross(added_mass_force, virtual_lin_mom, ang_vel);
|
||
mji_cross(added_mass_torque1, virtual_lin_mom, lin_vel);
|
||
mji_cross(added_mass_torque2, virtual_ang_mom, ang_vel);
|
||
|
||
mji_addTo3(local_force, added_mass_torque1);
|
||
mji_addTo3(local_force, added_mass_torque2);
|
||
mji_addTo3(local_force+3, added_mass_force);
|
||
}
|
||
|
||
|
||
// inlined helper functions
|
||
static inline mjtNum mji_pow4(const mjtNum val) {
|
||
return (val*val)*(val*val);
|
||
}
|
||
|
||
static inline mjtNum mji_pow2(const mjtNum val) {
|
||
return val*val;
|
||
}
|
||
|
||
static inline mjtNum mji_ellipsoid_max_moment(const mjtNum size[3], const int dir) {
|
||
const mjtNum d0 = size[dir], d1 = size[(dir+1) % 3], d2 = size[(dir+2) % 3];
|
||
return 8.0/15.0 * mjPI * d0 * mji_pow4(mju_max(d1, d2));
|
||
}
|
||
|
||
|
||
// lift and drag forces due to motion in the fluid
|
||
void mj_viscousForces(
|
||
const mjtNum local_vels[6], const mjtNum fluid_density,
|
||
const mjtNum fluid_viscosity, const mjtNum size[3],
|
||
const mjtNum magnus_lift_coef, const mjtNum kutta_lift_coef,
|
||
const mjtNum blunt_drag_coef, const mjtNum slender_drag_coef,
|
||
const mjtNum ang_drag_coef, mjtNum local_force[6])
|
||
{
|
||
const mjtNum lin_vel[3] = {local_vels[3], local_vels[4], local_vels[5]};
|
||
const mjtNum ang_vel[3] = {local_vels[0], local_vels[1], local_vels[2]};
|
||
const mjtNum volume = 4.0/3.0 * mjPI * size[0] * size[1] * size[2];
|
||
const mjtNum d_max = mju_max(mju_max(size[0], size[1]), size[2]);
|
||
const mjtNum d_min = mju_min(mju_min(size[0], size[1]), size[2]);
|
||
const mjtNum d_mid = size[0] + size[1] + size[2] - d_max - d_min;
|
||
const mjtNum A_max = mjPI * d_max * d_mid;
|
||
|
||
mjtNum magnus_force[3];
|
||
mji_cross(magnus_force, ang_vel, lin_vel);
|
||
magnus_force[0] *= magnus_lift_coef * fluid_density * volume;
|
||
magnus_force[1] *= magnus_lift_coef * fluid_density * volume;
|
||
magnus_force[2] *= magnus_lift_coef * fluid_density * volume;
|
||
|
||
// the dot product between velocity and the normal to the cross-section that
|
||
// defines the body's projection along velocity is proj_num/sqrt(proj_denom)
|
||
const mjtNum proj_denom = mji_pow4(size[1] * size[2]) * mji_pow2(lin_vel[0]) +
|
||
mji_pow4(size[2] * size[0]) * mji_pow2(lin_vel[1]) +
|
||
mji_pow4(size[0] * size[1]) * mji_pow2(lin_vel[2]);
|
||
const mjtNum proj_num = mji_pow2(size[1] * size[2] * lin_vel[0]) +
|
||
mji_pow2(size[2] * size[0] * lin_vel[1]) +
|
||
mji_pow2(size[0] * size[1] * lin_vel[2]);
|
||
|
||
// projected surface in the direction of the velocity
|
||
const mjtNum A_proj = mjPI * mju_sqrt(proj_denom/mju_max(mjMINVAL, proj_num));
|
||
|
||
// not-unit normal to ellipsoid's projected area in the direction of velocity
|
||
const mjtNum norm[3] = {
|
||
mji_pow2(size[1] * size[2]) * lin_vel[0],
|
||
mji_pow2(size[2] * size[0]) * lin_vel[1],
|
||
mji_pow2(size[0] * size[1]) * lin_vel[2]
|
||
};
|
||
|
||
// cosine between velocity and normal to the surface
|
||
// divided by proj_denom instead of sqrt(proj_denom) to account for skipped normalization in norm
|
||
const mjtNum cos_alpha = proj_num / mju_max(
|
||
mjMINVAL, mju_norm3(lin_vel) * proj_denom);
|
||
mjtNum kutta_circ[3];
|
||
mji_cross(kutta_circ, norm, lin_vel);
|
||
kutta_circ[0] *= kutta_lift_coef * fluid_density * cos_alpha * A_proj;
|
||
kutta_circ[1] *= kutta_lift_coef * fluid_density * cos_alpha * A_proj;
|
||
kutta_circ[2] *= kutta_lift_coef * fluid_density * cos_alpha * A_proj;
|
||
mjtNum kutta_force[3];
|
||
mji_cross(kutta_force, kutta_circ, lin_vel);
|
||
|
||
// viscous force and torque in Stokes flow, analytical for spherical bodies
|
||
const mjtNum eq_sphere_D = 2.0/3.0 * (size[0] + size[1] + size[2]);
|
||
const mjtNum lin_visc_force_coef = 3.0 * mjPI * eq_sphere_D;
|
||
const mjtNum lin_visc_torq_coef = mjPI * eq_sphere_D*eq_sphere_D*eq_sphere_D;
|
||
|
||
// moments of inertia used to compute angular quadratic drag
|
||
const mjtNum I_max = 8.0/15.0 * mjPI * d_mid * mji_pow4(d_max);
|
||
const mjtNum II[3] = {
|
||
mji_ellipsoid_max_moment(size, 0),
|
||
mji_ellipsoid_max_moment(size, 1),
|
||
mji_ellipsoid_max_moment(size, 2)
|
||
};
|
||
const mjtNum mom_visc[3] = {
|
||
ang_vel[0] * (ang_drag_coef*II[0] + slender_drag_coef*(I_max - II[0])),
|
||
ang_vel[1] * (ang_drag_coef*II[1] + slender_drag_coef*(I_max - II[1])),
|
||
ang_vel[2] * (ang_drag_coef*II[2] + slender_drag_coef*(I_max - II[2]))
|
||
};
|
||
|
||
const mjtNum drag_lin_coef = // linear plus quadratic
|
||
fluid_viscosity*lin_visc_force_coef + fluid_density*mju_norm3(lin_vel)*(
|
||
A_proj*blunt_drag_coef + slender_drag_coef*(A_max - A_proj));
|
||
const mjtNum drag_ang_coef = // linear plus quadratic
|
||
fluid_viscosity * lin_visc_torq_coef +
|
||
fluid_density * mju_norm3(mom_visc);
|
||
|
||
local_force[0] -= drag_ang_coef * ang_vel[0];
|
||
local_force[1] -= drag_ang_coef * ang_vel[1];
|
||
local_force[2] -= drag_ang_coef * ang_vel[2];
|
||
local_force[3] += magnus_force[0] + kutta_force[0] - drag_lin_coef*lin_vel[0];
|
||
local_force[4] += magnus_force[1] + kutta_force[1] - drag_lin_coef*lin_vel[1];
|
||
local_force[5] += magnus_force[2] + kutta_force[2] - drag_lin_coef*lin_vel[2];
|
||
}
|
||
|
||
|
||
// read the geom_fluid_coefs array into its constituent parts
|
||
void readFluidGeomInteraction(const mjtNum* geom_fluid_coefs,
|
||
mjtNum* geom_fluid_coef,
|
||
mjtNum* blunt_drag_coef,
|
||
mjtNum* slender_drag_coef,
|
||
mjtNum* ang_drag_coef,
|
||
mjtNum* kutta_lift_coef,
|
||
mjtNum* magnus_lift_coef,
|
||
mjtNum virtual_mass[3],
|
||
mjtNum virtual_inertia[3]) {
|
||
int i = 0;
|
||
geom_fluid_coef[0] = geom_fluid_coefs[i++];
|
||
blunt_drag_coef[0] = geom_fluid_coefs[i++];
|
||
slender_drag_coef[0] = geom_fluid_coefs[i++];
|
||
ang_drag_coef[0] = geom_fluid_coefs[i++];
|
||
kutta_lift_coef[0] = geom_fluid_coefs[i++];
|
||
magnus_lift_coef[0] = geom_fluid_coefs[i++];
|
||
virtual_mass[0] = geom_fluid_coefs[i++];
|
||
virtual_mass[1] = geom_fluid_coefs[i++];
|
||
virtual_mass[2] = geom_fluid_coefs[i++];
|
||
virtual_inertia[0] = geom_fluid_coefs[i++];
|
||
virtual_inertia[1] = geom_fluid_coefs[i++];
|
||
virtual_inertia[2] = geom_fluid_coefs[i++];
|
||
if (i != mjNFLUID) {
|
||
mjERROR("wrong number of entries.");
|
||
}
|
||
}
|
||
|
||
|
||
// write components into geom_fluid_coefs array
|
||
void writeFluidGeomInteraction (mjtNum* geom_fluid_coefs,
|
||
const mjtNum* geom_fluid_coef,
|
||
const mjtNum* blunt_drag_coef,
|
||
const mjtNum* slender_drag_coef,
|
||
const mjtNum* ang_drag_coef,
|
||
const mjtNum* kutta_lift_coef,
|
||
const mjtNum* magnus_lift_coef,
|
||
const mjtNum virtual_mass[3],
|
||
const mjtNum virtual_inertia[3]) {
|
||
int i = 0;
|
||
geom_fluid_coefs[i++] = geom_fluid_coef[0];
|
||
geom_fluid_coefs[i++] = blunt_drag_coef[0];
|
||
geom_fluid_coefs[i++] = slender_drag_coef[0];
|
||
geom_fluid_coefs[i++] = ang_drag_coef[0];
|
||
geom_fluid_coefs[i++] = kutta_lift_coef[0];
|
||
geom_fluid_coefs[i++] = magnus_lift_coef[0];
|
||
geom_fluid_coefs[i++] = virtual_mass[0];
|
||
geom_fluid_coefs[i++] = virtual_mass[1];
|
||
geom_fluid_coefs[i++] = virtual_mass[2];
|
||
geom_fluid_coefs[i++] = virtual_inertia[0];
|
||
geom_fluid_coefs[i++] = virtual_inertia[1];
|
||
geom_fluid_coefs[i++] = virtual_inertia[2];
|
||
if (i != mjNFLUID) {
|
||
mjERROR("wrong number of entries.");
|
||
}
|
||
}
|