Move tactile sensor to the engine.

PiperOrigin-RevId: 788863133
Change-Id: I3172ebb7641fa8146469da25cba7db794760e7b1
This commit is contained in:
Alessio Quaglino
2025-07-30 05:26:39 -07:00
committed by Copybara-Service
parent 89f4789085
commit 51babec9c5
28 changed files with 418 additions and 687 deletions
+180
View File
@@ -21,6 +21,7 @@
#include <mujoco/mjplugin.h>
#include <mujoco/mjsan.h> // IWYU pragma: keep
#include "engine/engine_callback.h"
#include "engine/engine_collision_sdf.h"
#include "engine/engine_core_smooth.h"
#include "engine/engine_crossplatform.h"
#include "engine/engine_io.h"
@@ -1082,6 +1083,185 @@ void mj_sensorAcc(const mjModel* m, mjData* d) {
}
break;
case mjSENS_TACTILE: // tactile
{
mj_markStack(d);
// get parent weld id
int mesh_id = m->sensor_objid[i];
int geom_id = m->sensor_refid[i];
int parent_body = m->geom_bodyid[geom_id];
int parent_weld = m->body_weldid[parent_body];
int nchannel = m->sensor_dim[i] / m->mesh_vertnum[mesh_id];
// clear sensordata and distance matrix
mjtNum* sensordata = d->sensordata + m->sensor_adr[i];
mju_zero(sensordata, m->sensor_dim[i]);
// count contacts and get contact geom ids
// TODO: use a more efficient C version of unordered_set
int* contact_geom_ids = mj_stackAllocInt(d, d->ncon);
int ncontact = 0;
for (int k = 0; k < d->ncon; k++) {
int body1 = m->body_weldid[m->geom_bodyid[d->contact[k].geom1]];
int body2 = m->body_weldid[m->geom_bodyid[d->contact[k].geom2]];
if (body1 == parent_weld) {
int add = 1;
for (int j = 0; j < ncontact; j++) {
if (contact_geom_ids[j] == d->contact[k].geom2) {
add = 0;
break;
}
}
if (add) {
contact_geom_ids[ncontact] = d->contact[k].geom2;
ncontact++;
}
}
if (body2 == parent_weld) {
int add = 1;
for (int j = 0; j < ncontact; j++) {
if (contact_geom_ids[j] == d->contact[k].geom1) {
add = 0;
break;
}
}
if (add) {
contact_geom_ids[ncontact] = d->contact[k].geom1;
ncontact++;
}
}
}
// no contacts, return
if (ncontact == 0) {
mj_freeStack(d);
break;
}
// all of the quadrature points are contact points
int ncon = m->mesh_vertnum[mesh_id];
// get site frame
mjtNum* geom_pos = d->geom_xpos + 3*geom_id;
mjtNum* geom_mat = d->geom_xmat + 9*geom_id;
// allocate contact forces and positions
mjtNum* forcesT = mj_stackAllocNum(d, ncon*3);
mju_zero(forcesT, ncon*3);
// iterate over colliding geoms
for (int g = 0; g < ncontact; g++) {
int geom = contact_geom_ids[g];
int body = m->geom_bodyid[geom];
// get sdf plugin of the geoms
int sdf_instance[2] = {-1, -1};
mjtGeom geomtype[2] = {mjGEOM_SDF, mjGEOM_SPHERE};
const mjpPlugin* sdf_ptr[2] = {NULL, NULL};
if (m->geom_type[geom] == mjGEOM_SDF) {
sdf_instance[0] = m->geom_plugin[geom];
sdf_ptr[0] = mjc_getSDF(m, geom);
} else if (m->geom_type[geom] == mjGEOM_MESH) {
sdf_instance[0] = m->geom_dataid[geom];
geomtype[0] = (mjtGeom)m->geom_type[geom];
} else {
sdf_instance[0] = geom;
geomtype[0] = (mjtGeom)m->geom_type[geom];
}
// skip mesh geoms not having an octree
if (geomtype[0] == mjGEOM_MESH &&
m->mesh_octadr[m->geom_dataid[geom]] == -1) {
continue;
}
// set SDF parameters
mjSDF geom_sdf;
geom_sdf.id = &sdf_instance[0];
geom_sdf.type = mjSDFTYPE_SINGLE;
geom_sdf.plugin = &sdf_ptr[0];
geom_sdf.geomtype = &geomtype[0];
// get forces in mesh coordinates
int node = 0;
float* mesh_vert = m->mesh_vert + 3*m->mesh_vertadr[mesh_id];
float* mesh_normal = m->mesh_normal + 3*m->mesh_normaladr[mesh_id];
for (int j = 0; j < ncon; j++) {
// position in site frame
mjtNum pos[3] = {mesh_vert[3 * j + 0], mesh_vert[3 * j + 1],
mesh_vert[3 * j + 2]};
// position in global frame
mjtNum xpos[3];
mju_mulMatVec3(xpos, geom_mat, pos);
mju_addTo3(xpos, geom_pos);
// position in other geom frame
mjtNum lpos[3];
mju_sub3(tmp, xpos, d->geom_xpos + 3*geom);
mju_mulMatTVec3(lpos, d->geom_xmat + 9*geom, tmp);
// SDF plugins are in the original mesh frame
if (sdf_ptr[0] != NULL) {
mjtNum mesh_mat[9];
mju_quat2Mat(mesh_mat, m->mesh_quat + 4 * m->geom_dataid[geom]);
mju_mulMatVec3(lpos, mesh_mat, lpos);
mju_addTo3(lpos, m->mesh_pos + 3 * m->geom_dataid[geom]);
}
// compute distance
mjtNum depth = mju_min(mjc_distance(m, d, &geom_sdf, lpos), 0);
if (depth == 0) {
node++;
continue;
}
// get velocity in global frame
mjtNum vel_sensor[6], vel_other[6], vel_rel[3];
mju_transformSpatial(
vel_sensor, d->cvel + 6 * parent_weld, 0, xpos,
d->subtree_com + 3 * m->body_rootid[parent_weld], NULL);
mju_transformSpatial(
vel_other, d->cvel + 6 * body, 0, d->geom_xpos + 3 * geom,
d->subtree_com + 3 * m->body_rootid[body], NULL);
mju_sub3(vel_rel, vel_sensor+3, vel_other+3);
mjtNum normal[3] = {mesh_normal[9 * j + 0], mesh_normal[9 * j + 1],
mesh_normal[9 * j + 2]};
mjtNum tang1[3] = {mesh_normal[9 * j + 3], mesh_normal[9 * j + 4],
mesh_normal[9 * j + 5]};
mjtNum tang2[3] = {mesh_normal[9 * j + 6], mesh_normal[9 * j + 7],
mesh_normal[9 * j + 8]};
// get contact force/torque, rotate into node frame
mju_rotVecQuat(normal, normal, m->mesh_quat + 4 * mesh_id);
mju_rotVecQuat(tang1, tang1, m->mesh_quat + 4 * mesh_id);
mju_rotVecQuat(tang2, tang2, m->mesh_quat + 4 * mesh_id);
mjtNum force[3];
mjtNum kMaxDepth = 0.05;
mjtNum pressure = depth / (kMaxDepth - depth);
mju_scl3(force, normal, pressure);
// one row of mat^T * force
forcesT[0*ncon + node] = mju_dot3(force, normal);
forcesT[1*ncon + node] = mju_abs(mju_dot3(vel_rel, tang1));
forcesT[2*ncon + node] = mju_abs(mju_dot3(vel_rel, tang2));
node++;
}
}
// compute sensor output
for (int c = 0; c < nchannel; c++) {
if (!mju_isZero(forcesT + c*ncon, ncon)) {
mju_addTo(sensordata + c*ncon, forcesT + c*ncon, ncon);
}
}
mj_freeStack(d);
}
break;
case mjSENS_ACCELEROMETER: // accelerometer
// tmp = site acceleration, in site frame
mj_objectAcceleration(m, d, mjOBJ_SITE, objid, tmp, 1);