warp-lang 1.7.0__py3-none-manylinux_2_28_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 (429) hide show
  1. warp/__init__.py +139 -0
  2. warp/__init__.pyi +1 -0
  3. warp/autograd.py +1142 -0
  4. warp/bin/warp-clang.so +0 -0
  5. warp/bin/warp.so +0 -0
  6. warp/build.py +557 -0
  7. warp/build_dll.py +405 -0
  8. warp/builtins.py +6855 -0
  9. warp/codegen.py +3969 -0
  10. warp/config.py +158 -0
  11. warp/constants.py +57 -0
  12. warp/context.py +6812 -0
  13. warp/dlpack.py +462 -0
  14. warp/examples/__init__.py +24 -0
  15. warp/examples/assets/bear.usd +0 -0
  16. warp/examples/assets/bunny.usd +0 -0
  17. warp/examples/assets/cartpole.urdf +110 -0
  18. warp/examples/assets/crazyflie.usd +0 -0
  19. warp/examples/assets/cube.usd +0 -0
  20. warp/examples/assets/nonuniform.usd +0 -0
  21. warp/examples/assets/nv_ant.xml +92 -0
  22. warp/examples/assets/nv_humanoid.xml +183 -0
  23. warp/examples/assets/nvidia_logo.png +0 -0
  24. warp/examples/assets/pixel.jpg +0 -0
  25. warp/examples/assets/quadruped.urdf +268 -0
  26. warp/examples/assets/rocks.nvdb +0 -0
  27. warp/examples/assets/rocks.usd +0 -0
  28. warp/examples/assets/sphere.usd +0 -0
  29. warp/examples/assets/square_cloth.usd +0 -0
  30. warp/examples/benchmarks/benchmark_api.py +389 -0
  31. warp/examples/benchmarks/benchmark_cloth.py +296 -0
  32. warp/examples/benchmarks/benchmark_cloth_cupy.py +96 -0
  33. warp/examples/benchmarks/benchmark_cloth_jax.py +105 -0
  34. warp/examples/benchmarks/benchmark_cloth_numba.py +161 -0
  35. warp/examples/benchmarks/benchmark_cloth_numpy.py +85 -0
  36. warp/examples/benchmarks/benchmark_cloth_paddle.py +94 -0
  37. warp/examples/benchmarks/benchmark_cloth_pytorch.py +94 -0
  38. warp/examples/benchmarks/benchmark_cloth_taichi.py +120 -0
  39. warp/examples/benchmarks/benchmark_cloth_warp.py +153 -0
  40. warp/examples/benchmarks/benchmark_gemm.py +164 -0
  41. warp/examples/benchmarks/benchmark_interop_paddle.py +166 -0
  42. warp/examples/benchmarks/benchmark_interop_torch.py +166 -0
  43. warp/examples/benchmarks/benchmark_launches.py +301 -0
  44. warp/examples/benchmarks/benchmark_tile_load_store.py +103 -0
  45. warp/examples/browse.py +37 -0
  46. warp/examples/core/example_cupy.py +86 -0
  47. warp/examples/core/example_dem.py +241 -0
  48. warp/examples/core/example_fluid.py +299 -0
  49. warp/examples/core/example_graph_capture.py +150 -0
  50. warp/examples/core/example_marching_cubes.py +194 -0
  51. warp/examples/core/example_mesh.py +180 -0
  52. warp/examples/core/example_mesh_intersect.py +211 -0
  53. warp/examples/core/example_nvdb.py +182 -0
  54. warp/examples/core/example_raycast.py +111 -0
  55. warp/examples/core/example_raymarch.py +205 -0
  56. warp/examples/core/example_render_opengl.py +193 -0
  57. warp/examples/core/example_sample_mesh.py +300 -0
  58. warp/examples/core/example_sph.py +411 -0
  59. warp/examples/core/example_torch.py +211 -0
  60. warp/examples/core/example_wave.py +269 -0
  61. warp/examples/fem/example_adaptive_grid.py +286 -0
  62. warp/examples/fem/example_apic_fluid.py +423 -0
  63. warp/examples/fem/example_burgers.py +261 -0
  64. warp/examples/fem/example_convection_diffusion.py +178 -0
  65. warp/examples/fem/example_convection_diffusion_dg.py +204 -0
  66. warp/examples/fem/example_deformed_geometry.py +172 -0
  67. warp/examples/fem/example_diffusion.py +196 -0
  68. warp/examples/fem/example_diffusion_3d.py +225 -0
  69. warp/examples/fem/example_diffusion_mgpu.py +220 -0
  70. warp/examples/fem/example_distortion_energy.py +228 -0
  71. warp/examples/fem/example_magnetostatics.py +240 -0
  72. warp/examples/fem/example_mixed_elasticity.py +291 -0
  73. warp/examples/fem/example_navier_stokes.py +261 -0
  74. warp/examples/fem/example_nonconforming_contact.py +298 -0
  75. warp/examples/fem/example_stokes.py +213 -0
  76. warp/examples/fem/example_stokes_transfer.py +262 -0
  77. warp/examples/fem/example_streamlines.py +352 -0
  78. warp/examples/fem/utils.py +1000 -0
  79. warp/examples/interop/example_jax_callable.py +116 -0
  80. warp/examples/interop/example_jax_ffi_callback.py +132 -0
  81. warp/examples/interop/example_jax_kernel.py +205 -0
  82. warp/examples/optim/example_bounce.py +266 -0
  83. warp/examples/optim/example_cloth_throw.py +228 -0
  84. warp/examples/optim/example_diffray.py +561 -0
  85. warp/examples/optim/example_drone.py +870 -0
  86. warp/examples/optim/example_fluid_checkpoint.py +497 -0
  87. warp/examples/optim/example_inverse_kinematics.py +182 -0
  88. warp/examples/optim/example_inverse_kinematics_torch.py +191 -0
  89. warp/examples/optim/example_softbody_properties.py +400 -0
  90. warp/examples/optim/example_spring_cage.py +245 -0
  91. warp/examples/optim/example_trajectory.py +227 -0
  92. warp/examples/sim/example_cartpole.py +143 -0
  93. warp/examples/sim/example_cloth.py +225 -0
  94. warp/examples/sim/example_cloth_self_contact.py +322 -0
  95. warp/examples/sim/example_granular.py +130 -0
  96. warp/examples/sim/example_granular_collision_sdf.py +202 -0
  97. warp/examples/sim/example_jacobian_ik.py +244 -0
  98. warp/examples/sim/example_particle_chain.py +124 -0
  99. warp/examples/sim/example_quadruped.py +203 -0
  100. warp/examples/sim/example_rigid_chain.py +203 -0
  101. warp/examples/sim/example_rigid_contact.py +195 -0
  102. warp/examples/sim/example_rigid_force.py +133 -0
  103. warp/examples/sim/example_rigid_gyroscopic.py +115 -0
  104. warp/examples/sim/example_rigid_soft_contact.py +140 -0
  105. warp/examples/sim/example_soft_body.py +196 -0
  106. warp/examples/tile/example_tile_cholesky.py +87 -0
  107. warp/examples/tile/example_tile_convolution.py +66 -0
  108. warp/examples/tile/example_tile_fft.py +55 -0
  109. warp/examples/tile/example_tile_filtering.py +113 -0
  110. warp/examples/tile/example_tile_matmul.py +85 -0
  111. warp/examples/tile/example_tile_mlp.py +383 -0
  112. warp/examples/tile/example_tile_nbody.py +199 -0
  113. warp/examples/tile/example_tile_walker.py +327 -0
  114. warp/fabric.py +355 -0
  115. warp/fem/__init__.py +106 -0
  116. warp/fem/adaptivity.py +508 -0
  117. warp/fem/cache.py +572 -0
  118. warp/fem/dirichlet.py +202 -0
  119. warp/fem/domain.py +411 -0
  120. warp/fem/field/__init__.py +125 -0
  121. warp/fem/field/field.py +619 -0
  122. warp/fem/field/nodal_field.py +326 -0
  123. warp/fem/field/restriction.py +37 -0
  124. warp/fem/field/virtual.py +848 -0
  125. warp/fem/geometry/__init__.py +32 -0
  126. warp/fem/geometry/adaptive_nanogrid.py +857 -0
  127. warp/fem/geometry/closest_point.py +84 -0
  128. warp/fem/geometry/deformed_geometry.py +221 -0
  129. warp/fem/geometry/element.py +776 -0
  130. warp/fem/geometry/geometry.py +362 -0
  131. warp/fem/geometry/grid_2d.py +392 -0
  132. warp/fem/geometry/grid_3d.py +452 -0
  133. warp/fem/geometry/hexmesh.py +911 -0
  134. warp/fem/geometry/nanogrid.py +571 -0
  135. warp/fem/geometry/partition.py +389 -0
  136. warp/fem/geometry/quadmesh.py +663 -0
  137. warp/fem/geometry/tetmesh.py +855 -0
  138. warp/fem/geometry/trimesh.py +806 -0
  139. warp/fem/integrate.py +2335 -0
  140. warp/fem/linalg.py +419 -0
  141. warp/fem/operator.py +293 -0
  142. warp/fem/polynomial.py +229 -0
  143. warp/fem/quadrature/__init__.py +17 -0
  144. warp/fem/quadrature/pic_quadrature.py +299 -0
  145. warp/fem/quadrature/quadrature.py +591 -0
  146. warp/fem/space/__init__.py +228 -0
  147. warp/fem/space/basis_function_space.py +468 -0
  148. warp/fem/space/basis_space.py +667 -0
  149. warp/fem/space/dof_mapper.py +251 -0
  150. warp/fem/space/function_space.py +309 -0
  151. warp/fem/space/grid_2d_function_space.py +177 -0
  152. warp/fem/space/grid_3d_function_space.py +227 -0
  153. warp/fem/space/hexmesh_function_space.py +257 -0
  154. warp/fem/space/nanogrid_function_space.py +201 -0
  155. warp/fem/space/partition.py +367 -0
  156. warp/fem/space/quadmesh_function_space.py +223 -0
  157. warp/fem/space/restriction.py +179 -0
  158. warp/fem/space/shape/__init__.py +143 -0
  159. warp/fem/space/shape/cube_shape_function.py +1105 -0
  160. warp/fem/space/shape/shape_function.py +133 -0
  161. warp/fem/space/shape/square_shape_function.py +926 -0
  162. warp/fem/space/shape/tet_shape_function.py +834 -0
  163. warp/fem/space/shape/triangle_shape_function.py +672 -0
  164. warp/fem/space/tetmesh_function_space.py +271 -0
  165. warp/fem/space/topology.py +424 -0
  166. warp/fem/space/trimesh_function_space.py +194 -0
  167. warp/fem/types.py +99 -0
  168. warp/fem/utils.py +420 -0
  169. warp/jax.py +187 -0
  170. warp/jax_experimental/__init__.py +16 -0
  171. warp/jax_experimental/custom_call.py +351 -0
  172. warp/jax_experimental/ffi.py +698 -0
  173. warp/jax_experimental/xla_ffi.py +602 -0
  174. warp/math.py +244 -0
  175. warp/native/array.h +1145 -0
  176. warp/native/builtin.h +1800 -0
  177. warp/native/bvh.cpp +492 -0
  178. warp/native/bvh.cu +791 -0
  179. warp/native/bvh.h +554 -0
  180. warp/native/clang/clang.cpp +536 -0
  181. warp/native/coloring.cpp +613 -0
  182. warp/native/crt.cpp +51 -0
  183. warp/native/crt.h +362 -0
  184. warp/native/cuda_crt.h +1058 -0
  185. warp/native/cuda_util.cpp +646 -0
  186. warp/native/cuda_util.h +307 -0
  187. warp/native/error.cpp +77 -0
  188. warp/native/error.h +36 -0
  189. warp/native/exports.h +1878 -0
  190. warp/native/fabric.h +245 -0
  191. warp/native/hashgrid.cpp +311 -0
  192. warp/native/hashgrid.cu +87 -0
  193. warp/native/hashgrid.h +240 -0
  194. warp/native/initializer_array.h +41 -0
  195. warp/native/intersect.h +1230 -0
  196. warp/native/intersect_adj.h +375 -0
  197. warp/native/intersect_tri.h +339 -0
  198. warp/native/marching.cpp +19 -0
  199. warp/native/marching.cu +514 -0
  200. warp/native/marching.h +19 -0
  201. warp/native/mat.h +2220 -0
  202. warp/native/mathdx.cpp +87 -0
  203. warp/native/matnn.h +343 -0
  204. warp/native/mesh.cpp +266 -0
  205. warp/native/mesh.cu +404 -0
  206. warp/native/mesh.h +1980 -0
  207. warp/native/nanovdb/GridHandle.h +366 -0
  208. warp/native/nanovdb/HostBuffer.h +590 -0
  209. warp/native/nanovdb/NanoVDB.h +6624 -0
  210. warp/native/nanovdb/PNanoVDB.h +3390 -0
  211. warp/native/noise.h +859 -0
  212. warp/native/quat.h +1371 -0
  213. warp/native/rand.h +342 -0
  214. warp/native/range.h +139 -0
  215. warp/native/reduce.cpp +174 -0
  216. warp/native/reduce.cu +364 -0
  217. warp/native/runlength_encode.cpp +79 -0
  218. warp/native/runlength_encode.cu +61 -0
  219. warp/native/scan.cpp +47 -0
  220. warp/native/scan.cu +53 -0
  221. warp/native/scan.h +23 -0
  222. warp/native/solid_angle.h +466 -0
  223. warp/native/sort.cpp +251 -0
  224. warp/native/sort.cu +277 -0
  225. warp/native/sort.h +33 -0
  226. warp/native/sparse.cpp +378 -0
  227. warp/native/sparse.cu +524 -0
  228. warp/native/spatial.h +657 -0
  229. warp/native/svd.h +702 -0
  230. warp/native/temp_buffer.h +46 -0
  231. warp/native/tile.h +2584 -0
  232. warp/native/tile_reduce.h +264 -0
  233. warp/native/vec.h +1426 -0
  234. warp/native/volume.cpp +501 -0
  235. warp/native/volume.cu +67 -0
  236. warp/native/volume.h +969 -0
  237. warp/native/volume_builder.cu +477 -0
  238. warp/native/volume_builder.h +52 -0
  239. warp/native/volume_impl.h +70 -0
  240. warp/native/warp.cpp +1082 -0
  241. warp/native/warp.cu +3636 -0
  242. warp/native/warp.h +381 -0
  243. warp/optim/__init__.py +17 -0
  244. warp/optim/adam.py +163 -0
  245. warp/optim/linear.py +1137 -0
  246. warp/optim/sgd.py +112 -0
  247. warp/paddle.py +407 -0
  248. warp/render/__init__.py +18 -0
  249. warp/render/render_opengl.py +3518 -0
  250. warp/render/render_usd.py +784 -0
  251. warp/render/utils.py +160 -0
  252. warp/sim/__init__.py +65 -0
  253. warp/sim/articulation.py +793 -0
  254. warp/sim/collide.py +2395 -0
  255. warp/sim/graph_coloring.py +300 -0
  256. warp/sim/import_mjcf.py +790 -0
  257. warp/sim/import_snu.py +227 -0
  258. warp/sim/import_urdf.py +579 -0
  259. warp/sim/import_usd.py +894 -0
  260. warp/sim/inertia.py +324 -0
  261. warp/sim/integrator.py +242 -0
  262. warp/sim/integrator_euler.py +1997 -0
  263. warp/sim/integrator_featherstone.py +2101 -0
  264. warp/sim/integrator_vbd.py +2048 -0
  265. warp/sim/integrator_xpbd.py +3292 -0
  266. warp/sim/model.py +4791 -0
  267. warp/sim/particles.py +121 -0
  268. warp/sim/render.py +427 -0
  269. warp/sim/utils.py +428 -0
  270. warp/sparse.py +2057 -0
  271. warp/stubs.py +3333 -0
  272. warp/tape.py +1203 -0
  273. warp/tests/__init__.py +1 -0
  274. warp/tests/__main__.py +4 -0
  275. warp/tests/assets/curlnoise_golden.npy +0 -0
  276. warp/tests/assets/mlp_golden.npy +0 -0
  277. warp/tests/assets/pixel.npy +0 -0
  278. warp/tests/assets/pnoise_golden.npy +0 -0
  279. warp/tests/assets/spiky.usd +0 -0
  280. warp/tests/assets/test_grid.nvdb +0 -0
  281. warp/tests/assets/test_index_grid.nvdb +0 -0
  282. warp/tests/assets/test_int32_grid.nvdb +0 -0
  283. warp/tests/assets/test_vec_grid.nvdb +0 -0
  284. warp/tests/assets/torus.nvdb +0 -0
  285. warp/tests/assets/torus.usda +105 -0
  286. warp/tests/aux_test_class_kernel.py +34 -0
  287. warp/tests/aux_test_compile_consts_dummy.py +18 -0
  288. warp/tests/aux_test_conditional_unequal_types_kernels.py +29 -0
  289. warp/tests/aux_test_dependent.py +29 -0
  290. warp/tests/aux_test_grad_customs.py +29 -0
  291. warp/tests/aux_test_instancing_gc.py +26 -0
  292. warp/tests/aux_test_module_unload.py +23 -0
  293. warp/tests/aux_test_name_clash1.py +40 -0
  294. warp/tests/aux_test_name_clash2.py +40 -0
  295. warp/tests/aux_test_reference.py +9 -0
  296. warp/tests/aux_test_reference_reference.py +8 -0
  297. warp/tests/aux_test_square.py +16 -0
  298. warp/tests/aux_test_unresolved_func.py +22 -0
  299. warp/tests/aux_test_unresolved_symbol.py +22 -0
  300. warp/tests/cuda/__init__.py +0 -0
  301. warp/tests/cuda/test_async.py +676 -0
  302. warp/tests/cuda/test_ipc.py +124 -0
  303. warp/tests/cuda/test_mempool.py +233 -0
  304. warp/tests/cuda/test_multigpu.py +169 -0
  305. warp/tests/cuda/test_peer.py +139 -0
  306. warp/tests/cuda/test_pinned.py +84 -0
  307. warp/tests/cuda/test_streams.py +634 -0
  308. warp/tests/geometry/__init__.py +0 -0
  309. warp/tests/geometry/test_bvh.py +200 -0
  310. warp/tests/geometry/test_hash_grid.py +221 -0
  311. warp/tests/geometry/test_marching_cubes.py +74 -0
  312. warp/tests/geometry/test_mesh.py +316 -0
  313. warp/tests/geometry/test_mesh_query_aabb.py +399 -0
  314. warp/tests/geometry/test_mesh_query_point.py +932 -0
  315. warp/tests/geometry/test_mesh_query_ray.py +311 -0
  316. warp/tests/geometry/test_volume.py +1103 -0
  317. warp/tests/geometry/test_volume_write.py +346 -0
  318. warp/tests/interop/__init__.py +0 -0
  319. warp/tests/interop/test_dlpack.py +729 -0
  320. warp/tests/interop/test_jax.py +371 -0
  321. warp/tests/interop/test_paddle.py +800 -0
  322. warp/tests/interop/test_torch.py +1001 -0
  323. warp/tests/run_coverage_serial.py +39 -0
  324. warp/tests/sim/__init__.py +0 -0
  325. warp/tests/sim/disabled_kinematics.py +244 -0
  326. warp/tests/sim/flaky_test_sim_grad.py +290 -0
  327. warp/tests/sim/test_collision.py +604 -0
  328. warp/tests/sim/test_coloring.py +258 -0
  329. warp/tests/sim/test_model.py +224 -0
  330. warp/tests/sim/test_sim_grad_bounce_linear.py +212 -0
  331. warp/tests/sim/test_sim_kinematics.py +98 -0
  332. warp/tests/sim/test_vbd.py +597 -0
  333. warp/tests/test_adam.py +163 -0
  334. warp/tests/test_arithmetic.py +1096 -0
  335. warp/tests/test_array.py +2972 -0
  336. warp/tests/test_array_reduce.py +156 -0
  337. warp/tests/test_assert.py +250 -0
  338. warp/tests/test_atomic.py +153 -0
  339. warp/tests/test_bool.py +220 -0
  340. warp/tests/test_builtins_resolution.py +1298 -0
  341. warp/tests/test_closest_point_edge_edge.py +327 -0
  342. warp/tests/test_codegen.py +810 -0
  343. warp/tests/test_codegen_instancing.py +1495 -0
  344. warp/tests/test_compile_consts.py +215 -0
  345. warp/tests/test_conditional.py +252 -0
  346. warp/tests/test_context.py +42 -0
  347. warp/tests/test_copy.py +238 -0
  348. warp/tests/test_ctypes.py +638 -0
  349. warp/tests/test_dense.py +73 -0
  350. warp/tests/test_devices.py +97 -0
  351. warp/tests/test_examples.py +482 -0
  352. warp/tests/test_fabricarray.py +996 -0
  353. warp/tests/test_fast_math.py +74 -0
  354. warp/tests/test_fem.py +2003 -0
  355. warp/tests/test_fp16.py +136 -0
  356. warp/tests/test_func.py +454 -0
  357. warp/tests/test_future_annotations.py +98 -0
  358. warp/tests/test_generics.py +656 -0
  359. warp/tests/test_grad.py +893 -0
  360. warp/tests/test_grad_customs.py +339 -0
  361. warp/tests/test_grad_debug.py +341 -0
  362. warp/tests/test_implicit_init.py +411 -0
  363. warp/tests/test_import.py +45 -0
  364. warp/tests/test_indexedarray.py +1140 -0
  365. warp/tests/test_intersect.py +73 -0
  366. warp/tests/test_iter.py +76 -0
  367. warp/tests/test_large.py +177 -0
  368. warp/tests/test_launch.py +411 -0
  369. warp/tests/test_lerp.py +151 -0
  370. warp/tests/test_linear_solvers.py +193 -0
  371. warp/tests/test_lvalue.py +427 -0
  372. warp/tests/test_mat.py +2089 -0
  373. warp/tests/test_mat_lite.py +122 -0
  374. warp/tests/test_mat_scalar_ops.py +2913 -0
  375. warp/tests/test_math.py +178 -0
  376. warp/tests/test_mlp.py +282 -0
  377. warp/tests/test_module_hashing.py +258 -0
  378. warp/tests/test_modules_lite.py +44 -0
  379. warp/tests/test_noise.py +252 -0
  380. warp/tests/test_operators.py +299 -0
  381. warp/tests/test_options.py +129 -0
  382. warp/tests/test_overwrite.py +551 -0
  383. warp/tests/test_print.py +339 -0
  384. warp/tests/test_quat.py +2315 -0
  385. warp/tests/test_rand.py +339 -0
  386. warp/tests/test_reload.py +302 -0
  387. warp/tests/test_rounding.py +185 -0
  388. warp/tests/test_runlength_encode.py +196 -0
  389. warp/tests/test_scalar_ops.py +105 -0
  390. warp/tests/test_smoothstep.py +108 -0
  391. warp/tests/test_snippet.py +318 -0
  392. warp/tests/test_sparse.py +582 -0
  393. warp/tests/test_spatial.py +2229 -0
  394. warp/tests/test_special_values.py +361 -0
  395. warp/tests/test_static.py +592 -0
  396. warp/tests/test_struct.py +734 -0
  397. warp/tests/test_tape.py +204 -0
  398. warp/tests/test_transient_module.py +93 -0
  399. warp/tests/test_triangle_closest_point.py +145 -0
  400. warp/tests/test_types.py +562 -0
  401. warp/tests/test_utils.py +588 -0
  402. warp/tests/test_vec.py +1487 -0
  403. warp/tests/test_vec_lite.py +80 -0
  404. warp/tests/test_vec_scalar_ops.py +2327 -0
  405. warp/tests/test_verify_fp.py +100 -0
  406. warp/tests/tile/__init__.py +0 -0
  407. warp/tests/tile/test_tile.py +780 -0
  408. warp/tests/tile/test_tile_load.py +407 -0
  409. warp/tests/tile/test_tile_mathdx.py +208 -0
  410. warp/tests/tile/test_tile_mlp.py +402 -0
  411. warp/tests/tile/test_tile_reduce.py +447 -0
  412. warp/tests/tile/test_tile_shared_memory.py +247 -0
  413. warp/tests/tile/test_tile_view.py +173 -0
  414. warp/tests/unittest_serial.py +47 -0
  415. warp/tests/unittest_suites.py +427 -0
  416. warp/tests/unittest_utils.py +468 -0
  417. warp/tests/walkthrough_debug.py +93 -0
  418. warp/thirdparty/__init__.py +0 -0
  419. warp/thirdparty/appdirs.py +598 -0
  420. warp/thirdparty/dlpack.py +145 -0
  421. warp/thirdparty/unittest_parallel.py +570 -0
  422. warp/torch.py +391 -0
  423. warp/types.py +5230 -0
  424. warp/utils.py +1137 -0
  425. warp_lang-1.7.0.dist-info/METADATA +516 -0
  426. warp_lang-1.7.0.dist-info/RECORD +429 -0
  427. warp_lang-1.7.0.dist-info/WHEEL +5 -0
  428. warp_lang-1.7.0.dist-info/licenses/LICENSE.md +202 -0
  429. warp_lang-1.7.0.dist-info/top_level.txt +1 -0
warp/sim/collide.py ADDED
@@ -0,0 +1,2395 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """
17
+ Collision handling functions and kernels.
18
+ """
19
+
20
+ from typing import Optional
21
+
22
+ import numpy as np
23
+
24
+ import warp as wp
25
+ from warp.sim.model import Model, State
26
+
27
+ from .model import PARTICLE_FLAG_ACTIVE, ModelShapeGeometry
28
+
29
+ # types of triangle's closest point to a point
30
+ TRI_CONTACT_FEATURE_VERTEX_A = wp.constant(0)
31
+ TRI_CONTACT_FEATURE_VERTEX_B = wp.constant(1)
32
+ TRI_CONTACT_FEATURE_VERTEX_C = wp.constant(2)
33
+ TRI_CONTACT_FEATURE_EDGE_AB = wp.constant(3)
34
+ TRI_CONTACT_FEATURE_EDGE_AC = wp.constant(4)
35
+ TRI_CONTACT_FEATURE_EDGE_BC = wp.constant(5)
36
+ TRI_CONTACT_FEATURE_FACE_INTERIOR = wp.constant(6)
37
+
38
+ # constants used to access TriMeshCollisionDetector.resize_flags
39
+ VERTEX_COLLISION_BUFFER_OVERFLOW_INDEX = wp.constant(0)
40
+ TRI_COLLISION_BUFFER_OVERFLOW_INDEX = wp.constant(1)
41
+ EDGE_COLLISION_BUFFER_OVERFLOW_INDEX = wp.constant(2)
42
+ TRI_TRI_COLLISION_BUFFER_OVERFLOW_INDEX = wp.constant(3)
43
+
44
+
45
+ @wp.func
46
+ def build_orthonormal_basis(n: wp.vec3):
47
+ """
48
+ Builds an orthonormal basis given a normal vector `n`. Return the two axes that are perpendicular to `n`.
49
+
50
+ :param n: A 3D vector (list or array-like) representing the normal vector
51
+ """
52
+ b1 = wp.vec3()
53
+ b2 = wp.vec3()
54
+ if n[2] < 0.0:
55
+ a = 1.0 / (1.0 - n[2])
56
+ b = n[0] * n[1] * a
57
+ b1[0] = 1.0 - n[0] * n[0] * a
58
+ b1[1] = -b
59
+ b1[2] = n[0]
60
+
61
+ b2[0] = b
62
+ b2[1] = n[1] * n[1] * a - 1.0
63
+ b2[2] = -n[1]
64
+ else:
65
+ a = 1.0 / (1.0 + n[2])
66
+ b = -n[0] * n[1] * a
67
+ b1[0] = 1.0 - n[0] * n[0] * a
68
+ b1[1] = b
69
+ b1[2] = -n[0]
70
+
71
+ b2[0] = b
72
+ b2[1] = 1.0 - n[1] * n[1] * a
73
+ b2[2] = -n[1]
74
+
75
+ return b1, b2
76
+
77
+
78
+ @wp.func
79
+ def triangle_closest_point_barycentric(a: wp.vec3, b: wp.vec3, c: wp.vec3, p: wp.vec3):
80
+ ab = b - a
81
+ ac = c - a
82
+ ap = p - a
83
+
84
+ d1 = wp.dot(ab, ap)
85
+ d2 = wp.dot(ac, ap)
86
+
87
+ if d1 <= 0.0 and d2 <= 0.0:
88
+ return wp.vec3(1.0, 0.0, 0.0)
89
+
90
+ bp = p - b
91
+ d3 = wp.dot(ab, bp)
92
+ d4 = wp.dot(ac, bp)
93
+
94
+ if d3 >= 0.0 and d4 <= d3:
95
+ return wp.vec3(0.0, 1.0, 0.0)
96
+
97
+ vc = d1 * d4 - d3 * d2
98
+ v = d1 / (d1 - d3)
99
+ if vc <= 0.0 and d1 >= 0.0 and d3 <= 0.0:
100
+ return wp.vec3(1.0 - v, v, 0.0)
101
+
102
+ cp = p - c
103
+ d5 = wp.dot(ab, cp)
104
+ d6 = wp.dot(ac, cp)
105
+
106
+ if d6 >= 0.0 and d5 <= d6:
107
+ return wp.vec3(0.0, 0.0, 1.0)
108
+
109
+ vb = d5 * d2 - d1 * d6
110
+ w = d2 / (d2 - d6)
111
+ if vb <= 0.0 and d2 >= 0.0 and d6 <= 0.0:
112
+ return wp.vec3(1.0 - w, 0.0, w)
113
+
114
+ va = d3 * d6 - d5 * d4
115
+ w = (d4 - d3) / ((d4 - d3) + (d5 - d6))
116
+ if va <= 0.0 and (d4 - d3) >= 0.0 and (d5 - d6) >= 0.0:
117
+ return wp.vec3(0.0, 1.0 - w, w)
118
+
119
+ denom = 1.0 / (va + vb + vc)
120
+ v = vb * denom
121
+ w = vc * denom
122
+
123
+ return wp.vec3(1.0 - v - w, v, w)
124
+
125
+
126
+ @wp.func
127
+ def triangle_closest_point(a: wp.vec3, b: wp.vec3, c: wp.vec3, p: wp.vec3):
128
+ """
129
+ feature_type type:
130
+ TRI_CONTACT_FEATURE_VERTEX_A
131
+ TRI_CONTACT_FEATURE_VERTEX_B
132
+ TRI_CONTACT_FEATURE_VERTEX_C
133
+ TRI_CONTACT_FEATURE_EDGE_AB : at edge A-B
134
+ TRI_CONTACT_FEATURE_EDGE_AC : at edge A-C
135
+ TRI_CONTACT_FEATURE_EDGE_BC : at edge B-C
136
+ TRI_CONTACT_FEATURE_FACE_INTERIOR
137
+ """
138
+ ab = b - a
139
+ ac = c - a
140
+ ap = p - a
141
+
142
+ d1 = wp.dot(ab, ap)
143
+ d2 = wp.dot(ac, ap)
144
+ if d1 <= 0.0 and d2 <= 0.0:
145
+ feature_type = TRI_CONTACT_FEATURE_VERTEX_A
146
+ bary = wp.vec3(1.0, 0.0, 0.0)
147
+ return a, bary, feature_type
148
+
149
+ bp = p - b
150
+ d3 = wp.dot(ab, bp)
151
+ d4 = wp.dot(ac, bp)
152
+ if d3 >= 0.0 and d4 <= d3:
153
+ feature_type = TRI_CONTACT_FEATURE_VERTEX_B
154
+ bary = wp.vec3(0.0, 1.0, 0.0)
155
+ return b, bary, feature_type
156
+
157
+ cp = p - c
158
+ d5 = wp.dot(ab, cp)
159
+ d6 = wp.dot(ac, cp)
160
+ if d6 >= 0.0 and d5 <= d6:
161
+ feature_type = TRI_CONTACT_FEATURE_VERTEX_C
162
+ bary = wp.vec3(0.0, 0.0, 1.0)
163
+ return c, bary, feature_type
164
+
165
+ vc = d1 * d4 - d3 * d2
166
+ if vc <= 0.0 and d1 >= 0.0 and d3 <= 0.0:
167
+ v = d1 / (d1 - d3)
168
+ feature_type = TRI_CONTACT_FEATURE_EDGE_AB
169
+ bary = wp.vec3(1.0 - v, v, 0.0)
170
+ return a + v * ab, bary, feature_type
171
+
172
+ vb = d5 * d2 - d1 * d6
173
+ if vb <= 0.0 and d2 >= 0.0 and d6 <= 0.0:
174
+ v = d2 / (d2 - d6)
175
+ feature_type = TRI_CONTACT_FEATURE_EDGE_AC
176
+ bary = wp.vec3(1.0 - v, 0.0, v)
177
+ return a + v * ac, bary, feature_type
178
+
179
+ va = d3 * d6 - d5 * d4
180
+ if va <= 0.0 and (d4 - d3) >= 0.0 and (d5 - d6) >= 0.0:
181
+ v = (d4 - d3) / ((d4 - d3) + (d5 - d6))
182
+ feature_type = TRI_CONTACT_FEATURE_EDGE_BC
183
+ bary = wp.vec3(0.0, 1.0 - v, v)
184
+ return b + v * (c - b), bary, feature_type
185
+
186
+ denom = 1.0 / (va + vb + vc)
187
+ v = vb * denom
188
+ w = vc * denom
189
+ feature_type = TRI_CONTACT_FEATURE_FACE_INTERIOR
190
+ bary = wp.vec3(1.0 - v - w, v, w)
191
+ return a + v * ab + w * ac, bary, feature_type
192
+
193
+
194
+ @wp.func
195
+ def sphere_sdf(center: wp.vec3, radius: float, p: wp.vec3):
196
+ return wp.length(p - center) - radius
197
+
198
+
199
+ @wp.func
200
+ def sphere_sdf_grad(center: wp.vec3, radius: float, p: wp.vec3):
201
+ return wp.normalize(p - center)
202
+
203
+
204
+ @wp.func
205
+ def box_sdf(upper: wp.vec3, p: wp.vec3):
206
+ # adapted from https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
207
+ qx = abs(p[0]) - upper[0]
208
+ qy = abs(p[1]) - upper[1]
209
+ qz = abs(p[2]) - upper[2]
210
+
211
+ e = wp.vec3(wp.max(qx, 0.0), wp.max(qy, 0.0), wp.max(qz, 0.0))
212
+
213
+ return wp.length(e) + wp.min(wp.max(qx, wp.max(qy, qz)), 0.0)
214
+
215
+
216
+ @wp.func
217
+ def box_sdf_grad(upper: wp.vec3, p: wp.vec3):
218
+ qx = abs(p[0]) - upper[0]
219
+ qy = abs(p[1]) - upper[1]
220
+ qz = abs(p[2]) - upper[2]
221
+
222
+ # exterior case
223
+ if qx > 0.0 or qy > 0.0 or qz > 0.0:
224
+ x = wp.clamp(p[0], -upper[0], upper[0])
225
+ y = wp.clamp(p[1], -upper[1], upper[1])
226
+ z = wp.clamp(p[2], -upper[2], upper[2])
227
+
228
+ return wp.normalize(p - wp.vec3(x, y, z))
229
+
230
+ sx = wp.sign(p[0])
231
+ sy = wp.sign(p[1])
232
+ sz = wp.sign(p[2])
233
+
234
+ # x projection
235
+ if qx > qy and qx > qz or qy == 0.0 and qz == 0.0:
236
+ return wp.vec3(sx, 0.0, 0.0)
237
+
238
+ # y projection
239
+ if qy > qx and qy > qz or qx == 0.0 and qz == 0.0:
240
+ return wp.vec3(0.0, sy, 0.0)
241
+
242
+ # z projection
243
+ return wp.vec3(0.0, 0.0, sz)
244
+
245
+
246
+ @wp.func
247
+ def capsule_sdf(radius: float, half_height: float, p: wp.vec3):
248
+ if p[1] > half_height:
249
+ return wp.length(wp.vec3(p[0], p[1] - half_height, p[2])) - radius
250
+
251
+ if p[1] < -half_height:
252
+ return wp.length(wp.vec3(p[0], p[1] + half_height, p[2])) - radius
253
+
254
+ return wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
255
+
256
+
257
+ @wp.func
258
+ def capsule_sdf_grad(radius: float, half_height: float, p: wp.vec3):
259
+ if p[1] > half_height:
260
+ return wp.normalize(wp.vec3(p[0], p[1] - half_height, p[2]))
261
+
262
+ if p[1] < -half_height:
263
+ return wp.normalize(wp.vec3(p[0], p[1] + half_height, p[2]))
264
+
265
+ return wp.normalize(wp.vec3(p[0], 0.0, p[2]))
266
+
267
+
268
+ @wp.func
269
+ def cylinder_sdf(radius: float, half_height: float, p: wp.vec3):
270
+ dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
271
+ dy = wp.abs(p[1]) - half_height
272
+ return wp.min(wp.max(dx, dy), 0.0) + wp.length(wp.vec2(wp.max(dx, 0.0), wp.max(dy, 0.0)))
273
+
274
+
275
+ @wp.func
276
+ def cylinder_sdf_grad(radius: float, half_height: float, p: wp.vec3):
277
+ dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius
278
+ dy = wp.abs(p[1]) - half_height
279
+ if dx > dy:
280
+ return wp.normalize(wp.vec3(p[0], 0.0, p[2]))
281
+ return wp.vec3(0.0, wp.sign(p[1]), 0.0)
282
+
283
+
284
+ @wp.func
285
+ def cone_sdf(radius: float, half_height: float, p: wp.vec3):
286
+ dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius * (p[1] + half_height) / (2.0 * half_height)
287
+ dy = wp.abs(p[1]) - half_height
288
+ return wp.min(wp.max(dx, dy), 0.0) + wp.length(wp.vec2(wp.max(dx, 0.0), wp.max(dy, 0.0)))
289
+
290
+
291
+ @wp.func
292
+ def cone_sdf_grad(radius: float, half_height: float, p: wp.vec3):
293
+ dx = wp.length(wp.vec3(p[0], 0.0, p[2])) - radius * (p[1] + half_height) / (2.0 * half_height)
294
+ dy = wp.abs(p[1]) - half_height
295
+ if dy < 0.0 or dx == 0.0:
296
+ return wp.vec3(0.0, wp.sign(p[1]), 0.0)
297
+ return wp.normalize(wp.vec3(p[0], 0.0, p[2])) + wp.vec3(0.0, radius / (2.0 * half_height), 0.0)
298
+
299
+
300
+ @wp.func
301
+ def plane_sdf(width: float, length: float, p: wp.vec3):
302
+ # SDF for a quad in the xz plane
303
+ if width > 0.0 and length > 0.0:
304
+ d = wp.max(wp.abs(p[0]) - width, wp.abs(p[2]) - length)
305
+ return wp.max(d, wp.abs(p[1]))
306
+ return p[1]
307
+
308
+
309
+ @wp.func
310
+ def closest_point_plane(width: float, length: float, point: wp.vec3):
311
+ # projects the point onto the quad in the xz plane (if width and length > 0.0, otherwise the plane is infinite)
312
+ if width > 0.0:
313
+ x = wp.clamp(point[0], -width, width)
314
+ else:
315
+ x = point[0]
316
+ if length > 0.0:
317
+ z = wp.clamp(point[2], -length, length)
318
+ else:
319
+ z = point[2]
320
+ return wp.vec3(x, 0.0, z)
321
+
322
+
323
+ @wp.func
324
+ def closest_point_line_segment(a: wp.vec3, b: wp.vec3, point: wp.vec3):
325
+ ab = b - a
326
+ ap = point - a
327
+ t = wp.dot(ap, ab) / wp.dot(ab, ab)
328
+ t = wp.clamp(t, 0.0, 1.0)
329
+ return a + t * ab
330
+
331
+
332
+ @wp.func
333
+ def closest_point_box(upper: wp.vec3, point: wp.vec3):
334
+ # closest point to box surface
335
+ x = wp.clamp(point[0], -upper[0], upper[0])
336
+ y = wp.clamp(point[1], -upper[1], upper[1])
337
+ z = wp.clamp(point[2], -upper[2], upper[2])
338
+ if wp.abs(point[0]) <= upper[0] and wp.abs(point[1]) <= upper[1] and wp.abs(point[2]) <= upper[2]:
339
+ # the point is inside, find closest face
340
+ sx = wp.abs(wp.abs(point[0]) - upper[0])
341
+ sy = wp.abs(wp.abs(point[1]) - upper[1])
342
+ sz = wp.abs(wp.abs(point[2]) - upper[2])
343
+ # return closest point on closest side, handle corner cases
344
+ if sx < sy and sx < sz or sy == 0.0 and sz == 0.0:
345
+ x = wp.sign(point[0]) * upper[0]
346
+ elif sy < sx and sy < sz or sx == 0.0 and sz == 0.0:
347
+ y = wp.sign(point[1]) * upper[1]
348
+ else:
349
+ z = wp.sign(point[2]) * upper[2]
350
+ return wp.vec3(x, y, z)
351
+
352
+
353
+ @wp.func
354
+ def get_box_vertex(point_id: int, upper: wp.vec3):
355
+ # box vertex numbering:
356
+ # 6---7
357
+ # |\ |\ y
358
+ # | 2-+-3 |
359
+ # 4-+-5 | z \|
360
+ # \| \| o---x
361
+ # 0---1
362
+ # get the vertex of the box given its ID (0-7)
363
+ sign_x = float(point_id % 2) * 2.0 - 1.0
364
+ sign_y = float((point_id // 2) % 2) * 2.0 - 1.0
365
+ sign_z = float((point_id // 4) % 2) * 2.0 - 1.0
366
+ return wp.vec3(sign_x * upper[0], sign_y * upper[1], sign_z * upper[2])
367
+
368
+
369
+ @wp.func
370
+ def get_box_edge(edge_id: int, upper: wp.vec3):
371
+ # get the edge of the box given its ID (0-11)
372
+ if edge_id < 4:
373
+ # edges along x: 0-1, 2-3, 4-5, 6-7
374
+ i = edge_id * 2
375
+ j = i + 1
376
+ return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
377
+ elif edge_id < 8:
378
+ # edges along y: 0-2, 1-3, 4-6, 5-7
379
+ edge_id -= 4
380
+ i = edge_id % 2 + edge_id // 2 * 4
381
+ j = i + 2
382
+ return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
383
+ # edges along z: 0-4, 1-5, 2-6, 3-7
384
+ edge_id -= 8
385
+ i = edge_id
386
+ j = i + 4
387
+ return wp.spatial_vector(get_box_vertex(i, upper), get_box_vertex(j, upper))
388
+
389
+
390
+ @wp.func
391
+ def get_plane_edge(edge_id: int, plane_width: float, plane_length: float):
392
+ # get the edge of the plane given its ID (0-3)
393
+ p0x = (2.0 * float(edge_id % 2) - 1.0) * plane_width
394
+ p0z = (2.0 * float(edge_id // 2) - 1.0) * plane_length
395
+ if edge_id == 0 or edge_id == 3:
396
+ p1x = p0x
397
+ p1z = -p0z
398
+ else:
399
+ p1x = -p0x
400
+ p1z = p0z
401
+ return wp.spatial_vector(wp.vec3(p0x, 0.0, p0z), wp.vec3(p1x, 0.0, p1z))
402
+
403
+
404
+ @wp.func
405
+ def closest_edge_coordinate_box(upper: wp.vec3, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int):
406
+ # find point on edge closest to box, return its barycentric edge coordinate
407
+ # Golden-section search
408
+ a = float(0.0)
409
+ b = float(1.0)
410
+ h = b - a
411
+ invphi = 0.61803398875 # 1 / phi
412
+ invphi2 = 0.38196601125 # 1 / phi^2
413
+ c = a + invphi2 * h
414
+ d = a + invphi * h
415
+ query = (1.0 - c) * edge_a + c * edge_b
416
+ yc = box_sdf(upper, query)
417
+ query = (1.0 - d) * edge_a + d * edge_b
418
+ yd = box_sdf(upper, query)
419
+
420
+ for _k in range(max_iter):
421
+ if yc < yd: # yc > yd to find the maximum
422
+ b = d
423
+ d = c
424
+ yd = yc
425
+ h = invphi * h
426
+ c = a + invphi2 * h
427
+ query = (1.0 - c) * edge_a + c * edge_b
428
+ yc = box_sdf(upper, query)
429
+ else:
430
+ a = c
431
+ c = d
432
+ yc = yd
433
+ h = invphi * h
434
+ d = a + invphi * h
435
+ query = (1.0 - d) * edge_a + d * edge_b
436
+ yd = box_sdf(upper, query)
437
+
438
+ if yc < yd:
439
+ return 0.5 * (a + d)
440
+ return 0.5 * (c + b)
441
+
442
+
443
+ @wp.func
444
+ def closest_edge_coordinate_plane(
445
+ plane_width: float,
446
+ plane_length: float,
447
+ edge_a: wp.vec3,
448
+ edge_b: wp.vec3,
449
+ max_iter: int,
450
+ ):
451
+ # find point on edge closest to plane, return its barycentric edge coordinate
452
+ # Golden-section search
453
+ a = float(0.0)
454
+ b = float(1.0)
455
+ h = b - a
456
+ invphi = 0.61803398875 # 1 / phi
457
+ invphi2 = 0.38196601125 # 1 / phi^2
458
+ c = a + invphi2 * h
459
+ d = a + invphi * h
460
+ query = (1.0 - c) * edge_a + c * edge_b
461
+ yc = plane_sdf(plane_width, plane_length, query)
462
+ query = (1.0 - d) * edge_a + d * edge_b
463
+ yd = plane_sdf(plane_width, plane_length, query)
464
+
465
+ for _k in range(max_iter):
466
+ if yc < yd: # yc > yd to find the maximum
467
+ b = d
468
+ d = c
469
+ yd = yc
470
+ h = invphi * h
471
+ c = a + invphi2 * h
472
+ query = (1.0 - c) * edge_a + c * edge_b
473
+ yc = plane_sdf(plane_width, plane_length, query)
474
+ else:
475
+ a = c
476
+ c = d
477
+ yc = yd
478
+ h = invphi * h
479
+ d = a + invphi * h
480
+ query = (1.0 - d) * edge_a + d * edge_b
481
+ yd = plane_sdf(plane_width, plane_length, query)
482
+
483
+ if yc < yd:
484
+ return 0.5 * (a + d)
485
+ return 0.5 * (c + b)
486
+
487
+
488
+ @wp.func
489
+ def closest_edge_coordinate_capsule(radius: float, half_height: float, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int):
490
+ # find point on edge closest to capsule, return its barycentric edge coordinate
491
+ # Golden-section search
492
+ a = float(0.0)
493
+ b = float(1.0)
494
+ h = b - a
495
+ invphi = 0.61803398875 # 1 / phi
496
+ invphi2 = 0.38196601125 # 1 / phi^2
497
+ c = a + invphi2 * h
498
+ d = a + invphi * h
499
+ query = (1.0 - c) * edge_a + c * edge_b
500
+ yc = capsule_sdf(radius, half_height, query)
501
+ query = (1.0 - d) * edge_a + d * edge_b
502
+ yd = capsule_sdf(radius, half_height, query)
503
+
504
+ for _k in range(max_iter):
505
+ if yc < yd: # yc > yd to find the maximum
506
+ b = d
507
+ d = c
508
+ yd = yc
509
+ h = invphi * h
510
+ c = a + invphi2 * h
511
+ query = (1.0 - c) * edge_a + c * edge_b
512
+ yc = capsule_sdf(radius, half_height, query)
513
+ else:
514
+ a = c
515
+ c = d
516
+ yc = yd
517
+ h = invphi * h
518
+ d = a + invphi * h
519
+ query = (1.0 - d) * edge_a + d * edge_b
520
+ yd = capsule_sdf(radius, half_height, query)
521
+
522
+ if yc < yd:
523
+ return 0.5 * (a + d)
524
+
525
+ return 0.5 * (c + b)
526
+
527
+
528
+ @wp.func
529
+ def mesh_sdf(mesh: wp.uint64, point: wp.vec3, max_dist: float):
530
+ face_index = int(0)
531
+ face_u = float(0.0)
532
+ face_v = float(0.0)
533
+ sign = float(0.0)
534
+ res = wp.mesh_query_point_sign_normal(mesh, point, max_dist, sign, face_index, face_u, face_v)
535
+
536
+ if res:
537
+ closest = wp.mesh_eval_position(mesh, face_index, face_u, face_v)
538
+ return wp.length(point - closest) * sign
539
+ return max_dist
540
+
541
+
542
+ @wp.func
543
+ def closest_point_mesh(mesh: wp.uint64, point: wp.vec3, max_dist: float):
544
+ face_index = int(0)
545
+ face_u = float(0.0)
546
+ face_v = float(0.0)
547
+ sign = float(0.0)
548
+ res = wp.mesh_query_point_sign_normal(mesh, point, max_dist, sign, face_index, face_u, face_v)
549
+
550
+ if res:
551
+ return wp.mesh_eval_position(mesh, face_index, face_u, face_v)
552
+ # return arbitrary point from mesh
553
+ return wp.mesh_eval_position(mesh, 0, 0.0, 0.0)
554
+
555
+
556
+ @wp.func
557
+ def closest_edge_coordinate_mesh(mesh: wp.uint64, edge_a: wp.vec3, edge_b: wp.vec3, max_iter: int, max_dist: float):
558
+ # find point on edge closest to mesh, return its barycentric edge coordinate
559
+ # Golden-section search
560
+ a = float(0.0)
561
+ b = float(1.0)
562
+ h = b - a
563
+ invphi = 0.61803398875 # 1 / phi
564
+ invphi2 = 0.38196601125 # 1 / phi^2
565
+ c = a + invphi2 * h
566
+ d = a + invphi * h
567
+ query = (1.0 - c) * edge_a + c * edge_b
568
+ yc = mesh_sdf(mesh, query, max_dist)
569
+ query = (1.0 - d) * edge_a + d * edge_b
570
+ yd = mesh_sdf(mesh, query, max_dist)
571
+
572
+ for _k in range(max_iter):
573
+ if yc < yd: # yc > yd to find the maximum
574
+ b = d
575
+ d = c
576
+ yd = yc
577
+ h = invphi * h
578
+ c = a + invphi2 * h
579
+ query = (1.0 - c) * edge_a + c * edge_b
580
+ yc = mesh_sdf(mesh, query, max_dist)
581
+ else:
582
+ a = c
583
+ c = d
584
+ yc = yd
585
+ h = invphi * h
586
+ d = a + invphi * h
587
+ query = (1.0 - d) * edge_a + d * edge_b
588
+ yd = mesh_sdf(mesh, query, max_dist)
589
+
590
+ if yc < yd:
591
+ return 0.5 * (a + d)
592
+ return 0.5 * (c + b)
593
+
594
+
595
+ @wp.func
596
+ def volume_grad(volume: wp.uint64, p: wp.vec3):
597
+ eps = 0.05 # TODO make this a parameter
598
+ q = wp.volume_world_to_index(volume, p)
599
+
600
+ # compute gradient of the SDF using finite differences
601
+ dx = wp.volume_sample_f(volume, q + wp.vec3(eps, 0.0, 0.0), wp.Volume.LINEAR) - wp.volume_sample_f(
602
+ volume, q - wp.vec3(eps, 0.0, 0.0), wp.Volume.LINEAR
603
+ )
604
+ dy = wp.volume_sample_f(volume, q + wp.vec3(0.0, eps, 0.0), wp.Volume.LINEAR) - wp.volume_sample_f(
605
+ volume, q - wp.vec3(0.0, eps, 0.0), wp.Volume.LINEAR
606
+ )
607
+ dz = wp.volume_sample_f(volume, q + wp.vec3(0.0, 0.0, eps), wp.Volume.LINEAR) - wp.volume_sample_f(
608
+ volume, q - wp.vec3(0.0, 0.0, eps), wp.Volume.LINEAR
609
+ )
610
+
611
+ return wp.normalize(wp.vec3(dx, dy, dz))
612
+
613
+
614
+ @wp.func
615
+ def counter_increment(counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int):
616
+ # increment counter, remember which thread received which counter value
617
+ next_count = wp.atomic_add(counter, counter_index, 1)
618
+ tids[tid] = next_count
619
+ return next_count
620
+
621
+
622
+ @wp.func_replay(counter_increment)
623
+ def replay_counter_increment(counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int):
624
+ return tids[tid]
625
+
626
+
627
+ @wp.func
628
+ def limited_counter_increment(
629
+ counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int, index_limit: int
630
+ ):
631
+ # increment counter but only if it is smaller than index_limit, remember which thread received which counter value
632
+ next_count = wp.atomic_add(counter, counter_index, 1)
633
+ if next_count < index_limit or index_limit < 0:
634
+ tids[tid] = next_count
635
+ return next_count
636
+ tids[tid] = -1
637
+ return -1
638
+
639
+
640
+ @wp.func_replay(limited_counter_increment)
641
+ def replay_limited_counter_increment(
642
+ counter: wp.array(dtype=int), counter_index: int, tids: wp.array(dtype=int), tid: int, index_limit: int
643
+ ):
644
+ return tids[tid]
645
+
646
+
647
+ @wp.kernel
648
+ def create_soft_contacts(
649
+ particle_x: wp.array(dtype=wp.vec3),
650
+ particle_radius: wp.array(dtype=float),
651
+ particle_flags: wp.array(dtype=wp.uint32),
652
+ body_X_wb: wp.array(dtype=wp.transform),
653
+ shape_X_bs: wp.array(dtype=wp.transform),
654
+ shape_body: wp.array(dtype=int),
655
+ geo: ModelShapeGeometry,
656
+ margin: float,
657
+ soft_contact_max: int,
658
+ shape_count: int,
659
+ # outputs
660
+ soft_contact_count: wp.array(dtype=int),
661
+ soft_contact_particle: wp.array(dtype=int),
662
+ soft_contact_shape: wp.array(dtype=int),
663
+ soft_contact_body_pos: wp.array(dtype=wp.vec3),
664
+ soft_contact_body_vel: wp.array(dtype=wp.vec3),
665
+ soft_contact_normal: wp.array(dtype=wp.vec3),
666
+ soft_contact_tids: wp.array(dtype=int),
667
+ ):
668
+ tid = wp.tid()
669
+ particle_index, shape_index = tid // shape_count, tid % shape_count
670
+ if (particle_flags[particle_index] & PARTICLE_FLAG_ACTIVE) == 0:
671
+ return
672
+
673
+ rigid_index = shape_body[shape_index]
674
+
675
+ px = particle_x[particle_index]
676
+ radius = particle_radius[particle_index]
677
+
678
+ X_wb = wp.transform_identity()
679
+ if rigid_index >= 0:
680
+ X_wb = body_X_wb[rigid_index]
681
+
682
+ X_bs = shape_X_bs[shape_index]
683
+
684
+ X_ws = wp.transform_multiply(X_wb, X_bs)
685
+ X_sw = wp.transform_inverse(X_ws)
686
+
687
+ # transform particle position to shape local space
688
+ x_local = wp.transform_point(X_sw, px)
689
+
690
+ # geo description
691
+ geo_type = geo.type[shape_index]
692
+ geo_scale = geo.scale[shape_index]
693
+
694
+ # evaluate shape sdf
695
+ d = 1.0e6
696
+ n = wp.vec3()
697
+ v = wp.vec3()
698
+
699
+ if geo_type == wp.sim.GEO_SPHERE:
700
+ d = sphere_sdf(wp.vec3(), geo_scale[0], x_local)
701
+ n = sphere_sdf_grad(wp.vec3(), geo_scale[0], x_local)
702
+
703
+ if geo_type == wp.sim.GEO_BOX:
704
+ d = box_sdf(geo_scale, x_local)
705
+ n = box_sdf_grad(geo_scale, x_local)
706
+
707
+ if geo_type == wp.sim.GEO_CAPSULE:
708
+ d = capsule_sdf(geo_scale[0], geo_scale[1], x_local)
709
+ n = capsule_sdf_grad(geo_scale[0], geo_scale[1], x_local)
710
+
711
+ if geo_type == wp.sim.GEO_CYLINDER:
712
+ d = cylinder_sdf(geo_scale[0], geo_scale[1], x_local)
713
+ n = cylinder_sdf_grad(geo_scale[0], geo_scale[1], x_local)
714
+
715
+ if geo_type == wp.sim.GEO_CONE:
716
+ d = cone_sdf(geo_scale[0], geo_scale[1], x_local)
717
+ n = cone_sdf_grad(geo_scale[0], geo_scale[1], x_local)
718
+
719
+ if geo_type == wp.sim.GEO_MESH:
720
+ mesh = geo.source[shape_index]
721
+
722
+ face_index = int(0)
723
+ face_u = float(0.0)
724
+ face_v = float(0.0)
725
+ sign = float(0.0)
726
+
727
+ min_scale = wp.min(geo_scale)
728
+ if wp.mesh_query_point_sign_normal(
729
+ mesh, wp.cw_div(x_local, geo_scale), margin + radius / min_scale, sign, face_index, face_u, face_v
730
+ ):
731
+ shape_p = wp.mesh_eval_position(mesh, face_index, face_u, face_v)
732
+ shape_v = wp.mesh_eval_velocity(mesh, face_index, face_u, face_v)
733
+
734
+ shape_p = wp.cw_mul(shape_p, geo_scale)
735
+ shape_v = wp.cw_mul(shape_v, geo_scale)
736
+
737
+ delta = x_local - shape_p
738
+
739
+ d = wp.length(delta) * sign
740
+ n = wp.normalize(delta) * sign
741
+ v = shape_v
742
+
743
+ if geo_type == wp.sim.GEO_SDF:
744
+ volume = geo.source[shape_index]
745
+ xpred_local = wp.volume_world_to_index(volume, wp.cw_div(x_local, geo_scale))
746
+ nn = wp.vec3(0.0, 0.0, 0.0)
747
+ d = wp.volume_sample_grad_f(volume, xpred_local, wp.Volume.LINEAR, nn)
748
+ n = wp.normalize(nn)
749
+
750
+ if geo_type == wp.sim.GEO_PLANE:
751
+ d = plane_sdf(geo_scale[0], geo_scale[1], x_local)
752
+ n = wp.vec3(0.0, 1.0, 0.0)
753
+
754
+ if d < margin + radius:
755
+ index = counter_increment(soft_contact_count, 0, soft_contact_tids, tid)
756
+
757
+ if index < soft_contact_max:
758
+ # compute contact point in body local space
759
+ body_pos = wp.transform_point(X_bs, x_local - n * d)
760
+ body_vel = wp.transform_vector(X_bs, v)
761
+
762
+ world_normal = wp.transform_vector(X_ws, n)
763
+
764
+ soft_contact_shape[index] = shape_index
765
+ soft_contact_body_pos[index] = body_pos
766
+ soft_contact_body_vel[index] = body_vel
767
+ soft_contact_particle[index] = particle_index
768
+ soft_contact_normal[index] = world_normal
769
+
770
+
771
+ @wp.kernel(enable_backward=False)
772
+ def count_contact_points(
773
+ contact_pairs: wp.array(dtype=int, ndim=2),
774
+ geo: ModelShapeGeometry,
775
+ mesh_contact_max: int,
776
+ # outputs
777
+ contact_count: wp.array(dtype=int),
778
+ ):
779
+ tid = wp.tid()
780
+ shape_a = contact_pairs[tid, 0]
781
+ shape_b = contact_pairs[tid, 1]
782
+
783
+ if shape_b == -1:
784
+ actual_shape_a = shape_a
785
+ actual_type_a = geo.type[shape_a]
786
+ # ground plane
787
+ actual_type_b = wp.sim.GEO_PLANE
788
+ actual_shape_b = -1
789
+ else:
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
+ else:
799
+ actual_shape_a = shape_b
800
+ actual_shape_b = shape_a
801
+ actual_type_a = type_b
802
+ actual_type_b = type_a
803
+
804
+ # determine how many contact points need to be evaluated
805
+ num_contacts = 0
806
+ num_actual_contacts = 0
807
+ if actual_type_a == wp.sim.GEO_SPHERE:
808
+ num_contacts = 1
809
+ num_actual_contacts = 1
810
+ elif actual_type_a == wp.sim.GEO_CAPSULE:
811
+ if actual_type_b == wp.sim.GEO_PLANE:
812
+ if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
813
+ num_contacts = 2 # vertex-based collision for infinite plane
814
+ num_actual_contacts = 2
815
+ else:
816
+ num_contacts = 2 + 4 # vertex-based collision + plane edges
817
+ num_actual_contacts = 2 + 4
818
+ elif actual_type_b == wp.sim.GEO_MESH:
819
+ num_contacts_a = 2
820
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
821
+ num_contacts_b = mesh_b.points.shape[0]
822
+ num_contacts = num_contacts_a + num_contacts_b
823
+ if mesh_contact_max > 0:
824
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
825
+ num_actual_contacts = num_contacts_a + num_contacts_b
826
+ else:
827
+ num_contacts = 2
828
+ num_actual_contacts = 2
829
+ elif actual_type_a == wp.sim.GEO_BOX:
830
+ if actual_type_b == wp.sim.GEO_BOX:
831
+ num_contacts = 24
832
+ num_actual_contacts = 24
833
+ elif actual_type_b == wp.sim.GEO_MESH:
834
+ num_contacts_a = 8
835
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
836
+ num_contacts_b = mesh_b.points.shape[0]
837
+ num_contacts = num_contacts_a + num_contacts_b
838
+ if mesh_contact_max > 0:
839
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
840
+ num_actual_contacts = num_contacts_a + num_contacts_b
841
+ elif actual_type_b == wp.sim.GEO_PLANE:
842
+ if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
843
+ num_contacts = 8 # vertex-based collision
844
+ num_actual_contacts = 8
845
+ else:
846
+ num_contacts = 8 + 4 # vertex-based collision + plane edges
847
+ num_actual_contacts = 8 + 4
848
+ else:
849
+ num_contacts = 8
850
+ num_actual_contacts = 8
851
+ elif actual_type_a == wp.sim.GEO_MESH:
852
+ mesh_a = wp.mesh_get(geo.source[actual_shape_a])
853
+ num_contacts_a = mesh_a.points.shape[0]
854
+ if mesh_contact_max > 0:
855
+ num_contacts_a = wp.min(mesh_contact_max, num_contacts_a)
856
+ if actual_type_b == wp.sim.GEO_MESH:
857
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
858
+ num_contacts_b = mesh_b.points.shape[0]
859
+ num_contacts = num_contacts_a + num_contacts_b
860
+ if mesh_contact_max > 0:
861
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
862
+ else:
863
+ num_contacts_b = 0
864
+ num_contacts = num_contacts_a + num_contacts_b
865
+ num_actual_contacts = num_contacts_a + num_contacts_b
866
+ elif actual_type_a == wp.sim.GEO_PLANE:
867
+ return # no plane-plane contacts
868
+ else:
869
+ wp.printf(
870
+ "count_contact_points: unsupported geometry type combination %d and %d\n", actual_type_a, actual_type_b
871
+ )
872
+
873
+ wp.atomic_add(contact_count, 0, num_contacts)
874
+ wp.atomic_add(contact_count, 1, num_actual_contacts)
875
+
876
+
877
+ @wp.kernel(enable_backward=False)
878
+ def broadphase_collision_pairs(
879
+ contact_pairs: wp.array(dtype=int, ndim=2),
880
+ body_q: wp.array(dtype=wp.transform),
881
+ shape_X_bs: wp.array(dtype=wp.transform),
882
+ shape_body: wp.array(dtype=int),
883
+ body_mass: wp.array(dtype=float),
884
+ num_shapes: int,
885
+ geo: ModelShapeGeometry,
886
+ collision_radius: wp.array(dtype=float),
887
+ rigid_contact_max: int,
888
+ rigid_contact_margin: float,
889
+ mesh_contact_max: int,
890
+ iterate_mesh_vertices: bool,
891
+ # outputs
892
+ contact_count: wp.array(dtype=int),
893
+ contact_shape0: wp.array(dtype=int),
894
+ contact_shape1: wp.array(dtype=int),
895
+ contact_point_id: wp.array(dtype=int),
896
+ contact_point_limit: wp.array(dtype=int),
897
+ ):
898
+ tid = wp.tid()
899
+ shape_a = contact_pairs[tid, 0]
900
+ shape_b = contact_pairs[tid, 1]
901
+
902
+ mass_a = 0.0
903
+ mass_b = 0.0
904
+ rigid_a = shape_body[shape_a]
905
+ if rigid_a == -1:
906
+ X_ws_a = shape_X_bs[shape_a]
907
+ else:
908
+ X_ws_a = wp.transform_multiply(body_q[rigid_a], shape_X_bs[shape_a])
909
+ mass_a = body_mass[rigid_a]
910
+ rigid_b = shape_body[shape_b]
911
+ if rigid_b == -1:
912
+ X_ws_b = shape_X_bs[shape_b]
913
+ else:
914
+ X_ws_b = wp.transform_multiply(body_q[rigid_b], shape_X_bs[shape_b])
915
+ mass_b = body_mass[rigid_b]
916
+ if mass_a == 0.0 and mass_b == 0.0:
917
+ # skip if both bodies are static
918
+ return
919
+
920
+ type_a = geo.type[shape_a]
921
+ type_b = geo.type[shape_b]
922
+ # unique ordering of shape pairs
923
+ if type_a < type_b:
924
+ actual_shape_a = shape_a
925
+ actual_shape_b = shape_b
926
+ actual_type_a = type_a
927
+ actual_type_b = type_b
928
+ actual_X_ws_a = X_ws_a
929
+ actual_X_ws_b = X_ws_b
930
+ else:
931
+ actual_shape_a = shape_b
932
+ actual_shape_b = shape_a
933
+ actual_type_a = type_b
934
+ actual_type_b = type_a
935
+ actual_X_ws_a = X_ws_b
936
+ actual_X_ws_b = X_ws_a
937
+
938
+ p_a = wp.transform_get_translation(actual_X_ws_a)
939
+ if actual_type_b == wp.sim.GEO_PLANE:
940
+ if actual_type_a == wp.sim.GEO_PLANE:
941
+ return
942
+ query_b = wp.transform_point(wp.transform_inverse(actual_X_ws_b), p_a)
943
+ scale = geo.scale[actual_shape_b]
944
+ closest = closest_point_plane(scale[0], scale[1], query_b)
945
+ d = wp.length(query_b - closest)
946
+ r_a = collision_radius[actual_shape_a]
947
+ if d > r_a + rigid_contact_margin:
948
+ return
949
+ else:
950
+ p_b = wp.transform_get_translation(actual_X_ws_b)
951
+ d = wp.length(p_a - p_b) * 0.5 - 0.1
952
+ r_a = collision_radius[actual_shape_a]
953
+ r_b = collision_radius[actual_shape_b]
954
+ if d > r_a + r_b + rigid_contact_margin:
955
+ return
956
+
957
+ pair_index_ab = actual_shape_a * num_shapes + actual_shape_b
958
+ pair_index_ba = actual_shape_b * num_shapes + actual_shape_a
959
+
960
+ # determine how many contact points need to be evaluated
961
+ num_contacts = 0
962
+ if actual_type_a == wp.sim.GEO_SPHERE:
963
+ num_contacts = 1
964
+ elif actual_type_a == wp.sim.GEO_CAPSULE:
965
+ if actual_type_b == wp.sim.GEO_PLANE:
966
+ if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
967
+ num_contacts = 2 # vertex-based collision for infinite plane
968
+ else:
969
+ num_contacts = 2 + 4 # vertex-based collision + plane edges
970
+ elif actual_type_b == wp.sim.GEO_MESH:
971
+ num_contacts_a = 2
972
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
973
+ if iterate_mesh_vertices:
974
+ num_contacts_b = mesh_b.points.shape[0]
975
+ else:
976
+ num_contacts_b = 0
977
+ num_contacts = num_contacts_a + num_contacts_b
978
+ index = wp.atomic_add(contact_count, 0, num_contacts)
979
+ if index + num_contacts - 1 >= rigid_contact_max:
980
+ print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
981
+ return
982
+ # allocate contact points from capsule A against mesh B
983
+ for i in range(num_contacts_a):
984
+ contact_shape0[index + i] = actual_shape_a
985
+ contact_shape1[index + i] = actual_shape_b
986
+ contact_point_id[index + i] = i
987
+ # allocate contact points from mesh B against capsule A
988
+ for i in range(num_contacts_b):
989
+ contact_shape0[index + num_contacts_a + i] = actual_shape_b
990
+ contact_shape1[index + num_contacts_a + i] = actual_shape_a
991
+ contact_point_id[index + num_contacts_a + i] = i
992
+ if mesh_contact_max > 0 and contact_point_limit and pair_index_ba < contact_point_limit.shape[0]:
993
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
994
+ contact_point_limit[pair_index_ba] = num_contacts_b
995
+ return
996
+ else:
997
+ num_contacts = 2
998
+ elif actual_type_a == wp.sim.GEO_BOX:
999
+ if actual_type_b == wp.sim.GEO_BOX:
1000
+ index = wp.atomic_add(contact_count, 0, 24)
1001
+ if index + 23 >= rigid_contact_max:
1002
+ print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
1003
+ return
1004
+ # allocate contact points from box A against B
1005
+ for i in range(12): # 12 edges
1006
+ contact_shape0[index + i] = shape_a
1007
+ contact_shape1[index + i] = shape_b
1008
+ contact_point_id[index + i] = i
1009
+ # allocate contact points from box B against A
1010
+ for i in range(12):
1011
+ contact_shape0[index + 12 + i] = shape_b
1012
+ contact_shape1[index + 12 + i] = shape_a
1013
+ contact_point_id[index + 12 + i] = i
1014
+ return
1015
+ elif actual_type_b == wp.sim.GEO_MESH:
1016
+ num_contacts_a = 8
1017
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
1018
+ if iterate_mesh_vertices:
1019
+ num_contacts_b = mesh_b.points.shape[0]
1020
+ else:
1021
+ num_contacts_b = 0
1022
+ num_contacts = num_contacts_a + num_contacts_b
1023
+ index = wp.atomic_add(contact_count, 0, num_contacts)
1024
+ if index + num_contacts - 1 >= rigid_contact_max:
1025
+ print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
1026
+ return
1027
+ # allocate contact points from box A against mesh B
1028
+ for i in range(num_contacts_a):
1029
+ contact_shape0[index + i] = actual_shape_a
1030
+ contact_shape1[index + i] = actual_shape_b
1031
+ contact_point_id[index + i] = i
1032
+ # allocate contact points from mesh B against box A
1033
+ for i in range(num_contacts_b):
1034
+ contact_shape0[index + num_contacts_a + i] = actual_shape_b
1035
+ contact_shape1[index + num_contacts_a + i] = actual_shape_a
1036
+ contact_point_id[index + num_contacts_a + i] = i
1037
+
1038
+ if mesh_contact_max > 0 and contact_point_limit and pair_index_ba < contact_point_limit.shape[0]:
1039
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
1040
+ contact_point_limit[pair_index_ba] = num_contacts_b
1041
+ return
1042
+ elif actual_type_b == wp.sim.GEO_PLANE:
1043
+ if geo.scale[actual_shape_b][0] == 0.0 and geo.scale[actual_shape_b][1] == 0.0:
1044
+ num_contacts = 8 # vertex-based collision
1045
+ else:
1046
+ num_contacts = 8 + 4 # vertex-based collision + plane edges
1047
+ else:
1048
+ num_contacts = 8
1049
+ elif actual_type_a == wp.sim.GEO_MESH:
1050
+ mesh_a = wp.mesh_get(geo.source[actual_shape_a])
1051
+ num_contacts_a = mesh_a.points.shape[0]
1052
+ num_contacts_b = 0
1053
+ if actual_type_b == wp.sim.GEO_MESH:
1054
+ mesh_b = wp.mesh_get(geo.source[actual_shape_b])
1055
+ num_contacts_b = mesh_b.points.shape[0]
1056
+ elif actual_type_b != wp.sim.GEO_PLANE:
1057
+ print("broadphase_collision_pairs: unsupported geometry type for mesh collision")
1058
+ return
1059
+ num_contacts = num_contacts_a + num_contacts_b
1060
+ if num_contacts > 0:
1061
+ index = wp.atomic_add(contact_count, 0, num_contacts)
1062
+ if index + num_contacts - 1 >= rigid_contact_max:
1063
+ print("Mesh contact: Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
1064
+ return
1065
+ # allocate contact points from mesh A against B
1066
+ for i in range(num_contacts_a):
1067
+ contact_shape0[index + i] = actual_shape_a
1068
+ contact_shape1[index + i] = actual_shape_b
1069
+ contact_point_id[index + i] = i
1070
+ # allocate contact points from mesh B against A
1071
+ for i in range(num_contacts_b):
1072
+ contact_shape0[index + num_contacts_a + i] = actual_shape_b
1073
+ contact_shape1[index + num_contacts_a + i] = actual_shape_a
1074
+ contact_point_id[index + num_contacts_a + i] = i
1075
+
1076
+ if mesh_contact_max > 0 and contact_point_limit:
1077
+ num_contacts_a = wp.min(mesh_contact_max, num_contacts_a)
1078
+ num_contacts_b = wp.min(mesh_contact_max, num_contacts_b)
1079
+ if pair_index_ab < contact_point_limit.shape[0]:
1080
+ contact_point_limit[pair_index_ab] = num_contacts_a
1081
+ if pair_index_ba < contact_point_limit.shape[0]:
1082
+ contact_point_limit[pair_index_ba] = num_contacts_b
1083
+ return
1084
+ elif actual_type_a == wp.sim.GEO_PLANE:
1085
+ return # no plane-plane contacts
1086
+ else:
1087
+ print("broadphase_collision_pairs: unsupported geometry type")
1088
+
1089
+ if num_contacts > 0:
1090
+ index = wp.atomic_add(contact_count, 0, num_contacts)
1091
+ if index + num_contacts - 1 >= rigid_contact_max:
1092
+ print("Number of rigid contacts exceeded limit. Increase Model.rigid_contact_max.")
1093
+ return
1094
+ # allocate contact points
1095
+ for i in range(num_contacts):
1096
+ cp_index = index + i
1097
+ contact_shape0[cp_index] = actual_shape_a
1098
+ contact_shape1[cp_index] = actual_shape_b
1099
+ contact_point_id[cp_index] = i
1100
+ if contact_point_limit:
1101
+ if pair_index_ab < contact_point_limit.shape[0]:
1102
+ contact_point_limit[pair_index_ab] = num_contacts
1103
+ if pair_index_ba < contact_point_limit.shape[0]:
1104
+ contact_point_limit[pair_index_ba] = 0
1105
+
1106
+
1107
+ @wp.kernel
1108
+ def handle_contact_pairs(
1109
+ body_q: wp.array(dtype=wp.transform),
1110
+ shape_X_bs: wp.array(dtype=wp.transform),
1111
+ shape_body: wp.array(dtype=int),
1112
+ geo: ModelShapeGeometry,
1113
+ rigid_contact_margin: float,
1114
+ contact_broad_shape0: wp.array(dtype=int),
1115
+ contact_broad_shape1: wp.array(dtype=int),
1116
+ num_shapes: int,
1117
+ contact_point_id: wp.array(dtype=int),
1118
+ contact_point_limit: wp.array(dtype=int),
1119
+ edge_sdf_iter: int,
1120
+ # outputs
1121
+ contact_count: wp.array(dtype=int),
1122
+ contact_shape0: wp.array(dtype=int),
1123
+ contact_shape1: wp.array(dtype=int),
1124
+ contact_point0: wp.array(dtype=wp.vec3),
1125
+ contact_point1: wp.array(dtype=wp.vec3),
1126
+ contact_offset0: wp.array(dtype=wp.vec3),
1127
+ contact_offset1: wp.array(dtype=wp.vec3),
1128
+ contact_normal: wp.array(dtype=wp.vec3),
1129
+ contact_thickness: wp.array(dtype=float),
1130
+ contact_pairwise_counter: wp.array(dtype=int),
1131
+ contact_tids: wp.array(dtype=int),
1132
+ ):
1133
+ tid = wp.tid()
1134
+ shape_a = contact_broad_shape0[tid]
1135
+ shape_b = contact_broad_shape1[tid]
1136
+ if shape_a == shape_b:
1137
+ return
1138
+
1139
+ if contact_point_limit:
1140
+ pair_index = shape_a * num_shapes + shape_b
1141
+ contact_limit = contact_point_limit[pair_index]
1142
+ if contact_pairwise_counter[pair_index] >= contact_limit:
1143
+ # reached limit of contact points per contact pair
1144
+ return
1145
+
1146
+ point_id = contact_point_id[tid]
1147
+
1148
+ rigid_a = shape_body[shape_a]
1149
+ X_wb_a = wp.transform_identity()
1150
+ if rigid_a >= 0:
1151
+ X_wb_a = body_q[rigid_a]
1152
+ X_bs_a = shape_X_bs[shape_a]
1153
+ X_ws_a = wp.transform_multiply(X_wb_a, X_bs_a)
1154
+ X_sw_a = wp.transform_inverse(X_ws_a)
1155
+ X_bw_a = wp.transform_inverse(X_wb_a)
1156
+ geo_type_a = geo.type[shape_a]
1157
+ geo_scale_a = geo.scale[shape_a]
1158
+ min_scale_a = min(geo_scale_a)
1159
+ thickness_a = geo.thickness[shape_a]
1160
+ # is_solid_a = geo.is_solid[shape_a]
1161
+
1162
+ rigid_b = shape_body[shape_b]
1163
+ X_wb_b = wp.transform_identity()
1164
+ if rigid_b >= 0:
1165
+ X_wb_b = body_q[rigid_b]
1166
+ X_bs_b = shape_X_bs[shape_b]
1167
+ X_ws_b = wp.transform_multiply(X_wb_b, X_bs_b)
1168
+ X_sw_b = wp.transform_inverse(X_ws_b)
1169
+ X_bw_b = wp.transform_inverse(X_wb_b)
1170
+ geo_type_b = geo.type[shape_b]
1171
+ geo_scale_b = geo.scale[shape_b]
1172
+ min_scale_b = min(geo_scale_b)
1173
+ thickness_b = geo.thickness[shape_b]
1174
+ # is_solid_b = geo.is_solid[shape_b]
1175
+
1176
+ distance = 1.0e6
1177
+ u = float(0.0)
1178
+ thickness = thickness_a + thickness_b
1179
+
1180
+ if geo_type_a == wp.sim.GEO_SPHERE:
1181
+ p_a_world = wp.transform_get_translation(X_ws_a)
1182
+ if geo_type_b == wp.sim.GEO_SPHERE:
1183
+ p_b_world = wp.transform_get_translation(X_ws_b)
1184
+ elif geo_type_b == wp.sim.GEO_BOX:
1185
+ # contact point in frame of body B
1186
+ p_a_body = wp.transform_point(X_sw_b, p_a_world)
1187
+ p_b_body = closest_point_box(geo_scale_b, p_a_body)
1188
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1189
+ elif geo_type_b == wp.sim.GEO_CAPSULE:
1190
+ half_height_b = geo_scale_b[1]
1191
+ # capsule B
1192
+ A_b = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1193
+ B_b = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1194
+ p_b_world = closest_point_line_segment(A_b, B_b, p_a_world)
1195
+ elif geo_type_b == wp.sim.GEO_MESH:
1196
+ mesh_b = geo.source[shape_b]
1197
+ query_b_local = wp.transform_point(X_sw_b, p_a_world)
1198
+ face_index = int(0)
1199
+ face_u = float(0.0)
1200
+ face_v = float(0.0)
1201
+ sign = float(0.0)
1202
+ max_dist = (thickness + rigid_contact_margin) / min_scale_b
1203
+ res = wp.mesh_query_point_sign_normal(
1204
+ mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1205
+ )
1206
+ if res:
1207
+ shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1208
+ shape_p = wp.cw_mul(shape_p, geo_scale_b)
1209
+ p_b_world = wp.transform_point(X_ws_b, shape_p)
1210
+ else:
1211
+ return
1212
+ elif geo_type_b == wp.sim.GEO_PLANE:
1213
+ p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], wp.transform_point(X_sw_b, p_a_world))
1214
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1215
+ else:
1216
+ print("Unsupported geometry type in sphere collision handling")
1217
+ print(geo_type_b)
1218
+ return
1219
+ diff = p_a_world - p_b_world
1220
+ normal = wp.normalize(diff)
1221
+ distance = wp.dot(diff, normal)
1222
+
1223
+ elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_BOX:
1224
+ # edge-based box contact
1225
+ edge = get_box_edge(point_id, geo_scale_a)
1226
+ edge0_world = wp.transform_point(X_ws_a, wp.spatial_top(edge))
1227
+ edge1_world = wp.transform_point(X_ws_a, wp.spatial_bottom(edge))
1228
+ edge0_b = wp.transform_point(X_sw_b, edge0_world)
1229
+ edge1_b = wp.transform_point(X_sw_b, edge1_world)
1230
+ max_iter = edge_sdf_iter
1231
+ u = closest_edge_coordinate_box(geo_scale_b, edge0_b, edge1_b, max_iter)
1232
+ p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1233
+
1234
+ # find closest point + contact normal on box B
1235
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1236
+ p_b_body = closest_point_box(geo_scale_b, query_b)
1237
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1238
+ diff = p_a_world - p_b_world
1239
+ # use center of box A to query normal to make sure we are not inside B
1240
+ query_b = wp.transform_point(X_sw_b, wp.transform_get_translation(X_ws_a))
1241
+ normal = wp.transform_vector(X_ws_b, box_sdf_grad(geo_scale_b, query_b))
1242
+ distance = wp.dot(diff, normal)
1243
+
1244
+ elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_CAPSULE:
1245
+ half_height_b = geo_scale_b[1]
1246
+ # capsule B
1247
+ # depending on point id, we query an edge from 0 to 0.5 or 0.5 to 1
1248
+ e0 = wp.vec3(0.0, -half_height_b * float(point_id % 2), 0.0)
1249
+ e1 = wp.vec3(0.0, half_height_b * float((point_id + 1) % 2), 0.0)
1250
+ edge0_world = wp.transform_point(X_ws_b, e0)
1251
+ edge1_world = wp.transform_point(X_ws_b, e1)
1252
+ edge0_a = wp.transform_point(X_sw_a, edge0_world)
1253
+ edge1_a = wp.transform_point(X_sw_a, edge1_world)
1254
+ max_iter = edge_sdf_iter
1255
+ u = closest_edge_coordinate_box(geo_scale_a, edge0_a, edge1_a, max_iter)
1256
+ p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1257
+ # find closest point + contact normal on box A
1258
+ query_a = wp.transform_point(X_sw_a, p_b_world)
1259
+ p_a_body = closest_point_box(geo_scale_a, query_a)
1260
+ p_a_world = wp.transform_point(X_ws_a, p_a_body)
1261
+ diff = p_a_world - p_b_world
1262
+ # the contact point inside the capsule should already be outside the box
1263
+ normal = -wp.transform_vector(X_ws_a, box_sdf_grad(geo_scale_a, query_a))
1264
+ distance = wp.dot(diff, normal)
1265
+
1266
+ elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_PLANE:
1267
+ plane_width = geo_scale_b[0]
1268
+ plane_length = geo_scale_b[1]
1269
+ if point_id < 8:
1270
+ # vertex-based contact
1271
+ p_a_body = get_box_vertex(point_id, geo_scale_a)
1272
+ p_a_world = wp.transform_point(X_ws_a, p_a_body)
1273
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1274
+ p_b_body = closest_point_plane(plane_width, plane_length, query_b)
1275
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1276
+ diff = p_a_world - p_b_world
1277
+ normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1278
+ if plane_width > 0.0 and plane_length > 0.0:
1279
+ if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1280
+ # skip, we will evaluate the plane edge contact with the box later
1281
+ return
1282
+ # check whether the COM is above the plane
1283
+ # sign = wp.sign(wp.dot(wp.transform_get_translation(X_ws_a) - p_b_world, normal))
1284
+ # if sign < 0.0:
1285
+ # # the entire box is most likely below the plane
1286
+ # return
1287
+ # the contact point is within plane boundaries
1288
+ distance = wp.dot(diff, normal)
1289
+ else:
1290
+ # contact between box A and edges of finite plane B
1291
+ edge = get_plane_edge(point_id - 8, plane_width, plane_length)
1292
+ edge0_world = wp.transform_point(X_ws_b, wp.spatial_top(edge))
1293
+ edge1_world = wp.transform_point(X_ws_b, wp.spatial_bottom(edge))
1294
+ edge0_a = wp.transform_point(X_sw_a, edge0_world)
1295
+ edge1_a = wp.transform_point(X_sw_a, edge1_world)
1296
+ max_iter = edge_sdf_iter
1297
+ u = closest_edge_coordinate_box(geo_scale_a, edge0_a, edge1_a, max_iter)
1298
+ p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1299
+
1300
+ # find closest point + contact normal on box A
1301
+ query_a = wp.transform_point(X_sw_a, p_b_world)
1302
+ p_a_body = closest_point_box(geo_scale_a, query_a)
1303
+ p_a_world = wp.transform_point(X_ws_a, p_a_body)
1304
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1305
+ if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1306
+ # ensure that the closest point is actually inside the plane
1307
+ return
1308
+ diff = p_a_world - p_b_world
1309
+ com_a = wp.transform_get_translation(X_ws_a)
1310
+ query_b = wp.transform_point(X_sw_b, com_a)
1311
+ if wp.abs(query_b[0]) > plane_width or wp.abs(query_b[2]) > plane_length:
1312
+ # the COM is outside the plane
1313
+ normal = wp.normalize(com_a - p_b_world)
1314
+ else:
1315
+ normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1316
+ distance = wp.dot(diff, normal)
1317
+
1318
+ elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_CAPSULE:
1319
+ # find closest edge coordinate to capsule SDF B
1320
+ half_height_a = geo_scale_a[1]
1321
+ half_height_b = geo_scale_b[1]
1322
+ # edge from capsule A
1323
+ # depending on point id, we query an edge from 0 to 0.5 or 0.5 to 1
1324
+ e0 = wp.vec3(0.0, half_height_a * float(point_id % 2), 0.0)
1325
+ e1 = wp.vec3(0.0, -half_height_a * float((point_id + 1) % 2), 0.0)
1326
+ edge0_world = wp.transform_point(X_ws_a, e0)
1327
+ edge1_world = wp.transform_point(X_ws_a, e1)
1328
+ edge0_b = wp.transform_point(X_sw_b, edge0_world)
1329
+ edge1_b = wp.transform_point(X_sw_b, edge1_world)
1330
+ max_iter = edge_sdf_iter
1331
+ u = closest_edge_coordinate_capsule(geo_scale_b[0], geo_scale_b[1], edge0_b, edge1_b, max_iter)
1332
+ p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1333
+ p0_b_world = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1334
+ p1_b_world = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1335
+ p_b_world = closest_point_line_segment(p0_b_world, p1_b_world, p_a_world)
1336
+ diff = p_a_world - p_b_world
1337
+ normal = wp.normalize(diff)
1338
+ distance = wp.dot(diff, normal)
1339
+
1340
+ elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_MESH:
1341
+ # find closest edge coordinate to mesh SDF B
1342
+ half_height_a = geo_scale_a[1]
1343
+ # edge from capsule A
1344
+ # depending on point id, we query an edge from -h to 0 or 0 to h
1345
+ e0 = wp.vec3(0.0, -half_height_a * float(point_id % 2), 0.0)
1346
+ e1 = wp.vec3(0.0, half_height_a * float((point_id + 1) % 2), 0.0)
1347
+ edge0_world = wp.transform_point(X_ws_a, e0)
1348
+ edge1_world = wp.transform_point(X_ws_a, e1)
1349
+ edge0_b = wp.transform_point(X_sw_b, edge0_world)
1350
+ edge1_b = wp.transform_point(X_sw_b, edge1_world)
1351
+ max_iter = edge_sdf_iter
1352
+ max_dist = (rigid_contact_margin + thickness) / min_scale_b
1353
+ mesh_b = geo.source[shape_b]
1354
+ u = closest_edge_coordinate_mesh(
1355
+ mesh_b, wp.cw_div(edge0_b, geo_scale_b), wp.cw_div(edge1_b, geo_scale_b), max_iter, max_dist
1356
+ )
1357
+ p_a_world = (1.0 - u) * edge0_world + u * edge1_world
1358
+ query_b_local = wp.transform_point(X_sw_b, p_a_world)
1359
+ mesh_b = geo.source[shape_b]
1360
+
1361
+ face_index = int(0)
1362
+ face_u = float(0.0)
1363
+ face_v = float(0.0)
1364
+ sign = float(0.0)
1365
+ res = wp.mesh_query_point_sign_normal(
1366
+ mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1367
+ )
1368
+ if res:
1369
+ shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1370
+ shape_p = wp.cw_mul(shape_p, geo_scale_b)
1371
+ p_b_world = wp.transform_point(X_ws_b, shape_p)
1372
+ p_a_world = closest_point_line_segment(edge0_world, edge1_world, p_b_world)
1373
+ # contact direction vector in world frame
1374
+ diff = p_a_world - p_b_world
1375
+ normal = wp.normalize(diff)
1376
+ distance = wp.dot(diff, normal)
1377
+ else:
1378
+ return
1379
+
1380
+ elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_CAPSULE:
1381
+ # vertex-based contact
1382
+ mesh = wp.mesh_get(geo.source[shape_a])
1383
+ body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1384
+ p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1385
+ # find closest point + contact normal on capsule B
1386
+ half_height_b = geo_scale_b[1]
1387
+ A_b = wp.transform_point(X_ws_b, wp.vec3(0.0, half_height_b, 0.0))
1388
+ B_b = wp.transform_point(X_ws_b, wp.vec3(0.0, -half_height_b, 0.0))
1389
+ p_b_world = closest_point_line_segment(A_b, B_b, p_a_world)
1390
+ diff = p_a_world - p_b_world
1391
+ # this is more reliable in practice than using the SDF gradient
1392
+ normal = wp.normalize(diff)
1393
+ distance = wp.dot(diff, normal)
1394
+
1395
+ elif geo_type_a == wp.sim.GEO_CAPSULE and geo_type_b == wp.sim.GEO_PLANE:
1396
+ plane_width = geo_scale_b[0]
1397
+ plane_length = geo_scale_b[1]
1398
+ if point_id < 2:
1399
+ # vertex-based collision
1400
+ half_height_a = geo_scale_a[1]
1401
+ side = float(point_id) * 2.0 - 1.0
1402
+ p_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, side * half_height_a, 0.0))
1403
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1404
+ p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], query_b)
1405
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1406
+ diff = p_a_world - p_b_world
1407
+ if geo_scale_b[0] > 0.0 and geo_scale_b[1] > 0.0:
1408
+ normal = wp.normalize(diff)
1409
+ else:
1410
+ normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1411
+ distance = wp.dot(diff, normal)
1412
+ else:
1413
+ # contact between capsule A and edges of finite plane B
1414
+ plane_width = geo_scale_b[0]
1415
+ plane_length = geo_scale_b[1]
1416
+ edge = get_plane_edge(point_id - 2, plane_width, plane_length)
1417
+ edge0_world = wp.transform_point(X_ws_b, wp.spatial_top(edge))
1418
+ edge1_world = wp.transform_point(X_ws_b, wp.spatial_bottom(edge))
1419
+ edge0_a = wp.transform_point(X_sw_a, edge0_world)
1420
+ edge1_a = wp.transform_point(X_sw_a, edge1_world)
1421
+ max_iter = edge_sdf_iter
1422
+ u = closest_edge_coordinate_capsule(geo_scale_a[0], geo_scale_a[1], edge0_a, edge1_a, max_iter)
1423
+ p_b_world = (1.0 - u) * edge0_world + u * edge1_world
1424
+
1425
+ # find closest point + contact normal on capsule A
1426
+ half_height_a = geo_scale_a[1]
1427
+ p0_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, half_height_a, 0.0))
1428
+ p1_a_world = wp.transform_point(X_ws_a, wp.vec3(0.0, -half_height_a, 0.0))
1429
+ p_a_world = closest_point_line_segment(p0_a_world, p1_a_world, p_b_world)
1430
+ diff = p_a_world - p_b_world
1431
+ # normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1432
+ normal = wp.normalize(diff)
1433
+ distance = wp.dot(diff, normal)
1434
+
1435
+ elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_BOX:
1436
+ # vertex-based contact
1437
+ mesh = wp.mesh_get(geo.source[shape_a])
1438
+ body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1439
+ p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1440
+ # find closest point + contact normal on box B
1441
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1442
+ p_b_body = closest_point_box(geo_scale_b, query_b)
1443
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1444
+ diff = p_a_world - p_b_world
1445
+ # this is more reliable in practice than using the SDF gradient
1446
+ normal = wp.normalize(diff)
1447
+ if box_sdf(geo_scale_b, query_b) < 0.0:
1448
+ normal = -normal
1449
+ distance = wp.dot(diff, normal)
1450
+
1451
+ elif geo_type_a == wp.sim.GEO_BOX and geo_type_b == wp.sim.GEO_MESH:
1452
+ # vertex-based contact
1453
+ query_a = get_box_vertex(point_id, geo_scale_a)
1454
+ p_a_world = wp.transform_point(X_ws_a, query_a)
1455
+ query_b_local = wp.transform_point(X_sw_b, p_a_world)
1456
+ mesh_b = geo.source[shape_b]
1457
+ max_dist = (rigid_contact_margin + thickness) / min_scale_b
1458
+ face_index = int(0)
1459
+ face_u = float(0.0)
1460
+ face_v = float(0.0)
1461
+ sign = float(0.0)
1462
+ res = wp.mesh_query_point_sign_normal(
1463
+ mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1464
+ )
1465
+
1466
+ if res:
1467
+ shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1468
+ shape_p = wp.cw_mul(shape_p, geo_scale_b)
1469
+ p_b_world = wp.transform_point(X_ws_b, shape_p)
1470
+ # contact direction vector in world frame
1471
+ diff_b = p_a_world - p_b_world
1472
+ normal = wp.normalize(diff_b) * sign
1473
+ distance = wp.dot(diff_b, normal)
1474
+ else:
1475
+ return
1476
+
1477
+ elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_MESH:
1478
+ # vertex-based contact
1479
+ mesh = wp.mesh_get(geo.source[shape_a])
1480
+ mesh_b = geo.source[shape_b]
1481
+
1482
+ body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1483
+ p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1484
+ query_b_local = wp.transform_point(X_sw_b, p_a_world)
1485
+
1486
+ face_index = int(0)
1487
+ face_u = float(0.0)
1488
+ face_v = float(0.0)
1489
+ sign = float(0.0)
1490
+ min_scale = min(min_scale_a, min_scale_b)
1491
+ max_dist = (rigid_contact_margin + thickness) / min_scale
1492
+
1493
+ res = wp.mesh_query_point_sign_normal(
1494
+ mesh_b, wp.cw_div(query_b_local, geo_scale_b), max_dist, sign, face_index, face_u, face_v
1495
+ )
1496
+
1497
+ if res:
1498
+ shape_p = wp.mesh_eval_position(mesh_b, face_index, face_u, face_v)
1499
+ shape_p = wp.cw_mul(shape_p, geo_scale_b)
1500
+ p_b_world = wp.transform_point(X_ws_b, shape_p)
1501
+ # contact direction vector in world frame
1502
+ diff_b = p_a_world - p_b_world
1503
+ normal = wp.normalize(diff_b) * sign
1504
+ distance = wp.dot(diff_b, normal)
1505
+ else:
1506
+ return
1507
+
1508
+ elif geo_type_a == wp.sim.GEO_MESH and geo_type_b == wp.sim.GEO_PLANE:
1509
+ # vertex-based contact
1510
+ mesh = wp.mesh_get(geo.source[shape_a])
1511
+ body_a_pos = wp.cw_mul(mesh.points[point_id], geo_scale_a)
1512
+ p_a_world = wp.transform_point(X_ws_a, body_a_pos)
1513
+ query_b = wp.transform_point(X_sw_b, p_a_world)
1514
+ p_b_body = closest_point_plane(geo_scale_b[0], geo_scale_b[1], query_b)
1515
+ p_b_world = wp.transform_point(X_ws_b, p_b_body)
1516
+ diff = p_a_world - p_b_world
1517
+
1518
+ # if the plane is infinite or the point is within the plane we fix the normal to prevent intersections
1519
+ if (
1520
+ geo_scale_b[0] == 0.0
1521
+ and geo_scale_b[1] == 0.0
1522
+ or wp.abs(query_b[0]) < geo_scale_b[0]
1523
+ and wp.abs(query_b[2]) < geo_scale_b[1]
1524
+ ):
1525
+ normal = wp.transform_vector(X_ws_b, wp.vec3(0.0, 1.0, 0.0))
1526
+ distance = wp.dot(diff, normal)
1527
+ else:
1528
+ normal = wp.normalize(diff)
1529
+ distance = wp.dot(diff, normal)
1530
+ # ignore extreme penetrations (e.g. when mesh is below the plane)
1531
+ if distance < -rigid_contact_margin:
1532
+ return
1533
+
1534
+ else:
1535
+ print("Unsupported geometry pair in collision handling")
1536
+ return
1537
+
1538
+ d = distance - thickness
1539
+ if d < rigid_contact_margin:
1540
+ if contact_pairwise_counter:
1541
+ pair_contact_id = limited_counter_increment(
1542
+ contact_pairwise_counter, pair_index, contact_tids, tid, contact_limit
1543
+ )
1544
+ if pair_contact_id == -1:
1545
+ # wp.printf("Reached contact point limit %d >= %d for shape pair %d and %d (pair_index: %d)\n",
1546
+ # contact_pairwise_counter[pair_index], contact_limit, shape_a, shape_b, pair_index)
1547
+ # reached contact point limit
1548
+ return
1549
+ index = counter_increment(contact_count, 0, contact_tids, tid)
1550
+ contact_shape0[index] = shape_a
1551
+ contact_shape1[index] = shape_b
1552
+ # transform from world into body frame (so the contact point includes the shape transform)
1553
+ contact_point0[index] = wp.transform_point(X_bw_a, p_a_world)
1554
+ contact_point1[index] = wp.transform_point(X_bw_b, p_b_world)
1555
+ contact_offset0[index] = wp.transform_vector(X_bw_a, -thickness_a * normal)
1556
+ contact_offset1[index] = wp.transform_vector(X_bw_b, thickness_b * normal)
1557
+ contact_normal[index] = normal
1558
+ contact_thickness[index] = thickness
1559
+
1560
+
1561
+ def collide(
1562
+ model: Model,
1563
+ state: State,
1564
+ edge_sdf_iter: int = 10,
1565
+ iterate_mesh_vertices: bool = True,
1566
+ requires_grad: Optional[bool] = None,
1567
+ ) -> None:
1568
+ """Generate contact points for the particles and rigid bodies in the model for use in contact-dynamics kernels.
1569
+
1570
+ Args:
1571
+ model: The model to be simulated.
1572
+ state: The state of the model.
1573
+ edge_sdf_iter: Number of search iterations for finding closest contact points between edges and SDF.
1574
+ iterate_mesh_vertices: Whether to iterate over all vertices of a mesh for contact generation
1575
+ (used for capsule/box <> mesh collision).
1576
+ requires_grad: Whether to duplicate contact arrays for gradient computation
1577
+ (if ``None``, uses ``model.requires_grad``).
1578
+ """
1579
+
1580
+ if requires_grad is None:
1581
+ requires_grad = model.requires_grad
1582
+
1583
+ with wp.ScopedTimer("collide", False):
1584
+ # generate soft contacts for particles and shapes except ground plane (last shape)
1585
+ if model.particle_count and model.shape_count > 1:
1586
+ if requires_grad:
1587
+ model.soft_contact_body_pos = wp.empty_like(model.soft_contact_body_pos)
1588
+ model.soft_contact_body_vel = wp.empty_like(model.soft_contact_body_vel)
1589
+ model.soft_contact_normal = wp.empty_like(model.soft_contact_normal)
1590
+ # clear old count
1591
+ model.soft_contact_count.zero_()
1592
+ wp.launch(
1593
+ kernel=create_soft_contacts,
1594
+ dim=model.particle_count * (model.shape_count - 1),
1595
+ inputs=[
1596
+ state.particle_q,
1597
+ model.particle_radius,
1598
+ model.particle_flags,
1599
+ state.body_q,
1600
+ model.shape_transform,
1601
+ model.shape_body,
1602
+ model.shape_geo,
1603
+ model.soft_contact_margin,
1604
+ model.soft_contact_max,
1605
+ model.shape_count - 1,
1606
+ ],
1607
+ outputs=[
1608
+ model.soft_contact_count,
1609
+ model.soft_contact_particle,
1610
+ model.soft_contact_shape,
1611
+ model.soft_contact_body_pos,
1612
+ model.soft_contact_body_vel,
1613
+ model.soft_contact_normal,
1614
+ model.soft_contact_tids,
1615
+ ],
1616
+ device=model.device,
1617
+ )
1618
+
1619
+ if model.shape_contact_pair_count or model.ground and model.shape_ground_contact_pair_count:
1620
+ # clear old count
1621
+ model.rigid_contact_count.zero_()
1622
+
1623
+ model.rigid_contact_broad_shape0.fill_(-1)
1624
+ model.rigid_contact_broad_shape1.fill_(-1)
1625
+
1626
+ if model.shape_contact_pair_count:
1627
+ wp.launch(
1628
+ kernel=broadphase_collision_pairs,
1629
+ dim=model.shape_contact_pair_count,
1630
+ inputs=[
1631
+ model.shape_contact_pairs,
1632
+ state.body_q,
1633
+ model.shape_transform,
1634
+ model.shape_body,
1635
+ model.body_mass,
1636
+ model.shape_count,
1637
+ model.shape_geo,
1638
+ model.shape_collision_radius,
1639
+ model.rigid_contact_max,
1640
+ model.rigid_contact_margin,
1641
+ model.rigid_mesh_contact_max,
1642
+ iterate_mesh_vertices,
1643
+ ],
1644
+ outputs=[
1645
+ model.rigid_contact_count,
1646
+ model.rigid_contact_broad_shape0,
1647
+ model.rigid_contact_broad_shape1,
1648
+ model.rigid_contact_point_id,
1649
+ model.rigid_contact_point_limit,
1650
+ ],
1651
+ device=model.device,
1652
+ record_tape=False,
1653
+ )
1654
+
1655
+ if model.ground and model.shape_ground_contact_pair_count:
1656
+ wp.launch(
1657
+ kernel=broadphase_collision_pairs,
1658
+ dim=model.shape_ground_contact_pair_count,
1659
+ inputs=[
1660
+ model.shape_ground_contact_pairs,
1661
+ state.body_q,
1662
+ model.shape_transform,
1663
+ model.shape_body,
1664
+ model.body_mass,
1665
+ model.shape_count,
1666
+ model.shape_geo,
1667
+ model.shape_collision_radius,
1668
+ model.rigid_contact_max,
1669
+ model.rigid_contact_margin,
1670
+ model.rigid_mesh_contact_max,
1671
+ iterate_mesh_vertices,
1672
+ ],
1673
+ outputs=[
1674
+ model.rigid_contact_count,
1675
+ model.rigid_contact_broad_shape0,
1676
+ model.rigid_contact_broad_shape1,
1677
+ model.rigid_contact_point_id,
1678
+ model.rigid_contact_point_limit,
1679
+ ],
1680
+ device=model.device,
1681
+ record_tape=False,
1682
+ )
1683
+
1684
+ if model.shape_contact_pair_count or model.ground and model.shape_ground_contact_pair_count:
1685
+ if requires_grad:
1686
+ model.rigid_contact_point0 = wp.empty_like(model.rigid_contact_point0)
1687
+ model.rigid_contact_point1 = wp.empty_like(model.rigid_contact_point1)
1688
+ model.rigid_contact_offset0 = wp.empty_like(model.rigid_contact_offset0)
1689
+ model.rigid_contact_offset1 = wp.empty_like(model.rigid_contact_offset1)
1690
+ model.rigid_contact_normal = wp.empty_like(model.rigid_contact_normal)
1691
+ model.rigid_contact_thickness = wp.empty_like(model.rigid_contact_thickness)
1692
+ model.rigid_contact_count = wp.zeros_like(model.rigid_contact_count)
1693
+ model.rigid_contact_tids = wp.zeros_like(model.rigid_contact_tids)
1694
+ model.rigid_contact_shape0 = wp.empty_like(model.rigid_contact_shape0)
1695
+ model.rigid_contact_shape1 = wp.empty_like(model.rigid_contact_shape1)
1696
+
1697
+ if model.rigid_contact_pairwise_counter is not None:
1698
+ model.rigid_contact_pairwise_counter = wp.zeros_like(model.rigid_contact_pairwise_counter)
1699
+ else:
1700
+ model.rigid_contact_count.zero_()
1701
+ model.rigid_contact_tids.zero_()
1702
+
1703
+ if model.rigid_contact_pairwise_counter is not None:
1704
+ model.rigid_contact_pairwise_counter.zero_()
1705
+
1706
+ model.rigid_contact_shape0.fill_(-1)
1707
+ model.rigid_contact_shape1.fill_(-1)
1708
+
1709
+ wp.launch(
1710
+ kernel=handle_contact_pairs,
1711
+ dim=model.rigid_contact_max,
1712
+ inputs=[
1713
+ state.body_q,
1714
+ model.shape_transform,
1715
+ model.shape_body,
1716
+ model.shape_geo,
1717
+ model.rigid_contact_margin,
1718
+ model.rigid_contact_broad_shape0,
1719
+ model.rigid_contact_broad_shape1,
1720
+ model.shape_count,
1721
+ model.rigid_contact_point_id,
1722
+ model.rigid_contact_point_limit,
1723
+ edge_sdf_iter,
1724
+ ],
1725
+ outputs=[
1726
+ model.rigid_contact_count,
1727
+ model.rigid_contact_shape0,
1728
+ model.rigid_contact_shape1,
1729
+ model.rigid_contact_point0,
1730
+ model.rigid_contact_point1,
1731
+ model.rigid_contact_offset0,
1732
+ model.rigid_contact_offset1,
1733
+ model.rigid_contact_normal,
1734
+ model.rigid_contact_thickness,
1735
+ model.rigid_contact_pairwise_counter,
1736
+ model.rigid_contact_tids,
1737
+ ],
1738
+ device=model.device,
1739
+ )
1740
+
1741
+
1742
+ @wp.func
1743
+ def compute_tri_aabb(
1744
+ v1: wp.vec3,
1745
+ v2: wp.vec3,
1746
+ v3: wp.vec3,
1747
+ ):
1748
+ lower = wp.min(wp.min(v1, v2), v3)
1749
+ upper = wp.max(wp.max(v1, v2), v3)
1750
+
1751
+ return lower, upper
1752
+
1753
+
1754
+ @wp.kernel
1755
+ def compute_tri_aabbs(
1756
+ pos: wp.array(dtype=wp.vec3),
1757
+ tri_indices: wp.array(dtype=wp.int32, ndim=2),
1758
+ lower_bounds: wp.array(dtype=wp.vec3),
1759
+ upper_bounds: wp.array(dtype=wp.vec3),
1760
+ ):
1761
+ t_id = wp.tid()
1762
+
1763
+ v1 = pos[tri_indices[t_id, 0]]
1764
+ v2 = pos[tri_indices[t_id, 1]]
1765
+ v3 = pos[tri_indices[t_id, 2]]
1766
+
1767
+ lower, upper = compute_tri_aabb(v1, v2, v3)
1768
+
1769
+ lower_bounds[t_id] = lower
1770
+ upper_bounds[t_id] = upper
1771
+
1772
+
1773
+ @wp.kernel
1774
+ def compute_edge_aabbs(
1775
+ pos: wp.array(dtype=wp.vec3),
1776
+ edge_indices: wp.array(dtype=wp.int32, ndim=2),
1777
+ lower_bounds: wp.array(dtype=wp.vec3),
1778
+ upper_bounds: wp.array(dtype=wp.vec3),
1779
+ ):
1780
+ e_id = wp.tid()
1781
+
1782
+ v1 = pos[edge_indices[e_id, 2]]
1783
+ v2 = pos[edge_indices[e_id, 3]]
1784
+
1785
+ lower_bounds[e_id] = wp.min(v1, v2)
1786
+ upper_bounds[e_id] = wp.max(v1, v2)
1787
+
1788
+
1789
+ @wp.func
1790
+ def tri_is_neighbor(a_1: wp.int32, a_2: wp.int32, a_3: wp.int32, b_1: wp.int32, b_2: wp.int32, b_3: wp.int32):
1791
+ tri_is_neighbor = (
1792
+ a_1 == b_1
1793
+ or a_1 == b_2
1794
+ or a_1 == b_3
1795
+ or a_2 == b_1
1796
+ or a_2 == b_2
1797
+ or a_2 == b_3
1798
+ or a_3 == b_1
1799
+ or a_3 == b_2
1800
+ or a_3 == b_3
1801
+ )
1802
+
1803
+ return tri_is_neighbor
1804
+
1805
+
1806
+ @wp.func
1807
+ def vertex_adjacent_to_triangle(v: wp.int32, a: wp.int32, b: wp.int32, c: wp.int32):
1808
+ return v == a or v == b or v == c
1809
+
1810
+
1811
+ @wp.kernel
1812
+ def init_triangle_collision_data_kernel(
1813
+ query_radius: float,
1814
+ # outputs
1815
+ triangle_colliding_vertices_count: wp.array(dtype=wp.int32),
1816
+ triangle_colliding_vertices_min_dist: wp.array(dtype=float),
1817
+ resize_flags: wp.array(dtype=wp.int32),
1818
+ ):
1819
+ tri_index = wp.tid()
1820
+
1821
+ triangle_colliding_vertices_count[tri_index] = 0
1822
+ triangle_colliding_vertices_min_dist[tri_index] = query_radius
1823
+
1824
+ if tri_index == 0:
1825
+ for i in range(3):
1826
+ resize_flags[i] = 0
1827
+
1828
+
1829
+ @wp.kernel
1830
+ def vertex_triangle_collision_detection_kernel(
1831
+ query_radius: float,
1832
+ bvh_id: wp.uint64,
1833
+ pos: wp.array(dtype=wp.vec3),
1834
+ tri_indices: wp.array(dtype=wp.int32, ndim=2),
1835
+ vertex_colliding_triangles_offsets: wp.array(dtype=wp.int32),
1836
+ vertex_colliding_triangles_buffer_sizes: wp.array(dtype=wp.int32),
1837
+ triangle_colliding_vertices_offsets: wp.array(dtype=wp.int32),
1838
+ triangle_colliding_vertices_buffer_sizes: wp.array(dtype=wp.int32),
1839
+ # outputs
1840
+ vertex_colliding_triangles: wp.array(dtype=wp.int32),
1841
+ vertex_colliding_triangles_count: wp.array(dtype=wp.int32),
1842
+ vertex_colliding_triangles_min_dist: wp.array(dtype=float),
1843
+ triangle_colliding_vertices: wp.array(dtype=wp.int32),
1844
+ triangle_colliding_vertices_count: wp.array(dtype=wp.int32),
1845
+ triangle_colliding_vertices_min_dist: wp.array(dtype=float),
1846
+ resize_flags: wp.array(dtype=wp.int32),
1847
+ ):
1848
+ """
1849
+ This function applies discrete collision detection between vertices and triangles. It uses pre-allocated spaces to
1850
+ record the collision data. This collision detector works both ways, i.e., it records vertices' colliding triangles to
1851
+ vertex_colliding_triangles, and records each triangles colliding vertices to triangle_colliding_vertices.
1852
+
1853
+ This function assumes that all the vertices are on triangles, and can be indexed from the pos argument.
1854
+
1855
+ Note:
1856
+
1857
+ The collision date buffer is pre-allocated and cannot be changed during collision detection, therefore, the space
1858
+ may not be enough. If the space is not enough to record all the collision information, the function will set a
1859
+ certain element in resized_flag to be true. The user can reallocate the buffer based on vertex_colliding_triangles_count
1860
+ and vertex_colliding_triangles_count.
1861
+
1862
+ Attributes:
1863
+ bvh_id (int): the bvh id you want to collide with
1864
+ query_radius (float): the contact radius. vertex-triangle pairs whose distance are less than this will get detected
1865
+ pos (array): positions of all the vertices that make up triangles
1866
+ vertex_colliding_triangles (array): flattened buffer of vertices' collision triangles
1867
+ vertex_colliding_triangles_count (array): number of triangles each vertex collides
1868
+ vertex_colliding_triangles_offsets (array): where each vertex' collision buffer starts
1869
+ vertex_colliding_triangles_buffer_sizes (array): size of each vertex' collision buffer, will be modified if resizing is needed
1870
+ vertex_colliding_triangles_min_dist (array): each vertex' min distance to all (non-neighbor) triangles
1871
+ triangle_colliding_vertices (array): positions of all the triangles' collision vertices
1872
+ triangle_colliding_vertices_count (array): number of triangles each vertex collides
1873
+ triangle_colliding_vertices_offsets (array): where each triangle's collision buffer starts
1874
+ triangle_colliding_vertices_buffer_sizes (array): size of each triangle's collision buffer, will be modified if resizing is needed
1875
+ triangle_colliding_vertices_min_dist (array): each triangle's min distance to all (non-self) vertices
1876
+ resized_flag (array): size == 3, (vertex_buffer_resize_required, triangle_buffer_resize_required, edge_buffer_resize_required)
1877
+ """
1878
+
1879
+ v_index = wp.tid()
1880
+ v = pos[v_index]
1881
+
1882
+ lower = wp.vec3(v[0] - query_radius, v[1] - query_radius, v[2] - query_radius)
1883
+ upper = wp.vec3(v[0] + query_radius, v[1] + query_radius, v[2] + query_radius)
1884
+
1885
+ query = wp.bvh_query_aabb(bvh_id, lower, upper)
1886
+
1887
+ tri_index = wp.int32(0)
1888
+ vertex_num_collisions = wp.int32(0)
1889
+ min_dis_to_tris = query_radius
1890
+ while wp.bvh_query_next(query, tri_index):
1891
+ t1 = tri_indices[tri_index, 0]
1892
+ t2 = tri_indices[tri_index, 1]
1893
+ t3 = tri_indices[tri_index, 2]
1894
+ if vertex_adjacent_to_triangle(v_index, t1, t2, t3):
1895
+ continue
1896
+
1897
+ u1 = pos[t1]
1898
+ u2 = pos[t2]
1899
+ u3 = pos[t3]
1900
+
1901
+ closest_p, bary, feature_type = triangle_closest_point(u1, u2, u3, v)
1902
+
1903
+ dist = wp.length(closest_p - v)
1904
+
1905
+ if dist < query_radius:
1906
+ vertex_buffer_offset = vertex_colliding_triangles_offsets[v_index]
1907
+ vertex_buffer_size = vertex_colliding_triangles_offsets[v_index + 1] - vertex_buffer_offset
1908
+
1909
+ # record v-f collision to vertex
1910
+ min_dis_to_tris = wp.min(min_dis_to_tris, dist)
1911
+ if vertex_num_collisions < vertex_buffer_size:
1912
+ vertex_colliding_triangles[vertex_buffer_offset + vertex_num_collisions] = tri_index
1913
+ else:
1914
+ resize_flags[VERTEX_COLLISION_BUFFER_OVERFLOW_INDEX] = 1
1915
+
1916
+ vertex_num_collisions = vertex_num_collisions + 1
1917
+
1918
+ wp.atomic_min(triangle_colliding_vertices_min_dist, tri_index, dist)
1919
+ tri_buffer_size = triangle_colliding_vertices_buffer_sizes[tri_index]
1920
+ tri_num_collisions = wp.atomic_add(triangle_colliding_vertices_count, tri_index, 1)
1921
+
1922
+ if tri_num_collisions < tri_buffer_size:
1923
+ tri_buffer_offset = triangle_colliding_vertices_offsets[tri_index]
1924
+ # record v-f collision to triangle
1925
+ triangle_colliding_vertices[tri_buffer_offset + tri_num_collisions] = v_index
1926
+ else:
1927
+ resize_flags[TRI_COLLISION_BUFFER_OVERFLOW_INDEX] = 1
1928
+
1929
+ vertex_colliding_triangles_count[v_index] = vertex_num_collisions
1930
+ vertex_colliding_triangles_min_dist[v_index] = min_dis_to_tris
1931
+
1932
+
1933
+ @wp.kernel
1934
+ def edge_colliding_edges_detection_kernel(
1935
+ query_radius: float,
1936
+ bvh_id: wp.uint64,
1937
+ pos: wp.array(dtype=wp.vec3),
1938
+ edge_indices: wp.array(dtype=wp.int32, ndim=2),
1939
+ edge_colliding_edges_offsets: wp.array(dtype=wp.int32),
1940
+ edge_colliding_edges_buffer_sizes: wp.array(dtype=wp.int32),
1941
+ edge_edge_parallel_epsilon: float,
1942
+ # outputs
1943
+ edge_colliding_edges: wp.array(dtype=wp.int32),
1944
+ edge_colliding_edges_count: wp.array(dtype=wp.int32),
1945
+ edge_colliding_edges_min_dist: wp.array(dtype=float),
1946
+ resize_flags: wp.array(dtype=wp.int32),
1947
+ ):
1948
+ """
1949
+ bvh_id (int): the bvh id you want to do collision detection on
1950
+ query_radius (float):
1951
+ pos (array): positions of all the vertices that make up edges
1952
+ edge_colliding_triangles (array): flattened buffer of edges' collision edges
1953
+ edge_colliding_edges_count (array): number of edges each edge collides
1954
+ edge_colliding_triangles_offsets (array): where each edge's collision buffer starts
1955
+ edge_colliding_triangles_buffer_size (array): size of each edge's collision buffer, will be modified if resizing is needed
1956
+ edge_min_dis_to_triangles (array): each vertex' min distance to all (non-neighbor) triangles
1957
+ resized_flag (array): size == 3, (vertex_buffer_resize_required, triangle_buffer_resize_required, edge_buffer_resize_required)
1958
+ """
1959
+ e_index = wp.tid()
1960
+
1961
+ e0_v0 = edge_indices[e_index, 2]
1962
+ e0_v1 = edge_indices[e_index, 3]
1963
+
1964
+ e0_v0_pos = pos[e0_v0]
1965
+ e0_v1_pos = pos[e0_v1]
1966
+
1967
+ lower = wp.min(e0_v0_pos, e0_v1_pos)
1968
+ upper = wp.max(e0_v0_pos, e0_v1_pos)
1969
+
1970
+ lower = wp.vec3(lower[0] - query_radius, lower[1] - query_radius, lower[2] - query_radius)
1971
+ upper = wp.vec3(upper[0] + query_radius, upper[1] + query_radius, upper[2] + query_radius)
1972
+
1973
+ query = wp.bvh_query_aabb(bvh_id, lower, upper)
1974
+
1975
+ colliding_edge_index = wp.int32(0)
1976
+ edge_num_collisions = wp.int32(0)
1977
+ min_dis_to_edges = query_radius
1978
+ while wp.bvh_query_next(query, colliding_edge_index):
1979
+ e1_v0 = edge_indices[colliding_edge_index, 2]
1980
+ e1_v1 = edge_indices[colliding_edge_index, 3]
1981
+
1982
+ if e0_v0 == e1_v0 or e0_v0 == e1_v1 or e0_v1 == e1_v0 or e0_v1 == e1_v1:
1983
+ continue
1984
+
1985
+ e1_v0_pos = pos[e1_v0]
1986
+ e1_v1_pos = pos[e1_v1]
1987
+
1988
+ st = wp.closest_point_edge_edge(e0_v0_pos, e0_v1_pos, e1_v0_pos, e1_v1_pos, edge_edge_parallel_epsilon)
1989
+ s = st[0]
1990
+ t = st[1]
1991
+ c1 = e0_v0_pos + (e0_v1_pos - e0_v0_pos) * s
1992
+ c2 = e1_v0_pos + (e1_v1_pos - e1_v0_pos) * t
1993
+
1994
+ dist = wp.length(c1 - c2)
1995
+ if dist < query_radius:
1996
+ edge_buffer_offset = edge_colliding_edges_offsets[e_index]
1997
+ edge_buffer_size = edge_colliding_edges_offsets[e_index + 1] - edge_buffer_offset
1998
+
1999
+ # record e-e collision to e0, and leave e1; e1 will detect this collision from its own thread
2000
+ min_dis_to_edges = wp.min(min_dis_to_edges, dist)
2001
+ if edge_num_collisions < edge_buffer_size:
2002
+ edge_colliding_edges[edge_buffer_offset + edge_num_collisions] = colliding_edge_index
2003
+ else:
2004
+ resize_flags[EDGE_COLLISION_BUFFER_OVERFLOW_INDEX] = 1
2005
+
2006
+ edge_num_collisions = edge_num_collisions + 1
2007
+
2008
+ edge_colliding_edges_count[e_index] = edge_num_collisions
2009
+ edge_colliding_edges_min_dist[e_index] = min_dis_to_edges
2010
+
2011
+
2012
+ @wp.kernel
2013
+ def triangle_triangle_collision_detection_kernel(
2014
+ bvh_id: wp.uint64,
2015
+ pos: wp.array(dtype=wp.vec3),
2016
+ tri_indices: wp.array(dtype=wp.int32, ndim=2),
2017
+ triangle_intersecting_triangles_offsets: wp.array(dtype=wp.int32),
2018
+ # outputs
2019
+ triangle_intersecting_triangles: wp.array(dtype=wp.int32),
2020
+ triangle_intersecting_triangles_count: wp.array(dtype=wp.int32),
2021
+ resize_flags: wp.array(dtype=wp.int32),
2022
+ ):
2023
+ tri_index = wp.tid()
2024
+ t1_v1 = tri_indices[tri_index, 0]
2025
+ t1_v2 = tri_indices[tri_index, 1]
2026
+ t1_v3 = tri_indices[tri_index, 2]
2027
+
2028
+ v1 = pos[t1_v1]
2029
+ v2 = pos[t1_v2]
2030
+ v3 = pos[t1_v3]
2031
+
2032
+ lower, upper = compute_tri_aabb(v1, v2, v3)
2033
+
2034
+ buffer_offset = triangle_intersecting_triangles_offsets[tri_index]
2035
+ buffer_size = triangle_intersecting_triangles_offsets[tri_index + 1] - buffer_offset
2036
+
2037
+ query = wp.bvh_query_aabb(bvh_id, lower, upper)
2038
+ tri_index_2 = wp.int32(0)
2039
+ intersection_count = wp.int32(0)
2040
+ while wp.bvh_query_next(query, tri_index_2):
2041
+ t2_v1 = tri_indices[tri_index_2, 0]
2042
+ t2_v2 = tri_indices[tri_index_2, 1]
2043
+ t2_v3 = tri_indices[tri_index_2, 2]
2044
+
2045
+ # filter out intersection test with neighbor triangles
2046
+ if (
2047
+ vertex_adjacent_to_triangle(t1_v1, t2_v1, t2_v2, t2_v3)
2048
+ or vertex_adjacent_to_triangle(t1_v2, t2_v1, t2_v2, t2_v3)
2049
+ or vertex_adjacent_to_triangle(t1_v3, t2_v1, t2_v2, t2_v3)
2050
+ ):
2051
+ continue
2052
+
2053
+ u1 = pos[t2_v1]
2054
+ u2 = pos[t2_v2]
2055
+ u3 = pos[t2_v3]
2056
+
2057
+ if wp.intersect_tri_tri(v1, v2, v3, u1, u2, u3):
2058
+ if intersection_count < buffer_size:
2059
+ triangle_intersecting_triangles[buffer_offset + intersection_count] = tri_index_2
2060
+ else:
2061
+ resize_flags[TRI_TRI_COLLISION_BUFFER_OVERFLOW_INDEX] = 1
2062
+ intersection_count = intersection_count + 1
2063
+
2064
+ triangle_intersecting_triangles_count[tri_index] = intersection_count
2065
+
2066
+
2067
+ @wp.struct
2068
+ class TriMeshCollisionInfo:
2069
+ vertex_colliding_triangles: wp.array(dtype=wp.int32)
2070
+ vertex_colliding_triangles_offsets: wp.array(dtype=wp.int32)
2071
+ vertex_colliding_triangles_buffer_sizes: wp.array(dtype=wp.int32)
2072
+ vertex_colliding_triangles_count: wp.array(dtype=wp.int32)
2073
+ vertex_colliding_triangles_min_dist: wp.array(dtype=float)
2074
+
2075
+ triangle_colliding_vertices: wp.array(dtype=wp.int32)
2076
+ triangle_colliding_vertices_offsets: wp.array(dtype=wp.int32)
2077
+ triangle_colliding_vertices_buffer_sizes: wp.array(dtype=wp.int32)
2078
+ triangle_colliding_vertices_count: wp.array(dtype=wp.int32)
2079
+ triangle_colliding_vertices_min_dist: wp.array(dtype=float)
2080
+
2081
+ edge_colliding_edges: wp.array(dtype=wp.int32)
2082
+ edge_colliding_edges_offsets: wp.array(dtype=wp.int32)
2083
+ edge_colliding_edges_buffer_sizes: wp.array(dtype=wp.int32)
2084
+ edge_colliding_edges_count: wp.array(dtype=wp.int32)
2085
+ edge_colliding_edges_min_dist: wp.array(dtype=float)
2086
+
2087
+
2088
+ @wp.func
2089
+ def get_vertex_colliding_triangles_count(col_info: TriMeshCollisionInfo, v: int):
2090
+ return wp.min(col_info.vertex_colliding_triangles_count[v], col_info.vertex_colliding_triangles_buffer_sizes[v])
2091
+
2092
+
2093
+ @wp.func
2094
+ def get_vertex_colliding_triangles(col_info: TriMeshCollisionInfo, v: int, i_collision: int):
2095
+ offset = col_info.vertex_colliding_triangles_offsets[v]
2096
+ return col_info.vertex_colliding_triangles[offset + i_collision]
2097
+
2098
+
2099
+ @wp.func
2100
+ def get_triangle_colliding_vertices_count(col_info: TriMeshCollisionInfo, tri: int):
2101
+ return wp.min(
2102
+ col_info.triangle_colliding_vertices_count[tri], col_info.triangle_colliding_vertices_buffer_sizes[tri]
2103
+ )
2104
+
2105
+
2106
+ @wp.func
2107
+ def get_triangle_colliding_vertices(col_info: TriMeshCollisionInfo, tri: int, i_collision: int):
2108
+ offset = col_info.triangle_colliding_vertices_offsets[tri]
2109
+ return col_info.triangle_colliding_vertices[offset + i_collision]
2110
+
2111
+
2112
+ @wp.func
2113
+ def get_edge_colliding_edges_count(col_info: TriMeshCollisionInfo, e: int):
2114
+ return wp.min(col_info.edge_colliding_edges_count[e], col_info.edge_colliding_edges_buffer_sizes[e])
2115
+
2116
+
2117
+ @wp.func
2118
+ def get_edge_colliding_edges(col_info: TriMeshCollisionInfo, e: int, i_collision: int):
2119
+ offset = col_info.edge_colliding_edges_offsets[e]
2120
+ return col_info.edge_colliding_edges[offset + i_collision]
2121
+
2122
+
2123
+ class TriMeshCollisionDetector:
2124
+ def __init__(
2125
+ self,
2126
+ model: Model,
2127
+ vertex_positions=None,
2128
+ vertex_collision_buffer_pre_alloc=8,
2129
+ vertex_collision_buffer_max_alloc=256,
2130
+ triangle_collision_buffer_pre_alloc=16,
2131
+ triangle_collision_buffer_max_alloc=256,
2132
+ edge_collision_buffer_pre_alloc=8,
2133
+ edge_collision_buffer_max_alloc=256,
2134
+ triangle_triangle_collision_buffer_pre_alloc=8,
2135
+ triangle_triangle_collision_buffer_max_alloc=256,
2136
+ edge_edge_parallel_epsilon=1e-5,
2137
+ ):
2138
+ self.model = model
2139
+ self.vertex_positions = model.particle_q if vertex_positions is None else vertex_positions
2140
+ self.device = model.device
2141
+ self.vertex_collision_buffer_pre_alloc = vertex_collision_buffer_pre_alloc
2142
+ self.vertex_collision_buffer_max_alloc = vertex_collision_buffer_max_alloc
2143
+ self.triangle_collision_buffer_pre_alloc = triangle_collision_buffer_pre_alloc
2144
+ self.triangle_collision_buffer_max_alloc = triangle_collision_buffer_max_alloc
2145
+ self.edge_collision_buffer_pre_alloc = edge_collision_buffer_pre_alloc
2146
+ self.edge_collision_buffer_max_alloc = edge_collision_buffer_max_alloc
2147
+ self.triangle_triangle_collision_buffer_pre_alloc = triangle_triangle_collision_buffer_pre_alloc
2148
+ self.triangle_triangle_collision_buffer_max_alloc = triangle_triangle_collision_buffer_max_alloc
2149
+
2150
+ self.edge_edge_parallel_epsilon = edge_edge_parallel_epsilon
2151
+
2152
+ self.lower_bounds_tris = wp.array(shape=(model.tri_count,), dtype=wp.vec3, device=model.device)
2153
+ self.upper_bounds_tris = wp.array(shape=(model.tri_count,), dtype=wp.vec3, device=model.device)
2154
+ wp.launch(
2155
+ kernel=compute_tri_aabbs,
2156
+ inputs=[self.vertex_positions, model.tri_indices, self.lower_bounds_tris, self.upper_bounds_tris],
2157
+ dim=model.tri_count,
2158
+ device=model.device,
2159
+ )
2160
+
2161
+ self.bvh_tris = wp.Bvh(self.lower_bounds_tris, self.upper_bounds_tris)
2162
+
2163
+ # collision detections results
2164
+
2165
+ # vertex collision buffers
2166
+ self.vertex_colliding_triangles = wp.zeros(
2167
+ shape=(model.particle_count * self.vertex_collision_buffer_pre_alloc,), dtype=wp.int32, device=self.device
2168
+ )
2169
+ self.vertex_colliding_triangles_count = wp.array(
2170
+ shape=(model.particle_count,), dtype=wp.int32, device=self.device
2171
+ )
2172
+ self.vertex_colliding_triangles_min_dist = wp.array(
2173
+ shape=(model.particle_count,), dtype=float, device=self.device
2174
+ )
2175
+ self.vertex_colliding_triangles_buffer_sizes = wp.full(
2176
+ shape=(model.particle_count,),
2177
+ value=self.vertex_collision_buffer_pre_alloc,
2178
+ dtype=wp.int32,
2179
+ device=self.device,
2180
+ )
2181
+ self.vertex_colliding_triangles_offsets = wp.array(
2182
+ shape=(model.particle_count + 1,), dtype=wp.int32, device=self.device
2183
+ )
2184
+ self.compute_collision_buffer_offsets(
2185
+ self.vertex_colliding_triangles_buffer_sizes, self.vertex_colliding_triangles_offsets
2186
+ )
2187
+
2188
+ # triangle collision buffers
2189
+ self.triangle_colliding_vertices = wp.zeros(
2190
+ shape=(model.tri_count * self.triangle_collision_buffer_pre_alloc,), dtype=wp.int32, device=self.device
2191
+ )
2192
+ self.triangle_colliding_vertices_count = wp.zeros(shape=(model.tri_count,), dtype=wp.int32, device=self.device)
2193
+ self.triangle_colliding_vertices_buffer_sizes = wp.full(
2194
+ shape=(model.tri_count,), value=self.triangle_collision_buffer_pre_alloc, dtype=wp.int32, device=self.device
2195
+ )
2196
+ self.triangle_colliding_vertices_min_dist = wp.array(shape=(model.tri_count,), dtype=float, device=self.device)
2197
+
2198
+ self.triangle_colliding_vertices_offsets = wp.array(
2199
+ shape=(model.tri_count + 1,), dtype=wp.int32, device=self.device
2200
+ )
2201
+ self.compute_collision_buffer_offsets(
2202
+ self.triangle_colliding_vertices_buffer_sizes, self.triangle_colliding_vertices_offsets
2203
+ )
2204
+
2205
+ # edge collision buffers
2206
+ self.edge_colliding_edges = wp.zeros(
2207
+ shape=(model.edge_count * self.edge_collision_buffer_pre_alloc,), dtype=wp.int32, device=self.device
2208
+ )
2209
+ self.edge_colliding_edges_count = wp.zeros(shape=(model.edge_count,), dtype=wp.int32, device=self.device)
2210
+ self.edge_colliding_edges_buffer_sizes = wp.full(
2211
+ shape=(model.edge_count,),
2212
+ value=self.edge_collision_buffer_pre_alloc,
2213
+ dtype=wp.int32,
2214
+ device=self.device,
2215
+ )
2216
+ self.edge_colliding_edges_offsets = wp.array(shape=(model.edge_count + 1,), dtype=wp.int32, device=self.device)
2217
+ self.compute_collision_buffer_offsets(self.edge_colliding_edges_buffer_sizes, self.edge_colliding_edges_offsets)
2218
+ self.edge_colliding_edges_min_dist = wp.array(shape=(model.edge_count,), dtype=float, device=self.device)
2219
+
2220
+ self.lower_bounds_edges = wp.array(shape=(model.edge_count,), dtype=wp.vec3, device=model.device)
2221
+ self.upper_bounds_edges = wp.array(shape=(model.edge_count,), dtype=wp.vec3, device=model.device)
2222
+ wp.launch(
2223
+ kernel=compute_edge_aabbs,
2224
+ inputs=[self.vertex_positions, model.edge_indices, self.lower_bounds_edges, self.upper_bounds_edges],
2225
+ dim=model.edge_count,
2226
+ device=model.device,
2227
+ )
2228
+
2229
+ self.bvh_edges = wp.Bvh(self.lower_bounds_edges, self.upper_bounds_edges)
2230
+
2231
+ self.resize_flags = wp.zeros(shape=(4,), dtype=wp.int32, device=self.device)
2232
+
2233
+ self.collision_info = self.get_collision_data()
2234
+
2235
+ # data for triangle-triangle intersection; they will only be initialized on demand, as triangle-triangle intersection is not needed for simulation
2236
+ self.triangle_intersecting_triangles = None
2237
+ self.triangle_intersecting_triangles_count = None
2238
+ self.triangle_intersecting_triangles_offsets = None
2239
+
2240
+ def get_collision_data(self):
2241
+ collision_info = TriMeshCollisionInfo()
2242
+
2243
+ collision_info.vertex_colliding_triangles = self.vertex_colliding_triangles
2244
+ collision_info.vertex_colliding_triangles_offsets = self.vertex_colliding_triangles_offsets
2245
+ collision_info.vertex_colliding_triangles_buffer_sizes = self.vertex_colliding_triangles_buffer_sizes
2246
+ collision_info.vertex_colliding_triangles_count = self.vertex_colliding_triangles_count
2247
+ collision_info.vertex_colliding_triangles_min_dist = self.vertex_colliding_triangles_min_dist
2248
+
2249
+ collision_info.triangle_colliding_vertices = self.triangle_colliding_vertices
2250
+ collision_info.triangle_colliding_vertices_offsets = self.triangle_colliding_vertices_offsets
2251
+ collision_info.triangle_colliding_vertices_buffer_sizes = self.triangle_colliding_vertices_buffer_sizes
2252
+ collision_info.triangle_colliding_vertices_count = self.triangle_colliding_vertices_count
2253
+ collision_info.triangle_colliding_vertices_min_dist = self.triangle_colliding_vertices_min_dist
2254
+
2255
+ collision_info.edge_colliding_edges = self.edge_colliding_edges
2256
+ collision_info.edge_colliding_edges_offsets = self.edge_colliding_edges_offsets
2257
+ collision_info.edge_colliding_edges_buffer_sizes = self.edge_colliding_edges_buffer_sizes
2258
+ collision_info.edge_colliding_edges_count = self.edge_colliding_edges_count
2259
+ collision_info.edge_colliding_edges_min_dist = self.edge_colliding_edges_min_dist
2260
+
2261
+ return collision_info
2262
+
2263
+ def compute_collision_buffer_offsets(
2264
+ self, buffer_sizes: wp.array(dtype=wp.int32), offsets: wp.array(dtype=wp.int32)
2265
+ ):
2266
+ assert offsets.size == buffer_sizes.size + 1
2267
+ offsets_np = np.empty(shape=(offsets.size,), dtype=np.int32)
2268
+ offsets_np[1:] = np.cumsum(buffer_sizes.numpy())[:]
2269
+ offsets_np[0] = 0
2270
+
2271
+ offsets.assign(offsets_np)
2272
+
2273
+ def refit(self, new_pos=None):
2274
+ if new_pos is not None:
2275
+ self.vertex_positions = new_pos
2276
+
2277
+ self.refit_triangles()
2278
+ self.refit_edges()
2279
+
2280
+ def refit_triangles(self):
2281
+ wp.launch(
2282
+ kernel=compute_tri_aabbs,
2283
+ inputs=[self.vertex_positions, self.model.tri_indices, self.lower_bounds_tris, self.upper_bounds_tris],
2284
+ dim=self.model.tri_count,
2285
+ device=self.model.device,
2286
+ )
2287
+ self.bvh_tris.refit()
2288
+
2289
+ def refit_edges(self):
2290
+ wp.launch(
2291
+ kernel=compute_edge_aabbs,
2292
+ inputs=[self.vertex_positions, self.model.edge_indices, self.lower_bounds_edges, self.upper_bounds_edges],
2293
+ dim=self.model.edge_count,
2294
+ device=self.model.device,
2295
+ )
2296
+ self.bvh_edges.refit()
2297
+
2298
+ def vertex_triangle_collision_detection(self, query_radius):
2299
+ wp.launch(
2300
+ kernel=init_triangle_collision_data_kernel,
2301
+ inputs=[
2302
+ query_radius,
2303
+ ],
2304
+ outputs=[
2305
+ self.triangle_colliding_vertices_count,
2306
+ self.triangle_colliding_vertices_min_dist,
2307
+ self.resize_flags,
2308
+ ],
2309
+ dim=self.model.tri_count,
2310
+ device=self.model.device,
2311
+ )
2312
+
2313
+ wp.launch(
2314
+ kernel=vertex_triangle_collision_detection_kernel,
2315
+ inputs=[
2316
+ query_radius,
2317
+ self.bvh_tris.id,
2318
+ self.vertex_positions,
2319
+ self.model.tri_indices,
2320
+ self.vertex_colliding_triangles_offsets,
2321
+ self.vertex_colliding_triangles_buffer_sizes,
2322
+ self.triangle_colliding_vertices_offsets,
2323
+ self.triangle_colliding_vertices_buffer_sizes,
2324
+ ],
2325
+ outputs=[
2326
+ self.vertex_colliding_triangles,
2327
+ self.vertex_colliding_triangles_count,
2328
+ self.vertex_colliding_triangles_min_dist,
2329
+ self.triangle_colliding_vertices,
2330
+ self.triangle_colliding_vertices_count,
2331
+ self.triangle_colliding_vertices_min_dist,
2332
+ self.resize_flags,
2333
+ ],
2334
+ dim=self.model.particle_count,
2335
+ device=self.model.device,
2336
+ )
2337
+
2338
+ def edge_edge_collision_detection(self, query_radius):
2339
+ wp.launch(
2340
+ kernel=edge_colliding_edges_detection_kernel,
2341
+ inputs=[
2342
+ query_radius,
2343
+ self.bvh_edges.id,
2344
+ self.vertex_positions,
2345
+ self.model.edge_indices,
2346
+ self.edge_colliding_edges_offsets,
2347
+ self.edge_colliding_edges_buffer_sizes,
2348
+ self.edge_edge_parallel_epsilon,
2349
+ ],
2350
+ outputs=[
2351
+ self.edge_colliding_edges,
2352
+ self.edge_colliding_edges_count,
2353
+ self.edge_colliding_edges_min_dist,
2354
+ self.resize_flags,
2355
+ ],
2356
+ dim=self.model.edge_count,
2357
+ device=self.model.device,
2358
+ )
2359
+
2360
+ def triangle_triangle_intersection_detection(self):
2361
+ if self.triangle_intersecting_triangles is None:
2362
+ self.triangle_intersecting_triangles = wp.zeros(
2363
+ shape=(self.model.tri_count * self.triangle_triangle_collision_buffer_pre_alloc,),
2364
+ dtype=wp.int32,
2365
+ device=self.device,
2366
+ )
2367
+
2368
+ if self.triangle_intersecting_triangles_count is None:
2369
+ self.triangle_intersecting_triangles_count = wp.array(
2370
+ shape=(self.model.tri_count,), dtype=wp.int32, device=self.device
2371
+ )
2372
+
2373
+ if self.triangle_intersecting_triangles_offsets is None:
2374
+ buffer_sizes = np.full((self.model.tri_count,), self.triangle_triangle_collision_buffer_pre_alloc)
2375
+ offsets = np.zeros((self.model.tri_count + 1,), dtype=np.int32)
2376
+ offsets[1:] = np.cumsum(buffer_sizes)
2377
+
2378
+ self.triangle_intersecting_triangles_offsets = wp.array(offsets, dtype=wp.int32, device=self.device)
2379
+
2380
+ wp.launch(
2381
+ kernel=triangle_triangle_collision_detection_kernel,
2382
+ inputs=[
2383
+ self.bvh_tris.id,
2384
+ self.vertex_positions,
2385
+ self.model.tri_indices,
2386
+ self.triangle_intersecting_triangles_offsets,
2387
+ ],
2388
+ outputs=[
2389
+ self.triangle_intersecting_triangles,
2390
+ self.triangle_intersecting_triangles_count,
2391
+ self.resize_flags,
2392
+ ],
2393
+ dim=self.model.tri_count,
2394
+ device=self.model.device,
2395
+ )