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.
- warp/__init__.py +301 -287
- warp/__init__.pyi +2220 -313
- warp/_src/__init__.py +14 -0
- warp/_src/autograd.py +1075 -0
- warp/_src/build.py +618 -0
- warp/_src/build_dll.py +640 -0
- warp/{builtins.py → _src/builtins.py} +1497 -226
- warp/_src/codegen.py +4359 -0
- warp/{config.py → _src/config.py} +178 -169
- warp/_src/constants.py +57 -0
- warp/_src/context.py +8294 -0
- warp/_src/dlpack.py +462 -0
- warp/_src/fabric.py +355 -0
- warp/_src/fem/__init__.py +14 -0
- warp/_src/fem/adaptivity.py +508 -0
- warp/_src/fem/cache.py +687 -0
- warp/_src/fem/dirichlet.py +188 -0
- warp/{fem → _src/fem}/domain.py +40 -30
- warp/_src/fem/field/__init__.py +131 -0
- warp/_src/fem/field/field.py +701 -0
- warp/{fem → _src/fem}/field/nodal_field.py +30 -15
- warp/{fem → _src/fem}/field/restriction.py +1 -1
- warp/{fem → _src/fem}/field/virtual.py +53 -27
- warp/_src/fem/geometry/__init__.py +32 -0
- warp/{fem → _src/fem}/geometry/adaptive_nanogrid.py +77 -163
- warp/_src/fem/geometry/closest_point.py +97 -0
- warp/{fem → _src/fem}/geometry/deformed_geometry.py +14 -22
- warp/{fem → _src/fem}/geometry/element.py +32 -10
- warp/{fem → _src/fem}/geometry/geometry.py +48 -20
- warp/{fem → _src/fem}/geometry/grid_2d.py +12 -23
- warp/{fem → _src/fem}/geometry/grid_3d.py +12 -23
- warp/{fem → _src/fem}/geometry/hexmesh.py +40 -63
- warp/{fem → _src/fem}/geometry/nanogrid.py +255 -248
- warp/{fem → _src/fem}/geometry/partition.py +121 -63
- warp/{fem → _src/fem}/geometry/quadmesh.py +26 -45
- warp/{fem → _src/fem}/geometry/tetmesh.py +40 -63
- warp/{fem → _src/fem}/geometry/trimesh.py +26 -45
- warp/{fem → _src/fem}/integrate.py +164 -158
- warp/_src/fem/linalg.py +383 -0
- warp/_src/fem/operator.py +396 -0
- warp/_src/fem/polynomial.py +229 -0
- warp/{fem → _src/fem}/quadrature/pic_quadrature.py +15 -20
- warp/{fem → _src/fem}/quadrature/quadrature.py +95 -47
- warp/_src/fem/space/__init__.py +248 -0
- warp/{fem → _src/fem}/space/basis_function_space.py +20 -11
- warp/_src/fem/space/basis_space.py +679 -0
- warp/{fem → _src/fem}/space/dof_mapper.py +3 -3
- warp/{fem → _src/fem}/space/function_space.py +14 -13
- warp/{fem → _src/fem}/space/grid_2d_function_space.py +4 -7
- warp/{fem → _src/fem}/space/grid_3d_function_space.py +4 -4
- warp/{fem → _src/fem}/space/hexmesh_function_space.py +4 -10
- warp/{fem → _src/fem}/space/nanogrid_function_space.py +3 -9
- warp/{fem → _src/fem}/space/partition.py +117 -60
- warp/{fem → _src/fem}/space/quadmesh_function_space.py +4 -10
- warp/{fem → _src/fem}/space/restriction.py +66 -33
- warp/_src/fem/space/shape/__init__.py +152 -0
- warp/{fem → _src/fem}/space/shape/cube_shape_function.py +9 -9
- warp/{fem → _src/fem}/space/shape/shape_function.py +8 -9
- warp/{fem → _src/fem}/space/shape/square_shape_function.py +6 -6
- warp/{fem → _src/fem}/space/shape/tet_shape_function.py +3 -3
- warp/{fem → _src/fem}/space/shape/triangle_shape_function.py +3 -3
- warp/{fem → _src/fem}/space/tetmesh_function_space.py +3 -9
- warp/_src/fem/space/topology.py +459 -0
- warp/{fem → _src/fem}/space/trimesh_function_space.py +3 -9
- warp/_src/fem/types.py +112 -0
- warp/_src/fem/utils.py +486 -0
- warp/_src/jax.py +186 -0
- warp/_src/jax_experimental/__init__.py +14 -0
- warp/_src/jax_experimental/custom_call.py +387 -0
- warp/_src/jax_experimental/ffi.py +1284 -0
- warp/_src/jax_experimental/xla_ffi.py +656 -0
- warp/_src/marching_cubes.py +708 -0
- warp/_src/math.py +414 -0
- warp/_src/optim/__init__.py +14 -0
- warp/_src/optim/adam.py +163 -0
- warp/_src/optim/linear.py +1606 -0
- warp/_src/optim/sgd.py +112 -0
- warp/_src/paddle.py +406 -0
- warp/_src/render/__init__.py +14 -0
- warp/_src/render/imgui_manager.py +289 -0
- warp/_src/render/render_opengl.py +3636 -0
- warp/_src/render/render_usd.py +937 -0
- warp/_src/render/utils.py +160 -0
- warp/_src/sparse.py +2716 -0
- warp/_src/tape.py +1206 -0
- warp/{thirdparty → _src/thirdparty}/unittest_parallel.py +9 -2
- warp/_src/torch.py +391 -0
- warp/_src/types.py +5870 -0
- warp/_src/utils.py +1693 -0
- warp/autograd.py +12 -1054
- warp/bin/warp-clang.dll +0 -0
- warp/bin/warp.dll +0 -0
- warp/build.py +8 -588
- warp/build_dll.py +6 -471
- warp/codegen.py +6 -4246
- warp/constants.py +6 -39
- warp/context.py +12 -7851
- warp/dlpack.py +6 -444
- warp/examples/distributed/example_jacobi_mpi.py +4 -5
- warp/examples/fem/example_adaptive_grid.py +1 -1
- warp/examples/fem/example_apic_fluid.py +1 -1
- warp/examples/fem/example_burgers.py +8 -8
- warp/examples/fem/example_diffusion.py +1 -1
- warp/examples/fem/example_distortion_energy.py +1 -1
- warp/examples/fem/example_mixed_elasticity.py +2 -2
- warp/examples/fem/example_navier_stokes.py +1 -1
- warp/examples/fem/example_nonconforming_contact.py +7 -7
- warp/examples/fem/example_stokes.py +1 -1
- warp/examples/fem/example_stokes_transfer.py +1 -1
- warp/examples/fem/utils.py +2 -2
- warp/examples/interop/example_jax_callable.py +1 -1
- warp/examples/interop/example_jax_ffi_callback.py +1 -1
- warp/examples/interop/example_jax_kernel.py +3 -2
- warp/examples/tile/example_tile_mcgp.py +191 -0
- warp/fabric.py +6 -337
- warp/fem/__init__.py +159 -97
- warp/fem/adaptivity.py +7 -489
- warp/fem/cache.py +9 -648
- warp/fem/dirichlet.py +6 -184
- warp/fem/field/__init__.py +8 -109
- warp/fem/field/field.py +7 -652
- warp/fem/geometry/__init__.py +7 -18
- warp/fem/geometry/closest_point.py +11 -77
- warp/fem/linalg.py +18 -366
- warp/fem/operator.py +11 -369
- warp/fem/polynomial.py +9 -209
- warp/fem/space/__init__.py +5 -211
- warp/fem/space/basis_space.py +6 -662
- warp/fem/space/shape/__init__.py +41 -118
- warp/fem/space/topology.py +6 -437
- warp/fem/types.py +6 -81
- warp/fem/utils.py +11 -444
- warp/jax.py +8 -165
- warp/jax_experimental/__init__.py +14 -1
- warp/jax_experimental/custom_call.py +8 -342
- warp/jax_experimental/ffi.py +17 -853
- warp/jax_experimental/xla_ffi.py +5 -596
- warp/marching_cubes.py +5 -689
- warp/math.py +16 -393
- warp/native/array.h +385 -37
- warp/native/builtin.h +316 -39
- warp/native/bvh.cpp +43 -9
- warp/native/bvh.cu +62 -27
- warp/native/bvh.h +310 -309
- warp/native/clang/clang.cpp +102 -97
- warp/native/coloring.cpp +0 -1
- warp/native/crt.h +208 -0
- warp/native/exports.h +156 -0
- warp/native/hashgrid.cu +2 -0
- warp/native/intersect.h +24 -1
- warp/native/intersect_tri.h +44 -35
- warp/native/mat.h +1456 -276
- warp/native/mesh.cpp +4 -4
- warp/native/mesh.cu +4 -2
- warp/native/mesh.h +176 -61
- warp/native/quat.h +0 -52
- warp/native/scan.cu +2 -0
- warp/native/sort.cu +22 -13
- warp/native/sort.h +2 -0
- warp/native/sparse.cu +7 -3
- warp/native/spatial.h +12 -0
- warp/native/tile.h +837 -70
- warp/native/tile_radix_sort.h +1 -1
- warp/native/tile_reduce.h +394 -46
- warp/native/tile_scan.h +4 -4
- warp/native/vec.h +469 -53
- warp/native/version.h +23 -0
- warp/native/volume.cpp +1 -1
- warp/native/volume.cu +1 -0
- warp/native/volume.h +1 -1
- warp/native/volume_builder.cu +2 -0
- warp/native/warp.cpp +60 -32
- warp/native/warp.cu +313 -201
- warp/native/warp.h +14 -11
- warp/optim/__init__.py +6 -3
- warp/optim/adam.py +6 -145
- warp/optim/linear.py +14 -1585
- warp/optim/sgd.py +6 -94
- warp/paddle.py +6 -388
- warp/render/__init__.py +8 -4
- warp/render/imgui_manager.py +7 -267
- warp/render/render_opengl.py +6 -3616
- warp/render/render_usd.py +6 -918
- warp/render/utils.py +6 -142
- warp/sparse.py +37 -2563
- warp/tape.py +6 -1188
- warp/tests/__main__.py +1 -1
- warp/tests/cuda/test_async.py +4 -4
- warp/tests/cuda/test_conditional_captures.py +1 -1
- warp/tests/cuda/test_multigpu.py +1 -1
- warp/tests/cuda/test_streams.py +58 -1
- warp/tests/geometry/test_bvh.py +157 -22
- warp/tests/geometry/test_hash_grid.py +38 -0
- warp/tests/geometry/test_marching_cubes.py +0 -1
- warp/tests/geometry/test_mesh.py +5 -3
- warp/tests/geometry/test_mesh_query_aabb.py +5 -12
- warp/tests/geometry/test_mesh_query_point.py +5 -2
- warp/tests/geometry/test_mesh_query_ray.py +15 -3
- warp/tests/geometry/test_volume_write.py +5 -5
- warp/tests/interop/test_dlpack.py +14 -14
- warp/tests/interop/test_jax.py +1382 -79
- warp/tests/interop/test_paddle.py +1 -1
- warp/tests/test_adam.py +0 -1
- warp/tests/test_arithmetic.py +9 -9
- warp/tests/test_array.py +529 -100
- warp/tests/test_array_reduce.py +3 -3
- warp/tests/test_atomic.py +12 -8
- warp/tests/test_atomic_bitwise.py +209 -0
- warp/tests/test_atomic_cas.py +4 -4
- warp/tests/test_bool.py +2 -2
- warp/tests/test_builtins_resolution.py +5 -571
- warp/tests/test_codegen.py +34 -15
- warp/tests/test_conditional.py +1 -1
- warp/tests/test_context.py +6 -6
- warp/tests/test_copy.py +242 -161
- warp/tests/test_ctypes.py +3 -3
- warp/tests/test_devices.py +24 -2
- warp/tests/test_examples.py +16 -84
- warp/tests/test_fabricarray.py +35 -35
- warp/tests/test_fast_math.py +0 -2
- warp/tests/test_fem.py +60 -14
- warp/tests/test_fixedarray.py +3 -3
- warp/tests/test_func.py +8 -5
- warp/tests/test_generics.py +1 -1
- warp/tests/test_indexedarray.py +24 -24
- warp/tests/test_intersect.py +39 -9
- warp/tests/test_large.py +1 -1
- warp/tests/test_lerp.py +3 -1
- warp/tests/test_linear_solvers.py +1 -1
- warp/tests/test_map.py +49 -4
- warp/tests/test_mat.py +52 -62
- warp/tests/test_mat_constructors.py +4 -5
- warp/tests/test_mat_lite.py +1 -1
- warp/tests/test_mat_scalar_ops.py +121 -121
- warp/tests/test_math.py +34 -0
- warp/tests/test_module_aot.py +4 -4
- warp/tests/test_modules_lite.py +28 -2
- warp/tests/test_print.py +11 -11
- warp/tests/test_quat.py +93 -58
- warp/tests/test_runlength_encode.py +1 -1
- warp/tests/test_scalar_ops.py +38 -10
- warp/tests/test_smoothstep.py +1 -1
- warp/tests/test_sparse.py +126 -15
- warp/tests/test_spatial.py +105 -87
- warp/tests/test_special_values.py +6 -6
- warp/tests/test_static.py +7 -7
- warp/tests/test_struct.py +13 -2
- warp/tests/test_triangle_closest_point.py +48 -1
- warp/tests/test_tuple.py +96 -0
- warp/tests/test_types.py +82 -9
- warp/tests/test_utils.py +52 -52
- warp/tests/test_vec.py +29 -29
- warp/tests/test_vec_constructors.py +5 -5
- warp/tests/test_vec_scalar_ops.py +97 -97
- warp/tests/test_version.py +75 -0
- warp/tests/tile/test_tile.py +239 -0
- warp/tests/tile/test_tile_atomic_bitwise.py +403 -0
- warp/tests/tile/test_tile_cholesky.py +7 -4
- warp/tests/tile/test_tile_load.py +26 -2
- warp/tests/tile/test_tile_mathdx.py +3 -3
- warp/tests/tile/test_tile_matmul.py +1 -1
- warp/tests/tile/test_tile_mlp.py +2 -4
- warp/tests/tile/test_tile_reduce.py +214 -13
- warp/tests/unittest_suites.py +6 -14
- warp/tests/unittest_utils.py +10 -9
- warp/tests/walkthrough_debug.py +3 -1
- warp/torch.py +6 -373
- warp/types.py +29 -5750
- warp/utils.py +10 -1659
- {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/METADATA +47 -103
- warp_lang-1.10.0rc2.dist-info/RECORD +468 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/Gaia-LICENSE.txt +6 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/appdirs-LICENSE.txt +22 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/asset_pixel_jpg-LICENSE.txt +3 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/cuda-LICENSE.txt +1582 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/dlpack-LICENSE.txt +201 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/fp16-LICENSE.txt +28 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/libmathdx-LICENSE.txt +220 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/llvm-LICENSE.txt +279 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/moller-LICENSE.txt +16 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/nanovdb-LICENSE.txt +2 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/nvrtc-LICENSE.txt +1592 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/svd-LICENSE.txt +23 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/unittest_parallel-LICENSE.txt +21 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/usd-LICENSE.txt +213 -0
- warp_lang-1.10.0rc2.dist-info/licenses/licenses/windingnumber-LICENSE.txt +21 -0
- warp/examples/assets/cartpole.urdf +0 -110
- warp/examples/assets/crazyflie.usd +0 -0
- warp/examples/assets/nv_ant.xml +0 -92
- warp/examples/assets/nv_humanoid.xml +0 -183
- warp/examples/assets/quadruped.urdf +0 -268
- warp/examples/optim/example_bounce.py +0 -266
- warp/examples/optim/example_cloth_throw.py +0 -228
- warp/examples/optim/example_drone.py +0 -870
- warp/examples/optim/example_inverse_kinematics.py +0 -182
- warp/examples/optim/example_inverse_kinematics_torch.py +0 -191
- warp/examples/optim/example_softbody_properties.py +0 -400
- warp/examples/optim/example_spring_cage.py +0 -245
- warp/examples/optim/example_trajectory.py +0 -227
- warp/examples/sim/example_cartpole.py +0 -143
- warp/examples/sim/example_cloth.py +0 -225
- warp/examples/sim/example_cloth_self_contact.py +0 -316
- warp/examples/sim/example_granular.py +0 -130
- warp/examples/sim/example_granular_collision_sdf.py +0 -202
- warp/examples/sim/example_jacobian_ik.py +0 -244
- warp/examples/sim/example_particle_chain.py +0 -124
- warp/examples/sim/example_quadruped.py +0 -203
- warp/examples/sim/example_rigid_chain.py +0 -203
- warp/examples/sim/example_rigid_contact.py +0 -195
- warp/examples/sim/example_rigid_force.py +0 -133
- warp/examples/sim/example_rigid_gyroscopic.py +0 -115
- warp/examples/sim/example_rigid_soft_contact.py +0 -140
- warp/examples/sim/example_soft_body.py +0 -196
- warp/examples/tile/example_tile_walker.py +0 -327
- warp/sim/__init__.py +0 -74
- warp/sim/articulation.py +0 -793
- warp/sim/collide.py +0 -2570
- warp/sim/graph_coloring.py +0 -307
- warp/sim/import_mjcf.py +0 -791
- warp/sim/import_snu.py +0 -227
- warp/sim/import_urdf.py +0 -579
- warp/sim/import_usd.py +0 -898
- warp/sim/inertia.py +0 -357
- warp/sim/integrator.py +0 -245
- warp/sim/integrator_euler.py +0 -2000
- warp/sim/integrator_featherstone.py +0 -2101
- warp/sim/integrator_vbd.py +0 -2487
- warp/sim/integrator_xpbd.py +0 -3295
- warp/sim/model.py +0 -4821
- warp/sim/particles.py +0 -121
- warp/sim/render.py +0 -431
- warp/sim/utils.py +0 -431
- warp/tests/sim/disabled_kinematics.py +0 -244
- warp/tests/sim/test_cloth.py +0 -863
- warp/tests/sim/test_collision.py +0 -743
- warp/tests/sim/test_coloring.py +0 -347
- warp/tests/sim/test_inertia.py +0 -161
- warp/tests/sim/test_model.py +0 -226
- warp/tests/sim/test_sim_grad.py +0 -287
- warp/tests/sim/test_sim_grad_bounce_linear.py +0 -212
- warp/tests/sim/test_sim_kinematics.py +0 -98
- warp/thirdparty/__init__.py +0 -0
- warp_lang-1.9.0.dist-info/RECORD +0 -456
- /warp/{fem → _src/fem}/quadrature/__init__.py +0 -0
- /warp/{tests/sim → _src/thirdparty}/__init__.py +0 -0
- /warp/{thirdparty → _src/thirdparty}/appdirs.py +0 -0
- /warp/{thirdparty → _src/thirdparty}/dlpack.py +0 -0
- {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/WHEEL +0 -0
- {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/licenses/LICENSE.md +0 -0
- {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
|