Files
Mujoco_WASM/src/engine/engine_collision_box.c
T
Yuval Tassa 8655446f25 Fix spurious deep contacts in box-box collision.
In the edge-edge path of the box-box collider, when line clipping yields
no points, the corner generators accept points whose projection
parameters are out of range and clamp them into the valid range. The
depth of such a point is the Euclidean distance between two unrelated
points, mixing lateral offset into penetration depth, and on the
penetrating side it is admitted with no margin check. For thin boxes
meeting edge-to-face within margin, this produced a contact with
penetration three orders of magnitude larger than the boxes' true
separation, exploding resting stacks.

No contact can penetrate deeper than the support-interval overlap along
the separating axis, which the SAT stage has already computed. Enforce
this bound on all points emitted by the edge-edge path. The bound
carries margin plus relative and size-scaled slack covering rounding
error: the depth of the deepest legitimate point is algebraically equal
to the bound, so an exact comparison would drop real contacts. The
slack is precision-dependent: in mjUSESINGLE builds the two
computations of the same overlap disagree by tens of ulps, and slack
calibrated for double precision rejects real single-precision contacts.

Differential fuzzing against the nativeccd oracle over 200k random
near-contact thin-box configurations, in both precisions: impossibly
deep contacts drop from 1018 to 4 (worst excess from 2.5x the bounding
diameter to 0.001x), with no legitimate shallow-penetration contact
lost.

PiperOrigin-RevId: 957628830
Change-Id: Iaa1f10742f91c55bf831296cba0936eb50ea09b0
2026-08-01 07:48:32 -07:00

1441 lines
36 KiB
C

// Copyright 2016 Svetoslav Kolev
//
// 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 <math.h>
#include "engine/engine_collision_primitive.h"
#include "engine/engine_inline.h"
#include "engine/engine_util_blas.h"
#include "engine/engine_util_misc.h"
// rounding slack for the edge-edge depth bound: relative, and absolute times the sum of
// half-sizes; wide enough to cover rounding between two computations of the same overlap,
// orders of magnitude below the box-scale depths of spurious clipping artifacts
#ifdef mjUSESINGLE
#define mjDEPTHSLACKREL 1e-4f
#define mjDEPTHSLACKABS 1e-5f
#else
#define mjDEPTHSLACKREL 1e-6
#define mjDEPTHSLACKABS 1e-12
#endif
// hard-clamp vector to range [-limit(i), +limit(i)]
static void mju_clampVec(mjtNum* vec, const mjtNum* limit, int n) {
for (int i = 0; i < n; i++) {
// loop over active limits
if (limit[i] > 0) {
vec[i] = mju_clip(vec[i], -limit[i], limit[i]);
}
}
}
// raw sphere : box
int mjraw_SphereBox(mjPreContact* con, mjtNum margin,
const mjtNum* pos1, const mjtNum* mat1, const mjtNum* size1,
const mjtNum* pos2, const mjtNum* mat2, const mjtNum* size2) {
int i, k;
mjtNum tmp[3], center[3], clamped[3], deepest[3];
mjtNum pos[3];
mjtNum dist, closest;
mji_sub3(tmp, pos1, pos2);
mji_mulMatTVec3(center, mat2, tmp);
mji_copy3(clamped, center);
mju_clampVec(clamped, size2, 3);
mji_copy3(deepest, center);
mji_sub3(tmp, clamped, center);
dist = mju_normalize3(tmp);
if (dist - size1[0] > margin)
return 0;
// sphere center inside box
if (dist <= mjMINVAL) {
closest = (size2[0] + size2[1] + size2[2]) * 2;
for (i = 0; i < 6; i++) {
if (closest > mju_abs((i % 2 ? 1 : -1)*size2[i / 2] - center[i / 2])) {
closest = mju_abs((i % 2 ? 1 : -1) * size2[i / 2] - center[i / 2]);
k = i;
}
}
mjtNum nearest[3] = {0};
nearest[k / 2] = (k % 2 ? -1 : 1);
mji_copy3(pos, center);
mji_addToScl3(pos, nearest, (size1[0] - closest) / 2);
mji_mulMatVec3(con[0].normal, mat2, nearest);
dist = -closest;
} else {
mji_addToScl3(deepest, tmp, size1[0]);
mju_zero3(pos);
mji_addToScl3(pos, clamped, 0.5);
mji_addToScl3(pos, deepest, 0.5);
mji_mulMatVec3(con[0].normal, mat2, tmp);
}
mji_mulMatVec3(tmp, mat2, pos);
mji_add3(con[0].pos, tmp, pos2);
con[0].dist = dist - size1[0];
mji_zero3(con[0].tangent);
return 1;
}
// sphere : box
int mjc_SphereBox(const mjModel* m, mjData* d, mjPreContact* con, int g1, int g2, mjtNum margin) {
const mjtNum* pos1 = d->geom_xpos + 3*g1;
const mjtNum* mat1 = d->geom_xmat + 9*g1;
const mjtNum* size1 = m->geom_size + 3*g1;
const mjtNum* pos2 = d->geom_xpos + 3*g2;
const mjtNum* mat2 = d->geom_xmat + 9*g2;
const mjtNum* size2 = m->geom_size + 3*g2;
return mjraw_SphereBox(con, margin, pos1, mat1, size1, pos2, mat2, size2);
}
/* GENERAL THEORY OF OPERATION
the following code is mostly for finding (line segment)/(box) collision
after which box-sphere is called
First the closest point to the box is found.
Then a "sensible" second point is found if the angle
between the segment and the box is low enough < 45
In the comments that follow, capsule just means the capsule's line segment
It might be hard to understand all comments but you would need
a picture to see what is happening at each line of the code
*/
// raw capsule : box
int mjraw_CapsuleBox(mjPreContact* con, mjtNum margin,
const mjtNum* pos1, const mjtNum* mat1, const mjtNum* size1,
const mjtNum* pos2, const mjtNum* mat2,
const mjtNum* size2) {
mjtNum tmp1[3], tmp2[3], tmp3[3], halfaxis[3], axis[3], dif[3];
mjtNum pos[3]; // position of capsule in box-local frame
mjtNum halflength; // half of capsule's length
mjtNum bestdist; // closest contact point distance
mjtNum bestdistmax; // init value for bestdist
mjtNum bestsegmentpos; // between -1 and 1 : which point on the segment is closest to the box
mjtNum secondpos; // distance of 2nd contact position on capsule segment from the first
mjtNum dist;
mjtNum bestboxpos; // closest contact point, position on the box's edge
mjtNum mul, e1, e2, dp, de;
// mjtNum penetration;
mjtNum ma, mb, mc, u, v, det, x1, x2, idet; // linelinedist temps
int s1, s2; // hold linelinedist info
int i, j, c1, c2; // temporary variables
int cltype = -4; // closest type
int clface; // closest face
int clcorner = 0; // closest corner (0..7 in binary)
int cledge; // closest edge axis
int axisdir; // direction of capsule axis in relation to the box
int n; // number of contacts
int ax1, ax2, ax; // axis temporaries
halflength = size1[1];
secondpos = -4; // initialize to no 2nd contact (valid values are between -1 and 1)
mji_sub3(tmp1, pos1, pos2); // bring capsule to box-local frame (center's box is at (0,0,0))
mji_mulMatTVec3(pos, mat2, tmp1); // and axis parallel to world
tmp1[0] = mat1[2]; // capsule's axis
tmp1[1] = mat1[5];
tmp1[2] = mat1[8];
mji_mulMatTVec3(axis, mat2, tmp1); // do the same for the capsule axis
mji_scl3(halfaxis, axis, halflength); // scale to get actual capsule half-axis
axisdir = 0;
if (halfaxis[0] > 0)
axisdir += 1;
if (halfaxis[1] > 0)
axisdir += 2;
if (halfaxis[2] > 0)
axisdir += 4;
// under this notion "axisdir" and "7-axisdir" point in opposite directions,
// essentially the same for a capsule
bestdistmax = margin + 2 * (size1[0] + halflength + size2[0] + size2[1] +
size2[2]); // initialize bestdist
bestdist = bestdistmax;
bestsegmentpos = 0;
mju_zero3(tmp2);
// test to see if maybe the a face of the box is closest to the capsule
for (i = -1; i <= 1; i += 2) {
mji_copy3(tmp1, pos);
mji_addToScl3(tmp1, halfaxis, i);
mji_copy3(tmp2, tmp1);
for (c1 = 0, j = 0, c2 = -1; j < 3; j++) {
if (tmp1[j] < -size2[j]) {
c1++;
c2 = j;
tmp1[j] = -size2[j];
} else if (tmp1[j] > size2[j]) {
c1++;
c2 = j;
tmp1[j] = size2[j];
}
}
if (c1 > 1)
continue;
mji_subFrom3(tmp1, tmp2);
dist = mju_dot3(tmp1, tmp1);
if (dist < bestdist) {
bestdist = dist;
bestsegmentpos = i;
cltype = -2 + i;
clface = c2;
}
}
mju_zero3(tmp2);
for (j = 0; j < 3; j++) {
for (i = 0; i < 8; i++) {
if ((i & (1 << j)) == 0) {
// trick to get a corner
tmp3[0] = ((i & 1) ? 1 : -1) * size2[0];
tmp3[1] = ((i & 2) ? 1 : -1) * size2[1];
tmp3[2] = ((i & 4) ? 1 : -1) * size2[2];
tmp3[j] = 0;
// tmp3 is the starting point on the box
// tmp2 is the direction along the "j"-th axis
// pos is the capsule's center
// halfaxis is the capsule direction
// find closest point between capsule and the edge
mji_sub3(dif, tmp3, pos);
ma = size2[j] * size2[j];
mb = -size2[j] * halfaxis[j];
mc = size1[1] * size1[1];
u = -size2[j] * dif[j];
v = mju_dot3(halfaxis, dif);
det = ma * mc - mb * mb;
if (mju_abs(det) < mjMINVAL)
continue;
idet = 1 / det;
// sX : X=1 means middle of segment. X=0 or 2 one or the other end
x1 = (mc * u - mb * v) * idet;
x2 = (ma * v - mb * u) * idet;
s1 = s2 = 1;
if (x1 > 1) {
x1 = 1;
s1 = 2;
x2 = (v - mb) * (1 / mc);
} else if (x1 < -1) {
x1 = -1;
s1 = 0;
x2 = (v + mb) * (1 / mc);
}
if (x2 > 1) {
x2 = 1;
s2 = 2;
x1 = (u - mb) * (1 / ma);
if (x1 > 1)
x1 = 1, s1 = 2;
else if (x1 < -1)
x1 = -1, s1 = 0;
} else if (x2 < -1) {
x2 = -1;
s2 = 0;
x1 = (u + mb) * (1 / ma);
if (x1 > 1)
x1 = 1, s1 = 2;
else if (x1 < -1)
x1 = -1, s1 = 0;
}
mji_sub3(dif, tmp3, pos);
mji_addToScl3(dif, halfaxis, -x2);
dif[j] += size2[j] * x1;
tmp1[2] = mju_dot3(dif, dif);
c1 = s1 * 3 + s2;
// the -MINVAL might not be necessary. Fixes numerical problem when axis is numerically
// parallel to the box
if (tmp1[2] < bestdist - mjMINVAL) {
bestdist = tmp1[2];
bestsegmentpos = x2;
bestboxpos = x1;
// c1<6 means that closest point on the box is at the lower end
// or in the middle of the edge
c2 = c1 / 6;
clcorner = i + (1 << j) * c2; // which corner is the closest
cledge = j; // which axis
cltype = c1; // save clamped info
}
}
}
}
// penetration = -bestdist;
for (j = 0; j < 3; j++) {
if (j == 2) {
typedef union {
struct {
mjtNum x, y;
};
mjtNum c[2];
} d2;
d2 p, s, dd /*, c, tmp1*/;
mjtNum uu, vv, w, ee1, best /* ,e2 */, l /* , e3, e4 */;
bestdist = bestdistmax;
p.x = pos[0];
p.y = pos[1];
dd.x = halfaxis[0];
dd.y = halfaxis[1];
s.x = size2[0];
s.y = size2[1];
l = sqrt(dd.x * dd.x + dd.y * dd.y);
uu = dd.x * s.y;
vv = dd.y * s.x;
w = dd.x * p.y - dd.y * p.x;
best = -1;
ee1 = +uu - vv;
if ((ee1 < 0) == (w < 0)) {
if (best < mju_abs(ee1)) {
best = mju_abs(ee1);
c1 = 0;
}
}
ee1 = -uu - vv;
if ((ee1 < 0) == (w < 0)) {
if (best < mju_abs(ee1)) {
best = mju_abs(ee1);
c1 = 1;
}
}
ee1 = +uu + vv;
if ((ee1 < 0) == (w < 0)) {
if (best < mju_abs(ee1)) {
best = mju_abs(ee1);
c1 = 2;
}
}
ee1 = -uu + vv;
if ((ee1 < 0) == (w < 0)) {
if (best < mju_abs(ee1)) {
best = mju_abs(ee1);
c1 = 3;
}
}
// c.x = s.x * ((c1 / 2) ? -1 : 1);
// c.y = s.y * ((c1 % 2) ? -1 : 1);
ee1 = mju_abs(w) / l;
// e2 = best / l;
// printf("%g %g %g %g %g %g\n",c.x,c.y,d.x,d.y,e1,e2);
// tmp1.x = c.x - p.x;
// tmp1.y = c.y - p.y;
ee1 = dd.x * dd.x + dd.y * dd.y;
// e2 = tmp1.x * d.x + tmp1.y * d.y;
// e3 = e2 / e1;
// printf("%g %g %g %g %g %g %g \n",c.x,c.y,d.x,d.y,e1,e2,e3);
ee1 = p.x + (+s.y - p.y) / dd.y * dd.x;
// e2 = p.x + (-s.y - p.y) / d.y * d.x;
// e3 = p.y + (+s.x - p.x) / d.x * d.y;
// e4 = p.y + (-s.x - p.x) / d.x * d.y;
// printf("%g %g %g %g\n",e1,e2,e3,e4);
}
}
// goto skip; // allow only the closest contact
// cltype: -3 -1 : face is closest to the capsule
// cltype: 0..8 : edge is closest to the capsule
// cltype/3==0 means the lower corner is closest to the capsule (note that edges include corners)
// cltype/3==2 means the upper corner is closest to the capsule (note that edges include corners)
// cltype/3==1 means the middle of the edge is closest to the capsule
// cltype%3==0 means the lower corner is closest to the box (note that edges include corners)
// cltype%3==2 means the upper corner is closest to the box (note that edges include corners)
// cltype%3==1 means the middle of the capsule is closest to the box
// invalid type
if (cltype == -4)
return 0;
if (cltype >= 0 && cltype / 3 != 1) { // closest to a corner of the box
c1 = axisdir ^ clcorner;
// hack to find the relative orientation of capsule and corner
// there are 2 cases:
// 1: pointing to or away from the corner
// 2: oriented along a face or an edge
if (c1 == 0 || c1 == 7)
goto skip; // case 1: no chance of additional contact
if (c1 == 1 || c1 == 2 || c1 == 4) {
mul = 1;
de = 1 - bestsegmentpos;
dp = 1 + bestsegmentpos;
}
if (c1 == 3 || c1 == 5 || c1 == 6) {
mul = -1;
c1 = 7 - c1;
dp = 1 - bestsegmentpos;
de = 1 + bestsegmentpos;
}
// "de" and "dp" distance from first closest point on the capsule to both ends of it
// mul is a direction along the capsule's axis
if (c1 == 1)
ax = 0, ax1 = 1, ax2 = 2;
if (c1 == 2)
ax = 1, ax1 = 2, ax2 = 0;
if (c1 == 4)
ax = 2, ax1 = 0, ax2 = 1;
if (axis[ax]*axis[ax] > 0.5) { // second point along the edge of the box
secondpos = de; // initial position from the
e1 = 2 * size2[ax] / mju_abs(halfaxis[ax]);
if (e1 < secondpos) {
secondpos = e1; // we overshoot, move back to the other corner of the edge
}
secondpos *= mul;
} else { // second point along a face of the box
secondpos = dp;
// check for overshoot again
e1 = 2 * size2[ax1] / mju_abs(halfaxis[ax1]);
if (e1 < secondpos)
secondpos = e1;
e1 = 2 * size2[ax2] / mju_abs(halfaxis[ax2]);
if (e1 < secondpos)
secondpos = e1;
secondpos *= -mul;
}
} else if (cltype >= 0 && cltype / 3 == 1) { // we are on box's edge
// hacks to find the relative orientation of capsule and edge
// there are 2 cases:
// c1= 2^n: edge and capsule are oriented in a T configuration (no more contacts
// c1!=2^n: oriented in a cross X configuration
c1 = axisdir ^ clcorner; // same trick
c1 &= 7 - (1 << cledge); // even more hacks
// printf("%d %d %d %d %lf %lf %lf\n",
// axisdir,clcorner,c1,cledge,halfaxis[0],halfaxis[1],halfaxis[2]);
if (c1 != 1 && c1 != 2 && c1 != 4)
goto skip;
if (cledge == 0)
ax1 = 1, ax2 = 2;
if (cledge == 1)
ax1 = 2, ax2 = 0;
if (cledge == 2)
ax1 = 0, ax2 = 1;
ax = cledge;
// Then it finds with which face the capsule has a lower angle and switches the axis names
if (mju_abs(axis[ax1]) > mju_abs(axis[ax2]))
ax1 = ax2;
ax2 = 3 - ax - ax1;
// keep track of the axis orientation (mul will tell us which direction along the capsule to
// find the second point) you can notice all other references to the axis "halfaxis" are with
// absolute value
if (c1 & (1 << ax2)) {
mul = 1;
secondpos = 1 - bestsegmentpos;
} else {
mul = -1;
secondpos = 1 + bestsegmentpos;
}
// now we have to find out whether we point towards the opposite side or towards one of the
// sides and also find the farthest point along the capsule that is above the box
e1 = 2 * size2[ax2] / mju_abs(halfaxis[ax2]);
if (e1 < secondpos)
secondpos = e1;
if (((axisdir & (1 << ax)) != 0) == ((c1 & (1 << ax2)) != 0)) // that is insane
e2 = 1 - bestboxpos;
else
e2 = 1 + bestboxpos;
e1 = size2[ax] * e2 / mju_abs(halfaxis[ax]);
if (e1 < secondpos)
secondpos = e1;
secondpos *= mul;
} else if (cltype < 0) {
// similarly we handle the case when one capsule's end is closest to a face of the box
// and find where is the other end pointing to and clamping to the farthest point
// of the capsule that's above the box
if (clface == -1)
goto skip; // here the closest point is inside the box, no need for a second point
if (cltype == -3)
mul = 1;
else
mul = -1;
secondpos = 2;
mji_copy3(tmp1, pos);
mji_addToScl3(tmp1, halfaxis, -mul);
for (i = 0; i < 3; i++) {
if (i != clface) {
e1 = (size2[i] - tmp1[i]) / halfaxis[i] * mul;
if (e1 > 0)
if (e1 < secondpos)
secondpos = e1;
e1 = (-size2[i] - tmp1[i]) / halfaxis[i] * mul;
if (e1 > 0)
if (e1 < secondpos)
secondpos = e1;
}
}
secondpos *= mul;
}
skip:
// create sphere in original orientation at first contact point
mju_copy3(tmp1, pos);
mji_addToScl3(tmp1, halfaxis, bestsegmentpos);
mju_mulMatVec3(tmp2, mat2, tmp1);
mju_addTo3(tmp2, pos2);
// collide with
n = mjraw_SphereBox(con, margin, tmp2, mat1, size1, pos2, mat2, size2);
if (secondpos > -3) { // secondpos was modified
mju_copy3(tmp1, pos);
mji_addToScl3(tmp1, halfaxis, secondpos + bestsegmentpos); // note the summation
mju_mulMatVec3(tmp2, mat2, tmp1);
mju_addTo3(tmp2, pos2);
n += mjraw_SphereBox(con + n, margin, tmp2, mat1, size1, pos2, mat2, size2);
}
return n;
}
// capsule : box
int mjc_CapsuleBox(const mjModel* m, mjData* d, mjPreContact* con, int g1, int g2, mjtNum margin) {
const mjtNum* pos1 = d->geom_xpos + 3*g1;
const mjtNum* pos2 = d->geom_xpos + 3*g2;
const mjtNum* mat1 = d->geom_xmat + 9*g1;
const mjtNum* mat2 = d->geom_xmat + 9*g2;
const mjtNum* size1 = m->geom_size + 3*g1;
const mjtNum* size2 = m->geom_size + 3*g2;
return mjraw_CapsuleBox(con, margin, pos1, mat1, size1, pos2, mat2, size2);
}
// internal box : box
static inline
int _boxbox(const mjModel* M, const mjData* D, mjPreContact* con, int g1, int g2, mjtNum margin) {
const mjtNum* pos1 = D->geom_xpos + 3 * g1;
const mjtNum* pos2 = D->geom_xpos + 3 * g2;
const mjtNum* mat1 = D->geom_xmat + 9 * g1;
const mjtNum* mat2 = D->geom_xmat + 9 * g2;
const mjtNum* size1 = M->geom_size + 3 * g1;
const mjtNum* size2 = M->geom_size + 3 * g2;
mjtNum pos12[3], pos21[3], rot[9], rott[9], rotabs[9], rottabs[9], tmp1[3], tmp2[3], plen1[3],
plen2[3];
mjtNum rotmore[9], p[3], r[9], s[3], ss[3], lp[3], rt[9], points[mjMAXCONPAIR][3],
depth[mjMAXCONPAIR], pts[6][3], ppts2[4][2], pu[4][3], axi[3][3];
mjtNum linesu[4][6], lines[4][6], clnorm[3], rnorm[3];
mjtNum penetration, c1, c2, c3, a, b, c, d, lx, ly, hz, l, x, y, u, v, llx, lly, innorm, margin2;
mjtNum maxdepth;
int i0, i1, i2;
mjtNum f0, f1, f2;
int i, j, q, code, q1, q2, clcorner, n, m, k;
int cle1, cle2, in, ax1, ax2, pax1, pax2, clface, nl, nf;
n = 0;
code = -1;
margin2 = margin * margin;
mji_sub3(tmp1, pos2, pos1);
mji_mulMatTVec3(pos21, mat1, tmp1);
mji_sub3(tmp1, pos1, pos2);
mji_mulMatTVec3(pos12, mat2, tmp1);
mju_mulMatTMat3(rot, mat1, mat2);
mju_transpose(rott, rot, 3, 3);
for (i = 0; i < 9; i++)
rotabs[i] = mju_abs(rot[i]);
for (i = 0; i < 9; i++)
rottabs[i] = mju_abs(rott[i]);
mji_mulMatVec3(plen2, rotabs, size2);
mji_mulMatTVec3(plen1, rotabs, size1);
for (i = 0, penetration = margin; i < 3; i++)
penetration += size1[i] * 3 + size2[i] * 3;
for (i = 0; i < 3; i++) {
c1 = -mju_abs(pos21[i]) + size1[i] + plen2[i];
c2 = -mju_abs(pos12[i]) + size2[i] + plen1[i];
if (c1 < -margin || c2 < -margin)
return 0;
if (c1 < penetration) {
penetration = c1;
code = i + 3 * (pos21[i] < 0) + 0;
}
if (c2 < penetration) {
penetration = c2;
code = i + 3 * (pos12[i] < 0) + 6;
}
// printf("%24.16e %24.16e %d %24.16e %d \n",c1,c2,i,penetration,code);
}
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
mju_zero3(tmp2);
if (i == 0) {
tmp2[1] = -rott[3 * j + 2];
tmp2[2] = +rott[3 * j + 1];
} else if (i == 1) {
tmp2[0] = +rott[3 * j + 2];
tmp2[2] = -rott[3 * j + 0];
} else if (i == 2) {
tmp2[0] = -rott[3 * j + 1];
tmp2[1] = +rott[3 * j + 0];
}
c1 = mju_normalize3(tmp2);
if (c1 < mjMINVAL)
continue;
c2 = mju_dot3(pos21, tmp2);
c3 = 0;
for (k = 0; k < 3; k++)
if (k != i)
c3 += size1[k] * mju_abs(tmp2[k]);
for (k = 0; k < 3; k++)
if (k != j)
c3 += size2[k] * rotabs[3 * i + 3 - k - j] / c1;
c3 -= mju_abs(c2);
if (c3 < -margin)
return 0;
if (c3 < penetration * (1 - 1e-12))
{
penetration = c3;
for (k = cle1 = 0; k < 3; k++)
if (k != i)
if ((tmp2[k] > 0) ^ (c2 < 0))
cle1 += 1 << k;
for (k = cle2 = 0; k < 3; k++)
if (k != j)
if ((rot[3 * i + 3 - k - j] > 0) ^ (c2 < 0) ^ ((k - j + 3) % 3 == 1))
cle2 += 1 << k;
code = 12 + i * 3 + j;
mji_copy3(clnorm, tmp2);
in = c2 < 0;
}
// printf("%24.16e %d %24.16e %d\n",c3,12+i*3+j,penetration,code);
}
}
// return 0;
// printf("%d\n",code);
if (code == -1)
return 0; // shouldn't happen
if (code >= 12)
goto edgeedge;
q1 = code % 6;
q2 = code / 6;
// printf("%d %d\n",q1,q2);
mju_zero(rotmore, 9);
if (q1 == 0)
rotmore[2] = -1, rotmore[4] = +1, rotmore[6] = +1;
else if (q1 == 1)
rotmore[0] = +1, rotmore[5] = -1, rotmore[7] = +1;
else if (q1 == 2)
rotmore[0] = +1, rotmore[4] = +1, rotmore[8] = +1;
else if (q1 == 3)
rotmore[2] = +1, rotmore[4] = +1, rotmore[6] = -1;
else if (q1 == 4)
rotmore[0] = +1, rotmore[5] = +1, rotmore[7] = -1;
else if (q1 == 5)
rotmore[0] = -1, rotmore[4] = +1, rotmore[8] = -1;
i0 = 0;
i1 = 1;
i2 = 2;
f0 = f1 = f2 = 1;
if (q1 == 0) {
i0 = 2;
f0 = -1;
i2 = 0;
} else if (q1 == 1) {
i1 = 2;
f1 = -1;
i2 = 1;
} else if (q1 == 2) {
} else if (q1 == 3) {
i0 = 2;
i2 = 0;
f2 = -1;
} else if (q1 == 4) {
i1 = 2;
i2 = 1;
f2 = -1;
} else if (q1 == 5) {
f0 = -1;
f2 = -1;
}
#define rotaxis(vecres, vecin) \
{ \
vecres[0]=vecin[i0]*f0; \
vecres[1]=vecin[i1]*f1; \
vecres[2]=vecin[i2]*f2; \
}
#define rotmatx(matres, matin) \
{ \
mji_scl3(matres+0, matin+i0*3, f0); \
mji_scl3(matres+3, matin+i1*3, f1); \
mji_scl3(matres+6, matin+i2*3, f2); \
}
if (q2) {
mju_mulMatMatT3(r, rotmore, rot);
// mju_mulMatVec3(p,rotmore,pos12);
// mju_mulMatVec3(tmp1,rotmore,size2);
rotaxis(p, pos12);
rotaxis(tmp1, size2);
mji_copy3(s, size1);
} else {
// mju_mulMatMat(r,rotmore,rot,3,3,3);
rotmatx(r, rot);
// mju_mulMatVec3(p,rotmore,pos21);
// mju_mulMatVec3(tmp1,rotmore,size1);
rotaxis(p, pos21);
rotaxis(tmp1, size1);
mji_copy3(s, size2);
}
mju_transpose(rt, r, 3, 3);
for (i = 0; i < 3; i++)
ss[i] = mju_abs(tmp1[i]);
lx = ss[0];
ly = ss[1];
hz = ss[2];
p[2] -= hz;
mji_copy3(lp, p);
for (clcorner = 0, i = 0; i < 3; i++)
if (r[6 + i] < 0)
clcorner += 1 << i;
mji_addToScl3(lp, rt + 0, s[0] * ((clcorner & 1) ? 1 : -1));
mji_addToScl3(lp, rt + 3, s[1] * ((clcorner & 2) ? 1 : -1));
mji_addToScl3(lp, rt + 6, s[2] * ((clcorner & 4) ? 1 : -1));
m = k = 0;
mji_copy3(pts[m++], lp);
for (i = 0; i < 3; i++)
if (mju_abs(r[6 + i]) < 0.5)
mju_scl3(pts[m++], rt + 3 * i, s[i] * ((clcorner & (1 << i)) ? -2 : 2));
mji_add3(pts[3], pts[0], pts[1]);
mji_add3(pts[4], pts[0], pts[2]);
mji_add3(pts[5], pts[3], pts[2]);
if (m > 1)
{
mji_copy3(lines[k] + 0, pts[0]);
mji_copy3(lines[k++] + 3, pts[1]);
}
if (m > 2)
{
mji_copy3(lines[k] + 0, pts[0]);
mji_copy3(lines[k++] + 3, pts[2]);
mji_copy3(lines[k] + 0, pts[3]);
mji_copy3(lines[k++] + 3, pts[2]);
mji_copy3(lines[k] + 0, pts[4]);
mji_copy3(lines[k++] + 3, pts[1]);
}
for (i = 0; i < k; i++) {
for (q = 0; q < 2; q++) {
a = lines[i][0 + q];
b = lines[i][3 + q];
c = lines[i][1 - q];
d = lines[i][4 - q];
if (mju_abs(b) > mjMINVAL) {
for (j = -1; j <= 1; j += 2) {
l = ss[q] * j;
c1 = (l - a) * (1 / b);
if (c1 < 0 || c1 > 1)
continue;
c2 = c + d * c1;
if (mju_abs(c2) > ss[1 - q])
continue;
if (n < mjMAXCONPAIR) {
mji_copy3(points[n], lines[i]);
mji_addToScl3(points[n++], lines[i] + 3, c1);
}
}
}
}
}
a = pts[1][0];
b = pts[2][0];
c = pts[1][1];
d = pts[2][1];
c1 = a * d - b * c;
if (m > 2) {
for (i = 0; i < 4; i++) {
llx = i / 2 ? lx : -lx;
lly = i % 2 ? ly : -ly;
x = llx - pts[0][0];
y = lly - pts[0][1];
u = (x * d - y * b) * (1 / c1);
v = (y * a - x * c) * (1 / c1);
if (u <= 0 || v <= 0 || u >= 1 || v >= 1)
continue;
if (n < mjMAXCONPAIR) {
points[n][0] = llx;
points[n][1] = lly;
points[n][2] = (pts[0][2] + u * pts[1][2] + v * pts[2][2]);
n++;
}
}
}
for (i = 0; i < (1 << (m - 1)); i++) {
mji_copy3(tmp1, pts[i == 0 ? 0 : i + 2]);
if (i)
if (tmp1[0] <= -lx || tmp1[0] >= lx)
continue;
if (i)
if (tmp1[1] <= -ly || tmp1[1] >= ly)
continue;
if (n < mjMAXCONPAIR) {
mji_copy3(points[n++], tmp1);
}
}
m = n;
n = 0;
for (i = 0; i < m; i++) {
if (points[i][2] > margin)
continue;
if (n != i) mji_copy3(points[n], points[i]);
depth[n] = points[n][2];
points[n][2] *= 0.5;
n++;
}
mju_mulMatMatT3(r, q2 ? mat2 : mat1, rotmore);
mju_copy3(p, q2 ? pos2 : pos1);
tmp2[0] = (q2 ? -1 : 1) * r[2];
tmp2[1] = (q2 ? -1 : 1) * r[5];
tmp2[2] = (q2 ? -1 : 1) * r[8];
mji_copy3(con[0].normal, tmp2);
mji_zero3(con[0].tangent);
for (i = 0; i < n; i++) {
con[i].dist = 2 * points[i][2];
points[i][2] += hz;
mji_mulMatVec3(tmp2, r, points[i]);
mji_add3(con[i].pos, tmp2, p);
if (i) {
mji_copy3(con[i].normal, con[0].normal);
mji_zero3(con[i].tangent);
}
}
// printf("Path1: %d\n",n);
return n;
edgeedge:
code -= 12;
q1 = code / 3;
q2 = code % 3;
if (q2 == 0)
ax1 = 1, ax2 = 2;
if (q2 == 1)
ax1 = 0, ax2 = 2;
if (q2 == 2)
ax1 = 1, ax2 = 0;
if (q1 == 0)
pax1 = 1, pax2 = 2;
if (q1 == 1)
pax1 = 0, pax2 = 2;
if (q1 == 2)
pax1 = 1, pax2 = 0;
// printf("%lf %lf %lf %lf\n",rot[ 3*q1+ ax1],rot [3*q1+ ax2],rott[3*q2+pax1],rott[3*q2+pax2]);
// printf("%lf %lf\n",mju_dot3(clnorm,rott+3*ax1),mju_dot3(clnorm,rott+3*ax2));
if (rotabs [3 * q1 + ax1] < rotabs [3 * q1 + ax2]) {
ax1 = ax2;
ax2 = 3 - q2 - ax1;
}
if (rottabs[3 * q2 + pax1] < rottabs[3 * q2 + pax2]) {
pax1 = pax2;
pax2 = 3 - q1 - pax1;
}
if (cle1 & (1 << pax2))
clface = pax2;
else
clface = pax2 + 3;
// printf("%lf - %d %d %d %d %d %d %d %d %d %d %d\n",
// penetration,cle1,cle2,code,in,q1,q2,clface,ax1,ax2,pax1,pax2);
mju_zero(rotmore, 9);
if (clface == 0)
rotmore[2] = -1, rotmore[4] = +1, rotmore[6] = +1;
else if (clface == 1)
rotmore[0] = +1, rotmore[5] = -1, rotmore[7] = +1;
else if (clface == 2)
rotmore[0] = +1, rotmore[4] = +1, rotmore[8] = +1;
else if (clface == 3)
rotmore[2] = +1, rotmore[4] = +1, rotmore[6] = -1;
else if (clface == 4)
rotmore[0] = +1, rotmore[5] = +1, rotmore[7] = -1;
else if (clface == 5)
rotmore[0] = -1, rotmore[4] = +1, rotmore[8] = -1;
i0 = 0;
i1 = 1;
i2 = 2;
f0 = f1 = f2 = 1;
if (clface == 0) {
i0 = 2;
f0 = -1;
i2 = 0;
} else if (clface == 1) {
i1 = 2;
f1 = -1;
i2 = 1;
} else if (clface == 2) {
} else if (clface == 3) {
i0 = 2;
i2 = 0;
f2 = -1;
} else if (clface == 4) {
i1 = 2;
i2 = 1;
f2 = -1;
} else if (clface == 5) {
f0 = -1;
f2 = -1;
}
// mju_mulMatVec3(p,rotmore,pos21);
// mju_mulMatVec3(rnorm,rotmore,clnorm);
rotaxis(p, pos21);
rotaxis(rnorm, clnorm);
// print("rnorm",rnorm);
// mju_mulMatMat(r,rotmore,rot,3,3,3);
rotmatx(r, rot);
mji_mulMatTVec3(tmp1, rotmore, size1);
for (i = 0; i < 3; i++)
s[i] = mju_abs(tmp1[i]);
mju_transpose(rt, r, 3, 3);
lx = s[0];
ly = s[1];
hz = s[2];
p[2] -= hz;
n = 0;
mji_copy3(points[n], p);
mji_addToScl3(points[n], rt + 3 * ax1, size2[ax1] * ((cle2 & (1 << ax1)) ? 1 : -1));
mji_addToScl3(points[n], rt + 3 * ax2, size2[ax2] * ((cle2 & (1 << ax2)) ? 1 : -1));
mji_copy3(points[n + 1], points[n]);
mji_addToScl3(points[n], rt + 3 * q2, size2[q2]);
n = 1;
mji_addToScl3(points[n], rt + 3 * q2, -size2[q2]);
n = 2;
mji_copy3(points[n], p);
mji_addToScl3(points[n], rt + 3 * ax1, size2[ax1] * ((cle2 & (1 << ax1)) ? -1 : 1));
mji_addToScl3(points[n], rt + 3 * ax2, size2[ax2] * ((cle2 & (1 << ax2)) ? 1 : -1));
mji_copy3(points[n + 1], points[n]);
mji_addToScl3(points[n], rt + 3 * q2, size2[q2]);
n = 3;
mji_addToScl3(points[n], rt + 3 * q2, -size2[q2]);
n = 4;
mji_copy3(axi[0], points[0]);
mji_sub3(axi[1], points[1], points[0]);
mji_sub3(axi[2], points[2], points[0]);
if (mju_abs(rnorm[2]) < mjMINVAL)
return 0; // shouldn't happen
innorm = (1 / rnorm[2]) * (in ? -1 : 1);
// printf("%lf\n",innorm);
for (i = 0; i < 4; i++)
{
c1 = -points[i][2] * (1 / rnorm[2]);
mji_copy3(pu[i], points[i]);
mji_addToScl3(points[i], rnorm, c1);
// ppts[i][0]=points[i][0];
// ppts[i][1]=points[i][1];
ppts2[i][0] = points[i][0];
ppts2[i][1] = points[i][1];
}
mji_copy3(pts[0], points[0]);
mji_sub3(pts[1], points[1], points[0]);
mji_sub3(pts[2], points[2], points[0]);
m = 3;
k = 0;
n = 0;
if (m > 1) {
mji_copy3(lines[k] + 0, pts[0]);
mji_copy3(lines[k] + 3, pts[1]);
mji_copy3(linesu[k] + 0, axi[0]);
mji_copy3(linesu[k++] + 3, axi[1]);
}
if (m > 2) {
mji_copy3(lines[k] + 0, pts[0]);
mji_copy3(lines[k] + 3, pts[2]);
mji_copy3(linesu[k] + 0, axi[0]);
mji_copy3(linesu[k++] + 3, axi[2]);
mji_add3(lines[k] + 0, pts[0], pts[1]);
mji_copy3(lines[k] + 3, pts[2]);
mji_add3(linesu[k] + 0, axi[0], axi[1]);
mji_copy3(linesu[k++] + 3, axi[2]);
mji_add3(lines[k] + 0, pts[0], pts[2]);
mji_copy3(lines[k] + 3, pts[1]);
mji_add3(linesu[k] + 0, axi[0], axi[2]);
mji_copy3(linesu[k++] + 3, axi[1]);
}
for (i = 0; i < k; i++) {
for (q = 0; q < 2; q++) {
a = lines[i][0 + q];
b = lines[i][3 + q];
c = lines[i][1 - q];
d = lines[i][4 - q];
if (mju_abs(b) > mjMINVAL) {
for (j = -1; j <= 1; j += 2) {
if (n < mjMAXCONPAIR) {
l = s[q] * j;
c1 = (l - a) * (1 / b);
if (c1 < 0 || c1 > 1)
continue;
c2 = c + d * c1;
if (mju_abs(c2) > s[1 - q])
continue;
if ((linesu[i][2] + linesu[i][5]*c1)*innorm > margin)
continue;
mji_scl3(points[n], linesu[i], 0.5);
mji_addToScl3(points[n], linesu[i] + 3, 0.5 * c1);
points[n][0 + q] += 0.5 * l;
points[n][1 - q] += 0.5 * c2;
depth[n] = points[n][2] * innorm * 2;
n++;
}
}
}
}
}
nl = n;
a = pts[1][0];
b = pts[2][0];
c = pts[1][1];
d = pts[2][1];
c1 = a * d - b * c;
for (i = 0; i < 4; i++) {
if (n < mjMAXCONPAIR) {
llx = i / 2 ? lx : -lx;
lly = i % 2 ? ly : -ly;
x = llx - pts[0][0];
y = lly - pts[0][1];
u = (x * d - y * b) * (1 / c1);
v = (y * a - x * c) * (1 / c1);
if (nl == 0) {
if ((u < 0 || u > 1) && (v < 0 || v > 1))
continue;
} else {
if ((u < 0 || u > 1 || v < 0 || v > 1))
continue;
}
if (u < 0)
u = 0;
if (u > 1)
u = 1;
if (v < 0)
v = 0;
if (v > 1)
v = 1;
mji_scl3(tmp1, pu[0], 1 - u - v);
mji_addToScl3(tmp1, pu[1], u);
mji_addToScl3(tmp1, pu[2], v);
points[n][0] = llx;
points[n][1] = lly;
points[n][2] = 0;
mji_sub3(tmp2, points[n], tmp1);
c1 = mju_dot3(tmp2, tmp2);
if (tmp1[2] > 0)
if (c1 > margin2)
continue;
mji_addTo3(points[n], tmp1);
mju_scl3(points[n], points[n], 0.5);
depth[n] = sqrt(c1) * (tmp1[2] < 0 ? -1 : 1);
n++;
}
}
nf = n;
for (i = 0; i < 4; i++) {
if (n < mjMAXCONPAIR) {
x = ppts2[i][0];
y = ppts2[i][1];
if (nl == 0) {
if (nf == 0) {
} else {
if (x < -lx || x > lx)
if (y < -ly || y > ly)
continue;
}
} else {
if (x < -lx || x > lx || y < -ly || y > ly)
continue;
}
for (c1 = 0, j = 0; j < 2; j++)
if (ppts2[i][j] < -s[j])
c1 += (ppts2[i][j] + s[j]) * (ppts2[i][j] + s[j]);
else if (ppts2[i][j] > s[j])
c1 += (ppts2[i][j] - s[j]) * (ppts2[i][j] - s[j]);
c1 += pu[i][2] * innorm * pu[i][2] * innorm;
if (pu[i][2] > 0)
if (c1 > margin2)
continue;
tmp1[0] = ppts2[i][0] * 0.5;
tmp1[1] = ppts2[i][1] * 0.5;
tmp1[2] = 0;
for (j = 0; j < 2; j++) {
if (ppts2[i][j] < -s[j])
tmp1[j] = -s[j] * 0.5;
else if (ppts2[i][j] > s[j])
tmp1[j] = +s[j] * 0.5;
}
mji_addToScl3(tmp1, pu[i], 0.5);
mji_copy3(points[n], tmp1);
depth[n] = sqrt(c1) * (pu[i][2] < 0 ? -1 : 1);
n++;
}
}
mju_mulMatMatT3(r, mat1, rotmore);
mji_mulMatVec3(tmp1, r, rnorm);
mji_scl3(con[0].normal, tmp1, in ? -1 : 1);
mji_zero3(con[0].tangent);
// no contact can be deeper than the support overlap along the separating axis: clipping
// against a grazing face can synthesize spurious points with arbitrarily large depth;
// the slack covers rounding error so the deepest legitimate point is never rejected
maxdepth = mju_max(0, penetration);
maxdepth += margin + mjDEPTHSLACKREL * maxdepth +
mjDEPTHSLACKABS * (size1[0] + size1[1] + size1[2] +
size2[0] + size2[1] + size2[2]);
for (i = 0, m = 0; i < n; i++) {
if (depth[i] < -maxdepth)
continue;
con[m].dist = depth[i];
points[i][2] += hz;
mji_mulMatVec3(tmp2, r, points[i]);
mji_add3(con[m].pos, tmp2, pos1);
mji_copy3(con[m].normal, con[0].normal);
mji_zero3(con[m].tangent);
m++;
}
return m;
#undef rotaxis
#undef rotmatx
}
// box : box
int mjc_BoxBox(const mjModel* m, mjData* d, mjPreContact* con, int g1, int g2, mjtNum margin) {
mjPreContact tmp[mjMAXCONPAIR];
int num = _boxbox(m, d, tmp, g1, g2, margin);
// -1: bad, 0: good
int dupe[mjMAXCONPAIR] = {0};
// get box info
const mjtNum* pos1 = d->geom_xpos + 3 * g1;
const mjtNum* mat1 = d->geom_xmat + 9 * g1;
const mjtNum* size1 = m->geom_size + 3 * g1;
const mjtNum* pos2 = d->geom_xpos + 3 * g2;
const mjtNum* mat2 = d->geom_xmat + 9 * g2;
const mjtNum* size2 = m->geom_size + 3 * g2;
// find bad: contacts outside one of the boxes
for (int i=0; i < num; i++) {
// box sizes with margin
mjtNum sz1[3] = {size1[0] + margin, size1[1] + margin, size1[2] + margin};
mjtNum sz2[3] = {size2[0] + margin, size2[1] + margin, size2[2] + margin};
// relative distance from surface (1%) outside of which box-box contacts are removed
static mjtNum kRemoveRatio = 1.01;
// is the contact outside: 1, inside: -1, within the removal width: 0
int out1 = mju_outsideBox(tmp[i].pos, pos1, mat1, sz1, kRemoveRatio);
int out2 = mju_outsideBox(tmp[i].pos, pos2, mat2, sz2, kRemoveRatio);
// mark as bad if outside one box and not inside the other box
if ((out1 == 1 && out2 != -1) || (out2 == 1 && out1 != -1)) {
dupe[i] = -1;
}
}
// find duplicates
for (int i=0; i < num-1; i++) {
if (dupe[i] == -1) {
continue; // already marked bad: skip
}
for (int j=i+1; j < num; j++) {
if (dupe[j] == -1) {
continue; // already marked bad: skip
}
if (tmp[i].pos[0] == tmp[j].pos[0] &&
tmp[i].pos[1] == tmp[j].pos[1] &&
tmp[i].pos[2] == tmp[j].pos[2]) {
dupe[i] = -1;
break;
}
}
}
// consolidate good
int ncon = 0;
for (int j=0; j < num; j++) {
if (dupe[j] == 0) {
con[ncon++] = tmp[j];
if (ncon >= 8) {
break;
}
}
}
return ncon;
}