Change tactile sensor normal component to report penetration depth.

The normal component of the tactile sensor now reports the maximum penetration depth at each taxel, instead of a derived normal force. The depth is negated so that positive values indicate penetration.

PiperOrigin-RevId: 921980899
Change-Id: Ide4103c0aff465e25a81cfdda650f6a0e2da9e0b
This commit is contained in:
Alessio Quaglino
2026-05-27 02:00:00 -07:00
committed by Copybara-Service
parent 583aa5ad4f
commit f6cd0234fd
5 changed files with 18 additions and 27 deletions
+8 -9
View File
@@ -8514,15 +8514,14 @@ Extraction
:width: 30%
:target: https://github.com/google-deepmind/mujoco/blob/main/model/tactile/tactile.xml
The tactile sensor returns the penetration pressure and the sliding velocities in the tangent frame at given points
between the geom associated with the sensor and the SDF geoms in contact with it. We define the penetration pressure as
a function of the penetration depth :math:`p(d) = \frac{d}{d_{max}-d}`, which is zero at the surface and goes to
infinity as the maximum depth is reached. The sensor is associated with a geom and a mesh. It is activated by the
contact between its associated geom and other geoms. The vertices of the mesh, when positioned in the geom frame, are
the points at which sensor values are computed, so the dimension of the output is 3 times the number of vertices in the
mesh. The mesh must have 3 normal vectors per vertex, which are used to compute the tangent frame. If the penetration
depth is positive (no contact), then all values are 0 for the corresponding vertex. Only contacts with geoms of type SDF
contribute to the sensor output. The sensor can be visualized by enabling the visualization of contact points.
The tactile sensor returns the maximum penetration depth and the sliding velocities in the tangent frame at given points
between the geom associated with the sensor and the SDF geoms in contact with it. The sensor is associated with a geom
and a mesh. It is activated by the contact between its associated geom and other geoms. The vertices of the mesh, when
positioned in the geom frame, are the points at which sensor values are computed, so the dimension of the output is 3
times the number of vertices in the mesh. The mesh must have 3 normal vectors per vertex, which are used to compute the
tangent frame. If the penetration depth is positive (no contact), then all values are 0 for the corresponding vertex.
Only contacts with geoms of type SDF contribute to the sensor output. The sensor can be visualized by enabling the
visualization of contact points.
.. _sensor-tactile-geom:
+2
View File
@@ -79,6 +79,8 @@ General
`mjtype.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjtype.h>` and now includes all
enum type definitions.
- The :ref:`tactile<sensor-tactile>` sensor now reports raw depth instead of an estimated pressure.
- MJX: Removed the deprecated ``nconmax`` argument from ``mjx.make_data`` and ``mjx.put_data`` in favor of
``naconmax``.
+2 -6
View File
@@ -2252,18 +2252,14 @@ def _sensor_tactile(
)
vel_rel = vel_sensor - vel_other
kMaxDepth = 0.05
pressure = depth / wp.max(kMaxDepth - depth, MJ_MINVAL)
force = wp.mul(normal, pressure)
forceT = wp.vec3(0.0, 0.0, 0.0)
forceT[0] = wp.dot(force, normal)
forceT[0] = -depth
if has_frame:
forceT[1] = wp.abs(wp.dot(vel_rel, tang1))
forceT[2] = wp.abs(wp.dot(vel_rel, tang2))
dim = sensor_dim[sensor_id] // 3
wp.atomic_add(sensordata_out, worldid, sensor_adr[sensor_id] + 0 * dim + vertid, forceT[0])
wp.atomic_max(sensordata_out, worldid, sensor_adr[sensor_id] + 0 * dim + vertid, forceT[0])
wp.atomic_add(sensordata_out, worldid, sensor_adr[sensor_id] + 1 * dim + vertid, forceT[1])
wp.atomic_add(sensordata_out, worldid, sensor_adr[sensor_id] + 2 * dim + vertid, forceT[2])
+2 -8
View File
@@ -172,14 +172,8 @@ static void* tactile_taxel_batch(void* args) {
mesh_normal[normal_stride*j + 2]};
mju_rotVecQuat(normal, normal, m->mesh_quat + 4 * mesh_id);
// get contact force
mjtNum force[3];
mjtNum kMaxDepth = 0.05;
mjtNum pressure = depth / mju_max(kMaxDepth - depth, mjMINVAL);
mju_scl3(force, normal, pressure);
// accumulate into forcesT (disjoint writes per taxel j)
t->forcesT[0*ncon + j] += mju_dot3(force, normal);
// take max penetration depth (SDF distance is negative; negate for positive output)
t->forcesT[0*ncon + j] = mju_max(t->forcesT[0*ncon + j], -depth);
if (has_frame) {
mjtNum tang1[3] = {mesh_normal[normal_stride*j + 3],
+4 -4
View File
@@ -1702,7 +1702,7 @@ TEST_F(SensorTest, TactileSkipTangents) {
EXPECT_EQ(data->time, 0.0);
EXPECT_GT(data->ncon, 0) << "No contacts generated";
// Tactile sensor layout: [normal_forces..., tang1_forces..., tang2_forces...]
// Tactile sensor layout: [depths..., tang1_vel..., tang2_vel...]
int ntaxel = model->nsensordata / 3;
ASSERT_EQ(model->nsensordata % 3, 0) << "Sensor dim should be divisible by 3";
@@ -1712,13 +1712,13 @@ TEST_F(SensorTest, TactileSkipTangents) {
<< "Tangent component at index " << i << " should be 0";
}
// Normal force components: verify count, sign, and magnitude ~-0.8
// Penetration depth components: verify count, sign, and magnitude ~0.2
int nonzero_count = 0;
for (int i = 0; i < ntaxel; i++) {
if (data->sensordata[i] != 0) {
nonzero_count++;
EXPECT_NEAR(data->sensordata[i], -0.8, 0.1)
<< "Normal force at taxel " << i;
EXPECT_NEAR(data->sensordata[i], 0.2, 0.1)
<< "Penetration depth at taxel " << i;
}
}
EXPECT_EQ(nonzero_count, 2) << "Expected 2 taxels in contact";