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% :width: 30%
:target: https://github.com/google-deepmind/mujoco/blob/main/model/tactile/tactile.xml :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 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. We define the penetration pressure as between the geom associated with the sensor and the SDF geoms in contact with it. The sensor is associated with a geom
a function of the penetration depth :math:`p(d) = \frac{d}{d_{max}-d}`, which is zero at the surface and goes to and a mesh. It is activated by the contact between its associated geom and other geoms. The vertices of the mesh, when
infinity as the maximum depth is reached. The sensor is associated with a geom and a mesh. It is activated by the positioned in the geom frame, are the points at which sensor values are computed, so the dimension of the output is 3
contact between its associated geom and other geoms. The vertices of the mesh, when positioned in the geom frame, are times the number of vertices in the mesh. The mesh must have 3 normal vectors per vertex, which are used to compute the
the points at which sensor values are computed, so the dimension of the output is 3 times the number of vertices in the tangent frame. If the penetration depth is positive (no contact), then all values are 0 for the corresponding vertex.
mesh. The mesh must have 3 normal vectors per vertex, which are used to compute the tangent frame. If the penetration Only contacts with geoms of type SDF contribute to the sensor output. The sensor can be visualized by enabling the
depth is positive (no contact), then all values are 0 for the corresponding vertex. Only contacts with geoms of type SDF visualization of contact points.
contribute to the sensor output. The sensor can be visualized by enabling the visualization of contact points.
.. _sensor-tactile-geom: .. _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 `mjtype.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjtype.h>` and now includes all
enum type definitions. 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 - MJX: Removed the deprecated ``nconmax`` argument from ``mjx.make_data`` and ``mjx.put_data`` in favor of
``naconmax``. ``naconmax``.
+2 -6
View File
@@ -2252,18 +2252,14 @@ def _sensor_tactile(
) )
vel_rel = vel_sensor - vel_other 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 = wp.vec3(0.0, 0.0, 0.0)
forceT[0] = wp.dot(force, normal) forceT[0] = -depth
if has_frame: if has_frame:
forceT[1] = wp.abs(wp.dot(vel_rel, tang1)) forceT[1] = wp.abs(wp.dot(vel_rel, tang1))
forceT[2] = wp.abs(wp.dot(vel_rel, tang2)) forceT[2] = wp.abs(wp.dot(vel_rel, tang2))
dim = sensor_dim[sensor_id] // 3 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] + 1 * dim + vertid, forceT[1])
wp.atomic_add(sensordata_out, worldid, sensor_adr[sensor_id] + 2 * dim + vertid, forceT[2]) 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]}; mesh_normal[normal_stride*j + 2]};
mju_rotVecQuat(normal, normal, m->mesh_quat + 4 * mesh_id); mju_rotVecQuat(normal, normal, m->mesh_quat + 4 * mesh_id);
// get contact force // take max penetration depth (SDF distance is negative; negate for positive output)
mjtNum force[3]; t->forcesT[0*ncon + j] = mju_max(t->forcesT[0*ncon + j], -depth);
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);
if (has_frame) { if (has_frame) {
mjtNum tang1[3] = {mesh_normal[normal_stride*j + 3], 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_EQ(data->time, 0.0);
EXPECT_GT(data->ncon, 0) << "No contacts generated"; 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; int ntaxel = model->nsensordata / 3;
ASSERT_EQ(model->nsensordata % 3, 0) << "Sensor dim should be divisible by 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"; << "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; int nonzero_count = 0;
for (int i = 0; i < ntaxel; i++) { for (int i = 0; i < ntaxel; i++) {
if (data->sensordata[i] != 0) { if (data->sensordata[i] != 0) {
nonzero_count++; nonzero_count++;
EXPECT_NEAR(data->sensordata[i], -0.8, 0.1) EXPECT_NEAR(data->sensordata[i], 0.2, 0.1)
<< "Normal force at taxel " << i; << "Penetration depth at taxel " << i;
} }
} }
EXPECT_EQ(nonzero_count, 2) << "Expected 2 taxels in contact"; EXPECT_EQ(nonzero_count, 2) << "Expected 2 taxels in contact";