warp-lang 1.7.0__py3-none-win_amd64.whl → 1.7.1__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 (46) hide show
  1. warp/autograd.py +12 -2
  2. warp/bin/warp-clang.dll +0 -0
  3. warp/bin/warp.dll +0 -0
  4. warp/build.py +1 -1
  5. warp/builtins.py +11 -10
  6. warp/codegen.py +17 -5
  7. warp/config.py +1 -1
  8. warp/context.py +6 -0
  9. warp/examples/benchmarks/benchmark_cloth.py +1 -1
  10. warp/examples/distributed/example_jacobi_mpi.py +507 -0
  11. warp/fem/field/field.py +11 -1
  12. warp/fem/field/nodal_field.py +36 -22
  13. warp/fem/geometry/adaptive_nanogrid.py +7 -3
  14. warp/fem/geometry/trimesh.py +4 -12
  15. warp/jax_experimental/custom_call.py +14 -2
  16. warp/jax_experimental/ffi.py +5 -1
  17. warp/native/tile.h +11 -11
  18. warp/native/warp.cu +1 -1
  19. warp/render/render_opengl.py +19 -17
  20. warp/render/render_usd.py +93 -3
  21. warp/sim/collide.py +11 -9
  22. warp/sim/inertia.py +189 -156
  23. warp/sim/integrator_euler.py +3 -0
  24. warp/sim/integrator_xpbd.py +3 -0
  25. warp/sim/model.py +29 -12
  26. warp/sim/render.py +4 -0
  27. warp/stubs.py +1 -1
  28. warp/tests/assets/torus.usda +1 -1
  29. warp/tests/sim/test_collision.py +237 -206
  30. warp/tests/sim/test_inertia.py +161 -0
  31. warp/tests/sim/{flaky_test_sim_grad.py → test_sim_grad.py} +4 -0
  32. warp/tests/sim/test_xpbd.py +399 -0
  33. warp/tests/test_codegen.py +24 -3
  34. warp/tests/test_examples.py +16 -6
  35. warp/tests/test_fem.py +75 -10
  36. warp/tests/test_mat.py +370 -103
  37. warp/tests/test_quat.py +321 -137
  38. warp/tests/test_vec.py +320 -174
  39. warp/tests/tile/test_tile_load.py +97 -0
  40. warp/tests/unittest_suites.py +2 -5
  41. warp/types.py +65 -8
  42. {warp_lang-1.7.0.dist-info → warp_lang-1.7.1.dist-info}/METADATA +21 -9
  43. {warp_lang-1.7.0.dist-info → warp_lang-1.7.1.dist-info}/RECORD +46 -43
  44. {warp_lang-1.7.0.dist-info → warp_lang-1.7.1.dist-info}/WHEEL +1 -1
  45. {warp_lang-1.7.0.dist-info → warp_lang-1.7.1.dist-info}/licenses/LICENSE.md +0 -26
  46. {warp_lang-1.7.0.dist-info → warp_lang-1.7.1.dist-info}/top_level.txt +0 -0
@@ -18,7 +18,12 @@ import unittest
18
18
  import warp as wp
19
19
  import warp.examples
20
20
  import warp.sim
21
- from warp.sim.collide import *
21
+ from warp.sim.collide import (
22
+ TriMeshCollisionDetector,
23
+ init_triangle_collision_data_kernel,
24
+ triangle_closest_point,
25
+ vertex_adjacent_to_triangle,
26
+ )
22
27
  from warp.sim.integrator_euler import eval_triangles_contact
23
28
  from warp.tests.unittest_utils import *
24
29
 
@@ -260,229 +265,205 @@ def validate_edge_collisions(
260
265
  wp.expect_eq(dist >= min_dist, True)
261
266
 
262
267
 
263
- class Example:
264
- def __init__(self, device, vs, fs):
265
- self.device = device
268
+ def init_model(vs, fs, device):
269
+ vertices = [wp.vec3(v) for v in vs]
266
270
 
267
- self.input_scale_factor = 1.0
268
- self.renderer_scale_factor = 0.01
269
- vertices = [wp.vec3(v) * self.input_scale_factor for v in vs]
271
+ builder = wp.sim.ModelBuilder()
272
+ builder.add_cloth_mesh(
273
+ pos=wp.vec3(0.0, 200.0, 0.0),
274
+ rot=wp.quat_from_axis_angle(wp.vec3(1.0, 0.0, 0.0), 0.0),
275
+ scale=1.0,
276
+ vertices=vertices,
277
+ indices=fs,
278
+ vel=wp.vec3(0.0, 0.0, 0.0),
279
+ density=0.02,
280
+ tri_ke=0,
281
+ tri_ka=0,
282
+ tri_kd=0,
283
+ )
284
+ model = builder.finalize(device=device)
270
285
 
271
- builder = wp.sim.ModelBuilder()
272
- builder.add_cloth_mesh(
273
- pos=wp.vec3(0.0, 200.0, 0.0),
274
- rot=wp.quat_from_axis_angle(wp.vec3(1.0, 0.0, 0.0), 0.0),
275
- scale=1.0,
276
- vertices=vertices,
277
- indices=fs,
278
- vel=wp.vec3(0.0, 0.0, 0.0),
279
- density=0.02,
280
- tri_ke=0,
281
- tri_ka=0,
282
- tri_kd=0,
283
- )
284
- self.model = builder.finalize(device=self.device)
285
-
286
- self.collision_detector = TriMeshCollisionDetector(model=self.model)
287
-
288
- def run_vertex_triangle_test(self):
289
- rs = [1e-2, 2e-2, 5e-2, 1e-1]
290
- for query_radius in rs:
291
- self.collision_detector.vertex_triangle_collision_detection(query_radius)
292
- vertex_colliding_triangles_count_1 = self.collision_detector.vertex_colliding_triangles_count.numpy()
293
- vertex_min_dis_1 = self.collision_detector.vertex_colliding_triangles_min_dist.numpy()
294
-
295
- triangle_colliding_vertices_count_1 = self.collision_detector.triangle_colliding_vertices_count.numpy()
296
- triangle_min_dis_1 = self.collision_detector.triangle_colliding_vertices_min_dist.numpy()
297
-
298
- wp.launch(
299
- kernel=init_triangle_collision_data_kernel,
300
- inputs=[
301
- query_radius,
302
- self.collision_detector.triangle_colliding_vertices_count,
303
- self.collision_detector.triangle_colliding_vertices_min_dist,
304
- self.collision_detector.resize_flags,
305
- ],
306
- dim=self.model.tri_count,
307
- device=self.model.device,
308
- )
309
-
310
- wp.launch(
311
- kernel=vertex_triangle_collision_detection_brute_force,
312
- inputs=[
313
- query_radius,
314
- self.collision_detector.bvh_tris.id,
315
- self.collision_detector.model.particle_q,
316
- self.collision_detector.model.tri_indices,
317
- self.collision_detector.vertex_colliding_triangles,
318
- self.collision_detector.vertex_colliding_triangles_count,
319
- self.collision_detector.vertex_colliding_triangles_offsets,
320
- self.collision_detector.vertex_colliding_triangles_buffer_sizes,
321
- self.collision_detector.vertex_colliding_triangles_min_dist,
322
- self.collision_detector.triangle_colliding_vertices,
323
- self.collision_detector.triangle_colliding_vertices_count,
324
- self.collision_detector.triangle_colliding_vertices_offsets,
325
- self.collision_detector.triangle_colliding_vertices_buffer_sizes,
326
- self.collision_detector.triangle_colliding_vertices_min_dist,
327
- self.collision_detector.resize_flags,
328
- ],
329
- dim=self.model.particle_count,
330
- device=self.model.device,
331
- )
332
-
333
- vertex_colliding_triangles_count_2 = self.collision_detector.vertex_colliding_triangles_count.numpy()
334
- vertex_min_dis_2 = self.collision_detector.vertex_colliding_triangles_min_dist.numpy()
335
-
336
- triangle_colliding_vertices_count_2 = self.collision_detector.triangle_colliding_vertices_count.numpy()
337
- triangle_min_dis_2 = self.collision_detector.triangle_colliding_vertices_min_dist.numpy()
338
-
339
- assert (vertex_colliding_triangles_count_2 == vertex_colliding_triangles_count_1).all()
340
- assert (triangle_min_dis_2 == triangle_min_dis_1).all()
341
- assert (triangle_colliding_vertices_count_2 == triangle_colliding_vertices_count_1).all()
342
- assert (vertex_min_dis_2 == vertex_min_dis_1).all()
343
-
344
- wp.launch(
345
- kernel=validate_vertex_collisions,
346
- inputs=[
347
- query_radius,
348
- self.collision_detector.bvh_tris.id,
349
- self.collision_detector.model.particle_q,
350
- self.collision_detector.model.tri_indices,
351
- self.collision_detector.vertex_colliding_triangles,
352
- self.collision_detector.vertex_colliding_triangles_count,
353
- self.collision_detector.vertex_colliding_triangles_offsets,
354
- self.collision_detector.vertex_colliding_triangles_buffer_sizes,
355
- self.collision_detector.vertex_colliding_triangles_min_dist,
356
- self.collision_detector.resize_flags,
357
- ],
358
- dim=self.model.particle_count,
359
- device=self.model.device,
360
- )
361
-
362
- wp.launch(
363
- kernel=validate_triangle_collisions,
364
- inputs=[
365
- query_radius,
366
- self.collision_detector.bvh_tris.id,
367
- self.collision_detector.model.particle_q,
368
- self.collision_detector.model.tri_indices,
369
- self.collision_detector.triangle_colliding_vertices,
370
- self.collision_detector.triangle_colliding_vertices_count,
371
- self.collision_detector.triangle_colliding_vertices_offsets,
372
- self.collision_detector.triangle_colliding_vertices_buffer_sizes,
373
- self.collision_detector.triangle_colliding_vertices_min_dist,
374
- self.collision_detector.resize_flags,
375
- ],
376
- dim=self.model.tri_count,
377
- device=self.model.device,
378
- )
379
-
380
- def run_edge_edge_test(self):
381
- rs = [1e-2, 2e-2, 5e-2, 1e-1]
382
- edge_edge_parallel_epsilon = 1e-5
383
- for query_radius in rs:
384
- self.collision_detector.edge_edge_collision_detection(query_radius)
385
- edge_colliding_edges_count_1 = self.collision_detector.edge_colliding_edges_count.numpy()
386
- edge_min_dist_1 = self.collision_detector.edge_colliding_edges_min_dist.numpy()
387
-
388
- print(edge_colliding_edges_count_1)
389
-
390
- wp.launch(
391
- kernel=edge_edge_collision_detection_brute_force,
392
- inputs=[
393
- query_radius,
394
- self.collision_detector.bvh_edges.id,
395
- self.collision_detector.model.particle_q,
396
- self.collision_detector.model.edge_indices,
397
- self.collision_detector.edge_colliding_edges_offsets,
398
- self.collision_detector.edge_colliding_edges_buffer_sizes,
399
- edge_edge_parallel_epsilon,
400
- ],
401
- outputs=[
402
- self.collision_detector.edge_colliding_edges,
403
- self.collision_detector.edge_colliding_edges_count,
404
- self.collision_detector.edge_colliding_edges_min_dist,
405
- self.collision_detector.resize_flags,
406
- ],
407
- dim=self.model.edge_count,
408
- device=self.model.device,
409
- )
410
-
411
- edge_colliding_edges_count_2 = self.collision_detector.edge_colliding_edges_count.numpy()
412
- edge_min_dist_2 = self.collision_detector.edge_colliding_edges_min_dist.numpy()
413
-
414
- assert (edge_colliding_edges_count_2 == edge_colliding_edges_count_1).all()
415
- assert (edge_min_dist_1 == edge_min_dist_2).all()
416
-
417
- wp.launch(
418
- kernel=validate_edge_collisions,
419
- inputs=[
420
- query_radius,
421
- self.collision_detector.bvh_edges.id,
422
- self.collision_detector.model.particle_q,
423
- self.collision_detector.model.edge_indices,
424
- self.collision_detector.edge_colliding_edges_offsets,
425
- self.collision_detector.edge_colliding_edges_buffer_sizes,
426
- edge_edge_parallel_epsilon,
427
- ],
428
- outputs=[
429
- self.collision_detector.edge_colliding_edges,
430
- self.collision_detector.edge_colliding_edges_count,
431
- self.collision_detector.edge_colliding_edges_min_dist,
432
- self.collision_detector.resize_flags,
433
- ],
434
- dim=self.model.particle_count,
435
- device=self.model.device,
436
- )
437
-
438
- def set_points_fixed(self, model, fixed_particles):
439
- if len(fixed_particles):
440
- flags = model.particle_flags.numpy()
441
- for fixed_vertex_id in fixed_particles:
442
- flags[fixed_vertex_id] = wp.uint32(int(flags[fixed_vertex_id]) & ~int(PARTICLE_FLAG_ACTIVE))
443
-
444
- model.particle_flags = wp.array(flags, device=model.device)
286
+ collision_detector = TriMeshCollisionDetector(model=model)
445
287
 
288
+ return model, collision_detector
446
289
 
447
- @unittest.skipUnless(USD_AVAILABLE, "Requires usd-core")
448
- def test_vertex_triangle_collision(test, device):
290
+
291
+ def get_data():
449
292
  from pxr import Usd, UsdGeom
450
293
 
451
- def get_data():
452
- usd_stage = Usd.Stage.Open(os.path.join(warp.examples.get_asset_directory(), "bunny.usd"))
453
- usd_geom = UsdGeom.Mesh(usd_stage.GetPrimAtPath("/root/bunny"))
294
+ usd_stage = Usd.Stage.Open(os.path.join(warp.examples.get_asset_directory(), "bunny.usd"))
295
+ usd_geom = UsdGeom.Mesh(usd_stage.GetPrimAtPath("/root/bunny"))
296
+
297
+ vertices = np.array(usd_geom.GetPointsAttr().Get())
298
+ faces = np.array(usd_geom.GetFaceVertexIndicesAttr().Get())
454
299
 
455
- vertices = np.array(usd_geom.GetPointsAttr().Get())
456
- faces = np.array(usd_geom.GetFaceVertexIndicesAttr().Get())
300
+ return vertices, faces
457
301
 
458
- return vertices, faces
459
302
 
303
+ @unittest.skipUnless(USD_AVAILABLE, "Requires usd-core")
304
+ def test_vertex_triangle_collision(test, device):
460
305
  vertices, faces = get_data()
461
306
 
462
- sim = Example(device, vertices, faces)
463
- sim.run_vertex_triangle_test()
307
+ model, collision_detector = init_model(vertices, faces, device)
464
308
 
309
+ rs = [1e-2, 2e-2, 5e-2, 1e-1]
465
310
 
466
- @unittest.skipUnless(USD_AVAILABLE, "Requires usd-core")
467
- def test_edge_edge_collision(test, device):
468
- from pxr import Usd, UsdGeom
311
+ for query_radius in rs:
312
+ collision_detector.vertex_triangle_collision_detection(query_radius)
313
+ vertex_colliding_triangles_count_1 = collision_detector.vertex_colliding_triangles_count.numpy()
314
+ vertex_min_dis_1 = collision_detector.vertex_colliding_triangles_min_dist.numpy()
469
315
 
470
- def get_data():
471
- usd_stage = Usd.Stage.Open(os.path.join(warp.examples.get_asset_directory(), "bunny.usd"))
472
- usd_geom = UsdGeom.Mesh(usd_stage.GetPrimAtPath("/root/bunny"))
316
+ triangle_colliding_vertices_count_1 = collision_detector.triangle_colliding_vertices_count.numpy()
317
+ triangle_min_dis_1 = collision_detector.triangle_colliding_vertices_min_dist.numpy()
473
318
 
474
- vertices = np.array(usd_geom.GetPointsAttr().Get())
475
- faces = np.array(usd_geom.GetFaceVertexIndicesAttr().Get())
319
+ wp.launch(
320
+ kernel=init_triangle_collision_data_kernel,
321
+ inputs=[
322
+ query_radius,
323
+ collision_detector.triangle_colliding_vertices_count,
324
+ collision_detector.triangle_colliding_vertices_min_dist,
325
+ collision_detector.resize_flags,
326
+ ],
327
+ dim=model.tri_count,
328
+ device=device,
329
+ )
476
330
 
477
- return vertices, faces
331
+ wp.launch(
332
+ kernel=vertex_triangle_collision_detection_brute_force,
333
+ inputs=[
334
+ query_radius,
335
+ collision_detector.bvh_tris.id,
336
+ collision_detector.model.particle_q,
337
+ collision_detector.model.tri_indices,
338
+ collision_detector.vertex_colliding_triangles,
339
+ collision_detector.vertex_colliding_triangles_count,
340
+ collision_detector.vertex_colliding_triangles_offsets,
341
+ collision_detector.vertex_colliding_triangles_buffer_sizes,
342
+ collision_detector.vertex_colliding_triangles_min_dist,
343
+ collision_detector.triangle_colliding_vertices,
344
+ collision_detector.triangle_colliding_vertices_count,
345
+ collision_detector.triangle_colliding_vertices_offsets,
346
+ collision_detector.triangle_colliding_vertices_buffer_sizes,
347
+ collision_detector.triangle_colliding_vertices_min_dist,
348
+ collision_detector.resize_flags,
349
+ ],
350
+ dim=model.particle_count,
351
+ device=device,
352
+ )
353
+
354
+ vertex_colliding_triangles_count_2 = collision_detector.vertex_colliding_triangles_count.numpy()
355
+ vertex_min_dis_2 = collision_detector.vertex_colliding_triangles_min_dist.numpy()
356
+
357
+ triangle_colliding_vertices_count_2 = collision_detector.triangle_colliding_vertices_count.numpy()
358
+ triangle_min_dis_2 = collision_detector.triangle_colliding_vertices_min_dist.numpy()
359
+
360
+ assert_np_equal(vertex_colliding_triangles_count_2, vertex_colliding_triangles_count_1)
361
+ assert_np_equal(triangle_min_dis_2, triangle_min_dis_1)
362
+ assert_np_equal(triangle_colliding_vertices_count_2, triangle_colliding_vertices_count_1)
363
+ assert_np_equal(vertex_min_dis_2, vertex_min_dis_1)
364
+
365
+ wp.launch(
366
+ kernel=validate_vertex_collisions,
367
+ inputs=[
368
+ query_radius,
369
+ collision_detector.bvh_tris.id,
370
+ collision_detector.model.particle_q,
371
+ collision_detector.model.tri_indices,
372
+ collision_detector.vertex_colliding_triangles,
373
+ collision_detector.vertex_colliding_triangles_count,
374
+ collision_detector.vertex_colliding_triangles_offsets,
375
+ collision_detector.vertex_colliding_triangles_buffer_sizes,
376
+ collision_detector.vertex_colliding_triangles_min_dist,
377
+ collision_detector.resize_flags,
378
+ ],
379
+ dim=model.particle_count,
380
+ device=device,
381
+ )
382
+
383
+ wp.launch(
384
+ kernel=validate_triangle_collisions,
385
+ inputs=[
386
+ query_radius,
387
+ collision_detector.bvh_tris.id,
388
+ collision_detector.model.particle_q,
389
+ collision_detector.model.tri_indices,
390
+ collision_detector.triangle_colliding_vertices,
391
+ collision_detector.triangle_colliding_vertices_count,
392
+ collision_detector.triangle_colliding_vertices_offsets,
393
+ collision_detector.triangle_colliding_vertices_buffer_sizes,
394
+ collision_detector.triangle_colliding_vertices_min_dist,
395
+ collision_detector.resize_flags,
396
+ ],
397
+ dim=model.tri_count,
398
+ device=device,
399
+ )
400
+
401
+ wp.synchronize_device(device)
478
402
 
403
+
404
+ @unittest.skipUnless(USD_AVAILABLE, "Requires usd-core")
405
+ def test_edge_edge_collision(test, device):
479
406
  vertices, faces = get_data()
480
407
 
481
- sim = Example(device, vertices, faces)
482
- sim.run_edge_edge_test()
408
+ model, collision_detector = init_model(vertices, faces, device)
409
+
410
+ rs = [1e-2, 2e-2, 5e-2, 1e-1]
411
+ edge_edge_parallel_epsilon = 1e-5
412
+
413
+ for query_radius in rs:
414
+ collision_detector.edge_edge_collision_detection(query_radius)
415
+ edge_colliding_edges_count_1 = collision_detector.edge_colliding_edges_count.numpy()
416
+ edge_min_dist_1 = collision_detector.edge_colliding_edges_min_dist.numpy()
417
+
418
+ wp.launch(
419
+ kernel=edge_edge_collision_detection_brute_force,
420
+ inputs=[
421
+ query_radius,
422
+ collision_detector.bvh_edges.id,
423
+ collision_detector.model.particle_q,
424
+ collision_detector.model.edge_indices,
425
+ collision_detector.edge_colliding_edges_offsets,
426
+ collision_detector.edge_colliding_edges_buffer_sizes,
427
+ edge_edge_parallel_epsilon,
428
+ ],
429
+ outputs=[
430
+ collision_detector.edge_colliding_edges,
431
+ collision_detector.edge_colliding_edges_count,
432
+ collision_detector.edge_colliding_edges_min_dist,
433
+ collision_detector.resize_flags,
434
+ ],
435
+ dim=model.edge_count,
436
+ device=device,
437
+ )
483
438
 
439
+ edge_colliding_edges_count_2 = collision_detector.edge_colliding_edges_count.numpy()
440
+ edge_min_dist_2 = collision_detector.edge_colliding_edges_min_dist.numpy()
441
+
442
+ assert_np_equal(edge_colliding_edges_count_2, edge_colliding_edges_count_1)
443
+ assert_np_equal(edge_min_dist_2, edge_min_dist_1)
444
+
445
+ wp.launch(
446
+ kernel=validate_edge_collisions,
447
+ inputs=[
448
+ query_radius,
449
+ collision_detector.bvh_edges.id,
450
+ collision_detector.model.particle_q,
451
+ collision_detector.model.edge_indices,
452
+ collision_detector.edge_colliding_edges_offsets,
453
+ collision_detector.edge_colliding_edges_buffer_sizes,
454
+ edge_edge_parallel_epsilon,
455
+ ],
456
+ outputs=[
457
+ collision_detector.edge_colliding_edges,
458
+ collision_detector.edge_colliding_edges_count,
459
+ collision_detector.edge_colliding_edges_min_dist,
460
+ collision_detector.resize_flags,
461
+ ],
462
+ dim=model.particle_count,
463
+ device=device,
464
+ )
484
465
 
485
- devices = get_test_devices()
466
+ wp.synchronize_device(device)
486
467
 
487
468
 
488
469
  def test_particle_collision(test, device):
@@ -591,14 +572,64 @@ def test_particle_collision(test, device):
591
572
  test.assertTrue((np.linalg.norm(particle_f_2.numpy(), axis=1) == 0).all())
592
573
 
593
574
 
575
+ def test_mesh_ground_collision_index(test, device):
576
+ # create a mesh with 1 triangle for testing
577
+ vertices = np.array(
578
+ [
579
+ [0.0, 0.0, 0.0],
580
+ [1.0, 1.0, 0.0],
581
+ [0.5, 2.0, 0.0],
582
+ ]
583
+ )
584
+ mesh = wp.sim.Mesh(vertices=vertices, indices=[0, 1, 2])
585
+ builder = wp.sim.ModelBuilder()
586
+ # create body with nonzero mass to ensure it is not static
587
+ # and contact points will be computed
588
+ b = builder.add_body(m=1.0)
589
+ builder.add_shape_mesh(
590
+ body=b,
591
+ mesh=mesh,
592
+ has_shape_collision=False,
593
+ )
594
+ # add another mesh that is not in contact
595
+ b2 = builder.add_body(m=1.0, origin=wp.transform((0.0, 3.0, 0.0), wp.quat_identity()))
596
+ builder.add_shape_mesh(
597
+ body=b2,
598
+ mesh=mesh,
599
+ has_shape_collision=False,
600
+ )
601
+ model = builder.finalize(device=device)
602
+ test.assertEqual(model.rigid_contact_max, 6)
603
+ test.assertEqual(model.shape_contact_pair_count, 0)
604
+ test.assertEqual(model.shape_ground_contact_pair_count, 2)
605
+ model.ground = True
606
+ # ensure all the mesh vertices will be within the contact margin
607
+ model.rigid_contact_margin = 2.0
608
+ state = model.state()
609
+ wp.sim.collide(model, state)
610
+ test.assertEqual(model.rigid_contact_count.numpy()[0], 3)
611
+ tids = model.rigid_contact_tids.list()
612
+ test.assertEqual(sorted(tids), [-1, -1, -1, 0, 1, 2])
613
+ tids = [t for t in tids if t != -1]
614
+ # retrieve the mesh vertices from the contact thread indices
615
+ assert_np_equal(model.rigid_contact_point0.numpy()[:3], vertices[tids])
616
+ assert_np_equal(model.rigid_contact_point1.numpy()[:3, 0], vertices[tids, 0])
617
+ assert_np_equal(model.rigid_contact_point1.numpy()[:3, 1:], np.zeros((3, 2)))
618
+ assert_np_equal(model.rigid_contact_normal.numpy()[:3], np.tile([0.0, 1.0, 0.0], (3, 1)))
619
+
620
+
621
+ devices = get_test_devices(mode="basic")
622
+
623
+
594
624
  class TestCollision(unittest.TestCase):
595
625
  pass
596
626
 
597
627
 
598
- # add_function_test(TestCollision, "test_vertex_triangle_collision", test_vertex_triangle_collision, devices=devices)
599
- # add_function_test(TestCollision, "test_edge_edge_collision", test_vertex_triangle_collision, devices=devices)
628
+ add_function_test(TestCollision, "test_vertex_triangle_collision", test_vertex_triangle_collision, devices=devices)
629
+ add_function_test(TestCollision, "test_edge_edge_collision", test_edge_edge_collision, devices=devices)
600
630
  add_function_test(TestCollision, "test_particle_collision", test_particle_collision, devices=devices)
631
+ add_function_test(TestCollision, "test_mesh_ground_collision_index", test_mesh_ground_collision_index, devices=devices)
601
632
 
602
633
  if __name__ == "__main__":
603
634
  wp.clear_kernel_cache()
604
- unittest.main(verbosity=2)
635
+ unittest.main(verbosity=2, failfast=True)
@@ -0,0 +1,161 @@
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
+ import unittest
17
+
18
+ import numpy as np
19
+
20
+ import warp as wp
21
+ from warp.render.render_opengl import OpenGLRenderer
22
+ from warp.sim import ModelBuilder
23
+ from warp.sim.inertia import (
24
+ compute_box_inertia,
25
+ compute_mesh_inertia,
26
+ compute_sphere_inertia,
27
+ )
28
+ from warp.tests.unittest_utils import assert_np_equal
29
+
30
+
31
+ class TestInertia(unittest.TestCase):
32
+ def test_cube_mesh_inertia(self):
33
+ # Unit cube
34
+ vertices = [
35
+ [1.0, 0.0, 0.0],
36
+ [1.0, 0.0, 1.0],
37
+ [0.0, 0.0, 1.0],
38
+ [0.0, 0.0, 0.0],
39
+ [1.0, 1.0, 0.0],
40
+ [1.0, 1.0, 1.0],
41
+ [0.0, 1.0, 1.0],
42
+ [0.0, 1.0, 0.0],
43
+ ]
44
+ indices = [
45
+ [1, 2, 3],
46
+ [7, 6, 5],
47
+ [4, 5, 1],
48
+ [5, 6, 2],
49
+ [2, 6, 7],
50
+ [0, 3, 7],
51
+ [0, 1, 3],
52
+ [4, 7, 5],
53
+ [0, 4, 1],
54
+ [1, 5, 2],
55
+ [3, 2, 7],
56
+ [4, 0, 7],
57
+ ]
58
+
59
+ mass_0, com_0, I_0, volume_0 = compute_mesh_inertia(
60
+ density=1000, vertices=vertices, indices=indices, is_solid=True
61
+ )
62
+
63
+ self.assertAlmostEqual(mass_0, 1000.0, delta=1e-6)
64
+ self.assertAlmostEqual(volume_0, 1.0, delta=1e-6)
65
+ assert_np_equal(np.array(com_0), np.array([0.5, 0.5, 0.5]), tol=1e-6)
66
+
67
+ # Check against analytical inertia
68
+ mass_box, com_box, I_box = compute_box_inertia(1000.0, 1.0, 1.0, 1.0)
69
+ self.assertAlmostEqual(mass_box, mass_0, delta=1e-6)
70
+ assert_np_equal(np.array(com_box), np.zeros(3), tol=1e-6)
71
+ assert_np_equal(np.array(I_0), np.array(I_box), tol=1e-4)
72
+
73
+ # Compute hollow box inertia
74
+ mass_0_hollow, com_0_hollow, I_0_hollow, volume_0_hollow = compute_mesh_inertia(
75
+ density=1000,
76
+ vertices=vertices,
77
+ indices=indices,
78
+ is_solid=False,
79
+ thickness=0.1,
80
+ )
81
+ assert_np_equal(np.array(com_0_hollow), np.array([0.5, 0.5, 0.5]), tol=1e-6)
82
+
83
+ # Add vertex between [0.0, 0.0, 0.0] and [1.0, 0.0, 0.0]
84
+ vertices.append([0.5, 0.0, 0.0])
85
+ indices[5] = [0, 8, 7]
86
+ indices.append([8, 3, 7])
87
+ indices[6] = [0, 1, 8]
88
+ indices.append([8, 1, 3])
89
+
90
+ mass_1, com_1, I_1, volume_1 = compute_mesh_inertia(
91
+ density=1000, vertices=vertices, indices=indices, is_solid=True
92
+ )
93
+
94
+ # Inertia values should be the same as before
95
+ self.assertAlmostEqual(mass_1, mass_0, delta=1e-6)
96
+ self.assertAlmostEqual(volume_1, volume_0, delta=1e-6)
97
+ assert_np_equal(np.array(com_1), np.array([0.5, 0.5, 0.5]), tol=1e-6)
98
+ assert_np_equal(np.array(I_1), np.array(I_0), tol=1e-4)
99
+
100
+ # Compute hollow box inertia
101
+ mass_1_hollow, com_1_hollow, I_1_hollow, volume_1_hollow = compute_mesh_inertia(
102
+ density=1000,
103
+ vertices=vertices,
104
+ indices=indices,
105
+ is_solid=False,
106
+ thickness=0.1,
107
+ )
108
+
109
+ # Inertia values should be the same as before
110
+ self.assertAlmostEqual(mass_1_hollow, mass_0_hollow, delta=2e-3)
111
+ self.assertAlmostEqual(volume_1_hollow, volume_0_hollow, delta=1e-6)
112
+ assert_np_equal(np.array(com_1_hollow), np.array([0.5, 0.5, 0.5]), tol=1e-6)
113
+ assert_np_equal(np.array(I_1_hollow), np.array(I_0_hollow), tol=1e-4)
114
+
115
+ def test_sphere_mesh_inertia(self):
116
+ vertices, indices = OpenGLRenderer._create_sphere_mesh(radius=2.5, num_latitudes=500, num_longitudes=500)
117
+
118
+ offset = np.array([1.0, 2.0, 3.0], dtype=np.float32)
119
+ vertices = vertices[:, :3] + offset
120
+
121
+ mass_mesh, com_mesh, I_mesh, vol_mesh = compute_mesh_inertia(
122
+ density=1000,
123
+ vertices=vertices,
124
+ indices=indices,
125
+ is_solid=True,
126
+ )
127
+
128
+ # Check against analytical inertia
129
+ mass_sphere, _, I_sphere = compute_sphere_inertia(1000.0, 2.5)
130
+ self.assertAlmostEqual(mass_mesh, mass_sphere, delta=1e2)
131
+ assert_np_equal(np.array(com_mesh), np.array(offset), tol=2e-3)
132
+ assert_np_equal(np.array(I_mesh), np.array(I_sphere), tol=4e2)
133
+ # Check volume
134
+ self.assertAlmostEqual(vol_mesh, 4.0 / 3.0 * np.pi * 2.5**3, delta=3e-2)
135
+
136
+ def test_body_inertia(self):
137
+ vertices, indices = OpenGLRenderer._create_sphere_mesh(radius=2.5, num_latitudes=500, num_longitudes=500)
138
+
139
+ offset = np.array([1.0, 2.0, 3.0], dtype=np.float32)
140
+ vertices = vertices[:, :3] + offset
141
+
142
+ builder = ModelBuilder()
143
+ b = builder.add_body()
144
+ tf = wp.transform(wp.vec3(4.0, 5.0, 6.0), wp.quat_rpy(0.5, -0.8, 1.3))
145
+ builder.add_shape_mesh(
146
+ b,
147
+ pos=tf.p,
148
+ rot=tf.q,
149
+ mesh=wp.sim.Mesh(vertices=vertices, indices=indices),
150
+ density=1000.0,
151
+ )
152
+ transformed_com = wp.transform_point(tf, wp.vec3(*offset))
153
+ assert_np_equal(np.array(builder.body_com[0]), np.array(transformed_com), tol=2e-3)
154
+ mass_sphere, _, I_sphere = compute_sphere_inertia(1000.0, 2.5)
155
+ assert_np_equal(np.array(builder.body_inertia[0]), np.array(I_sphere), tol=4e2)
156
+ self.assertAlmostEqual(builder.body_mass[0], mass_sphere, delta=1e2)
157
+
158
+
159
+ if __name__ == "__main__":
160
+ wp.clear_kernel_cache()
161
+ unittest.main(verbosity=2)
@@ -13,6 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+ import platform
16
17
  import unittest
17
18
 
18
19
  import numpy as np
@@ -103,6 +104,9 @@ def test_sphere_pushing_on_rails(
103
104
  static_contacts=True,
104
105
  print_grad=False,
105
106
  ):
107
+ if platform.system() == "Darwin":
108
+ test.skipTest("Crashes on Mac runners")
109
+
106
110
  # Two spheres on a rail (prismatic or D6 joint), one is pushed, the other is passive.
107
111
  # The absolute distance to a target is measured and gradients are compared for
108
112
  # a push that is too far and too close.