33
44import numpy as np
55import torch
6+ import trimesh
67
78import gstaichi as ti
89
@@ -186,11 +187,13 @@ def _compute_collision_pair_idx(self):
186187 Compute flat indices of all valid collision pairs.
187188
188189 For each pair of geoms, determine if they can collide based on their properties and the solver configuration.
190+ Pairs that are already colliding at the initial configuration (qpos0) are filtered out with a warning.
189191 """
190192 solver = self ._solver
191193 n_geoms = solver .n_geoms_
192194 n_equalities = solver .n_equalities
193195 enable_self_collision = solver ._enable_self_collision
196+ enable_neutral_collision = solver ._enable_neutral_collision
194197 enable_adjacent_collision = solver ._enable_adjacent_collision
195198 batch_links_info = solver ._static_rigid_sim_config .batch_links_info
196199
@@ -211,6 +214,18 @@ def _compute_collision_pair_idx(self):
211214 links_is_fixed = links_is_fixed [:, 0 ]
212215 entities_is_local_collision_mask = solver .entities_info .is_local_collision_mask .to_numpy ()
213216
217+ # Compute vertices all geometries, shrunk by 0.1% to avoid false positive when detecting self-collision
218+ geoms_verts : list [np .ndarray ] = []
219+ for geom in solver .geoms :
220+ verts = tensor_to_array (geom .get_verts ())
221+ verts = verts .reshape ((- 1 , * verts .shape [- 2 :]))
222+ centroid = verts .mean (axis = 1 , keepdims = True )
223+ verts = centroid + (1.0 - 1e-3 ) * (verts - centroid )
224+ geoms_verts .append (verts )
225+
226+ # Track pairs that are colliding in neutral configuration for warning
227+ self_colliding_pairs : list [tuple [int , int ]] = []
228+
214229 n_possible_pairs = 0
215230 collision_pair_idx = np .full ((n_geoms , n_geoms ), fill_value = - 1 , dtype = gs .np_int )
216231 for i_ga in range (n_geoms ):
@@ -224,17 +239,6 @@ def _compute_collision_pair_idx(self):
224239 if i_la == i_lb :
225240 continue
226241
227- # self collision
228- if links_root_idx [i_la ] == links_root_idx [i_lb ]:
229- if not enable_self_collision :
230- continue
231-
232- # adjacent links
233- if not enable_adjacent_collision and (
234- links_parent_idx [i_la ] == i_lb or links_parent_idx [i_lb ] == i_la
235- ):
236- continue
237-
238242 # Filter out right away weld constraint that have been declared statically and cannot be removed
239243 is_valid = True
240244 for i_eq in range (n_equalities ):
@@ -258,9 +262,49 @@ def _compute_collision_pair_idx(self):
258262 if links_is_fixed [i_la ] and links_is_fixed [i_lb ]:
259263 continue
260264
265+ # self collision
266+ if links_root_idx [i_la ] == links_root_idx [i_lb ]:
267+ if not enable_self_collision :
268+ continue
269+
270+ # adjacent links
271+ if not enable_adjacent_collision and (
272+ links_parent_idx [i_la ] == i_lb or links_parent_idx [i_lb ] == i_la
273+ ):
274+ continue
275+
276+ # active in neutral configuration (qpos0)
277+ is_self_colliding = False
278+ for i_b in range (1 if not enable_neutral_collision else 0 ):
279+ mesh_a = trimesh .Trimesh (
280+ vertices = geoms_verts [i_ga ][i_b ], faces = solver .geoms [i_ga ].init_faces , process = False
281+ )
282+ mesh_b = trimesh .Trimesh (
283+ vertices = geoms_verts [i_gb ][i_b ], faces = solver .geoms [i_gb ].init_faces , process = False
284+ )
285+ bounds_a , bounds_b = mesh_a .bounds , mesh_b .bounds
286+ if not ((bounds_a [1 ] < bounds_b [0 ]).any () or (bounds_b [1 ] < bounds_a [0 ]).any ()):
287+ if (mesh_a .is_watertight and mesh_a .contains (mesh_b .vertices ).any ()) or (
288+ mesh_b .is_watertight and mesh_b .contains (mesh_a .vertices ).any ()
289+ ):
290+ is_self_colliding = True
291+ self_colliding_pairs .append ((i_ga , i_gb ))
292+ break
293+ if is_self_colliding :
294+ continue
295+
261296 collision_pair_idx [i_ga , i_gb ] = n_possible_pairs
262297 n_possible_pairs = n_possible_pairs + 1
263298
299+ # Emit warning for self-collision pairs
300+ if self_colliding_pairs :
301+ pairs = ", " .join ((f"({ i_ga } , { i_gb } )" ) for i_ga , i_gb in self_colliding_pairs )
302+ gs .logger .warning (
303+ f"Filtered out geometry pairs causing self-collision for the neutral configuration (qpos0): { pairs } . "
304+ "Consider tuning Morph option 'decompose_robot_error_threshold' or specify dedicated collision meshes. "
305+ "This behavior can be disabled by setting Morph option 'enable_neutral_collision=True'."
306+ )
307+
264308 return n_possible_pairs , collision_pair_idx
265309
266310 def _compute_verts_connectivity (self ):
0 commit comments