@@ -357,76 +357,7 @@ def mpr_expand_portal(self, v, v1, v2, i_ga, i_gb, i_b):
357357 self .simplex_support [i_s , i_b ].v = v
358358
359359 @ti .func
360- def mpr_discover_portal (self , i_ga , i_gb , i_b , normal_ws ):
361- # MPR algorithm was initially design to check whether a pair of convex geometries was colliding. The author
362- # proposed to extend its application to collision detection as it can provide the contact normal and penetration
363- # depth in some cases, i.e. when the original of the Minkowski difference can be projected inside the refined
364- # portal. Beyond this specific scenario, it only provides an approximation, that gets worst and worst as the
365- # ray casting and portal normal are misaligned.
366- # For convex shape, one can show that everything should be fine for low penetration-to-size ratio for each
367- # geometry, and the probability to accurately estimate the contact point decreases as this ratio increases.
368- #
369- # This issue can be avoided by initializing the algorithm with the good seach direction, basically the one
370- # from the previous simulation timestep would do fine, as the penetration was smaller at that time and so the
371- # likely for this direction to be valid was larger. Alternatively, the direction of the linear velocity would
372- # be a good option.
373- #
374- # Enforcing a specific search direction to vanilla MPR is not straightforward, because the direction of the ray
375- # control by v0, which is defined as the difference between the respective centers of each geometry.
376- # The only option is to change the way the center of each geometry are defined, so as to make the ray casting
377- # from origin to v0 as colinear as possible with the direction we are interested, while remaining included in
378- # their respective geometry.
379- # The idea is to offset the original centers of each geometry by a ratio that corresponds to their respective
380- # (rotated) bounding box size along each axe. Each center cannot be moved more than half of its bound-box size
381- # along each axe. This could lead to a center that is outside the geometries if they do not collide, but
382- # should be fine otherwise. Anyway, this is not a big deal in practice and MPR is robust enough to converge to
383- # a meaningful solution and if the center is slightly off of each geometry. Nevertheless, if it turns out this
384- # is a real issue, one way to address it is to evaluate the exact signed distance of each center wrt their
385- # respective geometry. If one of the center is off, its offset from the original center is divided by 2 and the
386- # signed distance is computed once again until to find a valid point. This procedure should be cheap.
387-
388- g_state_a = self ._solver .geoms_state [i_ga , i_b ]
389- g_state_b = self ._solver .geoms_state [i_gb , i_b ]
390- g_info = self ._solver .geoms_info [i_ga ]
391- center_a = gu .ti_transform_by_trans_quat (g_info .center , g_state_a .pos , g_state_a .quat )
392- g_info = self ._solver .geoms_info [i_gb ]
393- center_b = gu .ti_transform_by_trans_quat (g_info .center , g_state_b .pos , g_state_b .quat )
394-
395- # Completely different center logics if a normal guess is provided
396- if ti .static (not self ._solver ._enable_mujoco_compatibility ):
397- if (ti .abs (normal_ws ) > self .CCD_EPS ).any ():
398- # Must start from the center of each bounding box
399- center_a_local = 0.5 * (self ._solver .geoms_init_AABB [i_ga , 7 ] + self ._solver .geoms_init_AABB [i_ga , 0 ])
400- center_a = gu .ti_transform_by_trans_quat (center_a_local , g_state_a .pos , g_state_a .quat )
401- center_b_local = 0.5 * (self ._solver .geoms_init_AABB [i_gb , 7 ] + self ._solver .geoms_init_AABB [i_gb , 0 ])
402- center_b = gu .ti_transform_by_trans_quat (center_b_local , g_state_b .pos , g_state_b .quat )
403- delta = center_a - center_b
404-
405- # Skip offset if normal is roughly pointing in the same direction already.
406- # Note that a threshold of 0.5 would probably make more sense, but this means that the center of each
407- # geometry would significantly affect collision detection, which is undesirable.
408- normal = delta .normalized ()
409- if normal_ws .cross (normal ).norm () > 0.01 :
410- # Compute the target offset
411- offset = delta .dot (normal_ws ) * normal_ws - delta
412- offset_norm = offset .norm ()
413-
414- if offset_norm > gs .EPS :
415- # Compute the size of the bounding boxes along the target offset direction.
416- # First, move the direction in local box frame
417- dir_offset = offset / offset_norm
418- dir_offset_local_a = gu .ti_inv_transform_by_quat (dir_offset , g_state_a .quat )
419- dir_offset_local_b = gu .ti_inv_transform_by_quat (dir_offset , g_state_b .quat )
420- box_size_a = self ._solver .geoms_init_AABB [i_ga , 7 ] - self ._solver .geoms_init_AABB [i_ga , 0 ]
421- box_size_b = self ._solver .geoms_init_AABB [i_gb , 7 ] - self ._solver .geoms_init_AABB [i_gb , 0 ]
422- length_a = box_size_a .dot (ti .abs (dir_offset_local_a ))
423- length_b = box_size_b .dot (ti .abs (dir_offset_local_b ))
424-
425- # Shift the center of each geometry
426- offset_ratio = ti .min (offset_norm / (length_a + length_b ), 0.5 )
427- center_a = center_a + dir_offset * length_a * offset_ratio
428- center_b = center_b - dir_offset * length_b * offset_ratio
429-
360+ def mpr_discover_portal (self , i_ga , i_gb , i_b , center_a , center_b ):
430361 self .simplex_support [0 , i_b ].v1 = center_a
431362 self .simplex_support [0 , i_b ].v2 = center_b
432363 self .simplex_support [0 , i_b ].v = center_a - center_b
@@ -526,8 +457,81 @@ def mpr_discover_portal(self, i_ga, i_gb, i_b, normal_ws):
526457 return ret
527458
528459 @ti .func
529- def func_mpr_contact (self , i_ga , i_gb , i_b , normal_ws ):
530- res = self .mpr_discover_portal (i_ga , i_gb , i_b , normal_ws )
460+ def guess_geoms_center (self , i_ga , i_gb , i_b , normal_ws ):
461+ # MPR algorithm was initially design to check whether a pair of convex geometries was colliding. The author
462+ # proposed to extend its application to collision detection as it can provide the contact normal and penetration
463+ # depth in some cases, i.e. when the original of the Minkowski difference can be projected inside the refined
464+ # portal. Beyond this specific scenario, it only provides an approximation, that gets worst and worst as the
465+ # ray casting and portal normal are misaligned.
466+ # For convex shape, one can show that everything should be fine for low penetration-to-size ratio for each
467+ # geometry, and the probability to accurately estimate the contact point decreases as this ratio increases.
468+ #
469+ # This issue can be avoided by initializing the algorithm with the good seach direction, basically the one
470+ # from the previous simulation timestep would do fine, as the penetration was smaller at that time and so the
471+ # likely for this direction to be valid was larger. Alternatively, the direction of the linear velocity would
472+ # be a good option.
473+ #
474+ # Enforcing a specific search direction to vanilla MPR is not straightforward, because the direction of the ray
475+ # control by v0, which is defined as the difference between the respective centers of each geometry.
476+ # The only option is to change the way the center of each geometry are defined, so as to make the ray casting
477+ # from origin to v0 as colinear as possible with the direction we are interested, while remaining included in
478+ # their respective geometry.
479+ # The idea is to offset the original centers of each geometry by a ratio that corresponds to their respective
480+ # (rotated) bounding box size along each axe. Each center cannot be moved more than half of its bound-box size
481+ # along each axe. This could lead to a center that is outside the geometries if they do not collide, but
482+ # should be fine otherwise. Anyway, this is not a big deal in practice and MPR is robust enough to converge to
483+ # a meaningful solution and if the center is slightly off of each geometry. Nevertheless, if it turns out this
484+ # is a real issue, one way to address it is to evaluate the exact signed distance of each center wrt their
485+ # respective geometry. If one of the center is off, its offset from the original center is divided by 2 and the
486+ # signed distance is computed once again until to find a valid point. This procedure should be cheap.
487+
488+ g_state_a = self ._solver .geoms_state [i_ga , i_b ]
489+ g_state_b = self ._solver .geoms_state [i_gb , i_b ]
490+ g_info = self ._solver .geoms_info [i_ga ]
491+ center_a = gu .ti_transform_by_trans_quat (g_info .center , g_state_a .pos , g_state_a .quat )
492+ g_info = self ._solver .geoms_info [i_gb ]
493+ center_b = gu .ti_transform_by_trans_quat (g_info .center , g_state_b .pos , g_state_b .quat )
494+
495+ # Completely different center logics if a normal guess is provided
496+ if ti .static (not self ._solver ._enable_mujoco_compatibility ):
497+ if (ti .abs (normal_ws ) > self .CCD_EPS ).any ():
498+ # Must start from the center of each bounding box
499+ center_a_local = 0.5 * (self ._solver .geoms_init_AABB [i_ga , 7 ] + self ._solver .geoms_init_AABB [i_ga , 0 ])
500+ center_a = gu .ti_transform_by_trans_quat (center_a_local , g_state_a .pos , g_state_a .quat )
501+ center_b_local = 0.5 * (self ._solver .geoms_init_AABB [i_gb , 7 ] + self ._solver .geoms_init_AABB [i_gb , 0 ])
502+ center_b = gu .ti_transform_by_trans_quat (center_b_local , g_state_b .pos , g_state_b .quat )
503+ delta = center_a - center_b
504+
505+ # Skip offset if normal is roughly pointing in the same direction already.
506+ # Note that a threshold of 0.5 would probably make more sense, but this means that the center of each
507+ # geometry would significantly affect collision detection, which is undesirable.
508+ normal = delta .normalized ()
509+ if normal_ws .cross (normal ).norm () > 0.01 :
510+ # Compute the target offset
511+ offset = delta .dot (normal_ws ) * normal_ws - delta
512+ offset_norm = offset .norm ()
513+
514+ if offset_norm > gs .EPS :
515+ # Compute the size of the bounding boxes along the target offset direction.
516+ # First, move the direction in local box frame
517+ dir_offset = offset / offset_norm
518+ dir_offset_local_a = gu .ti_inv_transform_by_quat (dir_offset , g_state_a .quat )
519+ dir_offset_local_b = gu .ti_inv_transform_by_quat (dir_offset , g_state_b .quat )
520+ box_size_a = self ._solver .geoms_init_AABB [i_ga , 7 ] - self ._solver .geoms_init_AABB [i_ga , 0 ]
521+ box_size_b = self ._solver .geoms_init_AABB [i_gb , 7 ] - self ._solver .geoms_init_AABB [i_gb , 0 ]
522+ length_a = box_size_a .dot (ti .abs (dir_offset_local_a ))
523+ length_b = box_size_b .dot (ti .abs (dir_offset_local_b ))
524+
525+ # Shift the center of each geometry
526+ offset_ratio = ti .min (offset_norm / (length_a + length_b ), 0.5 )
527+ center_a = center_a + dir_offset * length_a * offset_ratio
528+ center_b = center_b - dir_offset * length_b * offset_ratio
529+
530+ return center_a , center_b
531+
532+ @ti .func
533+ def func_mpr_contact_from_centers (self , i_ga , i_gb , i_b , center_a , center_b ):
534+ res = self .mpr_discover_portal (i_ga , i_gb , i_b , center_a , center_b )
531535
532536 is_col = False
533537 pos = gs .ti_vec3 ([0.0 , 0.0 , 0.0 ])
@@ -544,3 +548,8 @@ def func_mpr_contact(self, i_ga, i_gb, i_b, normal_ws):
544548 is_col , normal , penetration , pos = self .mpr_find_penetration (i_ga , i_gb , i_b )
545549
546550 return is_col , normal , penetration , pos
551+
552+ @ti .func
553+ def func_mpr_contact (self , i_ga , i_gb , i_b , normal_ws ):
554+ center_a , center_b = self .guess_geoms_center (i_ga , i_gb , i_b , normal_ws )
555+ return self .func_mpr_contact_from_centers (i_ga , i_gb , i_b , center_a , center_b )
0 commit comments