Skip to content

Commit 12b40b9

Browse files
kbayescopybara-github
authored andcommitted
Remove MuJoCo stack calls in nativeccd.
1. There is now thread local static memory for use with a conservative max number of iterations needed for contact. 2. For possible needs of high precision contact recovery with large iteration numbers (i.e. ellipsoid-ellipsoid collisions), there are callbacks to allocate memory when needed. PiperOrigin-RevId: 739187134 Change-Id: I3d210f75218922d969c78465da3fe50da4fe3080
1 parent dc96ed6 commit 12b40b9

File tree

4 files changed

+123
-74
lines changed

4 files changed

+123
-74
lines changed

src/engine/engine_collision_convex.c

+19
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,24 @@
2525
#include <mujoco/mjmodel.h>
2626
#include "engine/engine_collision_gjk.h"
2727
#include "engine/engine_collision_primitive.h"
28+
#include "engine/engine_io.h"
2829
#include "engine/engine_util_blas.h"
2930
#include "engine/engine_util_errmem.h"
3031
#include "engine/engine_util_misc.h"
3132
#include "engine/engine_util_spatial.h"
3233

34+
35+
// allocate callback for EPA in nativeccd
36+
static void* ccd_allocate(void* data, size_t nbytes) {
37+
mj_markStack((mjData*)data);
38+
return mj_stackAllocByte((mjData*)data, nbytes, sizeof(mjtNum));
39+
}
40+
41+
// free callback for EPA in nativeccd
42+
static void ccd_free(void* data, void* buffer) {
43+
mj_freeStack((mjData*)data);
44+
}
45+
3346
// call libccd or nativeccd to recover penetration info
3447
static int mjc_penetration(const mjModel* m, mjCCDObj* obj1, mjCCDObj* obj2,
3548
const ccd_t* ccd, ccd_real_t* depth, ccd_vec3_t* dir, ccd_vec3_t* pos) {
@@ -46,6 +59,9 @@ static int mjc_penetration(const mjModel* m, mjCCDObj* obj1, mjCCDObj* obj2,
4659
config.tolerance = ccd->mpr_tolerance,
4760
config.max_contacts = 1;
4861
config.dist_cutoff = 0; // no geom distances needed
62+
config.context = (void*)obj1->data;
63+
config.alloc = ccd_allocate;
64+
config.free = ccd_free;
4965

5066
mjtNum dist = mjc_ccd(&config, &status, obj1, obj2);
5167
if (dist < 0) {
@@ -800,6 +816,9 @@ static int mjc_CCDIteration(const mjModel* m, const mjData* d, mjCCDObj* obj1, m
800816
config.tolerance = m->opt.ccd_tolerance;
801817
config.max_contacts = max_contacts;
802818
config.dist_cutoff = 0; // no geom distances needed
819+
config.context = (void*)d;
820+
config.alloc = ccd_allocate;
821+
config.free = ccd_free;
803822

804823
mjtNum dist = mjc_ccd(&config, &status, obj1, obj2);
805824
if (dist < 0) {

src/engine/engine_collision_gjk.c

+76-70
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616

1717
#include <float.h>
1818
#include <stddef.h>
19+
#include <stdint.h>
1920
#include <stdlib.h>
2021

2122
#include <mujoco/mjtnum.h>
2223
#include <mujoco/mjmodel.h>
2324
#include "engine/engine_collision_convex.h"
24-
#include "engine/engine_io.h"
2525
#include "engine/engine_util_blas.h"
2626
#include "engine/engine_util_errmem.h"
2727

@@ -64,6 +64,12 @@ typedef struct {
6464
int maxfaces; // max number of faces that can be stored in polytope
6565
Face** map; // linear map storing faces
6666
int nmap; // number of faces in map
67+
struct Horizon { // polytope boundary edges that can be seen from w
68+
int* indices; // indices of faces on horizon
69+
int* edges; // corresponding edge of each face on the horizon
70+
int nedges; // number of edges in horizon
71+
mjtNum* w; // point where horizon is created
72+
} horizon;
6773
} Polytope;
6874

6975
// compute the support point for EPA
@@ -1223,21 +1229,10 @@ static inline mjtNum attachFace(Polytope* pt, int v1, int v2, int v3,
12231229

12241230

12251231

1226-
// horizon: polytope boundary edges that can be seen from w
1227-
typedef struct {
1228-
Polytope* pt; // polytope for which the horizon is defined
1229-
int* indices; // indices of faces on horizon
1230-
int* edges; // corresponding edge of each face on the horizon
1231-
int nedges; // number of edges in horizon
1232-
mjtNum* w; // point where horizon is created
1233-
} Horizon;
1234-
1235-
1236-
12371232
// add an edge to the horizon
1238-
static inline void addEdge(Horizon* h, int index, int edge) {
1239-
h->edges[h->nedges] = edge;
1240-
h->indices[h->nedges++] = index;
1233+
static inline void addEdge(Polytope* pt, int index, int edge) {
1234+
pt->horizon.edges[pt->horizon.nedges] = edge;
1235+
pt->horizon.indices[pt->horizon.nedges++] = index;
12411236
}
12421237

12431238

@@ -1252,21 +1247,21 @@ static inline int getEdge(Face* face, int vertex) {
12521247

12531248

12541249
// recursive call to build horizon; return 1 if face is visible from w otherwise 0
1255-
static int horizonRec(Horizon* h, Face* face, int e) {
1250+
static int horizonRec(Polytope* pt, Face* face, int e) {
12561251
mjtNum dist2 = face->dist * face->dist;
12571252

12581253
// v is visible from w so it is deleted and adjacent faces are checked
1259-
if (dot3(face->v, h->w) >= dist2) {
1260-
deleteFace(h->pt, face);
1254+
if (dot3(face->v, pt->horizon.w) >= dist2) {
1255+
deleteFace(pt, face);
12611256

12621257
// recursively search the adjacent faces on the next two edges
12631258
for (int k = 1; k < 3; k++) {
12641259
int i = (e + k) % 3;
1265-
Face* adjFace = &h->pt->faces[face->adj[i]];
1260+
Face* adjFace = &pt->faces[face->adj[i]];
12661261
if (adjFace->index > -2) {
12671262
int adjEdge = getEdge(adjFace, face->verts[(i + 1) % 3]);
1268-
if (!horizonRec(h, adjFace, adjEdge)) {
1269-
addEdge(h, face->adj[i], adjEdge);
1263+
if (!horizonRec(pt, adjFace, adjEdge)) {
1264+
addEdge(pt, face->adj[i], adjEdge);
12701265
}
12711266
}
12721267
}
@@ -1278,28 +1273,28 @@ static int horizonRec(Horizon* h, Face* face, int e) {
12781273

12791274

12801275
// create horizon given the face as starting point
1281-
static void horizon(Horizon* h, Face* face) {
1282-
deleteFace(h->pt, face);
1276+
static void horizon(Polytope* pt, Face* face) {
1277+
deleteFace(pt, face);
12831278

12841279
// first edge
1285-
Face* adjFace = &h->pt->faces[face->adj[0]];
1280+
Face* adjFace = &pt->faces[face->adj[0]];
12861281
int adjEdge = getEdge(adjFace, face->verts[1]);
1287-
if (!horizonRec(h, adjFace, adjEdge)) {
1288-
addEdge(h, face->adj[0], adjEdge);
1282+
if (!horizonRec(pt, adjFace, adjEdge)) {
1283+
addEdge(pt, face->adj[0], adjEdge);
12891284
}
12901285

12911286
// second edge
1292-
adjFace = &h->pt->faces[face->adj[1]];
1287+
adjFace = &pt->faces[face->adj[1]];
12931288
adjEdge = getEdge(adjFace, face->verts[2]);
1294-
if (adjFace->index > -2 && !horizonRec(h, adjFace, adjEdge)) {
1295-
addEdge(h, face->adj[1], adjEdge);
1289+
if (adjFace->index > -2 && !horizonRec(pt, adjFace, adjEdge)) {
1290+
addEdge(pt, face->adj[1], adjEdge);
12961291
}
12971292

12981293
// third edge
1299-
adjFace = &h->pt->faces[face->adj[2]];
1294+
adjFace = &pt->faces[face->adj[2]];
13001295
adjEdge = getEdge(adjFace, face->verts[0]);
1301-
if (adjFace->index > -2 && !horizonRec(h, adjFace, adjEdge)) {
1302-
addEdge(h, face->adj[2], adjEdge);
1296+
if (adjFace->index > -2 && !horizonRec(pt, adjFace, adjEdge)) {
1297+
addEdge(pt, face->adj[2], adjEdge);
13031298
}
13041299
}
13051300

@@ -1338,17 +1333,8 @@ static void epaWitness(const Polytope* pt, const Face* face, mjtNum x1[3], mjtNu
13381333
static Face* epa(mjCCDStatus* status, Polytope* pt, mjCCDObj* obj1, mjCCDObj* obj2) {
13391334
mjtNum tolerance = status->tolerance, lower, upper = FLT_MAX;
13401335
int k, kmax = status->max_iterations;
1341-
mjData* d = (mjData*) obj1->data;
13421336
Face* face = NULL, *pface = NULL; // face closest to origin
13431337

1344-
// initialize horizon
1345-
Horizon h;
1346-
mj_markStack(d);
1347-
h.indices = mjSTACKALLOC(d, 6 + status->max_iterations, int);
1348-
h.edges = mjSTACKALLOC(d, 6 + status->max_iterations, int);
1349-
h.nedges = 0;
1350-
h.pt = pt;
1351-
13521338
for (k = 0; k < kmax; k++) {
13531339
pface = face;
13541340

@@ -1382,17 +1368,17 @@ static Face* epa(mjCCDStatus* status, Polytope* pt, mjCCDObj* obj1, mjCCDObj* ob
13821368
break;
13831369
}
13841370

1385-
h.w = w;
1386-
horizon(&h, face);
1371+
pt->horizon.w = w;
1372+
horizon(pt, face);
13871373

13881374
// unrecoverable numerical issue; at least one face was deleted so nedges is 3 or more
1389-
if (h.nedges < 3) {
1375+
if (pt->horizon.nedges < 3) {
13901376
face = NULL;
13911377
break;
13921378
}
13931379

13941380
// insert w as new vertex and attach faces along the horizon
1395-
int nfaces = pt->nfaces, nedges = h.nedges;
1381+
int nfaces = pt->nfaces, nedges = pt->horizon.nedges;
13961382

13971383
// check if there's enough memory to store new faces
13981384
if (nedges > maxFaces(pt)) {
@@ -1401,7 +1387,7 @@ static Face* epa(mjCCDStatus* status, Polytope* pt, mjCCDObj* obj1, mjCCDObj* ob
14011387
}
14021388

14031389
// attach first face
1404-
int horIndex = h.indices[0], horEdge = h.edges[0];
1390+
int horIndex = pt->horizon.indices[0], horEdge = pt->horizon.edges[0];
14051391
Face* horFace = &pt->faces[horIndex];
14061392
int v1 = horFace->verts[horEdge],
14071393
v2 = horFace->verts[(horEdge + 1) % 3];
@@ -1426,7 +1412,7 @@ static Face* epa(mjCCDStatus* status, Polytope* pt, mjCCDObj* obj1, mjCCDObj* ob
14261412
int cur = nfaces + i; // index of attached face
14271413
int next = nfaces + (i + 1) % nedges; // index of next face
14281414

1429-
horIndex = h.indices[i], horEdge = h.edges[i];
1415+
horIndex = pt->horizon.indices[i], horEdge = pt->horizon.edges[i];
14301416
horFace = &pt->faces[horIndex];
14311417
v1 = horFace->verts[horEdge];
14321418
v2 = horFace->verts[(horEdge + 1) % 3];
@@ -1446,15 +1432,14 @@ static Face* epa(mjCCDStatus* status, Polytope* pt, mjCCDObj* obj1, mjCCDObj* ob
14461432
pt->map[idx]->index = idx;
14471433
}
14481434
}
1449-
h.nedges = 0; // clear horizon
1435+
pt->horizon.nedges = 0; // clear horizon
14501436

14511437
// no face candidates left
14521438
if (!pt->nmap || !face) {
14531439
break;
14541440
}
14551441
}
14561442

1457-
mj_freeStack(d);
14581443
status->epa_iterations = k;
14591444
if (face) {
14601445
epaWitness(pt, face, status->x1, status->x2);
@@ -2222,6 +2207,14 @@ static inline void inflate(mjCCDStatus* status, mjtNum margin1, mjtNum margin2)
22222207

22232208
// general convex collision detection
22242209
mjtNum mjc_ccd(const mjCCDConfig* config, mjCCDStatus* status, mjCCDObj* obj1, mjCCDObj* obj2) {
2210+
// pre-allocate static memory for low iterations
2211+
void* buffer = NULL;
2212+
static _Thread_local Vertex vert_data[5 + mjMAX_EPA_ITERATIONS];
2213+
static _Thread_local Face face_data[6 * mjMAX_EPA_ITERATIONS];
2214+
static _Thread_local Face* map_data[6 * mjMAX_EPA_ITERATIONS];
2215+
static _Thread_local int index_data[6 + mjMAX_EPA_ITERATIONS];
2216+
static _Thread_local int edge_data[6 + mjMAX_EPA_ITERATIONS];
2217+
22252218
// setup
22262219
obj1->center(status->x1, obj1);
22272220
obj2->center(status->x2, obj2);
@@ -2305,29 +2298,40 @@ mjtNum mjc_ccd(const mjCCDConfig* config, mjCCDStatus* status, mjCCDObj* obj1, m
23052298

23062299
if (status->dist <= config->tolerance && status->nsimplex > 1) {
23072300
status->dist = 0; // assume touching
2308-
int N = status->max_iterations;
2309-
mjData* d = (mjData*) obj1->data;
2310-
mj_markStack((mjData*) obj1->data);
2311-
23122301
Polytope pt;
2313-
pt.nfaces = pt.nmap = pt.nverts = 0;
2314-
2315-
// allocate memory for vertices
2316-
pt.verts = mjSTACKALLOC(d, 5 + N, Vertex);
2317-
2318-
// allocate memory for faces
2319-
pt.maxfaces = (6*N > 1000) ? 6*N : 1000; // use 1000 faces as lower bound
2320-
size_t size1 = sizeof(Face) * pt.maxfaces;
2321-
size_t size2 = sizeof(Face*) * pt.maxfaces;
2302+
pt.nfaces = pt.nmap = pt.nverts = pt.horizon.nedges = 0;
2303+
2304+
// allocate memory via static thread-local storage
2305+
int N = config->max_iterations;
2306+
if (N <= mjMAX_EPA_ITERATIONS) {
2307+
pt.maxfaces = 6 * mjMAX_EPA_ITERATIONS;
2308+
pt.verts = vert_data;
2309+
pt.faces = face_data;
2310+
pt.map = map_data;
2311+
pt.horizon.indices = index_data;
2312+
pt.horizon.edges = edge_data;
2313+
}
23222314

2323-
// since a generous upper bound is used, we need to rescale stack use if not enough
2324-
// memory is available
2325-
size_t max_size = mj_stackBytesAvailable(d) - 12*(N * sizeof(int));
2326-
if (size1 + size2 > max_size) {
2327-
pt.maxfaces = max_size / (sizeof(Face) + sizeof(Face*));
2315+
// static storage insufficient, allocate with callback
2316+
else {
2317+
size_t nbytes = (sizeof(Face) * 6 * N) // faces in polytope
2318+
+ (sizeof(Face*) * 6 * N) // map in polytope
2319+
+ (sizeof(Vertex) * (5 + N)) // vertices in polytope
2320+
+ 2*(sizeof(int) * (6 + N)); // horizon data
2321+
2322+
pt.maxfaces = 6 * N;
2323+
buffer = config->alloc(config->context, nbytes);
2324+
uint8_t* bbuffer = (uint8_t*)buffer;
2325+
pt.verts = (Vertex*)bbuffer;
2326+
bbuffer += sizeof(Vertex) * (5 + N);
2327+
pt.faces = (Face*)bbuffer;
2328+
bbuffer += sizeof(Face) * (6 * N);
2329+
pt.map = (Face**)bbuffer;
2330+
bbuffer += sizeof(Face*) * (6 * N);
2331+
pt.horizon.indices = (int*)bbuffer;
2332+
bbuffer += sizeof(int) * (6 + N);
2333+
pt.horizon.edges = (int*)bbuffer;
23282334
}
2329-
pt.faces = mjSTACKALLOC(d, pt.maxfaces, Face);
2330-
pt.map = mjSTACKALLOC(d, pt.maxfaces, Face*);
23312335

23322336
int ret;
23332337
if (status->nsimplex == 2) {
@@ -2346,7 +2350,9 @@ mjtNum mjc_ccd(const mjCCDConfig* config, mjCCDStatus* status, mjCCDObj* obj1, m
23462350
multicontact(&pt, face, status, obj1, obj2);
23472351
}
23482352
}
2349-
mj_freeStack(d);
2353+
}
2354+
if (buffer) {
2355+
config->free(config->context, buffer);
23502356
}
23512357
return status->dist;
23522358
}

src/engine/engine_collision_gjk.h

+16-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#ifndef MUJOCO_SRC_ENGINE_ENGINE_COLLISION_GJK_H_
1616
#define MUJOCO_SRC_ENGINE_ENGINE_COLLISION_GJK_H_
1717

18+
#include <stddef.h>
19+
1820
#include <mujoco/mjexport.h>
1921
#include <mujoco/mjmodel.h>
2022
#include <mujoco/mjtnum.h>
@@ -25,6 +27,9 @@
2527
extern "C" {
2628
#endif
2729

30+
// max number of EPA iterations
31+
#define mjMAX_EPA_ITERATIONS 170
32+
2833
// tolerance for normal alignment of two faces (cosine of 1.6e-3)
2934
#define mjFACE_TOL 0.99999872
3035

@@ -60,10 +65,17 @@ typedef struct {
6065

6166
// configuration for convex collision detection
6267
typedef struct {
63-
int max_iterations; // the maximum number of iterations for GJK and EPA
64-
mjtNum tolerance; // tolerance used by GJK and EPA
65-
int max_contacts; // set to max number of contact points to recover
66-
mjtNum dist_cutoff; // set to max geom distance to recover
68+
int max_iterations; // the maximum number of iterations for GJK and EPA
69+
mjtNum tolerance; // tolerance used by GJK and EPA
70+
int max_contacts; // set to max number of contact points to recover
71+
mjtNum dist_cutoff; // set to max geom distance to recover
72+
void* context; // opaque data pointer passed to callbacks
73+
74+
// callback to allocate memory for polytope (only needed for penetration recovery)
75+
void*(*alloc)(void* context, size_t nbytes);
76+
77+
// callback to free memory from alloc callback
78+
void(*free)(void* context, void* buffer);
6779
} mjCCDConfig;
6880

6981
// data produced from running GJK and EPA

0 commit comments

Comments
 (0)