warp-lang 0.15.0__py3-none-manylinux2014_x86_64.whl → 1.0.0__py3-none-manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of warp-lang might be problematic. Click here for more details.

Files changed (80) hide show
  1. warp/__init__.py +1 -0
  2. warp/codegen.py +7 -3
  3. warp/config.py +2 -1
  4. warp/constants.py +3 -0
  5. warp/context.py +44 -21
  6. warp/examples/assets/bunny.usd +0 -0
  7. warp/examples/assets/cartpole.urdf +110 -0
  8. warp/examples/assets/crazyflie.usd +0 -0
  9. warp/examples/assets/cube.usda +42 -0
  10. warp/examples/assets/nv_ant.xml +92 -0
  11. warp/examples/assets/nv_humanoid.xml +183 -0
  12. warp/examples/assets/quadruped.urdf +268 -0
  13. warp/examples/assets/rocks.nvdb +0 -0
  14. warp/examples/assets/rocks.usd +0 -0
  15. warp/examples/assets/sphere.usda +56 -0
  16. warp/examples/assets/torus.usda +105 -0
  17. warp/examples/core/example_dem.py +6 -6
  18. warp/examples/core/example_fluid.py +3 -3
  19. warp/examples/core/example_graph_capture.py +3 -6
  20. warp/examples/optim/example_bounce.py +9 -8
  21. warp/examples/optim/example_cloth_throw.py +12 -8
  22. warp/examples/optim/example_diffray.py +10 -12
  23. warp/examples/optim/example_drone.py +31 -14
  24. warp/examples/optim/example_spring_cage.py +10 -15
  25. warp/examples/optim/example_trajectory.py +7 -24
  26. warp/examples/sim/example_cartpole.py +3 -9
  27. warp/examples/sim/example_cloth.py +10 -10
  28. warp/examples/sim/example_granular.py +3 -3
  29. warp/examples/sim/example_granular_collision_sdf.py +9 -4
  30. warp/examples/sim/example_jacobian_ik.py +0 -10
  31. warp/examples/sim/example_particle_chain.py +4 -4
  32. warp/examples/sim/example_quadruped.py +15 -11
  33. warp/examples/sim/example_rigid_chain.py +13 -8
  34. warp/examples/sim/example_rigid_contact.py +4 -4
  35. warp/examples/sim/example_rigid_force.py +7 -7
  36. warp/examples/sim/example_rigid_soft_contact.py +4 -4
  37. warp/examples/sim/example_soft_body.py +3 -3
  38. warp/jax.py +45 -0
  39. warp/jax_experimental.py +339 -0
  40. warp/render/render_opengl.py +188 -95
  41. warp/render/render_usd.py +34 -10
  42. warp/sim/__init__.py +13 -4
  43. warp/sim/articulation.py +4 -5
  44. warp/sim/collide.py +320 -175
  45. warp/sim/import_mjcf.py +25 -30
  46. warp/sim/import_urdf.py +94 -63
  47. warp/sim/import_usd.py +51 -36
  48. warp/sim/inertia.py +3 -2
  49. warp/sim/integrator.py +233 -0
  50. warp/sim/integrator_euler.py +447 -469
  51. warp/sim/integrator_featherstone.py +1991 -0
  52. warp/sim/integrator_xpbd.py +1420 -640
  53. warp/sim/model.py +741 -487
  54. warp/sim/particles.py +2 -1
  55. warp/sim/render.py +18 -2
  56. warp/sim/utils.py +222 -11
  57. warp/stubs.py +1 -0
  58. warp/tape.py +6 -9
  59. warp/tests/test_examples.py +87 -20
  60. warp/tests/test_grad_customs.py +122 -0
  61. warp/tests/test_jax.py +254 -0
  62. warp/tests/test_options.py +13 -53
  63. warp/tests/test_quat.py +23 -0
  64. warp/tests/test_snippet.py +2 -0
  65. warp/tests/test_utils.py +31 -26
  66. warp/tests/test_verify_fp.py +65 -0
  67. warp/tests/unittest_suites.py +4 -0
  68. warp/utils.py +50 -1
  69. {warp_lang-0.15.0.dist-info → warp_lang-1.0.0.dist-info}/METADATA +1 -1
  70. {warp_lang-0.15.0.dist-info → warp_lang-1.0.0.dist-info}/RECORD +73 -64
  71. warp/examples/env/__init__.py +0 -0
  72. warp/examples/env/env_ant.py +0 -61
  73. warp/examples/env/env_cartpole.py +0 -63
  74. warp/examples/env/env_humanoid.py +0 -65
  75. warp/examples/env/env_usd.py +0 -97
  76. warp/examples/env/environment.py +0 -526
  77. warp/sim/optimizer.py +0 -138
  78. {warp_lang-0.15.0.dist-info → warp_lang-1.0.0.dist-info}/LICENSE.md +0 -0
  79. {warp_lang-0.15.0.dist-info → warp_lang-1.0.0.dist-info}/WHEEL +0 -0
  80. {warp_lang-0.15.0.dist-info → warp_lang-1.0.0.dist-info}/top_level.txt +0 -0
warp/sim/collide.py CHANGED
@@ -222,6 +222,13 @@ def closest_point_box(upper: wp.vec3, point: wp.vec3):
222
222
 
223
223
  @wp.func
224
224
  def get_box_vertex(point_id: int, upper: wp.vec3):
225
+ # box vertex numbering:
226
+ # 6---7
227
+ # |\ |\ y
228
+ # | 2-+-3 |
229
+ # 4-+-5 | z \|
230
+ # \| \| o---x
231
+ # 0---1
225
232
  # get the vertex of the box given its ID (0-7)
226
233
  sign_x = float(point_id % 2) * 2.0 - 1.0
227
234
  sign_y = float((point_id // 2) % 2) * 2.0 - 1.0
@@ -474,6 +481,39 @@ def volume_grad(volume: wp.uint64, p: wp.vec3):
474
481
  return wp.normalize(wp.vec3(dx, dy, dz))
475
482
 
476
483
 
484
+ @wp.func
485
+ def counter_increment(counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int):
486
+ # increment counter, remember which thread received which counter value
487
+ next_count = wp.atomic_add(counter, counter_index, 1)
488
+ tids[tid] = next_count
489
+ return next_count
490
+
491
+
492
+ @wp.func_replay(counter_increment)
493
+ def replay_counter_increment(counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int):
494
+ return tids[tid]
495
+
496
+
497
+ @wp.func
498
+ def limited_counter_increment(
499
+ counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int, index_limit: int
500
+ ):
501
+ # increment counter but only if it is smaller than index_limit, remember which thread received which counter value
502
+ next_count = wp.atomic_add(counter, counter_index, 1)
503
+ if next_count < index_limit or index_limit < 0:
504
+ tids[tid] = next_count
505
+ return next_count
506
+ tids[tid] = -1
507
+ return -1
508
+
509
+
510
+ @wp.func_replay(limited_counter_increment)
511
+ def replay_limited_counter_increment(
512
+ counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int, index_limit: int
513
+ ):
514
+ return tids[tid]
515
+
516
+
477
517
  @wp.kernel
478
518
  def create_soft_contacts(
479
519
  particle_x: wp.array(dtype=wp.vec3),
@@ -485,6 +525,7 @@ def create_soft_contacts(
485
525
  geo: ModelShapeGeometry,
486
526
  margin: float,
487
527
  soft_contact_max: int,
528
+ shape_count: int,
488
529
  # outputs
489
530
  soft_contact_count: wp.array(dtype=int),
490
531
  soft_contact_particle: wp.array(dtype=int),
@@ -492,8 +533,10 @@ def create_soft_contacts(
492
533
  soft_contact_body_pos: wp.array(dtype=wp.vec3),
493
534
  soft_contact_body_vel: wp.array(dtype=wp.vec3),
494
535
  soft_contact_normal: wp.array(dtype=wp.vec3),
536
+ soft_contact_tids: wp.array(dtype=int),
495
537
  ):
496
- particle_index, shape_index = wp.tid()
538
+ tid = wp.tid()
539
+ particle_index, shape_index = tid // shape_count, tid % shape_count
497
540
  if (particle_flags[particle_index] & PARTICLE_FLAG_ACTIVE) == 0:
498
541
  return
499
542
 
@@ -551,8 +594,9 @@ def create_soft_contacts(
551
594
  face_v = float(0.0)
552
595
  sign = float(0.0)
553
596
 
597
+ min_scale = wp.min(geo_scale)
554
598
  if wp.mesh_query_point_sign_normal(
555
- mesh, wp.cw_div(x_local, geo_scale), margin + radius, sign, face_index, face_u, face_v
599
+ mesh, wp.cw_div(x_local, geo_scale), margin + radius / min_scale, sign, face_index, face_u, face_v
556
600
  ):
557
601
  shape_p = wp.mesh_eval_position(mesh, face_index, face_u, face_v)
558
602
  shape_v = wp.mesh_eval_velocity(mesh, face_index, face_u, face_v)
@@ -561,24 +605,24 @@ def create_soft_contacts(
561
605
  shape_v = wp.cw_mul(shape_v, geo_scale)
562
606
 
563
607
  delta = x_local - shape_p
564
-
608
+
565
609
  d = wp.length(delta) * sign
566
610
  n = wp.normalize(delta) * sign
567
611
  v = shape_v
568
-
612
+
569
613
  if geo_type == wp.sim.GEO_SDF:
570
614
  volume = geo.source[shape_index]
571
615
  xpred_local = wp.volume_world_to_index(volume, wp.cw_div(x_local, geo_scale))
572
616
  nn = wp.vec3(0.0, 0.0, 0.0)
573
617
  d = wp.volume_sample_grad_f(volume, xpred_local, wp.Volume.LINEAR, nn)
574
- n = wp.normalize(nn)
618
+ n = wp.normalize(nn)
575
619
 
576
620
  if geo_type == wp.sim.GEO_PLANE:
577
621
  d = plane_sdf(geo_scale[0], geo_scale[1], x_local)
578
622
  n = wp.vec3(0.0, 1.0, 0.0)
579
623
 
580
624
  if d < margin + radius:
581
- index = wp.atomic_add(soft_contact_count, 0, 1)
625
+ index = counter_increment(soft_contact_count, 0, soft_contact_tids, tid)
582
626
 
583
627
  if index < soft_contact_max:
584
628
  # compute contact point in body local space
@@ -594,10 +638,11 @@ def create_soft_contacts(
594
638
  soft_contact_normal[index] = world_normal
595
639
 
596
640
 
597
- @wp.kernel
641
+ @wp.kernel(enable_backward=False)
598
642
  def count_contact_points(
599
643
  contact_pairs: wp.array(dtype=int, ndim=2),
600
644
  geo: ModelShapeGeometry,
645
+ mesh_contact_max: int,
601
646
  # outputs
602
647
  contact_count: wp.array(dtype=int),
603
648
  ):
@@ -606,9 +651,11 @@ def count_contact_points(
606
651
  shape_b = contact_pairs[tid, 1]
607
652
 
608
653
  if shape_b == -1:
654
+ actual_shape_a = shape_a
609
655
  actual_type_a = geo.type[shape_a]
610
656
  # ground plane
611
657
  actual_type_b = wp.sim.GEO_PLANE
658
+ actual_shape_b = -1
612
659
  else:
613
660
  type_a = geo.type[shape_a]
614
661
  type_b = geo.type[shape_b]
@@ -626,85 +673,118 @@ def count_contact_points(
626
673
 
627
674
  # determine how many contact points need to be evaluated
628
675
  num_contacts = 0
676
+ num_actual_contacts = 0
629
677
  if actual_type_a == wp.sim.GEO_SPHERE:
630
678
  num_contacts = 1
679
+ num_actual_contacts = 1
631
680
  elif actual_type_a == wp.sim.GEO_CAPSULE:
632
681
  if actual_type_b == wp.sim.GEO_PLANE:
633
682
  if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
634
683
  num_contacts = 2 # vertex-based collision for infinite plane
684
+ num_actual_contacts = 2
635
685
  else:
636
686
  num_contacts = 2 + 4 # vertex-based collision + plane edges
687
+ num_actual_contacts = 2 + 4
637
688
  elif actual_type_b == wp.sim.GEO_MESH:
638
689
  num_contacts_a = 2
639
690
  mesh_b = wp.mesh_get(geo.source[actual_shape_b])
640
691
  num_contacts_b = mesh_b.points.shape[0]
641
692
  num_contacts = num_contacts_a + num_contacts_b
693
+ if mesh_contact_max > 0:
694
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
695
+ num_actual_contacts = num_contacts_a + num_contacts_b
642
696
  else:
643
697
  num_contacts = 2
698
+ num_actual_contacts = 2
644
699
  elif actual_type_a == wp.sim.GEO_BOX:
645
700
  if actual_type_b == wp.sim.GEO_BOX:
646
701
  num_contacts = 24
702
+ num_actual_contacts = 24
647
703
  elif actual_type_b == wp.sim.GEO_MESH:
648
704
  num_contacts_a = 8
649
705
  mesh_b = wp.mesh_get(geo.source[actual_shape_b])
650
706
  num_contacts_b = mesh_b.points.shape[0]
651
707
  num_contacts = num_contacts_a + num_contacts_b
708
+ if mesh_contact_max > 0:
709
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
710
+ num_actual_contacts = num_contacts_a + num_contacts_b
652
711
  elif actual_type_b == wp.sim.GEO_PLANE:
653
712
  if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
654
713
  num_contacts = 8 # vertex-based collision
714
+ num_actual_contacts = 8
655
715
  else:
656
716
  num_contacts = 8 + 4 # vertex-based collision + plane edges
717
+ num_actual_contacts = 8 + 4
657
718
  else:
658
719
  num_contacts = 8
659
720
  elif actual_type_a == wp.sim.GEO_MESH:
660
721
  mesh_a = wp.mesh_get(geo.source[actual_shape_a])
661
722
  num_contacts_a = mesh_a.points.shape[0]
723
+ if mesh_contact_max > 0:
724
+ num_contacts_a = wp.min(mesh_contact_max, num_contacts_a)
662
725
  if actual_type_b == wp.sim.GEO_MESH:
663
726
  mesh_b = wp.mesh_get(geo.source[actual_shape_b])
664
727
  num_contacts_b = mesh_b.points.shape[0]
728
+ num_contacts = num_contacts_a + num_contacts_b
729
+ if mesh_contact_max > 0:
730
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
665
731
  else:
666
732
  num_contacts_b = 0
667
733
  num_contacts = num_contacts_a + num_contacts_b
734
+ num_actual_contacts = num_contacts_a + num_contacts_b
668
735
  elif actual_type_a == wp.sim.GEO_PLANE:
669
736
  return # no plane-plane contacts
670
737
  else:
671
- print("count_contact_points: unsupported geometry type")
672
- print(actual_type_a)
673
- print(actual_type_b)
738
+ wp.printf(
739
+ "count_contact_points: unsupported geometry type combination %d and %d\n", actual_type_a, actual_type_b
740
+ )
674
741
 
675
742
  wp.atomic_add(contact_count, 0, num_contacts)
743
+ wp.atomic_add(contact_count, 1, num_actual_contacts)
676
744
 
677
745
 
678
- @wp.kernel
746
+ @wp.kernel(enable_backward=False)
679
747
  def broadphase_collision_pairs(
680
748
  contact_pairs: wp.array(dtype=int, ndim=2),
681
749
  body_q: wp.array(dtype=wp.transform),
682
750
  shape_X_bs: wp.array(dtype=wp.transform),
683
751
  shape_body: wp.array(dtype=int),
752
+ body_mass: wp.array(dtype=float),
753
+ num_shapes: int,
684
754
  geo: ModelShapeGeometry,
685
755
  collision_radius: wp.array(dtype=float),
686
756
  rigid_contact_max: int,
687
757
  rigid_contact_margin: float,
758
+ mesh_contact_max: int,
759
+ iterate_mesh_vertices: bool,
688
760
  # outputs
689
761
  contact_count: wp.array(dtype=int),
690
762
  contact_shape0: wp.array(dtype=int),
691
763
  contact_shape1: wp.array(dtype=int),
692
764
  contact_point_id: wp.array(dtype=int),
765
+ contact_point_limit: wp.array(dtype=int),
693
766
  ):
694
767
  tid = wp.tid()
695
768
  shape_a = contact_pairs[tid, 0]
696
769
  shape_b = contact_pairs[tid, 1]
697
770
 
771
+ mass_a = 0.0
772
+ mass_b = 0.0
698
773
  rigid_a = shape_body[shape_a]
699
774
  if rigid_a == -1:
700
775
  X_ws_a = shape_X_bs[shape_a]
701
776
  else:
702
777
  X_ws_a = wp.transform_multiply(body_q[rigid_a], shape_X_bs[shape_a])
778
+ mass_a = body_mass[rigid_a]
703
779
  rigid_b = shape_body[shape_b]
704
780
  if rigid_b == -1:
705
781
  X_ws_b = shape_X_bs[shape_b]
706
782
  else:
707
783
  X_ws_b = wp.transform_multiply(body_q[rigid_b], shape_X_bs[shape_b])
784
+ mass_b = body_mass[rigid_b]
785
+ if mass_a == 0.0 and mass_b == 0.0:
786
+ # skip if both bodies are static
787
+ return
708
788
 
709
789
  type_a = geo.type[shape_a]
710
790
  type_b = geo.type[shape_b]
@@ -743,6 +823,9 @@ def broadphase_collision_pairs(
743
823
  if d > r_a + r_b + rigid_contact_margin:
744
824
  return
745
825
 
826
+ pair_index_ab = actual_shape_a * num_shapes + actual_shape_b
827
+ pair_index_ba = actual_shape_b * num_shapes + actual_shape_a
828
+
746
829
  # determine how many contact points need to be evaluated
747
830
  num_contacts = 0
748
831
  if actual_type_a == wp.sim.GEO_SPHERE:
@@ -756,7 +839,10 @@ def broadphase_collision_pairs(
756
839
  elif actual_type_b == wp.sim.GEO_MESH:
757
840
  num_contacts_a = 2
758
841
  mesh_b = wp.mesh_get(geo.source[actual_shape_b])
759
- num_contacts_b = mesh_b.points.shape[0]
842
+ if iterate_mesh_vertices:
843
+ num_contacts_b = mesh_b.points.shape[0]
844
+ else:
845
+ num_contacts_b = 0
760
846
  num_contacts = num_contacts_a + num_contacts_b
761
847
  index = wp.atomic_add(contact_count, 0, num_contacts)
762
848
  if index + num_contacts - 1 >= rigid_contact_max:
@@ -772,6 +858,10 @@ def broadphase_collision_pairs(
772
858
  contact_shape0[index + num_contacts_a + i] = actual_shape_b
773
859
  contact_shape1[index + num_contacts_a + i] = actual_shape_a
774
860
  contact_point_id[index + num_contacts_a + i] = i
861
+ contact_point_limit[pair_index_ab] = 2
862
+ if mesh_contact_max > 0:
863
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
864
+ contact_point_limit[pair_index_ba] = num_contacts_b
775
865
  return
776
866
  else:
777
867
  num_contacts = 2
@@ -786,16 +876,21 @@ def broadphase_collision_pairs(
786
876
  contact_shape0[index + i] = shape_a
787
877
  contact_shape1[index + i] = shape_b
788
878
  contact_point_id[index + i] = i
879
+ contact_point_limit[pair_index_ab] = 12
789
880
  # allocate contact points from box B against A
790
881
  for i in range(12):
791
882
  contact_shape0[index + 12 + i] = shape_b
792
883
  contact_shape1[index + 12 + i] = shape_a
793
884
  contact_point_id[index + 12 + i] = i
885
+ contact_point_limit[pair_index_ba] = 12
794
886
  return
795
887
  elif actual_type_b == wp.sim.GEO_MESH:
796
888
  num_contacts_a = 8
797
889
  mesh_b = wp.mesh_get(geo.source[actual_shape_b])
798
- num_contacts_b = mesh_b.points.shape[0]
890
+ if iterate_mesh_vertices:
891
+ num_contacts_b = mesh_b.points.shape[0]
892
+ else:
893
+ num_contacts_b = 0
799
894
  num_contacts = num_contacts_a + num_contacts_b
800
895
  index = wp.atomic_add(contact_count, 0, num_contacts)
801
896
  if index + num_contacts - 1 >= rigid_contact_max:
@@ -811,6 +906,11 @@ def broadphase_collision_pairs(
811
906
  contact_shape0[index + num_contacts_a + i] = actual_shape_b
812
907
  contact_shape1[index + num_contacts_a + i] = actual_shape_a
813
908
  contact_point_id[index + num_contacts_a + i] = i
909
+
910
+ contact_point_limit[pair_index_ab] = num_contacts_a
911
+ if mesh_contact_max > 0:
912
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
913
+ contact_point_limit[pair_index_ba] = num_contacts_b
814
914
  return
815
915
  elif actual_type_b == wp.sim.GEO_PLANE:
816
916
  if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
@@ -845,6 +945,12 @@ def broadphase_collision_pairs(
845
945
  contact_shape0[index + num_contacts_a + i] = actual_shape_b
846
946
  contact_shape1[index + num_contacts_a + i] = actual_shape_a
847
947
  contact_point_id[index + num_contacts_a + i] = i
948
+
949
+ if mesh_contact_max > 0:
950
+ num_contacts_a = wp.min(mesh_contact_max, num_contacts_a)
951
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
952
+ contact_point_limit[pair_index_ab] = num_contacts_a
953
+ contact_point_limit[pair_index_ba] = num_contacts_b
848
954
  return
849
955
  elif actual_type_a == wp.sim.GEO_PLANE:
850
956
  return # no plane-plane contacts
@@ -858,9 +964,12 @@ def broadphase_collision_pairs(
858
964
  return
859
965
  # allocate contact points
860
966
  for i in range(num_contacts):
861
- contact_shape0[index + i] = actual_shape_a
862
- contact_shape1[index + i] = actual_shape_b
863
- contact_point_id[index + i] = i
967
+ cp_index = index + i
968
+ contact_shape0[cp_index] = actual_shape_a
969
+ contact_shape1[cp_index] = actual_shape_b
970
+ contact_point_id[cp_index] = i
971
+ contact_point_limit[pair_index_ab] = num_contacts
972
+ contact_point_limit[pair_index_ba] = 0
864
973
 
865
974
 
866
975
  @wp.kernel
@@ -870,31 +979,37 @@ def handle_contact_pairs(
870
979
  shape_body: wp.array(dtype=int),
871
980
  geo: ModelShapeGeometry,
872
981
  rigid_contact_margin: float,
873
- body_com: wp.array(dtype=wp.vec3),
874
- contact_shape0: wp.array(dtype=int),
875
- contact_shape1: wp.array(dtype=int),
982
+ contact_broad_shape0: wp.array(dtype=int),
983
+ contact_broad_shape1: wp.array(dtype=int),
984
+ num_shapes: int,
876
985
  contact_point_id: wp.array(dtype=int),
877
- rigid_contact_count: wp.array(dtype=int),
986
+ contact_point_limit: wp.array(dtype=int),
878
987
  edge_sdf_iter: int,
879
988
  # outputs
880
- contact_body0: wp.array(dtype=int),
881
- contact_body1: wp.array(dtype=int),
989
+ contact_count: wp.array(dtype=int),
990
+ contact_shape0: wp.array(dtype=int),
991
+ contact_shape1: wp.array(dtype=int),
882
992
  contact_point0: wp.array(dtype=wp.vec3),
883
993
  contact_point1: wp.array(dtype=wp.vec3),
884
994
  contact_offset0: wp.array(dtype=wp.vec3),
885
995
  contact_offset1: wp.array(dtype=wp.vec3),
886
996
  contact_normal: wp.array(dtype=wp.vec3),
887
997
  contact_thickness: wp.array(dtype=float),
998
+ contact_pairwise_counter: wp.array(dtype=int),
999
+ contact_tids: wp.array(dtype=int),
888
1000
  ):
889
1001
  tid = wp.tid()
890
- if tid >= rigid_contact_count[0]:
891
- return
892
- shape_a = contact_shape0[tid]
893
- shape_b = contact_shape1[tid]
1002
+ shape_a = contact_broad_shape0[tid]
1003
+ shape_b = contact_broad_shape1[tid]
894
1004
  if shape_a == shape_b:
895
1005
  return
896
1006
 
897
1007
  point_id = contact_point_id[tid]
1008
+ pair_index = shape_a * num_shapes + shape_b
1009
+ contact_limit = contact_point_limit[pair_index]
1010
+ if contact_pairwise_counter[pair_index] >= contact_limit:
1011
+ # reached limit of contact points per contact pair
1012
+ return
898
1013
 
899
1014
  rigid_a = shape_body[shape_a]
900
1015
  X_wb_a = wp.transform_identity()
@@ -924,12 +1039,9 @@ def handle_contact_pairs(
924
1039
  thickness_b = geo.thickness[shape_b]
925
1040
  # is_solid_b = geo.is_solid[shape_b]
926
1041
 
927
- # fill in contact rigid body ids
928
- contact_body0[tid] = rigid_a
929
- contact_body1[tid] = rigid_b
930
-
931
1042
  distance = 1.0e6
932
1043
  u = float(0.0)
1044
+ thickness = thickness_a + thickness_b
933
1045
 
934
1046
  if geo_type_a == wp.sim.GEO_SPHERE:
935
1047
  p_a_world = wp.transform_get_translation(X_ws_a)
@@ -953,7 +1065,7 @@ def handle_contact_pairs(
953
1065
  face_u = float(0.0)
954
1066
  face_v = float(0.0)
955
1067
  sign = float(0.0)
956
- max_dist = (thickness_a + thickness_b + rigid_contact_margin) / geo_scale_b[0]
1068
+ max_dist = (thickness + rigid_contact_margin) / min_scale_b
957
1069
  res = wp.mesh_query_point_sign_normal(
958
1070
  mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
959
1071
  )
@@ -962,8 +1074,6 @@ def handle_contact_pairs(
962
1074
  shape_p = wp.cw_mul(shape_p, geo_scale_b)
963
1075
  p_b_world = wp.transform_point(X_ws_b, shape_p)
964
1076
  else:
965
- contact_shape0[tid] = -1
966
- contact_shape1[tid] = -1
967
1077
  return
968
1078
  elif geo_type_b == wp.sim.GEO_PLANE:
969
1079
  p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], wp.transform_point(X_sw_b, p_a_world))
@@ -1034,15 +1144,11 @@ def handle_contact_pairs(
1034
1144
  if plane_width > 0.0 and plane_length > 0.0:
1035
1145
  if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1036
1146
  # skip, we will evaluate the plane edge contact with the box later
1037
- contact_shape0[tid] = -1
1038
- contact_shape1[tid] = -1
1039
1147
  return
1040
1148
  # check whether the COM is above the plane
1041
1149
  # sign = wp.sign(wp.dot(wp.transform_get_translation(X_ws_a) - p_b_world, normal))
1042
1150
  # if sign < 0.0:
1043
1151
  # # the entire box is most likely below the plane
1044
- # contact_shape0[tid] = -1
1045
- # contact_shape1[tid] = -1
1046
1152
  # return
1047
1153
  # the contact point is within plane boundaries
1048
1154
  distance = wp.dot(diff, normal)
@@ -1064,8 +1170,6 @@ def handle_contact_pairs(
1064
1170
  query_b = wp.transform_point(X_sw_b, p_a_world)
1065
1171
  if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1066
1172
  # ensure that the closest point is actually inside the plane
1067
- contact_shape0[tid] = -1
1068
- contact_shape1[tid] = -1
1069
1173
  return
1070
1174
  diff = p_a_world - p_b_world
1071
1175
  com_a = wp.transform_get_translation(X_ws_a)
@@ -1111,7 +1215,7 @@ def handle_contact_pairs(
1111
1215
  edge0_b = wp.transform_point(X_sw_b, edge0_world)
1112
1216
  edge1_b = wp.transform_point(X_sw_b, edge1_world)
1113
1217
  max_iter = edge_sdf_iter
1114
- max_dist = (rigid_contact_margin + thickness_a + thickness_b) / min_scale_b
1218
+ max_dist = (rigid_contact_margin + thickness) / min_scale_b
1115
1219
  mesh_b = geo.source[shape_b]
1116
1220
  u = closest_edge_coordinate_mesh(
1117
1221
  mesh_b, wp.cw_div(edge0_b, geo_scale_b), wp.cw_div(edge1_b, geo_scale_b), max_iter, max_dist
@@ -1127,7 +1231,6 @@ def handle_contact_pairs(
1127
1231
  res = wp.mesh_query_point_sign_normal(
1128
1232
  mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1129
1233
  )
1130
-
1131
1234
  if res:
1132
1235
  shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1133
1236
  shape_p = wp.cw_mul(shape_p, geo_scale_b)
@@ -1138,8 +1241,6 @@ def handle_contact_pairs(
1138
1241
  normal = wp.normalize(diff)
1139
1242
  distance = wp.dot(diff, normal)
1140
1243
  else:
1141
- contact_shape0[tid] = -1
1142
- contact_shape1[tid] = -1
1143
1244
  return
1144
1245
 
1145
1246
  elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_CAPSULE:
@@ -1219,7 +1320,7 @@ def handle_contact_pairs(
1219
1320
  p_a_world = wp.transform_point(X_ws_a, query_a)
1220
1321
  query_b_local = wp.transform_point(X_sw_b, p_a_world)
1221
1322
  mesh_b = geo.source[shape_b]
1222
- max_dist = (rigid_contact_margin + thickness_a + thickness_b) / min_scale_b
1323
+ max_dist = (rigid_contact_margin + thickness) / min_scale_b
1223
1324
  face_index = int(0)
1224
1325
  face_u = float(0.0)
1225
1326
  face_v = float(0.0)
@@ -1237,8 +1338,6 @@ def handle_contact_pairs(
1237
1338
  normal = wp.normalize(diff_b) * sign
1238
1339
  distance = wp.dot(diff_b, normal)
1239
1340
  else:
1240
- contact_shape0[tid] = -1
1241
- contact_shape1[tid] = -1
1242
1341
  return
1243
1342
 
1244
1343
  elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_MESH:
@@ -1255,7 +1354,7 @@ def handle_contact_pairs(
1255
1354
  face_v = float(0.0)
1256
1355
  sign = float(0.0)
1257
1356
  min_scale = min(min_scale_a, min_scale_b)
1258
- max_dist = (rigid_contact_margin + thickness_a + thickness_b) / min_scale
1357
+ max_dist = (rigid_contact_margin + thickness) / min_scale
1259
1358
 
1260
1359
  res = wp.mesh_query_point_sign_normal(
1261
1360
  mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
@@ -1270,8 +1369,6 @@ def handle_contact_pairs(
1270
1369
  normal = wp.normalize(diff_b) * sign
1271
1370
  distance = wp.dot(diff_b, normal)
1272
1371
  else:
1273
- contact_shape0[tid] = -1
1274
- contact_shape1[tid] = -1
1275
1372
  return
1276
1373
 
1277
1374
  elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_PLANE:
@@ -1283,8 +1380,6 @@ def handle_contact_pairs(
1283
1380
  p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], query_b)
1284
1381
  p_b_world = wp.transform_point(X_ws_b, p_b_body)
1285
1382
  diff = p_a_world - p_b_world
1286
- normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1287
- distance = wp.length(diff)
1288
1383
 
1289
1384
  # if the plane is infinite or the point is within the plane we fix the normal to prevent intersections
1290
1385
  if (
@@ -1294,36 +1389,41 @@ def handle_contact_pairs(
1294
1389
  and wp.abs(query_b[2]) < geo_scale_b[1]
1295
1390
  ):
1296
1391
  normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1392
+ distance = wp.dot(diff, normal)
1297
1393
  else:
1298
1394
  normal = wp.normalize(diff)
1299
- distance = wp.dot(diff, normal)
1300
- # ignore extreme penetrations (e.g. when mesh is below the plane)
1301
- if distance < -rigid_contact_margin:
1302
- contact_shape0[tid] = -1
1303
- contact_shape1[tid] = -1
1304
- return
1395
+ distance = wp.dot(diff, normal)
1396
+ # ignore extreme penetrations (e.g. when mesh is below the plane)
1397
+ if distance < -rigid_contact_margin:
1398
+ return
1305
1399
 
1306
1400
  else:
1307
1401
  print("Unsupported geometry pair in collision handling")
1308
1402
  return
1309
1403
 
1310
- thickness = thickness_a + thickness_b
1311
1404
  d = distance - thickness
1312
1405
  if d < rigid_contact_margin:
1406
+ pair_contact_id = limited_counter_increment(
1407
+ contact_pairwise_counter, pair_index, contact_tids, tid, contact_limit
1408
+ )
1409
+ if pair_contact_id == -1:
1410
+ # wp.printf("Reached contact point limit %d >= %d for shape pair %d and %d (pair_index: %d)\n",
1411
+ # contact_pairwise_counter[pair_index], contact_limit, shape_a, shape_b, pair_index)
1412
+ # reached contact point limit
1413
+ return
1414
+ index = limited_counter_increment(contact_count, 0, contact_tids, tid, -1)
1415
+ contact_shape0[index] = shape_a
1416
+ contact_shape1[index] = shape_b
1313
1417
  # transform from world into body frame (so the contact point includes the shape transform)
1314
- contact_point0[tid] = wp.transform_point(X_bw_a, p_a_world)
1315
- contact_point1[tid] = wp.transform_point(X_bw_b, p_b_world)
1316
- contact_offset0[tid] = wp.transform_vector(X_bw_a, -thickness_a * normal)
1317
- contact_offset1[tid] = wp.transform_vector(X_bw_b, thickness_b * normal)
1318
- contact_normal[tid] = normal
1319
- contact_thickness[tid] = thickness
1320
- # wp.printf("distance: %f\tnormal: %.3f %.3f %.3f\tp_a_world: %.3f %.3f %.3f\tp_b_world: %.3f %.3f %.3f\n", distance, normal[0], normal[1], normal[2], p_a_world[0], p_a_world[1], p_a_world[2], p_b_world[0], p_b_world[1], p_b_world[2])
1321
- else:
1322
- contact_shape0[tid] = -1
1323
- contact_shape1[tid] = -1
1418
+ contact_point0[index] = wp.transform_point(X_bw_a, p_a_world)
1419
+ contact_point1[index] = wp.transform_point(X_bw_b, p_b_world)
1420
+ contact_offset0[index] = wp.transform_vector(X_bw_a, -thickness_a * normal)
1421
+ contact_offset1[index] = wp.transform_vector(X_bw_b, thickness_b * normal)
1422
+ contact_normal[index] = normal
1423
+ contact_thickness[index] = thickness
1324
1424
 
1325
1425
 
1326
- def collide(model, state, edge_sdf_iter: int = 10):
1426
+ def collide(model, state, edge_sdf_iter: int = 10, iterate_mesh_vertices: bool = True, requires_grad: bool = None):
1327
1427
  """
1328
1428
  Generates contact points for the particles and rigid bodies in the model,
1329
1429
  to be used in the contact dynamics kernel of the integrator.
@@ -1332,114 +1432,159 @@ def collide(model, state, edge_sdf_iter: int = 10):
1332
1432
  model: the model to be simulated
1333
1433
  state: the state of the model
1334
1434
  edge_sdf_iter: number of search iterations for finding closest contact points between edges and SDF
1435
+ iterate_mesh_vertices: whether to iterate over all vertices of a mesh for contact generation (used for capsule/box <> mesh collision)
1436
+ requires_grad: whether to duplicate contact arrays for gradient computation (if None uses model.requires_grad)
1335
1437
  """
1336
1438
 
1337
- # generate soft contacts for particles and shapes except ground plane (last shape)
1338
- if model.particle_count and model.shape_count > 1:
1339
- # clear old count
1340
- model.soft_contact_count.zero_()
1341
- wp.launch(
1342
- kernel=create_soft_contacts,
1343
- dim=(model.particle_count, model.shape_count - 1),
1344
- inputs=[
1345
- state.particle_q,
1346
- model.particle_radius,
1347
- model.particle_flags,
1348
- state.body_q,
1349
- model.shape_transform,
1350
- model.shape_body,
1351
- model.shape_geo,
1352
- model.soft_contact_margin,
1353
- model.soft_contact_max,
1354
- ],
1355
- outputs=[
1356
- model.soft_contact_count,
1357
- model.soft_contact_particle,
1358
- model.soft_contact_shape,
1359
- model.soft_contact_body_pos,
1360
- model.soft_contact_body_vel,
1361
- model.soft_contact_normal,
1362
- ],
1363
- device=model.device,
1364
- )
1439
+ if requires_grad is None:
1440
+ requires_grad = model.requires_grad
1441
+
1442
+ with wp.ScopedTimer("collide", False):
1443
+
1444
+ # generate soft contacts for particles and shapes except ground plane (last shape)
1445
+ if model.particle_count and model.shape_count > 1:
1446
+ if requires_grad:
1447
+ model.soft_contact_body_pos = wp.clone(model.soft_contact_body_pos)
1448
+ model.soft_contact_body_vel = wp.clone(model.soft_contact_body_vel)
1449
+ model.soft_contact_normal = wp.clone(model.soft_contact_normal)
1450
+ # clear old count
1451
+ model.soft_contact_count.zero_()
1452
+ wp.launch(
1453
+ kernel=create_soft_contacts,
1454
+ dim=model.particle_count * (model.shape_count - 1),
1455
+ inputs=[
1456
+ state.particle_q,
1457
+ model.particle_radius,
1458
+ model.particle_flags,
1459
+ state.body_q,
1460
+ model.shape_transform,
1461
+ model.shape_body,
1462
+ model.shape_geo,
1463
+ model.soft_contact_margin,
1464
+ model.soft_contact_max,
1465
+ model.shape_count - 1,
1466
+ ],
1467
+ outputs=[
1468
+ model.soft_contact_count,
1469
+ model.soft_contact_particle,
1470
+ model.soft_contact_shape,
1471
+ model.soft_contact_body_pos,
1472
+ model.soft_contact_body_vel,
1473
+ model.soft_contact_normal,
1474
+ model.soft_contact_tids,
1475
+ ],
1476
+ device=model.device,
1477
+ )
1365
1478
 
1366
- # clear old count
1367
- model.rigid_contact_count.zero_()
1368
-
1369
- if model.shape_contact_pair_count:
1370
- wp.launch(
1371
- kernel=broadphase_collision_pairs,
1372
- dim=model.shape_contact_pair_count,
1373
- inputs=[
1374
- model.shape_contact_pairs,
1375
- state.body_q,
1376
- model.shape_transform,
1377
- model.shape_body,
1378
- model.shape_geo,
1379
- model.shape_collision_radius,
1380
- model.rigid_contact_max,
1381
- model.rigid_contact_margin,
1382
- ],
1383
- outputs=[
1384
- model.rigid_contact_count,
1385
- model.rigid_contact_shape0,
1386
- model.rigid_contact_shape1,
1387
- model.rigid_contact_point_id,
1388
- ],
1389
- device=model.device,
1390
- record_tape=False,
1391
- )
1479
+ if model.shape_contact_pair_count or model.ground and model.shape_ground_contact_pair_count:
1480
+ # clear old count
1481
+ model.rigid_contact_count.zero_()
1482
+
1483
+ model.rigid_contact_broad_shape0.fill_(-1)
1484
+ model.rigid_contact_broad_shape1.fill_(-1)
1485
+
1486
+ if model.shape_contact_pair_count:
1487
+ wp.launch(
1488
+ kernel=broadphase_collision_pairs,
1489
+ dim=model.shape_contact_pair_count,
1490
+ inputs=[
1491
+ model.shape_contact_pairs,
1492
+ state.body_q,
1493
+ model.shape_transform,
1494
+ model.shape_body,
1495
+ model.body_mass,
1496
+ model.shape_count,
1497
+ model.shape_geo,
1498
+ model.shape_collision_radius,
1499
+ model.rigid_contact_max,
1500
+ model.rigid_contact_margin,
1501
+ model.rigid_mesh_contact_max,
1502
+ iterate_mesh_vertices,
1503
+ ],
1504
+ outputs=[
1505
+ model.rigid_contact_count,
1506
+ model.rigid_contact_broad_shape0,
1507
+ model.rigid_contact_broad_shape1,
1508
+ model.rigid_contact_point_id,
1509
+ model.rigid_contact_point_limit,
1510
+ ],
1511
+ device=model.device,
1512
+ record_tape=False,
1513
+ )
1392
1514
 
1393
- if model.ground and model.shape_ground_contact_pair_count:
1394
- wp.launch(
1395
- kernel=broadphase_collision_pairs,
1396
- dim=model.shape_ground_contact_pair_count,
1397
- inputs=[
1398
- model.shape_ground_contact_pairs,
1399
- state.body_q,
1400
- model.shape_transform,
1401
- model.shape_body,
1402
- model.shape_geo,
1403
- model.shape_collision_radius,
1404
- model.rigid_contact_max,
1405
- model.rigid_contact_margin,
1406
- ],
1407
- outputs=[
1408
- model.rigid_contact_count,
1409
- model.rigid_contact_shape0,
1410
- model.rigid_contact_shape1,
1411
- model.rigid_contact_point_id,
1412
- ],
1413
- device=model.device,
1414
- record_tape=False,
1415
- )
1515
+ if model.ground and model.shape_ground_contact_pair_count:
1516
+ wp.launch(
1517
+ kernel=broadphase_collision_pairs,
1518
+ dim=model.shape_ground_contact_pair_count,
1519
+ inputs=[
1520
+ model.shape_ground_contact_pairs,
1521
+ state.body_q,
1522
+ model.shape_transform,
1523
+ model.shape_body,
1524
+ model.body_mass,
1525
+ model.shape_count,
1526
+ model.shape_geo,
1527
+ model.shape_collision_radius,
1528
+ model.rigid_contact_max,
1529
+ model.rigid_contact_margin,
1530
+ model.rigid_mesh_contact_max,
1531
+ iterate_mesh_vertices,
1532
+ ],
1533
+ outputs=[
1534
+ model.rigid_contact_count,
1535
+ model.rigid_contact_broad_shape0,
1536
+ model.rigid_contact_broad_shape1,
1537
+ model.rigid_contact_point_id,
1538
+ model.rigid_contact_point_limit,
1539
+ ],
1540
+ device=model.device,
1541
+ record_tape=False,
1542
+ )
1416
1543
 
1417
- if model.shape_contact_pair_count or model.ground and model.shape_ground_contact_pair_count:
1418
- wp.launch(
1419
- kernel=handle_contact_pairs,
1420
- dim=model.rigid_contact_max,
1421
- inputs=[
1422
- state.body_q,
1423
- model.shape_transform,
1424
- model.shape_body,
1425
- model.shape_geo,
1426
- model.rigid_contact_margin,
1427
- model.body_com,
1428
- model.rigid_contact_shape0,
1429
- model.rigid_contact_shape1,
1430
- model.rigid_contact_point_id,
1431
- model.rigid_contact_count,
1432
- edge_sdf_iter,
1433
- ],
1434
- outputs=[
1435
- model.rigid_contact_body0,
1436
- model.rigid_contact_body1,
1437
- model.rigid_contact_point0,
1438
- model.rigid_contact_point1,
1439
- model.rigid_contact_offset0,
1440
- model.rigid_contact_offset1,
1441
- model.rigid_contact_normal,
1442
- model.rigid_contact_thickness,
1443
- ],
1444
- device=model.device,
1445
- )
1544
+ if model.shape_contact_pair_count or model.ground and model.shape_ground_contact_pair_count:
1545
+
1546
+ model.rigid_contact_count.zero_()
1547
+ model.rigid_contact_pairwise_counter.zero_()
1548
+ model.rigid_contact_tids.zero_()
1549
+ model.rigid_contact_shape0.fill_(-1)
1550
+ model.rigid_contact_shape1.fill_(-1)
1551
+
1552
+ if requires_grad:
1553
+ model.rigid_contact_point0 = wp.clone(model.rigid_contact_point0)
1554
+ model.rigid_contact_point1 = wp.clone(model.rigid_contact_point1)
1555
+ model.rigid_contact_offset0 = wp.clone(model.rigid_contact_offset0)
1556
+ model.rigid_contact_offset1 = wp.clone(model.rigid_contact_offset1)
1557
+ model.rigid_contact_normal = wp.clone(model.rigid_contact_normal)
1558
+ model.rigid_contact_thickness = wp.clone(model.rigid_contact_thickness)
1559
+
1560
+ wp.launch(
1561
+ kernel=handle_contact_pairs,
1562
+ dim=model.rigid_contact_max,
1563
+ inputs=[
1564
+ state.body_q,
1565
+ model.shape_transform,
1566
+ model.shape_body,
1567
+ model.shape_geo,
1568
+ model.rigid_contact_margin,
1569
+ model.rigid_contact_broad_shape0,
1570
+ model.rigid_contact_broad_shape1,
1571
+ model.shape_count,
1572
+ model.rigid_contact_point_id,
1573
+ model.rigid_contact_point_limit,
1574
+ edge_sdf_iter,
1575
+ ],
1576
+ outputs=[
1577
+ model.rigid_contact_count,
1578
+ model.rigid_contact_shape0,
1579
+ model.rigid_contact_shape1,
1580
+ model.rigid_contact_point0,
1581
+ model.rigid_contact_point1,
1582
+ model.rigid_contact_offset0,
1583
+ model.rigid_contact_offset1,
1584
+ model.rigid_contact_normal,
1585
+ model.rigid_contact_thickness,
1586
+ model.rigid_contact_pairwise_counter,
1587
+ model.rigid_contact_tids,
1588
+ ],
1589
+ device=model.device,
1590
+ )