warp-lang 1.9.0__py3-none-win_amd64.whl → 1.10.0rc2__py3-none-win_amd64.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 (350) hide show
  1. warp/__init__.py +301 -287
  2. warp/__init__.pyi +2220 -313
  3. warp/_src/__init__.py +14 -0
  4. warp/_src/autograd.py +1075 -0
  5. warp/_src/build.py +618 -0
  6. warp/_src/build_dll.py +640 -0
  7. warp/{builtins.py → _src/builtins.py} +1497 -226
  8. warp/_src/codegen.py +4359 -0
  9. warp/{config.py → _src/config.py} +178 -169
  10. warp/_src/constants.py +57 -0
  11. warp/_src/context.py +8294 -0
  12. warp/_src/dlpack.py +462 -0
  13. warp/_src/fabric.py +355 -0
  14. warp/_src/fem/__init__.py +14 -0
  15. warp/_src/fem/adaptivity.py +508 -0
  16. warp/_src/fem/cache.py +687 -0
  17. warp/_src/fem/dirichlet.py +188 -0
  18. warp/{fem → _src/fem}/domain.py +40 -30
  19. warp/_src/fem/field/__init__.py +131 -0
  20. warp/_src/fem/field/field.py +701 -0
  21. warp/{fem → _src/fem}/field/nodal_field.py +30 -15
  22. warp/{fem → _src/fem}/field/restriction.py +1 -1
  23. warp/{fem → _src/fem}/field/virtual.py +53 -27
  24. warp/_src/fem/geometry/__init__.py +32 -0
  25. warp/{fem → _src/fem}/geometry/adaptive_nanogrid.py +77 -163
  26. warp/_src/fem/geometry/closest_point.py +97 -0
  27. warp/{fem → _src/fem}/geometry/deformed_geometry.py +14 -22
  28. warp/{fem → _src/fem}/geometry/element.py +32 -10
  29. warp/{fem → _src/fem}/geometry/geometry.py +48 -20
  30. warp/{fem → _src/fem}/geometry/grid_2d.py +12 -23
  31. warp/{fem → _src/fem}/geometry/grid_3d.py +12 -23
  32. warp/{fem → _src/fem}/geometry/hexmesh.py +40 -63
  33. warp/{fem → _src/fem}/geometry/nanogrid.py +255 -248
  34. warp/{fem → _src/fem}/geometry/partition.py +121 -63
  35. warp/{fem → _src/fem}/geometry/quadmesh.py +26 -45
  36. warp/{fem → _src/fem}/geometry/tetmesh.py +40 -63
  37. warp/{fem → _src/fem}/geometry/trimesh.py +26 -45
  38. warp/{fem → _src/fem}/integrate.py +164 -158
  39. warp/_src/fem/linalg.py +383 -0
  40. warp/_src/fem/operator.py +396 -0
  41. warp/_src/fem/polynomial.py +229 -0
  42. warp/{fem → _src/fem}/quadrature/pic_quadrature.py +15 -20
  43. warp/{fem → _src/fem}/quadrature/quadrature.py +95 -47
  44. warp/_src/fem/space/__init__.py +248 -0
  45. warp/{fem → _src/fem}/space/basis_function_space.py +20 -11
  46. warp/_src/fem/space/basis_space.py +679 -0
  47. warp/{fem → _src/fem}/space/dof_mapper.py +3 -3
  48. warp/{fem → _src/fem}/space/function_space.py +14 -13
  49. warp/{fem → _src/fem}/space/grid_2d_function_space.py +4 -7
  50. warp/{fem → _src/fem}/space/grid_3d_function_space.py +4 -4
  51. warp/{fem → _src/fem}/space/hexmesh_function_space.py +4 -10
  52. warp/{fem → _src/fem}/space/nanogrid_function_space.py +3 -9
  53. warp/{fem → _src/fem}/space/partition.py +117 -60
  54. warp/{fem → _src/fem}/space/quadmesh_function_space.py +4 -10
  55. warp/{fem → _src/fem}/space/restriction.py +66 -33
  56. warp/_src/fem/space/shape/__init__.py +152 -0
  57. warp/{fem → _src/fem}/space/shape/cube_shape_function.py +9 -9
  58. warp/{fem → _src/fem}/space/shape/shape_function.py +8 -9
  59. warp/{fem → _src/fem}/space/shape/square_shape_function.py +6 -6
  60. warp/{fem → _src/fem}/space/shape/tet_shape_function.py +3 -3
  61. warp/{fem → _src/fem}/space/shape/triangle_shape_function.py +3 -3
  62. warp/{fem → _src/fem}/space/tetmesh_function_space.py +3 -9
  63. warp/_src/fem/space/topology.py +459 -0
  64. warp/{fem → _src/fem}/space/trimesh_function_space.py +3 -9
  65. warp/_src/fem/types.py +112 -0
  66. warp/_src/fem/utils.py +486 -0
  67. warp/_src/jax.py +186 -0
  68. warp/_src/jax_experimental/__init__.py +14 -0
  69. warp/_src/jax_experimental/custom_call.py +387 -0
  70. warp/_src/jax_experimental/ffi.py +1284 -0
  71. warp/_src/jax_experimental/xla_ffi.py +656 -0
  72. warp/_src/marching_cubes.py +708 -0
  73. warp/_src/math.py +414 -0
  74. warp/_src/optim/__init__.py +14 -0
  75. warp/_src/optim/adam.py +163 -0
  76. warp/_src/optim/linear.py +1606 -0
  77. warp/_src/optim/sgd.py +112 -0
  78. warp/_src/paddle.py +406 -0
  79. warp/_src/render/__init__.py +14 -0
  80. warp/_src/render/imgui_manager.py +289 -0
  81. warp/_src/render/render_opengl.py +3636 -0
  82. warp/_src/render/render_usd.py +937 -0
  83. warp/_src/render/utils.py +160 -0
  84. warp/_src/sparse.py +2716 -0
  85. warp/_src/tape.py +1206 -0
  86. warp/{thirdparty → _src/thirdparty}/unittest_parallel.py +9 -2
  87. warp/_src/torch.py +391 -0
  88. warp/_src/types.py +5870 -0
  89. warp/_src/utils.py +1693 -0
  90. warp/autograd.py +12 -1054
  91. warp/bin/warp-clang.dll +0 -0
  92. warp/bin/warp.dll +0 -0
  93. warp/build.py +8 -588
  94. warp/build_dll.py +6 -471
  95. warp/codegen.py +6 -4246
  96. warp/constants.py +6 -39
  97. warp/context.py +12 -7851
  98. warp/dlpack.py +6 -444
  99. warp/examples/distributed/example_jacobi_mpi.py +4 -5
  100. warp/examples/fem/example_adaptive_grid.py +1 -1
  101. warp/examples/fem/example_apic_fluid.py +1 -1
  102. warp/examples/fem/example_burgers.py +8 -8
  103. warp/examples/fem/example_diffusion.py +1 -1
  104. warp/examples/fem/example_distortion_energy.py +1 -1
  105. warp/examples/fem/example_mixed_elasticity.py +2 -2
  106. warp/examples/fem/example_navier_stokes.py +1 -1
  107. warp/examples/fem/example_nonconforming_contact.py +7 -7
  108. warp/examples/fem/example_stokes.py +1 -1
  109. warp/examples/fem/example_stokes_transfer.py +1 -1
  110. warp/examples/fem/utils.py +2 -2
  111. warp/examples/interop/example_jax_callable.py +1 -1
  112. warp/examples/interop/example_jax_ffi_callback.py +1 -1
  113. warp/examples/interop/example_jax_kernel.py +3 -2
  114. warp/examples/tile/example_tile_mcgp.py +191 -0
  115. warp/fabric.py +6 -337
  116. warp/fem/__init__.py +159 -97
  117. warp/fem/adaptivity.py +7 -489
  118. warp/fem/cache.py +9 -648
  119. warp/fem/dirichlet.py +6 -184
  120. warp/fem/field/__init__.py +8 -109
  121. warp/fem/field/field.py +7 -652
  122. warp/fem/geometry/__init__.py +7 -18
  123. warp/fem/geometry/closest_point.py +11 -77
  124. warp/fem/linalg.py +18 -366
  125. warp/fem/operator.py +11 -369
  126. warp/fem/polynomial.py +9 -209
  127. warp/fem/space/__init__.py +5 -211
  128. warp/fem/space/basis_space.py +6 -662
  129. warp/fem/space/shape/__init__.py +41 -118
  130. warp/fem/space/topology.py +6 -437
  131. warp/fem/types.py +6 -81
  132. warp/fem/utils.py +11 -444
  133. warp/jax.py +8 -165
  134. warp/jax_experimental/__init__.py +14 -1
  135. warp/jax_experimental/custom_call.py +8 -342
  136. warp/jax_experimental/ffi.py +17 -853
  137. warp/jax_experimental/xla_ffi.py +5 -596
  138. warp/marching_cubes.py +5 -689
  139. warp/math.py +16 -393
  140. warp/native/array.h +385 -37
  141. warp/native/builtin.h +316 -39
  142. warp/native/bvh.cpp +43 -9
  143. warp/native/bvh.cu +62 -27
  144. warp/native/bvh.h +310 -309
  145. warp/native/clang/clang.cpp +102 -97
  146. warp/native/coloring.cpp +0 -1
  147. warp/native/crt.h +208 -0
  148. warp/native/exports.h +156 -0
  149. warp/native/hashgrid.cu +2 -0
  150. warp/native/intersect.h +24 -1
  151. warp/native/intersect_tri.h +44 -35
  152. warp/native/mat.h +1456 -276
  153. warp/native/mesh.cpp +4 -4
  154. warp/native/mesh.cu +4 -2
  155. warp/native/mesh.h +176 -61
  156. warp/native/quat.h +0 -52
  157. warp/native/scan.cu +2 -0
  158. warp/native/sort.cu +22 -13
  159. warp/native/sort.h +2 -0
  160. warp/native/sparse.cu +7 -3
  161. warp/native/spatial.h +12 -0
  162. warp/native/tile.h +837 -70
  163. warp/native/tile_radix_sort.h +1 -1
  164. warp/native/tile_reduce.h +394 -46
  165. warp/native/tile_scan.h +4 -4
  166. warp/native/vec.h +469 -53
  167. warp/native/version.h +23 -0
  168. warp/native/volume.cpp +1 -1
  169. warp/native/volume.cu +1 -0
  170. warp/native/volume.h +1 -1
  171. warp/native/volume_builder.cu +2 -0
  172. warp/native/warp.cpp +60 -32
  173. warp/native/warp.cu +313 -201
  174. warp/native/warp.h +14 -11
  175. warp/optim/__init__.py +6 -3
  176. warp/optim/adam.py +6 -145
  177. warp/optim/linear.py +14 -1585
  178. warp/optim/sgd.py +6 -94
  179. warp/paddle.py +6 -388
  180. warp/render/__init__.py +8 -4
  181. warp/render/imgui_manager.py +7 -267
  182. warp/render/render_opengl.py +6 -3616
  183. warp/render/render_usd.py +6 -918
  184. warp/render/utils.py +6 -142
  185. warp/sparse.py +37 -2563
  186. warp/tape.py +6 -1188
  187. warp/tests/__main__.py +1 -1
  188. warp/tests/cuda/test_async.py +4 -4
  189. warp/tests/cuda/test_conditional_captures.py +1 -1
  190. warp/tests/cuda/test_multigpu.py +1 -1
  191. warp/tests/cuda/test_streams.py +58 -1
  192. warp/tests/geometry/test_bvh.py +157 -22
  193. warp/tests/geometry/test_hash_grid.py +38 -0
  194. warp/tests/geometry/test_marching_cubes.py +0 -1
  195. warp/tests/geometry/test_mesh.py +5 -3
  196. warp/tests/geometry/test_mesh_query_aabb.py +5 -12
  197. warp/tests/geometry/test_mesh_query_point.py +5 -2
  198. warp/tests/geometry/test_mesh_query_ray.py +15 -3
  199. warp/tests/geometry/test_volume_write.py +5 -5
  200. warp/tests/interop/test_dlpack.py +14 -14
  201. warp/tests/interop/test_jax.py +1382 -79
  202. warp/tests/interop/test_paddle.py +1 -1
  203. warp/tests/test_adam.py +0 -1
  204. warp/tests/test_arithmetic.py +9 -9
  205. warp/tests/test_array.py +529 -100
  206. warp/tests/test_array_reduce.py +3 -3
  207. warp/tests/test_atomic.py +12 -8
  208. warp/tests/test_atomic_bitwise.py +209 -0
  209. warp/tests/test_atomic_cas.py +4 -4
  210. warp/tests/test_bool.py +2 -2
  211. warp/tests/test_builtins_resolution.py +5 -571
  212. warp/tests/test_codegen.py +34 -15
  213. warp/tests/test_conditional.py +1 -1
  214. warp/tests/test_context.py +6 -6
  215. warp/tests/test_copy.py +242 -161
  216. warp/tests/test_ctypes.py +3 -3
  217. warp/tests/test_devices.py +24 -2
  218. warp/tests/test_examples.py +16 -84
  219. warp/tests/test_fabricarray.py +35 -35
  220. warp/tests/test_fast_math.py +0 -2
  221. warp/tests/test_fem.py +60 -14
  222. warp/tests/test_fixedarray.py +3 -3
  223. warp/tests/test_func.py +8 -5
  224. warp/tests/test_generics.py +1 -1
  225. warp/tests/test_indexedarray.py +24 -24
  226. warp/tests/test_intersect.py +39 -9
  227. warp/tests/test_large.py +1 -1
  228. warp/tests/test_lerp.py +3 -1
  229. warp/tests/test_linear_solvers.py +1 -1
  230. warp/tests/test_map.py +49 -4
  231. warp/tests/test_mat.py +52 -62
  232. warp/tests/test_mat_constructors.py +4 -5
  233. warp/tests/test_mat_lite.py +1 -1
  234. warp/tests/test_mat_scalar_ops.py +121 -121
  235. warp/tests/test_math.py +34 -0
  236. warp/tests/test_module_aot.py +4 -4
  237. warp/tests/test_modules_lite.py +28 -2
  238. warp/tests/test_print.py +11 -11
  239. warp/tests/test_quat.py +93 -58
  240. warp/tests/test_runlength_encode.py +1 -1
  241. warp/tests/test_scalar_ops.py +38 -10
  242. warp/tests/test_smoothstep.py +1 -1
  243. warp/tests/test_sparse.py +126 -15
  244. warp/tests/test_spatial.py +105 -87
  245. warp/tests/test_special_values.py +6 -6
  246. warp/tests/test_static.py +7 -7
  247. warp/tests/test_struct.py +13 -2
  248. warp/tests/test_triangle_closest_point.py +48 -1
  249. warp/tests/test_tuple.py +96 -0
  250. warp/tests/test_types.py +82 -9
  251. warp/tests/test_utils.py +52 -52
  252. warp/tests/test_vec.py +29 -29
  253. warp/tests/test_vec_constructors.py +5 -5
  254. warp/tests/test_vec_scalar_ops.py +97 -97
  255. warp/tests/test_version.py +75 -0
  256. warp/tests/tile/test_tile.py +239 -0
  257. warp/tests/tile/test_tile_atomic_bitwise.py +403 -0
  258. warp/tests/tile/test_tile_cholesky.py +7 -4
  259. warp/tests/tile/test_tile_load.py +26 -2
  260. warp/tests/tile/test_tile_mathdx.py +3 -3
  261. warp/tests/tile/test_tile_matmul.py +1 -1
  262. warp/tests/tile/test_tile_mlp.py +2 -4
  263. warp/tests/tile/test_tile_reduce.py +214 -13
  264. warp/tests/unittest_suites.py +6 -14
  265. warp/tests/unittest_utils.py +10 -9
  266. warp/tests/walkthrough_debug.py +3 -1
  267. warp/torch.py +6 -373
  268. warp/types.py +29 -5750
  269. warp/utils.py +10 -1659
  270. {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/METADATA +47 -103
  271. warp_lang-1.10.0rc2.dist-info/RECORD +468 -0
  272. warp_lang-1.10.0rc2.dist-info/licenses/licenses/Gaia-LICENSE.txt +6 -0
  273. warp_lang-1.10.0rc2.dist-info/licenses/licenses/appdirs-LICENSE.txt +22 -0
  274. warp_lang-1.10.0rc2.dist-info/licenses/licenses/asset_pixel_jpg-LICENSE.txt +3 -0
  275. warp_lang-1.10.0rc2.dist-info/licenses/licenses/cuda-LICENSE.txt +1582 -0
  276. warp_lang-1.10.0rc2.dist-info/licenses/licenses/dlpack-LICENSE.txt +201 -0
  277. warp_lang-1.10.0rc2.dist-info/licenses/licenses/fp16-LICENSE.txt +28 -0
  278. warp_lang-1.10.0rc2.dist-info/licenses/licenses/libmathdx-LICENSE.txt +220 -0
  279. warp_lang-1.10.0rc2.dist-info/licenses/licenses/llvm-LICENSE.txt +279 -0
  280. warp_lang-1.10.0rc2.dist-info/licenses/licenses/moller-LICENSE.txt +16 -0
  281. warp_lang-1.10.0rc2.dist-info/licenses/licenses/nanovdb-LICENSE.txt +2 -0
  282. warp_lang-1.10.0rc2.dist-info/licenses/licenses/nvrtc-LICENSE.txt +1592 -0
  283. warp_lang-1.10.0rc2.dist-info/licenses/licenses/svd-LICENSE.txt +23 -0
  284. warp_lang-1.10.0rc2.dist-info/licenses/licenses/unittest_parallel-LICENSE.txt +21 -0
  285. warp_lang-1.10.0rc2.dist-info/licenses/licenses/usd-LICENSE.txt +213 -0
  286. warp_lang-1.10.0rc2.dist-info/licenses/licenses/windingnumber-LICENSE.txt +21 -0
  287. warp/examples/assets/cartpole.urdf +0 -110
  288. warp/examples/assets/crazyflie.usd +0 -0
  289. warp/examples/assets/nv_ant.xml +0 -92
  290. warp/examples/assets/nv_humanoid.xml +0 -183
  291. warp/examples/assets/quadruped.urdf +0 -268
  292. warp/examples/optim/example_bounce.py +0 -266
  293. warp/examples/optim/example_cloth_throw.py +0 -228
  294. warp/examples/optim/example_drone.py +0 -870
  295. warp/examples/optim/example_inverse_kinematics.py +0 -182
  296. warp/examples/optim/example_inverse_kinematics_torch.py +0 -191
  297. warp/examples/optim/example_softbody_properties.py +0 -400
  298. warp/examples/optim/example_spring_cage.py +0 -245
  299. warp/examples/optim/example_trajectory.py +0 -227
  300. warp/examples/sim/example_cartpole.py +0 -143
  301. warp/examples/sim/example_cloth.py +0 -225
  302. warp/examples/sim/example_cloth_self_contact.py +0 -316
  303. warp/examples/sim/example_granular.py +0 -130
  304. warp/examples/sim/example_granular_collision_sdf.py +0 -202
  305. warp/examples/sim/example_jacobian_ik.py +0 -244
  306. warp/examples/sim/example_particle_chain.py +0 -124
  307. warp/examples/sim/example_quadruped.py +0 -203
  308. warp/examples/sim/example_rigid_chain.py +0 -203
  309. warp/examples/sim/example_rigid_contact.py +0 -195
  310. warp/examples/sim/example_rigid_force.py +0 -133
  311. warp/examples/sim/example_rigid_gyroscopic.py +0 -115
  312. warp/examples/sim/example_rigid_soft_contact.py +0 -140
  313. warp/examples/sim/example_soft_body.py +0 -196
  314. warp/examples/tile/example_tile_walker.py +0 -327
  315. warp/sim/__init__.py +0 -74
  316. warp/sim/articulation.py +0 -793
  317. warp/sim/collide.py +0 -2570
  318. warp/sim/graph_coloring.py +0 -307
  319. warp/sim/import_mjcf.py +0 -791
  320. warp/sim/import_snu.py +0 -227
  321. warp/sim/import_urdf.py +0 -579
  322. warp/sim/import_usd.py +0 -898
  323. warp/sim/inertia.py +0 -357
  324. warp/sim/integrator.py +0 -245
  325. warp/sim/integrator_euler.py +0 -2000
  326. warp/sim/integrator_featherstone.py +0 -2101
  327. warp/sim/integrator_vbd.py +0 -2487
  328. warp/sim/integrator_xpbd.py +0 -3295
  329. warp/sim/model.py +0 -4821
  330. warp/sim/particles.py +0 -121
  331. warp/sim/render.py +0 -431
  332. warp/sim/utils.py +0 -431
  333. warp/tests/sim/disabled_kinematics.py +0 -244
  334. warp/tests/sim/test_cloth.py +0 -863
  335. warp/tests/sim/test_collision.py +0 -743
  336. warp/tests/sim/test_coloring.py +0 -347
  337. warp/tests/sim/test_inertia.py +0 -161
  338. warp/tests/sim/test_model.py +0 -226
  339. warp/tests/sim/test_sim_grad.py +0 -287
  340. warp/tests/sim/test_sim_grad_bounce_linear.py +0 -212
  341. warp/tests/sim/test_sim_kinematics.py +0 -98
  342. warp/thirdparty/__init__.py +0 -0
  343. warp_lang-1.9.0.dist-info/RECORD +0 -456
  344. /warp/{fem → _src/fem}/quadrature/__init__.py +0 -0
  345. /warp/{tests/sim → _src/thirdparty}/__init__.py +0 -0
  346. /warp/{thirdparty → _src/thirdparty}/appdirs.py +0 -0
  347. /warp/{thirdparty → _src/thirdparty}/dlpack.py +0 -0
  348. {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/WHEEL +0 -0
  349. {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/licenses/LICENSE.md +0 -0
  350. {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,708 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 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
+ from __future__ import annotations
17
+
18
+ import numpy as np
19
+
20
+ import warp as wp
21
+
22
+
23
+ def marching_cubes_extract_vertices(
24
+ field: wp.array3d(dtype=wp.float32),
25
+ threshold: float,
26
+ domain_bounds_lower_corner: wp.vec3,
27
+ grid_pos_delta: wp.vec3,
28
+ ):
29
+ """Invoke kernels to extract vertices and indices to uniquely identify them."""
30
+ device = field.device
31
+ nnode_x, nnode_y, nnode_z = field.shape[0], field.shape[1], field.shape[2]
32
+
33
+ ### First pass: count the vertices each thread will generate
34
+ thread_output_count = wp.zeros(shape=(nnode_x * nnode_y * nnode_z * 3), dtype=wp.int32, device=device)
35
+ wp.launch(
36
+ extract_vertices_kernel,
37
+ dim=(nnode_x, nnode_y, nnode_z, 3),
38
+ inputs=[
39
+ field,
40
+ threshold,
41
+ domain_bounds_lower_corner,
42
+ grid_pos_delta,
43
+ None,
44
+ True, # count only == True : just count the vertices
45
+ ],
46
+ outputs=[thread_output_count, None, None, None],
47
+ device=device,
48
+ )
49
+
50
+ ### Evaluate a cumulative sum, to compute the output index for each generated vertex
51
+ vertex_result_ind = wp.zeros(shape=(nnode_x * nnode_y * nnode_z * 3), dtype=wp.int32, device=device)
52
+ wp._src.utils.array_scan(thread_output_count, vertex_result_ind, inclusive=True)
53
+
54
+ # (synchronization point!)
55
+ N_vert = int(vertex_result_ind[-1:].numpy()[0])
56
+
57
+ # Allocate output arrays
58
+ # edge_generated_vert_ind: corresponds to the the 3 positive-facing edges emanating from each node.
59
+ # The last boundary entries of this array will be unused, but we can't make it any smaller.
60
+ edge_generated_vert_ind = wp.zeros(shape=(nnode_x, nnode_y, nnode_z, 3), dtype=wp.int32, device=device)
61
+ verts_pos_out = wp.empty(
62
+ shape=N_vert, dtype=wp.vec3, device=device, requires_grad=field.requires_grad
63
+ ) # TODO is this the right way to decide setting requires_grad?
64
+ verts_is_boundary_out = wp.empty(shape=N_vert, dtype=wp.bool, device=device)
65
+
66
+ ### Second pass: actually generate the vertices and write to the output arrays
67
+ wp.launch(
68
+ extract_vertices_kernel,
69
+ dim=(nnode_x, nnode_y, nnode_z, 3),
70
+ inputs=[
71
+ field,
72
+ threshold,
73
+ domain_bounds_lower_corner,
74
+ grid_pos_delta,
75
+ vertex_result_ind,
76
+ False, # count only == False : actually write out the vertices
77
+ ],
78
+ outputs=[
79
+ None,
80
+ verts_pos_out,
81
+ verts_is_boundary_out,
82
+ edge_generated_vert_ind,
83
+ ],
84
+ device=device,
85
+ )
86
+
87
+ return (verts_pos_out, verts_is_boundary_out, edge_generated_vert_ind)
88
+
89
+
90
+ @wp.kernel
91
+ def extract_vertices_kernel(
92
+ values: wp.array3d(dtype=wp.float32),
93
+ threshold: wp.float32,
94
+ domain_bounds_lower_corner: wp.vec3,
95
+ grid_pos_delta: wp.vec3,
96
+ vertex_result_ind: wp.array(dtype=wp.int32),
97
+ count_only: bool,
98
+ thread_output_count: wp.array(dtype=wp.int32),
99
+ verts_pos_out: wp.array(dtype=wp.vec3),
100
+ verts_is_boundary_out: wp.array(dtype=wp.bool),
101
+ edge_generated_vert_ind: wp.array(dtype=wp.int32, ndim=4),
102
+ ):
103
+ """Kernel for vertex extraction.
104
+
105
+ This kernel runs in two different "modes", which share much of their logic.
106
+ In a first pass when count_only==True, we just count the number of vertices that will be
107
+ generated. We then cumulative-sum those counts to generate output indices. Then, in a
108
+ second pass when count_only==False, we actually generate the vertices and write them to
109
+ the appropriate output location.
110
+ """
111
+ ti, tj, tk, t_side = wp.tid()
112
+ nnode_x, nnode_y, nnode_z = values.shape[0], values.shape[1], values.shape[2]
113
+
114
+ # Assemble indices
115
+ i_opp = ti + wp.where(t_side == 0, 1, 0)
116
+ j_opp = tj + wp.where(t_side == 1, 1, 0)
117
+ k_opp = tk + wp.where(t_side == 2, 1, 0)
118
+ out_ind = -1
119
+
120
+ # Out of bounds edges off the sides of the grid
121
+ if i_opp >= nnode_x or j_opp >= nnode_y or k_opp >= nnode_z:
122
+ if not count_only:
123
+ edge_generated_vert_ind[ti, tj, tk, t_side] = out_ind
124
+ return
125
+
126
+ # Fetch values from the field
127
+ this_val = values[ti, tj, tk]
128
+ opp_val = values[i_opp, j_opp, k_opp]
129
+
130
+ ind = ti * nnode_y * nnode_z * 3 + tj * nnode_z * 3 + tk * 3 + t_side
131
+
132
+ # Check if we generate a vertex
133
+ if (this_val >= threshold and opp_val < threshold) or (this_val < threshold and opp_val >= threshold):
134
+ if count_only:
135
+ thread_output_count[ind] = 1
136
+ else:
137
+ out_ind = vertex_result_ind[ind] - 1
138
+
139
+ # generated vertex along the edge
140
+ t_interp = (threshold - this_val) / (opp_val - this_val)
141
+ t_interp = wp.clamp(t_interp, 0.0, 1.0)
142
+ this_pos = domain_bounds_lower_corner + wp.vec3(
143
+ wp.float32(ti) * grid_pos_delta.x,
144
+ wp.float32(tj) * grid_pos_delta.y,
145
+ wp.float32(tk) * grid_pos_delta.z,
146
+ )
147
+ opp_pos = domain_bounds_lower_corner + wp.vec3(
148
+ wp.float32(i_opp) * grid_pos_delta.x,
149
+ wp.float32(j_opp) * grid_pos_delta.y,
150
+ wp.float32(k_opp) * grid_pos_delta.z,
151
+ )
152
+ interp_pos = wp.lerp(this_pos, opp_pos, t_interp)
153
+ this_boundary = (
154
+ ti == 0 or (ti + 1) == nnode_x or tj == 0 or (tj + 1) == nnode_y or tk == 0 or (tk + 1) == nnode_z
155
+ )
156
+ opp_boundary = (
157
+ i_opp == 0
158
+ or (i_opp + 1) == nnode_x
159
+ or j_opp == 0
160
+ or (j_opp + 1) == nnode_y
161
+ or k_opp == 0
162
+ or (k_opp + 1) == nnode_z
163
+ )
164
+ vert_is_boundary = this_boundary and opp_boundary
165
+
166
+ # store output data
167
+ verts_pos_out[out_ind] = interp_pos
168
+ verts_is_boundary_out[out_ind] = vert_is_boundary
169
+
170
+ if not count_only:
171
+ edge_generated_vert_ind[ti, tj, tk, t_side] = out_ind
172
+
173
+
174
+ def marching_cubes_extract_faces(
175
+ values: wp.array3d(dtype=wp.float32),
176
+ threshold: wp.float32,
177
+ edge_generated_vert_ind: wp.array(dtype=wp.int32, ndim=4),
178
+ ):
179
+ """Invoke kernels to extract faces and index the appropriate vertices."""
180
+ device = values.device
181
+ nnode_x, nnode_y, nnode_z = values.shape[0], values.shape[1], values.shape[2]
182
+ ncell_x, ncell_y, ncell_z = nnode_x - 1, nnode_y - 1, nnode_z - 1
183
+
184
+ # First pass: count the number of faces each thread will generate
185
+ thread_output_count = wp.zeros(shape=(ncell_x * ncell_y * ncell_z), dtype=wp.int32, device=device)
186
+ wp.launch(
187
+ extract_faces_kernel,
188
+ dim=(ncell_x, ncell_y, ncell_z),
189
+ inputs=[
190
+ values,
191
+ threshold,
192
+ edge_generated_vert_ind,
193
+ None,
194
+ _get_mc_case_to_tri_range_table(device),
195
+ _get_mc_tri_local_inds_table(device),
196
+ _get_mc_edge_offset_table(device),
197
+ True,
198
+ ],
199
+ outputs=[
200
+ thread_output_count,
201
+ None,
202
+ ],
203
+ device=device,
204
+ )
205
+
206
+ ### Evaluate a cumulative sum, to compute the output index for each generated face
207
+ face_result_ind = wp.zeros(shape=(ncell_x * ncell_y * ncell_z), dtype=wp.int32, device=device)
208
+ wp._src.utils.array_scan(thread_output_count, face_result_ind, inclusive=True)
209
+
210
+ # (synchronization point!)
211
+ N_faces = int(face_result_ind[-1:].numpy()[0])
212
+
213
+ # Allocate output array
214
+ faces_out = wp.empty(shape=3 * N_faces, dtype=wp.int32, device=device)
215
+
216
+ ### Second pass: actually generate the faces and write to the output array
217
+ wp.launch(
218
+ extract_faces_kernel,
219
+ dim=(ncell_x, ncell_y, ncell_z),
220
+ inputs=[
221
+ values,
222
+ threshold,
223
+ edge_generated_vert_ind,
224
+ face_result_ind,
225
+ _get_mc_case_to_tri_range_table(device),
226
+ _get_mc_tri_local_inds_table(device),
227
+ _get_mc_edge_offset_table(device),
228
+ False,
229
+ ],
230
+ outputs=[
231
+ None,
232
+ faces_out,
233
+ ],
234
+ device=device,
235
+ )
236
+
237
+ return faces_out
238
+
239
+
240
+ # NOTE: differentiating this kernel does nothing, since all of its outputs are discrete, but
241
+ # Warp issues warnings if we set enable_backward=False
242
+ @wp.kernel
243
+ def extract_faces_kernel(
244
+ values: wp.array3d(dtype=wp.float32),
245
+ threshold: wp.float32,
246
+ edge_generated_vert_ind: wp.array(dtype=wp.int32, ndim=4),
247
+ face_result_ind: wp.array(dtype=wp.int32),
248
+ mc_case_to_tri_range_table: wp.array(dtype=wp.int32),
249
+ mc_tri_local_inds_table: wp.array(dtype=wp.int32),
250
+ mc_edge_offset_table: wp.array(dtype=wp.int32, ndim=2),
251
+ count_only: bool,
252
+ thread_output_count: wp.array(dtype=wp.int32),
253
+ faces_out: wp.array(dtype=wp.int32),
254
+ ):
255
+ """
256
+ Kernel for face extraction
257
+
258
+ This kernel runs in two different "modes", which share much of their logic.
259
+ In a first pass when count_only==True, we just count the number of faces that will be
260
+ generated. We then cumulative-sum those counts to generate output indices. Then, in a
261
+ second pass when count_only==False, we actually generate the faces and write them to
262
+ the appropriate output location.
263
+ """
264
+ ti, tj, tk = wp.tid()
265
+ nnode_x, nnode_y, nnode_z = values.shape[0], values.shape[1], values.shape[2]
266
+ _ncell_x, ncell_y, ncell_z = nnode_x - 1, nnode_y - 1, nnode_z - 1
267
+ ind = ti * ncell_y * ncell_z + tj * ncell_z + tk
268
+
269
+ # Check which case we're in
270
+ # NOTE: this loop should get unrolled (confirmed it does in Warp 1.4.1)
271
+ case_code = 0
272
+ for i_c in range(8):
273
+ indX = ti + wp.static(mc_cube_corner_offsets[i_c][0])
274
+ indY = tj + wp.static(mc_cube_corner_offsets[i_c][1])
275
+ indZ = tk + wp.static(mc_cube_corner_offsets[i_c][2])
276
+ val = values[indX, indY, indZ]
277
+ if val >= threshold:
278
+ case_code += wp.static(2**i_c)
279
+
280
+ # Gather the range of triangles we will emit
281
+ tri_range_start = mc_case_to_tri_range_table[case_code]
282
+ tri_range_end = mc_case_to_tri_range_table[case_code + 1]
283
+ N_tri = wp.int32(tri_range_end - tri_range_start) // 3
284
+
285
+ # If we are just counting, record the number of triangles and move on
286
+ if count_only:
287
+ thread_output_count[ind] = N_tri
288
+ return
289
+
290
+ if N_tri == 0:
291
+ return
292
+
293
+ # Find the output index for this thread's faces
294
+ # The indexing logic is slightly awkward here because we use an inclusive sum,
295
+ # so we need to check the previous thread's output index, with a special case for
296
+ # the first thread. Doing it this way makes it simpler to fetch N_faces in the host
297
+ # function.
298
+ prev_thread_id = ti * ncell_y * ncell_z + tj * ncell_z + tk - 1
299
+ if prev_thread_id < 0:
300
+ out_ind = 0
301
+ else:
302
+ out_ind = face_result_ind[prev_thread_id]
303
+
304
+ # Emit triangles
305
+ for i_tri in range(N_tri):
306
+ for s in range(3):
307
+ local_ind = mc_tri_local_inds_table[tri_range_start + 3 * i_tri + s]
308
+
309
+ global_ind = edge_generated_vert_ind[
310
+ ti + mc_edge_offset_table[local_ind][0],
311
+ tj + mc_edge_offset_table[local_ind][1],
312
+ tk + mc_edge_offset_table[local_ind][2],
313
+ mc_edge_offset_table[local_ind][3],
314
+ ]
315
+
316
+ faces_out[3 * (out_ind + i_tri) + s] = global_ind
317
+
318
+ return
319
+
320
+
321
+ ### Marching Cubes tables
322
+
323
+ # cached warp device arrays for the tables below
324
+ mc_case_to_tri_range_wpcache = {}
325
+ mc_tri_local_inds_wpcache = {}
326
+ mc_edge_offset_wpcache = {}
327
+
328
+
329
+ def _get_mc_case_to_tri_range_table(device) -> wp.array:
330
+ """Lazily loads and caches the MC tri range table on the target device."""
331
+ device = str(device)
332
+ if device not in mc_case_to_tri_range_wpcache:
333
+ mc_case_to_tri_range_wpcache[device] = wp.from_numpy(mc_case_to_tri_range_np, dtype=wp.int32, device=device)
334
+
335
+ return mc_case_to_tri_range_wpcache[device]
336
+
337
+
338
+ def _get_mc_tri_local_inds_table(device) -> wp.array:
339
+ """Lazily loads and caches the MC tri local inds table on the target device."""
340
+ device = str(device)
341
+ if device not in mc_tri_local_inds_wpcache:
342
+ mc_tri_local_inds_wpcache[device] = wp.from_numpy(mc_tri_local_inds, dtype=wp.int32, device=device)
343
+
344
+ return mc_tri_local_inds_wpcache[device]
345
+
346
+
347
+ def _get_mc_edge_offset_table(device) -> wp.array:
348
+ """Lazily loads and caches the MC edge offset table on the target device."""
349
+ device = str(device)
350
+ if device not in mc_edge_offset_wpcache:
351
+ mc_edge_offset_wpcache[device] = wp.from_numpy(mc_edge_offset_np, dtype=wp.int32, device=device)
352
+
353
+ return mc_edge_offset_wpcache[device]
354
+
355
+
356
+ # fmt: off
357
+ mc_case_to_tri_range_np = np.array( [
358
+ 0, 0, 3, 6, 12, 15, 21, 27, 36, 39, 45, 51, 60, 66, 75, 84, 90, 93, 99, 105, 114,
359
+ 120, 129, 138, 150, 156, 165, 174, 186, 195, 207, 219, 228, 231, 237, 243, 252,
360
+ 258, 267, 276, 288, 294, 303, 312, 324, 333, 345, 357, 366, 372, 381, 390, 396,
361
+ 405, 417, 429, 438, 447, 459, 471, 480, 492, 507, 522, 528, 531, 537, 543, 552,
362
+ 558, 567, 576, 588, 594, 603, 612, 624, 633, 645, 657, 666, 672, 681, 690, 702,
363
+ 711, 723, 735, 750, 759, 771, 783, 798, 810, 825, 840, 852, 858, 867, 876, 888,
364
+ 897, 909, 915, 924, 933, 945, 957, 972, 984, 999, 1008, 1014, 1023, 1035, 1047,
365
+ 1056, 1068, 1083, 1092, 1098, 1110, 1125, 1140, 1152, 1167, 1173, 1185, 1188, 1191,
366
+ 1197, 1203, 1212, 1218, 1227, 1236, 1248, 1254, 1263, 1272, 1284, 1293, 1305, 1317,
367
+ 1326, 1332, 1341, 1350, 1362, 1371, 1383, 1395, 1410, 1419, 1425, 1437, 1446, 1458,
368
+ 1467, 1482, 1488, 1494, 1503, 1512, 1524, 1533, 1545, 1557, 1572, 1581, 1593, 1605,
369
+ 1620, 1632, 1647, 1662, 1674, 1683, 1695, 1707, 1716, 1728, 1743, 1758, 1770, 1782,
370
+ 1791, 1806, 1812, 1827, 1839, 1845, 1848, 1854, 1863, 1872, 1884, 1893, 1905, 1917,
371
+ 1932, 1941, 1953, 1965, 1980, 1986, 1995, 2004, 2010, 2019, 2031, 2043, 2058, 2070,
372
+ 2085, 2100, 2106, 2118, 2127, 2142, 2154, 2163, 2169, 2181, 2184, 2193, 2205, 2217,
373
+ 2232, 2244, 2259, 2268, 2280, 2292, 2307, 2322, 2328, 2337, 2349, 2355, 2358, 2364,
374
+ 2373, 2382, 2388, 2397, 2409, 2415, 2418, 2427, 2433, 2445, 2448, 2454, 2457, 2460,
375
+ 2460
376
+ ])
377
+
378
+ mc_tri_local_inds = np.array([
379
+ 0, 8, 3, 0, 1, 9, 1, 8, 3, 9, 8, 1, 1, 2, 10, 0, 8, 3, 1, 2, 10, 9, 2, 10, 0, 2, 9, 2, 8, 3, 2,
380
+ 10, 8, 10, 9, 8, 3, 11, 2, 0, 11, 2, 8, 11, 0, 1, 9, 0, 2, 3, 11, 1, 11, 2, 1, 9, 11, 9, 8, 11, 3,
381
+ 10, 1, 11, 10, 3, 0, 10, 1, 0, 8, 10, 8, 11, 10, 3, 9, 0, 3, 11, 9, 11, 10, 9, 9, 8, 10, 10, 8, 11, 4,
382
+ 7, 8, 4, 3, 0, 7, 3, 4, 0, 1, 9, 8, 4, 7, 4, 1, 9, 4, 7, 1, 7, 3, 1, 1, 2, 10, 8, 4, 7, 3,
383
+ 4, 7, 3, 0, 4, 1, 2, 10, 9, 2, 10, 9, 0, 2, 8, 4, 7, 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, 8,
384
+ 4, 7, 3, 11, 2, 11, 4, 7, 11, 2, 4, 2, 0, 4, 9, 0, 1, 8, 4, 7, 2, 3, 11, 4, 7, 11, 9, 4, 11, 9,
385
+ 11, 2, 9, 2, 1, 3, 10, 1, 3, 11, 10, 7, 8, 4, 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, 4, 7, 8, 9,
386
+ 0, 11, 9, 11, 10, 11, 0, 3, 4, 7, 11, 4, 11, 9, 9, 11, 10, 9, 5, 4, 9, 5, 4, 0, 8, 3, 0, 5, 4, 1,
387
+ 5, 0, 8, 5, 4, 8, 3, 5, 3, 1, 5, 1, 2, 10, 9, 5, 4, 3, 0, 8, 1, 2, 10, 4, 9, 5, 5, 2, 10, 5,
388
+ 4, 2, 4, 0, 2, 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, 9, 5, 4, 2, 3, 11, 0, 11, 2, 0, 8, 11, 4,
389
+ 9, 5, 0, 5, 4, 0, 1, 5, 2, 3, 11, 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, 10, 3, 11, 10, 1, 3, 9,
390
+ 5, 4, 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, 5, 4, 8, 5,
391
+ 8, 10, 10, 8, 11, 9, 7, 8, 5, 7, 9, 9, 3, 0, 9, 5, 3, 5, 7, 3, 0, 7, 8, 0, 1, 7, 1, 5, 7, 1,
392
+ 5, 3, 3, 5, 7, 9, 7, 8, 9, 5, 7, 10, 1, 2, 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, 8, 0, 2, 8,
393
+ 2, 5, 8, 5, 7, 10, 5, 2, 2, 10, 5, 2, 5, 3, 3, 5, 7, 7, 9, 5, 7, 8, 9, 3, 11, 2, 9, 5, 7, 9,
394
+ 7, 2, 9, 2, 0, 2, 7, 11, 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, 11, 2, 1, 11, 1, 7, 7, 1, 5, 9,
395
+ 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, 11, 10, 0, 11,
396
+ 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, 11, 10, 5, 7, 11, 5, 10, 6, 5, 0, 8, 3, 5, 10, 6, 9, 0, 1, 5,
397
+ 10, 6, 1, 8, 3, 1, 9, 8, 5, 10, 6, 1, 6, 5, 2, 6, 1, 1, 6, 5, 1, 2, 6, 3, 0, 8, 9, 6, 5, 9,
398
+ 0, 6, 0, 2, 6, 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, 2, 3, 11, 10, 6, 5, 11, 0, 8, 11, 2, 0, 10,
399
+ 6, 5, 0, 1, 9, 2, 3, 11, 5, 10, 6, 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, 6, 3, 11, 6, 5, 3, 5,
400
+ 1, 3, 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, 6, 5, 9, 6,
401
+ 9, 11, 11, 9, 8, 5, 10, 6, 4, 7, 8, 4, 3, 0, 4, 7, 3, 6, 5, 10, 1, 9, 0, 5, 10, 6, 8, 4, 7, 10,
402
+ 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, 6, 1, 2, 6, 5, 1, 4, 7, 8, 1, 2, 5, 5, 2, 6, 3, 0, 4, 3,
403
+ 4, 7, 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, 3,
404
+ 11, 2, 7, 8, 4, 10, 6, 5, 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, 0, 1, 9, 4, 7, 8, 2, 3, 11, 5,
405
+ 10, 6, 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, 5,
406
+ 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, 6,
407
+ 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, 10, 4, 9, 6, 4, 10, 4, 10, 6, 4, 9, 10, 0, 8, 3, 10, 0, 1, 10,
408
+ 6, 0, 6, 4, 0, 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, 1, 4, 9, 1, 2, 4, 2, 6, 4, 3, 0, 8, 1,
409
+ 2, 9, 2, 4, 9, 2, 6, 4, 0, 2, 4, 4, 2, 6, 8, 3, 2, 8, 2, 4, 4, 2, 6, 10, 4, 9, 10, 6, 4, 11,
410
+ 2, 3, 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, 6, 4, 1, 6,
411
+ 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, 8, 11, 1, 8, 1, 0, 11,
412
+ 6, 1, 9, 1, 4, 6, 4, 1, 3, 11, 6, 3, 6, 0, 0, 6, 4, 6, 4, 8, 11, 6, 8, 7, 10, 6, 7, 8, 10, 8,
413
+ 9, 10, 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, 10, 6, 7, 10,
414
+ 7, 1, 1, 7, 3, 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7,
415
+ 3, 9, 7, 8, 0, 7, 0, 6, 6, 0, 2, 7, 3, 2, 6, 7, 2, 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, 2,
416
+ 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, 11,
417
+ 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, 0, 9, 1, 11,
418
+ 6, 7, 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, 7, 11, 6, 7, 6, 11, 3, 0, 8, 11, 7, 6, 0, 1, 9, 11,
419
+ 7, 6, 8, 1, 9, 8, 3, 1, 11, 7, 6, 10, 1, 2, 6, 11, 7, 1, 2, 10, 3, 0, 8, 6, 11, 7, 2, 9, 0, 2,
420
+ 10, 9, 6, 11, 7, 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, 7, 2, 3, 6, 2, 7, 7, 0, 8, 7, 6, 0, 6,
421
+ 2, 0, 2, 7, 6, 2, 3, 7, 0, 1, 9, 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, 10, 7, 6, 10, 1, 7, 1,
422
+ 3, 7, 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, 7, 6, 10, 7,
423
+ 10, 8, 8, 10, 9, 6, 8, 4, 11, 8, 6, 3, 6, 11, 3, 0, 6, 0, 4, 6, 8, 6, 11, 8, 4, 6, 9, 0, 1, 9,
424
+ 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, 6, 8, 4, 6, 11, 8, 2, 10, 1, 1, 2, 10, 3, 0, 11, 0, 6, 11, 0,
425
+ 4, 6, 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, 8,
426
+ 2, 3, 8, 4, 2, 4, 6, 2, 0, 4, 2, 4, 6, 2, 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, 1, 9, 4, 1,
427
+ 4, 2, 2, 4, 6, 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, 10, 1, 0, 10, 0, 6, 6, 0, 4, 4, 6, 3, 4,
428
+ 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, 10, 9, 4, 6, 10, 4, 4, 9, 5, 7, 6, 11, 0, 8, 3, 4, 9, 5, 11,
429
+ 7, 6, 5, 0, 1, 5, 4, 0, 7, 6, 11, 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, 9, 5, 4, 10, 1, 2, 7,
430
+ 6, 11, 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, 3, 4, 8, 3,
431
+ 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, 7, 2, 3, 7, 6, 2, 5, 4, 9, 9, 5, 4, 0, 8, 6, 0, 6, 2, 6,
432
+ 8, 7, 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, 9,
433
+ 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, 4, 0, 10, 4,
434
+ 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, 6, 9, 5, 6, 11, 9, 11,
435
+ 8, 9, 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, 6, 11, 3, 6,
436
+ 3, 5, 5, 3, 1, 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1,
437
+ 2, 10, 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, 5,
438
+ 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, 9, 5, 6, 9, 6, 0, 0, 6, 2, 1, 5, 8, 1, 8, 0, 5, 6, 8, 3,
439
+ 8, 2, 6, 2, 8, 1, 5, 6, 2, 1, 6, 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, 10, 1, 0, 10,
440
+ 0, 6, 9, 5, 0, 5, 6, 0, 0, 3, 8, 5, 6, 10, 10, 5, 6, 11, 5, 10, 7, 5, 11, 11, 5, 10, 11, 7, 5, 8,
441
+ 3, 0, 5, 11, 7, 5, 10, 11, 1, 9, 0, 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, 11, 1, 2, 11, 7, 1, 7,
442
+ 5, 1, 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, 7, 5, 2, 7,
443
+ 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, 2, 5, 10, 2, 3, 5, 3, 7, 5, 8, 2, 0, 8, 5, 2, 8, 7, 5, 10,
444
+ 2, 5, 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, 1,
445
+ 3, 5, 3, 7, 5, 0, 8, 7, 0, 7, 1, 1, 7, 5, 9, 0, 3, 9, 3, 5, 5, 3, 7, 9, 8, 7, 5, 9, 7, 5,
446
+ 8, 4, 5, 10, 8, 10, 11, 8, 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, 0, 1, 9, 8, 4, 10, 8, 10, 11, 10,
447
+ 4, 5, 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, 0,
448
+ 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, 9,
449
+ 4, 5, 2, 11, 3, 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, 5, 10, 2, 5, 2, 4, 4, 2, 0, 3, 10, 2, 3,
450
+ 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, 8, 4, 5, 8, 5, 3, 3,
451
+ 5, 1, 0, 4, 5, 1, 0, 5, 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, 9, 4, 5, 4, 11, 7, 4, 9, 11, 9,
452
+ 10, 11, 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, 3, 1, 4, 3,
453
+ 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, 9, 7, 4, 9, 11, 7, 9,
454
+ 1, 11, 2, 11, 1, 0, 8, 3, 11, 7, 4, 11, 4, 2, 2, 4, 0, 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, 2,
455
+ 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, 3, 7, 10, 3,
456
+ 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, 1, 10, 2, 8, 7, 4, 4, 9, 1, 4, 1, 7, 7, 1, 3, 4, 9, 1, 4,
457
+ 1, 7, 0, 8, 1, 8, 7, 1, 4, 0, 3, 7, 4, 3, 4, 8, 7, 9, 10, 8, 10, 11, 8, 3, 0, 9, 3, 9, 11, 11,
458
+ 9, 10, 0, 1, 10, 0, 10, 8, 8, 10, 11, 3, 1, 10, 11, 3, 10, 1, 2, 11, 1, 11, 9, 9, 11, 8, 3, 0, 9, 3,
459
+ 9, 11, 1, 2, 9, 2, 11, 9, 0, 2, 11, 8, 0, 11, 3, 2, 11, 2, 3, 8, 2, 8, 10, 10, 8, 9, 9, 10, 2, 0,
460
+ 9, 2, 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, 1, 10, 2, 1, 3, 8, 9, 1, 8, 0, 9, 1, 0, 3, 8
461
+ ])
462
+
463
+ mc_edge_offset_np = np.array([
464
+ [0, 0, 0, 0],
465
+ [1, 0, 0, 1],
466
+ [0, 1, 0, 0],
467
+ [0, 0, 0, 1],
468
+
469
+ [0, 0, 1, 0],
470
+ [1, 0, 1, 1],
471
+ [0, 1, 1, 0],
472
+ [0, 0, 1, 1],
473
+
474
+ [0, 0, 0, 2],
475
+ [1, 0, 0, 2],
476
+ [1, 1, 0, 2],
477
+ [0, 1, 0, 2]
478
+ ])
479
+
480
+ mc_cube_corner_offsets = [[0,0,0], [1,0,0], [1,1,0], [0,1,0], [0,0,1], [1,0,1], [1,1,1], [0,1,1]]
481
+ # fmt: on
482
+
483
+
484
+ class MarchingCubes:
485
+ """A reusable context for marching cubes surface extraction.
486
+
487
+ This class provides a stateful interface for isosurface extraction. You
488
+ can initialize it with a specific grid configuration and then call the
489
+ :meth:`~.surface` method multiple times, which is efficient for processing
490
+ fields of the same size.
491
+
492
+ For a simpler, stateless operation, use the static method
493
+ :meth:`~.extract_surface_marching_cubes`.
494
+
495
+ Attributes:
496
+ nx (int): The number of grid nodes in the x-direction.
497
+ ny (int): The number of grid nodes in the y-direction.
498
+ nz (int): The number of grid nodes in the z-direction.
499
+ domain_bounds_lower_corner (wp.vec3 | tuple | None): The lower bound
500
+ for the mesh coordinate scaling. See the documentation in
501
+ :meth:`~.extract_surface_marching_cubes` for more details.
502
+ domain_bounds_upper_corner (wp.vec3 | tuple | None): The upper bound
503
+ for the mesh coordinate scaling. See the documentation in
504
+ :meth:`~.extract_surface_marching_cubes` for more details.
505
+ verts (warp.array | None): An array of vertex positions of type
506
+ :class:`warp.vec3f` for the output mesh.
507
+ This is populated by calling the :meth:`~.surface` method.
508
+ indices (warp.array | None): An array of triangle indices of type
509
+ :class:`warp.int32` for the output mesh.
510
+ This is populated by calling the :meth:`~.surface` method.
511
+ device (warp._src.context.Device): The device on which the context was created. This
512
+ attribute is for backward compatibility and is not used by the
513
+ class's methods.
514
+ """
515
+
516
+ def __new__(cls, *args, **kwargs):
517
+ instance = super().__new__(cls)
518
+ return instance
519
+
520
+ def __init__(
521
+ self,
522
+ nx: int,
523
+ ny: int,
524
+ nz: int,
525
+ max_verts: int = 0,
526
+ max_tris: int = 0,
527
+ device=None,
528
+ domain_bounds_lower_corner=None,
529
+ domain_bounds_upper_corner=None,
530
+ ):
531
+ """Initialize the marching cubes context with a grid configuration.
532
+
533
+ Args:
534
+ nx: Number of grid nodes in the x-direction.
535
+ ny: Number of grid nodes in the y-direction.
536
+ nz: Number of grid nodes in the z-direction.
537
+ max_verts: (Deprecated) This argument is ignored.
538
+ max_tris: (Deprecated) This argument is ignored.
539
+ device (Devicelike): (Deprecated) The value is assigned to
540
+ `self.device` for backward compatibility but is not used by the
541
+ class's methods. It may be removed in a future version.
542
+ domain_bounds_lower_corner: See the documentation in
543
+ :meth:`~.extract_surface_marching_cubes`.
544
+ domain_bounds_upper_corner: See the documentation in
545
+ :meth:`~.extract_surface_marching_cubes`.
546
+ """
547
+ # Input domain sizes, as number of nodes in the grid (note this is 1 more than the number of cubes)
548
+ self.nx = nx
549
+ self.ny = ny
550
+ self.nz = nz
551
+
552
+ # Geometry of the extraction domain
553
+ # (or None, to implicitly use a domain with integer-coordinate nodes)
554
+ self.domain_bounds_lower_corner = domain_bounds_lower_corner
555
+ self.domain_bounds_upper_corner = domain_bounds_upper_corner
556
+
557
+ # These are unused, but retained for backwards-compatibility for code which might use them
558
+ self.max_verts = max_verts
559
+ self.max_tris = max_tris
560
+
561
+ # Output arrays
562
+ self.verts: wp.array(dtype=wp.vec3f) | None = None
563
+ self.indices: wp.array(dtype=wp.int32) | None = None
564
+
565
+ # These are unused, but retained for backwards-compatibility for code which might use them
566
+ self.id = 0
567
+ self.runtime = wp._src.context.runtime
568
+ self.device = self.runtime.get_device(device)
569
+
570
+ def resize(self, nx: int, ny: int, nz: int, max_verts: int = 0, max_tris: int = 0) -> None:
571
+ """Update the grid dimensions for the context.
572
+
573
+ This allows the instance to be reused for scalar fields of a different
574
+ resolution. The new dimensions take effect on the next call to
575
+ :meth:`~.surface`.
576
+
577
+ Args:
578
+ nx: New number of nodes in the x-direction.
579
+ ny: New number of nodes in the y-direction.
580
+ nz: New number of nodes in the z-direction.
581
+ max_verts: (Deprecated) This argument is ignored.
582
+ max_tris: (Deprecated) This argument is ignored.
583
+ """
584
+ self.nx = nx
585
+ self.ny = ny
586
+ self.nz = nz
587
+
588
+ def surface(self, field: wp.array(dtype=float, ndim=3), threshold: float) -> None:
589
+ """Compute a 2D surface mesh of a given isosurface from a 3D scalar field.
590
+
591
+ This method is a convenience wrapper that calls the core static method
592
+ and stores the resulting mesh data in the :attr:`verts` and
593
+ :attr:`indices` attributes.
594
+
595
+ Args:
596
+ field: A 3D scalar field whose shape must match the grid dimensions
597
+ (nx, ny, nz) of the instance.
598
+ threshold: The field value defining the isosurface to extract.
599
+
600
+ Raises:
601
+ ValueError: If the shape of ``field`` does not match the configured
602
+ grid dimensions of the instance.
603
+ """
604
+ # nx, ny, nz is the number of nodes, which should agree with the size of the field
605
+ if field.shape != (self.nx, self.ny, self.nz):
606
+ raise ValueError(
607
+ f"Field shape {field.shape} does not match context grid dimensions {(self.nx, self.ny, self.nz)}."
608
+ )
609
+
610
+ verts, faces = self.extract_surface_marching_cubes(
611
+ field=field,
612
+ threshold=wp.float32(threshold),
613
+ domain_bounds_lower_corner=self.domain_bounds_lower_corner,
614
+ domain_bounds_upper_corner=self.domain_bounds_upper_corner,
615
+ )
616
+
617
+ self.verts = verts
618
+ self.indices = faces
619
+
620
+ @staticmethod
621
+ def extract_surface_marching_cubes(
622
+ field: wp.array3d(dtype=wp.float32),
623
+ threshold: float = 0.0,
624
+ domain_bounds_lower_corner: wp.vec3 | tuple[float, float, float] | None = None,
625
+ domain_bounds_upper_corner: wp.vec3 | tuple[float, float, float] | None = None,
626
+ ) -> tuple[wp.array(dtype=wp.vec3), wp.array(dtype=wp.int32)]:
627
+ """Extract a triangular mesh from a 3D scalar field.
628
+
629
+ This function generates an isosurface by processing the entire input ``field``.
630
+ The resolution of the output mesh is determined by the shape of the ``field``
631
+ array and may differ along each dimension.
632
+
633
+ The coordinates of the mesh can be scaled to a specific bounding box
634
+ using the ``domain_bounds_lower_corner`` and
635
+ ``domain_bounds_upper_corner`` parameters. If a bound is not provided
636
+ (i.e., left as ``None``), it will be assigned a default value that
637
+ aligns the mesh with the integer indices of the input grid.
638
+
639
+ For example, setting the bounds to ``wp.vec3(0.0, 0.0, 0.0)`` and
640
+ ``wp.vec3(1.0, 1.0, 1.0)`` will scale the output mesh to fit
641
+ within the unit cube.
642
+
643
+ Args:
644
+ field: A 3D array representing the scalar values on a regular grid.
645
+ threshold: The field value defining the isosurface to extract.
646
+ domain_bounds_lower_corner: The 3D coordinate that the grid's corner
647
+ at index (0,0,0) maps to. Defaults to ``(0.0, 0.0, 0.0)``
648
+ if ``None``.
649
+ domain_bounds_upper_corner: The 3D coordinate that the grid's corner
650
+ at index (nx-1, ny-1, nz-1) maps to. Defaults to align with the
651
+ grid's maximal indices if ``None``.
652
+
653
+ Returns:
654
+ A tuple ``(vertices, indices)`` containing the output mesh data. The
655
+ ``indices`` array is a flat list where each group of three consecutive
656
+ integers forms a single triangle by referencing vertices in the
657
+ ``vertices`` array.
658
+
659
+ Raises:
660
+ ValueError: If ``field`` is not a 3D array or is empty.
661
+ TypeError: If the ``field`` data type is not ``wp.float32``.
662
+ """
663
+ # Do some validation
664
+ if len(field.shape) != 3:
665
+ raise ValueError(f"Expected a 3D array for 'field', but got an array with shape {field.shape}.")
666
+
667
+ if field.size == 0:
668
+ raise ValueError("The 'field' array cannot be empty.")
669
+
670
+ if field.dtype != wp.float32:
671
+ raise TypeError(f"Expected a dtype of wp.float32 for 'field', but got {field.dtype}.")
672
+
673
+ # Parse out dimensions, being careful to distinguish between nodes and cells
674
+ nnode_x, nnode_y, nnode_z = field.shape[0], field.shape[1], field.shape[2]
675
+ ncell_x, ncell_y, ncell_z = nnode_x - 1, nnode_y - 1, nnode_z - 1
676
+
677
+ # Apply default policies for bounds
678
+ if domain_bounds_lower_corner is None:
679
+ domain_bounds_lower_corner = wp.vec3((0.0, 0.0, 0.0))
680
+ if domain_bounds_upper_corner is None:
681
+ # The default convention is to treat the nodes of the grid as having integer coordinates at 0,1,2,...
682
+ # This means the upper-rightmost node of the grid has coordinates (nnode_x-1, nnode_y-1, nnode_z-1)
683
+ # (which happens to be the same as the number cells, although it may be more confusing to think of it that way)
684
+ domain_bounds_upper_corner = wp.vec3((float(nnode_x - 1), float(nnode_y - 1), float(nnode_z - 1)))
685
+
686
+ # quietly allow tuples as input too, although this technically violates
687
+ # the type hinting
688
+ domain_bounds_lower_corner = wp.vec3(domain_bounds_lower_corner)
689
+ domain_bounds_upper_corner = wp.vec3(domain_bounds_upper_corner)
690
+
691
+ # Compute the grid spacing
692
+ domain_width = domain_bounds_upper_corner - domain_bounds_lower_corner
693
+ grid_delta = wp.cw_div(domain_width, wp.vec3(ncell_x, ncell_y, ncell_z))
694
+
695
+ # Extract the vertices
696
+ # The second output of this kernel is an is-boundary flag for each vertex, which
697
+ # we currently do not expose. (maybe this should be exposed in the future)
698
+ verts, _, edge_generated_vert_ind = marching_cubes_extract_vertices(
699
+ field, threshold, domain_bounds_lower_corner, grid_delta
700
+ )
701
+
702
+ # Extract faces between those vertices
703
+ tris = marching_cubes_extract_faces(field, threshold, edge_generated_vert_ind)
704
+
705
+ return verts, tris
706
+
707
+ def __del__(self):
708
+ return