Add collisions between flexes and SDF geoms.
The flex-SDF algorithm uses the same logic as mesh-SDF. Previous to this change, the flex was instead colliding with the convex hull of the mesh generated automatically with marching cubes from the SDF. PiperOrigin-RevId: 876420302 Change-Id: I3bf6552b6b8e108ba4c4209bbcf10fe5f2b2fd90
This commit is contained in:
committed by
Copybara-Service
parent
c2e67ab507
commit
bf74d01d93
@@ -5,6 +5,11 @@ Changelog
|
|||||||
Upcoming version (not yet released)
|
Upcoming version (not yet released)
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
General
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
- Flexes now support collisions with SDF geoms.
|
||||||
|
|
||||||
MJX
|
MJX
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<!-- Copyright 2026 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<mujoco>
|
||||||
|
<extension>
|
||||||
|
<plugin plugin="mujoco.sdf.torus">
|
||||||
|
<instance name="torus">
|
||||||
|
<config key="radius1" value="0.55"/>
|
||||||
|
<config key="radius2" value="0.15"/>
|
||||||
|
</instance>
|
||||||
|
</plugin>
|
||||||
|
</extension>
|
||||||
|
|
||||||
|
<asset>
|
||||||
|
<mesh name="torus">
|
||||||
|
<plugin instance="torus"/>
|
||||||
|
</mesh>
|
||||||
|
</asset>
|
||||||
|
|
||||||
|
<include file="scene.xml"/>
|
||||||
|
|
||||||
|
<worldbody>
|
||||||
|
<body pos="0 0 0.3">
|
||||||
|
<geom type="sdf" mesh="torus" rgba=".8 .17 .15 1">
|
||||||
|
<plugin instance="torus"/>
|
||||||
|
</geom>
|
||||||
|
</body>
|
||||||
|
<body name="flex" pos="0 0 .6">
|
||||||
|
<flexcomp name="test" type="grid" count="8 8 1" spacing=".2 .2 .2" dim="2" rgba="0 .7 .7 1">
|
||||||
|
<edge equality="true"/>
|
||||||
|
<contact selfcollide="none" internal="false"/>
|
||||||
|
</flexcomp>
|
||||||
|
</body>
|
||||||
|
<body pos="0 0 1.0">
|
||||||
|
<freejoint/>
|
||||||
|
<geom type="sphere" size="0.1" mass=".5" rgba="0 0 1 .2"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
@@ -484,6 +484,14 @@ void mj_collision(const mjModel* m, mjData* d) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDF special processing
|
||||||
|
if (m->geom_type[g] == mjGEOM_SDF) {
|
||||||
|
int ncon_before = d->ncon;
|
||||||
|
mj_collideSdfFlex(m, d, g, f);
|
||||||
|
filterFlexContacts(d, ncon_before);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// collide geom with flex elements
|
// collide geom with flex elements
|
||||||
int ncon_before = d->ncon;
|
int ncon_before = d->ncon;
|
||||||
int elemnum = m->flex_elemnum[f];
|
int elemnum = m->flex_elemnum[f];
|
||||||
@@ -754,6 +762,15 @@ void mj_collideTree(const mjModel* m, mjData* d, int bf1, int bf2,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for body:flex, if body has SDFs, call mj_collideSdfFlex directly
|
||||||
|
if (isbody1 && !isbody2) {
|
||||||
|
for (int i=m->body_geomadr[bf1]; i < m->body_geomadr[bf1]+m->body_geomnum[bf1]; i++) {
|
||||||
|
if (m->geom_type[i] == mjGEOM_SDF) {
|
||||||
|
mj_collideSdfFlex(m, d, i, f2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// collide trees
|
// collide trees
|
||||||
while (nstack) {
|
while (nstack) {
|
||||||
// pop from stack
|
// pop from stack
|
||||||
@@ -820,8 +837,9 @@ void mj_collideTree(const mjModel* m, mjData* d, int bf1, int bf2,
|
|||||||
d->geom_xpos + 3*nodeid1, d->geom_xmat + 9*nodeid1,
|
d->geom_xpos + 3*nodeid1, d->geom_xmat + 9*nodeid1,
|
||||||
NULL, NULL,
|
NULL, NULL,
|
||||||
margin, NULL, NULL, &initialize)) {
|
margin, NULL, NULL, &initialize)) {
|
||||||
// collide unless geom is plane (plane:flex handled separately)
|
// collide unless geom is plane or SDF (handled separately)
|
||||||
if (m->geom_type[nodeid1] != mjGEOM_PLANE) {
|
if (m->geom_type[nodeid1] != mjGEOM_PLANE &&
|
||||||
|
m->geom_type[nodeid1] != mjGEOM_SDF) {
|
||||||
mj_collideGeomElem(m, d, nodeid1, f2, nodeid2);
|
mj_collideGeomElem(m, d, nodeid1, f2, nodeid2);
|
||||||
}
|
}
|
||||||
if (mark_active) {
|
if (mark_active) {
|
||||||
@@ -1800,6 +1818,54 @@ void mj_collidePlaneFlex(const mjModel* m, mjData* d, int g, int f) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// test an SDF geom and a flex for collision, add to contact list
|
||||||
|
void mj_collideSdfFlex(const mjModel* m, mjData* d, int g, int f) {
|
||||||
|
// only support dim==2 (triangular elements)
|
||||||
|
if (m->flex_dim[f] != 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare contact parameters (same for all contacts)
|
||||||
|
mjtNum margin = mj_assignMargin(m, m->geom_margin[g] + m->flex_margin[f]);
|
||||||
|
int condim;
|
||||||
|
mjtNum gap, solref[mjNREF], solimp[mjNIMP], friction[5];
|
||||||
|
mjtNum solreffriction[mjNREF] = {0};
|
||||||
|
mj_contactParam(m, &condim, &gap, solref, solimp, friction, g, -1, -1, f);
|
||||||
|
|
||||||
|
// allocate temporary contact array on stack (zero-initialized)
|
||||||
|
mj_markStack(d);
|
||||||
|
mjContact* con = mjSTACKALLOC(d, mjMAXCONPAIR, mjContact);
|
||||||
|
memset(con, 0, mjMAXCONPAIR * sizeof(mjContact));
|
||||||
|
|
||||||
|
// call batched flex-SDF collision
|
||||||
|
int num = mjc_FlexSDF(m, d, con, g, f, margin);
|
||||||
|
|
||||||
|
// add contacts to mjData
|
||||||
|
for (int i = 0; i < num; i++) {
|
||||||
|
// set contact ids
|
||||||
|
con[i].geom[0] = g;
|
||||||
|
con[i].geom[1] = -1;
|
||||||
|
con[i].flex[0] = -1;
|
||||||
|
con[i].flex[1] = f;
|
||||||
|
con[i].elem[0] = -1;
|
||||||
|
// con[i].elem[1] already set by mjc_FlexSDF with actual element index
|
||||||
|
con[i].vert[0] = -1;
|
||||||
|
con[i].vert[1] = -1;
|
||||||
|
|
||||||
|
// set remaining contact parameters
|
||||||
|
mj_setContact(m, con + i, condim, margin-gap, solref, solreffriction, solimp, friction);
|
||||||
|
|
||||||
|
// add to mjData, abort if too many contacts
|
||||||
|
if (mj_addContact(m, d, con + i)) {
|
||||||
|
mj_freeStack(d);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mj_freeStack(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// test single triangle plane : vertex
|
// test single triangle plane : vertex
|
||||||
static int planeVertex(mjContact* con, const mjtNum* pos, mjtNum rad,
|
static int planeVertex(mjContact* con, const mjtNum* pos, mjtNum rad,
|
||||||
int t0, int t1, int t2, int v) {
|
int t0, int t1, int t2, int v) {
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ void mj_collideGeoms(const mjModel* m, mjData* d, int g1, int g2);
|
|||||||
// test a plane geom and a flex for collision, add to contact list
|
// test a plane geom and a flex for collision, add to contact list
|
||||||
void mj_collidePlaneFlex(const mjModel* m, mjData* d, int g, int f);
|
void mj_collidePlaneFlex(const mjModel* m, mjData* d, int g, int f);
|
||||||
|
|
||||||
|
// test an SDF geom and a flex for collision, add to contact list
|
||||||
|
void mj_collideSdfFlex(const mjModel* m, mjData* d, int g, int f);
|
||||||
|
|
||||||
// test for internal flex collisions, add to contact list
|
// test for internal flex collisions, add to contact list
|
||||||
void mj_collideFlexInternal(const mjModel* m, mjData* d, int f);
|
void mj_collideFlexInternal(const mjModel* m, mjData* d, int f);
|
||||||
|
|
||||||
|
|||||||
+391
-126
@@ -22,9 +22,7 @@
|
|||||||
#include <mujoco/mjsan.h> // IWYU pragma: keep
|
#include <mujoco/mjsan.h> // IWYU pragma: keep
|
||||||
#include <mujoco/mjtnum.h>
|
#include <mujoco/mjtnum.h>
|
||||||
#include "engine/engine_collision_primitive.h"
|
#include "engine/engine_collision_primitive.h"
|
||||||
#include "engine/engine_memory.h"
|
|
||||||
#include "engine/engine_plugin.h"
|
#include "engine/engine_plugin.h"
|
||||||
#include "engine/engine_sort.h"
|
|
||||||
#include "engine/engine_util_blas.h"
|
#include "engine/engine_util_blas.h"
|
||||||
#include "engine/engine_util_errmem.h"
|
#include "engine/engine_util_errmem.h"
|
||||||
#include "engine/engine_util_misc.h"
|
#include "engine/engine_util_misc.h"
|
||||||
@@ -526,9 +524,12 @@ static int isknown(const mjtNum* points, const mjtNum x[3], int cnt) {
|
|||||||
|
|
||||||
|
|
||||||
// adds candidate point to result
|
// adds candidate point to result
|
||||||
|
// flipNormal: 0 = normal points INTO SDF (for mesh-SDF where SDF is g2)
|
||||||
|
// 1 = normal points OUT of SDF (for flex-SDF where SDF is g1)
|
||||||
static int addContact(mjtNum* points, mjContact* con, const mjtNum x[3],
|
static int addContact(mjtNum* points, mjContact* con, const mjtNum x[3],
|
||||||
const mjtNum pos2[3], const mjtNum quat2[4], mjtNum dist,
|
const mjtNum pos2[3], const mjtNum quat2[4], mjtNum dist,
|
||||||
int cnt, const mjModel* m, const mjSDF* s, mjData* d) {
|
int cnt, const mjModel* m, const mjSDF* s, mjData* d,
|
||||||
|
int flipNormal) {
|
||||||
// check if there is a collision
|
// check if there is a collision
|
||||||
if (dist > 0 || isknown(points, x, cnt)) {
|
if (dist > 0 || isknown(points, x, cnt)) {
|
||||||
return cnt;
|
return cnt;
|
||||||
@@ -539,7 +540,17 @@ static int addContact(mjtNum* points, mjContact* con, const mjtNum x[3],
|
|||||||
// compute normal in local coordinates
|
// compute normal in local coordinates
|
||||||
mjtNum norm[3], vec[3];
|
mjtNum norm[3], vec[3];
|
||||||
mjc_gradient(m, d, s, norm, x);
|
mjc_gradient(m, d, s, norm, x);
|
||||||
mju_scl3(norm, norm, -1);
|
|
||||||
|
// validate normal - skip if gradient is degenerate (zero or near-zero)
|
||||||
|
mjtNum norm_len = mju_normalize3(norm);
|
||||||
|
if (norm_len < mjMINVAL) {
|
||||||
|
return cnt; // degenerate gradient, skip this contact
|
||||||
|
}
|
||||||
|
|
||||||
|
// normal direction: flipNormal=0 -> INTO SDF, flipNormal=1 -> OUT of SDF
|
||||||
|
if (!flipNormal) {
|
||||||
|
mju_scl3(norm, norm, -1);
|
||||||
|
}
|
||||||
|
|
||||||
// construct contact
|
// construct contact
|
||||||
con[cnt].dist = dist;
|
con[cnt].dist = dist;
|
||||||
@@ -720,6 +731,106 @@ static int boxIntersect(const mjtNum bvh[6], const mjtNum offset[3],
|
|||||||
return mjc_distance(m, d, s, candidate) < r;
|
return mjc_distance(m, d, s, candidate) < r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Farthest point sampling (FPS) to select spatially diverse contacts
|
||||||
|
// Returns indices of selected candidates in nselected, fills selected_indices array
|
||||||
|
static int selectFPS(const mjtNum* candidate, const mjtNum* dist, int ncandidate,
|
||||||
|
int* selected_indices, int max_select) {
|
||||||
|
if (ncandidate <= 0) return 0;
|
||||||
|
|
||||||
|
mjtByte selected[mjMAXCONPAIR] = {0};
|
||||||
|
mjtNum min_dist2[mjMAXCONPAIR];
|
||||||
|
for (int i = 0; i < ncandidate; i++) {
|
||||||
|
min_dist2[i] = mjMAXVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start with deepest penetrating contact
|
||||||
|
int best = 0;
|
||||||
|
mjtNum bestval = -dist[0];
|
||||||
|
for (int i = 1; i < ncandidate; i++) {
|
||||||
|
if (-dist[i] > bestval) {
|
||||||
|
bestval = -dist[i];
|
||||||
|
best = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iteratively select contacts using FPS
|
||||||
|
int nselected = 0;
|
||||||
|
while (nselected < max_select && nselected < mjMAXCONPAIR && best >= 0) {
|
||||||
|
selected[best] = 1;
|
||||||
|
selected_indices[nselected] = best;
|
||||||
|
nselected++;
|
||||||
|
|
||||||
|
const mjtNum* bestpos = candidate + 3*best;
|
||||||
|
|
||||||
|
// find next farthest point
|
||||||
|
int nextbest = -1;
|
||||||
|
mjtNum nextbestdist = -1;
|
||||||
|
for (int i = 0; i < ncandidate; i++) {
|
||||||
|
if (selected[i]) continue;
|
||||||
|
|
||||||
|
mjtNum dx = candidate[3*i+0] - bestpos[0];
|
||||||
|
mjtNum dy = candidate[3*i+1] - bestpos[1];
|
||||||
|
mjtNum dz = candidate[3*i+2] - bestpos[2];
|
||||||
|
mjtNum d2 = dx*dx + dy*dy + dz*dz;
|
||||||
|
if (d2 < min_dist2[i]) {
|
||||||
|
min_dist2[i] = d2;
|
||||||
|
}
|
||||||
|
if (min_dist2[i] > nextbestdist) {
|
||||||
|
nextbestdist = min_dist2[i];
|
||||||
|
nextbest = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best = nextbest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nselected;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process triangle corners against SDF using Halton sampling + Frank-Wolfe.
|
||||||
|
// Corners are assumed to already be in SDF local coordinates.
|
||||||
|
// Adds penetrating candidates to candidate/dist arrays.
|
||||||
|
static void processSdfCorners(const mjtNum corners[9], const mjModel* m, mjData* d,
|
||||||
|
const mjSDF* sdf, int nstartpts,
|
||||||
|
mjtNum* candidate, mjtNum* dist, int* ncandidate) {
|
||||||
|
mjtNum x[3], depth;
|
||||||
|
|
||||||
|
// stricter culling using triangle circumsphere
|
||||||
|
if (!triangleIntersect(corners, m, sdf, d)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sample multiple starting points using Halton sequence
|
||||||
|
for (int sp = 0; sp < nstartpts; sp++) {
|
||||||
|
if (*ncandidate >= mjMAXCONPAIR) break;
|
||||||
|
|
||||||
|
// barycentric coordinates from Halton sequence
|
||||||
|
mjtNum u = mju_Halton(sp + 1, 2);
|
||||||
|
mjtNum v = mju_Halton(sp + 1, 3);
|
||||||
|
if (u + v > 1) {
|
||||||
|
u = 1 - u;
|
||||||
|
v = 1 - v;
|
||||||
|
}
|
||||||
|
mjtNum b0 = 1 - u - v;
|
||||||
|
mjtNum b1 = u;
|
||||||
|
mjtNum b2 = v;
|
||||||
|
|
||||||
|
// starting point
|
||||||
|
x[0] = b0*corners[0] + b1*corners[3] + b2*corners[6];
|
||||||
|
x[1] = b0*corners[1] + b1*corners[4] + b2*corners[7];
|
||||||
|
x[2] = b0*corners[2] + b1*corners[5] + b2*corners[8];
|
||||||
|
|
||||||
|
depth = stepFrankWolfe(x, corners, 3, m, sdf, d);
|
||||||
|
|
||||||
|
// store candidate if penetration
|
||||||
|
if (depth < 0) {
|
||||||
|
int nc = *ncandidate;
|
||||||
|
mju_copy3(candidate + 3*nc, x);
|
||||||
|
dist[nc] = depth;
|
||||||
|
(*ncandidate)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Context for inline face processing during BVH traversal
|
// Context for inline face processing during BVH traversal
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const mjModel* m;
|
const mjModel* m;
|
||||||
@@ -738,9 +849,8 @@ typedef struct {
|
|||||||
// process a single mesh face inline during BVH traversal
|
// process a single mesh face inline during BVH traversal
|
||||||
static void processOneFace(int faceid, mjtByte* bvh_active, int node,
|
static void processOneFace(int faceid, mjtByte* bvh_active, int node,
|
||||||
MeshSDFContext* ctx) {
|
MeshSDFContext* ctx) {
|
||||||
mjtNum corners[9], x[3], depth;
|
mjtNum corners[9];
|
||||||
const mjModel* m = ctx->m;
|
const mjModel* m = ctx->m;
|
||||||
mjData* d = ctx->d;
|
|
||||||
int face = ctx->faceadr + faceid;
|
int face = ctx->faceadr + faceid;
|
||||||
|
|
||||||
// transform triangle vertices to SDF local coordinates
|
// transform triangle vertices to SDF local coordinates
|
||||||
@@ -754,85 +864,48 @@ static void processOneFace(int faceid, mjtByte* bvh_active, int node,
|
|||||||
mju_addTo3(corners + 3*v, ctx->offset);
|
mju_addTo3(corners + 3*v, ctx->offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// stricter culling using triangle circumsphere
|
// use shared helper for Halton sampling + Frank-Wolfe
|
||||||
if (!triangleIntersect(corners, m, ctx->sdf, d)) {
|
processSdfCorners(corners, m, ctx->d, ctx->sdf, ctx->nstartpts,
|
||||||
return;
|
ctx->candidate, ctx->dist, ctx->ncandidate);
|
||||||
}
|
|
||||||
|
|
||||||
if (bvh_active) bvh_active[node] = 1;
|
if (bvh_active && *(ctx->ncandidate) > 0) bvh_active[node] = 1;
|
||||||
|
|
||||||
// sample multiple starting points using Halton sequence
|
|
||||||
for (int sp = 0; sp < ctx->nstartpts; sp++) {
|
|
||||||
if (*(ctx->ncandidate) >= mjMAXCONPAIR) break;
|
|
||||||
|
|
||||||
// barycentric coordinates from Halton sequence
|
|
||||||
mjtNum u = mju_Halton(sp + 1, 2);
|
|
||||||
mjtNum v = mju_Halton(sp + 1, 3);
|
|
||||||
if (u + v > 1) {
|
|
||||||
u = 1 - u;
|
|
||||||
v = 1 - v;
|
|
||||||
}
|
|
||||||
mjtNum b0 = 1 - u - v;
|
|
||||||
mjtNum b1 = u;
|
|
||||||
mjtNum b2 = v;
|
|
||||||
|
|
||||||
// starting point
|
|
||||||
x[0] = b0*corners[0] + b1*corners[3] + b2*corners[6];
|
|
||||||
x[1] = b0*corners[1] + b1*corners[4] + b2*corners[7];
|
|
||||||
x[2] = b0*corners[2] + b1*corners[5] + b2*corners[8];
|
|
||||||
|
|
||||||
depth = stepFrankWolfe(x, corners, 3, m, ctx->sdf, d);
|
|
||||||
|
|
||||||
// store candidate if penetration
|
|
||||||
if (depth < 0) {
|
|
||||||
int nc = *(ctx->ncandidate);
|
|
||||||
mju_copy3(ctx->candidate + 3*nc, x);
|
|
||||||
ctx->dist[nc] = depth;
|
|
||||||
(*(ctx->ncandidate))++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tree vs sdf binary search with inline face processing
|
// Callback type for leaf node processing during BVH traversal
|
||||||
static void collideBVHInline(const mjModel* m, mjData* d, int g,
|
// Returns 1 if node should be marked active (for visualization), 0 otherwise
|
||||||
MeshSDFContext* ctx) {
|
typedef int (*BVHLeafCallback)(int leaf_id, int node, void* ctx);
|
||||||
const int bvhadr = m->mesh_bvhadr[m->geom_dataid[g]];
|
|
||||||
|
|
||||||
// mesh has no BVH tree
|
// Generic BVH traversal with callback for leaf processing
|
||||||
if (bvhadr < 0) {
|
// bvh: pointer to BVH AABBs (6 floats per node: center xyz, half-size xyz)
|
||||||
return;
|
// nodeid: leaf ID per node (-1 for intermediate nodes)
|
||||||
}
|
// child: child indices (2 per node)
|
||||||
|
// bvh_active: visualization array or NULL
|
||||||
const int* faceid = m->bvh_nodeid + bvhadr;
|
// offset/rotation: transform for boxIntersect
|
||||||
const mjtNum* bvh = m->bvh_aabb + 6*bvhadr;
|
// m, d, sdf: for boxIntersect
|
||||||
const int* child = m->bvh_child + 2*bvhadr;
|
// callback: function called for each leaf node
|
||||||
mjtByte* bvh_active = m->vis.global.bvactive ? d->bvh_active + bvhadr : NULL;
|
// ctx: user context passed to callback
|
||||||
|
static void traverseBVH(const mjtNum* bvh, const int* nodeid, const int* child,
|
||||||
mj_markStack(d);
|
mjtByte* bvh_active, const mjtNum* offset, const mjtNum* rotation,
|
||||||
int max_stack = m->mesh_bvhnum[m->geom_dataid[g]];
|
const mjModel* m, mjData* d, const mjSDF* sdf,
|
||||||
struct CollideTreeArgs_ {
|
BVHLeafCallback callback, void* ctx) {
|
||||||
int node;
|
int stack[64];
|
||||||
};
|
|
||||||
typedef struct CollideTreeArgs_ CollideTreeArgs;
|
|
||||||
CollideTreeArgs* stack = mjSTACKALLOC(d, max_stack, CollideTreeArgs);
|
|
||||||
int nstack = 0;
|
int nstack = 0;
|
||||||
stack[nstack].node = 0;
|
stack[nstack++] = 0;
|
||||||
nstack++;
|
|
||||||
|
|
||||||
while (nstack) {
|
while (nstack) {
|
||||||
nstack--;
|
int node = stack[--nstack];
|
||||||
int node = stack[nstack].node;
|
|
||||||
|
|
||||||
// leaf node: process face inline
|
// leaf node: call callback
|
||||||
if (faceid[node] != -1) {
|
if (nodeid[node] != -1) {
|
||||||
if (boxIntersect(bvh + 6*node, ctx->offset, ctx->rotation, m, ctx->sdf, d)) {
|
if (boxIntersect(bvh + 6*node, offset, rotation, m, sdf, d)) {
|
||||||
processOneFace(faceid[node], bvh_active, node, ctx);
|
int active = callback(nodeid[node], node, ctx);
|
||||||
|
if (bvh_active && active) bvh_active[node] = 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// intermediate node: check bounding box
|
// intermediate node: check bounding box
|
||||||
if (!boxIntersect(bvh + 6*node, ctx->offset, ctx->rotation, m, ctx->sdf, d)) {
|
if (!boxIntersect(bvh + 6*node, offset, rotation, m, sdf, d)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,16 +914,24 @@ static void collideBVHInline(const mjModel* m, mjData* d, int g,
|
|||||||
// push children
|
// push children
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
if (child[2*node+i] != -1) {
|
if (child[2*node+i] != -1) {
|
||||||
if (nstack >= max_stack) mjERROR("BVH stack depth exceeded.");
|
if (nstack >= 64) {
|
||||||
stack[nstack].node = child[2*node+i];
|
mjERROR("BVH stack depth exceeded.");
|
||||||
nstack++;
|
}
|
||||||
|
stack[nstack++] = child[2*node+i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mj_freeStack(d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mesh face callback for traverseBVH
|
||||||
|
static int meshFaceCallback(int face_id, int node, void* ctx) {
|
||||||
|
MeshSDFContext* mctx = (MeshSDFContext*)ctx;
|
||||||
|
int ncandidate_before = *(mctx->ncandidate);
|
||||||
|
processOneFace(face_id, NULL, node, mctx);
|
||||||
|
return *(mctx->ncandidate) > ncandidate_before; // mark active if candidates added
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//------------------------------ collision functions -----------------------------------------------
|
//------------------------------ collision functions -----------------------------------------------
|
||||||
|
|
||||||
// collision between a height field and a signed distance field
|
// collision between a height field and a signed distance field
|
||||||
@@ -901,65 +982,36 @@ int mjc_MeshSDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g
|
|||||||
ctx.dist = dist;
|
ctx.dist = dist;
|
||||||
ctx.ncandidate = &ncandidate;
|
ctx.ncandidate = &ncandidate;
|
||||||
|
|
||||||
// binary tree search with inline face processing
|
// BVH traversal for mesh faces
|
||||||
collideBVHInline(m, (mjData*)d, g1, &ctx);
|
const int bvhadr = m->mesh_bvhadr[m->geom_dataid[g1]];
|
||||||
|
if (bvhadr >= 0) {
|
||||||
|
const int* nodeid = m->bvh_nodeid + bvhadr;
|
||||||
|
const mjtNum* bvh = m->bvh_aabb + 6*bvhadr;
|
||||||
|
const int* child = m->bvh_child + 2*bvhadr;
|
||||||
|
mjtByte* bvh_active = m->vis.global.bvactive ? d->bvh_active + bvhadr : NULL;
|
||||||
|
|
||||||
|
traverseBVH(bvh, nodeid, child, bvh_active, ctx.offset, ctx.rotation,
|
||||||
|
m, (mjData*)d, ctx.sdf, meshFaceCallback, &ctx);
|
||||||
|
}
|
||||||
|
|
||||||
// if few candidates, add them all directly
|
// if few candidates, add them all directly
|
||||||
if (ncandidate <= mjMAXCONPAIR) {
|
if (ncandidate <= mjMAXCONPAIR) {
|
||||||
for (int i = 0; i < ncandidate; i++) {
|
for (int i = 0; i < ncandidate; i++) {
|
||||||
cnt = addContact(points, con, candidate + 3*i, pos2, sdf_quat,
|
cnt = addContact(points, con, candidate + 3*i, pos2, sdf_quat,
|
||||||
dist[i], cnt, m, &sdf, (mjData*)d);
|
dist[i], cnt, m, &sdf, (mjData*)d, 0);
|
||||||
}
|
}
|
||||||
return cnt;
|
return cnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// farthest point sampling (FPS) for spatial coverage
|
// farthest point sampling (FPS) for spatial coverage
|
||||||
mjtByte selected[mjMAXCONPAIR] = {0};
|
int selected_indices[mjMAXCONPAIR];
|
||||||
mjtNum min_dist2[mjMAXCONPAIR];
|
int nselected = selectFPS(candidate, dist, ncandidate, selected_indices, mjMAXCONPAIR);
|
||||||
for (int i = 0; i < ncandidate; i++) {
|
|
||||||
min_dist2[i] = mjMAXVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start with deepest penetrating contact
|
// add selected contacts
|
||||||
int best = 0;
|
for (int i = 0; i < nselected; i++) {
|
||||||
mjtNum bestval = -dist[0];
|
int idx = selected_indices[i];
|
||||||
for (int i = 1; i < ncandidate; i++) {
|
cnt = addContact(points, con, candidate + 3*idx, pos2, sdf_quat,
|
||||||
if (-dist[i] > bestval) {
|
dist[idx], cnt, m, &sdf, (mjData*)d, 0);
|
||||||
bestval = -dist[i];
|
|
||||||
best = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// iteratively select contacts using FPS
|
|
||||||
int nselected = 0;
|
|
||||||
while (nselected < mjMAXCONPAIR && best >= 0) {
|
|
||||||
selected[best] = 1;
|
|
||||||
mjtNum* bestpos = candidate + 3*best;
|
|
||||||
|
|
||||||
// add the selected contact
|
|
||||||
cnt = addContact(points, con, bestpos, pos2, sdf_quat,
|
|
||||||
dist[best], cnt, m, &sdf, (mjData*)d);
|
|
||||||
nselected++;
|
|
||||||
|
|
||||||
// find next farthest point
|
|
||||||
int nextbest = -1;
|
|
||||||
mjtNum nextbestdist = -1;
|
|
||||||
for (int i = 0; i < ncandidate; i++) {
|
|
||||||
if (selected[i]) continue;
|
|
||||||
|
|
||||||
mjtNum dx = candidate[3*i+0] - bestpos[0];
|
|
||||||
mjtNum dy = candidate[3*i+1] - bestpos[1];
|
|
||||||
mjtNum dz = candidate[3*i+2] - bestpos[2];
|
|
||||||
mjtNum d2 = dx*dx + dy*dy + dz*dz;
|
|
||||||
if (d2 < min_dist2[i]) {
|
|
||||||
min_dist2[i] = d2;
|
|
||||||
}
|
|
||||||
if (min_dist2[i] > nextbestdist) {
|
|
||||||
nextbestdist = min_dist2[i];
|
|
||||||
nextbest = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
best = nextbest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cnt;
|
return cnt;
|
||||||
@@ -1092,7 +1144,7 @@ int mjc_SDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, m
|
|||||||
|
|
||||||
// contact point and normal - we use the midsurface where SDF1=SDF2 as zero level set
|
// contact point and normal - we use the midsurface where SDF1=SDF2 as zero level set
|
||||||
sdf.type = mjSDFTYPE_MIDSURFACE;
|
sdf.type = mjSDFTYPE_MIDSURFACE;
|
||||||
cnt = addContact(contacts, con, x, pos2, quat2, dist, cnt, m, &sdf, (mjData*)d);
|
cnt = addContact(contacts, con, x, pos2, quat2, dist, cnt, m, &sdf, (mjData*)d, 0);
|
||||||
|
|
||||||
// SHOULD NOT OCCUR
|
// SHOULD NOT OCCUR
|
||||||
if (cnt > mjMAXCONPAIR) {
|
if (cnt > mjMAXCONPAIR) {
|
||||||
@@ -1102,3 +1154,216 @@ int mjc_SDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, m
|
|||||||
|
|
||||||
return cnt;
|
return cnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context for flex element processing during BVH traversal
|
||||||
|
typedef struct {
|
||||||
|
const mjModel* m;
|
||||||
|
mjData* d;
|
||||||
|
const mjSDF* sdf;
|
||||||
|
const mjtNum* offset;
|
||||||
|
const mjtNum* rotation;
|
||||||
|
const mjtNum* vertxpos;
|
||||||
|
int f;
|
||||||
|
int dim;
|
||||||
|
int nstartpts;
|
||||||
|
mjtNum* candidate;
|
||||||
|
mjtNum* dist;
|
||||||
|
int* elem_id;
|
||||||
|
int* ncandidate;
|
||||||
|
} FlexSDFContext;
|
||||||
|
|
||||||
|
// flex element callback for traverseBVH
|
||||||
|
static int flexElemCallback(int elem_idx, int node, void* ctx) {
|
||||||
|
FlexSDFContext* fctx = (FlexSDFContext*)ctx;
|
||||||
|
const mjModel* m = fctx->m;
|
||||||
|
int dim = fctx->dim;
|
||||||
|
|
||||||
|
// get element vertex indices
|
||||||
|
const int* edata = m->flex_elem + m->flex_elemdataadr[fctx->f] + elem_idx*(dim+1);
|
||||||
|
|
||||||
|
// get triangle corners in world coordinates
|
||||||
|
mjtNum world_corners[9];
|
||||||
|
for (int v = 0; v < 3; v++) {
|
||||||
|
mju_copy3(world_corners + 3*v, fctx->vertxpos + 3*edata[v]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform corners to SDF local coordinates
|
||||||
|
mjtNum corners[9];
|
||||||
|
for (int v = 0; v < 3; v++) {
|
||||||
|
mju_mulMatVec3(corners + 3*v, fctx->rotation, world_corners + 3*v);
|
||||||
|
mju_addTo3(corners + 3*v, fctx->offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// process triangle (per-element candidates)
|
||||||
|
mjtNum elem_candidate[3*mjMAXCONPAIR];
|
||||||
|
mjtNum elem_dist[mjMAXCONPAIR];
|
||||||
|
int elem_ncandidate = 0;
|
||||||
|
processSdfCorners(corners, m, fctx->d, fctx->sdf, fctx->nstartpts,
|
||||||
|
elem_candidate, elem_dist, &elem_ncandidate);
|
||||||
|
|
||||||
|
// keep only deepest penetrating candidate from this element
|
||||||
|
if (elem_ncandidate > 0) {
|
||||||
|
int best_i = 0;
|
||||||
|
for (int i = 1; i < elem_ncandidate; i++) {
|
||||||
|
if (elem_dist[i] < elem_dist[best_i]) {
|
||||||
|
best_i = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add to global candidate list
|
||||||
|
int nc = *(fctx->ncandidate);
|
||||||
|
if (nc < mjMAXCONPAIR) {
|
||||||
|
mju_copy3(fctx->candidate + 3*nc, elem_candidate + 3*best_i);
|
||||||
|
fctx->dist[nc] = elem_dist[best_i];
|
||||||
|
fctx->elem_id[nc] = elem_idx;
|
||||||
|
(*(fctx->ncandidate))++;
|
||||||
|
return 1; // mark active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int mjc_FlexSDF(const mjModel* m, mjData* d, mjContact* con,
|
||||||
|
int g, int f, mjtNum margin) {
|
||||||
|
// g = SDF geom, f = flex
|
||||||
|
int dim = m->flex_dim[f];
|
||||||
|
|
||||||
|
// only support dim==2 (triangular elements)
|
||||||
|
if (dim != 2) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjtNum points[3*mjMAXCONPAIR], dist[mjMAXCONPAIR], candidate[3*mjMAXCONPAIR];
|
||||||
|
int elem_id[mjMAXCONPAIR]; // track which element generated each candidate
|
||||||
|
int cnt = 0, ncandidate = 0;
|
||||||
|
|
||||||
|
// get SDF info (once for entire flex)
|
||||||
|
int instance = m->geom_plugin[g];
|
||||||
|
const mjpPlugin* sdf_ptr = instance == -1 ? NULL : mjc_getSDF(m, g);
|
||||||
|
instance = instance == -1 ? m->geom_dataid[g] : instance;
|
||||||
|
mjtGeom geomtype = mjGEOM_SDF;
|
||||||
|
|
||||||
|
mjSDF sdf;
|
||||||
|
sdf.id = &instance;
|
||||||
|
sdf.type = mjSDFTYPE_SINGLE;
|
||||||
|
sdf.plugin = &sdf_ptr;
|
||||||
|
sdf.geomtype = &geomtype;
|
||||||
|
|
||||||
|
// get SDF pose (once for entire flex)
|
||||||
|
mjtNum sdf_quat[4];
|
||||||
|
const mjtNum* sdf_pos = d->geom_xpos + 3*g;
|
||||||
|
const mjtNum* sdf_mat = d->geom_xmat + 9*g;
|
||||||
|
mju_mat2Quat(sdf_quat, sdf_mat);
|
||||||
|
|
||||||
|
// compute world-to-SDF transform (once for entire flex)
|
||||||
|
mjtNum rotation[9], offset[3];
|
||||||
|
mjtNum world_origin[3] = {0, 0, 0}, world_quat[4] = {1, 0, 0, 0};
|
||||||
|
mapPose(world_origin, world_quat, sdf_pos, sdf_quat, offset, rotation);
|
||||||
|
|
||||||
|
// get flex element and vertex data
|
||||||
|
const mjtNum* vertxpos = d->flexvert_xpos + 3*m->flex_vertadr[f];
|
||||||
|
int nstartpts = mju_max(1, m->opt.sdf_initpoints);
|
||||||
|
int elemnum = m->flex_elemnum[f];
|
||||||
|
|
||||||
|
// flex has no BVH tree: iterate all elements directly
|
||||||
|
const int bvhadr = m->flex_bvhadr[f];
|
||||||
|
if (bvhadr < 0) {
|
||||||
|
mjtNum elem_candidate[3*mjMAXCONPAIR];
|
||||||
|
mjtNum elem_dist[mjMAXCONPAIR];
|
||||||
|
for (int e = 0; e < elemnum; e++) {
|
||||||
|
const int* edata = m->flex_elem + m->flex_elemdataadr[f] + e*(dim+1);
|
||||||
|
|
||||||
|
// get triangle corners in world coordinates
|
||||||
|
mjtNum world_corners[9];
|
||||||
|
for (int v = 0; v < 3; v++) {
|
||||||
|
mju_copy3(world_corners + 3*v, vertxpos + 3*edata[v]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// transform corners to SDF local coordinates
|
||||||
|
mjtNum corners[9];
|
||||||
|
for (int v = 0; v < 3; v++) {
|
||||||
|
mju_mulMatVec3(corners + 3*v, rotation, world_corners + 3*v);
|
||||||
|
mju_addTo3(corners + 3*v, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// process triangle using shared helper (per-element candidates)
|
||||||
|
int elem_ncandidate = 0;
|
||||||
|
processSdfCorners(corners, m, d, &sdf, nstartpts, elem_candidate, elem_dist, &elem_ncandidate);
|
||||||
|
|
||||||
|
// add contacts for this element immediately (per-element points tracking)
|
||||||
|
mjtNum elem_points[3*mjMAXCONPAIR];
|
||||||
|
int elem_cnt = 0;
|
||||||
|
for (int i = 0; i < elem_ncandidate && cnt < mjMAXCONPAIR; i++) {
|
||||||
|
int old_elem_cnt = elem_cnt;
|
||||||
|
elem_cnt = addContact(elem_points, con + cnt, elem_candidate + 3*i, sdf_pos, sdf_quat,
|
||||||
|
elem_dist[i], elem_cnt, m, &sdf, d, 1);
|
||||||
|
// set element ID for successfully added contact
|
||||||
|
if (elem_cnt > old_elem_cnt) {
|
||||||
|
con[cnt].elem[1] = e;
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// have BVH: use tree traversal for efficiency
|
||||||
|
const int* elemid = m->bvh_nodeid + bvhadr;
|
||||||
|
// flexes use dynamic AABBs (vertices move during simulation)
|
||||||
|
const int nbvhstatic = m->nbvhstatic;
|
||||||
|
const mjtNum* bvh = d->bvh_aabb_dyn + 6*(bvhadr - nbvhstatic);
|
||||||
|
const int* child = m->bvh_child + 2*bvhadr;
|
||||||
|
mjtByte* bvh_active = m->vis.global.bvactive ? d->bvh_active + bvhadr : NULL;
|
||||||
|
|
||||||
|
// set up context for flex element processing
|
||||||
|
FlexSDFContext fctx;
|
||||||
|
fctx.m = m;
|
||||||
|
fctx.d = d;
|
||||||
|
fctx.sdf = &sdf;
|
||||||
|
fctx.offset = offset;
|
||||||
|
fctx.rotation = rotation;
|
||||||
|
fctx.vertxpos = vertxpos;
|
||||||
|
fctx.f = f;
|
||||||
|
fctx.dim = dim;
|
||||||
|
fctx.nstartpts = nstartpts;
|
||||||
|
fctx.candidate = candidate;
|
||||||
|
fctx.dist = dist;
|
||||||
|
fctx.elem_id = elem_id;
|
||||||
|
fctx.ncandidate = &ncandidate;
|
||||||
|
|
||||||
|
traverseBVH(bvh, elemid, child, bvh_active, offset, rotation,
|
||||||
|
m, d, &sdf, flexElemCallback, &fctx);
|
||||||
|
|
||||||
|
|
||||||
|
// if few candidates, add them all directly
|
||||||
|
if (ncandidate <= mjMAXCONPAIR) {
|
||||||
|
for (int i = 0; i < ncandidate; i++) {
|
||||||
|
int old_cnt = cnt;
|
||||||
|
cnt = addContact(points, con, candidate + 3*i, sdf_pos, sdf_quat,
|
||||||
|
dist[i], cnt, m, &sdf, d, 1);
|
||||||
|
// set element ID for successfully added contact
|
||||||
|
if (cnt > old_cnt) {
|
||||||
|
con[old_cnt].elem[1] = elem_id[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// farthest point sampling (FPS) for spatial coverage
|
||||||
|
int selected_indices[mjMAXCONPAIR];
|
||||||
|
int nselected = selectFPS(candidate, dist, ncandidate, selected_indices, mjMAXCONPAIR);
|
||||||
|
|
||||||
|
// add selected contacts
|
||||||
|
for (int i = 0; i < nselected; i++) {
|
||||||
|
int idx = selected_indices[i];
|
||||||
|
int old_cnt = cnt;
|
||||||
|
cnt = addContact(points, con, candidate + 3*idx, sdf_pos, sdf_quat,
|
||||||
|
dist[idx], cnt, m, &sdf, d, 1);
|
||||||
|
// set element ID for successfully added contact
|
||||||
|
if (cnt > old_cnt) {
|
||||||
|
con[old_cnt].elem[1] = elem_id[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ int mjc_MeshSDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g
|
|||||||
// collision between two signed distance fields
|
// collision between two signed distance fields
|
||||||
int mjc_SDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, mjtNum margin);
|
int mjc_SDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, mjtNum margin);
|
||||||
|
|
||||||
|
// collision between entire flex and SDF geom (batched processing)
|
||||||
|
int mjc_FlexSDF(const mjModel* m, mjData* d, mjContact* con,
|
||||||
|
int g, int f, mjtNum margin);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -81,5 +81,71 @@ TEST_F(SdfTest, SdfPrimitive) {
|
|||||||
mj_deleteModel(model);
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr char kFlexSdfModel[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<extension>
|
||||||
|
<plugin plugin="mujoco.sdf.torus">
|
||||||
|
<instance name="torus">
|
||||||
|
<config key="radius1" value="0.35"/>
|
||||||
|
<config key="radius2" value="0.15"/>
|
||||||
|
</instance>
|
||||||
|
</plugin>
|
||||||
|
</extension>
|
||||||
|
|
||||||
|
<asset>
|
||||||
|
<mesh name="torus">
|
||||||
|
<plugin instance="torus"/>
|
||||||
|
</mesh>
|
||||||
|
</asset>
|
||||||
|
|
||||||
|
<worldbody>
|
||||||
|
<body pos="0 0 -0.3">
|
||||||
|
<geom type="sdf" mesh="torus">
|
||||||
|
<plugin instance="torus"/>
|
||||||
|
</geom>
|
||||||
|
</body>
|
||||||
|
<body name="flex">
|
||||||
|
<flexcomp name="test" type="grid" count="3 3 1" spacing=".2 .2 .2" dim="2">
|
||||||
|
<elasticity young="1e4"/>
|
||||||
|
<contact selfcollide="none" internal="false"/>
|
||||||
|
</flexcomp>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
|
||||||
|
TEST_F(SdfTest, FlexSdfCollision) {
|
||||||
|
char error[1024];
|
||||||
|
mjModel* model = LoadModelFromString(kFlexSdfModel, error, sizeof(error));
|
||||||
|
ASSERT_THAT(model, NotNull()) << error;
|
||||||
|
mjData* data = mj_makeData(model);
|
||||||
|
ASSERT_THAT(data, NotNull());
|
||||||
|
|
||||||
|
// check there is at least one flex
|
||||||
|
ASSERT_GT(model->nflex, 0);
|
||||||
|
|
||||||
|
// simulate for a few steps to let flex fall onto torus
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
mj_step(model, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// should have contacts between flex and SDF
|
||||||
|
EXPECT_GT(data->ncon, 0) << "Expected flex-SDF contacts";
|
||||||
|
|
||||||
|
// check that contacts involve the flex
|
||||||
|
bool has_flex_contact = false;
|
||||||
|
for (int i = 0; i < data->ncon; i++) {
|
||||||
|
if (data->contact[i].flex[0] >= 0 || data->contact[i].flex[1] >= 0) {
|
||||||
|
has_flex_contact = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(has_flex_contact) << "Expected at least one flex contact";
|
||||||
|
|
||||||
|
mj_deleteData(data);
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace mujoco
|
} // namespace mujoco
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user