1313from genesis .options .sensors import ContactDepthProbe as ContactDepthProbeOptions
1414from genesis .options .sensors import ContactProbe as ContactProbeOptions
1515from genesis .options .sensors import KinematicTaxel as KinematicTaxelOptions
16- from genesis .utils .misc import concat_with_tensor , make_tensor_field , tensor_to_array
16+ from genesis .utils .misc import append_filter_links_idx , concat_with_tensor , make_tensor_field , tensor_to_array
1717
1818from .base_sensor import RigidSensorMetadataMixin , RigidSensorMixin , SimpleSensor , SimpleSensorMetadata
1919from .probe import (
3333 from .sensor_manager import SensorManager
3434
3535
36+ @qd .func
37+ def _func_link_is_filtered (filter_links_idx : qd .types .ndarray (), i_s : int , link : int ):
38+ """Whether ``link`` is in sensor ``i_s``'s filter list. ``-1`` padding entries never match a real link index."""
39+ is_filtered = False
40+ for i_f in range (filter_links_idx .shape [- 1 ]):
41+ if filter_links_idx [i_s , i_f ] == link :
42+ is_filtered = True
43+ return is_filtered
44+
45+
3646@qd .func
3747def _func_query_contact_depth_penetration (
3848 i_b : int ,
3949 probe_pos : qd .types .vector (3 ),
4050 probe_radius_gt : float ,
4151 probe_radius_m : float ,
4252 sensor_link_idx : int ,
53+ filter_links_idx : qd .types .ndarray (),
54+ i_s : int ,
4355 geoms_info : array_class .GeomsInfo ,
4456 geoms_state : array_class .GeomsState ,
4557 collider_state : array_class .ColliderState ,
@@ -50,7 +62,8 @@ def _func_query_contact_depth_penetration(
5062
5163 Returns ``(max_pen_gt, max_pen_m)`` from a single SDF pass: both penetrations come from the same ``sd`` per contact
5264 via ``pen = radius - sd``. Callers that do not need the noised-radius branch pass ``probe_radius_m ==
53- probe_radius_gt`` and ignore the second return.
65+ probe_radius_gt`` and ignore the second return. Contacts whose counterpart link is in sensor ``i_s``'s
66+ ``filter_links_idx`` list are skipped.
5467 """
5568 max_pen_gt = gs .qd_float (0.0 )
5669 max_pen_m = gs .qd_float (0.0 )
@@ -65,9 +78,10 @@ def _func_query_contact_depth_penetration(
6578
6679 for side in qd .static (range (2 )):
6780 c_link = c_link_a if side == 0 else c_link_b
81+ c_link_other = c_link_b if side == 0 else c_link_a
6882 i_g = c_geom_b if side == 0 else c_geom_a
6983
70- if c_link == sensor_link_idx :
84+ if c_link == sensor_link_idx and not _func_link_is_filtered ( filter_links_idx , i_s , c_link_other ) :
7185 g_pos = geoms_state .pos [i_g , i_b ]
7286 g_quat = geoms_state .quat [i_g , i_b ]
7387 sd = sdf .sdf_func_world_local (geoms_info , sdf_info , probe_pos , i_g , g_pos , g_quat )
@@ -88,6 +102,8 @@ def _func_query_contact_depth(
88102 probe_radius_gt : float ,
89103 probe_radius_m : float ,
90104 sensor_link_idx : int ,
105+ filter_links_idx : qd .types .ndarray (),
106+ i_s : int ,
91107 geoms_info : array_class .GeomsInfo ,
92108 geoms_state : array_class .GeomsState ,
93109 rigid_global_info : array_class .RigidGlobalInfo ,
@@ -101,7 +117,8 @@ def _func_query_contact_depth(
101117
102118 Returns ``(max_pen_gt, contact_link_gt, contact_normal_gt, max_pen_m, contact_link_m, contact_normal_m)``. AABB
103119 pre-filter expands by ``max(probe_radius_gt, probe_radius_m)`` so neither branch is silently skipped. Callers
104- without a noised radius pass ``probe_radius_m == probe_radius_gt``.
120+ without a noised radius pass ``probe_radius_m == probe_radius_gt``. Contacts whose counterpart link is in sensor
121+ ``i_s``'s ``filter_links_idx`` list are skipped.
105122 """
106123 max_pen_gt = gs .qd_float (0.0 )
107124 contact_link_gt = gs .qd_int (- 1 )
@@ -123,9 +140,14 @@ def _func_query_contact_depth(
123140 # Check if either side of this contact involves the sensor link.
124141 for side in qd .static (range (2 )):
125142 c_link = c_link_a if side == 0 else c_link_b
143+ c_link_other = c_link_b if side == 0 else c_link_a
126144 i_g = c_geom_b if side == 0 else c_geom_a
127145
128- if c_link == sensor_link_idx and func_point_in_geom_aabb (geoms_state , i_g , i_b , probe_pos , aabb_expansion ):
146+ if (
147+ c_link == sensor_link_idx
148+ and not _func_link_is_filtered (filter_links_idx , i_s , c_link_other )
149+ and func_point_in_geom_aabb (geoms_state , i_g , i_b , probe_pos , aabb_expansion )
150+ ):
129151 g_pos = geoms_state .pos [i_g , i_b ]
130152 g_quat = geoms_state .quat [i_g , i_b ]
131153 sd = sdf .sdf_func_world_local (geoms_info , sdf_info , probe_pos , i_g , g_pos , g_quat )
@@ -139,11 +161,11 @@ def _func_query_contact_depth(
139161 )
140162 if pen_gt > max_pen_gt and pen_gt > eps :
141163 max_pen_gt = pen_gt
142- contact_link_gt = c_link_b if side == 0 else c_link_a
164+ contact_link_gt = c_link_other
143165 contact_normal_gt = normal
144166 if pen_m > max_pen_m and pen_m > eps :
145167 max_pen_m = pen_m
146- contact_link_m = c_link_b if side == 0 else c_link_a
168+ contact_link_m = c_link_other
147169 contact_normal_m = normal
148170
149171 return max_pen_gt , contact_link_gt , contact_normal_gt , max_pen_m , contact_link_m , contact_normal_m
@@ -161,6 +183,7 @@ def _kernel_kinematic_taxel(
161183 shear_scalar : qd .types .ndarray (),
162184 twist_scalar : qd .types .ndarray (),
163185 links_idx : qd .types .ndarray (),
186+ filter_links_idx : qd .types .ndarray (),
164187 sensor_cache_start : qd .types .ndarray (),
165188 sensor_probe_start : qd .types .ndarray (),
166189 n_probes_per_sensor : qd .types .ndarray (),
@@ -211,6 +234,8 @@ def _kernel_kinematic_taxel(
211234 probe_radius ,
212235 probe_radius_m ,
213236 sensor_link_idx ,
237+ filter_links_idx ,
238+ i_s ,
214239 geoms_info ,
215240 geoms_state ,
216241 rigid_global_info ,
@@ -305,6 +330,7 @@ def _kernel_contact_depth_probe(
305330 probe_radii : qd .types .ndarray (),
306331 probe_radii_noise : qd .types .ndarray (),
307332 links_idx : qd .types .ndarray (),
333+ filter_links_idx : qd .types .ndarray (),
308334 sensor_cache_start : qd .types .ndarray (),
309335 sensor_probe_start : qd .types .ndarray (),
310336 collider_state : array_class .ColliderState ,
@@ -343,6 +369,8 @@ def _kernel_contact_depth_probe(
343369 probe_radius ,
344370 probe_radius_m ,
345371 sensor_link_idx ,
372+ filter_links_idx ,
373+ i_s ,
346374 geoms_info ,
347375 geoms_state ,
348376 collider_state ,
@@ -368,6 +396,9 @@ def __init__(
368396 def build (self ):
369397 super ().build ()
370398 self ._shared_metadata .solver .collider .activate_sdf ()
399+ self ._shared_metadata .filter_links_idx = append_filter_links_idx (
400+ self ._shared_metadata .filter_links_idx , self ._options .filter_link_idx
401+ )
371402
372403 def _draw_debug_probes (self , context : "RasterizerContext" , get_is_contact : Callable [[object ], object ]):
373404 for obj in self ._debug_objects :
@@ -392,7 +423,8 @@ def _draw_debug_probes(self, context: "RasterizerContext", get_is_contact: Calla
392423
393424@dataclass
394425class ContactDepthProbeMetadata (ProbeSensorMetadataMixin , RigidSensorMetadataMixin , SimpleSensorMetadata ):
395- pass
426+ # (n_sensors, max_num_filter_links); unused slots and empty filters are -1. See `append_filter_links_idx`.
427+ filter_links_idx : torch .Tensor = make_tensor_field ((0 , 0 ), dtype_factory = lambda : gs .tc_int )
396428
397429
398430class ContactDepthProbeSensor (
@@ -433,6 +465,7 @@ def _update_current_timestep_data(
433465 shared_metadata .probe_radii ,
434466 shared_metadata .probe_radii_noise ,
435467 shared_metadata .links_idx ,
468+ shared_metadata .filter_links_idx ,
436469 shared_metadata .sensor_cache_start ,
437470 shared_metadata .sensor_probe_start ,
438471 solver .collider ._collider_state ,
@@ -522,6 +555,8 @@ class KinematicTaxelMetadata(ProbesWithNormalSensorMetadataMixin, RigidSensorMet
522555 normal_exponent : torch .Tensor = make_tensor_field ((0 ,))
523556 shear_scalar : torch .Tensor = make_tensor_field ((0 ,))
524557 twist_scalar : torch .Tensor = make_tensor_field ((0 ,))
558+ # (n_sensors, max_num_filter_links); unused slots and empty filters are -1. See `append_filter_links_idx`.
559+ filter_links_idx : torch .Tensor = make_tensor_field ((0 , 0 ), dtype_factory = lambda : gs .tc_int )
525560
526561
527562class KinematicTaxelSensor (
@@ -597,6 +632,7 @@ def _update_current_timestep_data(
597632 shared_metadata .shear_scalar ,
598633 shared_metadata .twist_scalar ,
599634 shared_metadata .links_idx ,
635+ shared_metadata .filter_links_idx ,
600636 shared_metadata .sensor_cache_start ,
601637 shared_metadata .sensor_probe_start ,
602638 shared_metadata .n_probes_per_sensor ,
0 commit comments