warp-lang 1.3.2__py3-none-win_amd64.whl → 1.4.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of warp-lang might be problematic. Click here for more details.

Files changed (108) hide show
  1. warp/__init__.py +6 -0
  2. warp/autograd.py +59 -6
  3. warp/bin/warp-clang.dll +0 -0
  4. warp/bin/warp.dll +0 -0
  5. warp/build_dll.py +8 -10
  6. warp/builtins.py +126 -4
  7. warp/codegen.py +435 -53
  8. warp/config.py +1 -1
  9. warp/context.py +678 -403
  10. warp/dlpack.py +2 -0
  11. warp/examples/benchmarks/benchmark_cloth.py +10 -0
  12. warp/examples/core/example_render_opengl.py +12 -10
  13. warp/examples/fem/example_adaptive_grid.py +251 -0
  14. warp/examples/fem/example_apic_fluid.py +1 -1
  15. warp/examples/fem/example_diffusion_3d.py +2 -2
  16. warp/examples/fem/example_magnetostatics.py +1 -1
  17. warp/examples/fem/example_streamlines.py +1 -0
  18. warp/examples/fem/utils.py +23 -4
  19. warp/examples/sim/example_cloth.py +50 -6
  20. warp/fem/__init__.py +2 -0
  21. warp/fem/adaptivity.py +493 -0
  22. warp/fem/field/field.py +2 -1
  23. warp/fem/field/nodal_field.py +18 -26
  24. warp/fem/field/test.py +4 -4
  25. warp/fem/field/trial.py +4 -4
  26. warp/fem/geometry/__init__.py +1 -0
  27. warp/fem/geometry/adaptive_nanogrid.py +843 -0
  28. warp/fem/geometry/nanogrid.py +55 -28
  29. warp/fem/space/__init__.py +1 -1
  30. warp/fem/space/nanogrid_function_space.py +69 -35
  31. warp/fem/utils.py +113 -107
  32. warp/jax_experimental.py +28 -15
  33. warp/native/array.h +0 -1
  34. warp/native/builtin.h +103 -6
  35. warp/native/bvh.cu +2 -0
  36. warp/native/cuda_util.cpp +14 -0
  37. warp/native/cuda_util.h +2 -0
  38. warp/native/error.cpp +4 -2
  39. warp/native/exports.h +99 -17
  40. warp/native/mat.h +97 -0
  41. warp/native/mesh.cpp +36 -0
  42. warp/native/mesh.cu +51 -0
  43. warp/native/mesh.h +1 -0
  44. warp/native/quat.h +43 -0
  45. warp/native/spatial.h +6 -0
  46. warp/native/vec.h +74 -0
  47. warp/native/warp.cpp +2 -1
  48. warp/native/warp.cu +10 -3
  49. warp/native/warp.h +8 -1
  50. warp/paddle.py +382 -0
  51. warp/sim/__init__.py +1 -0
  52. warp/sim/collide.py +519 -0
  53. warp/sim/integrator_euler.py +18 -5
  54. warp/sim/integrator_featherstone.py +5 -5
  55. warp/sim/integrator_vbd.py +1026 -0
  56. warp/sim/model.py +49 -23
  57. warp/stubs.py +459 -0
  58. warp/tape.py +2 -0
  59. warp/tests/aux_test_dependent.py +1 -0
  60. warp/tests/aux_test_name_clash1.py +32 -0
  61. warp/tests/aux_test_name_clash2.py +32 -0
  62. warp/tests/aux_test_square.py +1 -0
  63. warp/tests/test_array.py +222 -0
  64. warp/tests/test_async.py +3 -3
  65. warp/tests/test_atomic.py +6 -0
  66. warp/tests/test_closest_point_edge_edge.py +93 -1
  67. warp/tests/test_codegen.py +62 -15
  68. warp/tests/test_codegen_instancing.py +1457 -0
  69. warp/tests/test_collision.py +486 -0
  70. warp/tests/test_compile_consts.py +3 -28
  71. warp/tests/test_dlpack.py +170 -0
  72. warp/tests/test_examples.py +22 -8
  73. warp/tests/test_fast_math.py +10 -4
  74. warp/tests/test_fem.py +64 -0
  75. warp/tests/test_func.py +46 -0
  76. warp/tests/test_implicit_init.py +49 -0
  77. warp/tests/test_jax.py +58 -0
  78. warp/tests/test_mat.py +84 -0
  79. warp/tests/test_mesh_query_point.py +188 -0
  80. warp/tests/test_module_hashing.py +40 -0
  81. warp/tests/test_multigpu.py +3 -3
  82. warp/tests/test_overwrite.py +8 -0
  83. warp/tests/test_paddle.py +852 -0
  84. warp/tests/test_print.py +89 -0
  85. warp/tests/test_quat.py +111 -0
  86. warp/tests/test_reload.py +31 -1
  87. warp/tests/test_scalar_ops.py +2 -0
  88. warp/tests/test_static.py +412 -0
  89. warp/tests/test_streams.py +64 -3
  90. warp/tests/test_struct.py +4 -4
  91. warp/tests/test_torch.py +24 -0
  92. warp/tests/test_triangle_closest_point.py +137 -0
  93. warp/tests/test_types.py +1 -1
  94. warp/tests/test_vbd.py +386 -0
  95. warp/tests/test_vec.py +143 -0
  96. warp/tests/test_vec_scalar_ops.py +139 -0
  97. warp/tests/test_volume.py +30 -0
  98. warp/tests/unittest_suites.py +12 -0
  99. warp/tests/unittest_utils.py +9 -5
  100. warp/thirdparty/dlpack.py +3 -1
  101. warp/types.py +157 -34
  102. warp/utils.py +37 -14
  103. {warp_lang-1.3.2.dist-info → warp_lang-1.4.0.dist-info}/METADATA +10 -8
  104. {warp_lang-1.3.2.dist-info → warp_lang-1.4.0.dist-info}/RECORD +107 -95
  105. warp/tests/test_point_triangle_closest_point.py +0 -143
  106. {warp_lang-1.3.2.dist-info → warp_lang-1.4.0.dist-info}/LICENSE.md +0 -0
  107. {warp_lang-1.3.2.dist-info → warp_lang-1.4.0.dist-info}/WHEEL +0 -0
  108. {warp_lang-1.3.2.dist-info → warp_lang-1.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1026 @@
1
+ # Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved.
2
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
3
+ # and proprietary rights in and to this software, related documentation
4
+ # and any modifications thereto. Any use, reproduction, disclosure or
5
+ # distribution of this software and related documentation without an express
6
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
7
+ import numpy as np
8
+
9
+ import warp as wp
10
+
11
+ from ..types import float32, matrix
12
+ from .integrator import Integrator
13
+ from .model import PARTICLE_FLAG_ACTIVE, Control, Model, ModelShapeMaterials, State
14
+
15
+
16
+ class mat66(matrix(shape=(6, 6), dtype=float32)):
17
+ pass
18
+
19
+
20
+ class mat32(matrix(shape=(3, 2), dtype=float32)):
21
+ pass
22
+
23
+
24
+ @wp.struct
25
+ class ForceElementAdjacencyInfo:
26
+ r"""
27
+ - vertex_adjacent_[element]: the flatten adjacency information. Its size is \sum_{i\inV} 2*N_i, where N_i is the
28
+ number of vertex i’s adjacent [element]. For each adjacent element it stores 2 information:
29
+ - the id of the adjacent element
30
+ - the order of the vertex in the element, which is essential to compute the force and hessian for the vertex
31
+ - vertex_adjacent_[element]_offsets: stores where each vertex’ information starts in the flatten adjacency array.
32
+ Its size is |V|+1 such that the number of vertex i’s adjacent [element] can be computed as
33
+ vertex_adjacent_[element]_offsets[i+1]-vertex_adjacent_[element]_offsets[i].
34
+ """
35
+
36
+ v_adj_faces: wp.array(dtype=int)
37
+ v_adj_faces_offsets: wp.array(dtype=int)
38
+
39
+ v_adj_edges: wp.array(dtype=int)
40
+ v_adj_edges_offsets: wp.array(dtype=int)
41
+
42
+ def to(self, device):
43
+ if device.is_cpu:
44
+ return self
45
+ else:
46
+ adjacency_gpu = ForceElementAdjacencyInfo()
47
+ adjacency_gpu.v_adj_faces = self.v_adj_faces.to(device)
48
+ adjacency_gpu.v_adj_faces_offsets = self.v_adj_faces_offsets.to(device)
49
+
50
+ adjacency_gpu.v_adj_edges = self.v_adj_edges.to(device)
51
+ adjacency_gpu.v_adj_edges_offsets = self.v_adj_edges_offsets.to(device)
52
+
53
+ return adjacency_gpu
54
+
55
+
56
+ @wp.func
57
+ def get_vertex_num_adjacent_edges(vertex: wp.int32, adjacency: ForceElementAdjacencyInfo):
58
+ return (adjacency.v_adj_edges_offsets[vertex + 1] - adjacency.v_adj_edges_offsets[vertex]) >> 1
59
+
60
+
61
+ @wp.func
62
+ def get_vertex_adjacent_edge_id_order(vertex: wp.int32, edge: wp.int32, adjacency: ForceElementAdjacencyInfo):
63
+ offset = adjacency.v_adj_edges_offsets[vertex]
64
+ return adjacency.v_adj_edges[offset + edge * 2], adjacency.v_adj_edges[offset + edge * 2 + 1]
65
+
66
+
67
+ @wp.func
68
+ def get_vertex_num_adjacent_faces(vertex: wp.int32, adjacency: ForceElementAdjacencyInfo):
69
+ return (adjacency.v_adj_faces_offsets[vertex + 1] - adjacency.v_adj_faces_offsets[vertex]) >> 1
70
+
71
+
72
+ @wp.func
73
+ def get_vertex_adjacent_face_id_order(vertex: wp.int32, face: wp.int32, adjacency: ForceElementAdjacencyInfo):
74
+ offset = adjacency.v_adj_faces_offsets[vertex]
75
+ return adjacency.v_adj_faces[offset + face * 2], adjacency.v_adj_faces[offset + face * 2 + 1]
76
+
77
+
78
+ @wp.kernel
79
+ def _test_compute_force_element_adjacency(
80
+ adjacency: ForceElementAdjacencyInfo,
81
+ edge_indices: wp.array(dtype=wp.int32, ndim=2),
82
+ face_indices: wp.array(dtype=wp.int32, ndim=2),
83
+ ):
84
+ wp.printf("num vertices: %d\n", adjacency.v_adj_edges_offsets.shape[0] - 1)
85
+ for vertex in range(adjacency.v_adj_edges_offsets.shape[0] - 1):
86
+ num_adj_edges = get_vertex_num_adjacent_edges(vertex, adjacency)
87
+ for i_bd in range(num_adj_edges):
88
+ bd_id, v_order = get_vertex_adjacent_edge_id_order(vertex, i_bd, adjacency)
89
+
90
+ if edge_indices[bd_id, v_order] != vertex:
91
+ print("Error!!!")
92
+ wp.printf("vertex: %d | num_adj_edges: %d\n", vertex, num_adj_edges)
93
+ wp.printf("--iBd: %d | ", i_bd)
94
+ wp.printf("edge id: %d | v_order: %d\n", bd_id, v_order)
95
+
96
+ num_adj_faces = get_vertex_num_adjacent_faces(vertex, adjacency)
97
+
98
+ for i_face in range(num_adj_faces):
99
+ face, v_order = get_vertex_adjacent_face_id_order(vertex, i_face, adjacency)
100
+
101
+ if face_indices[face, v_order] != vertex:
102
+ print("Error!!!")
103
+ wp.printf("vertex: %d | num_adj_faces: %d\n", vertex, num_adj_faces)
104
+ wp.printf("--i_face: %d | face id: %d | v_order: %d\n", i_face, face, v_order)
105
+ wp.printf(
106
+ "--face: %d %d %d\n",
107
+ face_indices[face, 0],
108
+ face_indices[face, 1],
109
+ face_indices[face, 2],
110
+ )
111
+
112
+
113
+ @wp.func
114
+ def build_orthonormal_basis(n: wp.vec3):
115
+ """
116
+ Builds an orthonormal basis given a normal vector `n`. Return the two axes that is perpendicular to `n`.
117
+
118
+ :param n: A 3D vector (list or array-like) representing the normal vector
119
+ """
120
+ b1 = wp.vec3()
121
+ b2 = wp.vec3()
122
+ if n[2] < 0.0:
123
+ a = 1.0 / (1.0 - n[2])
124
+ b = n[0] * n[1] * a
125
+ b1[0] = 1.0 - n[0] * n[0] * a
126
+ b1[1] = -b
127
+ b1[2] = n[0]
128
+
129
+ b2[0] = b
130
+ b2[1] = n[1] * n[1] * a - 1.0
131
+ b2[2] = -n[1]
132
+ else:
133
+ a = 1.0 / (1.0 + n[2])
134
+ b = -n[0] * n[1] * a
135
+ b1[0] = 1.0 - n[0] * n[0] * a
136
+ b1[1] = b
137
+ b1[2] = -n[0]
138
+
139
+ b2[0] = b
140
+ b2[1] = 1.0 - n[1] * n[1] * a
141
+ b2[2] = -n[1]
142
+
143
+ return b1, b2
144
+
145
+
146
+ @wp.func
147
+ def calculate_triangle_deformation_gradient(
148
+ face: int, tri_indices: wp.array(dtype=wp.int32, ndim=2), pos: wp.array(dtype=wp.vec3), tri_pose: wp.mat22
149
+ ):
150
+ F = mat32()
151
+ v1 = pos[tri_indices[face, 1]] - pos[tri_indices[face, 0]]
152
+ v2 = pos[tri_indices[face, 2]] - pos[tri_indices[face, 0]]
153
+
154
+ F[0, 0] = v1[0]
155
+ F[1, 0] = v1[1]
156
+ F[2, 0] = v1[2]
157
+ F[0, 1] = v2[0]
158
+ F[1, 1] = v2[1]
159
+ F[2, 1] = v2[2]
160
+
161
+ F = F * tri_pose
162
+ return F
163
+
164
+
165
+ @wp.func
166
+ def green_strain(F: mat32):
167
+ return 0.5 * (wp.transpose(F) * F - wp.identity(n=2, dtype=float))
168
+
169
+
170
+ @wp.func
171
+ def assemble_membrane_hessian(h: mat66, m1: float, m2: float):
172
+ h_vert = wp.mat33(
173
+ m1 * (h[0, 0] * m1 + h[3, 0] * m2) + m2 * (h[0, 3] * m1 + h[3, 3] * m2),
174
+ m1 * (h[0, 1] * m1 + h[3, 1] * m2) + m2 * (h[0, 4] * m1 + h[3, 4] * m2),
175
+ m1 * (h[0, 2] * m1 + h[3, 2] * m2) + m2 * (h[0, 5] * m1 + h[3, 5] * m2),
176
+ m1 * (h[1, 0] * m1 + h[4, 0] * m2) + m2 * (h[1, 3] * m1 + h[4, 3] * m2),
177
+ m1 * (h[1, 1] * m1 + h[4, 1] * m2) + m2 * (h[1, 4] * m1 + h[4, 4] * m2),
178
+ m1 * (h[1, 2] * m1 + h[4, 2] * m2) + m2 * (h[1, 5] * m1 + h[4, 5] * m2),
179
+ m1 * (h[2, 0] * m1 + h[5, 0] * m2) + m2 * (h[2, 3] * m1 + h[5, 3] * m2),
180
+ m1 * (h[2, 1] * m1 + h[5, 1] * m2) + m2 * (h[2, 4] * m1 + h[5, 4] * m2),
181
+ m1 * (h[2, 2] * m1 + h[5, 2] * m2) + m2 * (h[2, 5] * m1 + h[5, 5] * m2),
182
+ )
183
+
184
+ return h_vert
185
+
186
+
187
+ @wp.func
188
+ def evaluate_stvk_force_hessian(
189
+ face: int,
190
+ v_order: int,
191
+ pos: wp.array(dtype=wp.vec3),
192
+ tri_indices: wp.array(dtype=wp.int32, ndim=2),
193
+ tri_pose: wp.mat22,
194
+ area: float,
195
+ mu: float,
196
+ lmbd: float,
197
+ damping: float,
198
+ ):
199
+ D2W_DFDF = mat66()
200
+ F = calculate_triangle_deformation_gradient(face, tri_indices, pos, tri_pose)
201
+ G = green_strain(F)
202
+
203
+ # wp.printf("face: %d, \nF12:\n, %f %f\n, %f %f\n, %f %f\nG:\n%f %f\n, %f %f\n",
204
+ # face,
205
+ # F[0, 0], F[0, 1],
206
+ # F[1, 0], F[1, 1],
207
+ # F[2, 0], F[2, 1],
208
+ #
209
+ # G[0, 0], G[0, 1],
210
+ # G[1, 0], G[1, 1],
211
+ # )
212
+
213
+ S = 2.0 * mu * G + lmbd * (G[0, 0] + G[1, 1]) * wp.identity(n=2, dtype=float)
214
+
215
+ F12 = -area * F * S * wp.transpose(tri_pose)
216
+
217
+ Dm_inv1_1 = tri_pose[0, 0]
218
+ Dm_inv2_1 = tri_pose[1, 0]
219
+ Dm_inv1_2 = tri_pose[0, 1]
220
+ Dm_inv2_2 = tri_pose[1, 1]
221
+
222
+ F1_1 = F[0, 0]
223
+ F2_1 = F[1, 0]
224
+ F3_1 = F[2, 0]
225
+ F1_2 = F[0, 1]
226
+ F2_2 = F[1, 1]
227
+ F3_2 = F[2, 1]
228
+
229
+ F1_1_sqr = F1_1 * F1_1
230
+ F2_1_sqr = F2_1 * F2_1
231
+ F3_1_sqr = F3_1 * F3_1
232
+ F1_2_sqr = F1_2 * F1_2
233
+ F2_2_sqr = F2_2 * F2_2
234
+ F3_2_sqr = F3_2 * F3_2
235
+
236
+ e_uu = G[0, 0]
237
+ e_vv = G[1, 1]
238
+ e_uv = G[0, 1]
239
+ e_uuvvSum = e_uu + e_vv
240
+
241
+ D2W_DFDF[0, 0] = F1_1 * (F1_1 * lmbd + 2.0 * F1_1 * mu) + 2.0 * mu * e_uu + lmbd * (e_uuvvSum) + F1_2_sqr * mu
242
+
243
+ D2W_DFDF[1, 0] = F1_1 * (F2_1 * lmbd + 2.0 * F2_1 * mu) + F1_2 * F2_2 * mu
244
+ D2W_DFDF[0, 1] = D2W_DFDF[1, 0]
245
+
246
+ D2W_DFDF[2, 0] = F1_1 * (F3_1 * lmbd + 2.0 * F3_1 * mu) + F1_2 * F3_2 * mu
247
+ D2W_DFDF[0, 2] = D2W_DFDF[2, 0]
248
+
249
+ D2W_DFDF[3, 0] = 2.0 * mu * e_uv + F1_1 * F1_2 * lmbd + F1_1 * F1_2 * mu
250
+ D2W_DFDF[0, 3] = D2W_DFDF[3, 0]
251
+
252
+ D2W_DFDF[4, 0] = F1_1 * F2_2 * lmbd + F1_2 * F2_1 * mu
253
+ D2W_DFDF[0, 4] = D2W_DFDF[4, 0]
254
+
255
+ D2W_DFDF[5, 0] = F1_1 * F3_2 * lmbd + F1_2 * F3_1 * mu
256
+ D2W_DFDF[0, 5] = D2W_DFDF[5, 0]
257
+
258
+ D2W_DFDF[1, 1] = F2_1 * (F2_1 * lmbd + 2.0 * F2_1 * mu) + 2.0 * mu * e_uu + lmbd * (e_uuvvSum) + F2_2_sqr * mu
259
+
260
+ D2W_DFDF[2, 1] = F2_1 * (F3_1 * lmbd + 2.0 * F3_1 * mu) + F2_2 * F3_2 * mu
261
+ D2W_DFDF[1, 2] = D2W_DFDF[2, 1]
262
+
263
+ D2W_DFDF[3, 1] = F1_2 * F2_1 * lmbd + F1_1 * F2_2 * mu
264
+ D2W_DFDF[1, 3] = D2W_DFDF[3, 1]
265
+
266
+ D2W_DFDF[4, 1] = 2.0 * mu * e_uv + F2_1 * F2_2 * lmbd + F2_1 * F2_2 * mu
267
+ D2W_DFDF[1, 4] = D2W_DFDF[4, 1]
268
+
269
+ D2W_DFDF[5, 1] = F2_1 * F3_2 * lmbd + F2_2 * F3_1 * mu
270
+ D2W_DFDF[1, 5] = D2W_DFDF[5, 1]
271
+
272
+ D2W_DFDF[2, 2] = F3_1 * (F3_1 * lmbd + 2.0 * F3_1 * mu) + 2.0 * mu * e_uu + lmbd * (e_uuvvSum) + F3_2_sqr * mu
273
+
274
+ D2W_DFDF[3, 2] = F1_2 * F3_1 * lmbd + F1_1 * F3_2 * mu
275
+ D2W_DFDF[2, 3] = D2W_DFDF[3, 2]
276
+
277
+ D2W_DFDF[4, 2] = F2_2 * F3_1 * lmbd + F2_1 * F3_2 * mu
278
+ D2W_DFDF[2, 4] = D2W_DFDF[4, 2]
279
+
280
+ D2W_DFDF[5, 2] = 2.0 * mu * e_uv + F3_1 * F3_2 * lmbd + F3_1 * F3_2 * mu
281
+ D2W_DFDF[2, 5] = D2W_DFDF[5, 2]
282
+
283
+ D2W_DFDF[3, 3] = F1_2 * (F1_2 * lmbd + 2.0 * F1_2 * mu) + 2.0 * mu * e_vv + lmbd * (e_uuvvSum) + F1_1_sqr * mu
284
+
285
+ D2W_DFDF[4, 3] = F1_2 * (F2_2 * lmbd + 2.0 * F2_2 * mu) + F1_1 * F2_1 * mu
286
+ D2W_DFDF[3, 4] = D2W_DFDF[4, 3]
287
+
288
+ D2W_DFDF[5, 3] = F1_2 * (F3_2 * lmbd + 2.0 * F3_2 * mu) + F1_1 * F3_1 * mu
289
+ D2W_DFDF[3, 5] = D2W_DFDF[5, 3]
290
+
291
+ D2W_DFDF[4, 4] = F2_2 * (F2_2 * lmbd + 2.0 * F2_2 * mu) + 2.0 * mu * e_vv + lmbd * (e_uuvvSum) + F2_1_sqr * mu
292
+
293
+ D2W_DFDF[5, 4] = F2_2 * (F3_2 * lmbd + 2.0 * F3_2 * mu) + F2_1 * F3_1 * mu
294
+ D2W_DFDF[4, 5] = D2W_DFDF[5, 4]
295
+
296
+ D2W_DFDF[5, 5] = F3_2 * (F3_2 * lmbd + 2.0 * F3_2 * mu) + 2.0 * mu * e_vv + lmbd * (e_uuvvSum) + F3_1_sqr * mu
297
+
298
+ D2W_DFDF = D2W_DFDF * area
299
+
300
+ # m1s = wp.vec3(-Dm_inv1_1 - Dm_inv2_1, Dm_inv1_1, Dm_inv2_1)
301
+ # m2s = wp.vec3(-Dm_inv1_2 - Dm_inv2_2, Dm_inv1_2, Dm_inv2_2)
302
+ #
303
+ # m1 = m1s[v_order]
304
+ # m2 = m2s[v_order]
305
+
306
+ if v_order == 0:
307
+ m1 = -Dm_inv1_1 - Dm_inv2_1
308
+ m2 = -Dm_inv1_2 - Dm_inv2_2
309
+ f = wp.vec3(-F12[0, 0] - F12[0, 1], -F12[1, 0] - F12[1, 1], -F12[2, 0] - F12[2, 1])
310
+ elif v_order == 1:
311
+ m1 = Dm_inv1_1
312
+ m2 = Dm_inv1_2
313
+ f = wp.vec3(F12[0, 0], F12[1, 0], F12[2, 0])
314
+ else:
315
+ m1 = Dm_inv2_1
316
+ m2 = Dm_inv2_2
317
+ f = wp.vec3(F12[0, 1], F12[1, 1], F12[2, 1])
318
+
319
+ h = assemble_membrane_hessian(D2W_DFDF, m1, m2)
320
+
321
+ return f, h
322
+
323
+
324
+ @wp.func
325
+ def evaluate_ground_contact_force_hessian(
326
+ vertex_pos: wp.vec3,
327
+ vertex_prev_pos: wp.vec3,
328
+ particle_radius: float,
329
+ ground_normal: wp.vec3,
330
+ ground_level: float,
331
+ soft_contact_ke: float,
332
+ friction_mu: float,
333
+ friction_epsilon: float,
334
+ dt: float,
335
+ ):
336
+ penetration_depth = -(wp.dot(ground_normal, vertex_pos) + ground_level - particle_radius)
337
+
338
+ if penetration_depth > 0:
339
+ ground_contact_force_norm = penetration_depth * soft_contact_ke
340
+ ground_contact_force = ground_normal * ground_contact_force_norm
341
+ ground_contact_hessian = soft_contact_ke * wp.outer(ground_normal, ground_normal)
342
+
343
+ dx = vertex_pos - vertex_prev_pos
344
+
345
+ # friction
346
+ e0, e1 = build_orthonormal_basis(ground_normal)
347
+
348
+ T = mat32(e0[0], e1[0], e0[1], e1[1], e0[2], e1[2])
349
+
350
+ relative_translation = dx
351
+ u = wp.transpose(T) * relative_translation
352
+ eps_u = friction_epsilon * dt
353
+
354
+ friction_force, friction_hessian = compute_friction(friction_mu, ground_contact_force_norm, T, u, eps_u)
355
+ ground_contact_force = ground_contact_force + friction_force
356
+ ground_contact_hessian = ground_contact_hessian + friction_hessian
357
+ else:
358
+ ground_contact_force = wp.vec3(0.0, 0.0, 0.0)
359
+ ground_contact_hessian = wp.mat33(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
360
+
361
+ return ground_contact_force, ground_contact_hessian
362
+
363
+
364
+ @wp.func
365
+ def evaluate_body_particle_contact(
366
+ particle_index: int,
367
+ particle_pos: wp.vec3,
368
+ particle_prev_pos: wp.vec3,
369
+ contact_index: int,
370
+ soft_contact_ke: float,
371
+ friction_mu: float,
372
+ friction_epsilon: float,
373
+ particle_radius: wp.array(dtype=float),
374
+ shape_materials: ModelShapeMaterials,
375
+ shape_body: wp.array(dtype=int),
376
+ body_q: wp.array(dtype=wp.transform),
377
+ body_qd: wp.array(dtype=wp.spatial_vector),
378
+ body_com: wp.array(dtype=wp.vec3),
379
+ contact_shape: wp.array(dtype=int),
380
+ contact_body_pos: wp.array(dtype=wp.vec3),
381
+ contact_body_vel: wp.array(dtype=wp.vec3),
382
+ contact_normal: wp.array(dtype=wp.vec3),
383
+ dt: float,
384
+ ):
385
+ shape_index = contact_shape[contact_index]
386
+ body_index = shape_body[shape_index]
387
+
388
+ X_wb = wp.transform_identity()
389
+ X_com = wp.vec3()
390
+ if body_index >= 0:
391
+ X_wb = body_q[body_index]
392
+ X_com = body_com[body_index]
393
+
394
+ # body position in world space
395
+ bx = wp.transform_point(X_wb, contact_body_pos[contact_index])
396
+ r = bx - wp.transform_point(X_wb, X_com)
397
+
398
+ n = contact_normal[contact_index]
399
+
400
+ penetration_depth = -(wp.dot(n, particle_pos - bx) - particle_radius[particle_index])
401
+ if penetration_depth > 0:
402
+ body_contact_force_norm = penetration_depth * soft_contact_ke
403
+ body_contact_force = n * body_contact_force_norm
404
+ body_contact_hessian = soft_contact_ke * wp.outer(n, n)
405
+
406
+ mu = 0.5 * (friction_mu + shape_materials.mu[shape_index])
407
+
408
+ dx = particle_pos - particle_prev_pos
409
+
410
+ # body velocity
411
+ body_v_s = wp.spatial_vector()
412
+ if body_index >= 0:
413
+ body_v_s = body_qd[body_index]
414
+
415
+ body_w = wp.spatial_top(body_v_s)
416
+ body_v = wp.spatial_bottom(body_v_s)
417
+
418
+ # compute the body velocity at the particle position
419
+ bv = body_v + wp.cross(body_w, r) + wp.transform_vector(X_wb, contact_body_vel[contact_index])
420
+
421
+ relative_translation = dx - bv * dt
422
+
423
+ # friction
424
+ e0, e1 = build_orthonormal_basis(n)
425
+
426
+ T = mat32(e0[0], e1[0], e0[1], e1[1], e0[2], e1[2])
427
+
428
+ u = wp.transpose(T) * relative_translation
429
+ eps_u = friction_epsilon * dt
430
+
431
+ friction_force, friction_hessian = compute_friction(mu, body_contact_force_norm, T, u, eps_u)
432
+ body_contact_force = body_contact_force + friction_force
433
+ body_contact_hessian = body_contact_hessian + friction_hessian
434
+ else:
435
+ body_contact_force = wp.vec3(0.0, 0.0, 0.0)
436
+ body_contact_hessian = wp.mat33(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
437
+
438
+ return body_contact_force, body_contact_hessian
439
+
440
+
441
+ @wp.func
442
+ def compute_friction(mu: float, normal_contact_force: float, T: mat32, u: wp.vec2, eps_u: float):
443
+ """
444
+ Returns the friction force and hessian.
445
+ Args:
446
+ mu: Friction coefficient.
447
+ normal_contact_force: normal contact force.
448
+ T: Transformation matrix (3x2 matrix).
449
+ u: 2D displacement vector.
450
+ """
451
+ # Friction
452
+ u_norm = wp.length(u)
453
+
454
+ if u_norm > 0.0:
455
+ # IPC friction
456
+ if u_norm > eps_u:
457
+ # constant stage
458
+ f1_SF_over_x = 1.0 / u_norm
459
+ else:
460
+ # smooth transition
461
+ f1_SF_over_x = (-u_norm / eps_u + 2.0) / eps_u
462
+
463
+ force = -mu * normal_contact_force * T * (f1_SF_over_x * u)
464
+
465
+ # Different from IPC, we treat the contact normal as constant
466
+ # this significantly improves the stability
467
+ hessian = mu * normal_contact_force * T * (f1_SF_over_x * wp.identity(2, float)) * wp.transpose(T)
468
+ else:
469
+ force = wp.vec3(0.0, 0.0, 0.0)
470
+ hessian = wp.mat33(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
471
+
472
+ return force, hessian
473
+
474
+
475
+ @wp.kernel
476
+ def forward_step(
477
+ dt: float,
478
+ gravity: wp.vec3,
479
+ prev_pos: wp.array(dtype=wp.vec3),
480
+ pos: wp.array(dtype=wp.vec3),
481
+ vel: wp.array(dtype=wp.vec3),
482
+ inv_mass: wp.array(dtype=float),
483
+ external_force: wp.array(dtype=wp.vec3),
484
+ particle_flags: wp.array(dtype=wp.uint32),
485
+ inertia: wp.array(dtype=wp.vec3),
486
+ ):
487
+ particle = wp.tid()
488
+
489
+ prev_pos[particle] = pos[particle]
490
+ if not particle_flags[particle] & PARTICLE_FLAG_ACTIVE:
491
+ inertia[particle] = prev_pos[particle]
492
+ return
493
+ vel_new = vel[particle] + (gravity + external_force[particle] * inv_mass[particle]) * dt
494
+ pos[particle] = pos[particle] + vel_new * dt
495
+ inertia[particle] = pos[particle]
496
+
497
+
498
+ @wp.kernel
499
+ def VBD_solve_trimesh(
500
+ dt: float,
501
+ particle_ids_in_color: wp.array(dtype=wp.int32),
502
+ prev_pos: wp.array(dtype=wp.vec3),
503
+ pos: wp.array(dtype=wp.vec3),
504
+ pos_new: wp.array(dtype=wp.vec3),
505
+ vel: wp.array(dtype=wp.vec3),
506
+ mass: wp.array(dtype=float),
507
+ inertia: wp.array(dtype=wp.vec3),
508
+ particle_flags: wp.array(dtype=wp.uint32),
509
+ tri_indices: wp.array(dtype=wp.int32, ndim=2),
510
+ tri_poses: wp.array(dtype=wp.mat22),
511
+ tri_materials: wp.array(dtype=float, ndim=2),
512
+ tri_areas: wp.array(dtype=float),
513
+ edge_indices: wp.array(dtype=wp.int32, ndim=2),
514
+ adjacency: ForceElementAdjacencyInfo,
515
+ # contact info
516
+ # self contact
517
+ soft_contact_ke: float,
518
+ friction_mu: float,
519
+ friction_epsilon: float,
520
+ # body-particle contact
521
+ particle_radius: wp.array(dtype=float),
522
+ body_particle_contact_buffer_pre_alloc: int,
523
+ body_particle_contact_buffer: wp.array(dtype=int),
524
+ body_particle_contact_count: wp.array(dtype=int),
525
+ shape_materials: ModelShapeMaterials,
526
+ shape_body: wp.array(dtype=int),
527
+ body_q: wp.array(dtype=wp.transform),
528
+ body_qd: wp.array(dtype=wp.spatial_vector),
529
+ body_com: wp.array(dtype=wp.vec3),
530
+ contact_shape: wp.array(dtype=int),
531
+ contact_body_pos: wp.array(dtype=wp.vec3),
532
+ contact_body_vel: wp.array(dtype=wp.vec3),
533
+ contact_normal: wp.array(dtype=wp.vec3),
534
+ # ground-particle contact
535
+ has_ground: bool,
536
+ ground: wp.array(dtype=float),
537
+ ):
538
+ tid = wp.tid()
539
+
540
+ particle_index = particle_ids_in_color[tid]
541
+ # wp.printf("vId: %d\n", particle)
542
+
543
+ if not particle_flags[particle_index] & PARTICLE_FLAG_ACTIVE:
544
+ return
545
+
546
+ particle_pos = pos[particle_index]
547
+ particle_prev_pos = pos[particle_index]
548
+
549
+ dt_sqr_reciprocal = 1.0 / (dt * dt)
550
+
551
+ # inertia force and hessian
552
+ f = mass[particle_index] * (inertia[particle_index] - pos[particle_index]) * (dt_sqr_reciprocal)
553
+ h = mass[particle_index] * dt_sqr_reciprocal * wp.identity(n=3, dtype=float)
554
+
555
+ # elastic force and hessian
556
+ for i_adj_tri in range(get_vertex_num_adjacent_faces(particle_index, adjacency)):
557
+ # wp.printf("particle: %d | num_adj_faces: %d | ", particle, get_particle_num_adjacent_faces(particle, adjacency))
558
+ tri_id, particle_order = get_vertex_adjacent_face_id_order(particle_index, i_adj_tri, adjacency)
559
+
560
+ # wp.printf("i_face: %d | face id: %d | v_order: %d | ", i_adj_tri, tri_id, particle_order)
561
+ # wp.printf("face: %d %d %d\n", tri_indices[tri_id, 0], tri_indices[tri_id, 1], tri_indices[tri_id, 2], )
562
+
563
+ f_tri, h_tri = evaluate_stvk_force_hessian(
564
+ tri_id,
565
+ particle_order,
566
+ pos,
567
+ tri_indices,
568
+ tri_poses[tri_id],
569
+ tri_areas[tri_id],
570
+ tri_materials[tri_id, 0],
571
+ tri_materials[tri_id, 1],
572
+ tri_materials[tri_id, 2],
573
+ )
574
+ # compute damping
575
+ k_d = tri_materials[tri_id, 2]
576
+ h_d = h_tri * (k_d / dt)
577
+
578
+ f_d = h_d * (prev_pos[particle_index] - pos[particle_index])
579
+
580
+ f = f + f_tri + f_d
581
+ h = h + h_tri + h_d
582
+
583
+ # wp.printf("particle: %d, i_adj_tri: %d, particle_order: %d, \nforce:\n %f %f %f, \nhessian:, \n%f %f %f, \n%f %f %f, \n%f %f %f\n",
584
+ # particle, i_adj_tri, particle_order,
585
+ # f[0], f[1], f[2],
586
+ # h[0, 0], h[0, 1], h[0, 2],
587
+ # h[1, 0], h[1, 1], h[1, 2],
588
+ # h[2, 0], h[2, 1], h[2, 2],
589
+ # )
590
+
591
+ # body-particle contact
592
+ particle_contact_count = min(body_particle_contact_count[particle_index], body_particle_contact_buffer_pre_alloc)
593
+
594
+ offset = body_particle_contact_buffer_pre_alloc * particle_index
595
+ for contact_counter in range(particle_contact_count):
596
+ # the index to access body-particle data, which is size-variable and only contains active contact
597
+ contact_index = body_particle_contact_buffer[offset + contact_counter]
598
+
599
+ body_contact_force, body_contact_hessian = evaluate_body_particle_contact(
600
+ particle_index,
601
+ particle_pos,
602
+ particle_prev_pos,
603
+ contact_index,
604
+ soft_contact_ke,
605
+ friction_mu,
606
+ friction_epsilon,
607
+ particle_radius,
608
+ shape_materials,
609
+ shape_body,
610
+ body_q,
611
+ body_qd,
612
+ body_com,
613
+ contact_shape,
614
+ contact_body_pos,
615
+ contact_body_vel,
616
+ contact_normal,
617
+ dt,
618
+ )
619
+
620
+ f = f + body_contact_force
621
+ h = h + body_contact_hessian
622
+
623
+ if has_ground:
624
+ ground_normal = wp.vec3(ground[0], ground[1], ground[2])
625
+ ground_level = ground[3]
626
+ ground_contact_force, ground_contact_hessian = evaluate_ground_contact_force_hessian(
627
+ particle_pos,
628
+ particle_prev_pos,
629
+ particle_radius[particle_index],
630
+ ground_normal,
631
+ ground_level,
632
+ soft_contact_ke,
633
+ friction_mu,
634
+ friction_epsilon,
635
+ dt,
636
+ )
637
+
638
+ f = f + ground_contact_force
639
+ h = h + ground_contact_hessian
640
+
641
+ if abs(wp.determinant(h)) > 1e-5:
642
+ hInv = wp.inverse(h)
643
+ pos_new[particle_index] = particle_pos + hInv * f
644
+
645
+
646
+ @wp.kernel
647
+ def VBD_copy_particle_positions_back(
648
+ particle_ids_in_color: wp.array(dtype=wp.int32),
649
+ pos: wp.array(dtype=wp.vec3),
650
+ pos_new: wp.array(dtype=wp.vec3),
651
+ ):
652
+ tid = wp.tid()
653
+ particle = particle_ids_in_color[tid]
654
+
655
+ pos[particle] = pos_new[particle]
656
+
657
+
658
+ @wp.kernel
659
+ def update_velocity(
660
+ dt: float, prev_pos: wp.array(dtype=wp.vec3), pos: wp.array(dtype=wp.vec3), vel: wp.array(dtype=wp.vec3)
661
+ ):
662
+ particle = wp.tid()
663
+ vel[particle] = (pos[particle] - prev_pos[particle]) / dt
664
+
665
+
666
+ @wp.kernel
667
+ def convert_body_particle_contact_data_kernel(
668
+ # inputs
669
+ body_particle_contact_buffer_pre_alloc: int,
670
+ soft_contact_particle: wp.array(dtype=int),
671
+ contact_count: wp.array(dtype=int),
672
+ contact_max: int,
673
+ # outputs
674
+ body_particle_contact_buffer: wp.array(dtype=int),
675
+ body_particle_contact_count: wp.array(dtype=int),
676
+ ):
677
+ contact_index = wp.tid()
678
+ count = min(contact_max, contact_count[0])
679
+ if contact_index >= count:
680
+ return
681
+
682
+ particle_index = soft_contact_particle[contact_index]
683
+ offset = particle_index * body_particle_contact_buffer_pre_alloc
684
+
685
+ contact_counter = wp.atomic_add(body_particle_contact_count, particle_index, 1)
686
+ if contact_counter < body_particle_contact_buffer_pre_alloc:
687
+ body_particle_contact_buffer[offset + contact_counter] = contact_index
688
+
689
+
690
+ class VBDIntegrator(Integrator):
691
+ """An implicit integrator using Vertex Block Descent (VBD) for cloth simulation.
692
+
693
+ References:
694
+ - Anka He Chen, Ziheng Liu, Yin Yang, and Cem Yuksel. 2024. Vertex Block Descent. ACM Trans. Graph. 43, 4, Article 116 (July 2024), 16 pages. https://doi.org/10.1145/3658179
695
+
696
+ Note that VBDIntegrator's constructor requires a :class:`Model` object as input, so that it can do some precomputation and preallocate the space.
697
+ After construction, you must provide the same :class:`Model` object that you used that was used during construction.
698
+ Currently, you must manually provide particle coloring and assign it to `model.particle_coloring` to make VBD work.
699
+
700
+ VBDIntegrator.simulate accepts three arguments: class:`Model`, :class:`State`, and :class:`Control` (optional) objects, this time-integrator
701
+ may be used to advance the simulation state forward in time.
702
+
703
+ Example
704
+ -------
705
+
706
+ .. code-block:: python
707
+
708
+ model.particle_coloring = # load or generate particle coloring
709
+ integrator = wp.VBDIntegrator(model)
710
+
711
+ # simulation loop
712
+ for i in range(100):
713
+ state = integrator.simulate(model, state_in, state_out, dt, control)
714
+
715
+ """
716
+
717
+ def __init__(
718
+ self,
719
+ model: Model,
720
+ iterations=10,
721
+ body_particle_contact_buffer_pre_alloc=4,
722
+ friction_epsilon=1e-2,
723
+ ):
724
+ self.device = model.device
725
+ self.model = model
726
+ self.iterations = iterations
727
+
728
+ # add new attributes for VBD solve
729
+ self.particle_q_prev = wp.zeros_like(model.particle_q, device=self.device)
730
+ self.inertia = wp.zeros_like(model.particle_q, device=self.device)
731
+
732
+ self.adjacency = self.compute_force_element_adjacency(model).to(self.device)
733
+
734
+ self.body_particle_contact_buffer_pre_alloc = body_particle_contact_buffer_pre_alloc
735
+ self.body_particle_contact_buffer = wp.zeros(
736
+ (self.body_particle_contact_buffer_pre_alloc * model.particle_count,),
737
+ dtype=wp.int32,
738
+ device=self.device,
739
+ )
740
+ self.body_particle_contact_count = wp.zeros((model.particle_count,), dtype=wp.int32, device=self.device)
741
+ self.friction_epsilon = friction_epsilon
742
+
743
+ # tests
744
+ # wp.launch(kernel=_test_compute_force_element_adjacency,
745
+ # inputs=[self.adjacency, model.edge_indices, model.tri_indices],
746
+ # dim=1, device=self.device)
747
+
748
+ def compute_force_element_adjacency(self, model):
749
+ adjacency = ForceElementAdjacencyInfo()
750
+ edges_array = model.edge_indices.to("cpu")
751
+
752
+ if edges_array.size:
753
+ # build vertex-edge adjacency data
754
+ num_vertex_adjacent_edges = wp.zeros(shape=(self.model.particle_count,), dtype=wp.int32, device="cpu")
755
+
756
+ wp.launch(
757
+ kernel=self.count_num_adjacent_edges,
758
+ inputs=[edges_array, num_vertex_adjacent_edges],
759
+ dim=1,
760
+ device="cpu",
761
+ )
762
+
763
+ num_vertex_adjacent_edges = num_vertex_adjacent_edges.numpy()
764
+ vertex_adjacent_edges_offsets = np.empty(shape=(self.model.particle_count + 1,), dtype=wp.int32)
765
+ vertex_adjacent_edges_offsets[1:] = np.cumsum(2 * num_vertex_adjacent_edges)[:]
766
+ vertex_adjacent_edges_offsets[0] = 0
767
+ adjacency.v_adj_edges_offsets = wp.array(vertex_adjacent_edges_offsets, dtype=wp.int32, device="cpu")
768
+
769
+ # temporal variables to record how much adjacent edges has been filled to each vertex
770
+ vertex_adjacent_edges_fill_count = wp.zeros(
771
+ shape=(self.model.particle_count,), dtype=wp.int32, device="cpu"
772
+ )
773
+
774
+ edge_adjacency_array_size = 2 * num_vertex_adjacent_edges.sum()
775
+ # vertex order: o0: 0, o1: 1, v0: 2, v1: 3,
776
+ adjacency.v_adj_edges = wp.empty(shape=(edge_adjacency_array_size,), dtype=wp.int32, device="cpu")
777
+
778
+ wp.launch(
779
+ kernel=self.fill_adjacent_edges,
780
+ inputs=[
781
+ edges_array,
782
+ adjacency.v_adj_edges_offsets,
783
+ vertex_adjacent_edges_fill_count,
784
+ adjacency.v_adj_edges,
785
+ ],
786
+ dim=1,
787
+ device="cpu",
788
+ )
789
+ else:
790
+ adjacency.v_adj_edges_offsets = wp.empty(shape=(0,), dtype=wp.int32, device="cpu")
791
+ adjacency.v_adj_edges = wp.empty(shape=(0,), dtype=wp.int32, device="cpu")
792
+
793
+ # compute adjacent triangles
794
+
795
+ # count number of adjacent faces for each vertex
796
+ face_indices = model.tri_indices.to("cpu")
797
+ num_vertex_adjacent_faces = wp.zeros(shape=(self.model.particle_count,), dtype=wp.int32, device="cpu")
798
+ wp.launch(
799
+ kernel=self.count_num_adjacent_faces, inputs=[face_indices, num_vertex_adjacent_faces], dim=1, device="cpu"
800
+ )
801
+
802
+ # preallocate memory based on counting results
803
+ num_vertex_adjacent_faces = num_vertex_adjacent_faces.numpy()
804
+ vertex_adjacent_faces_offsets = np.empty(shape=(self.model.particle_count + 1,), dtype=wp.int32)
805
+ vertex_adjacent_faces_offsets[1:] = np.cumsum(2 * num_vertex_adjacent_faces)[:]
806
+ vertex_adjacent_faces_offsets[0] = 0
807
+ adjacency.v_adj_faces_offsets = wp.array(vertex_adjacent_faces_offsets, dtype=wp.int32, device="cpu")
808
+
809
+ vertex_adjacent_faces_fill_count = wp.zeros(shape=(self.model.particle_count,), dtype=wp.int32, device="cpu")
810
+
811
+ face_adjacency_array_size = 2 * num_vertex_adjacent_faces.sum()
812
+ # (face, vertex_order) * num_adj_faces * num_particles
813
+ # vertex order: v0: 0, v1: 1, o0: 2, v2: 3
814
+ adjacency.v_adj_faces = wp.empty(shape=(face_adjacency_array_size,), dtype=wp.int32, device="cpu")
815
+
816
+ wp.launch(
817
+ kernel=self.fill_adjacent_faces,
818
+ inputs=[
819
+ face_indices,
820
+ adjacency.v_adj_faces_offsets,
821
+ vertex_adjacent_faces_fill_count,
822
+ adjacency.v_adj_faces,
823
+ ],
824
+ dim=1,
825
+ device="cpu",
826
+ )
827
+
828
+ return adjacency
829
+
830
+ def simulate(self, model: Model, state_in: State, state_out: State, dt: float, control: Control = None):
831
+ if model is not self.model:
832
+ raise ValueError("model must be the one used to initialize VBDIntegrator")
833
+
834
+ self.convert_body_particle_contact_data()
835
+
836
+ wp.launch(
837
+ kernel=forward_step,
838
+ inputs=[
839
+ dt,
840
+ model.gravity,
841
+ self.particle_q_prev,
842
+ state_in.particle_q,
843
+ state_in.particle_qd,
844
+ self.model.particle_inv_mass,
845
+ state_in.particle_f,
846
+ self.model.particle_flags,
847
+ self.inertia,
848
+ ],
849
+ dim=self.model.particle_count,
850
+ device=self.device,
851
+ )
852
+
853
+ for _iter in range(self.iterations):
854
+ for color_counter in range(len(self.model.particle_coloring)):
855
+ wp.launch(
856
+ kernel=VBD_solve_trimesh,
857
+ inputs=[
858
+ dt,
859
+ self.model.particle_coloring[color_counter],
860
+ self.particle_q_prev,
861
+ state_in.particle_q,
862
+ state_out.particle_q,
863
+ state_in.particle_qd,
864
+ self.model.particle_mass,
865
+ self.inertia,
866
+ self.model.particle_flags,
867
+ self.model.tri_indices,
868
+ self.model.tri_poses,
869
+ self.model.tri_materials,
870
+ self.model.tri_areas,
871
+ self.model.edge_indices,
872
+ self.adjacency,
873
+ self.model.soft_contact_ke,
874
+ self.model.soft_contact_mu,
875
+ self.friction_epsilon,
876
+ # body-particle contact
877
+ self.model.particle_radius,
878
+ self.body_particle_contact_buffer_pre_alloc,
879
+ self.body_particle_contact_buffer,
880
+ self.body_particle_contact_count,
881
+ self.model.shape_materials,
882
+ self.model.shape_body,
883
+ self.model.body_q,
884
+ self.model.body_qd,
885
+ self.model.body_com,
886
+ self.model.soft_contact_shape,
887
+ self.model.soft_contact_body_pos,
888
+ self.model.soft_contact_body_vel,
889
+ self.model.soft_contact_normal,
890
+ self.model.ground,
891
+ self.model.ground_plane,
892
+ ],
893
+ dim=self.model.particle_coloring[color_counter].size,
894
+ device=self.device,
895
+ )
896
+
897
+ wp.launch(
898
+ kernel=VBD_copy_particle_positions_back,
899
+ inputs=[self.model.particle_coloring[color_counter], state_in.particle_q, state_out.particle_q],
900
+ dim=self.model.particle_coloring[color_counter].size,
901
+ device=self.device,
902
+ )
903
+
904
+ wp.launch(
905
+ kernel=update_velocity,
906
+ inputs=[dt, self.particle_q_prev, state_out.particle_q, state_out.particle_qd],
907
+ dim=self.model.particle_count,
908
+ device=self.device,
909
+ )
910
+
911
+ def convert_body_particle_contact_data(self):
912
+ self.body_particle_contact_count.zero_()
913
+
914
+ wp.launch(
915
+ kernel=convert_body_particle_contact_data_kernel,
916
+ inputs=[
917
+ self.body_particle_contact_buffer_pre_alloc,
918
+ self.model.soft_contact_particle,
919
+ self.model.soft_contact_count,
920
+ self.model.soft_contact_max,
921
+ ],
922
+ outputs=[self.body_particle_contact_buffer, self.body_particle_contact_count],
923
+ dim=self.model.soft_contact_max,
924
+ device=self.device,
925
+ )
926
+
927
+ @wp.kernel
928
+ def count_num_adjacent_edges(
929
+ edges_array: wp.array(dtype=wp.int32, ndim=2), num_vertex_adjacent_edges: wp.array(dtype=wp.int32)
930
+ ):
931
+ for edge_id in range(edges_array.shape[0]):
932
+ o0 = edges_array[edge_id, 0]
933
+ o1 = edges_array[edge_id, 1]
934
+
935
+ v0 = edges_array[edge_id, 2]
936
+ v1 = edges_array[edge_id, 3]
937
+
938
+ num_vertex_adjacent_edges[v0] = num_vertex_adjacent_edges[v0] + 1
939
+ num_vertex_adjacent_edges[v1] = num_vertex_adjacent_edges[v1] + 1
940
+
941
+ if o0 != -1:
942
+ num_vertex_adjacent_edges[o0] = num_vertex_adjacent_edges[o0] + 1
943
+ if o1 != -1:
944
+ num_vertex_adjacent_edges[o1] = num_vertex_adjacent_edges[o1] + 1
945
+
946
+ @wp.kernel
947
+ def fill_adjacent_edges(
948
+ edges_array: wp.array(dtype=wp.int32, ndim=2),
949
+ vertex_adjacent_edges_offsets: wp.array(dtype=wp.int32),
950
+ vertex_adjacent_edges_fill_count: wp.array(dtype=wp.int32),
951
+ vertex_adjacent_edges: wp.array(dtype=wp.int32),
952
+ ):
953
+ for edge_id in range(edges_array.shape[0]):
954
+ v0 = edges_array[edge_id, 2]
955
+ v1 = edges_array[edge_id, 3]
956
+
957
+ fill_count_v0 = vertex_adjacent_edges_fill_count[v0]
958
+ buffer_offset_v0 = vertex_adjacent_edges_offsets[v0]
959
+ vertex_adjacent_edges[buffer_offset_v0 + fill_count_v0 * 2] = edge_id
960
+ vertex_adjacent_edges[buffer_offset_v0 + fill_count_v0 * 2 + 1] = 2
961
+ vertex_adjacent_edges_fill_count[v0] = fill_count_v0 + 1
962
+
963
+ fill_count_v1 = vertex_adjacent_edges_fill_count[v1]
964
+ buffer_offset_v1 = vertex_adjacent_edges_offsets[v1]
965
+ vertex_adjacent_edges[buffer_offset_v1 + fill_count_v1 * 2] = edge_id
966
+ vertex_adjacent_edges[buffer_offset_v1 + fill_count_v1 * 2 + 1] = 3
967
+ vertex_adjacent_edges_fill_count[v1] = fill_count_v1 + 1
968
+
969
+ o0 = edges_array[edge_id, 2]
970
+ if o0 != -1:
971
+ fill_count_o0 = vertex_adjacent_edges_fill_count[o0]
972
+ buffer_offset_o0 = vertex_adjacent_edges_offsets[o0]
973
+ vertex_adjacent_edges[buffer_offset_o0 + fill_count_o0 * 2] = edge_id
974
+ vertex_adjacent_edges[buffer_offset_o0 + fill_count_o0 * 2 + 1] = 0
975
+ vertex_adjacent_edges_fill_count[o0] = fill_count_o0 + 1
976
+
977
+ o1 = edges_array[edge_id, 3]
978
+ if o1 != -1:
979
+ fill_count_o1 = vertex_adjacent_edges_fill_count[o1]
980
+ buffer_offset_o1 = vertex_adjacent_edges_offsets[o1]
981
+ vertex_adjacent_edges[buffer_offset_o1 + fill_count_o1 * 2] = edge_id
982
+ vertex_adjacent_edges[buffer_offset_o1 + fill_count_o1 * 2 + 1] = 1
983
+ vertex_adjacent_edges_fill_count[o1] = fill_count_o1 + 1
984
+
985
+ @wp.kernel
986
+ def count_num_adjacent_faces(
987
+ face_indices: wp.array(dtype=wp.int32, ndim=2), num_vertex_adjacent_faces: wp.array(dtype=wp.int32)
988
+ ):
989
+ for face in range(face_indices.shape[0]):
990
+ v0 = face_indices[face, 0]
991
+ v1 = face_indices[face, 1]
992
+ v2 = face_indices[face, 2]
993
+
994
+ num_vertex_adjacent_faces[v0] = num_vertex_adjacent_faces[v0] + 1
995
+ num_vertex_adjacent_faces[v1] = num_vertex_adjacent_faces[v1] + 1
996
+ num_vertex_adjacent_faces[v2] = num_vertex_adjacent_faces[v2] + 1
997
+
998
+ @wp.kernel
999
+ def fill_adjacent_faces(
1000
+ face_indices: wp.array(dtype=wp.int32, ndim=2),
1001
+ vertex_adjacent_faces_offsets: wp.array(dtype=wp.int32),
1002
+ vertex_adjacent_faces_fill_count: wp.array(dtype=wp.int32),
1003
+ vertex_adjacent_faces: wp.array(dtype=wp.int32),
1004
+ ):
1005
+ for face in range(face_indices.shape[0]):
1006
+ v0 = face_indices[face, 0]
1007
+ v1 = face_indices[face, 1]
1008
+ v2 = face_indices[face, 2]
1009
+
1010
+ fill_count_v0 = vertex_adjacent_faces_fill_count[v0]
1011
+ buffer_offset_v0 = vertex_adjacent_faces_offsets[v0]
1012
+ vertex_adjacent_faces[buffer_offset_v0 + fill_count_v0 * 2] = face
1013
+ vertex_adjacent_faces[buffer_offset_v0 + fill_count_v0 * 2 + 1] = 0
1014
+ vertex_adjacent_faces_fill_count[v0] = fill_count_v0 + 1
1015
+
1016
+ fill_count_v1 = vertex_adjacent_faces_fill_count[v1]
1017
+ buffer_offset_v1 = vertex_adjacent_faces_offsets[v1]
1018
+ vertex_adjacent_faces[buffer_offset_v1 + fill_count_v1 * 2] = face
1019
+ vertex_adjacent_faces[buffer_offset_v1 + fill_count_v1 * 2 + 1] = 1
1020
+ vertex_adjacent_faces_fill_count[v1] = fill_count_v1 + 1
1021
+
1022
+ fill_count_v2 = vertex_adjacent_faces_fill_count[v2]
1023
+ buffer_offset_v2 = vertex_adjacent_faces_offsets[v2]
1024
+ vertex_adjacent_faces[buffer_offset_v2 + fill_count_v2 * 2] = face
1025
+ vertex_adjacent_faces[buffer_offset_v2 + fill_count_v2 * 2 + 1] = 2
1026
+ vertex_adjacent_faces_fill_count[v2] = fill_count_v2 + 1