warp-lang 1.0.1__py3-none-manylinux2014_x86_64.whl → 1.1.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 (346) hide show
  1. warp/__init__.py +108 -97
  2. warp/__init__.pyi +1 -1
  3. warp/bin/warp-clang.so +0 -0
  4. warp/bin/warp.so +0 -0
  5. warp/build.py +115 -113
  6. warp/build_dll.py +383 -375
  7. warp/builtins.py +3425 -3354
  8. warp/codegen.py +2878 -2792
  9. warp/config.py +40 -36
  10. warp/constants.py +45 -45
  11. warp/context.py +5194 -5102
  12. warp/dlpack.py +442 -442
  13. warp/examples/__init__.py +16 -16
  14. warp/examples/assets/bear.usd +0 -0
  15. warp/examples/assets/bunny.usd +0 -0
  16. warp/examples/assets/cartpole.urdf +110 -110
  17. warp/examples/assets/crazyflie.usd +0 -0
  18. warp/examples/assets/cube.usd +0 -0
  19. warp/examples/assets/nv_ant.xml +92 -92
  20. warp/examples/assets/nv_humanoid.xml +183 -183
  21. warp/examples/assets/quadruped.urdf +267 -267
  22. warp/examples/assets/rocks.nvdb +0 -0
  23. warp/examples/assets/rocks.usd +0 -0
  24. warp/examples/assets/sphere.usd +0 -0
  25. warp/examples/benchmarks/benchmark_api.py +383 -383
  26. warp/examples/benchmarks/benchmark_cloth.py +278 -279
  27. warp/examples/benchmarks/benchmark_cloth_cupy.py +88 -88
  28. warp/examples/benchmarks/benchmark_cloth_jax.py +97 -100
  29. warp/examples/benchmarks/benchmark_cloth_numba.py +146 -142
  30. warp/examples/benchmarks/benchmark_cloth_numpy.py +77 -77
  31. warp/examples/benchmarks/benchmark_cloth_pytorch.py +86 -86
  32. warp/examples/benchmarks/benchmark_cloth_taichi.py +112 -112
  33. warp/examples/benchmarks/benchmark_cloth_warp.py +146 -146
  34. warp/examples/benchmarks/benchmark_launches.py +295 -295
  35. warp/examples/browse.py +29 -28
  36. warp/examples/core/example_dem.py +234 -221
  37. warp/examples/core/example_fluid.py +293 -267
  38. warp/examples/core/example_graph_capture.py +144 -129
  39. warp/examples/core/example_marching_cubes.py +188 -176
  40. warp/examples/core/example_mesh.py +174 -154
  41. warp/examples/core/example_mesh_intersect.py +205 -193
  42. warp/examples/core/example_nvdb.py +176 -169
  43. warp/examples/core/example_raycast.py +105 -89
  44. warp/examples/core/example_raymarch.py +199 -178
  45. warp/examples/core/example_render_opengl.py +185 -141
  46. warp/examples/core/example_sph.py +405 -389
  47. warp/examples/core/example_torch.py +222 -181
  48. warp/examples/core/example_wave.py +263 -249
  49. warp/examples/fem/bsr_utils.py +378 -380
  50. warp/examples/fem/example_apic_fluid.py +407 -391
  51. warp/examples/fem/example_convection_diffusion.py +182 -168
  52. warp/examples/fem/example_convection_diffusion_dg.py +219 -209
  53. warp/examples/fem/example_convection_diffusion_dg0.py +204 -194
  54. warp/examples/fem/example_deformed_geometry.py +177 -159
  55. warp/examples/fem/example_diffusion.py +201 -173
  56. warp/examples/fem/example_diffusion_3d.py +177 -152
  57. warp/examples/fem/example_diffusion_mgpu.py +221 -214
  58. warp/examples/fem/example_mixed_elasticity.py +244 -222
  59. warp/examples/fem/example_navier_stokes.py +259 -243
  60. warp/examples/fem/example_stokes.py +220 -192
  61. warp/examples/fem/example_stokes_transfer.py +265 -249
  62. warp/examples/fem/mesh_utils.py +133 -109
  63. warp/examples/fem/plot_utils.py +292 -287
  64. warp/examples/optim/example_bounce.py +260 -248
  65. warp/examples/optim/example_cloth_throw.py +222 -210
  66. warp/examples/optim/example_diffray.py +566 -535
  67. warp/examples/optim/example_drone.py +864 -835
  68. warp/examples/optim/example_inverse_kinematics.py +176 -169
  69. warp/examples/optim/example_inverse_kinematics_torch.py +185 -170
  70. warp/examples/optim/example_spring_cage.py +239 -234
  71. warp/examples/optim/example_trajectory.py +223 -201
  72. warp/examples/optim/example_walker.py +306 -292
  73. warp/examples/sim/example_cartpole.py +139 -128
  74. warp/examples/sim/example_cloth.py +196 -184
  75. warp/examples/sim/example_granular.py +124 -113
  76. warp/examples/sim/example_granular_collision_sdf.py +197 -185
  77. warp/examples/sim/example_jacobian_ik.py +236 -213
  78. warp/examples/sim/example_particle_chain.py +118 -106
  79. warp/examples/sim/example_quadruped.py +193 -179
  80. warp/examples/sim/example_rigid_chain.py +197 -189
  81. warp/examples/sim/example_rigid_contact.py +189 -176
  82. warp/examples/sim/example_rigid_force.py +127 -126
  83. warp/examples/sim/example_rigid_gyroscopic.py +109 -97
  84. warp/examples/sim/example_rigid_soft_contact.py +134 -124
  85. warp/examples/sim/example_soft_body.py +190 -178
  86. warp/fabric.py +337 -335
  87. warp/fem/__init__.py +60 -27
  88. warp/fem/cache.py +401 -388
  89. warp/fem/dirichlet.py +178 -179
  90. warp/fem/domain.py +262 -263
  91. warp/fem/field/__init__.py +100 -101
  92. warp/fem/field/field.py +148 -149
  93. warp/fem/field/nodal_field.py +298 -299
  94. warp/fem/field/restriction.py +22 -21
  95. warp/fem/field/test.py +180 -181
  96. warp/fem/field/trial.py +183 -183
  97. warp/fem/geometry/__init__.py +15 -19
  98. warp/fem/geometry/closest_point.py +69 -70
  99. warp/fem/geometry/deformed_geometry.py +270 -271
  100. warp/fem/geometry/element.py +744 -744
  101. warp/fem/geometry/geometry.py +184 -186
  102. warp/fem/geometry/grid_2d.py +380 -373
  103. warp/fem/geometry/grid_3d.py +441 -435
  104. warp/fem/geometry/hexmesh.py +953 -953
  105. warp/fem/geometry/partition.py +374 -376
  106. warp/fem/geometry/quadmesh_2d.py +532 -532
  107. warp/fem/geometry/tetmesh.py +840 -840
  108. warp/fem/geometry/trimesh_2d.py +577 -577
  109. warp/fem/integrate.py +1630 -1615
  110. warp/fem/operator.py +190 -191
  111. warp/fem/polynomial.py +214 -213
  112. warp/fem/quadrature/__init__.py +2 -2
  113. warp/fem/quadrature/pic_quadrature.py +243 -245
  114. warp/fem/quadrature/quadrature.py +295 -294
  115. warp/fem/space/__init__.py +294 -292
  116. warp/fem/space/basis_space.py +488 -489
  117. warp/fem/space/collocated_function_space.py +100 -105
  118. warp/fem/space/dof_mapper.py +236 -236
  119. warp/fem/space/function_space.py +148 -145
  120. warp/fem/space/grid_2d_function_space.py +267 -267
  121. warp/fem/space/grid_3d_function_space.py +305 -306
  122. warp/fem/space/hexmesh_function_space.py +350 -352
  123. warp/fem/space/partition.py +350 -350
  124. warp/fem/space/quadmesh_2d_function_space.py +368 -369
  125. warp/fem/space/restriction.py +158 -160
  126. warp/fem/space/shape/__init__.py +13 -15
  127. warp/fem/space/shape/cube_shape_function.py +738 -738
  128. warp/fem/space/shape/shape_function.py +102 -103
  129. warp/fem/space/shape/square_shape_function.py +611 -611
  130. warp/fem/space/shape/tet_shape_function.py +565 -567
  131. warp/fem/space/shape/triangle_shape_function.py +429 -429
  132. warp/fem/space/tetmesh_function_space.py +294 -292
  133. warp/fem/space/topology.py +297 -295
  134. warp/fem/space/trimesh_2d_function_space.py +223 -221
  135. warp/fem/types.py +77 -77
  136. warp/fem/utils.py +495 -495
  137. warp/jax.py +166 -141
  138. warp/jax_experimental.py +341 -339
  139. warp/native/array.h +1072 -1025
  140. warp/native/builtin.h +1560 -1560
  141. warp/native/bvh.cpp +398 -398
  142. warp/native/bvh.cu +525 -525
  143. warp/native/bvh.h +429 -429
  144. warp/native/clang/clang.cpp +495 -464
  145. warp/native/crt.cpp +31 -31
  146. warp/native/crt.h +334 -334
  147. warp/native/cuda_crt.h +1049 -1049
  148. warp/native/cuda_util.cpp +549 -540
  149. warp/native/cuda_util.h +288 -203
  150. warp/native/cutlass_gemm.cpp +34 -34
  151. warp/native/cutlass_gemm.cu +372 -372
  152. warp/native/error.cpp +66 -66
  153. warp/native/error.h +27 -27
  154. warp/native/fabric.h +228 -228
  155. warp/native/hashgrid.cpp +301 -278
  156. warp/native/hashgrid.cu +78 -77
  157. warp/native/hashgrid.h +227 -227
  158. warp/native/initializer_array.h +32 -32
  159. warp/native/intersect.h +1204 -1204
  160. warp/native/intersect_adj.h +365 -365
  161. warp/native/intersect_tri.h +322 -322
  162. warp/native/marching.cpp +2 -2
  163. warp/native/marching.cu +497 -497
  164. warp/native/marching.h +2 -2
  165. warp/native/mat.h +1498 -1498
  166. warp/native/matnn.h +333 -333
  167. warp/native/mesh.cpp +203 -203
  168. warp/native/mesh.cu +293 -293
  169. warp/native/mesh.h +1887 -1887
  170. warp/native/nanovdb/NanoVDB.h +4782 -4782
  171. warp/native/nanovdb/PNanoVDB.h +2553 -2553
  172. warp/native/nanovdb/PNanoVDBWrite.h +294 -294
  173. warp/native/noise.h +850 -850
  174. warp/native/quat.h +1084 -1084
  175. warp/native/rand.h +299 -299
  176. warp/native/range.h +108 -108
  177. warp/native/reduce.cpp +156 -156
  178. warp/native/reduce.cu +348 -348
  179. warp/native/runlength_encode.cpp +61 -61
  180. warp/native/runlength_encode.cu +46 -46
  181. warp/native/scan.cpp +30 -30
  182. warp/native/scan.cu +36 -36
  183. warp/native/scan.h +7 -7
  184. warp/native/solid_angle.h +442 -442
  185. warp/native/sort.cpp +94 -94
  186. warp/native/sort.cu +97 -97
  187. warp/native/sort.h +14 -14
  188. warp/native/sparse.cpp +337 -337
  189. warp/native/sparse.cu +544 -544
  190. warp/native/spatial.h +630 -630
  191. warp/native/svd.h +562 -562
  192. warp/native/temp_buffer.h +30 -30
  193. warp/native/vec.h +1132 -1132
  194. warp/native/volume.cpp +297 -297
  195. warp/native/volume.cu +32 -32
  196. warp/native/volume.h +538 -538
  197. warp/native/volume_builder.cu +425 -425
  198. warp/native/volume_builder.h +19 -19
  199. warp/native/warp.cpp +1057 -1052
  200. warp/native/warp.cu +2943 -2828
  201. warp/native/warp.h +313 -305
  202. warp/optim/__init__.py +9 -9
  203. warp/optim/adam.py +120 -120
  204. warp/optim/linear.py +1104 -939
  205. warp/optim/sgd.py +104 -92
  206. warp/render/__init__.py +10 -10
  207. warp/render/render_opengl.py +3217 -3204
  208. warp/render/render_usd.py +768 -749
  209. warp/render/utils.py +152 -150
  210. warp/sim/__init__.py +52 -59
  211. warp/sim/articulation.py +685 -685
  212. warp/sim/collide.py +1594 -1590
  213. warp/sim/import_mjcf.py +489 -481
  214. warp/sim/import_snu.py +220 -221
  215. warp/sim/import_urdf.py +536 -516
  216. warp/sim/import_usd.py +887 -881
  217. warp/sim/inertia.py +316 -317
  218. warp/sim/integrator.py +234 -233
  219. warp/sim/integrator_euler.py +1956 -1956
  220. warp/sim/integrator_featherstone.py +1910 -1991
  221. warp/sim/integrator_xpbd.py +3294 -3312
  222. warp/sim/model.py +4473 -4314
  223. warp/sim/particles.py +113 -112
  224. warp/sim/render.py +417 -403
  225. warp/sim/utils.py +413 -410
  226. warp/sparse.py +1227 -1227
  227. warp/stubs.py +2109 -2469
  228. warp/tape.py +1162 -225
  229. warp/tests/__init__.py +1 -1
  230. warp/tests/__main__.py +4 -4
  231. warp/tests/assets/torus.usda +105 -105
  232. warp/tests/aux_test_class_kernel.py +26 -26
  233. warp/tests/aux_test_compile_consts_dummy.py +10 -10
  234. warp/tests/aux_test_conditional_unequal_types_kernels.py +21 -21
  235. warp/tests/aux_test_dependent.py +22 -22
  236. warp/tests/aux_test_grad_customs.py +23 -23
  237. warp/tests/aux_test_reference.py +11 -11
  238. warp/tests/aux_test_reference_reference.py +10 -10
  239. warp/tests/aux_test_square.py +17 -17
  240. warp/tests/aux_test_unresolved_func.py +14 -14
  241. warp/tests/aux_test_unresolved_symbol.py +14 -14
  242. warp/tests/disabled_kinematics.py +239 -239
  243. warp/tests/run_coverage_serial.py +31 -31
  244. warp/tests/test_adam.py +157 -157
  245. warp/tests/test_arithmetic.py +1124 -1124
  246. warp/tests/test_array.py +2417 -2326
  247. warp/tests/test_array_reduce.py +150 -150
  248. warp/tests/test_async.py +668 -656
  249. warp/tests/test_atomic.py +141 -141
  250. warp/tests/test_bool.py +204 -149
  251. warp/tests/test_builtins_resolution.py +1292 -1292
  252. warp/tests/test_bvh.py +164 -171
  253. warp/tests/test_closest_point_edge_edge.py +228 -228
  254. warp/tests/test_codegen.py +566 -553
  255. warp/tests/test_compile_consts.py +97 -101
  256. warp/tests/test_conditional.py +246 -246
  257. warp/tests/test_copy.py +232 -215
  258. warp/tests/test_ctypes.py +632 -632
  259. warp/tests/test_dense.py +67 -67
  260. warp/tests/test_devices.py +91 -98
  261. warp/tests/test_dlpack.py +530 -529
  262. warp/tests/test_examples.py +400 -378
  263. warp/tests/test_fabricarray.py +955 -955
  264. warp/tests/test_fast_math.py +62 -54
  265. warp/tests/test_fem.py +1277 -1278
  266. warp/tests/test_fp16.py +130 -130
  267. warp/tests/test_func.py +338 -337
  268. warp/tests/test_generics.py +571 -571
  269. warp/tests/test_grad.py +746 -640
  270. warp/tests/test_grad_customs.py +333 -336
  271. warp/tests/test_hash_grid.py +210 -164
  272. warp/tests/test_import.py +39 -39
  273. warp/tests/test_indexedarray.py +1134 -1134
  274. warp/tests/test_intersect.py +67 -67
  275. warp/tests/test_jax.py +307 -307
  276. warp/tests/test_large.py +167 -164
  277. warp/tests/test_launch.py +354 -354
  278. warp/tests/test_lerp.py +261 -261
  279. warp/tests/test_linear_solvers.py +191 -171
  280. warp/tests/test_lvalue.py +421 -493
  281. warp/tests/test_marching_cubes.py +65 -65
  282. warp/tests/test_mat.py +1801 -1827
  283. warp/tests/test_mat_lite.py +115 -115
  284. warp/tests/test_mat_scalar_ops.py +2907 -2889
  285. warp/tests/test_math.py +126 -193
  286. warp/tests/test_matmul.py +500 -499
  287. warp/tests/test_matmul_lite.py +410 -410
  288. warp/tests/test_mempool.py +188 -190
  289. warp/tests/test_mesh.py +284 -324
  290. warp/tests/test_mesh_query_aabb.py +228 -241
  291. warp/tests/test_mesh_query_point.py +692 -702
  292. warp/tests/test_mesh_query_ray.py +292 -303
  293. warp/tests/test_mlp.py +276 -276
  294. warp/tests/test_model.py +110 -110
  295. warp/tests/test_modules_lite.py +39 -39
  296. warp/tests/test_multigpu.py +163 -163
  297. warp/tests/test_noise.py +248 -248
  298. warp/tests/test_operators.py +250 -250
  299. warp/tests/test_options.py +123 -125
  300. warp/tests/test_peer.py +133 -137
  301. warp/tests/test_pinned.py +78 -78
  302. warp/tests/test_print.py +54 -54
  303. warp/tests/test_quat.py +2086 -2086
  304. warp/tests/test_rand.py +288 -288
  305. warp/tests/test_reload.py +217 -217
  306. warp/tests/test_rounding.py +179 -179
  307. warp/tests/test_runlength_encode.py +190 -190
  308. warp/tests/test_sim_grad.py +243 -0
  309. warp/tests/test_sim_kinematics.py +91 -97
  310. warp/tests/test_smoothstep.py +168 -168
  311. warp/tests/test_snippet.py +305 -266
  312. warp/tests/test_sparse.py +468 -460
  313. warp/tests/test_spatial.py +2148 -2148
  314. warp/tests/test_streams.py +486 -473
  315. warp/tests/test_struct.py +710 -675
  316. warp/tests/test_tape.py +173 -148
  317. warp/tests/test_torch.py +743 -743
  318. warp/tests/test_transient_module.py +87 -87
  319. warp/tests/test_types.py +556 -659
  320. warp/tests/test_utils.py +490 -499
  321. warp/tests/test_vec.py +1264 -1268
  322. warp/tests/test_vec_lite.py +73 -73
  323. warp/tests/test_vec_scalar_ops.py +2099 -2099
  324. warp/tests/test_verify_fp.py +94 -94
  325. warp/tests/test_volume.py +737 -736
  326. warp/tests/test_volume_write.py +255 -265
  327. warp/tests/unittest_serial.py +37 -37
  328. warp/tests/unittest_suites.py +363 -359
  329. warp/tests/unittest_utils.py +603 -578
  330. warp/tests/unused_test_misc.py +71 -71
  331. warp/tests/walkthrough_debug.py +85 -85
  332. warp/thirdparty/appdirs.py +598 -598
  333. warp/thirdparty/dlpack.py +143 -143
  334. warp/thirdparty/unittest_parallel.py +566 -561
  335. warp/torch.py +321 -295
  336. warp/types.py +4504 -4450
  337. warp/utils.py +1008 -821
  338. {warp_lang-1.0.1.dist-info → warp_lang-1.1.0.dist-info}/LICENSE.md +126 -126
  339. {warp_lang-1.0.1.dist-info → warp_lang-1.1.0.dist-info}/METADATA +338 -400
  340. warp_lang-1.1.0.dist-info/RECORD +352 -0
  341. warp/examples/assets/cube.usda +0 -42
  342. warp/examples/assets/sphere.usda +0 -56
  343. warp/examples/assets/torus.usda +0 -105
  344. warp_lang-1.0.1.dist-info/RECORD +0 -352
  345. {warp_lang-1.0.1.dist-info → warp_lang-1.1.0.dist-info}/WHEEL +0 -0
  346. {warp_lang-1.0.1.dist-info → warp_lang-1.1.0.dist-info}/top_level.txt +0 -0
warp/sim/collide.py CHANGED
@@ -1,1590 +1,1594 @@
1
- # Copyright (c) 2022 NVIDIA CORPORATION. All rights reserved.
2
- # NVIDIA CORPORATION and its licensors retain all intellectual property
3
- # and proprietary rights in and to this software, related documentation
4
- # and any modifications thereto. Any use, reproduction, disclosure or
5
- # distribution of this software and related documentation without an express
6
- # license agreement from NVIDIA CORPORATION is strictly prohibited.
7
-
8
- """
9
- Collision handling functions and kernels.
10
- """
11
-
12
- import warp as wp
13
- from .model import PARTICLE_FLAG_ACTIVE, ModelShapeGeometry
14
-
15
-
16
- @wp.func
17
- def triangle_closest_point_barycentric(a: wp.vec3, b: wp.vec3, c: wp.vec3, p: wp.vec3):
18
- ab = b - a
19
- ac = c - a
20
- ap = p - a
21
-
22
- d1 = wp.dot(ab, ap)
23
- d2 = wp.dot(ac, ap)
24
-
25
- if d1 <= 0.0 and d2 <= 0.0:
26
- return wp.vec3(1.0, 0.0, 0.0)
27
-
28
- bp = p - b
29
- d3 = wp.dot(ab, bp)
30
- d4 = wp.dot(ac, bp)
31
-
32
- if d3 >= 0.0 and d4 <= d3:
33
- return wp.vec3(0.0, 1.0, 0.0)
34
-
35
- vc = d1 * d4 - d3 * d2
36
- v = d1 / (d1 - d3)
37
- if vc <= 0.0 and d1 >= 0.0 and d3 <= 0.0:
38
- return wp.vec3(1.0 - v, v, 0.0)
39
-
40
- cp = p - c
41
- d5 = wp.dot(ab, cp)
42
- d6 = wp.dot(ac, cp)
43
-
44
- if d6 >= 0.0 and d5 <= d6:
45
- return wp.vec3(0.0, 0.0, 1.0)
46
-
47
- vb = d5 * d2 - d1 * d6
48
- w = d2 / (d2 - d6)
49
- if vb <= 0.0 and d2 >= 0.0 and d6 <= 0.0:
50
- return wp.vec3(1.0 - w, 0.0, w)
51
-
52
- va = d3 * d6 - d5 * d4
53
- w = (d4 - d3) / ((d4 - d3) + (d5 - d6))
54
- if va <= 0.0 and (d4 - d3) >= 0.0 and (d5 - d6) >= 0.0:
55
- return wp.vec3(0.0, w, 1.0 - w)
56
-
57
- denom = 1.0 / (va + vb + vc)
58
- v = vb * denom
59
- w = vc * denom
60
-
61
- return wp.vec3(1.0 - v - w, v, w)
62
-
63
-
64
- @wp.func
65
- def sphere_sdf(center: wp.vec3, radius: float, p: wp.vec3):
66
- return wp.length(p - center) - radius
67
-
68
-
69
- @wp.func
70
- def sphere_sdf_grad(center: wp.vec3, radius: float, p: wp.vec3):
71
- return wp.normalize(p - center)
72
-
73
-
74
- @wp.func
75
- def box_sdf(upper: wp.vec3, p: wp.vec3):
76
- # adapted from https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
77
- qx = abs(p[0]) - upper[0]
78
- qy = abs(p[1]) - upper[1]
79
- qz = abs(p[2]) - upper[2]
80
-
81
- e = wp.vec3(wp.max(qx, 0.0), wp.max(qy, 0.0), wp.max(qz, 0.0))
82
-
83
- return wp.length(e) + wp.min(wp.max(qx, wp.max(qy, qz)), 0.0)
84
-
85
-
86
- @wp.func
87
- def box_sdf_grad(upper: wp.vec3, p: wp.vec3):
88
- qx = abs(p[0]) - upper[0]
89
- qy = abs(p[1]) - upper[1]
90
- qz = abs(p[2]) - upper[2]
91
-
92
- # exterior case
93
- if qx > 0.0 or qy > 0.0 or qz > 0.0:
94
- x = wp.clamp(p[0], -upper[0], upper[0])
95
- y = wp.clamp(p[1], -upper[1], upper[1])
96
- z = wp.clamp(p[2], -upper[2], upper[2])
97
-
98
- return wp.normalize(p - wp.vec3(x, y, z))
99
-
100
- sx = wp.sign(p[0])
101
- sy = wp.sign(p[1])
102
- sz = wp.sign(p[2])
103
-
104
- # x projection
105
- if qx > qy and qx > qz or qy == 0.0 and qz == 0.0:
106
- return wp.vec3(sx, 0.0, 0.0)
107
-
108
- # y projection
109
- if qy > qx and qy > qz or qx == 0.0 and qz == 0.0:
110
- return wp.vec3(0.0, sy, 0.0)
111
-
112
- # z projection
113
- return wp.vec3(0.0, 0.0, sz)
114
-
115
-
116
- @wp.func
117
- def capsule_sdf(radius: float, half_height: float, p: wp.vec3):
118
- if p[1] > half_height:
119
- return wp.length(wp.vec3(p[0], p[1] - half_height, p[2])) - radius
120
-
121
- if p[1] < -half_height:
122
- return wp.length(wp.vec3(p[0], p[1] + half_height, p[2])) - radius
123
-
124
- return wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
125
-
126
-
127
- @wp.func
128
- def capsule_sdf_grad(radius: float, half_height: float, p: wp.vec3):
129
- if p[1] > half_height:
130
- return wp.normalize(wp.vec3(p[0], p[1] - half_height, p[2]))
131
-
132
- if p[1] < -half_height:
133
- return wp.normalize(wp.vec3(p[0], p[1] + half_height, p[2]))
134
-
135
- return wp.normalize(wp.vec3(p[0], 0.0, p[2]))
136
-
137
-
138
- @wp.func
139
- def cylinder_sdf(radius: float, half_height: float, p: wp.vec3):
140
- dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
141
- dy = wp.abs(p[1]) - half_height
142
- return wp.min(wp.max(dx, dy), 0.0) + wp.length(wp.vec2(wp.max(dx, 0.0), wp.max(dy, 0.0)))
143
-
144
-
145
- @wp.func
146
- def cylinder_sdf_grad(radius: float, half_height: float, p: wp.vec3):
147
- dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
148
- dy = wp.abs(p[1]) - half_height
149
- if dx > dy:
150
- return wp.normalize(wp.vec3(p[0], 0.0, p[2]))
151
- return wp.vec3(0.0, wp.sign(p[1]), 0.0)
152
-
153
-
154
- @wp.func
155
- def cone_sdf(radius: float, half_height: float, p: wp.vec3):
156
- dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius * (p[1] + half_height) / (2.0 * half_height)
157
- dy = wp.abs(p[1]) - half_height
158
- return wp.min(wp.max(dx, dy), 0.0) + wp.length(wp.vec2(wp.max(dx, 0.0), wp.max(dy, 0.0)))
159
-
160
-
161
- @wp.func
162
- def cone_sdf_grad(radius: float, half_height: float, p: wp.vec3):
163
- dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius * (p[1] + half_height) / (2.0 * half_height)
164
- dy = wp.abs(p[1]) - half_height
165
- if dy < 0.0 or dx == 0.0:
166
- return wp.vec3(0.0, wp.sign(p[1]), 0.0)
167
- return wp.normalize(wp.vec3(p[0], 0.0, p[2])) + wp.vec3(0.0, radius / (2.0 * half_height), 0.0)
168
-
169
-
170
- @wp.func
171
- def plane_sdf(width: float, length: float, p: wp.vec3):
172
- # SDF for a quad in the xz plane
173
- if width > 0.0 and length > 0.0:
174
- d = wp.max(wp.abs(p[0]) - width, wp.abs(p[2]) - length)
175
- return wp.max(d, wp.abs(p[1]))
176
- return p[1]
177
-
178
-
179
- @wp.func
180
- def closest_point_plane(width: float, length: float, point: wp.vec3):
181
- # projects the point onto the quad in the xz plane (if width and length > 0.0, otherwise the plane is infinite)
182
- if width > 0.0:
183
- x = wp.clamp(point[0], -width, width)
184
- else:
185
- x = point[0]
186
- if length > 0.0:
187
- z = wp.clamp(point[2], -length, length)
188
- else:
189
- z = point[2]
190
- return wp.vec3(x, 0.0, z)
191
-
192
-
193
- @wp.func
194
- def closest_point_line_segment(a: wp.vec3, b: wp.vec3, point: wp.vec3):
195
- ab = b - a
196
- ap = point - a
197
- t = wp.dot(ap, ab) / wp.dot(ab, ab)
198
- t = wp.clamp(t, 0.0, 1.0)
199
- return a + t * ab
200
-
201
-
202
- @wp.func
203
- def closest_point_box(upper: wp.vec3, point: wp.vec3):
204
- # closest point to box surface
205
- x = wp.clamp(point[0], -upper[0], upper[0])
206
- y = wp.clamp(point[1], -upper[1], upper[1])
207
- z = wp.clamp(point[2], -upper[2], upper[2])
208
- if wp.abs(point[0]) <= upper[0] and wp.abs(point[1]) <= upper[1] and wp.abs(point[2]) <= upper[2]:
209
- # the point is inside, find closest face
210
- sx = wp.abs(wp.abs(point[0]) - upper[0])
211
- sy = wp.abs(wp.abs(point[1]) - upper[1])
212
- sz = wp.abs(wp.abs(point[2]) - upper[2])
213
- # return closest point on closest side, handle corner cases
214
- if sx < sy and sx < sz or sy == 0.0 and sz == 0.0:
215
- x = wp.sign(point[0]) * upper[0]
216
- elif sy < sx and sy < sz or sx == 0.0 and sz == 0.0:
217
- y = wp.sign(point[1]) * upper[1]
218
- else:
219
- z = wp.sign(point[2]) * upper[2]
220
- return wp.vec3(x, y, z)
221
-
222
-
223
- @wp.func
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
232
- # get the vertex of the box given its ID (0-7)
233
- sign_x = float(point_id % 2) * 2.0 - 1.0
234
- sign_y = float((point_id // 2) % 2) * 2.0 - 1.0
235
- sign_z = float((point_id // 4) % 2) * 2.0 - 1.0
236
- return wp.vec3(sign_x * upper[0], sign_y * upper[1], sign_z * upper[2])
237
-
238
-
239
- @wp.func
240
- def get_box_edge(edge_id: int, upper: wp.vec3):
241
- # get the edge of the box given its ID (0-11)
242
- if edge_id < 4:
243
- # edges along x: 0-1, 2-3, 4-5, 6-7
244
- i = edge_id * 2
245
- j = i + 1
246
- return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
247
- elif edge_id < 8:
248
- # edges along y: 0-2, 1-3, 4-6, 5-7
249
- edge_id -= 4
250
- i = edge_id % 2 + edge_id // 2 * 4
251
- j = i + 2
252
- return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
253
- # edges along z: 0-4, 1-5, 2-6, 3-7
254
- edge_id -= 8
255
- i = edge_id
256
- j = i + 4
257
- return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
258
-
259
-
260
- @wp.func
261
- def get_plane_edge(edge_id: int, plane_width: float, plane_length: float):
262
- # get the edge of the plane given its ID (0-3)
263
- p0x = (2.0 * float(edge_id % 2) - 1.0) * plane_width
264
- p0z = (2.0 * float(edge_id // 2) - 1.0) * plane_length
265
- if edge_id == 0 or edge_id == 3:
266
- p1x = p0x
267
- p1z = -p0z
268
- else:
269
- p1x = -p0x
270
- p1z = p0z
271
- return wp.spatial_vector(wp.vec3(p0x, 0.0, p0z), wp.vec3(p1x, 0.0, p1z))
272
-
273
-
274
- @wp.func
275
- def closest_edge_coordinate_box(upper: wp.vec3, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int):
276
- # find point on edge closest to box, return its barycentric edge coordinate
277
- # Golden-section search
278
- a = float(0.0)
279
- b = float(1.0)
280
- h = b - a
281
- invphi = 0.61803398875 # 1 / phi
282
- invphi2 = 0.38196601125 # 1 / phi^2
283
- c = a + invphi2 * h
284
- d = a + invphi * h
285
- query = (1.0 - c) * edge_a + c * edge_b
286
- yc = box_sdf(upper, query)
287
- query = (1.0 - d) * edge_a + d * edge_b
288
- yd = box_sdf(upper, query)
289
-
290
- for k in range(max_iter):
291
- if yc < yd: # yc > yd to find the maximum
292
- b = d
293
- d = c
294
- yd = yc
295
- h = invphi * h
296
- c = a + invphi2 * h
297
- query = (1.0 - c) * edge_a + c * edge_b
298
- yc = box_sdf(upper, query)
299
- else:
300
- a = c
301
- c = d
302
- yc = yd
303
- h = invphi * h
304
- d = a + invphi * h
305
- query = (1.0 - d) * edge_a + d * edge_b
306
- yd = box_sdf(upper, query)
307
-
308
- if yc < yd:
309
- return 0.5 * (a + d)
310
- return 0.5 * (c + b)
311
-
312
-
313
- @wp.func
314
- def closest_edge_coordinate_plane(
315
- plane_width: float,
316
- plane_length: float,
317
- edge_a: wp.vec3,
318
- edge_b: wp.vec3,
319
- max_iter: int,
320
- ):
321
- # find point on edge closest to plane, return its barycentric edge coordinate
322
- # Golden-section search
323
- a = float(0.0)
324
- b = float(1.0)
325
- h = b - a
326
- invphi = 0.61803398875 # 1 / phi
327
- invphi2 = 0.38196601125 # 1 / phi^2
328
- c = a + invphi2 * h
329
- d = a + invphi * h
330
- query = (1.0 - c) * edge_a + c * edge_b
331
- yc = plane_sdf(plane_width, plane_length, query)
332
- query = (1.0 - d) * edge_a + d * edge_b
333
- yd = plane_sdf(plane_width, plane_length, query)
334
-
335
- for k in range(max_iter):
336
- if yc < yd: # yc > yd to find the maximum
337
- b = d
338
- d = c
339
- yd = yc
340
- h = invphi * h
341
- c = a + invphi2 * h
342
- query = (1.0 - c) * edge_a + c * edge_b
343
- yc = plane_sdf(plane_width, plane_length, query)
344
- else:
345
- a = c
346
- c = d
347
- yc = yd
348
- h = invphi * h
349
- d = a + invphi * h
350
- query = (1.0 - d) * edge_a + d * edge_b
351
- yd = plane_sdf(plane_width, plane_length, query)
352
-
353
- if yc < yd:
354
- return 0.5 * (a + d)
355
- return 0.5 * (c + b)
356
-
357
-
358
- @wp.func
359
- def closest_edge_coordinate_capsule(radius: float, half_height: float, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int):
360
- # find point on edge closest to capsule, return its barycentric edge coordinate
361
- # Golden-section search
362
- a = float(0.0)
363
- b = float(1.0)
364
- h = b - a
365
- invphi = 0.61803398875 # 1 / phi
366
- invphi2 = 0.38196601125 # 1 / phi^2
367
- c = a + invphi2 * h
368
- d = a + invphi * h
369
- query = (1.0 - c) * edge_a + c * edge_b
370
- yc = capsule_sdf(radius, half_height, query)
371
- query = (1.0 - d) * edge_a + d * edge_b
372
- yd = capsule_sdf(radius, half_height, query)
373
-
374
- for k in range(max_iter):
375
- if yc < yd: # yc > yd to find the maximum
376
- b = d
377
- d = c
378
- yd = yc
379
- h = invphi * h
380
- c = a + invphi2 * h
381
- query = (1.0 - c) * edge_a + c * edge_b
382
- yc = capsule_sdf(radius, half_height, query)
383
- else:
384
- a = c
385
- c = d
386
- yc = yd
387
- h = invphi * h
388
- d = a + invphi * h
389
- query = (1.0 - d) * edge_a + d * edge_b
390
- yd = capsule_sdf(radius, half_height, query)
391
-
392
- if yc < yd:
393
- return 0.5 * (a + d)
394
-
395
- return 0.5 * (c + b)
396
-
397
-
398
- @wp.func
399
- def mesh_sdf(mesh: wp.uint64, point: wp.vec3, max_dist: float):
400
- face_index = int(0)
401
- face_u = float(0.0)
402
- face_v = float(0.0)
403
- sign = float(0.0)
404
- res = wp.mesh_query_point_sign_normal(mesh, point, max_dist, sign, face_index, face_u, face_v)
405
-
406
- if res:
407
- closest = wp.mesh_eval_position(mesh, face_index, face_u, face_v)
408
- return wp.length(point - closest) * sign
409
- return max_dist
410
-
411
-
412
- @wp.func
413
- def closest_point_mesh(mesh: wp.uint64, point: wp.vec3, max_dist: float):
414
- face_index = int(0)
415
- face_u = float(0.0)
416
- face_v = float(0.0)
417
- sign = float(0.0)
418
- res = wp.mesh_query_point_sign_normal(mesh, point, max_dist, sign, face_index, face_u, face_v)
419
-
420
- if res:
421
- return wp.mesh_eval_position(mesh, face_index, face_u, face_v)
422
- # return arbitrary point from mesh
423
- return wp.mesh_eval_position(mesh, 0, 0.0, 0.0)
424
-
425
-
426
- @wp.func
427
- def closest_edge_coordinate_mesh(mesh: wp.uint64, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int, max_dist: float):
428
- # find point on edge closest to mesh, return its barycentric edge coordinate
429
- # Golden-section search
430
- a = float(0.0)
431
- b = float(1.0)
432
- h = b - a
433
- invphi = 0.61803398875 # 1 / phi
434
- invphi2 = 0.38196601125 # 1 / phi^2
435
- c = a + invphi2 * h
436
- d = a + invphi * h
437
- query = (1.0 - c) * edge_a + c * edge_b
438
- yc = mesh_sdf(mesh, query, max_dist)
439
- query = (1.0 - d) * edge_a + d * edge_b
440
- yd = mesh_sdf(mesh, query, max_dist)
441
-
442
- for k in range(max_iter):
443
- if yc < yd: # yc > yd to find the maximum
444
- b = d
445
- d = c
446
- yd = yc
447
- h = invphi * h
448
- c = a + invphi2 * h
449
- query = (1.0 - c) * edge_a + c * edge_b
450
- yc = mesh_sdf(mesh, query, max_dist)
451
- else:
452
- a = c
453
- c = d
454
- yc = yd
455
- h = invphi * h
456
- d = a + invphi * h
457
- query = (1.0 - d) * edge_a + d * edge_b
458
- yd = mesh_sdf(mesh, query, max_dist)
459
-
460
- if yc < yd:
461
- return 0.5 * (a + d)
462
- return 0.5 * (c + b)
463
-
464
-
465
- @wp.func
466
- def volume_grad(volume: wp.uint64, p: wp.vec3):
467
- eps = 0.05 # TODO make this a parameter
468
- q = wp.volume_world_to_index(volume, p)
469
-
470
- # compute gradient of the SDF using finite differences
471
- dx = wp.volume_sample_f(volume, q + wp.vec3(eps, 0.0, 0.0), wp.Volume.LINEAR) - wp.volume_sample_f(
472
- volume, q - wp.vec3(eps, 0.0, 0.0), wp.Volume.LINEAR
473
- )
474
- dy = wp.volume_sample_f(volume, q + wp.vec3(0.0, eps, 0.0), wp.Volume.LINEAR) - wp.volume_sample_f(
475
- volume, q - wp.vec3(0.0, eps, 0.0), wp.Volume.LINEAR
476
- )
477
- dz = wp.volume_sample_f(volume, q + wp.vec3(0.0, 0.0, eps), wp.Volume.LINEAR) - wp.volume_sample_f(
478
- volume, q - wp.vec3(0.0, 0.0, eps), wp.Volume.LINEAR
479
- )
480
-
481
- return wp.normalize(wp.vec3(dx, dy, dz))
482
-
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
-
517
- @wp.kernel
518
- def create_soft_contacts(
519
- particle_x: wp.array(dtype=wp.vec3),
520
- particle_radius: wp.array(dtype=float),
521
- particle_flags: wp.array(dtype=wp.uint32),
522
- body_X_wb: wp.array(dtype=wp.transform),
523
- shape_X_bs: wp.array(dtype=wp.transform),
524
- shape_body: wp.array(dtype=int),
525
- geo: ModelShapeGeometry,
526
- margin: float,
527
- soft_contact_max: int,
528
- shape_count: int,
529
- # outputs
530
- soft_contact_count: wp.array(dtype=int),
531
- soft_contact_particle: wp.array(dtype=int),
532
- soft_contact_shape: wp.array(dtype=int),
533
- soft_contact_body_pos: wp.array(dtype=wp.vec3),
534
- soft_contact_body_vel: wp.array(dtype=wp.vec3),
535
- soft_contact_normal: wp.array(dtype=wp.vec3),
536
- soft_contact_tids: wp.array(dtype=int),
537
- ):
538
- tid = wp.tid()
539
- particle_index, shape_index = tid // shape_count, tid % shape_count
540
- if (particle_flags[particle_index] & PARTICLE_FLAG_ACTIVE) == 0:
541
- return
542
-
543
- rigid_index = shape_body[shape_index]
544
-
545
- px = particle_x[particle_index]
546
- radius = particle_radius[particle_index]
547
-
548
- X_wb = wp.transform_identity()
549
- if rigid_index >= 0:
550
- X_wb = body_X_wb[rigid_index]
551
-
552
- X_bs = shape_X_bs[shape_index]
553
-
554
- X_ws = wp.transform_multiply(X_wb, X_bs)
555
- X_sw = wp.transform_inverse(X_ws)
556
-
557
- # transform particle position to shape local space
558
- x_local = wp.transform_point(X_sw, px)
559
-
560
- # geo description
561
- geo_type = geo.type[shape_index]
562
- geo_scale = geo.scale[shape_index]
563
-
564
- # evaluate shape sdf
565
- d = 1.0e6
566
- n = wp.vec3()
567
- v = wp.vec3()
568
-
569
- if geo_type == wp.sim.GEO_SPHERE:
570
- d = sphere_sdf(wp.vec3(), geo_scale[0], x_local)
571
- n = sphere_sdf_grad(wp.vec3(), geo_scale[0], x_local)
572
-
573
- if geo_type == wp.sim.GEO_BOX:
574
- d = box_sdf(geo_scale, x_local)
575
- n = box_sdf_grad(geo_scale, x_local)
576
-
577
- if geo_type == wp.sim.GEO_CAPSULE:
578
- d = capsule_sdf(geo_scale[0], geo_scale[1], x_local)
579
- n = capsule_sdf_grad(geo_scale[0], geo_scale[1], x_local)
580
-
581
- if geo_type == wp.sim.GEO_CYLINDER:
582
- d = cylinder_sdf(geo_scale[0], geo_scale[1], x_local)
583
- n = cylinder_sdf_grad(geo_scale[0], geo_scale[1], x_local)
584
-
585
- if geo_type == wp.sim.GEO_CONE:
586
- d = cone_sdf(geo_scale[0], geo_scale[1], x_local)
587
- n = cone_sdf_grad(geo_scale[0], geo_scale[1], x_local)
588
-
589
- if geo_type == wp.sim.GEO_MESH:
590
- mesh = geo.source[shape_index]
591
-
592
- face_index = int(0)
593
- face_u = float(0.0)
594
- face_v = float(0.0)
595
- sign = float(0.0)
596
-
597
- min_scale = wp.min(geo_scale)
598
- if wp.mesh_query_point_sign_normal(
599
- mesh, wp.cw_div(x_local, geo_scale), margin + radius / min_scale, sign, face_index, face_u, face_v
600
- ):
601
- shape_p = wp.mesh_eval_position(mesh, face_index, face_u, face_v)
602
- shape_v = wp.mesh_eval_velocity(mesh, face_index, face_u, face_v)
603
-
604
- shape_p = wp.cw_mul(shape_p, geo_scale)
605
- shape_v = wp.cw_mul(shape_v, geo_scale)
606
-
607
- delta = x_local - shape_p
608
-
609
- d = wp.length(delta) * sign
610
- n = wp.normalize(delta) * sign
611
- v = shape_v
612
-
613
- if geo_type == wp.sim.GEO_SDF:
614
- volume = geo.source[shape_index]
615
- xpred_local = wp.volume_world_to_index(volume, wp.cw_div(x_local, geo_scale))
616
- nn = wp.vec3(0.0, 0.0, 0.0)
617
- d = wp.volume_sample_grad_f(volume, xpred_local, wp.Volume.LINEAR, nn)
618
- n = wp.normalize(nn)
619
-
620
- if geo_type == wp.sim.GEO_PLANE:
621
- d = plane_sdf(geo_scale[0], geo_scale[1], x_local)
622
- n = wp.vec3(0.0, 1.0, 0.0)
623
-
624
- if d < margin + radius:
625
- index = counter_increment(soft_contact_count, 0, soft_contact_tids, tid)
626
-
627
- if index < soft_contact_max:
628
- # compute contact point in body local space
629
- body_pos = wp.transform_point(X_bs, x_local - n * d)
630
- body_vel = wp.transform_vector(X_bs, v)
631
-
632
- world_normal = wp.transform_vector(X_ws, n)
633
-
634
- soft_contact_shape[index] = shape_index
635
- soft_contact_body_pos[index] = body_pos
636
- soft_contact_body_vel[index] = body_vel
637
- soft_contact_particle[index] = particle_index
638
- soft_contact_normal[index] = world_normal
639
-
640
-
641
- @wp.kernel(enable_backward=False)
642
- def count_contact_points(
643
- contact_pairs: wp.array(dtype=int, ndim=2),
644
- geo: ModelShapeGeometry,
645
- mesh_contact_max: int,
646
- # outputs
647
- contact_count: wp.array(dtype=int),
648
- ):
649
- tid = wp.tid()
650
- shape_a = contact_pairs[tid, 0]
651
- shape_b = contact_pairs[tid, 1]
652
-
653
- if shape_b == -1:
654
- actual_shape_a = shape_a
655
- actual_type_a = geo.type[shape_a]
656
- # ground plane
657
- actual_type_b = wp.sim.GEO_PLANE
658
- actual_shape_b = -1
659
- else:
660
- type_a = geo.type[shape_a]
661
- type_b = geo.type[shape_b]
662
- # unique ordering of shape pairs
663
- if type_a < type_b:
664
- actual_shape_a = shape_a
665
- actual_shape_b = shape_b
666
- actual_type_a = type_a
667
- actual_type_b = type_b
668
- else:
669
- actual_shape_a = shape_b
670
- actual_shape_b = shape_a
671
- actual_type_a = type_b
672
- actual_type_b = type_a
673
-
674
- # determine how many contact points need to be evaluated
675
- num_contacts = 0
676
- num_actual_contacts = 0
677
- if actual_type_a == wp.sim.GEO_SPHERE:
678
- num_contacts = 1
679
- num_actual_contacts = 1
680
- elif actual_type_a == wp.sim.GEO_CAPSULE:
681
- if actual_type_b == wp.sim.GEO_PLANE:
682
- if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
683
- num_contacts = 2 # vertex-based collision for infinite plane
684
- num_actual_contacts = 2
685
- else:
686
- num_contacts = 2 + 4 # vertex-based collision + plane edges
687
- num_actual_contacts = 2 + 4
688
- elif actual_type_b == wp.sim.GEO_MESH:
689
- num_contacts_a = 2
690
- mesh_b = wp.mesh_get(geo.source[actual_shape_b])
691
- num_contacts_b = mesh_b.points.shape[0]
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
696
- else:
697
- num_contacts = 2
698
- num_actual_contacts = 2
699
- elif actual_type_a == wp.sim.GEO_BOX:
700
- if actual_type_b == wp.sim.GEO_BOX:
701
- num_contacts = 24
702
- num_actual_contacts = 24
703
- elif actual_type_b == wp.sim.GEO_MESH:
704
- num_contacts_a = 8
705
- mesh_b = wp.mesh_get(geo.source[actual_shape_b])
706
- num_contacts_b = mesh_b.points.shape[0]
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
711
- elif actual_type_b == wp.sim.GEO_PLANE:
712
- if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
713
- num_contacts = 8 # vertex-based collision
714
- num_actual_contacts = 8
715
- else:
716
- num_contacts = 8 + 4 # vertex-based collision + plane edges
717
- num_actual_contacts = 8 + 4
718
- else:
719
- num_contacts = 8
720
- elif actual_type_a == wp.sim.GEO_MESH:
721
- mesh_a = wp.mesh_get(geo.source[actual_shape_a])
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)
725
- if actual_type_b == wp.sim.GEO_MESH:
726
- mesh_b = wp.mesh_get(geo.source[actual_shape_b])
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)
731
- else:
732
- num_contacts_b = 0
733
- num_contacts = num_contacts_a + num_contacts_b
734
- num_actual_contacts = num_contacts_a + num_contacts_b
735
- elif actual_type_a == wp.sim.GEO_PLANE:
736
- return # no plane-plane contacts
737
- else:
738
- wp.printf(
739
- "count_contact_points: unsupported geometry type combination %d and %d\n", actual_type_a, actual_type_b
740
- )
741
-
742
- wp.atomic_add(contact_count, 0, num_contacts)
743
- wp.atomic_add(contact_count, 1, num_actual_contacts)
744
-
745
-
746
- @wp.kernel(enable_backward=False)
747
- def broadphase_collision_pairs(
748
- contact_pairs: wp.array(dtype=int, ndim=2),
749
- body_q: wp.array(dtype=wp.transform),
750
- shape_X_bs: wp.array(dtype=wp.transform),
751
- shape_body: wp.array(dtype=int),
752
- body_mass: wp.array(dtype=float),
753
- num_shapes: int,
754
- geo: ModelShapeGeometry,
755
- collision_radius: wp.array(dtype=float),
756
- rigid_contact_max: int,
757
- rigid_contact_margin: float,
758
- mesh_contact_max: int,
759
- iterate_mesh_vertices: bool,
760
- # outputs
761
- contact_count: wp.array(dtype=int),
762
- contact_shape0: wp.array(dtype=int),
763
- contact_shape1: wp.array(dtype=int),
764
- contact_point_id: wp.array(dtype=int),
765
- contact_point_limit: wp.array(dtype=int),
766
- ):
767
- tid = wp.tid()
768
- shape_a = contact_pairs[tid, 0]
769
- shape_b = contact_pairs[tid, 1]
770
-
771
- mass_a = 0.0
772
- mass_b = 0.0
773
- rigid_a = shape_body[shape_a]
774
- if rigid_a == -1:
775
- X_ws_a = shape_X_bs[shape_a]
776
- else:
777
- X_ws_a = wp.transform_multiply(body_q[rigid_a], shape_X_bs[shape_a])
778
- mass_a = body_mass[rigid_a]
779
- rigid_b = shape_body[shape_b]
780
- if rigid_b == -1:
781
- X_ws_b = shape_X_bs[shape_b]
782
- else:
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
788
-
789
- type_a = geo.type[shape_a]
790
- type_b = geo.type[shape_b]
791
- # unique ordering of shape pairs
792
- if type_a < type_b:
793
- actual_shape_a = shape_a
794
- actual_shape_b = shape_b
795
- actual_type_a = type_a
796
- actual_type_b = type_b
797
- actual_X_ws_a = X_ws_a
798
- actual_X_ws_b = X_ws_b
799
- else:
800
- actual_shape_a = shape_b
801
- actual_shape_b = shape_a
802
- actual_type_a = type_b
803
- actual_type_b = type_a
804
- actual_X_ws_a = X_ws_b
805
- actual_X_ws_b = X_ws_a
806
-
807
- p_a = wp.transform_get_translation(actual_X_ws_a)
808
- if actual_type_b == wp.sim.GEO_PLANE:
809
- if actual_type_a == wp.sim.GEO_PLANE:
810
- return
811
- query_b = wp.transform_point(wp.transform_inverse(actual_X_ws_b), p_a)
812
- scale = geo.scale[actual_shape_b]
813
- closest = closest_point_plane(scale[0], scale[1], query_b)
814
- d = wp.length(query_b - closest)
815
- r_a = collision_radius[actual_shape_a]
816
- if d > r_a + rigid_contact_margin:
817
- return
818
- else:
819
- p_b = wp.transform_get_translation(actual_X_ws_b)
820
- d = wp.length(p_a - p_b) * 0.5 - 0.1
821
- r_a = collision_radius[actual_shape_a]
822
- r_b = collision_radius[actual_shape_b]
823
- if d > r_a + r_b + rigid_contact_margin:
824
- return
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
-
829
- # determine how many contact points need to be evaluated
830
- num_contacts = 0
831
- if actual_type_a == wp.sim.GEO_SPHERE:
832
- num_contacts = 1
833
- elif actual_type_a == wp.sim.GEO_CAPSULE:
834
- if actual_type_b == wp.sim.GEO_PLANE:
835
- if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
836
- num_contacts = 2 # vertex-based collision for infinite plane
837
- else:
838
- num_contacts = 2 + 4 # vertex-based collision + plane edges
839
- elif actual_type_b == wp.sim.GEO_MESH:
840
- num_contacts_a = 2
841
- mesh_b = wp.mesh_get(geo.source[actual_shape_b])
842
- if iterate_mesh_vertices:
843
- num_contacts_b = mesh_b.points.shape[0]
844
- else:
845
- num_contacts_b = 0
846
- num_contacts = num_contacts_a + num_contacts_b
847
- index = wp.atomic_add(contact_count, 0, num_contacts)
848
- if index + num_contacts - 1 >= rigid_contact_max:
849
- print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
850
- return
851
- # allocate contact points from capsule A against mesh B
852
- for i in range(num_contacts_a):
853
- contact_shape0[index + i] = actual_shape_a
854
- contact_shape1[index + i] = actual_shape_b
855
- contact_point_id[index + i] = i
856
- # allocate contact points from mesh B against capsule A
857
- for i in range(num_contacts_b):
858
- contact_shape0[index + num_contacts_a + i] = actual_shape_b
859
- contact_shape1[index + num_contacts_a + i] = actual_shape_a
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
865
- return
866
- else:
867
- num_contacts = 2
868
- elif actual_type_a == wp.sim.GEO_BOX:
869
- if actual_type_b == wp.sim.GEO_BOX:
870
- index = wp.atomic_add(contact_count, 0, 24)
871
- if index + 23 >= rigid_contact_max:
872
- print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
873
- return
874
- # allocate contact points from box A against B
875
- for i in range(12): # 12 edges
876
- contact_shape0[index + i] = shape_a
877
- contact_shape1[index + i] = shape_b
878
- contact_point_id[index + i] = i
879
- contact_point_limit[pair_index_ab] = 12
880
- # allocate contact points from box B against A
881
- for i in range(12):
882
- contact_shape0[index + 12 + i] = shape_b
883
- contact_shape1[index + 12 + i] = shape_a
884
- contact_point_id[index + 12 + i] = i
885
- contact_point_limit[pair_index_ba] = 12
886
- return
887
- elif actual_type_b == wp.sim.GEO_MESH:
888
- num_contacts_a = 8
889
- mesh_b = wp.mesh_get(geo.source[actual_shape_b])
890
- if iterate_mesh_vertices:
891
- num_contacts_b = mesh_b.points.shape[0]
892
- else:
893
- num_contacts_b = 0
894
- num_contacts = num_contacts_a + num_contacts_b
895
- index = wp.atomic_add(contact_count, 0, num_contacts)
896
- if index + num_contacts - 1 >= rigid_contact_max:
897
- print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
898
- return
899
- # allocate contact points from box A against mesh B
900
- for i in range(num_contacts_a):
901
- contact_shape0[index + i] = actual_shape_a
902
- contact_shape1[index + i] = actual_shape_b
903
- contact_point_id[index + i] = i
904
- # allocate contact points from mesh B against box A
905
- for i in range(num_contacts_b):
906
- contact_shape0[index + num_contacts_a + i] = actual_shape_b
907
- contact_shape1[index + num_contacts_a + i] = actual_shape_a
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
914
- return
915
- elif actual_type_b == wp.sim.GEO_PLANE:
916
- if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
917
- num_contacts = 8 # vertex-based collision
918
- else:
919
- num_contacts = 8 + 4 # vertex-based collision + plane edges
920
- else:
921
- num_contacts = 8
922
- elif actual_type_a == wp.sim.GEO_MESH:
923
- mesh_a = wp.mesh_get(geo.source[actual_shape_a])
924
- num_contacts_a = mesh_a.points.shape[0]
925
- num_contacts_b = 0
926
- if actual_type_b == wp.sim.GEO_MESH:
927
- mesh_b = wp.mesh_get(geo.source[actual_shape_b])
928
- num_contacts_b = mesh_b.points.shape[0]
929
- elif actual_type_b != wp.sim.GEO_PLANE:
930
- print("broadphase_collision_pairs: unsupported geometry type for mesh collision")
931
- return
932
- num_contacts = num_contacts_a + num_contacts_b
933
- if num_contacts > 0:
934
- index = wp.atomic_add(contact_count, 0, num_contacts)
935
- if index + num_contacts - 1 >= rigid_contact_max:
936
- print("Mesh contact: Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
937
- return
938
- # allocate contact points from mesh A against B
939
- for i in range(num_contacts_a):
940
- contact_shape0[index + i] = actual_shape_a
941
- contact_shape1[index + i] = actual_shape_b
942
- contact_point_id[index + i] = i
943
- # allocate contact points from mesh B against A
944
- for i in range(num_contacts_b):
945
- contact_shape0[index + num_contacts_a + i] = actual_shape_b
946
- contact_shape1[index + num_contacts_a + i] = actual_shape_a
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
954
- return
955
- elif actual_type_a == wp.sim.GEO_PLANE:
956
- return # no plane-plane contacts
957
- else:
958
- print("broadphase_collision_pairs: unsupported geometry type")
959
-
960
- if num_contacts > 0:
961
- index = wp.atomic_add(contact_count, 0, num_contacts)
962
- if index + num_contacts - 1 >= rigid_contact_max:
963
- print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
964
- return
965
- # allocate contact points
966
- for i in range(num_contacts):
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
973
-
974
-
975
- @wp.kernel
976
- def handle_contact_pairs(
977
- body_q: wp.array(dtype=wp.transform),
978
- shape_X_bs: wp.array(dtype=wp.transform),
979
- shape_body: wp.array(dtype=int),
980
- geo: ModelShapeGeometry,
981
- rigid_contact_margin: float,
982
- contact_broad_shape0: wp.array(dtype=int),
983
- contact_broad_shape1: wp.array(dtype=int),
984
- num_shapes: int,
985
- contact_point_id: wp.array(dtype=int),
986
- contact_point_limit: wp.array(dtype=int),
987
- edge_sdf_iter: int,
988
- # outputs
989
- contact_count: wp.array(dtype=int),
990
- contact_shape0: wp.array(dtype=int),
991
- contact_shape1: wp.array(dtype=int),
992
- contact_point0: wp.array(dtype=wp.vec3),
993
- contact_point1: wp.array(dtype=wp.vec3),
994
- contact_offset0: wp.array(dtype=wp.vec3),
995
- contact_offset1: wp.array(dtype=wp.vec3),
996
- contact_normal: wp.array(dtype=wp.vec3),
997
- contact_thickness: wp.array(dtype=float),
998
- contact_pairwise_counter: wp.array(dtype=int),
999
- contact_tids: wp.array(dtype=int),
1000
- ):
1001
- tid = wp.tid()
1002
- shape_a = contact_broad_shape0[tid]
1003
- shape_b = contact_broad_shape1[tid]
1004
- if shape_a == shape_b:
1005
- return
1006
-
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
1013
-
1014
- rigid_a = shape_body[shape_a]
1015
- X_wb_a = wp.transform_identity()
1016
- if rigid_a >= 0:
1017
- X_wb_a = body_q[rigid_a]
1018
- X_bs_a = shape_X_bs[shape_a]
1019
- X_ws_a = wp.transform_multiply(X_wb_a, X_bs_a)
1020
- X_sw_a = wp.transform_inverse(X_ws_a)
1021
- X_bw_a = wp.transform_inverse(X_wb_a)
1022
- geo_type_a = geo.type[shape_a]
1023
- geo_scale_a = geo.scale[shape_a]
1024
- min_scale_a = min(geo_scale_a)
1025
- thickness_a = geo.thickness[shape_a]
1026
- # is_solid_a = geo.is_solid[shape_a]
1027
-
1028
- rigid_b = shape_body[shape_b]
1029
- X_wb_b = wp.transform_identity()
1030
- if rigid_b >= 0:
1031
- X_wb_b = body_q[rigid_b]
1032
- X_bs_b = shape_X_bs[shape_b]
1033
- X_ws_b = wp.transform_multiply(X_wb_b, X_bs_b)
1034
- X_sw_b = wp.transform_inverse(X_ws_b)
1035
- X_bw_b = wp.transform_inverse(X_wb_b)
1036
- geo_type_b = geo.type[shape_b]
1037
- geo_scale_b = geo.scale[shape_b]
1038
- min_scale_b = min(geo_scale_b)
1039
- thickness_b = geo.thickness[shape_b]
1040
- # is_solid_b = geo.is_solid[shape_b]
1041
-
1042
- distance = 1.0e6
1043
- u = float(0.0)
1044
- thickness = thickness_a + thickness_b
1045
-
1046
- if geo_type_a == wp.sim.GEO_SPHERE:
1047
- p_a_world = wp.transform_get_translation(X_ws_a)
1048
- if geo_type_b == wp.sim.GEO_SPHERE:
1049
- p_b_world = wp.transform_get_translation(X_ws_b)
1050
- elif geo_type_b == wp.sim.GEO_BOX:
1051
- # contact point in frame of body B
1052
- p_a_body = wp.transform_point(X_sw_b, p_a_world)
1053
- p_b_body = closest_point_box(geo_scale_b, p_a_body)
1054
- p_b_world = wp.transform_point(X_ws_b, p_b_body)
1055
- elif geo_type_b == wp.sim.GEO_CAPSULE:
1056
- half_height_b = geo_scale_b[1]
1057
- # capsule B
1058
- A_b = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1059
- B_b = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1060
- p_b_world = closest_point_line_segment(A_b, B_b, p_a_world)
1061
- elif geo_type_b == wp.sim.GEO_MESH:
1062
- mesh_b = geo.source[shape_b]
1063
- query_b_local = wp.transform_point(X_sw_b, p_a_world)
1064
- face_index = int(0)
1065
- face_u = float(0.0)
1066
- face_v = float(0.0)
1067
- sign = float(0.0)
1068
- max_dist = (thickness + rigid_contact_margin) / min_scale_b
1069
- res = wp.mesh_query_point_sign_normal(
1070
- mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1071
- )
1072
- if res:
1073
- shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1074
- shape_p = wp.cw_mul(shape_p, geo_scale_b)
1075
- p_b_world = wp.transform_point(X_ws_b, shape_p)
1076
- else:
1077
- return
1078
- elif geo_type_b == wp.sim.GEO_PLANE:
1079
- p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], wp.transform_point(X_sw_b, p_a_world))
1080
- p_b_world = wp.transform_point(X_ws_b, p_b_body)
1081
- else:
1082
- print("Unsupported geometry type in sphere collision handling")
1083
- print(geo_type_b)
1084
- return
1085
- diff = p_a_world - p_b_world
1086
- normal = wp.normalize(diff)
1087
- distance = wp.dot(diff, normal)
1088
-
1089
- elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_BOX:
1090
- # edge-based box contact
1091
- edge = get_box_edge(point_id, geo_scale_a)
1092
- edge0_world = wp.transform_point(X_ws_a, wp.spatial_top(edge))
1093
- edge1_world = wp.transform_point(X_ws_a, wp.spatial_bottom(edge))
1094
- edge0_b = wp.transform_point(X_sw_b, edge0_world)
1095
- edge1_b = wp.transform_point(X_sw_b, edge1_world)
1096
- max_iter = edge_sdf_iter
1097
- u = closest_edge_coordinate_box(geo_scale_b, edge0_b, edge1_b, max_iter)
1098
- p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1099
-
1100
- # find closest point + contact normal on box B
1101
- query_b = wp.transform_point(X_sw_b, p_a_world)
1102
- p_b_body = closest_point_box(geo_scale_b, query_b)
1103
- p_b_world = wp.transform_point(X_ws_b, p_b_body)
1104
- diff = p_a_world - p_b_world
1105
- # use center of box A to query normal to make sure we are not inside B
1106
- query_b = wp.transform_point(X_sw_b, wp.transform_get_translation(X_ws_a))
1107
- normal = wp.transform_vector(X_ws_b, box_sdf_grad(geo_scale_b, query_b))
1108
- distance = wp.dot(diff, normal)
1109
-
1110
- elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_CAPSULE:
1111
- half_height_b = geo_scale_b[1]
1112
- # capsule B
1113
- # depending on point id, we query an edge from 0 to 0.5 or 0.5 to 1
1114
- e0 = wp.vec3(0.0, -half_height_b * float(point_id % 2), 0.0)
1115
- e1 = wp.vec3(0.0, half_height_b * float((point_id + 1) % 2), 0.0)
1116
- edge0_world = wp.transform_point(X_ws_b, e0)
1117
- edge1_world = wp.transform_point(X_ws_b, e1)
1118
- edge0_a = wp.transform_point(X_sw_a, edge0_world)
1119
- edge1_a = wp.transform_point(X_sw_a, edge1_world)
1120
- max_iter = edge_sdf_iter
1121
- u = closest_edge_coordinate_box(geo_scale_a, edge0_a, edge1_a, max_iter)
1122
- p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1123
- # find closest point + contact normal on box A
1124
- query_a = wp.transform_point(X_sw_a, p_b_world)
1125
- p_a_body = closest_point_box(geo_scale_a, query_a)
1126
- p_a_world = wp.transform_point(X_ws_a, p_a_body)
1127
- diff = p_a_world - p_b_world
1128
- # the contact point inside the capsule should already be outside the box
1129
- normal = -wp.transform_vector(X_ws_a, box_sdf_grad(geo_scale_a, query_a))
1130
- distance = wp.dot(diff, normal)
1131
-
1132
- elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_PLANE:
1133
- plane_width = geo_scale_b[0]
1134
- plane_length = geo_scale_b[1]
1135
- if point_id < 8:
1136
- # vertex-based contact
1137
- p_a_body = get_box_vertex(point_id, geo_scale_a)
1138
- p_a_world = wp.transform_point(X_ws_a, p_a_body)
1139
- query_b = wp.transform_point(X_sw_b, p_a_world)
1140
- p_b_body = closest_point_plane(plane_width, plane_length, query_b)
1141
- p_b_world = wp.transform_point(X_ws_b, p_b_body)
1142
- diff = p_a_world - p_b_world
1143
- normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1144
- if plane_width > 0.0 and plane_length > 0.0:
1145
- if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1146
- # skip, we will evaluate the plane edge contact with the box later
1147
- return
1148
- # check whether the COM is above the plane
1149
- # sign = wp.sign(wp.dot(wp.transform_get_translation(X_ws_a) - p_b_world, normal))
1150
- # if sign < 0.0:
1151
- # # the entire box is most likely below the plane
1152
- # return
1153
- # the contact point is within plane boundaries
1154
- distance = wp.dot(diff, normal)
1155
- else:
1156
- # contact between box A and edges of finite plane B
1157
- edge = get_plane_edge(point_id - 8, plane_width, plane_length)
1158
- edge0_world = wp.transform_point(X_ws_b, wp.spatial_top(edge))
1159
- edge1_world = wp.transform_point(X_ws_b, wp.spatial_bottom(edge))
1160
- edge0_a = wp.transform_point(X_sw_a, edge0_world)
1161
- edge1_a = wp.transform_point(X_sw_a, edge1_world)
1162
- max_iter = edge_sdf_iter
1163
- u = closest_edge_coordinate_box(geo_scale_a, edge0_a, edge1_a, max_iter)
1164
- p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1165
-
1166
- # find closest point + contact normal on box A
1167
- query_a = wp.transform_point(X_sw_a, p_b_world)
1168
- p_a_body = closest_point_box(geo_scale_a, query_a)
1169
- p_a_world = wp.transform_point(X_ws_a, p_a_body)
1170
- query_b = wp.transform_point(X_sw_b, p_a_world)
1171
- if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1172
- # ensure that the closest point is actually inside the plane
1173
- return
1174
- diff = p_a_world - p_b_world
1175
- com_a = wp.transform_get_translation(X_ws_a)
1176
- query_b = wp.transform_point(X_sw_b, com_a)
1177
- if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1178
- # the COM is outside the plane
1179
- normal = wp.normalize(com_a - p_b_world)
1180
- else:
1181
- normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1182
- distance = wp.dot(diff, normal)
1183
-
1184
- elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_CAPSULE:
1185
- # find closest edge coordinate to capsule SDF B
1186
- half_height_a = geo_scale_a[1]
1187
- half_height_b = geo_scale_b[1]
1188
- # edge from capsule A
1189
- # depending on point id, we query an edge from 0 to 0.5 or 0.5 to 1
1190
- e0 = wp.vec3(0.0, half_height_a * float(point_id % 2), 0.0)
1191
- e1 = wp.vec3(0.0, -half_height_a * float((point_id + 1) % 2), 0.0)
1192
- edge0_world = wp.transform_point(X_ws_a, e0)
1193
- edge1_world = wp.transform_point(X_ws_a, e1)
1194
- edge0_b = wp.transform_point(X_sw_b, edge0_world)
1195
- edge1_b = wp.transform_point(X_sw_b, edge1_world)
1196
- max_iter = edge_sdf_iter
1197
- u = closest_edge_coordinate_capsule(geo_scale_b[0], geo_scale_b[1], edge0_b, edge1_b, max_iter)
1198
- p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1199
- p0_b_world = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1200
- p1_b_world = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1201
- p_b_world = closest_point_line_segment(p0_b_world, p1_b_world, p_a_world)
1202
- diff = p_a_world - p_b_world
1203
- normal = wp.normalize(diff)
1204
- distance = wp.dot(diff, normal)
1205
-
1206
- elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_MESH:
1207
- # find closest edge coordinate to mesh SDF B
1208
- half_height_a = geo_scale_a[1]
1209
- # edge from capsule A
1210
- # depending on point id, we query an edge from -h to 0 or 0 to h
1211
- e0 = wp.vec3(0.0, -half_height_a * float(point_id % 2), 0.0)
1212
- e1 = wp.vec3(0.0, half_height_a * float((point_id + 1) % 2), 0.0)
1213
- edge0_world = wp.transform_point(X_ws_a, e0)
1214
- edge1_world = wp.transform_point(X_ws_a, e1)
1215
- edge0_b = wp.transform_point(X_sw_b, edge0_world)
1216
- edge1_b = wp.transform_point(X_sw_b, edge1_world)
1217
- max_iter = edge_sdf_iter
1218
- max_dist = (rigid_contact_margin + thickness) / min_scale_b
1219
- mesh_b = geo.source[shape_b]
1220
- u = closest_edge_coordinate_mesh(
1221
- mesh_b, wp.cw_div(edge0_b, geo_scale_b), wp.cw_div(edge1_b, geo_scale_b), max_iter, max_dist
1222
- )
1223
- p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1224
- query_b_local = wp.transform_point(X_sw_b, p_a_world)
1225
- mesh_b = geo.source[shape_b]
1226
-
1227
- face_index = int(0)
1228
- face_u = float(0.0)
1229
- face_v = float(0.0)
1230
- sign = float(0.0)
1231
- res = wp.mesh_query_point_sign_normal(
1232
- mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1233
- )
1234
- if res:
1235
- shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1236
- shape_p = wp.cw_mul(shape_p, geo_scale_b)
1237
- p_b_world = wp.transform_point(X_ws_b, shape_p)
1238
- p_a_world = closest_point_line_segment(edge0_world, edge1_world, p_b_world)
1239
- # contact direction vector in world frame
1240
- diff = p_a_world - p_b_world
1241
- normal = wp.normalize(diff)
1242
- distance = wp.dot(diff, normal)
1243
- else:
1244
- return
1245
-
1246
- elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_CAPSULE:
1247
- # vertex-based contact
1248
- mesh = wp.mesh_get(geo.source[shape_a])
1249
- body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1250
- p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1251
- # find closest point + contact normal on capsule B
1252
- half_height_b = geo_scale_b[1]
1253
- A_b = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1254
- B_b = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1255
- p_b_world = closest_point_line_segment(A_b, B_b, p_a_world)
1256
- diff = p_a_world - p_b_world
1257
- # this is more reliable in practice than using the SDF gradient
1258
- normal = wp.normalize(diff)
1259
- distance = wp.dot(diff, normal)
1260
-
1261
- elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_PLANE:
1262
- plane_width = geo_scale_b[0]
1263
- plane_length = geo_scale_b[1]
1264
- if point_id < 2:
1265
- # vertex-based collision
1266
- half_height_a = geo_scale_a[1]
1267
- side = float(point_id) * 2.0 - 1.0
1268
- p_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, side * half_height_a, 0.0))
1269
- query_b = wp.transform_point(X_sw_b, p_a_world)
1270
- p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], query_b)
1271
- p_b_world = wp.transform_point(X_ws_b, p_b_body)
1272
- diff = p_a_world - p_b_world
1273
- if geo_scale_b[0] > 0.0 and geo_scale_b[1] > 0.0:
1274
- normal = wp.normalize(diff)
1275
- else:
1276
- normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1277
- distance = wp.dot(diff, normal)
1278
- else:
1279
- # contact between capsule A and edges of finite plane B
1280
- plane_width = geo_scale_b[0]
1281
- plane_length = geo_scale_b[1]
1282
- edge = get_plane_edge(point_id - 2, plane_width, plane_length)
1283
- edge0_world = wp.transform_point(X_ws_b, wp.spatial_top(edge))
1284
- edge1_world = wp.transform_point(X_ws_b, wp.spatial_bottom(edge))
1285
- edge0_a = wp.transform_point(X_sw_a, edge0_world)
1286
- edge1_a = wp.transform_point(X_sw_a, edge1_world)
1287
- max_iter = edge_sdf_iter
1288
- u = closest_edge_coordinate_capsule(geo_scale_a[0], geo_scale_a[1], edge0_a, edge1_a, max_iter)
1289
- p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1290
-
1291
- # find closest point + contact normal on capsule A
1292
- half_height_a = geo_scale_a[1]
1293
- p0_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, half_height_a, 0.0))
1294
- p1_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, -half_height_a, 0.0))
1295
- p_a_world = closest_point_line_segment(p0_a_world, p1_a_world, p_b_world)
1296
- diff = p_a_world - p_b_world
1297
- # normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1298
- normal = wp.normalize(diff)
1299
- distance = wp.dot(diff, normal)
1300
-
1301
- elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_BOX:
1302
- # vertex-based contact
1303
- mesh = wp.mesh_get(geo.source[shape_a])
1304
- body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1305
- p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1306
- # find closest point + contact normal on box B
1307
- query_b = wp.transform_point(X_sw_b, p_a_world)
1308
- p_b_body = closest_point_box(geo_scale_b, query_b)
1309
- p_b_world = wp.transform_point(X_ws_b, p_b_body)
1310
- diff = p_a_world - p_b_world
1311
- # this is more reliable in practice than using the SDF gradient
1312
- normal = wp.normalize(diff)
1313
- if box_sdf(geo_scale_b, query_b) < 0.0:
1314
- normal = -normal
1315
- distance = wp.dot(diff, normal)
1316
-
1317
- elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_MESH:
1318
- # vertex-based contact
1319
- query_a = get_box_vertex(point_id, geo_scale_a)
1320
- p_a_world = wp.transform_point(X_ws_a, query_a)
1321
- query_b_local = wp.transform_point(X_sw_b, p_a_world)
1322
- mesh_b = geo.source[shape_b]
1323
- max_dist = (rigid_contact_margin + thickness) / min_scale_b
1324
- face_index = int(0)
1325
- face_u = float(0.0)
1326
- face_v = float(0.0)
1327
- sign = float(0.0)
1328
- res = wp.mesh_query_point_sign_normal(
1329
- mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1330
- )
1331
-
1332
- if res:
1333
- shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1334
- shape_p = wp.cw_mul(shape_p, geo_scale_b)
1335
- p_b_world = wp.transform_point(X_ws_b, shape_p)
1336
- # contact direction vector in world frame
1337
- diff_b = p_a_world - p_b_world
1338
- normal = wp.normalize(diff_b) * sign
1339
- distance = wp.dot(diff_b, normal)
1340
- else:
1341
- return
1342
-
1343
- elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_MESH:
1344
- # vertex-based contact
1345
- mesh = wp.mesh_get(geo.source[shape_a])
1346
- mesh_b = geo.source[shape_b]
1347
-
1348
- body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1349
- p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1350
- query_b_local = wp.transform_point(X_sw_b, p_a_world)
1351
-
1352
- face_index = int(0)
1353
- face_u = float(0.0)
1354
- face_v = float(0.0)
1355
- sign = float(0.0)
1356
- min_scale = min(min_scale_a, min_scale_b)
1357
- max_dist = (rigid_contact_margin + thickness) / min_scale
1358
-
1359
- res = wp.mesh_query_point_sign_normal(
1360
- mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1361
- )
1362
-
1363
- if res:
1364
- shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1365
- shape_p = wp.cw_mul(shape_p, geo_scale_b)
1366
- p_b_world = wp.transform_point(X_ws_b, shape_p)
1367
- # contact direction vector in world frame
1368
- diff_b = p_a_world - p_b_world
1369
- normal = wp.normalize(diff_b) * sign
1370
- distance = wp.dot(diff_b, normal)
1371
- else:
1372
- return
1373
-
1374
- elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_PLANE:
1375
- # vertex-based contact
1376
- mesh = wp.mesh_get(geo.source[shape_a])
1377
- body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1378
- p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1379
- query_b = wp.transform_point(X_sw_b, p_a_world)
1380
- p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], query_b)
1381
- p_b_world = wp.transform_point(X_ws_b, p_b_body)
1382
- diff = p_a_world - p_b_world
1383
-
1384
- # if the plane is infinite or the point is within the plane we fix the normal to prevent intersections
1385
- if (
1386
- geo_scale_b[0] == 0.0
1387
- and geo_scale_b[1] == 0.0
1388
- or wp.abs(query_b[0]) < geo_scale_b[0]
1389
- and wp.abs(query_b[2]) < geo_scale_b[1]
1390
- ):
1391
- normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1392
- distance = wp.dot(diff, normal)
1393
- else:
1394
- normal = wp.normalize(diff)
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
1399
-
1400
- else:
1401
- print("Unsupported geometry pair in collision handling")
1402
- return
1403
-
1404
- d = distance - thickness
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
1417
- # transform from world into body frame (so the contact point includes the shape transform)
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
1424
-
1425
-
1426
- def collide(model, state, edge_sdf_iter: int = 10, iterate_mesh_vertices: bool = True, requires_grad: bool = None):
1427
- """
1428
- Generates contact points for the particles and rigid bodies in the model,
1429
- to be used in the contact dynamics kernel of the integrator.
1430
-
1431
- Args:
1432
- model: the model to be simulated
1433
- state: the state of the model
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)
1437
- """
1438
-
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
- )
1478
-
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
- )
1514
-
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
- )
1543
-
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
- )
1
+ # Copyright (c) 2022 NVIDIA CORPORATION. All rights reserved.
2
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
3
+ # and proprietary rights in and to this software, related documentation
4
+ # and any modifications thereto. Any use, reproduction, disclosure or
5
+ # distribution of this software and related documentation without an express
6
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
7
+
8
+ """
9
+ Collision handling functions and kernels.
10
+ """
11
+
12
+ import warp as wp
13
+
14
+ from .model import PARTICLE_FLAG_ACTIVE, ModelShapeGeometry
15
+
16
+
17
+ @wp.func
18
+ def triangle_closest_point_barycentric(a: wp.vec3, b: wp.vec3, c: wp.vec3, p: wp.vec3):
19
+ ab = b - a
20
+ ac = c - a
21
+ ap = p - a
22
+
23
+ d1 = wp.dot(ab, ap)
24
+ d2 = wp.dot(ac, ap)
25
+
26
+ if d1 <= 0.0 and d2 <= 0.0:
27
+ return wp.vec3(1.0, 0.0, 0.0)
28
+
29
+ bp = p - b
30
+ d3 = wp.dot(ab, bp)
31
+ d4 = wp.dot(ac, bp)
32
+
33
+ if d3 >= 0.0 and d4 <= d3:
34
+ return wp.vec3(0.0, 1.0, 0.0)
35
+
36
+ vc = d1 * d4 - d3 * d2
37
+ v = d1 / (d1 - d3)
38
+ if vc <= 0.0 and d1 >= 0.0 and d3 <= 0.0:
39
+ return wp.vec3(1.0 - v, v, 0.0)
40
+
41
+ cp = p - c
42
+ d5 = wp.dot(ab, cp)
43
+ d6 = wp.dot(ac, cp)
44
+
45
+ if d6 >= 0.0 and d5 <= d6:
46
+ return wp.vec3(0.0, 0.0, 1.0)
47
+
48
+ vb = d5 * d2 - d1 * d6
49
+ w = d2 / (d2 - d6)
50
+ if vb <= 0.0 and d2 >= 0.0 and d6 <= 0.0:
51
+ return wp.vec3(1.0 - w, 0.0, w)
52
+
53
+ va = d3 * d6 - d5 * d4
54
+ w = (d4 - d3) / ((d4 - d3) + (d5 - d6))
55
+ if va <= 0.0 and (d4 - d3) >= 0.0 and (d5 - d6) >= 0.0:
56
+ return wp.vec3(0.0, w, 1.0 - w)
57
+
58
+ denom = 1.0 / (va + vb + vc)
59
+ v = vb * denom
60
+ w = vc * denom
61
+
62
+ return wp.vec3(1.0 - v - w, v, w)
63
+
64
+
65
+ @wp.func
66
+ def sphere_sdf(center: wp.vec3, radius: float, p: wp.vec3):
67
+ return wp.length(p - center) - radius
68
+
69
+
70
+ @wp.func
71
+ def sphere_sdf_grad(center: wp.vec3, radius: float, p: wp.vec3):
72
+ return wp.normalize(p - center)
73
+
74
+
75
+ @wp.func
76
+ def box_sdf(upper: wp.vec3, p: wp.vec3):
77
+ # adapted from https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
78
+ qx = abs(p[0]) - upper[0]
79
+ qy = abs(p[1]) - upper[1]
80
+ qz = abs(p[2]) - upper[2]
81
+
82
+ e = wp.vec3(wp.max(qx, 0.0), wp.max(qy, 0.0), wp.max(qz, 0.0))
83
+
84
+ return wp.length(e) + wp.min(wp.max(qx, wp.max(qy, qz)), 0.0)
85
+
86
+
87
+ @wp.func
88
+ def box_sdf_grad(upper: wp.vec3, p: wp.vec3):
89
+ qx = abs(p[0]) - upper[0]
90
+ qy = abs(p[1]) - upper[1]
91
+ qz = abs(p[2]) - upper[2]
92
+
93
+ # exterior case
94
+ if qx > 0.0 or qy > 0.0 or qz > 0.0:
95
+ x = wp.clamp(p[0], -upper[0], upper[0])
96
+ y = wp.clamp(p[1], -upper[1], upper[1])
97
+ z = wp.clamp(p[2], -upper[2], upper[2])
98
+
99
+ return wp.normalize(p - wp.vec3(x, y, z))
100
+
101
+ sx = wp.sign(p[0])
102
+ sy = wp.sign(p[1])
103
+ sz = wp.sign(p[2])
104
+
105
+ # x projection
106
+ if qx > qy and qx > qz or qy == 0.0 and qz == 0.0:
107
+ return wp.vec3(sx, 0.0, 0.0)
108
+
109
+ # y projection
110
+ if qy > qx and qy > qz or qx == 0.0 and qz == 0.0:
111
+ return wp.vec3(0.0, sy, 0.0)
112
+
113
+ # z projection
114
+ return wp.vec3(0.0, 0.0, sz)
115
+
116
+
117
+ @wp.func
118
+ def capsule_sdf(radius: float, half_height: float, p: wp.vec3):
119
+ if p[1] > half_height:
120
+ return wp.length(wp.vec3(p[0], p[1] - half_height, p[2])) - radius
121
+
122
+ if p[1] < -half_height:
123
+ return wp.length(wp.vec3(p[0], p[1] + half_height, p[2])) - radius
124
+
125
+ return wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
126
+
127
+
128
+ @wp.func
129
+ def capsule_sdf_grad(radius: float, half_height: float, p: wp.vec3):
130
+ if p[1] > half_height:
131
+ return wp.normalize(wp.vec3(p[0], p[1] - half_height, p[2]))
132
+
133
+ if p[1] < -half_height:
134
+ return wp.normalize(wp.vec3(p[0], p[1] + half_height, p[2]))
135
+
136
+ return wp.normalize(wp.vec3(p[0], 0.0, p[2]))
137
+
138
+
139
+ @wp.func
140
+ def cylinder_sdf(radius: float, half_height: float, p: wp.vec3):
141
+ dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
142
+ dy = wp.abs(p[1]) - half_height
143
+ return wp.min(wp.max(dx, dy), 0.0) + wp.length(wp.vec2(wp.max(dx, 0.0), wp.max(dy, 0.0)))
144
+
145
+
146
+ @wp.func
147
+ def cylinder_sdf_grad(radius: float, half_height: float, p: wp.vec3):
148
+ dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
149
+ dy = wp.abs(p[1]) - half_height
150
+ if dx > dy:
151
+ return wp.normalize(wp.vec3(p[0], 0.0, p[2]))
152
+ return wp.vec3(0.0, wp.sign(p[1]), 0.0)
153
+
154
+
155
+ @wp.func
156
+ def cone_sdf(radius: float, half_height: float, p: wp.vec3):
157
+ dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius * (p[1] + half_height) / (2.0 * half_height)
158
+ dy = wp.abs(p[1]) - half_height
159
+ return wp.min(wp.max(dx, dy), 0.0) + wp.length(wp.vec2(wp.max(dx, 0.0), wp.max(dy, 0.0)))
160
+
161
+
162
+ @wp.func
163
+ def cone_sdf_grad(radius: float, half_height: float, p: wp.vec3):
164
+ dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius * (p[1] + half_height) / (2.0 * half_height)
165
+ dy = wp.abs(p[1]) - half_height
166
+ if dy < 0.0 or dx == 0.0:
167
+ return wp.vec3(0.0, wp.sign(p[1]), 0.0)
168
+ return wp.normalize(wp.vec3(p[0], 0.0, p[2])) + wp.vec3(0.0, radius / (2.0 * half_height), 0.0)
169
+
170
+
171
+ @wp.func
172
+ def plane_sdf(width: float, length: float, p: wp.vec3):
173
+ # SDF for a quad in the xz plane
174
+ if width > 0.0 and length > 0.0:
175
+ d = wp.max(wp.abs(p[0]) - width, wp.abs(p[2]) - length)
176
+ return wp.max(d, wp.abs(p[1]))
177
+ return p[1]
178
+
179
+
180
+ @wp.func
181
+ def closest_point_plane(width: float, length: float, point: wp.vec3):
182
+ # projects the point onto the quad in the xz plane (if width and length > 0.0, otherwise the plane is infinite)
183
+ if width > 0.0:
184
+ x = wp.clamp(point[0], -width, width)
185
+ else:
186
+ x = point[0]
187
+ if length > 0.0:
188
+ z = wp.clamp(point[2], -length, length)
189
+ else:
190
+ z = point[2]
191
+ return wp.vec3(x, 0.0, z)
192
+
193
+
194
+ @wp.func
195
+ def closest_point_line_segment(a: wp.vec3, b: wp.vec3, point: wp.vec3):
196
+ ab = b - a
197
+ ap = point - a
198
+ t = wp.dot(ap, ab) / wp.dot(ab, ab)
199
+ t = wp.clamp(t, 0.0, 1.0)
200
+ return a + t * ab
201
+
202
+
203
+ @wp.func
204
+ def closest_point_box(upper: wp.vec3, point: wp.vec3):
205
+ # closest point to box surface
206
+ x = wp.clamp(point[0], -upper[0], upper[0])
207
+ y = wp.clamp(point[1], -upper[1], upper[1])
208
+ z = wp.clamp(point[2], -upper[2], upper[2])
209
+ if wp.abs(point[0]) <= upper[0] and wp.abs(point[1]) <= upper[1] and wp.abs(point[2]) <= upper[2]:
210
+ # the point is inside, find closest face
211
+ sx = wp.abs(wp.abs(point[0]) - upper[0])
212
+ sy = wp.abs(wp.abs(point[1]) - upper[1])
213
+ sz = wp.abs(wp.abs(point[2]) - upper[2])
214
+ # return closest point on closest side, handle corner cases
215
+ if sx < sy and sx < sz or sy == 0.0 and sz == 0.0:
216
+ x = wp.sign(point[0]) * upper[0]
217
+ elif sy < sx and sy < sz or sx == 0.0 and sz == 0.0:
218
+ y = wp.sign(point[1]) * upper[1]
219
+ else:
220
+ z = wp.sign(point[2]) * upper[2]
221
+ return wp.vec3(x, y, z)
222
+
223
+
224
+ @wp.func
225
+ def get_box_vertex(point_id: int, upper: wp.vec3):
226
+ # box vertex numbering:
227
+ # 6---7
228
+ # |\ |\ y
229
+ # | 2-+-3 |
230
+ # 4-+-5 | z \|
231
+ # \| \| o---x
232
+ # 0---1
233
+ # get the vertex of the box given its ID (0-7)
234
+ sign_x = float(point_id % 2) * 2.0 - 1.0
235
+ sign_y = float((point_id // 2) % 2) * 2.0 - 1.0
236
+ sign_z = float((point_id // 4) % 2) * 2.0 - 1.0
237
+ return wp.vec3(sign_x * upper[0], sign_y * upper[1], sign_z * upper[2])
238
+
239
+
240
+ @wp.func
241
+ def get_box_edge(edge_id: int, upper: wp.vec3):
242
+ # get the edge of the box given its ID (0-11)
243
+ if edge_id < 4:
244
+ # edges along x: 0-1, 2-3, 4-5, 6-7
245
+ i = edge_id * 2
246
+ j = i + 1
247
+ return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
248
+ elif edge_id < 8:
249
+ # edges along y: 0-2, 1-3, 4-6, 5-7
250
+ edge_id -= 4
251
+ i = edge_id % 2 + edge_id // 2 * 4
252
+ j = i + 2
253
+ return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
254
+ # edges along z: 0-4, 1-5, 2-6, 3-7
255
+ edge_id -= 8
256
+ i = edge_id
257
+ j = i + 4
258
+ return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
259
+
260
+
261
+ @wp.func
262
+ def get_plane_edge(edge_id: int, plane_width: float, plane_length: float):
263
+ # get the edge of the plane given its ID (0-3)
264
+ p0x = (2.0 * float(edge_id % 2) - 1.0) * plane_width
265
+ p0z = (2.0 * float(edge_id // 2) - 1.0) * plane_length
266
+ if edge_id == 0 or edge_id == 3:
267
+ p1x = p0x
268
+ p1z = -p0z
269
+ else:
270
+ p1x = -p0x
271
+ p1z = p0z
272
+ return wp.spatial_vector(wp.vec3(p0x, 0.0, p0z), wp.vec3(p1x, 0.0, p1z))
273
+
274
+
275
+ @wp.func
276
+ def closest_edge_coordinate_box(upper: wp.vec3, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int):
277
+ # find point on edge closest to box, return its barycentric edge coordinate
278
+ # Golden-section search
279
+ a = float(0.0)
280
+ b = float(1.0)
281
+ h = b - a
282
+ invphi = 0.61803398875 # 1 / phi
283
+ invphi2 = 0.38196601125 # 1 / phi^2
284
+ c = a + invphi2 * h
285
+ d = a + invphi * h
286
+ query = (1.0 - c) * edge_a + c * edge_b
287
+ yc = box_sdf(upper, query)
288
+ query = (1.0 - d) * edge_a + d * edge_b
289
+ yd = box_sdf(upper, query)
290
+
291
+ for _k in range(max_iter):
292
+ if yc < yd: # yc > yd to find the maximum
293
+ b = d
294
+ d = c
295
+ yd = yc
296
+ h = invphi * h
297
+ c = a + invphi2 * h
298
+ query = (1.0 - c) * edge_a + c * edge_b
299
+ yc = box_sdf(upper, query)
300
+ else:
301
+ a = c
302
+ c = d
303
+ yc = yd
304
+ h = invphi * h
305
+ d = a + invphi * h
306
+ query = (1.0 - d) * edge_a + d * edge_b
307
+ yd = box_sdf(upper, query)
308
+
309
+ if yc < yd:
310
+ return 0.5 * (a + d)
311
+ return 0.5 * (c + b)
312
+
313
+
314
+ @wp.func
315
+ def closest_edge_coordinate_plane(
316
+ plane_width: float,
317
+ plane_length: float,
318
+ edge_a: wp.vec3,
319
+ edge_b: wp.vec3,
320
+ max_iter: int,
321
+ ):
322
+ # find point on edge closest to plane, return its barycentric edge coordinate
323
+ # Golden-section search
324
+ a = float(0.0)
325
+ b = float(1.0)
326
+ h = b - a
327
+ invphi = 0.61803398875 # 1 / phi
328
+ invphi2 = 0.38196601125 # 1 / phi^2
329
+ c = a + invphi2 * h
330
+ d = a + invphi * h
331
+ query = (1.0 - c) * edge_a + c * edge_b
332
+ yc = plane_sdf(plane_width, plane_length, query)
333
+ query = (1.0 - d) * edge_a + d * edge_b
334
+ yd = plane_sdf(plane_width, plane_length, query)
335
+
336
+ for _k in range(max_iter):
337
+ if yc < yd: # yc > yd to find the maximum
338
+ b = d
339
+ d = c
340
+ yd = yc
341
+ h = invphi * h
342
+ c = a + invphi2 * h
343
+ query = (1.0 - c) * edge_a + c * edge_b
344
+ yc = plane_sdf(plane_width, plane_length, query)
345
+ else:
346
+ a = c
347
+ c = d
348
+ yc = yd
349
+ h = invphi * h
350
+ d = a + invphi * h
351
+ query = (1.0 - d) * edge_a + d * edge_b
352
+ yd = plane_sdf(plane_width, plane_length, query)
353
+
354
+ if yc < yd:
355
+ return 0.5 * (a + d)
356
+ return 0.5 * (c + b)
357
+
358
+
359
+ @wp.func
360
+ def closest_edge_coordinate_capsule(radius: float, half_height: float, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int):
361
+ # find point on edge closest to capsule, return its barycentric edge coordinate
362
+ # Golden-section search
363
+ a = float(0.0)
364
+ b = float(1.0)
365
+ h = b - a
366
+ invphi = 0.61803398875 # 1 / phi
367
+ invphi2 = 0.38196601125 # 1 / phi^2
368
+ c = a + invphi2 * h
369
+ d = a + invphi * h
370
+ query = (1.0 - c) * edge_a + c * edge_b
371
+ yc = capsule_sdf(radius, half_height, query)
372
+ query = (1.0 - d) * edge_a + d * edge_b
373
+ yd = capsule_sdf(radius, half_height, query)
374
+
375
+ for _k in range(max_iter):
376
+ if yc < yd: # yc > yd to find the maximum
377
+ b = d
378
+ d = c
379
+ yd = yc
380
+ h = invphi * h
381
+ c = a + invphi2 * h
382
+ query = (1.0 - c) * edge_a + c * edge_b
383
+ yc = capsule_sdf(radius, half_height, query)
384
+ else:
385
+ a = c
386
+ c = d
387
+ yc = yd
388
+ h = invphi * h
389
+ d = a + invphi * h
390
+ query = (1.0 - d) * edge_a + d * edge_b
391
+ yd = capsule_sdf(radius, half_height, query)
392
+
393
+ if yc < yd:
394
+ return 0.5 * (a + d)
395
+
396
+ return 0.5 * (c + b)
397
+
398
+
399
+ @wp.func
400
+ def mesh_sdf(mesh: wp.uint64, point: wp.vec3, max_dist: float):
401
+ face_index = int(0)
402
+ face_u = float(0.0)
403
+ face_v = float(0.0)
404
+ sign = float(0.0)
405
+ res = wp.mesh_query_point_sign_normal(mesh, point, max_dist, sign, face_index, face_u, face_v)
406
+
407
+ if res:
408
+ closest = wp.mesh_eval_position(mesh, face_index, face_u, face_v)
409
+ return wp.length(point - closest) * sign
410
+ return max_dist
411
+
412
+
413
+ @wp.func
414
+ def closest_point_mesh(mesh: wp.uint64, point: wp.vec3, max_dist: float):
415
+ face_index = int(0)
416
+ face_u = float(0.0)
417
+ face_v = float(0.0)
418
+ sign = float(0.0)
419
+ res = wp.mesh_query_point_sign_normal(mesh, point, max_dist, sign, face_index, face_u, face_v)
420
+
421
+ if res:
422
+ return wp.mesh_eval_position(mesh, face_index, face_u, face_v)
423
+ # return arbitrary point from mesh
424
+ return wp.mesh_eval_position(mesh, 0, 0.0, 0.0)
425
+
426
+
427
+ @wp.func
428
+ def closest_edge_coordinate_mesh(mesh: wp.uint64, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int, max_dist: float):
429
+ # find point on edge closest to mesh, return its barycentric edge coordinate
430
+ # Golden-section search
431
+ a = float(0.0)
432
+ b = float(1.0)
433
+ h = b - a
434
+ invphi = 0.61803398875 # 1 / phi
435
+ invphi2 = 0.38196601125 # 1 / phi^2
436
+ c = a + invphi2 * h
437
+ d = a + invphi * h
438
+ query = (1.0 - c) * edge_a + c * edge_b
439
+ yc = mesh_sdf(mesh, query, max_dist)
440
+ query = (1.0 - d) * edge_a + d * edge_b
441
+ yd = mesh_sdf(mesh, query, max_dist)
442
+
443
+ for _k in range(max_iter):
444
+ if yc < yd: # yc > yd to find the maximum
445
+ b = d
446
+ d = c
447
+ yd = yc
448
+ h = invphi * h
449
+ c = a + invphi2 * h
450
+ query = (1.0 - c) * edge_a + c * edge_b
451
+ yc = mesh_sdf(mesh, query, max_dist)
452
+ else:
453
+ a = c
454
+ c = d
455
+ yc = yd
456
+ h = invphi * h
457
+ d = a + invphi * h
458
+ query = (1.0 - d) * edge_a + d * edge_b
459
+ yd = mesh_sdf(mesh, query, max_dist)
460
+
461
+ if yc < yd:
462
+ return 0.5 * (a + d)
463
+ return 0.5 * (c + b)
464
+
465
+
466
+ @wp.func
467
+ def volume_grad(volume: wp.uint64, p: wp.vec3):
468
+ eps = 0.05 # TODO make this a parameter
469
+ q = wp.volume_world_to_index(volume, p)
470
+
471
+ # compute gradient of the SDF using finite differences
472
+ dx = wp.volume_sample_f(volume, q + wp.vec3(eps, 0.0, 0.0), wp.Volume.LINEAR) - wp.volume_sample_f(
473
+ volume, q - wp.vec3(eps, 0.0, 0.0), wp.Volume.LINEAR
474
+ )
475
+ dy = wp.volume_sample_f(volume, q + wp.vec3(0.0, eps, 0.0), wp.Volume.LINEAR) - wp.volume_sample_f(
476
+ volume, q - wp.vec3(0.0, eps, 0.0), wp.Volume.LINEAR
477
+ )
478
+ dz = wp.volume_sample_f(volume, q + wp.vec3(0.0, 0.0, eps), wp.Volume.LINEAR) - wp.volume_sample_f(
479
+ volume, q - wp.vec3(0.0, 0.0, eps), wp.Volume.LINEAR
480
+ )
481
+
482
+ return wp.normalize(wp.vec3(dx, dy, dz))
483
+
484
+
485
+ @wp.func
486
+ def counter_increment(counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int):
487
+ # increment counter, remember which thread received which counter value
488
+ next_count = wp.atomic_add(counter, counter_index, 1)
489
+ tids[tid] = next_count
490
+ return next_count
491
+
492
+
493
+ @wp.func_replay(counter_increment)
494
+ def replay_counter_increment(counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int):
495
+ return tids[tid]
496
+
497
+
498
+ @wp.func
499
+ def limited_counter_increment(
500
+ counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int, index_limit: int
501
+ ):
502
+ # increment counter but only if it is smaller than index_limit, remember which thread received which counter value
503
+ next_count = wp.atomic_add(counter, counter_index, 1)
504
+ if next_count < index_limit or index_limit < 0:
505
+ tids[tid] = next_count
506
+ return next_count
507
+ tids[tid] = -1
508
+ return -1
509
+
510
+
511
+ @wp.func_replay(limited_counter_increment)
512
+ def replay_limited_counter_increment(
513
+ counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int, index_limit: int
514
+ ):
515
+ return tids[tid]
516
+
517
+
518
+ @wp.kernel
519
+ def create_soft_contacts(
520
+ particle_x: wp.array(dtype=wp.vec3),
521
+ particle_radius: wp.array(dtype=float),
522
+ particle_flags: wp.array(dtype=wp.uint32),
523
+ body_X_wb: wp.array(dtype=wp.transform),
524
+ shape_X_bs: wp.array(dtype=wp.transform),
525
+ shape_body: wp.array(dtype=int),
526
+ geo: ModelShapeGeometry,
527
+ margin: float,
528
+ soft_contact_max: int,
529
+ shape_count: int,
530
+ # outputs
531
+ soft_contact_count: wp.array(dtype=int),
532
+ soft_contact_particle: wp.array(dtype=int),
533
+ soft_contact_shape: wp.array(dtype=int),
534
+ soft_contact_body_pos: wp.array(dtype=wp.vec3),
535
+ soft_contact_body_vel: wp.array(dtype=wp.vec3),
536
+ soft_contact_normal: wp.array(dtype=wp.vec3),
537
+ soft_contact_tids: wp.array(dtype=int),
538
+ ):
539
+ tid = wp.tid()
540
+ particle_index, shape_index = tid // shape_count, tid % shape_count
541
+ if (particle_flags[particle_index] & PARTICLE_FLAG_ACTIVE) == 0:
542
+ return
543
+
544
+ rigid_index = shape_body[shape_index]
545
+
546
+ px = particle_x[particle_index]
547
+ radius = particle_radius[particle_index]
548
+
549
+ X_wb = wp.transform_identity()
550
+ if rigid_index >= 0:
551
+ X_wb = body_X_wb[rigid_index]
552
+
553
+ X_bs = shape_X_bs[shape_index]
554
+
555
+ X_ws = wp.transform_multiply(X_wb, X_bs)
556
+ X_sw = wp.transform_inverse(X_ws)
557
+
558
+ # transform particle position to shape local space
559
+ x_local = wp.transform_point(X_sw, px)
560
+
561
+ # geo description
562
+ geo_type = geo.type[shape_index]
563
+ geo_scale = geo.scale[shape_index]
564
+
565
+ # evaluate shape sdf
566
+ d = 1.0e6
567
+ n = wp.vec3()
568
+ v = wp.vec3()
569
+
570
+ if geo_type == wp.sim.GEO_SPHERE:
571
+ d = sphere_sdf(wp.vec3(), geo_scale[0], x_local)
572
+ n = sphere_sdf_grad(wp.vec3(), geo_scale[0], x_local)
573
+
574
+ if geo_type == wp.sim.GEO_BOX:
575
+ d = box_sdf(geo_scale, x_local)
576
+ n = box_sdf_grad(geo_scale, x_local)
577
+
578
+ if geo_type == wp.sim.GEO_CAPSULE:
579
+ d = capsule_sdf(geo_scale[0], geo_scale[1], x_local)
580
+ n = capsule_sdf_grad(geo_scale[0], geo_scale[1], x_local)
581
+
582
+ if geo_type == wp.sim.GEO_CYLINDER:
583
+ d = cylinder_sdf(geo_scale[0], geo_scale[1], x_local)
584
+ n = cylinder_sdf_grad(geo_scale[0], geo_scale[1], x_local)
585
+
586
+ if geo_type == wp.sim.GEO_CONE:
587
+ d = cone_sdf(geo_scale[0], geo_scale[1], x_local)
588
+ n = cone_sdf_grad(geo_scale[0], geo_scale[1], x_local)
589
+
590
+ if geo_type == wp.sim.GEO_MESH:
591
+ mesh = geo.source[shape_index]
592
+
593
+ face_index = int(0)
594
+ face_u = float(0.0)
595
+ face_v = float(0.0)
596
+ sign = float(0.0)
597
+
598
+ min_scale = wp.min(geo_scale)
599
+ if wp.mesh_query_point_sign_normal(
600
+ mesh, wp.cw_div(x_local, geo_scale), margin + radius / min_scale, sign, face_index, face_u, face_v
601
+ ):
602
+ shape_p = wp.mesh_eval_position(mesh, face_index, face_u, face_v)
603
+ shape_v = wp.mesh_eval_velocity(mesh, face_index, face_u, face_v)
604
+
605
+ shape_p = wp.cw_mul(shape_p, geo_scale)
606
+ shape_v = wp.cw_mul(shape_v, geo_scale)
607
+
608
+ delta = x_local - shape_p
609
+
610
+ d = wp.length(delta) * sign
611
+ n = wp.normalize(delta) * sign
612
+ v = shape_v
613
+
614
+ if geo_type == wp.sim.GEO_SDF:
615
+ volume = geo.source[shape_index]
616
+ xpred_local = wp.volume_world_to_index(volume, wp.cw_div(x_local, geo_scale))
617
+ nn = wp.vec3(0.0, 0.0, 0.0)
618
+ d = wp.volume_sample_grad_f(volume, xpred_local, wp.Volume.LINEAR, nn)
619
+ n = wp.normalize(nn)
620
+
621
+ if geo_type == wp.sim.GEO_PLANE:
622
+ d = plane_sdf(geo_scale[0], geo_scale[1], x_local)
623
+ n = wp.vec3(0.0, 1.0, 0.0)
624
+
625
+ if d < margin + radius:
626
+ index = counter_increment(soft_contact_count, 0, soft_contact_tids, tid)
627
+
628
+ if index < soft_contact_max:
629
+ # compute contact point in body local space
630
+ body_pos = wp.transform_point(X_bs, x_local - n * d)
631
+ body_vel = wp.transform_vector(X_bs, v)
632
+
633
+ world_normal = wp.transform_vector(X_ws, n)
634
+
635
+ soft_contact_shape[index] = shape_index
636
+ soft_contact_body_pos[index] = body_pos
637
+ soft_contact_body_vel[index] = body_vel
638
+ soft_contact_particle[index] = particle_index
639
+ soft_contact_normal[index] = world_normal
640
+
641
+
642
+ @wp.kernel(enable_backward=False)
643
+ def count_contact_points(
644
+ contact_pairs: wp.array(dtype=int, ndim=2),
645
+ geo: ModelShapeGeometry,
646
+ mesh_contact_max: int,
647
+ # outputs
648
+ contact_count: wp.array(dtype=int),
649
+ ):
650
+ tid = wp.tid()
651
+ shape_a = contact_pairs[tid, 0]
652
+ shape_b = contact_pairs[tid, 1]
653
+
654
+ if shape_b == -1:
655
+ actual_shape_a = shape_a
656
+ actual_type_a = geo.type[shape_a]
657
+ # ground plane
658
+ actual_type_b = wp.sim.GEO_PLANE
659
+ actual_shape_b = -1
660
+ else:
661
+ type_a = geo.type[shape_a]
662
+ type_b = geo.type[shape_b]
663
+ # unique ordering of shape pairs
664
+ if type_a < type_b:
665
+ actual_shape_a = shape_a
666
+ actual_shape_b = shape_b
667
+ actual_type_a = type_a
668
+ actual_type_b = type_b
669
+ else:
670
+ actual_shape_a = shape_b
671
+ actual_shape_b = shape_a
672
+ actual_type_a = type_b
673
+ actual_type_b = type_a
674
+
675
+ # determine how many contact points need to be evaluated
676
+ num_contacts = 0
677
+ num_actual_contacts = 0
678
+ if actual_type_a == wp.sim.GEO_SPHERE:
679
+ num_contacts = 1
680
+ num_actual_contacts = 1
681
+ elif actual_type_a == wp.sim.GEO_CAPSULE:
682
+ if actual_type_b == wp.sim.GEO_PLANE:
683
+ if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
684
+ num_contacts = 2 # vertex-based collision for infinite plane
685
+ num_actual_contacts = 2
686
+ else:
687
+ num_contacts = 2 + 4 # vertex-based collision + plane edges
688
+ num_actual_contacts = 2 + 4
689
+ elif actual_type_b == wp.sim.GEO_MESH:
690
+ num_contacts_a = 2
691
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
692
+ num_contacts_b = mesh_b.points.shape[0]
693
+ num_contacts = num_contacts_a + num_contacts_b
694
+ if mesh_contact_max > 0:
695
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
696
+ num_actual_contacts = num_contacts_a + num_contacts_b
697
+ else:
698
+ num_contacts = 2
699
+ num_actual_contacts = 2
700
+ elif actual_type_a == wp.sim.GEO_BOX:
701
+ if actual_type_b == wp.sim.GEO_BOX:
702
+ num_contacts = 24
703
+ num_actual_contacts = 24
704
+ elif actual_type_b == wp.sim.GEO_MESH:
705
+ num_contacts_a = 8
706
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
707
+ num_contacts_b = mesh_b.points.shape[0]
708
+ num_contacts = num_contacts_a + num_contacts_b
709
+ if mesh_contact_max > 0:
710
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
711
+ num_actual_contacts = num_contacts_a + num_contacts_b
712
+ elif actual_type_b == wp.sim.GEO_PLANE:
713
+ if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
714
+ num_contacts = 8 # vertex-based collision
715
+ num_actual_contacts = 8
716
+ else:
717
+ num_contacts = 8 + 4 # vertex-based collision + plane edges
718
+ num_actual_contacts = 8 + 4
719
+ else:
720
+ num_contacts = 8
721
+ elif actual_type_a == wp.sim.GEO_MESH:
722
+ mesh_a = wp.mesh_get(geo.source[actual_shape_a])
723
+ num_contacts_a = mesh_a.points.shape[0]
724
+ if mesh_contact_max > 0:
725
+ num_contacts_a = wp.min(mesh_contact_max, num_contacts_a)
726
+ if actual_type_b == wp.sim.GEO_MESH:
727
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
728
+ num_contacts_b = mesh_b.points.shape[0]
729
+ num_contacts = num_contacts_a + num_contacts_b
730
+ if mesh_contact_max > 0:
731
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
732
+ else:
733
+ num_contacts_b = 0
734
+ num_contacts = num_contacts_a + num_contacts_b
735
+ num_actual_contacts = num_contacts_a + num_contacts_b
736
+ elif actual_type_a == wp.sim.GEO_PLANE:
737
+ return # no plane-plane contacts
738
+ else:
739
+ wp.printf(
740
+ "count_contact_points: unsupported geometry type combination %d and %d\n", actual_type_a, actual_type_b
741
+ )
742
+
743
+ wp.atomic_add(contact_count, 0, num_contacts)
744
+ wp.atomic_add(contact_count, 1, num_actual_contacts)
745
+
746
+
747
+ @wp.kernel(enable_backward=False)
748
+ def broadphase_collision_pairs(
749
+ contact_pairs: wp.array(dtype=int, ndim=2),
750
+ body_q: wp.array(dtype=wp.transform),
751
+ shape_X_bs: wp.array(dtype=wp.transform),
752
+ shape_body: wp.array(dtype=int),
753
+ body_mass: wp.array(dtype=float),
754
+ num_shapes: int,
755
+ geo: ModelShapeGeometry,
756
+ collision_radius: wp.array(dtype=float),
757
+ rigid_contact_max: int,
758
+ rigid_contact_margin: float,
759
+ mesh_contact_max: int,
760
+ iterate_mesh_vertices: bool,
761
+ # outputs
762
+ contact_count: wp.array(dtype=int),
763
+ contact_shape0: wp.array(dtype=int),
764
+ contact_shape1: wp.array(dtype=int),
765
+ contact_point_id: wp.array(dtype=int),
766
+ contact_point_limit: wp.array(dtype=int),
767
+ ):
768
+ tid = wp.tid()
769
+ shape_a = contact_pairs[tid, 0]
770
+ shape_b = contact_pairs[tid, 1]
771
+
772
+ mass_a = 0.0
773
+ mass_b = 0.0
774
+ rigid_a = shape_body[shape_a]
775
+ if rigid_a == -1:
776
+ X_ws_a = shape_X_bs[shape_a]
777
+ else:
778
+ X_ws_a = wp.transform_multiply(body_q[rigid_a], shape_X_bs[shape_a])
779
+ mass_a = body_mass[rigid_a]
780
+ rigid_b = shape_body[shape_b]
781
+ if rigid_b == -1:
782
+ X_ws_b = shape_X_bs[shape_b]
783
+ else:
784
+ X_ws_b = wp.transform_multiply(body_q[rigid_b], shape_X_bs[shape_b])
785
+ mass_b = body_mass[rigid_b]
786
+ if mass_a == 0.0 and mass_b == 0.0:
787
+ # skip if both bodies are static
788
+ return
789
+
790
+ type_a = geo.type[shape_a]
791
+ type_b = geo.type[shape_b]
792
+ # unique ordering of shape pairs
793
+ if type_a < type_b:
794
+ actual_shape_a = shape_a
795
+ actual_shape_b = shape_b
796
+ actual_type_a = type_a
797
+ actual_type_b = type_b
798
+ actual_X_ws_a = X_ws_a
799
+ actual_X_ws_b = X_ws_b
800
+ else:
801
+ actual_shape_a = shape_b
802
+ actual_shape_b = shape_a
803
+ actual_type_a = type_b
804
+ actual_type_b = type_a
805
+ actual_X_ws_a = X_ws_b
806
+ actual_X_ws_b = X_ws_a
807
+
808
+ p_a = wp.transform_get_translation(actual_X_ws_a)
809
+ if actual_type_b == wp.sim.GEO_PLANE:
810
+ if actual_type_a == wp.sim.GEO_PLANE:
811
+ return
812
+ query_b = wp.transform_point(wp.transform_inverse(actual_X_ws_b), p_a)
813
+ scale = geo.scale[actual_shape_b]
814
+ closest = closest_point_plane(scale[0], scale[1], query_b)
815
+ d = wp.length(query_b - closest)
816
+ r_a = collision_radius[actual_shape_a]
817
+ if d > r_a + rigid_contact_margin:
818
+ return
819
+ else:
820
+ p_b = wp.transform_get_translation(actual_X_ws_b)
821
+ d = wp.length(p_a - p_b) * 0.5 - 0.1
822
+ r_a = collision_radius[actual_shape_a]
823
+ r_b = collision_radius[actual_shape_b]
824
+ if d > r_a + r_b + rigid_contact_margin:
825
+ return
826
+
827
+ pair_index_ab = actual_shape_a * num_shapes + actual_shape_b
828
+ pair_index_ba = actual_shape_b * num_shapes + actual_shape_a
829
+
830
+ # determine how many contact points need to be evaluated
831
+ num_contacts = 0
832
+ if actual_type_a == wp.sim.GEO_SPHERE:
833
+ num_contacts = 1
834
+ elif actual_type_a == wp.sim.GEO_CAPSULE:
835
+ if actual_type_b == wp.sim.GEO_PLANE:
836
+ if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
837
+ num_contacts = 2 # vertex-based collision for infinite plane
838
+ else:
839
+ num_contacts = 2 + 4 # vertex-based collision + plane edges
840
+ elif actual_type_b == wp.sim.GEO_MESH:
841
+ num_contacts_a = 2
842
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
843
+ if iterate_mesh_vertices:
844
+ num_contacts_b = mesh_b.points.shape[0]
845
+ else:
846
+ num_contacts_b = 0
847
+ num_contacts = num_contacts_a + num_contacts_b
848
+ index = wp.atomic_add(contact_count, 0, num_contacts)
849
+ if index + num_contacts - 1 >= rigid_contact_max:
850
+ print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
851
+ return
852
+ # allocate contact points from capsule A against mesh B
853
+ for i in range(num_contacts_a):
854
+ contact_shape0[index + i] = actual_shape_a
855
+ contact_shape1[index + i] = actual_shape_b
856
+ contact_point_id[index + i] = i
857
+ # allocate contact points from mesh B against capsule A
858
+ for i in range(num_contacts_b):
859
+ contact_shape0[index + num_contacts_a + i] = actual_shape_b
860
+ contact_shape1[index + num_contacts_a + i] = actual_shape_a
861
+ contact_point_id[index + num_contacts_a + i] = i
862
+ contact_point_limit[pair_index_ab] = 2
863
+ if mesh_contact_max > 0:
864
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
865
+ contact_point_limit[pair_index_ba] = num_contacts_b
866
+ return
867
+ else:
868
+ num_contacts = 2
869
+ elif actual_type_a == wp.sim.GEO_BOX:
870
+ if actual_type_b == wp.sim.GEO_BOX:
871
+ index = wp.atomic_add(contact_count, 0, 24)
872
+ if index + 23 >= rigid_contact_max:
873
+ print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
874
+ return
875
+ # allocate contact points from box A against B
876
+ for i in range(12): # 12 edges
877
+ contact_shape0[index + i] = shape_a
878
+ contact_shape1[index + i] = shape_b
879
+ contact_point_id[index + i] = i
880
+ contact_point_limit[pair_index_ab] = 12
881
+ # allocate contact points from box B against A
882
+ for i in range(12):
883
+ contact_shape0[index + 12 + i] = shape_b
884
+ contact_shape1[index + 12 + i] = shape_a
885
+ contact_point_id[index + 12 + i] = i
886
+ contact_point_limit[pair_index_ba] = 12
887
+ return
888
+ elif actual_type_b == wp.sim.GEO_MESH:
889
+ num_contacts_a = 8
890
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
891
+ if iterate_mesh_vertices:
892
+ num_contacts_b = mesh_b.points.shape[0]
893
+ else:
894
+ num_contacts_b = 0
895
+ num_contacts = num_contacts_a + num_contacts_b
896
+ index = wp.atomic_add(contact_count, 0, num_contacts)
897
+ if index + num_contacts - 1 >= rigid_contact_max:
898
+ print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
899
+ return
900
+ # allocate contact points from box A against mesh B
901
+ for i in range(num_contacts_a):
902
+ contact_shape0[index + i] = actual_shape_a
903
+ contact_shape1[index + i] = actual_shape_b
904
+ contact_point_id[index + i] = i
905
+ # allocate contact points from mesh B against box A
906
+ for i in range(num_contacts_b):
907
+ contact_shape0[index + num_contacts_a + i] = actual_shape_b
908
+ contact_shape1[index + num_contacts_a + i] = actual_shape_a
909
+ contact_point_id[index + num_contacts_a + i] = i
910
+
911
+ contact_point_limit[pair_index_ab] = num_contacts_a
912
+ if mesh_contact_max > 0:
913
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
914
+ contact_point_limit[pair_index_ba] = num_contacts_b
915
+ return
916
+ elif actual_type_b == wp.sim.GEO_PLANE:
917
+ if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
918
+ num_contacts = 8 # vertex-based collision
919
+ else:
920
+ num_contacts = 8 + 4 # vertex-based collision + plane edges
921
+ else:
922
+ num_contacts = 8
923
+ elif actual_type_a == wp.sim.GEO_MESH:
924
+ mesh_a = wp.mesh_get(geo.source[actual_shape_a])
925
+ num_contacts_a = mesh_a.points.shape[0]
926
+ num_contacts_b = 0
927
+ if actual_type_b == wp.sim.GEO_MESH:
928
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
929
+ num_contacts_b = mesh_b.points.shape[0]
930
+ elif actual_type_b != wp.sim.GEO_PLANE:
931
+ print("broadphase_collision_pairs: unsupported geometry type for mesh collision")
932
+ return
933
+ num_contacts = num_contacts_a + num_contacts_b
934
+ if num_contacts > 0:
935
+ index = wp.atomic_add(contact_count, 0, num_contacts)
936
+ if index + num_contacts - 1 >= rigid_contact_max:
937
+ print("Mesh contact: Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
938
+ return
939
+ # allocate contact points from mesh A against B
940
+ for i in range(num_contacts_a):
941
+ contact_shape0[index + i] = actual_shape_a
942
+ contact_shape1[index + i] = actual_shape_b
943
+ contact_point_id[index + i] = i
944
+ # allocate contact points from mesh B against A
945
+ for i in range(num_contacts_b):
946
+ contact_shape0[index + num_contacts_a + i] = actual_shape_b
947
+ contact_shape1[index + num_contacts_a + i] = actual_shape_a
948
+ contact_point_id[index + num_contacts_a + i] = i
949
+
950
+ if mesh_contact_max > 0:
951
+ num_contacts_a = wp.min(mesh_contact_max, num_contacts_a)
952
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
953
+ contact_point_limit[pair_index_ab] = num_contacts_a
954
+ contact_point_limit[pair_index_ba] = num_contacts_b
955
+ return
956
+ elif actual_type_a == wp.sim.GEO_PLANE:
957
+ return # no plane-plane contacts
958
+ else:
959
+ print("broadphase_collision_pairs: unsupported geometry type")
960
+
961
+ if num_contacts > 0:
962
+ index = wp.atomic_add(contact_count, 0, num_contacts)
963
+ if index + num_contacts - 1 >= rigid_contact_max:
964
+ print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
965
+ return
966
+ # allocate contact points
967
+ for i in range(num_contacts):
968
+ cp_index = index + i
969
+ contact_shape0[cp_index] = actual_shape_a
970
+ contact_shape1[cp_index] = actual_shape_b
971
+ contact_point_id[cp_index] = i
972
+ contact_point_limit[pair_index_ab] = num_contacts
973
+ contact_point_limit[pair_index_ba] = 0
974
+
975
+
976
+ @wp.kernel
977
+ def handle_contact_pairs(
978
+ body_q: wp.array(dtype=wp.transform),
979
+ shape_X_bs: wp.array(dtype=wp.transform),
980
+ shape_body: wp.array(dtype=int),
981
+ geo: ModelShapeGeometry,
982
+ rigid_contact_margin: float,
983
+ contact_broad_shape0: wp.array(dtype=int),
984
+ contact_broad_shape1: wp.array(dtype=int),
985
+ num_shapes: int,
986
+ contact_point_id: wp.array(dtype=int),
987
+ contact_point_limit: wp.array(dtype=int),
988
+ edge_sdf_iter: int,
989
+ # outputs
990
+ contact_count: wp.array(dtype=int),
991
+ contact_shape0: wp.array(dtype=int),
992
+ contact_shape1: wp.array(dtype=int),
993
+ contact_point0: wp.array(dtype=wp.vec3),
994
+ contact_point1: wp.array(dtype=wp.vec3),
995
+ contact_offset0: wp.array(dtype=wp.vec3),
996
+ contact_offset1: wp.array(dtype=wp.vec3),
997
+ contact_normal: wp.array(dtype=wp.vec3),
998
+ contact_thickness: wp.array(dtype=float),
999
+ contact_pairwise_counter: wp.array(dtype=int),
1000
+ contact_tids: wp.array(dtype=int),
1001
+ ):
1002
+ tid = wp.tid()
1003
+ shape_a = contact_broad_shape0[tid]
1004
+ shape_b = contact_broad_shape1[tid]
1005
+ if shape_a == shape_b:
1006
+ return
1007
+
1008
+ point_id = contact_point_id[tid]
1009
+ pair_index = shape_a * num_shapes + shape_b
1010
+ contact_limit = contact_point_limit[pair_index]
1011
+ if contact_pairwise_counter[pair_index] >= contact_limit:
1012
+ # reached limit of contact points per contact pair
1013
+ return
1014
+
1015
+ rigid_a = shape_body[shape_a]
1016
+ X_wb_a = wp.transform_identity()
1017
+ if rigid_a >= 0:
1018
+ X_wb_a = body_q[rigid_a]
1019
+ X_bs_a = shape_X_bs[shape_a]
1020
+ X_ws_a = wp.transform_multiply(X_wb_a, X_bs_a)
1021
+ X_sw_a = wp.transform_inverse(X_ws_a)
1022
+ X_bw_a = wp.transform_inverse(X_wb_a)
1023
+ geo_type_a = geo.type[shape_a]
1024
+ geo_scale_a = geo.scale[shape_a]
1025
+ min_scale_a = min(geo_scale_a)
1026
+ thickness_a = geo.thickness[shape_a]
1027
+ # is_solid_a = geo.is_solid[shape_a]
1028
+
1029
+ rigid_b = shape_body[shape_b]
1030
+ X_wb_b = wp.transform_identity()
1031
+ if rigid_b >= 0:
1032
+ X_wb_b = body_q[rigid_b]
1033
+ X_bs_b = shape_X_bs[shape_b]
1034
+ X_ws_b = wp.transform_multiply(X_wb_b, X_bs_b)
1035
+ X_sw_b = wp.transform_inverse(X_ws_b)
1036
+ X_bw_b = wp.transform_inverse(X_wb_b)
1037
+ geo_type_b = geo.type[shape_b]
1038
+ geo_scale_b = geo.scale[shape_b]
1039
+ min_scale_b = min(geo_scale_b)
1040
+ thickness_b = geo.thickness[shape_b]
1041
+ # is_solid_b = geo.is_solid[shape_b]
1042
+
1043
+ distance = 1.0e6
1044
+ u = float(0.0)
1045
+ thickness = thickness_a + thickness_b
1046
+
1047
+ if geo_type_a == wp.sim.GEO_SPHERE:
1048
+ p_a_world = wp.transform_get_translation(X_ws_a)
1049
+ if geo_type_b == wp.sim.GEO_SPHERE:
1050
+ p_b_world = wp.transform_get_translation(X_ws_b)
1051
+ elif geo_type_b == wp.sim.GEO_BOX:
1052
+ # contact point in frame of body B
1053
+ p_a_body = wp.transform_point(X_sw_b, p_a_world)
1054
+ p_b_body = closest_point_box(geo_scale_b, p_a_body)
1055
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1056
+ elif geo_type_b == wp.sim.GEO_CAPSULE:
1057
+ half_height_b = geo_scale_b[1]
1058
+ # capsule B
1059
+ A_b = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1060
+ B_b = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1061
+ p_b_world = closest_point_line_segment(A_b, B_b, p_a_world)
1062
+ elif geo_type_b == wp.sim.GEO_MESH:
1063
+ mesh_b = geo.source[shape_b]
1064
+ query_b_local = wp.transform_point(X_sw_b, p_a_world)
1065
+ face_index = int(0)
1066
+ face_u = float(0.0)
1067
+ face_v = float(0.0)
1068
+ sign = float(0.0)
1069
+ max_dist = (thickness + rigid_contact_margin) / min_scale_b
1070
+ res = wp.mesh_query_point_sign_normal(
1071
+ mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1072
+ )
1073
+ if res:
1074
+ shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1075
+ shape_p = wp.cw_mul(shape_p, geo_scale_b)
1076
+ p_b_world = wp.transform_point(X_ws_b, shape_p)
1077
+ else:
1078
+ return
1079
+ elif geo_type_b == wp.sim.GEO_PLANE:
1080
+ p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], wp.transform_point(X_sw_b, p_a_world))
1081
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1082
+ else:
1083
+ print("Unsupported geometry type in sphere collision handling")
1084
+ print(geo_type_b)
1085
+ return
1086
+ diff = p_a_world - p_b_world
1087
+ normal = wp.normalize(diff)
1088
+ distance = wp.dot(diff, normal)
1089
+
1090
+ elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_BOX:
1091
+ # edge-based box contact
1092
+ edge = get_box_edge(point_id, geo_scale_a)
1093
+ edge0_world = wp.transform_point(X_ws_a, wp.spatial_top(edge))
1094
+ edge1_world = wp.transform_point(X_ws_a, wp.spatial_bottom(edge))
1095
+ edge0_b = wp.transform_point(X_sw_b, edge0_world)
1096
+ edge1_b = wp.transform_point(X_sw_b, edge1_world)
1097
+ max_iter = edge_sdf_iter
1098
+ u = closest_edge_coordinate_box(geo_scale_b, edge0_b, edge1_b, max_iter)
1099
+ p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1100
+
1101
+ # find closest point + contact normal on box B
1102
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1103
+ p_b_body = closest_point_box(geo_scale_b, query_b)
1104
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1105
+ diff = p_a_world - p_b_world
1106
+ # use center of box A to query normal to make sure we are not inside B
1107
+ query_b = wp.transform_point(X_sw_b, wp.transform_get_translation(X_ws_a))
1108
+ normal = wp.transform_vector(X_ws_b, box_sdf_grad(geo_scale_b, query_b))
1109
+ distance = wp.dot(diff, normal)
1110
+
1111
+ elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_CAPSULE:
1112
+ half_height_b = geo_scale_b[1]
1113
+ # capsule B
1114
+ # depending on point id, we query an edge from 0 to 0.5 or 0.5 to 1
1115
+ e0 = wp.vec3(0.0, -half_height_b * float(point_id % 2), 0.0)
1116
+ e1 = wp.vec3(0.0, half_height_b * float((point_id + 1) % 2), 0.0)
1117
+ edge0_world = wp.transform_point(X_ws_b, e0)
1118
+ edge1_world = wp.transform_point(X_ws_b, e1)
1119
+ edge0_a = wp.transform_point(X_sw_a, edge0_world)
1120
+ edge1_a = wp.transform_point(X_sw_a, edge1_world)
1121
+ max_iter = edge_sdf_iter
1122
+ u = closest_edge_coordinate_box(geo_scale_a, edge0_a, edge1_a, max_iter)
1123
+ p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1124
+ # find closest point + contact normal on box A
1125
+ query_a = wp.transform_point(X_sw_a, p_b_world)
1126
+ p_a_body = closest_point_box(geo_scale_a, query_a)
1127
+ p_a_world = wp.transform_point(X_ws_a, p_a_body)
1128
+ diff = p_a_world - p_b_world
1129
+ # the contact point inside the capsule should already be outside the box
1130
+ normal = -wp.transform_vector(X_ws_a, box_sdf_grad(geo_scale_a, query_a))
1131
+ distance = wp.dot(diff, normal)
1132
+
1133
+ elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_PLANE:
1134
+ plane_width = geo_scale_b[0]
1135
+ plane_length = geo_scale_b[1]
1136
+ if point_id < 8:
1137
+ # vertex-based contact
1138
+ p_a_body = get_box_vertex(point_id, geo_scale_a)
1139
+ p_a_world = wp.transform_point(X_ws_a, p_a_body)
1140
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1141
+ p_b_body = closest_point_plane(plane_width, plane_length, query_b)
1142
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1143
+ diff = p_a_world - p_b_world
1144
+ normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1145
+ if plane_width > 0.0 and plane_length > 0.0:
1146
+ if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1147
+ # skip, we will evaluate the plane edge contact with the box later
1148
+ return
1149
+ # check whether the COM is above the plane
1150
+ # sign = wp.sign(wp.dot(wp.transform_get_translation(X_ws_a) - p_b_world, normal))
1151
+ # if sign < 0.0:
1152
+ # # the entire box is most likely below the plane
1153
+ # return
1154
+ # the contact point is within plane boundaries
1155
+ distance = wp.dot(diff, normal)
1156
+ else:
1157
+ # contact between box A and edges of finite plane B
1158
+ edge = get_plane_edge(point_id - 8, plane_width, plane_length)
1159
+ edge0_world = wp.transform_point(X_ws_b, wp.spatial_top(edge))
1160
+ edge1_world = wp.transform_point(X_ws_b, wp.spatial_bottom(edge))
1161
+ edge0_a = wp.transform_point(X_sw_a, edge0_world)
1162
+ edge1_a = wp.transform_point(X_sw_a, edge1_world)
1163
+ max_iter = edge_sdf_iter
1164
+ u = closest_edge_coordinate_box(geo_scale_a, edge0_a, edge1_a, max_iter)
1165
+ p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1166
+
1167
+ # find closest point + contact normal on box A
1168
+ query_a = wp.transform_point(X_sw_a, p_b_world)
1169
+ p_a_body = closest_point_box(geo_scale_a, query_a)
1170
+ p_a_world = wp.transform_point(X_ws_a, p_a_body)
1171
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1172
+ if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1173
+ # ensure that the closest point is actually inside the plane
1174
+ return
1175
+ diff = p_a_world - p_b_world
1176
+ com_a = wp.transform_get_translation(X_ws_a)
1177
+ query_b = wp.transform_point(X_sw_b, com_a)
1178
+ if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1179
+ # the COM is outside the plane
1180
+ normal = wp.normalize(com_a - p_b_world)
1181
+ else:
1182
+ normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1183
+ distance = wp.dot(diff, normal)
1184
+
1185
+ elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_CAPSULE:
1186
+ # find closest edge coordinate to capsule SDF B
1187
+ half_height_a = geo_scale_a[1]
1188
+ half_height_b = geo_scale_b[1]
1189
+ # edge from capsule A
1190
+ # depending on point id, we query an edge from 0 to 0.5 or 0.5 to 1
1191
+ e0 = wp.vec3(0.0, half_height_a * float(point_id % 2), 0.0)
1192
+ e1 = wp.vec3(0.0, -half_height_a * float((point_id + 1) % 2), 0.0)
1193
+ edge0_world = wp.transform_point(X_ws_a, e0)
1194
+ edge1_world = wp.transform_point(X_ws_a, e1)
1195
+ edge0_b = wp.transform_point(X_sw_b, edge0_world)
1196
+ edge1_b = wp.transform_point(X_sw_b, edge1_world)
1197
+ max_iter = edge_sdf_iter
1198
+ u = closest_edge_coordinate_capsule(geo_scale_b[0], geo_scale_b[1], edge0_b, edge1_b, max_iter)
1199
+ p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1200
+ p0_b_world = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1201
+ p1_b_world = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1202
+ p_b_world = closest_point_line_segment(p0_b_world, p1_b_world, p_a_world)
1203
+ diff = p_a_world - p_b_world
1204
+ normal = wp.normalize(diff)
1205
+ distance = wp.dot(diff, normal)
1206
+
1207
+ elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_MESH:
1208
+ # find closest edge coordinate to mesh SDF B
1209
+ half_height_a = geo_scale_a[1]
1210
+ # edge from capsule A
1211
+ # depending on point id, we query an edge from -h to 0 or 0 to h
1212
+ e0 = wp.vec3(0.0, -half_height_a * float(point_id % 2), 0.0)
1213
+ e1 = wp.vec3(0.0, half_height_a * float((point_id + 1) % 2), 0.0)
1214
+ edge0_world = wp.transform_point(X_ws_a, e0)
1215
+ edge1_world = wp.transform_point(X_ws_a, e1)
1216
+ edge0_b = wp.transform_point(X_sw_b, edge0_world)
1217
+ edge1_b = wp.transform_point(X_sw_b, edge1_world)
1218
+ max_iter = edge_sdf_iter
1219
+ max_dist = (rigid_contact_margin + thickness) / min_scale_b
1220
+ mesh_b = geo.source[shape_b]
1221
+ u = closest_edge_coordinate_mesh(
1222
+ mesh_b, wp.cw_div(edge0_b, geo_scale_b), wp.cw_div(edge1_b, geo_scale_b), max_iter, max_dist
1223
+ )
1224
+ p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1225
+ query_b_local = wp.transform_point(X_sw_b, p_a_world)
1226
+ mesh_b = geo.source[shape_b]
1227
+
1228
+ face_index = int(0)
1229
+ face_u = float(0.0)
1230
+ face_v = float(0.0)
1231
+ sign = float(0.0)
1232
+ res = wp.mesh_query_point_sign_normal(
1233
+ mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1234
+ )
1235
+ if res:
1236
+ shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1237
+ shape_p = wp.cw_mul(shape_p, geo_scale_b)
1238
+ p_b_world = wp.transform_point(X_ws_b, shape_p)
1239
+ p_a_world = closest_point_line_segment(edge0_world, edge1_world, p_b_world)
1240
+ # contact direction vector in world frame
1241
+ diff = p_a_world - p_b_world
1242
+ normal = wp.normalize(diff)
1243
+ distance = wp.dot(diff, normal)
1244
+ else:
1245
+ return
1246
+
1247
+ elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_CAPSULE:
1248
+ # vertex-based contact
1249
+ mesh = wp.mesh_get(geo.source[shape_a])
1250
+ body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1251
+ p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1252
+ # find closest point + contact normal on capsule B
1253
+ half_height_b = geo_scale_b[1]
1254
+ A_b = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1255
+ B_b = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1256
+ p_b_world = closest_point_line_segment(A_b, B_b, p_a_world)
1257
+ diff = p_a_world - p_b_world
1258
+ # this is more reliable in practice than using the SDF gradient
1259
+ normal = wp.normalize(diff)
1260
+ distance = wp.dot(diff, normal)
1261
+
1262
+ elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_PLANE:
1263
+ plane_width = geo_scale_b[0]
1264
+ plane_length = geo_scale_b[1]
1265
+ if point_id < 2:
1266
+ # vertex-based collision
1267
+ half_height_a = geo_scale_a[1]
1268
+ side = float(point_id) * 2.0 - 1.0
1269
+ p_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, side * half_height_a, 0.0))
1270
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1271
+ p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], query_b)
1272
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1273
+ diff = p_a_world - p_b_world
1274
+ if geo_scale_b[0] > 0.0 and geo_scale_b[1] > 0.0:
1275
+ normal = wp.normalize(diff)
1276
+ else:
1277
+ normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1278
+ distance = wp.dot(diff, normal)
1279
+ else:
1280
+ # contact between capsule A and edges of finite plane B
1281
+ plane_width = geo_scale_b[0]
1282
+ plane_length = geo_scale_b[1]
1283
+ edge = get_plane_edge(point_id - 2, plane_width, plane_length)
1284
+ edge0_world = wp.transform_point(X_ws_b, wp.spatial_top(edge))
1285
+ edge1_world = wp.transform_point(X_ws_b, wp.spatial_bottom(edge))
1286
+ edge0_a = wp.transform_point(X_sw_a, edge0_world)
1287
+ edge1_a = wp.transform_point(X_sw_a, edge1_world)
1288
+ max_iter = edge_sdf_iter
1289
+ u = closest_edge_coordinate_capsule(geo_scale_a[0], geo_scale_a[1], edge0_a, edge1_a, max_iter)
1290
+ p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1291
+
1292
+ # find closest point + contact normal on capsule A
1293
+ half_height_a = geo_scale_a[1]
1294
+ p0_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, half_height_a, 0.0))
1295
+ p1_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, -half_height_a, 0.0))
1296
+ p_a_world = closest_point_line_segment(p0_a_world, p1_a_world, p_b_world)
1297
+ diff = p_a_world - p_b_world
1298
+ # normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1299
+ normal = wp.normalize(diff)
1300
+ distance = wp.dot(diff, normal)
1301
+
1302
+ elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_BOX:
1303
+ # vertex-based contact
1304
+ mesh = wp.mesh_get(geo.source[shape_a])
1305
+ body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1306
+ p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1307
+ # find closest point + contact normal on box B
1308
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1309
+ p_b_body = closest_point_box(geo_scale_b, query_b)
1310
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1311
+ diff = p_a_world - p_b_world
1312
+ # this is more reliable in practice than using the SDF gradient
1313
+ normal = wp.normalize(diff)
1314
+ if box_sdf(geo_scale_b, query_b) < 0.0:
1315
+ normal = -normal
1316
+ distance = wp.dot(diff, normal)
1317
+
1318
+ elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_MESH:
1319
+ # vertex-based contact
1320
+ query_a = get_box_vertex(point_id, geo_scale_a)
1321
+ p_a_world = wp.transform_point(X_ws_a, query_a)
1322
+ query_b_local = wp.transform_point(X_sw_b, p_a_world)
1323
+ mesh_b = geo.source[shape_b]
1324
+ max_dist = (rigid_contact_margin + thickness) / min_scale_b
1325
+ face_index = int(0)
1326
+ face_u = float(0.0)
1327
+ face_v = float(0.0)
1328
+ sign = float(0.0)
1329
+ res = wp.mesh_query_point_sign_normal(
1330
+ mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1331
+ )
1332
+
1333
+ if res:
1334
+ shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1335
+ shape_p = wp.cw_mul(shape_p, geo_scale_b)
1336
+ p_b_world = wp.transform_point(X_ws_b, shape_p)
1337
+ # contact direction vector in world frame
1338
+ diff_b = p_a_world - p_b_world
1339
+ normal = wp.normalize(diff_b) * sign
1340
+ distance = wp.dot(diff_b, normal)
1341
+ else:
1342
+ return
1343
+
1344
+ elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_MESH:
1345
+ # vertex-based contact
1346
+ mesh = wp.mesh_get(geo.source[shape_a])
1347
+ mesh_b = geo.source[shape_b]
1348
+
1349
+ body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1350
+ p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1351
+ query_b_local = wp.transform_point(X_sw_b, p_a_world)
1352
+
1353
+ face_index = int(0)
1354
+ face_u = float(0.0)
1355
+ face_v = float(0.0)
1356
+ sign = float(0.0)
1357
+ min_scale = min(min_scale_a, min_scale_b)
1358
+ max_dist = (rigid_contact_margin + thickness) / min_scale
1359
+
1360
+ res = wp.mesh_query_point_sign_normal(
1361
+ mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1362
+ )
1363
+
1364
+ if res:
1365
+ shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1366
+ shape_p = wp.cw_mul(shape_p, geo_scale_b)
1367
+ p_b_world = wp.transform_point(X_ws_b, shape_p)
1368
+ # contact direction vector in world frame
1369
+ diff_b = p_a_world - p_b_world
1370
+ normal = wp.normalize(diff_b) * sign
1371
+ distance = wp.dot(diff_b, normal)
1372
+ else:
1373
+ return
1374
+
1375
+ elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_PLANE:
1376
+ # vertex-based contact
1377
+ mesh = wp.mesh_get(geo.source[shape_a])
1378
+ body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1379
+ p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1380
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1381
+ p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], query_b)
1382
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1383
+ diff = p_a_world - p_b_world
1384
+
1385
+ # if the plane is infinite or the point is within the plane we fix the normal to prevent intersections
1386
+ if (
1387
+ geo_scale_b[0] == 0.0
1388
+ and geo_scale_b[1] == 0.0
1389
+ or wp.abs(query_b[0]) < geo_scale_b[0]
1390
+ and wp.abs(query_b[2]) < geo_scale_b[1]
1391
+ ):
1392
+ normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1393
+ distance = wp.dot(diff, normal)
1394
+ else:
1395
+ normal = wp.normalize(diff)
1396
+ distance = wp.dot(diff, normal)
1397
+ # ignore extreme penetrations (e.g. when mesh is below the plane)
1398
+ if distance < -rigid_contact_margin:
1399
+ return
1400
+
1401
+ else:
1402
+ print("Unsupported geometry pair in collision handling")
1403
+ return
1404
+
1405
+ d = distance - thickness
1406
+ if d < rigid_contact_margin:
1407
+ pair_contact_id = limited_counter_increment(
1408
+ contact_pairwise_counter, pair_index, contact_tids, tid, contact_limit
1409
+ )
1410
+ if pair_contact_id == -1:
1411
+ # wp.printf("Reached contact point limit %d >= %d for shape pair %d and %d (pair_index: %d)\n",
1412
+ # contact_pairwise_counter[pair_index], contact_limit, shape_a, shape_b, pair_index)
1413
+ # reached contact point limit
1414
+ return
1415
+ index = limited_counter_increment(contact_count, 0, contact_tids, tid, -1)
1416
+ contact_shape0[index] = shape_a
1417
+ contact_shape1[index] = shape_b
1418
+ # transform from world into body frame (so the contact point includes the shape transform)
1419
+ contact_point0[index] = wp.transform_point(X_bw_a, p_a_world)
1420
+ contact_point1[index] = wp.transform_point(X_bw_b, p_b_world)
1421
+ contact_offset0[index] = wp.transform_vector(X_bw_a, -thickness_a * normal)
1422
+ contact_offset1[index] = wp.transform_vector(X_bw_b, thickness_b * normal)
1423
+ contact_normal[index] = normal
1424
+ contact_thickness[index] = thickness
1425
+
1426
+
1427
+ def collide(model, state, edge_sdf_iter: int = 10, iterate_mesh_vertices: bool = True, requires_grad: bool = None):
1428
+ """
1429
+ Generates contact points for the particles and rigid bodies in the model,
1430
+ to be used in the contact dynamics kernel of the integrator.
1431
+
1432
+ Args:
1433
+ model: the model to be simulated
1434
+ state: the state of the model
1435
+ edge_sdf_iter: number of search iterations for finding closest contact points between edges and SDF
1436
+ iterate_mesh_vertices: whether to iterate over all vertices of a mesh for contact generation (used for capsule/box <> mesh collision)
1437
+ requires_grad: whether to duplicate contact arrays for gradient computation (if None uses model.requires_grad)
1438
+ """
1439
+
1440
+ if requires_grad is None:
1441
+ requires_grad = model.requires_grad
1442
+
1443
+ with wp.ScopedTimer("collide", False):
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
+ )
1478
+
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
+ )
1514
+
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
+ )
1543
+
1544
+ if model.shape_contact_pair_count or model.ground and model.shape_ground_contact_pair_count:
1545
+ if requires_grad:
1546
+ model.rigid_contact_point0 = wp.clone(model.rigid_contact_point0)
1547
+ model.rigid_contact_point1 = wp.clone(model.rigid_contact_point1)
1548
+ model.rigid_contact_offset0 = wp.clone(model.rigid_contact_offset0)
1549
+ model.rigid_contact_offset1 = wp.clone(model.rigid_contact_offset1)
1550
+ model.rigid_contact_normal = wp.clone(model.rigid_contact_normal)
1551
+ model.rigid_contact_thickness = wp.clone(model.rigid_contact_thickness)
1552
+ model.rigid_contact_count = wp.zeros_like(model.rigid_contact_count)
1553
+ model.rigid_contact_pairwise_counter = wp.zeros_like(model.rigid_contact_pairwise_counter)
1554
+ model.rigid_contact_tids = wp.zeros_like(model.rigid_contact_tids)
1555
+ model.rigid_contact_shape0 = wp.empty_like(model.rigid_contact_shape0)
1556
+ model.rigid_contact_shape1 = wp.empty_like(model.rigid_contact_shape1)
1557
+ else:
1558
+ model.rigid_contact_count.zero_()
1559
+ model.rigid_contact_pairwise_counter.zero_()
1560
+ model.rigid_contact_tids.zero_()
1561
+ model.rigid_contact_shape0.fill_(-1)
1562
+ model.rigid_contact_shape1.fill_(-1)
1563
+
1564
+ wp.launch(
1565
+ kernel=handle_contact_pairs,
1566
+ dim=model.rigid_contact_max,
1567
+ inputs=[
1568
+ state.body_q,
1569
+ model.shape_transform,
1570
+ model.shape_body,
1571
+ model.shape_geo,
1572
+ model.rigid_contact_margin,
1573
+ model.rigid_contact_broad_shape0,
1574
+ model.rigid_contact_broad_shape1,
1575
+ model.shape_count,
1576
+ model.rigid_contact_point_id,
1577
+ model.rigid_contact_point_limit,
1578
+ edge_sdf_iter,
1579
+ ],
1580
+ outputs=[
1581
+ model.rigid_contact_count,
1582
+ model.rigid_contact_shape0,
1583
+ model.rigid_contact_shape1,
1584
+ model.rigid_contact_point0,
1585
+ model.rigid_contact_point1,
1586
+ model.rigid_contact_offset0,
1587
+ model.rigid_contact_offset1,
1588
+ model.rigid_contact_normal,
1589
+ model.rigid_contact_thickness,
1590
+ model.rigid_contact_pairwise_counter,
1591
+ model.rigid_contact_tids,
1592
+ ],
1593
+ device=model.device,
1594
+ )