@@ -538,6 +538,7 @@ def func_rotate_frame(
538538
539539@qd .kernel (fastcache = True )
540540def func_clamp_prune_and_sort_contacts (
541+ links_info : array_class .LinksInfo ,
541542 collider_state : array_class .ColliderState ,
542543 collider_info : array_class .ColliderInfo ,
543544 rigid_global_info : array_class .RigidGlobalInfo ,
@@ -582,6 +583,7 @@ def func_clamp_prune_and_sort_contacts(
582583 max_contact_pairs = collider_info .max_contact_pairs [None ]
583584 tol = collider_info .contact_pruning_tolerance [None ]
584585 prune_deep_penetration_ratio = collider_info .prune_deep_penetration_ratio [None ]
586+ prune_degenerate_area_ratio = collider_info .prune_degenerate_area_ratio [None ]
585587 LP_KEY_STRIDE = gs .qd_float (1.0e7 )
586588 EPS = rigid_global_info .EPS [None ]
587589
@@ -869,6 +871,49 @@ def func_clamp_prune_and_sort_contacts(
869871 if collider_state .contact_data .penetration [phys_i , i_b ] > deep_keep_threshold :
870872 collider_state .contact_keep [i , i_b ] = 1
871873
874+ # Support-polygon degeneracy gate. When the hull polygon's surface area is too small to
875+ # absorb the first-order-Taylor bias in perturbed contact normals (each contact's bias
876+ # creates a horizontal force ~ N * mc_perturbation^2; if the polygon is thin the moment arms
877+ # align rather than cancel and the LCP drifts), restore every contact the hull dropped, i.e.
878+ # skip pruning for this bucket. Closed-form shoelace area on the hull stack
879+ # contact_hull_stack[b_start..b_start+k); The threshold is the link-pair effective inertia
880+ # radius squared (invweight_trans / invweight_rot): mass cancels out, so the ratio is a
881+ # shape-and-density-distribution quantity.
882+ hull_area = gs .qd_float (0.0 )
883+ if k >= 3 :
884+ for i in range (k ):
885+ a_pos = collider_state .contact_hull_stack [b_start + i , i_b ]
886+ j = i + 1
887+ if j == k :
888+ j = 0
889+ b_pos = collider_state .contact_hull_stack [b_start + j , i_b ]
890+ au = collider_state .contact_sort_key [a_pos , i_b ]
891+ av = collider_state .contact_proj_v [a_pos , i_b ]
892+ bu = collider_state .contact_sort_key [b_pos , i_b ]
893+ bv = collider_state .contact_proj_v [b_pos , i_b ]
894+ hull_area = hull_area + au * bv - bu * av
895+ hull_area = 0.5 * qd .abs (hull_area )
896+ I_la0 = (la0 , i_b ) if qd .static (static_rigid_sim_config .batch_links_info ) else la0
897+ I_lb0 = (lb0 , i_b ) if qd .static (static_rigid_sim_config .batch_links_info ) else lb0
898+ invw_ratio = gs .qd_float (0.0 )
899+ if links_info .is_fixed [I_la0 ]:
900+ invw_ratio = links_info .invweight [I_lb0 ][0 ] / qd .max (
901+ links_info .invweight [I_lb0 ][1 ], EPS
902+ )
903+ elif links_info .is_fixed [I_lb0 ]:
904+ invw_ratio = links_info .invweight [I_la0 ][0 ] / qd .max (
905+ links_info .invweight [I_la0 ][1 ], EPS
906+ )
907+ else :
908+ invw_ratio = min (
909+ links_info .invweight [I_la0 ][0 ] / qd .max (links_info .invweight [I_la0 ][1 ], EPS ),
910+ links_info .invweight [I_lb0 ][0 ] / qd .max (links_info .invweight [I_lb0 ][1 ], EPS ),
911+ )
912+ if hull_area < prune_degenerate_area_ratio * invw_ratio :
913+ for i in range (b_start , b_end ):
914+ if collider_state .contact_keep [i , i_b ] == 0 :
915+ collider_state .contact_keep [i , i_b ] = 1
916+
872917 b_start = b_end
873918
874919 # Phase 3: compact contact_sort_idx by squeezing out dropped slots.
@@ -923,6 +968,7 @@ def func_clamp_prune_and_sort_contacts(
923968
924969@qd .kernel (fastcache = True )
925970def func_clamp_prune_and_sort_contacts_coop (
971+ links_info : array_class .LinksInfo ,
926972 collider_state : array_class .ColliderState ,
927973 collider_info : array_class .ColliderInfo ,
928974 rigid_global_info : array_class .RigidGlobalInfo ,
0 commit comments