Files
Mujoco_WASM/src/engine/engine_collision_convex.c
T
Yuval Tassa a9ee497e33 Add spaces around comparison operators.
PiperOrigin-RevId: 573620198
Change-Id: Icf295cc0dd381a4a7f0e2c94f2e12b499193e862
2023-10-15 07:40:10 -07:00

1268 lines
35 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_collision_convex.h"
#include <math.h>
#include <stddef.h>
#include <ccd/ccd.h>
#include <ccd/vec3.h>
#include <mujoco/mjdata.h>
#include <mujoco/mjmacro.h>
#include <mujoco/mjmodel.h>
#include "engine/engine_collision_primitive.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"
// ccd center function
void mjccd_center(const void *obj, ccd_vec3_t *center) {
const mjtCCD* ccd = (const mjtCCD*)obj;
int g = ccd->geom;
int f = ccd->flex;
int e = ccd->elem;
int v = ccd->vert;
// return geom position
if (g >= 0) {
mju_copy3(center->v, ccd->data->geom_xpos + 3*g);
}
// return flex element position
else if (e >= 0) {
mju_copy3(center->v, ccd->data->flexelem_aabb + 6*(ccd->model->flex_elemadr[f]+e));
}
// return flex vertex position
else {
mju_copy3(center->v, ccd->data->flexvert_xpos + 3*(ccd->model->flex_vertadr[f]+v));
}
}
// ccd support function
void mjccd_support(const void *obj, const ccd_vec3_t *_dir, ccd_vec3_t *vec) {
const mjtCCD* ccd = (const mjtCCD*)obj;
const mjModel* m = ccd->model;
const mjData* d = ccd->data;
int g = ccd->geom;
//-------------------------- flex element or vertex -----------------------------
if (g < 0) {
int f = ccd->flex;
int dim = m->flex_dim[f];
mjtNum *res = vec->v;
const mjtNum *dir = _dir->v;
// flex element
if (ccd->elem >= 0) {
int e = ccd->elem;
const int* edata = m->flex_elem + m->flex_elemdataadr[f] + e*(dim+1);
const mjtNum* vert = d->flexvert_xpos + 3*m->flex_vertadr[f];
// find element vertex with largest projection along dir
mju_copy3(res, vert+3*edata[0]);
mjtNum best = mju_dot3(res, dir);
for (int i=1; i <= dim; i++) {
mjtNum dot = mju_dot3(vert+3*edata[i], dir);
// better vertex found: assign
if (dot > best) {
best = dot;
mju_copy3(res, vert+3*edata[i]);
}
}
// add radius and margin/2
mju_addToScl3(res, dir, m->flex_radius[f] + 0.5*ccd->margin);
return;
}
// flex vertex
else {
const mjtNum* vert = d->flexvert_xpos + 3*(m->flex_vertadr[f] + ccd->vert);
mju_addScl3(res, vert, dir, m->flex_radius[f] + 0.5*ccd->margin);
return;
}
}
//-------------------------- geom -------------------------------------------
float* vertdata;
int ibest, graphadr, numvert, change, locid;
int *vert_edgeadr, *vert_globalid, *edge_localid;
mjtNum tmp, vdot;
const mjtNum* size = m->geom_size+3*g; // geom sizes
mjtNum dir[3]; // direction in geom local frame
mjtNum res[3]; // result in geom local frame
// rotate dir to geom local frame
mju_rotVecMatT(dir, _dir->v, d->geom_xmat+9*g);
// compute result according to geom type
switch ((mjtGeom) m->geom_type[g]) {
case mjGEOM_SPHERE:
mju_scl3(res, dir, size[0]);
break;
case mjGEOM_CAPSULE:
// start with sphere
mju_scl3(res, dir, size[0]);
// add cylinder contribution
res[2] += mju_sign(dir[2]) * size[1];
break;
case mjGEOM_ELLIPSOID:
// find support point on unit sphere: scale dir by ellipsoid sizes and renormalize
for (int i=0; i < 3; i++) {
res[i] = dir[i] * size[i];
}
mju_normalize3(res);
// transform to ellipsoid
for (int i=0; i < 3; i++) {
res[i] *= size[i];
}
break;
case mjGEOM_CYLINDER:
// set result in XY plane: support on circle
tmp = mju_sqrt(dir[0]*dir[0] + dir[1]*dir[1]);
if (tmp > mjMINVAL) {
res[0] = dir[0]/tmp*size[0];
res[1] = dir[1]/tmp*size[0];
} else {
res[0] = res[1] = 0;
}
// set result in Z direction
res[2] = mju_sign(dir[2]) * size[1];
break;
case mjGEOM_BOX:
for (int i=0; i < 3; i++) {
res[i] = mju_sign(dir[i]) * size[i];
}
break;
case mjGEOM_MESH:
// init search
vertdata = m->mesh_vert + 3*m->mesh_vertadr[m->geom_dataid[g]];
tmp = -1E+10;
ibest = -1;
// no graph data: exhaustive search
if (m->mesh_graphadr[m->geom_dataid[g]] < 0) {
// search all vertices, find best
for (int i=0; i < m->mesh_vertnum[m->geom_dataid[g]]; i++) {
// vdot = dot(vertex, dir)
vdot = dir[0] * (mjtNum)vertdata[3*i] +
dir[1] * (mjtNum)vertdata[3*i+1] +
dir[2] * (mjtNum)vertdata[3*i+2];
// update best
if (vdot > tmp) {
tmp = vdot;
ibest = i;
}
}
// record best vertex index, in globalid format
((mjtCCD*)ccd)->meshindex = ibest;
}
// hill-climb using graph data
else {
// get info
graphadr = m->mesh_graphadr[m->geom_dataid[g]];
numvert = m->mesh_graph[graphadr];
vert_edgeadr = m->mesh_graph + graphadr + 2;
vert_globalid = m->mesh_graph + graphadr + 2 + numvert;
edge_localid = m->mesh_graph + graphadr + 2 + 2*numvert;
// init with first vertex in convex hull
ibest = 0;
tmp = dir[0] * (mjtNum)vertdata[3*vert_globalid[0]] +
dir[1] * (mjtNum)vertdata[3*vert_globalid[0]+1] +
dir[2] * (mjtNum)vertdata[3*vert_globalid[0]+2];
// hill-climb until no change
change = 1;
while (change) {
// look for improvement in ibest neighborhood
change = 0;
int i = vert_edgeadr[ibest];
while ((locid=edge_localid[i]) >= 0) {
// vdot = dot(vertex, dir)
vdot = dir[0] * (mjtNum)vertdata[3*vert_globalid[locid]] +
dir[1] * (mjtNum)vertdata[3*vert_globalid[locid]+1] +
dir[2] * (mjtNum)vertdata[3*vert_globalid[locid]+2];
// update best
if (vdot > tmp) {
tmp = vdot;
ibest = locid;
change = 1;
}
// advance to next edge
i++;
}
}
// record best vertex index, in locid format
((mjtCCD*)ccd)->meshindex = ibest;
// map best index to globalid
ibest = vert_globalid[ibest];
}
// sanity check, SHOULD NOT OCCUR
if (ibest < 0) {
mju_warning("mesh_support could not find support vertex");
mju_zero3(res);
}
// copy best vertex
else {
for (int i=0; i < 3; i++) {
res[i] = (mjtNum)vertdata[3*ibest + i];
}
}
break;
default:
mjERROR("ccd support function is undefined for geom type %d", m->geom_type[g]);
}
// add dir*margin/2 to result
for (int i=0; i < 3; i++) {
res[i] += dir[i] * ccd->margin/2;
}
// rotate result to global frame
mju_rotVecMat(vec->v, res, d->geom_xmat+9*g);
// add geom position
mju_addTo3(vec->v, d->geom_xpos+3*g);
}
// find single convex-convex collision, using libccd
static int mjc_MPRIteration(mjtCCD* obj1, mjtCCD* obj2, const ccd_t* ccd,
const mjModel* m, const mjData* d,
mjContact* con, mjtNum margin) {
ccd_vec3_t dir, pos;
ccd_real_t depth;
if (ccdMPRPenetration(obj1, obj2, ccd, &depth, &dir, &pos) == 0) {
// contact is found but normal is undefined
if (ccdVec3Eq(&dir, ccd_vec3_origin)) {
return 0;
}
// fill in contact data
con->dist = margin-depth;
mju_copy3(con->frame, dir.v);
mju_copy3(con->pos, pos.v);
mju_zero3(con->frame+3);
// both geoms: fix contact frame normal
if (obj1->geom >= 0 && obj2->geom >= 0) {
mjc_fixNormal(m, d, con, obj1->geom, obj2->geom);
}
return 1;
}
// no contact found
else {
return 0;
}
}
// compare new contact to previous contacts, return 1 if it is far from all of them
static int mjc_isDistinctContact(mjContact* con, int ncon, mjtNum tolerance) {
for (int i=0; i < ncon-1; i++) {
if (mju_dist3(con[i].pos, con[ncon - 1].pos) <= tolerance) {
return 0;
}
}
return 1;
}
// in-place rotation of spatial frame around given point of origin
static void mju_rotateFrame(const mjtNum origin[3], const mjtNum rot[9],
mjtNum xmat[9], mjtNum xpos[3]) {
mjtNum mat[9], vec[3], rel[3];
// rotate frame: xmat = rot*xmat
mju_mulMatMat(mat, rot, xmat, 3, 3, 3);
mju_copy(xmat, mat, 9);
// vector to rotation origin: rel = origin - xpos
mju_sub3(rel, origin, xpos);
// displacement of origin due to rotation: vec = rot*rel - rel
mju_rotVecMat(vec, rel, rot);
mju_subFrom3(vec, rel);
// correct xpos by subtracting displacement: xpos = xpos - vec
mju_subFrom3(xpos, vec);
}
// multi-point convex-convex collision, using libccd
int mjc_Convex(const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin) {
ccd_t ccd;
mjtCCD obj1 = {m, d, g1, -1, -1, -1, -1, margin, {1, 0, 0, 0}};
mjtCCD obj2 = {m, d, g2, -1, -1, -1, -1, margin, {1, 0, 0, 0}};
// init ccd structure
ccd.first_dir = ccdFirstDirDefault;
ccd.center1 = mjccd_center;
ccd.center2 = mjccd_center;
ccd.support1 = mjccd_support;
ccd.support2 = mjccd_support;
// set ccd parameters
ccd.max_iterations = m->opt.mpr_iterations;
ccd.mpr_tolerance = m->opt.mpr_tolerance;
// find initial contact
int ncon = mjc_MPRIteration(&obj1, &obj2, &ccd, m, d, con, margin);
// look for additional contacts
if (ncon && mjENABLED(mjENBL_MULTICCD) // TODO(tassa) leave as bitflag or make geom attribute (?)
&& m->geom_type[g1] != mjGEOM_ELLIPSOID && m->geom_type[g1] != mjGEOM_SPHERE
&& m->geom_type[g2] != mjGEOM_ELLIPSOID && m->geom_type[g2] != mjGEOM_SPHERE) {
// multiCCD parameters
const mjtNum relative_tolerance = 1e-3;
const mjtNum perturbation_angle = 1e-3;
// save positions and orientations of g1 and g2
mjtNum xpos1[3], xmat1[9], xpos2[3], xmat2[9];
mju_copy3(xpos1, d->geom_xpos+3*g1);
mju_copy(xmat1, d->geom_xmat+9*g1, 9);
mju_copy3(xpos2, d->geom_xpos+3*g2);
mju_copy(xmat2, d->geom_xmat+9*g2, 9);
// complete frame of initial contact
mjtNum frame[9];
mju_copy(frame, con[0].frame, 9);
mju_makeFrame(frame);
// tolerance for determining if newly found contacts are distinct
const mjtNum tolerance = relative_tolerance * mju_min(m->geom_rbound[g1], m->geom_rbound[g2]);
// axes and rotation angles for perturbation test
mjtNum* axes[2] = {frame+3, frame+6};
mjtNum angles[2] = {-perturbation_angle, perturbation_angle};
// rotate both geoms, search for new contacts
for (int axis_id = 0; axis_id < 2; ++axis_id) {
for (int angle_id = 0; angle_id < 2; ++angle_id) {
mjtNum* axis = axes[axis_id];
mjtNum angle = angles[angle_id];
// make rotation matrix rot
mjtNum quat[4], rot[9];
mju_axisAngle2Quat(quat, axis, angle);
mju_quat2Mat(rot, quat);
// rotate g1 around initial contact point
mju_rotateFrame(con[0].pos, rot, d->geom_xmat+9*g1, d->geom_xpos+3*g1);
// inversely rotate g2 around initial contact point
mjtNum invrot[9];
mju_transpose(invrot, rot, 3, 3);
mju_rotateFrame(con[0].pos, invrot, d->geom_xmat+9*g2, d->geom_xpos+3*g2);
// search for new contact
int new_contact = mjc_MPRIteration(&obj1, &obj2, &ccd, m, d, con+ncon, margin);
// check new contact
if (new_contact && mjc_isDistinctContact(con, ncon + 1, tolerance)) {
// set penetration of new point to equal that of initial point
con[ncon].dist = con[0].dist;
// add new point
ncon += 1;
}
// reset positions and orientations of g1 and g2
mju_copy3(d->geom_xpos+3*g1, xpos1);
mju_copy(d->geom_xmat+9*g1, xmat1, 9);
mju_copy3(d->geom_xpos+3*g2, xpos2);
mju_copy(d->geom_xmat+9*g2, xmat2, 9);
}
}
}
return ncon;
}
// parameters for plane-mesh extra contacts
const int maxplanemesh = 3;
const mjtNum tolplanemesh = 0.3;
// add one plane-mesh contact
static int addplanemesh(mjContact* con, const float vertex[3],
const mjtNum pos1[3], const mjtNum normal1[3],
const mjtNum pos2[3], const mjtNum mat2[9],
const mjtNum first[3], mjtNum rbound) {
// compute point in global coordinates
mjtNum pnt[3], v[3] = {vertex[0], vertex[1], vertex[2]};
mju_rotVecMat(pnt, v, mat2);
mju_addTo3(pnt, pos2);
// skip if too close to first contact
if (mju_dist3(pnt, first) < tolplanemesh*rbound) {
return 0;
}
// pnt-pos difference vector
mjtNum dif[3];
mju_sub3(dif, pnt, pos1);
// set distance
con->dist = mju_dot3(normal1, dif);
// set position
mju_copy3(con->pos, pnt);
mju_addToScl3(con->pos, normal1, -0.5*con->dist);
// set frame
mju_copy3(con->frame, normal1);
mju_zero3(con->frame+3);
return 1;
}
// plane-convex collision, using libccd
int mjc_PlaneConvex(const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin) {
mjGETINFO
mjtNum dist, dif[3], normal[3] = {mat1[2], mat1[5], mat1[8]};
ccd_vec3_t dir, vec;
mjtCCD obj = {m, d, g2, -1, -1, -1, -1, 0, {1, 0, 0, 0}};
// get support point in -normal direction
ccdVec3Set(&dir, -mat1[2], -mat1[5], -mat1[8]);
mjccd_support(&obj, &dir, &vec);
// compute normal distance, return if too far
mju_sub3(dif, vec.v, pos1);
dist = mju_dot3(normal, dif);
if (dist > margin) {
return 0;
}
// fill in contact data
con->dist = dist;
mju_copy3(con->pos, vec.v);
mju_addToScl3(con->pos, normal, -0.5*dist);
mju_copy3(con->frame, normal);
mju_zero3(con->frame+3);
//--------------- add all/connected vertices below margin
float* vertdata;
int graphadr, numvert, locid;
int *vert_edgeadr, *vert_globalid, *edge_localid;
mjtNum vdot;
int count = 1, g = g2;
// g is an ellipsoid: no need for further mesh-specific processing
if (m->geom_dataid[g] == -1) {
return count;
}
// init
vertdata = m->mesh_vert + 3*m->mesh_vertadr[m->geom_dataid[g]];
// express dir in geom local frame
mjtNum locdir[3];
mju_rotVecMatT(locdir, dir.v, d->geom_xmat+9*g);
// inclusion threshold along locdir, relative to geom2 center
mju_sub3(dif, pos2, pos1);
mjtNum threshold = mju_dot3(normal, dif) - margin;
// no graph data: exhaustive search
if (m->mesh_graphadr[m->geom_dataid[g]] < 0) {
// search all vertices, find best
for (int i=0; i < m->mesh_vertnum[m->geom_dataid[g]] && count < maxplanemesh; i++) {
// vdot = dot(vertex, dir)
vdot = locdir[0] * (mjtNum)vertdata[3*i] +
locdir[1] * (mjtNum)vertdata[3*i+1] +
locdir[2] * (mjtNum)vertdata[3*i+2];
// detect contact, skip best
if (vdot > threshold && i != obj.meshindex) {
count += addplanemesh(con+count, vertdata+3*i,
pos1, normal, pos2, mat2,
con->pos, m->geom_rbound[g2]);
}
}
}
// use graph data
else if (obj.meshindex >= 0) {
// get info
graphadr = m->mesh_graphadr[m->geom_dataid[g]];
numvert = m->mesh_graph[graphadr];
vert_edgeadr = m->mesh_graph + graphadr + 2;
vert_globalid = m->mesh_graph + graphadr + 2 + numvert;
edge_localid = m->mesh_graph + graphadr + 2 + 2*numvert;
// look for contacts in ibest neighborhood
int i = vert_edgeadr[obj.meshindex];
while ((locid=edge_localid[i]) >= 0 && count < maxplanemesh) {
// vdot = dot(vertex, dir)
vdot = locdir[0] * (mjtNum)vertdata[3*vert_globalid[locid]] +
locdir[1] * (mjtNum)vertdata[3*vert_globalid[locid]+1] +
locdir[2] * (mjtNum)vertdata[3*vert_globalid[locid]+2];
// detect contact
if (vdot > threshold) {
count += addplanemesh(con+count, vertdata+3*vert_globalid[locid],
pos1, normal, pos2, mat2,
con->pos, m->geom_rbound[g2]);
}
// advance to next edge
i++;
}
}
return count;
}
//---------------------------- heightfield collisions ---------------------------------------------
// ccd prism object type
struct _mjtPrism {
mjtNum v[6][3];
};
typedef struct _mjtPrism mjtPrism;
// ccd prism support function
static void prism_support(const void *obj, const ccd_vec3_t *dir, ccd_vec3_t *vec) {
int istart, ibest;
mjtNum best, tmp;
const mjtPrism* p = (const mjtPrism*)obj;
// find best vertex in halfspace determined by dir.z
istart = dir->v[2] < 0 ? 0 : 3;
ibest = istart;
best = mju_dot3(p->v[istart], dir->v);
for (int i=istart+1; i < istart+3; i++) {
if ((tmp = mju_dot3(p->v[i], dir->v)) > best) {
ibest = i;
best = tmp;
}
}
// copy best point
mju_copy3(vec->v, p->v[ibest]);
}
// ccd prism center function
static void prism_center(const void *obj, ccd_vec3_t *center) {
const mjtPrism* p = (const mjtPrism*)obj;
// compute mean
mju_zero3(center->v);
for (int i=0; i < 6; i++) {
mju_addTo3(center->v, p->v[i]);
}
mju_scl3(center->v, center->v, 1.0/6.0);
}
// ccd prism first dir
static void prism_firstdir(const void* o1, const void* o2, ccd_vec3_t *vec) {
ccdVec3Set(vec, 0, 0, 1);
}
// add vertex to prism, count vertices
static void addVert(int* nvert, mjtPrism* prism, mjtNum x, mjtNum y, mjtNum z) {
// move old data
mju_copy3(prism->v[0], prism->v[1]);
mju_copy3(prism->v[1], prism->v[2]);
mju_copy3(prism->v[3], prism->v[4]);
mju_copy3(prism->v[4], prism->v[5]);
// add new vertex at last position
prism->v[2][0] = prism->v[5][0] = x;
prism->v[2][1] = prism->v[5][1] = y;
prism->v[5][2] = z;
// count
(*nvert)++;
}
// entry point for heightfield collisions
int mjc_ConvexHField(const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin) {
mjGETINFO_HFIELD
mjtNum mat[9], savemat2[9], savepos2[3], pos[3], vec[3], r2, dx, dy;
mjtNum xmin, xmax, ymin, ymax, zmin, zmax;
int hid = m->geom_dataid[g1];
int nrow = m->hfield_nrow[hid];
int ncol = m->hfield_ncol[hid];
int dr[2], cnt, rmin, rmax, cmin, cmax;
const float* data = m->hfield_data + m->hfield_adr[hid];
mjtPrism prism;
// ccd-related
ccd_vec3_t dirccd, vecccd;
ccd_real_t depth;
mjtCCD obj = {m, d, g2, -1, -1, -1, -1, 0, {1, 0, 0, 0}};
ccd_t ccd;
// point size1 to hfield size instead of geom1 size
size1 = m->hfield_size + 4*hid;
//------------------------------------- frame alignment, box-sphere test
// express geom2 pos in heightfield frame
mju_sub3(vec, pos2, pos1);
mju_mulMatTVec(pos, mat1, vec, 3, 3);
// get geom2 rbound
r2 = m->geom_rbound[g2];
// box-sphere test: horizontal plane
for (int i=0; i < 2; i++) {
if ((size1[i] < pos[i]-r2-margin) || (-size1[i] > pos[i]+r2+margin)) {
return 0;
}
}
// box-sphere test in: vertical direction
if (size1[2] < pos[2]-r2-margin) { // up
return 0;
}
if (-size1[3] > pos[2]+r2+margin) { // down
return 0;
}
// express geom2 mat in heightfield frame
mju_mulMatTMat(mat, mat1, mat2, 3, 3, 3);
//------------------------------------- AABB computation, box-box test
// save mat2 and pos2, replace with relative frame
mju_copy(savemat2, mat2, 9);
mju_copy3(savepos2, pos2);
mju_copy(mat2, mat, 9);
mju_copy3(pos2, pos);
// get support point in +X
ccdVec3Set(&dirccd, 1, 0, 0);
mjccd_support(&obj, &dirccd, &vecccd);
xmax = vecccd.v[0];
// get support point in -X
ccdVec3Set(&dirccd, -1, 0, 0);
mjccd_support(&obj, &dirccd, &vecccd);
xmin = vecccd.v[0];
// get support point in +Y
ccdVec3Set(&dirccd, 0, 1, 0);
mjccd_support(&obj, &dirccd, &vecccd);
ymax = vecccd.v[1];
// get support point in -Y
ccdVec3Set(&dirccd, 0, -1, 0);
mjccd_support(&obj, &dirccd, &vecccd);
ymin = vecccd.v[1];
// get support point in +Z
ccdVec3Set(&dirccd, 0, 0, 1);
mjccd_support(&obj, &dirccd, &vecccd);
zmax = vecccd.v[2];
// get support point in -Z
ccdVec3Set(&dirccd, 0, 0, -1);
mjccd_support(&obj, &dirccd, &vecccd);
zmin = vecccd.v[2];
// box-box test
if ((xmin-margin > size1[0]) || (xmax+margin < -size1[0]) ||
(ymin-margin > size1[1]) || (ymax+margin < -size1[1]) ||
(zmin-margin > size1[2]) || (zmax+margin < -size1[3])) {
// restore mat2 and pos2
mju_copy(mat2, savemat2, 9);
mju_copy3(pos2, savepos2);
return 0;
}
// compute sub-grid bounds
cmin = (int) floor((xmin + size1[0]) / (2*size1[0]) * (ncol-1));
cmax = (int) ceil ((xmax + size1[0]) / (2*size1[0]) * (ncol-1));
rmin = (int) floor((ymin + size1[1]) / (2*size1[1]) * (nrow-1));
rmax = (int) ceil ((ymax + size1[1]) / (2*size1[1]) * (nrow-1));
cmin = mjMAX(0, cmin);
cmax = mjMIN(ncol-1, cmax);
rmin = mjMAX(0, rmin);
rmax = mjMIN(nrow-1, rmax);
//------------------------------------- collision testing
// init ccd structure
ccd.first_dir = prism_firstdir;
ccd.center1 = prism_center;
ccd.center2 = mjccd_center;
ccd.support1 = prism_support;
ccd.support2 = mjccd_support;
// set ccd parameters
ccd.max_iterations = m->opt.mpr_iterations;
ccd.mpr_tolerance = m->opt.mpr_tolerance;
// geom margin needed for actual collision test
obj.margin = margin;
// compute real-valued grid step, and triangulation direction
dx = (2.0*size1[0]) / (ncol-1);
dy = (2.0*size1[1]) / (nrow-1);
dr[0] = 1;
dr[1] = 0;
// set zbottom value using base size
prism.v[0][2] = prism.v[1][2] = prism.v[2][2] = -size1[3];
// process all prisms in sub-grid
cnt = 0;
for (int r=rmin; r < rmax; r++) {
int nvert = 0;
for (int c=cmin; c <= cmax; c++) {
for (int i=0; i < 2; i++) {
// send vertex to prism constructor
addVert(&nvert, &prism, dx*c-size1[0], dy*(r+dr[i])-size1[1],
data[(r+dr[i])*ncol+c]*size1[2]+margin);
// check for enough vertices
if (nvert > 2) {
// prism height test
if (prism.v[3][2] < zmin && prism.v[4][2] < zmin && prism.v[5][2] < zmin) {
continue;
}
// run MPR, save contact
if (ccdMPRPenetration(&prism, &obj, &ccd, &depth, &dirccd, &vecccd) == 0 &&
!ccdVec3Eq(&dirccd, ccd_vec3_origin)) {
// fill in contact data, transform to global coordinates
con[cnt].dist = -depth;
mju_rotVecMat(con[cnt].frame, dirccd.v, mat1);
mju_rotVecMat(con[cnt].pos, vecccd.v, mat1);
mju_addTo3(con[cnt].pos, pos1);
mju_zero3(con[cnt].frame+3);
// count, stop if max number reached
cnt++;
if (cnt >= mjMAXCONPAIR) {
r = rmax+1;
c = cmax+1;
i = 3;
break;
}
}
}
}
}
}
// restore mat2 and pos2
mju_copy(mat2, savemat2, 9);
mju_copy3(pos2, savepos2);
// fix contact normals
for (int i=0; i < cnt; i++) {
mjc_fixNormal(m, d, con+i, g1, g2);
}
return cnt;
}
//--------------------------- fix contact frame normal ---------------------------------------------
// compute normal for point outside ellipsoid, using ray-projection SQP
static int mjc_ellipsoidInside(mjtNum nrm[3], const mjtNum pos[3], const mjtNum size[3]) {
// algorithm constants
const int maxiter = 30;
const mjtNum tolerance = 1e-6;
// precompute quantities
mjtNum S2inv[3] = {1/(size[0]*size[0]), 1/(size[1]*size[1]), 1/(size[2]*size[2])};
mjtNum C = pos[0]*pos[0]*S2inv[0] + pos[1]*pos[1]*S2inv[1] + pos[2]*pos[2]*S2inv[2] - 1;
if (C > 0) {
return 0;
}
// normalize initial normal (just in case)
mju_normalize3(nrm);
// main iteration
int iter;
for (iter=0; iter < maxiter; iter++) {
// coefficients and determinant of quadratic
mjtNum A = nrm[0]*nrm[0]*S2inv[0] + nrm[1]*nrm[1]*S2inv[1] + nrm[2]*nrm[2]*S2inv[2];
mjtNum B = pos[0]*nrm[0]*S2inv[0] + pos[1]*nrm[1]*S2inv[1] + pos[2]*nrm[2]*S2inv[2];
mjtNum det = B*B - A*C;
if (det < mjMINVAL || A < mjMINVAL) {
return (iter > 0);
}
// ray intersection with ellipse: pos + x*nrm, x>=0
mjtNum x = (-B + mju_sqrt(det))/A;
if (x < 0) {
return (iter > 0);
}
// new point on ellipsoid
mjtNum pnt[3];
mju_addScl3(pnt, pos, nrm, x);
// normal at new point
mjtNum newnrm[3] = {pnt[0]*S2inv[0], pnt[1]*S2inv[1], pnt[2]*S2inv[2]};
mju_normalize3(newnrm);
// save change and assign
mjtNum change = mju_dist3(nrm, newnrm);
mju_copy3(nrm, newnrm);
// terminate if converged
if (change < tolerance) {
break;
}
}
return 1;
}
// compute normal for point inside ellipsoid, using diagonal QCQP
static int mjc_ellipsoidOutside(mjtNum nrm[3], const mjtNum pos[3], const mjtNum size[3]) {
// algorithm constants
const int maxiter = 30;
const mjtNum tolerance = 1e-6;
// precompute quantities
mjtNum S2[3] = {size[0]*size[0], size[1]*size[1], size[2]*size[2]};
mjtNum PS2[3] = {pos[0]*pos[0]*S2[0], pos[1]*pos[1]*S2[1], pos[2]*pos[2]*S2[2]};
// main iteration
mjtNum la = 0;
int iter;
for (iter=0; iter < maxiter; iter++) {
// precompute 1/(s^2+la)
mjtNum R[3] = {1/(S2[0]+la), 1/(S2[1]+la), 1/(S2[2]+la)};
// value
mjtNum val = PS2[0]*R[0]*R[0] + PS2[1]*R[1]*R[1] + PS2[2]*R[2]*R[2] - 1;
if (val < tolerance) {
break;
}
// derivative
mjtNum deriv = -2*(PS2[0]*R[0]*R[0]*R[0] + PS2[1]*R[1]*R[1]*R[1] + PS2[2]*R[2]*R[2]*R[2]);
if (deriv > -mjMINVAL) {
break;
}
// delta
mjtNum delta = -val/deriv;
if (delta < tolerance) {
break;
}
// update
la += delta;
}
// compute normal given lambda
nrm[0] = pos[0]/(S2[0]+la);
nrm[1] = pos[1]/(S2[1]+la);
nrm[2] = pos[2]/(S2[2]+la);
mju_normalize3(nrm);
return 1;
}
// entry point
void mjc_fixNormal(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2) {
mjtNum dst1, dst2;
// get geom ids and types
int gid[2] = {g1, g2};
mjtGeom type[2];
for (int i=0; i < 2; i++) {
type[i] = m->geom_type[gid[i]];
// set to mjGEOM_NONE if type cannot be processed
if (type[i] != mjGEOM_SPHERE &&
type[i] != mjGEOM_CAPSULE &&
type[i] != mjGEOM_ELLIPSOID &&
type[i] != mjGEOM_CYLINDER) {
type[i] = mjGEOM_NONE;
}
}
// neither type can be processed: nothing to do
if (type[0] == mjGEOM_NONE && type[1] == mjGEOM_NONE) {
return;
}
// init normals
mjtNum normal[2][3] = {
{con->frame[0], con->frame[1], con->frame[2]},
{-con->frame[0], -con->frame[1], -con->frame[2]}
};
// process geoms in type range
int processed[2] = {0, 0};
for (int i=0; i < 2; i++) {
if (type[i] != mjGEOM_NONE) {
// get geom mat and size
mjtNum* mat = d->geom_xmat + 9*gid[i];
mjtNum* size = m->geom_size + 3*gid[i];
// map contact point and normal to local frame
mjtNum dif[3], pos[3], nrm[3];
mju_sub3(dif, con->pos, d->geom_xpos+3*gid[i]);
mju_rotVecMatT(pos, dif, mat);
mju_rotVecMatT(nrm, normal[i], mat);
// process according to type
switch (type[i]) {
case mjGEOM_SPHERE:
mju_copy3(nrm, pos);
processed[i] = 1;
break;
case mjGEOM_CAPSULE:
// Z: bottom cap
if (pos[2] < -size[1]) {
nrm[2] = pos[2]+size[1];
}
// Z: top cap
else if (pos[2] > size[1]) {
nrm[2] = pos[2]-size[1];
}
// Z: cylinder
else {
nrm[2] = 0;
}
// copy XY
nrm[0] = pos[0];
nrm[1] = pos[1];
processed[i] = 1;
break;
case mjGEOM_ELLIPSOID:
// guard against invalid ellipsoid size (just in case)
if (size[0] < mjMINVAL || size[1] < mjMINVAL || size[2] < mjMINVAL) {
break;
}
// compute elliptic distance^2
dst1 = pos[0]*pos[0]/(size[0]*size[0]) +
pos[1]*pos[1]/(size[1]*size[1]) +
pos[2]*pos[2]/(size[2]*size[2]);
// dispatch to inside or outside solver
if (dst1 <= 1) {
processed[i] = mjc_ellipsoidInside(nrm, pos, size);
} else {
processed[i] = mjc_ellipsoidOutside(nrm, pos, size);
}
break;
case mjGEOM_CYLINDER:
// skip if within 5% length of flat wall
if (mju_abs(pos[2]) > 0.95*size[1]) {
break;
}
// compute distances to flat and round wall
dst1 = mju_abs(size[1]-mju_abs(pos[2]));
dst2 = mju_abs(size[0]-mju_norm(pos, 2));
// require 4x closer to round than flat wall
if (dst1 < 0.25*dst2) {
break;
}
// set normal for round wall
nrm[0] = pos[0];
nrm[1] = pos[1];
nrm[2] = 0;
processed[i] = 1;
break;
default:
// do nothing: only sphere, capsule, ellipsoid and cylinder are processed
break;
}
// normalize and map normal to global frame
if (processed[i]) {
mju_normalize3(nrm);
mju_rotVecMat(normal[i], nrm, mat);
}
}
}
// both processed: average
if (processed[0] && processed[1]) {
mju_sub3(con->frame, normal[0], normal[1]);
mju_normalize3(con->frame);
}
// first processed: copy
else if (processed[0]) {
mju_copy3(con->frame, normal[0]);
}
// second processed: copy reverse
else if (processed[1]) {
mju_scl3(con->frame, normal[1], -1);
}
// clear second frame axis if processed, just in case
if (processed[0] || processed[1]) {
mju_zero3(con->frame+3);
}
}
//---------------------------- flex collisions ---------------------------------------------
// geom-elem or elem-elem or vert-elem convex collision using ccd
int mjc_ConvexElem(const mjModel* m, const mjData* d, mjContact* con,
int g1, int f1, int e1, int v1, int f2, int e2, mjtNum margin) {
ccd_t ccd;
mjtCCD obj1 = {m, d, g1, -1, f1, e1, v1, margin, {1, 0, 0, 0}};
mjtCCD obj2 = {m, d, -1, -1, f2, e2, -1, margin, {1, 0, 0, 0}};
// init ccd structure
ccd.first_dir = ccdFirstDirDefault;
ccd.center1 = mjccd_center;
ccd.center2 = mjccd_center;
ccd.support1 = mjccd_support;
ccd.support2 = mjccd_support;
// set ccd parameters
ccd.max_iterations = m->opt.mpr_iterations;
ccd.mpr_tolerance = m->opt.mpr_tolerance;
// find contacts
int ncon = mjc_MPRIteration(&obj1, &obj2, &ccd, m, d, con, margin);
return ncon;
}
// test a heighfield geom and a flex flex element for collision
int mjc_HFieldElem(const mjModel* m, const mjData* d, mjContact* con,
int g, int f, int e, mjtNum margin) {
mjtNum vec[3], dx, dy;
mjtNum xmin, xmax, ymin, ymax, zmin, zmax;
int dr[2], cnt, rmin, rmax, cmin, cmax;
mjtPrism prism;
// get hfield info
int hid = m->geom_dataid[g];
int nrow = m->hfield_nrow[hid];
int ncol = m->hfield_ncol[hid];
mjtNum* hpos = d->geom_xpos + 3*g;
mjtNum* hmat = d->geom_xmat + 9*g;
mjtNum* hsize = m->hfield_size + 4*hid;
const float* hdata = m->hfield_data + m->hfield_adr[hid];
// get elem indo
int dim = m->flex_dim[f];
const int* edata = m->flex_elem + m->flex_elemdataadr[f] + e*(dim+1);
mjtNum* evert[4] = {NULL, NULL, NULL, NULL};
for (int i=0; i <= dim; i++) {
evert[i] = d->flexvert_xpos + 3*(m->flex_vertadr[f] + edata[i]);
}
mjtNum* ecenter = d->flexelem_aabb + 6*(m->flex_elemadr[f]+e);
// ccd-related
ccd_vec3_t dirccd, vecccd;
ccd_real_t depth;
mjtCCD obj = {m, d, -1, -1, f, e, -1, margin, {1, 0, 0, 0}};
ccd_t ccd;
//------------------------------------- AABB computation, box-box test
// save elem vertices, transform to hfield frame
mjtNum savevert[4][3];
for (int i=0; i <= dim; i++) {
mju_copy3(savevert[i], evert[i]);
mju_sub3(vec, evert[i], hpos);
mju_mulMatTVec(evert[i], hmat, vec, 3, 3);
}
// save elem center, transform to hfield frame
mjtNum savecenter[3];
mju_copy3(savecenter, ecenter);
mju_sub3(vec, ecenter, hpos);
mju_mulMatTVec(ecenter, hmat, vec, 3, 3);
// compute elem bounding box (in hfield frame)
xmin = xmax = evert[0][0];
ymin = ymax = evert[0][1];
zmin = zmax = evert[0][2];
for (int i=1; i <= dim; i++) {
xmin = mju_min(xmin, evert[i][0]);
xmax = mju_max(xmax, evert[i][0]);
ymin = mju_min(ymin, evert[i][1]);
ymax = mju_max(ymax, evert[i][1]);
zmin = mju_min(zmin, evert[i][2]);
zmax = mju_max(zmax, evert[i][2]);
}
// box-box test
if ((xmin-margin > hsize[0]) || (xmax+margin < -hsize[0]) ||
(ymin-margin > hsize[1]) || (ymax+margin < -hsize[1]) ||
(zmin-margin > hsize[2]) || (zmax+margin < -hsize[3])) {
// restore vertices and center
for (int i=0; i <= dim; i++) {
mju_copy3(evert[i], savevert[i]);
}
mju_copy3(ecenter, savecenter);
return 0;
}
// compute sub-grid bounds
cmin = (int) floor((xmin + hsize[0]) / (2*hsize[0]) * (ncol-1));
cmax = (int) ceil ((xmax + hsize[0]) / (2*hsize[0]) * (ncol-1));
rmin = (int) floor((ymin + hsize[1]) / (2*hsize[1]) * (nrow-1));
rmax = (int) ceil ((ymax + hsize[1]) / (2*hsize[1]) * (nrow-1));
cmin = mjMAX(0, cmin);
cmax = mjMIN(ncol-1, cmax);
rmin = mjMAX(0, rmin);
rmax = mjMIN(nrow-1, rmax);
//------------------------------------- collision testing
// init ccd structure
ccd.first_dir = prism_firstdir;
ccd.center1 = prism_center;
ccd.center2 = mjccd_center;
ccd.support1 = prism_support;
ccd.support2 = mjccd_support;
// set ccd parameters
ccd.max_iterations = m->opt.mpr_iterations;
ccd.mpr_tolerance = m->opt.mpr_tolerance;
// compute real-valued grid step, and triangulation direction
dx = (2.0*hsize[0]) / (ncol-1);
dy = (2.0*hsize[1]) / (nrow-1);
dr[0] = 1;
dr[1] = 0;
// set zbottom value using base size
prism.v[0][2] = prism.v[1][2] = prism.v[2][2] = -hsize[3];
// process all prisms in sub-grid
cnt = 0;
for (int r=rmin; r < rmax; r++) {
int nvert = 0;
for (int c=cmin; c <= cmax; c++) {
for (int k=0; k < 2; k++) {
// send vertex to prism constructor
addVert(&nvert, &prism, dx*c-hsize[0], dy*(r+dr[k])-hsize[1],
hdata[(r+dr[k])*ncol+c]*hsize[2]+margin);
// check for enough vertices
if (nvert > 2) {
// prism height test
if (prism.v[3][2] < zmin && prism.v[4][2] < zmin && prism.v[5][2] < zmin) {
continue;
}
// run MPR, save contact
if (ccdMPRPenetration(&prism, &obj, &ccd, &depth, &dirccd, &vecccd) == 0) {
if (!ccdVec3Eq(&dirccd, ccd_vec3_origin)) {
// fill in contact data, transform to global coordinates
con[cnt].dist = -depth;
mju_rotVecMat(con[cnt].frame, dirccd.v, hmat);
mju_rotVecMat(con[cnt].pos, vecccd.v, hmat);
mju_addTo3(con[cnt].pos, hpos);
mju_zero3(con[cnt].frame+3);
// count, stop if max number reached
cnt++;
if (cnt >= mjMAXCONPAIR) {
r = rmax+1;
c = cmax+1;
k = 3;
break;
}
}
}
}
}
}
}
// restore elem vertices and center
for (int i=0; i <= dim; i++) {
mju_copy3(evert[i], savevert[i]);
}
mju_copy3(ecenter, savecenter);
return cnt;
}