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