warp-lang 1.6.2__py3-none-macosx_10_13_universal2.whl → 1.7.0__py3-none-macosx_10_13_universal2.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 (179) hide show
  1. warp/__init__.py +7 -1
  2. warp/bin/libwarp-clang.dylib +0 -0
  3. warp/bin/libwarp.dylib +0 -0
  4. warp/build.py +410 -0
  5. warp/build_dll.py +6 -14
  6. warp/builtins.py +452 -362
  7. warp/codegen.py +179 -119
  8. warp/config.py +42 -6
  9. warp/context.py +490 -271
  10. warp/dlpack.py +8 -6
  11. warp/examples/assets/nonuniform.usd +0 -0
  12. warp/examples/assets/nvidia_logo.png +0 -0
  13. warp/examples/benchmarks/benchmark_tile_load_store.py +103 -0
  14. warp/examples/core/example_sample_mesh.py +300 -0
  15. warp/examples/fem/example_apic_fluid.py +1 -1
  16. warp/examples/fem/example_burgers.py +2 -2
  17. warp/examples/fem/example_deformed_geometry.py +1 -1
  18. warp/examples/fem/example_distortion_energy.py +1 -1
  19. warp/examples/fem/example_magnetostatics.py +6 -6
  20. warp/examples/fem/utils.py +9 -3
  21. warp/examples/interop/example_jax_callable.py +116 -0
  22. warp/examples/interop/example_jax_ffi_callback.py +132 -0
  23. warp/examples/interop/example_jax_kernel.py +205 -0
  24. warp/examples/optim/example_fluid_checkpoint.py +497 -0
  25. warp/examples/tile/example_tile_matmul.py +2 -4
  26. warp/fem/__init__.py +11 -1
  27. warp/fem/adaptivity.py +4 -4
  28. warp/fem/field/nodal_field.py +22 -68
  29. warp/fem/field/virtual.py +62 -23
  30. warp/fem/geometry/adaptive_nanogrid.py +9 -10
  31. warp/fem/geometry/closest_point.py +1 -1
  32. warp/fem/geometry/deformed_geometry.py +5 -2
  33. warp/fem/geometry/geometry.py +5 -0
  34. warp/fem/geometry/grid_2d.py +12 -12
  35. warp/fem/geometry/grid_3d.py +12 -15
  36. warp/fem/geometry/hexmesh.py +5 -7
  37. warp/fem/geometry/nanogrid.py +9 -11
  38. warp/fem/geometry/quadmesh.py +13 -13
  39. warp/fem/geometry/tetmesh.py +3 -4
  40. warp/fem/geometry/trimesh.py +3 -8
  41. warp/fem/integrate.py +262 -93
  42. warp/fem/linalg.py +5 -5
  43. warp/fem/quadrature/pic_quadrature.py +37 -22
  44. warp/fem/quadrature/quadrature.py +194 -25
  45. warp/fem/space/__init__.py +1 -1
  46. warp/fem/space/basis_function_space.py +4 -2
  47. warp/fem/space/basis_space.py +25 -18
  48. warp/fem/space/hexmesh_function_space.py +2 -2
  49. warp/fem/space/partition.py +6 -2
  50. warp/fem/space/quadmesh_function_space.py +8 -8
  51. warp/fem/space/shape/cube_shape_function.py +23 -23
  52. warp/fem/space/shape/square_shape_function.py +12 -12
  53. warp/fem/space/shape/triangle_shape_function.py +1 -1
  54. warp/fem/space/tetmesh_function_space.py +3 -3
  55. warp/fem/space/trimesh_function_space.py +2 -2
  56. warp/fem/utils.py +12 -6
  57. warp/jax.py +14 -1
  58. warp/jax_experimental/__init__.py +16 -0
  59. warp/{jax_experimental.py → jax_experimental/custom_call.py} +14 -27
  60. warp/jax_experimental/ffi.py +698 -0
  61. warp/jax_experimental/xla_ffi.py +602 -0
  62. warp/math.py +89 -0
  63. warp/native/array.h +13 -0
  64. warp/native/builtin.h +29 -3
  65. warp/native/bvh.cpp +3 -1
  66. warp/native/bvh.cu +42 -14
  67. warp/native/bvh.h +2 -1
  68. warp/native/clang/clang.cpp +30 -3
  69. warp/native/cuda_util.cpp +14 -0
  70. warp/native/cuda_util.h +2 -0
  71. warp/native/exports.h +68 -63
  72. warp/native/intersect.h +26 -26
  73. warp/native/intersect_adj.h +33 -33
  74. warp/native/marching.cu +1 -1
  75. warp/native/mat.h +513 -9
  76. warp/native/mesh.h +10 -10
  77. warp/native/quat.h +99 -11
  78. warp/native/rand.h +6 -0
  79. warp/native/sort.cpp +122 -59
  80. warp/native/sort.cu +152 -15
  81. warp/native/sort.h +8 -1
  82. warp/native/sparse.cpp +43 -22
  83. warp/native/sparse.cu +52 -17
  84. warp/native/svd.h +116 -0
  85. warp/native/tile.h +301 -105
  86. warp/native/tile_reduce.h +46 -3
  87. warp/native/vec.h +68 -7
  88. warp/native/volume.cpp +85 -113
  89. warp/native/volume_builder.cu +25 -10
  90. warp/native/volume_builder.h +6 -0
  91. warp/native/warp.cpp +5 -6
  92. warp/native/warp.cu +99 -10
  93. warp/native/warp.h +19 -10
  94. warp/optim/linear.py +10 -10
  95. warp/sim/articulation.py +4 -4
  96. warp/sim/collide.py +21 -10
  97. warp/sim/import_mjcf.py +449 -155
  98. warp/sim/import_urdf.py +32 -12
  99. warp/sim/integrator_euler.py +5 -5
  100. warp/sim/integrator_featherstone.py +3 -10
  101. warp/sim/integrator_vbd.py +207 -2
  102. warp/sim/integrator_xpbd.py +5 -5
  103. warp/sim/model.py +42 -13
  104. warp/sim/utils.py +2 -2
  105. warp/sparse.py +642 -555
  106. warp/stubs.py +216 -19
  107. warp/tests/__main__.py +0 -15
  108. warp/tests/cuda/__init__.py +0 -0
  109. warp/tests/{test_mempool.py → cuda/test_mempool.py} +39 -0
  110. warp/tests/{test_streams.py → cuda/test_streams.py} +71 -0
  111. warp/tests/geometry/__init__.py +0 -0
  112. warp/tests/{test_mesh_query_point.py → geometry/test_mesh_query_point.py} +66 -63
  113. warp/tests/{test_mesh_query_ray.py → geometry/test_mesh_query_ray.py} +1 -1
  114. warp/tests/{test_volume.py → geometry/test_volume.py} +41 -6
  115. warp/tests/interop/__init__.py +0 -0
  116. warp/tests/{test_dlpack.py → interop/test_dlpack.py} +28 -5
  117. warp/tests/sim/__init__.py +0 -0
  118. warp/tests/{disabled_kinematics.py → sim/disabled_kinematics.py} +9 -10
  119. warp/tests/{test_collision.py → sim/test_collision.py} +2 -2
  120. warp/tests/{test_model.py → sim/test_model.py} +40 -0
  121. warp/tests/{test_sim_kinematics.py → sim/test_sim_kinematics.py} +2 -1
  122. warp/tests/sim/test_vbd.py +597 -0
  123. warp/tests/test_bool.py +1 -1
  124. warp/tests/test_examples.py +28 -36
  125. warp/tests/test_fem.py +23 -4
  126. warp/tests/test_linear_solvers.py +0 -11
  127. warp/tests/test_mat.py +233 -79
  128. warp/tests/test_mat_scalar_ops.py +4 -4
  129. warp/tests/test_overwrite.py +0 -60
  130. warp/tests/test_quat.py +67 -46
  131. warp/tests/test_rand.py +44 -37
  132. warp/tests/test_sparse.py +47 -6
  133. warp/tests/test_spatial.py +75 -0
  134. warp/tests/test_static.py +1 -1
  135. warp/tests/test_utils.py +84 -4
  136. warp/tests/test_vec.py +46 -34
  137. warp/tests/tile/__init__.py +0 -0
  138. warp/tests/{test_tile.py → tile/test_tile.py} +136 -51
  139. warp/tests/{test_tile_load.py → tile/test_tile_load.py} +1 -1
  140. warp/tests/{test_tile_mathdx.py → tile/test_tile_mathdx.py} +9 -6
  141. warp/tests/{test_tile_mlp.py → tile/test_tile_mlp.py} +25 -14
  142. warp/tests/{test_tile_reduce.py → tile/test_tile_reduce.py} +60 -1
  143. warp/tests/{test_tile_view.py → tile/test_tile_view.py} +1 -1
  144. warp/tests/unittest_serial.py +1 -0
  145. warp/tests/unittest_suites.py +45 -59
  146. warp/tests/unittest_utils.py +2 -1
  147. warp/thirdparty/unittest_parallel.py +3 -1
  148. warp/types.py +110 -658
  149. warp/utils.py +137 -72
  150. {warp_lang-1.6.2.dist-info → warp_lang-1.7.0.dist-info}/METADATA +29 -7
  151. {warp_lang-1.6.2.dist-info → warp_lang-1.7.0.dist-info}/RECORD +172 -162
  152. {warp_lang-1.6.2.dist-info → warp_lang-1.7.0.dist-info}/WHEEL +1 -1
  153. warp/examples/optim/example_walker.py +0 -317
  154. warp/native/cutlass_gemm.cpp +0 -43
  155. warp/native/cutlass_gemm.cu +0 -382
  156. warp/tests/test_matmul.py +0 -511
  157. warp/tests/test_matmul_lite.py +0 -411
  158. warp/tests/test_vbd.py +0 -386
  159. warp/tests/unused_test_misc.py +0 -77
  160. /warp/tests/{test_async.py → cuda/test_async.py} +0 -0
  161. /warp/tests/{test_ipc.py → cuda/test_ipc.py} +0 -0
  162. /warp/tests/{test_multigpu.py → cuda/test_multigpu.py} +0 -0
  163. /warp/tests/{test_peer.py → cuda/test_peer.py} +0 -0
  164. /warp/tests/{test_pinned.py → cuda/test_pinned.py} +0 -0
  165. /warp/tests/{test_bvh.py → geometry/test_bvh.py} +0 -0
  166. /warp/tests/{test_hash_grid.py → geometry/test_hash_grid.py} +0 -0
  167. /warp/tests/{test_marching_cubes.py → geometry/test_marching_cubes.py} +0 -0
  168. /warp/tests/{test_mesh.py → geometry/test_mesh.py} +0 -0
  169. /warp/tests/{test_mesh_query_aabb.py → geometry/test_mesh_query_aabb.py} +0 -0
  170. /warp/tests/{test_volume_write.py → geometry/test_volume_write.py} +0 -0
  171. /warp/tests/{test_jax.py → interop/test_jax.py} +0 -0
  172. /warp/tests/{test_paddle.py → interop/test_paddle.py} +0 -0
  173. /warp/tests/{test_torch.py → interop/test_torch.py} +0 -0
  174. /warp/tests/{flaky_test_sim_grad.py → sim/flaky_test_sim_grad.py} +0 -0
  175. /warp/tests/{test_coloring.py → sim/test_coloring.py} +0 -0
  176. /warp/tests/{test_sim_grad_bounce_linear.py → sim/test_sim_grad_bounce_linear.py} +0 -0
  177. /warp/tests/{test_tile_shared_memory.py → tile/test_tile_shared_memory.py} +0 -0
  178. {warp_lang-1.6.2.dist-info → warp_lang-1.7.0.dist-info/licenses}/LICENSE.md +0 -0
  179. {warp_lang-1.6.2.dist-info → warp_lang-1.7.0.dist-info}/top_level.txt +0 -0
warp/sim/import_urdf.py CHANGED
@@ -20,7 +20,6 @@ from typing import Union
20
20
  import numpy as np
21
21
 
22
22
  import warp as wp
23
- import warp.sim
24
23
  from warp.sim.model import Mesh
25
24
 
26
25
 
@@ -46,6 +45,7 @@ def parse_urdf(
46
45
  joint_limit_lower=-1e6,
47
46
  joint_limit_upper=1e6,
48
47
  scale=1.0,
48
+ hide_visuals=False,
49
49
  parse_visuals_as_colliders=False,
50
50
  force_show_colliders=False,
51
51
  enable_self_collisions=True,
@@ -79,6 +79,7 @@ def parse_urdf(
79
79
  joint_limit_lower (float): The default lower joint limit if not specified in the URDF.
80
80
  joint_limit_upper (float): The default upper joint limit if not specified in the URDF.
81
81
  scale (float): The scaling factor to apply to the imported mechanism.
82
+ hide_visuals (bool): If True, hide visual shapes.
82
83
  parse_visuals_as_colliders (bool): If True, the geometry defined under the `<visual>` tags is used for collision handling instead of the `<collision>` geometries.
83
84
  force_show_colliders (bool): If True, the collision shapes are always shown, even if there are visual shapes.
84
85
  enable_self_collisions (bool): If True, self-collisions are enabled.
@@ -173,6 +174,22 @@ def parse_urdf(
173
174
  )
174
175
  shapes.append(s)
175
176
 
177
+ for capsule in geo.findall("capsule"):
178
+ s = builder.add_shape_capsule(
179
+ body=link,
180
+ pos=wp.vec3(tf.p),
181
+ rot=wp.quat(tf.q),
182
+ radius=float(capsule.get("radius") or "1") * scale,
183
+ half_height=float(capsule.get("height") or "1") * 0.5 * scale,
184
+ density=density,
185
+ up_axis=2, # capsules in URDF are aligned with z-axis
186
+ is_visible=visible,
187
+ has_ground_collision=not just_visual,
188
+ has_shape_collision=not just_visual,
189
+ **contact_vars,
190
+ )
191
+ shapes.append(s)
192
+
176
193
  for mesh in geo.findall("mesh"):
177
194
  filename = mesh.get("filename")
178
195
  if filename is None:
@@ -219,15 +236,15 @@ def parse_urdf(
219
236
  scaling = np.array([float(x) * scale for x in scaling.split()])
220
237
  if hasattr(m, "geometry"):
221
238
  # multiple meshes are contained in a scene
222
- for geom in m.geometry.values():
223
- geom_vertices = np.array(geom.vertices, dtype=np.float32) * scaling
224
- geom_faces = np.array(geom.faces.flatten(), dtype=np.int32)
225
- geom_mesh = Mesh(geom_vertices, geom_faces)
239
+ for m_geom in m.geometry.values():
240
+ m_vertices = np.array(m_geom.vertices, dtype=np.float32) * scaling
241
+ m_faces = np.array(m_geom.faces.flatten(), dtype=np.int32)
242
+ m_mesh = Mesh(m_vertices, m_faces)
226
243
  s = builder.add_shape_mesh(
227
244
  body=link,
228
245
  pos=wp.vec3(tf.p),
229
246
  rot=wp.quat(tf.q),
230
- mesh=geom_mesh,
247
+ mesh=m_mesh,
231
248
  density=density,
232
249
  is_visible=visible,
233
250
  has_ground_collision=not just_visual,
@@ -278,7 +295,7 @@ def parse_urdf(
278
295
  if parse_visuals_as_colliders:
279
296
  colliders = visuals
280
297
  else:
281
- s = parse_shapes(link, visuals, density=0.0, just_visual=True)
298
+ s = parse_shapes(link, visuals, density=0.0, just_visual=True, visible=not hide_visuals)
282
299
  visual_shapes.extend(s)
283
300
 
284
301
  show_colliders = force_show_colliders
@@ -309,10 +326,13 @@ def parse_urdf(
309
326
  I_m = rot @ wp.mat33(I_m)
310
327
  m = float(inertial.find("mass").get("value") or "0")
311
328
  builder.body_mass[link] = m
312
- builder.body_inv_mass[link] = 1.0 / m
329
+ builder.body_inv_mass[link] = 1.0 / m if m > 0.0 else 0.0
313
330
  builder.body_com[link] = com
314
331
  builder.body_inertia[link] = I_m
315
- builder.body_inv_inertia[link] = wp.inverse(I_m)
332
+ if any(x for x in I_m):
333
+ builder.body_inv_inertia[link] = wp.inverse(I_m)
334
+ else:
335
+ builder.body_inv_inertia[link] = I_m
316
336
  if m == 0.0 and ensure_nonstatic_links:
317
337
  # set the mass to something nonzero to ensure the body is dynamic
318
338
  m = static_link_mass
@@ -369,7 +389,7 @@ def parse_urdf(
369
389
 
370
390
  # topological sorting of joints because the FK solver will resolve body transforms
371
391
  # in joint order and needs the parent link transform to be resolved before the child
372
- visited = {name: False for name in link_index.keys()}
392
+ visited = dict.fromkeys(link_index.keys(), False)
373
393
  sorted_joints = []
374
394
 
375
395
  # depth-first search
@@ -520,7 +540,7 @@ def parse_urdf(
520
540
 
521
541
  builder.add_joint_d6(
522
542
  linear_axes=[
523
- warp.sim.JointAxis(
543
+ wp.sim.JointAxis(
524
544
  u,
525
545
  limit_lower=lower * scale,
526
546
  limit_upper=upper * scale,
@@ -530,7 +550,7 @@ def parse_urdf(
530
550
  target_kd=joint_damping,
531
551
  mode=joint_mode,
532
552
  ),
533
- warp.sim.JointAxis(
553
+ wp.sim.JointAxis(
534
554
  v,
535
555
  limit_lower=lower * scale,
536
556
  limit_upper=upper * scale,
@@ -559,7 +559,7 @@ def eval_tetrahedra(
559
559
  v20 = v2 - v0
560
560
  v30 = v3 - v0
561
561
 
562
- Ds = wp.mat33(x10, x20, x30)
562
+ Ds = wp.matrix_from_cols(x10, x20, x30)
563
563
  Dm = pose[tid]
564
564
 
565
565
  inv_rest_volume = wp.determinant(Dm) * 6.0
@@ -574,7 +574,7 @@ def eval_tetrahedra(
574
574
 
575
575
  # F = Xs*Xm^-1
576
576
  F = Ds * Dm
577
- dFdt = wp.mat33(v10, v20, v30) * Dm
577
+ dFdt = wp.matrix_from_cols(v10, v20, v30) * Dm
578
578
 
579
579
  col1 = wp.vec3(F[0, 0], F[1, 0], F[2, 0])
580
580
  col2 = wp.vec3(F[0, 1], F[1, 1], F[2, 1])
@@ -660,9 +660,9 @@ def eval_tetrahedra(
660
660
 
661
661
  # alpha = 1.0
662
662
 
663
- # I = wp.mat33(wp.vec3(1.0, 0.0, 0.0),
664
- # wp.vec3(0.0, 1.0, 0.0),
665
- # wp.vec3(0.0, 0.0, 1.0))
663
+ # I = wp.matrix_from_cols(wp.vec3(1.0, 0.0, 0.0),
664
+ # wp.vec3(0.0, 1.0, 0.0),
665
+ # wp.vec3(0.0, 0.0, 1.0))
666
666
 
667
667
  # P = (F + wp.transpose(F) + I*(0.0-2.0))*k_mu
668
668
  # H = P * wp.transpose(Dm)
@@ -128,7 +128,7 @@ def spatial_transform_inertia(t: wp.transform, I: wp.spatial_matrix):
128
128
  r2 = wp.quat_rotate(q, wp.vec3(0.0, 1.0, 0.0))
129
129
  r3 = wp.quat_rotate(q, wp.vec3(0.0, 0.0, 1.0))
130
130
 
131
- R = wp.mat33(r1, r2, r3)
131
+ R = wp.matrix_from_cols(r1, r2, r3)
132
132
  S = wp.skew(p) @ R
133
133
 
134
134
  T = spatial_adjoint(R, S)
@@ -298,7 +298,7 @@ def jcalc_motion(
298
298
  if type == wp.sim.JOINT_UNIVERSAL:
299
299
  axis_0 = joint_axis[axis_start + 0]
300
300
  axis_1 = joint_axis[axis_start + 1]
301
- q_off = wp.quat_from_matrix(wp.mat33(axis_0, axis_1, wp.cross(axis_0, axis_1)))
301
+ q_off = wp.quat_from_matrix(wp.matrix_from_cols(axis_0, axis_1, wp.cross(axis_0, axis_1)))
302
302
  local_0 = wp.quat_rotate(q_off, wp.vec3(1.0, 0.0, 0.0))
303
303
  local_1 = wp.quat_rotate(q_off, wp.vec3(0.0, 1.0, 0.0))
304
304
 
@@ -319,7 +319,7 @@ def jcalc_motion(
319
319
  axis_0 = joint_axis[axis_start + 0]
320
320
  axis_1 = joint_axis[axis_start + 1]
321
321
  axis_2 = joint_axis[axis_start + 2]
322
- q_off = wp.quat_from_matrix(wp.mat33(axis_0, axis_1, axis_2))
322
+ q_off = wp.quat_from_matrix(wp.matrix_from_cols(axis_0, axis_1, axis_2))
323
323
  local_0 = wp.quat_rotate(q_off, wp.vec3(1.0, 0.0, 0.0))
324
324
  local_1 = wp.quat_rotate(q_off, wp.vec3(0.0, 1.0, 0.0))
325
325
  local_2 = wp.quat_rotate(q_off, wp.vec3(0.0, 0.0, 1.0))
@@ -2066,13 +2066,6 @@ class FeatherstoneIntegrator(Integrator):
2066
2066
  ],
2067
2067
  device=model.device,
2068
2068
  )
2069
- # if wp.context.runtime.tape:
2070
- # wp.context.runtime.tape.record_func(
2071
- # backward=lambda: adj_matmul(
2072
- # a, b, c, a.grad, b.grad, c.grad, d.grad, alpha, beta, allow_tf32x3_arith, device
2073
- # ),
2074
- # arrays=[a, b, c, d],
2075
- # )
2076
2069
  # print("joint_qdd:")
2077
2070
  # print(state_aug.joint_qdd.numpy())
2078
2071
  # print("\n\n")
@@ -346,6 +346,172 @@ def evaluate_stvk_force_hessian(
346
346
  return f, h
347
347
 
348
348
 
349
+ @wp.func
350
+ def mat_vec_cross_from_3_basis(e1: wp.vec3, e2: wp.vec3, e3: wp.vec3, a: wp.vec3):
351
+ e1_cross_a = wp.cross(e1, a)
352
+ e2_cross_a = wp.cross(e2, a)
353
+ e3_cross_a = wp.cross(e3, a)
354
+
355
+ return wp.mat33(
356
+ e1_cross_a[0],
357
+ e2_cross_a[0],
358
+ e3_cross_a[0],
359
+ e1_cross_a[1],
360
+ e2_cross_a[1],
361
+ e3_cross_a[1],
362
+ e1_cross_a[2],
363
+ e2_cross_a[2],
364
+ e3_cross_a[2],
365
+ )
366
+
367
+
368
+ @wp.func
369
+ def mat_vec_cross(mat: wp.mat33, a: wp.vec3):
370
+ e1 = wp.vec3(mat[0, 0], mat[1, 0], mat[2, 0])
371
+ e2 = wp.vec3(mat[0, 1], mat[1, 1], mat[2, 1])
372
+ e3 = wp.vec3(mat[0, 2], mat[1, 2], mat[2, 2])
373
+
374
+ return mat_vec_cross_from_3_basis(e1, e2, e3, a)
375
+
376
+
377
+ @wp.func
378
+ def evaluate_dihedral_angle_based_bending_force_hessian(
379
+ bending_index: int,
380
+ v_order: int,
381
+ pos: wp.array(dtype=wp.vec3),
382
+ pos_prev: wp.array(dtype=wp.vec3),
383
+ edge_indices: wp.array(dtype=wp.int32, ndim=2),
384
+ edge_rest_angle: wp.array(dtype=float),
385
+ edge_rest_length: wp.array(dtype=float),
386
+ stiffness: float,
387
+ damping: float,
388
+ dt: float,
389
+ ):
390
+ if edge_indices[bending_index, 0] == -1 or edge_indices[bending_index, 1] == -1:
391
+ return wp.vec3(0.0), wp.mat33(0.0)
392
+
393
+ x1 = pos[edge_indices[bending_index, 0]]
394
+ x2 = pos[edge_indices[bending_index, 2]]
395
+ x3 = pos[edge_indices[bending_index, 3]]
396
+ x4 = pos[edge_indices[bending_index, 1]]
397
+
398
+ e1 = wp.vec3(1.0, 0.0, 0.0)
399
+ e2 = wp.vec3(0.0, 1.0, 0.0)
400
+ e3 = wp.vec3(0.0, 0.0, 1.0)
401
+
402
+ n1 = wp.cross((x2 - x1), (x3 - x1))
403
+ n2 = wp.cross((x3 - x4), (x2 - x4))
404
+ n1_norm = wp.length(n1)
405
+ n2_norm = wp.length(n2)
406
+
407
+ # degenerated bending edge
408
+ if n1_norm < 1.0e-6 or n2_norm < 1.0e-6:
409
+ return wp.vec3(0.0), wp.mat33(0.0)
410
+
411
+ n1_n = n1 / n1_norm
412
+ n2_n = n2 / n2_norm
413
+
414
+ # avoid the infinite gradient of acos at -1 or 1
415
+ cos_theta = wp.dot(n1_n, n2_n)
416
+ if wp.abs(cos_theta) > 0.9999:
417
+ cos_theta = 0.9999 * wp.sign(cos_theta)
418
+
419
+ angle_sign = wp.sign(wp.dot(wp.cross(n2, n1), x3 - x2))
420
+ theta = wp.acos(cos_theta) * angle_sign
421
+ rest_angle = edge_rest_angle[bending_index]
422
+
423
+ dE_dtheta = stiffness * (theta - rest_angle)
424
+
425
+ d_theta_d_cos_theta = angle_sign * (-1.0 / wp.sqrt(1.0 - cos_theta * cos_theta))
426
+ sin_theta = angle_sign * wp.sqrt(1.0 - cos_theta * cos_theta)
427
+ one_over_sin_theta = 1.0 / sin_theta
428
+ d_one_over_sin_theta_d_cos_theta = cos_theta / (sin_theta * sin_theta * sin_theta)
429
+
430
+ e_rest_len = edge_rest_length[bending_index]
431
+
432
+ if v_order == 0:
433
+ d_cos_theta_dx1 = 1.0 / n1_norm * (-wp.cross(x3 - x1, n2_n) + wp.cross(x2 - x1, n2_n))
434
+ d_one_over_sin_theta_dx1 = d_cos_theta_dx1 * d_one_over_sin_theta_d_cos_theta
435
+
436
+ d_theta_dx1 = d_theta_d_cos_theta * d_cos_theta_dx1
437
+ d2_theta_dx1_dx1 = -wp.outer(d_one_over_sin_theta_dx1, d_cos_theta_dx1)
438
+
439
+ dE_dx1 = e_rest_len * dE_dtheta * d_theta_d_cos_theta * d_cos_theta_dx1
440
+
441
+ d2_E_dx1_dx1 = (
442
+ e_rest_len * stiffness * (wp.outer(d_theta_dx1, d_theta_dx1) + (theta - rest_angle) * d2_theta_dx1_dx1)
443
+ )
444
+
445
+ bending_force = -dE_dx1
446
+ bending_hessian = d2_E_dx1_dx1
447
+ elif v_order == 1:
448
+ d_cos_theta_dx4 = 1.0 / n2_norm * (-wp.cross(x2 - x4, n1_n) + wp.cross(x3 - x4, n1_n))
449
+ d_one_over_sin_theta_dx4 = d_cos_theta_dx4 * d_one_over_sin_theta_d_cos_theta
450
+
451
+ d_theta_dx4 = d_theta_d_cos_theta * d_cos_theta_dx4
452
+ d2_theta_dx4_dx4 = -wp.outer(d_one_over_sin_theta_dx4, d_cos_theta_dx4)
453
+
454
+ dE_dx4 = e_rest_len * dE_dtheta * d_theta_d_cos_theta * d_cos_theta_dx4
455
+ d2_E_dx4_dx4 = (
456
+ e_rest_len * stiffness * (wp.outer(d_theta_dx4, d_theta_dx4) + (theta - rest_angle) * (d2_theta_dx4_dx4))
457
+ )
458
+
459
+ bending_force = -dE_dx4
460
+ bending_hessian = d2_E_dx4_dx4
461
+ elif v_order == 2:
462
+ d_cos_theta_dx2 = 1.0 / n1_norm * wp.cross(x3 - x1, n2_n) - 1.0 / n2_norm * wp.cross(x3 - x4, n1_n)
463
+ dn1_dx2 = mat_vec_cross_from_3_basis(e1, e2, e3, x3 - x1)
464
+ dn2_dx2 = -mat_vec_cross_from_3_basis(e1, e2, e3, x3 - x4)
465
+ d_one_over_sin_theta_dx2 = d_cos_theta_dx2 * d_one_over_sin_theta_d_cos_theta
466
+ d2_cos_theta_dx2_dx2 = -mat_vec_cross(dn2_dx2, (x3 - x1)) / (n1_norm * n2_norm) + mat_vec_cross(
467
+ dn1_dx2, x3 - x4
468
+ ) / (n1_norm * n2_norm)
469
+
470
+ d_theta_dx2 = d_theta_d_cos_theta * d_cos_theta_dx2
471
+ d2_theta_dx2_dx2 = (
472
+ -wp.outer(d_one_over_sin_theta_dx2, d_cos_theta_dx2) - one_over_sin_theta * d2_cos_theta_dx2_dx2
473
+ )
474
+
475
+ dE_dx2 = e_rest_len * dE_dtheta * d_theta_d_cos_theta * d_cos_theta_dx2
476
+ d2_E_dx2_dx2 = (
477
+ e_rest_len * stiffness * (wp.outer(d_theta_dx2, d_theta_dx2) + (theta - rest_angle) * d2_theta_dx2_dx2)
478
+ )
479
+
480
+ bending_force = -dE_dx2
481
+ bending_hessian = d2_E_dx2_dx2
482
+ else:
483
+ d_cos_theta_dx3 = -1.0 / n1_norm * wp.cross(x2 - x1, n2_n) + 1.0 / n2_norm * wp.cross(x2 - x4, n1_n)
484
+ dn1_dx3 = -mat_vec_cross_from_3_basis(e1, e2, e3, x2 - x1)
485
+ dn2_dx3 = mat_vec_cross_from_3_basis(e1, e2, e3, x2 - x4)
486
+ d_one_over_sin_theta_dx3 = d_cos_theta_dx3 * d_one_over_sin_theta_d_cos_theta
487
+ d2_cos_theta_dx3_dx3 = mat_vec_cross(dn2_dx3, (x2 - x1)) / (n1_norm * n2_norm) - mat_vec_cross(
488
+ dn1_dx3, x2 - x4
489
+ ) / (n1_norm * n2_norm)
490
+
491
+ d_theta_dx3 = d_theta_d_cos_theta * d_cos_theta_dx3
492
+ d2_theta_dx3_dx3 = (
493
+ -wp.outer(d_one_over_sin_theta_dx3, d_cos_theta_dx3) - one_over_sin_theta * d2_cos_theta_dx3_dx3
494
+ )
495
+
496
+ dE_dx3 = e_rest_len * dE_dtheta * d_theta_d_cos_theta * d_cos_theta_dx3
497
+
498
+ d2_E_dx3_dx3 = (
499
+ e_rest_len * stiffness * (wp.outer(d_theta_dx3, d_theta_dx3) + (theta - rest_angle) * d2_theta_dx3_dx3)
500
+ )
501
+
502
+ bending_force = -dE_dx3
503
+ bending_hessian = d2_E_dx3_dx3
504
+
505
+ displacement = pos_prev[edge_indices[bending_index, v_order]] - pos[edge_indices[bending_index, v_order]]
506
+ h_d = bending_hessian * (damping / dt)
507
+ f_d = h_d * displacement
508
+
509
+ bending_force = bending_force + f_d
510
+ bending_hessian = bending_hessian + h_d
511
+
512
+ return bending_force, bending_hessian
513
+
514
+
349
515
  @wp.func
350
516
  def evaluate_ground_contact_force_hessian(
351
517
  particle_pos: wp.vec3,
@@ -866,6 +1032,9 @@ def VBD_solve_trimesh_no_self_contact(
866
1032
  tri_materials: wp.array(dtype=float, ndim=2),
867
1033
  tri_areas: wp.array(dtype=float),
868
1034
  edge_indices: wp.array(dtype=wp.int32, ndim=2),
1035
+ edge_rest_angles: wp.array(dtype=float),
1036
+ edge_rest_length: wp.array(dtype=float),
1037
+ edge_bending_properties: wp.array(dtype=float, ndim=2),
869
1038
  adjacency: ForceElementAdjacencyInfo,
870
1039
  # contact info
871
1040
  # self contact
@@ -957,6 +1126,24 @@ def VBD_solve_trimesh_no_self_contact(
957
1126
  )
958
1127
  # fmt: on
959
1128
 
1129
+ for i_adj_edge in range(get_vertex_num_adjacent_edges(adjacency, particle_index)):
1130
+ nei_edge_index, vertex_order_on_edge = get_vertex_adjacent_edge_id_order(adjacency, particle_index, i_adj_edge)
1131
+ f_edge, h_edge = evaluate_dihedral_angle_based_bending_force_hessian(
1132
+ nei_edge_index,
1133
+ vertex_order_on_edge,
1134
+ pos,
1135
+ prev_pos,
1136
+ edge_indices,
1137
+ edge_rest_angles,
1138
+ edge_rest_length,
1139
+ edge_bending_properties[nei_edge_index, 0],
1140
+ edge_bending_properties[nei_edge_index, 1],
1141
+ dt,
1142
+ )
1143
+
1144
+ f = f + f_edge
1145
+ h = h + h_edge
1146
+
960
1147
  # body-particle contact
961
1148
  particle_contact_count = min(body_particle_contact_count[particle_index], body_particle_contact_buffer_pre_alloc)
962
1149
 
@@ -1072,6 +1259,9 @@ def VBD_solve_trimesh_with_self_contact_penetration_free(
1072
1259
  tri_materials: wp.array(dtype=float, ndim=2),
1073
1260
  tri_areas: wp.array(dtype=float),
1074
1261
  edge_indices: wp.array(dtype=wp.int32, ndim=2),
1262
+ edge_rest_angles: wp.array(dtype=float),
1263
+ edge_rest_length: wp.array(dtype=float),
1264
+ edge_bending_properties: wp.array(dtype=float, ndim=2),
1075
1265
  adjacency: ForceElementAdjacencyInfo,
1076
1266
  # contact info
1077
1267
  # self contact
@@ -1239,6 +1429,15 @@ def VBD_solve_trimesh_with_self_contact_penetration_free(
1239
1429
  for i_adj_edge in range(get_vertex_num_adjacent_edges(adjacency, particle_index)):
1240
1430
  nei_edge_index, vertex_order_on_edge = get_vertex_adjacent_edge_id_order(adjacency, particle_index, i_adj_edge)
1241
1431
  # vertex is on the edge; otherwise it only effects the bending energy n
1432
+ if edge_bending_properties[nei_edge_index, 0] != 0:
1433
+ f_edge, h_edge = evaluate_dihedral_angle_based_bending_force_hessian(
1434
+ nei_edge_index, vertex_order_on_edge, pos, pos_prev, edge_indices, edge_rest_angles, edge_rest_length,
1435
+ edge_bending_properties[nei_edge_index, 0], edge_bending_properties[nei_edge_index, 1], dt
1436
+ )
1437
+
1438
+ f = f + f_edge
1439
+ h = h + h_edge
1440
+
1242
1441
  if vertex_order_on_edge == 2 or vertex_order_on_edge == 3:
1243
1442
  # collisions of neighbor triangles
1244
1443
  if wp.static("contact_info" in VBD_DEBUG_PRINTING_OPTIONS):
@@ -1572,6 +1771,9 @@ class VBDIntegrator(Integrator):
1572
1771
  self.model.tri_materials,
1573
1772
  self.model.tri_areas,
1574
1773
  self.model.edge_indices,
1774
+ self.model.edge_rest_angle,
1775
+ self.model.edge_rest_length,
1776
+ self.model.edge_bending_properties,
1575
1777
  self.adjacency,
1576
1778
  self.model.soft_contact_ke,
1577
1779
  self.model.soft_contact_mu,
@@ -1659,6 +1861,9 @@ class VBDIntegrator(Integrator):
1659
1861
  self.model.tri_materials,
1660
1862
  self.model.tri_areas,
1661
1863
  self.model.edge_indices,
1864
+ self.model.edge_rest_angle,
1865
+ self.model.edge_rest_length,
1866
+ self.model.edge_bending_properties,
1662
1867
  self.adjacency,
1663
1868
  # self-contact
1664
1869
  self.trimesh_collision_detector.collision_info,
@@ -1783,7 +1988,7 @@ class VBDIntegrator(Integrator):
1783
1988
  vertex_adjacent_edges[buffer_offset_v1 + fill_count_v1 * 2 + 1] = 3
1784
1989
  vertex_adjacent_edges_fill_count[v1] = fill_count_v1 + 1
1785
1990
 
1786
- o0 = edges_array[edge_id, 2]
1991
+ o0 = edges_array[edge_id, 0]
1787
1992
  if o0 != -1:
1788
1993
  fill_count_o0 = vertex_adjacent_edges_fill_count[o0]
1789
1994
  buffer_offset_o0 = vertex_adjacent_edges_offsets[o0]
@@ -1791,7 +1996,7 @@ class VBDIntegrator(Integrator):
1791
1996
  vertex_adjacent_edges[buffer_offset_o0 + fill_count_o0 * 2 + 1] = 0
1792
1997
  vertex_adjacent_edges_fill_count[o0] = fill_count_o0 + 1
1793
1998
 
1794
- o1 = edges_array[edge_id, 3]
1999
+ o1 = edges_array[edge_id, 1]
1795
2000
  if o1 != -1:
1796
2001
  fill_count_o1 = vertex_adjacent_edges_fill_count[o1]
1797
2002
  buffer_offset_o1 = vertex_adjacent_edges_offsets[o1]
@@ -595,7 +595,7 @@ def solve_tetrahedra(
595
595
  x20 = x2 - x0
596
596
  x30 = x3 - x0
597
597
 
598
- Ds = wp.mat33(x10, x20, x30)
598
+ Ds = wp.matrix_from_cols(x10, x20, x30)
599
599
  Dm = rest_matrix[tid]
600
600
  inv_QT = wp.transpose(Dm)
601
601
 
@@ -627,7 +627,7 @@ def solve_tetrahedra(
627
627
  elif term == 1:
628
628
  # volume conservation
629
629
  C = wp.determinant(F) - 1.0
630
- dC = wp.mat33(wp.cross(f2, f3), wp.cross(f3, f1), wp.cross(f1, f2))
630
+ dC = wp.matrix_from_cols(wp.cross(f2, f3), wp.cross(f3, f1), wp.cross(f1, f2))
631
631
  compliance = volume_compliance
632
632
 
633
633
  if C != 0.0:
@@ -691,7 +691,7 @@ def solve_tetrahedra(
691
691
  # J = wp.determinant(F)
692
692
 
693
693
  # C_vol = J - alpha
694
- # # dCdx = wp.mat33(wp.cross(f2, f3), wp.cross(f3, f1), wp.cross(f1, f2))*wp.transpose(Dm)
694
+ # # dCdx = wp.matrix_from_cols(wp.cross(f2, f3), wp.cross(f3, f1), wp.cross(f1, f2))*wp.transpose(Dm)
695
695
 
696
696
  # # grad1 = wp.vec3(dCdx[0,0], dCdx[1,0], dCdx[2,0])
697
697
  # # grad2 = wp.vec3(dCdx[0,1], dCdx[1,1], dCdx[2,1])
@@ -766,7 +766,7 @@ def solve_tetrahedra2(
766
766
  x20 = x2 - x0
767
767
  x30 = x3 - x0
768
768
 
769
- Ds = wp.mat33(x10, x20, x30)
769
+ Ds = wp.matrix_from_cols(x10, x20, x30)
770
770
  Dm = pose[tid]
771
771
 
772
772
  inv_rest_volume = wp.determinant(Dm) * 6.0
@@ -837,7 +837,7 @@ def solve_tetrahedra2(
837
837
  J = wp.determinant(F)
838
838
 
839
839
  C_vol = J - alpha
840
- # dCdx = wp.mat33(wp.cross(f2, f3), wp.cross(f3, f1), wp.cross(f1, f2))*wp.transpose(Dm)
840
+ # dCdx = wp.matrix_from_cols(wp.cross(f2, f3), wp.cross(f3, f1), wp.cross(f1, f2))*wp.transpose(Dm)
841
841
 
842
842
  # grad1 = wp.vec3(dCdx[0,0], dCdx[1,0], dCdx[2,0])
843
843
  # grad2 = wp.vec3(dCdx[0,1], dCdx[1,1], dCdx[2,1])
warp/sim/model.py CHANGED
@@ -372,9 +372,14 @@ class Control:
372
372
  should generally be created using the :func:`Model.control()` function.
373
373
  """
374
374
 
375
- def __init__(self, model: Model):
376
- self.model: Model = model
377
- """Model to use as a reference for the control inputs."""
375
+ def __init__(self, model: Model = None):
376
+ if model:
377
+ wp.utils.warn(
378
+ "Passing arguments to Control's __init__ is deprecated\n"
379
+ "and will be disallowed in a future version. Use Control() without arguments\ninstead.",
380
+ category=DeprecationWarning,
381
+ stacklevel=2,
382
+ )
378
383
 
379
384
  self.joint_act: Optional[wp.array] = None
380
385
  """Array of joint control inputs with shape ``(joint_axis_count,)`` and type ``float``."""
@@ -388,17 +393,28 @@ class Control:
388
393
  self.muscle_activations: Optional[wp.array] = None
389
394
  """Array of muscle activations with shape ``(muscle_count,)`` and type ``float``."""
390
395
 
391
- def reset(self) -> None:
392
- """Reset the control inputs to their initial state defined in :attr:`model`."""
396
+ def clear(self) -> None:
397
+ """Reset the control inputs to zero."""
393
398
 
394
399
  if self.joint_act is not None:
395
- self.joint_act.assign(self.model.joint_act)
400
+ self.joint_act.zero_()
396
401
  if self.tri_activations is not None:
397
- self.tri_activations.assign(self.model.tri_activations)
402
+ self.tri_activations.zero_()
398
403
  if self.tet_activations is not None:
399
- self.tet_activations.assign(self.model.tet_activations)
404
+ self.tet_activations.zero_()
400
405
  if self.muscle_activations is not None:
401
- self.muscle_activations.assign(self.model.muscle_activations)
406
+ self.muscle_activations.zero_()
407
+
408
+ def reset(self) -> None:
409
+ """Reset the control inputs to zero."""
410
+
411
+ wp.utils.warn(
412
+ "Control.reset() is deprecated and will be removed\nin a future version. Use Control.clear() instead.",
413
+ category=DeprecationWarning,
414
+ stacklevel=2,
415
+ )
416
+
417
+ self.clear()
402
418
 
403
419
 
404
420
  def compute_shape_mass(type, scale, src, density, is_solid, thickness):
@@ -543,6 +559,7 @@ class Model:
543
559
 
544
560
  edge_indices (array): Bending edge indices, shape [edge_count*4], int, each row is [o0, o1, v1, v2], where v1, v2 are on the edge
545
561
  edge_rest_angle (array): Bending edge rest angle, shape [edge_count], float
562
+ edge_rest_length (array): Bending edge rest length, shape [edge_count], float
546
563
  edge_bending_properties (array): Bending edge stiffness and damping parameters, shape [edge_count, 2], float
547
564
 
548
565
  tet_indices (array): Tetrahedral element indices, shape [tet_count*4], int
@@ -716,6 +733,7 @@ class Model:
716
733
 
717
734
  self.edge_indices = None
718
735
  self.edge_rest_angle = None
736
+ self.edge_rest_length = None
719
737
  self.edge_bending_properties = None
720
738
  self.edge_constraint_lambdas = None
721
739
 
@@ -878,7 +896,7 @@ class Model:
878
896
  Returns:
879
897
  Control: The control object
880
898
  """
881
- c = Control(self)
899
+ c = Control()
882
900
  if requires_grad is None:
883
901
  requires_grad = self.requires_grad
884
902
  if clone_variables:
@@ -1205,6 +1223,7 @@ class ModelBuilder:
1205
1223
  # edges (bending)
1206
1224
  self.edge_indices = []
1207
1225
  self.edge_rest_angle = []
1226
+ self.edge_rest_length = []
1208
1227
  self.edge_bending_properties = []
1209
1228
 
1210
1229
  # tetrahedra
@@ -1384,7 +1403,11 @@ class ModelBuilder:
1384
1403
  if builder.spring_count:
1385
1404
  self.spring_indices.extend((np.array(builder.spring_indices, dtype=np.int32) + start_particle_idx).tolist())
1386
1405
  if builder.edge_count:
1387
- self.edge_indices.extend((np.array(builder.edge_indices, dtype=np.int32) + start_particle_idx).tolist())
1406
+ # Update edge indices by adding offset, preserving -1 values
1407
+ edge_indices = np.array(builder.edge_indices, dtype=np.int32)
1408
+ mask = edge_indices != -1
1409
+ edge_indices[mask] += start_particle_idx
1410
+ self.edge_indices.extend(edge_indices.tolist())
1388
1411
  if builder.tri_count:
1389
1412
  self.tri_indices.extend((np.array(builder.tri_indices, dtype=np.int32) + start_particle_idx).tolist())
1390
1413
  if builder.tet_count:
@@ -1518,6 +1541,7 @@ class ModelBuilder:
1518
1541
  "particle_radius",
1519
1542
  "particle_flags",
1520
1543
  "edge_rest_angle",
1544
+ "edge_rest_length",
1521
1545
  "edge_bending_properties",
1522
1546
  "spring_rest_length",
1523
1547
  "spring_stiffness",
@@ -3757,11 +3781,11 @@ class ModelBuilder:
3757
3781
  edge_kd = edge_kd if edge_kd is not None else self.default_edge_kd
3758
3782
 
3759
3783
  # compute rest angle
3784
+ x3 = self.particle_q[k]
3785
+ x4 = self.particle_q[l]
3760
3786
  if rest is None:
3761
3787
  x1 = self.particle_q[i]
3762
3788
  x2 = self.particle_q[j]
3763
- x3 = self.particle_q[k]
3764
- x4 = self.particle_q[l]
3765
3789
 
3766
3790
  n1 = wp.normalize(wp.cross(x3 - x1, x4 - x1))
3767
3791
  n2 = wp.normalize(wp.cross(x4 - x2, x3 - x2))
@@ -3776,6 +3800,7 @@ class ModelBuilder:
3776
3800
 
3777
3801
  self.edge_indices.append((i, j, k, l))
3778
3802
  self.edge_rest_angle.append(rest)
3803
+ self.edge_rest_length.append(wp.length(x4 - x3))
3779
3804
  self.edge_bending_properties.append((edge_ke, edge_kd))
3780
3805
 
3781
3806
  def add_edges(
@@ -3807,6 +3832,8 @@ class ModelBuilder:
3807
3832
  winding: (i, k, l), (j, l, k).
3808
3833
 
3809
3834
  """
3835
+ x3 = np.array(self.particle_q)[k]
3836
+ x4 = np.array(self.particle_q)[l]
3810
3837
  if rest is None:
3811
3838
  # compute rest angle
3812
3839
  x1 = np.array(self.particle_q)[i]
@@ -3837,6 +3864,7 @@ class ModelBuilder:
3837
3864
 
3838
3865
  self.edge_indices.extend(inds.tolist())
3839
3866
  self.edge_rest_angle.extend(rest.tolist())
3867
+ self.edge_rest_length.extend(np.linalg.norm(x4 - x3, axis=1).tolist())
3840
3868
 
3841
3869
  def init_if_none(arr, defaultValue):
3842
3870
  if arr is None:
@@ -4628,6 +4656,7 @@ class ModelBuilder:
4628
4656
 
4629
4657
  m.edge_indices = wp.array(self.edge_indices, dtype=wp.int32)
4630
4658
  m.edge_rest_angle = wp.array(self.edge_rest_angle, dtype=wp.float32, requires_grad=requires_grad)
4659
+ m.edge_rest_length = wp.array(self.edge_rest_length, dtype=wp.float32, requires_grad=requires_grad)
4631
4660
  m.edge_bending_properties = wp.array(
4632
4661
  self.edge_bending_properties, dtype=wp.float32, requires_grad=requires_grad
4633
4662
  )
warp/sim/utils.py CHANGED
@@ -65,7 +65,7 @@ def quat_decompose(q: wp.quat):
65
65
  Decompose a quaternion into a sequence of 3 rotations around x,y',z' respectively, i.e.: q = q_z''q_y'q_x.
66
66
  """
67
67
 
68
- R = wp.mat33(
68
+ R = wp.matrix_from_cols(
69
69
  wp.quat_rotate(q, wp.vec3(1.0, 0.0, 0.0)),
70
70
  wp.quat_rotate(q, wp.vec3(0.0, 1.0, 0.0)),
71
71
  wp.quat_rotate(q, wp.vec3(0.0, 0.0, 1.0)),
@@ -249,7 +249,7 @@ def transform_inertia(t: wp.transform, I: wp.spatial_matrix):
249
249
  r2 = wp.quat_rotate(q, wp.vec3(0.0, 1.0, 0.0))
250
250
  r3 = wp.quat_rotate(q, wp.vec3(0.0, 0.0, 1.0))
251
251
 
252
- R = wp.mat33(r1, r2, r3)
252
+ R = wp.matrix_from_cols(r1, r2, r3)
253
253
  S = wp.mul(wp.skew(p), R)
254
254
 
255
255
  T = wp.spatial_adjoint(R, S)