warp-lang 1.0.2__py3-none-manylinux2014_x86_64.whl → 1.2.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 (356) 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 +88 -113
  6. warp/build_dll.py +383 -375
  7. warp/builtins.py +3693 -3354
  8. warp/codegen.py +2925 -2792
  9. warp/config.py +40 -36
  10. warp/constants.py +49 -45
  11. warp/context.py +5409 -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 +381 -383
  26. warp/examples/benchmarks/benchmark_cloth.py +278 -277
  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 +145 -146
  34. warp/examples/benchmarks/benchmark_launches.py +293 -295
  35. warp/examples/browse.py +29 -29
  36. warp/examples/core/example_dem.py +232 -219
  37. warp/examples/core/example_fluid.py +291 -267
  38. warp/examples/core/example_graph_capture.py +142 -126
  39. warp/examples/core/example_marching_cubes.py +186 -174
  40. warp/examples/core/example_mesh.py +172 -155
  41. warp/examples/core/example_mesh_intersect.py +203 -193
  42. warp/examples/core/example_nvdb.py +174 -170
  43. warp/examples/core/example_raycast.py +103 -90
  44. warp/examples/core/example_raymarch.py +197 -178
  45. warp/examples/core/example_render_opengl.py +183 -141
  46. warp/examples/core/example_sph.py +403 -387
  47. warp/examples/core/example_torch.py +219 -181
  48. warp/examples/core/example_wave.py +261 -248
  49. warp/examples/fem/bsr_utils.py +378 -380
  50. warp/examples/fem/example_apic_fluid.py +432 -389
  51. warp/examples/fem/example_burgers.py +262 -0
  52. warp/examples/fem/example_convection_diffusion.py +180 -168
  53. warp/examples/fem/example_convection_diffusion_dg.py +217 -209
  54. warp/examples/fem/example_deformed_geometry.py +175 -159
  55. warp/examples/fem/example_diffusion.py +199 -173
  56. warp/examples/fem/example_diffusion_3d.py +178 -152
  57. warp/examples/fem/example_diffusion_mgpu.py +219 -214
  58. warp/examples/fem/example_mixed_elasticity.py +242 -222
  59. warp/examples/fem/example_navier_stokes.py +257 -243
  60. warp/examples/fem/example_stokes.py +218 -192
  61. warp/examples/fem/example_stokes_transfer.py +263 -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 +258 -246
  65. warp/examples/optim/example_cloth_throw.py +220 -209
  66. warp/examples/optim/example_diffray.py +564 -536
  67. warp/examples/optim/example_drone.py +862 -835
  68. warp/examples/optim/example_inverse_kinematics.py +174 -168
  69. warp/examples/optim/example_inverse_kinematics_torch.py +183 -169
  70. warp/examples/optim/example_spring_cage.py +237 -231
  71. warp/examples/optim/example_trajectory.py +221 -199
  72. warp/examples/optim/example_walker.py +304 -293
  73. warp/examples/sim/example_cartpole.py +137 -129
  74. warp/examples/sim/example_cloth.py +194 -186
  75. warp/examples/sim/example_granular.py +122 -111
  76. warp/examples/sim/example_granular_collision_sdf.py +195 -186
  77. warp/examples/sim/example_jacobian_ik.py +234 -214
  78. warp/examples/sim/example_particle_chain.py +116 -105
  79. warp/examples/sim/example_quadruped.py +191 -180
  80. warp/examples/sim/example_rigid_chain.py +195 -187
  81. warp/examples/sim/example_rigid_contact.py +187 -177
  82. warp/examples/sim/example_rigid_force.py +125 -125
  83. warp/examples/sim/example_rigid_gyroscopic.py +107 -95
  84. warp/examples/sim/example_rigid_soft_contact.py +132 -122
  85. warp/examples/sim/example_soft_body.py +188 -177
  86. warp/fabric.py +337 -335
  87. warp/fem/__init__.py +61 -27
  88. warp/fem/cache.py +403 -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 +16 -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 +748 -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 +437 -435
  104. warp/fem/geometry/hexmesh.py +953 -953
  105. warp/fem/geometry/nanogrid.py +455 -0
  106. warp/fem/geometry/partition.py +374 -376
  107. warp/fem/geometry/quadmesh_2d.py +532 -532
  108. warp/fem/geometry/tetmesh.py +840 -840
  109. warp/fem/geometry/trimesh_2d.py +577 -577
  110. warp/fem/integrate.py +1684 -1615
  111. warp/fem/operator.py +190 -191
  112. warp/fem/polynomial.py +214 -213
  113. warp/fem/quadrature/__init__.py +2 -2
  114. warp/fem/quadrature/pic_quadrature.py +243 -245
  115. warp/fem/quadrature/quadrature.py +295 -294
  116. warp/fem/space/__init__.py +179 -292
  117. warp/fem/space/basis_space.py +522 -489
  118. warp/fem/space/collocated_function_space.py +100 -105
  119. warp/fem/space/dof_mapper.py +236 -236
  120. warp/fem/space/function_space.py +148 -145
  121. warp/fem/space/grid_2d_function_space.py +148 -267
  122. warp/fem/space/grid_3d_function_space.py +167 -306
  123. warp/fem/space/hexmesh_function_space.py +253 -352
  124. warp/fem/space/nanogrid_function_space.py +202 -0
  125. warp/fem/space/partition.py +350 -350
  126. warp/fem/space/quadmesh_2d_function_space.py +261 -369
  127. warp/fem/space/restriction.py +161 -160
  128. warp/fem/space/shape/__init__.py +90 -15
  129. warp/fem/space/shape/cube_shape_function.py +728 -738
  130. warp/fem/space/shape/shape_function.py +102 -103
  131. warp/fem/space/shape/square_shape_function.py +611 -611
  132. warp/fem/space/shape/tet_shape_function.py +565 -567
  133. warp/fem/space/shape/triangle_shape_function.py +429 -429
  134. warp/fem/space/tetmesh_function_space.py +224 -292
  135. warp/fem/space/topology.py +297 -295
  136. warp/fem/space/trimesh_2d_function_space.py +153 -221
  137. warp/fem/types.py +77 -77
  138. warp/fem/utils.py +495 -495
  139. warp/jax.py +166 -141
  140. warp/jax_experimental.py +341 -339
  141. warp/native/array.h +1081 -1025
  142. warp/native/builtin.h +1603 -1560
  143. warp/native/bvh.cpp +402 -398
  144. warp/native/bvh.cu +533 -525
  145. warp/native/bvh.h +430 -429
  146. warp/native/clang/clang.cpp +496 -464
  147. warp/native/crt.cpp +42 -32
  148. warp/native/crt.h +352 -335
  149. warp/native/cuda_crt.h +1049 -1049
  150. warp/native/cuda_util.cpp +549 -540
  151. warp/native/cuda_util.h +288 -203
  152. warp/native/cutlass_gemm.cpp +34 -34
  153. warp/native/cutlass_gemm.cu +372 -372
  154. warp/native/error.cpp +66 -66
  155. warp/native/error.h +27 -27
  156. warp/native/exports.h +187 -0
  157. warp/native/fabric.h +228 -228
  158. warp/native/hashgrid.cpp +301 -278
  159. warp/native/hashgrid.cu +78 -77
  160. warp/native/hashgrid.h +227 -227
  161. warp/native/initializer_array.h +32 -32
  162. warp/native/intersect.h +1204 -1204
  163. warp/native/intersect_adj.h +365 -365
  164. warp/native/intersect_tri.h +322 -322
  165. warp/native/marching.cpp +2 -2
  166. warp/native/marching.cu +497 -497
  167. warp/native/marching.h +2 -2
  168. warp/native/mat.h +1545 -1498
  169. warp/native/matnn.h +333 -333
  170. warp/native/mesh.cpp +203 -203
  171. warp/native/mesh.cu +292 -293
  172. warp/native/mesh.h +1887 -1887
  173. warp/native/nanovdb/GridHandle.h +366 -0
  174. warp/native/nanovdb/HostBuffer.h +590 -0
  175. warp/native/nanovdb/NanoVDB.h +6624 -4782
  176. warp/native/nanovdb/PNanoVDB.h +3390 -2553
  177. warp/native/noise.h +850 -850
  178. warp/native/quat.h +1112 -1085
  179. warp/native/rand.h +303 -299
  180. warp/native/range.h +108 -108
  181. warp/native/reduce.cpp +156 -156
  182. warp/native/reduce.cu +348 -348
  183. warp/native/runlength_encode.cpp +61 -61
  184. warp/native/runlength_encode.cu +46 -46
  185. warp/native/scan.cpp +30 -30
  186. warp/native/scan.cu +36 -36
  187. warp/native/scan.h +7 -7
  188. warp/native/solid_angle.h +442 -442
  189. warp/native/sort.cpp +94 -94
  190. warp/native/sort.cu +97 -97
  191. warp/native/sort.h +14 -14
  192. warp/native/sparse.cpp +337 -337
  193. warp/native/sparse.cu +544 -544
  194. warp/native/spatial.h +630 -630
  195. warp/native/svd.h +562 -562
  196. warp/native/temp_buffer.h +30 -30
  197. warp/native/vec.h +1177 -1133
  198. warp/native/volume.cpp +529 -297
  199. warp/native/volume.cu +58 -32
  200. warp/native/volume.h +960 -538
  201. warp/native/volume_builder.cu +446 -425
  202. warp/native/volume_builder.h +34 -19
  203. warp/native/volume_impl.h +61 -0
  204. warp/native/warp.cpp +1057 -1052
  205. warp/native/warp.cu +2949 -2828
  206. warp/native/warp.h +321 -305
  207. warp/optim/__init__.py +9 -9
  208. warp/optim/adam.py +120 -120
  209. warp/optim/linear.py +1104 -939
  210. warp/optim/sgd.py +104 -92
  211. warp/render/__init__.py +10 -10
  212. warp/render/render_opengl.py +3356 -3204
  213. warp/render/render_usd.py +768 -749
  214. warp/render/utils.py +152 -150
  215. warp/sim/__init__.py +52 -59
  216. warp/sim/articulation.py +685 -685
  217. warp/sim/collide.py +1594 -1590
  218. warp/sim/import_mjcf.py +489 -481
  219. warp/sim/import_snu.py +220 -221
  220. warp/sim/import_urdf.py +536 -516
  221. warp/sim/import_usd.py +887 -881
  222. warp/sim/inertia.py +316 -317
  223. warp/sim/integrator.py +234 -233
  224. warp/sim/integrator_euler.py +1956 -1956
  225. warp/sim/integrator_featherstone.py +1917 -1991
  226. warp/sim/integrator_xpbd.py +3288 -3312
  227. warp/sim/model.py +4473 -4314
  228. warp/sim/particles.py +113 -112
  229. warp/sim/render.py +417 -403
  230. warp/sim/utils.py +413 -410
  231. warp/sparse.py +1289 -1227
  232. warp/stubs.py +2192 -2469
  233. warp/tape.py +1162 -225
  234. warp/tests/__init__.py +1 -1
  235. warp/tests/__main__.py +4 -4
  236. warp/tests/assets/test_index_grid.nvdb +0 -0
  237. warp/tests/assets/torus.usda +105 -105
  238. warp/tests/aux_test_class_kernel.py +26 -26
  239. warp/tests/aux_test_compile_consts_dummy.py +10 -10
  240. warp/tests/aux_test_conditional_unequal_types_kernels.py +21 -21
  241. warp/tests/aux_test_dependent.py +20 -22
  242. warp/tests/aux_test_grad_customs.py +21 -23
  243. warp/tests/aux_test_reference.py +9 -11
  244. warp/tests/aux_test_reference_reference.py +8 -10
  245. warp/tests/aux_test_square.py +15 -17
  246. warp/tests/aux_test_unresolved_func.py +14 -14
  247. warp/tests/aux_test_unresolved_symbol.py +14 -14
  248. warp/tests/disabled_kinematics.py +237 -239
  249. warp/tests/run_coverage_serial.py +31 -31
  250. warp/tests/test_adam.py +155 -157
  251. warp/tests/test_arithmetic.py +1088 -1124
  252. warp/tests/test_array.py +2415 -2326
  253. warp/tests/test_array_reduce.py +148 -150
  254. warp/tests/test_async.py +666 -656
  255. warp/tests/test_atomic.py +139 -141
  256. warp/tests/test_bool.py +212 -149
  257. warp/tests/test_builtins_resolution.py +1290 -1292
  258. warp/tests/test_bvh.py +162 -171
  259. warp/tests/test_closest_point_edge_edge.py +227 -228
  260. warp/tests/test_codegen.py +562 -553
  261. warp/tests/test_compile_consts.py +217 -101
  262. warp/tests/test_conditional.py +244 -246
  263. warp/tests/test_copy.py +230 -215
  264. warp/tests/test_ctypes.py +630 -632
  265. warp/tests/test_dense.py +65 -67
  266. warp/tests/test_devices.py +89 -98
  267. warp/tests/test_dlpack.py +528 -529
  268. warp/tests/test_examples.py +403 -378
  269. warp/tests/test_fabricarray.py +952 -955
  270. warp/tests/test_fast_math.py +60 -54
  271. warp/tests/test_fem.py +1298 -1278
  272. warp/tests/test_fp16.py +128 -130
  273. warp/tests/test_func.py +336 -337
  274. warp/tests/test_generics.py +596 -571
  275. warp/tests/test_grad.py +885 -640
  276. warp/tests/test_grad_customs.py +331 -336
  277. warp/tests/test_hash_grid.py +208 -164
  278. warp/tests/test_import.py +37 -39
  279. warp/tests/test_indexedarray.py +1132 -1134
  280. warp/tests/test_intersect.py +65 -67
  281. warp/tests/test_jax.py +305 -307
  282. warp/tests/test_large.py +169 -164
  283. warp/tests/test_launch.py +352 -354
  284. warp/tests/test_lerp.py +217 -261
  285. warp/tests/test_linear_solvers.py +189 -171
  286. warp/tests/test_lvalue.py +419 -493
  287. warp/tests/test_marching_cubes.py +63 -65
  288. warp/tests/test_mat.py +1799 -1827
  289. warp/tests/test_mat_lite.py +113 -115
  290. warp/tests/test_mat_scalar_ops.py +2905 -2889
  291. warp/tests/test_math.py +124 -193
  292. warp/tests/test_matmul.py +498 -499
  293. warp/tests/test_matmul_lite.py +408 -410
  294. warp/tests/test_mempool.py +186 -190
  295. warp/tests/test_mesh.py +281 -324
  296. warp/tests/test_mesh_query_aabb.py +226 -241
  297. warp/tests/test_mesh_query_point.py +690 -702
  298. warp/tests/test_mesh_query_ray.py +290 -303
  299. warp/tests/test_mlp.py +274 -276
  300. warp/tests/test_model.py +108 -110
  301. warp/tests/test_module_hashing.py +111 -0
  302. warp/tests/test_modules_lite.py +36 -39
  303. warp/tests/test_multigpu.py +161 -163
  304. warp/tests/test_noise.py +244 -248
  305. warp/tests/test_operators.py +248 -250
  306. warp/tests/test_options.py +121 -125
  307. warp/tests/test_peer.py +131 -137
  308. warp/tests/test_pinned.py +76 -78
  309. warp/tests/test_print.py +52 -54
  310. warp/tests/test_quat.py +2084 -2086
  311. warp/tests/test_rand.py +324 -288
  312. warp/tests/test_reload.py +207 -217
  313. warp/tests/test_rounding.py +177 -179
  314. warp/tests/test_runlength_encode.py +188 -190
  315. warp/tests/test_sim_grad.py +241 -0
  316. warp/tests/test_sim_kinematics.py +89 -97
  317. warp/tests/test_smoothstep.py +166 -168
  318. warp/tests/test_snippet.py +303 -266
  319. warp/tests/test_sparse.py +466 -460
  320. warp/tests/test_spatial.py +2146 -2148
  321. warp/tests/test_special_values.py +362 -0
  322. warp/tests/test_streams.py +484 -473
  323. warp/tests/test_struct.py +708 -675
  324. warp/tests/test_tape.py +171 -148
  325. warp/tests/test_torch.py +741 -743
  326. warp/tests/test_transient_module.py +85 -87
  327. warp/tests/test_types.py +554 -659
  328. warp/tests/test_utils.py +488 -499
  329. warp/tests/test_vec.py +1262 -1268
  330. warp/tests/test_vec_lite.py +71 -73
  331. warp/tests/test_vec_scalar_ops.py +2097 -2099
  332. warp/tests/test_verify_fp.py +92 -94
  333. warp/tests/test_volume.py +961 -736
  334. warp/tests/test_volume_write.py +338 -265
  335. warp/tests/unittest_serial.py +38 -37
  336. warp/tests/unittest_suites.py +367 -359
  337. warp/tests/unittest_utils.py +434 -578
  338. warp/tests/unused_test_misc.py +69 -71
  339. warp/tests/walkthrough_debug.py +85 -85
  340. warp/thirdparty/appdirs.py +598 -598
  341. warp/thirdparty/dlpack.py +143 -143
  342. warp/thirdparty/unittest_parallel.py +563 -561
  343. warp/torch.py +321 -295
  344. warp/types.py +4941 -4450
  345. warp/utils.py +1008 -821
  346. {warp_lang-1.0.2.dist-info → warp_lang-1.2.0.dist-info}/LICENSE.md +126 -126
  347. {warp_lang-1.0.2.dist-info → warp_lang-1.2.0.dist-info}/METADATA +365 -400
  348. warp_lang-1.2.0.dist-info/RECORD +359 -0
  349. warp/examples/assets/cube.usda +0 -42
  350. warp/examples/assets/sphere.usda +0 -56
  351. warp/examples/assets/torus.usda +0 -105
  352. warp/examples/fem/example_convection_diffusion_dg0.py +0 -194
  353. warp/native/nanovdb/PNanoVDBWrite.h +0 -295
  354. warp_lang-1.0.2.dist-info/RECORD +0 -352
  355. {warp_lang-1.0.2.dist-info → warp_lang-1.2.0.dist-info}/WHEEL +0 -0
  356. {warp_lang-1.0.2.dist-info → warp_lang-1.2.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
+ )