@@ -59,6 +59,83 @@ def compute_shape_radius(geo_type: int, scale: Vec3, src: Mesh | SDF | None) ->
5959 return 10.0
6060
6161
62+ def compute_aabb (vertices : nparray ) -> tuple [Vec3 , Vec3 ]:
63+ """Compute the axis-aligned bounding box of a set of vertices."""
64+ min_coords = np .min (vertices , axis = 0 )
65+ max_coords = np .max (vertices , axis = 0 )
66+ return min_coords , max_coords
67+
68+
69+ def compute_obb (vertices : nparray ) -> tuple [wp .transform , wp .vec3 ]:
70+ """Compute the oriented bounding box of a set of vertices.
71+
72+ Args:
73+ vertices: A numpy array of shape (N, 3) containing the vertex positions.
74+
75+ Returns:
76+ A tuple containing:
77+ - transform: The transform of the oriented bounding box
78+ - extents: The half-extents of the box along its principal axes
79+ """
80+ if len (vertices ) == 0 :
81+ return wp .transform_identity (), wp .vec3 (0.0 , 0.0 , 0.0 )
82+ if len (vertices ) == 1 :
83+ return wp .transform (wp .vec3 (vertices [0 ]), wp .quat_identity ()), wp .vec3 (0.0 , 0.0 , 0.0 )
84+
85+ # Center the vertices
86+ center = np .mean (vertices , axis = 0 )
87+ centered_vertices = vertices - center
88+
89+ # Compute covariance matrix with handling for degenerate cases
90+ if len (vertices ) < 3 :
91+ # For 2 points, create a line-aligned OBB
92+ direction = centered_vertices [1 ] if len (vertices ) > 1 else np .array ([1 , 0 , 0 ])
93+ direction = direction / np .linalg .norm (direction ) if np .linalg .norm (direction ) > 1e-6 else np .array ([1 , 0 , 0 ])
94+ # Create orthogonal basis
95+ if abs (direction [0 ]) < 0.9 :
96+ perpendicular = np .cross (direction , [1 , 0 , 0 ])
97+ else :
98+ perpendicular = np .cross (direction , [0 , 1 , 0 ])
99+ perpendicular = perpendicular / np .linalg .norm (perpendicular )
100+ third = np .cross (direction , perpendicular )
101+ eigenvectors = np .column_stack ([direction , perpendicular , third ])
102+ else :
103+ cov_matrix = np .cov (centered_vertices .T )
104+ eigenvalues , eigenvectors = np .linalg .eigh (cov_matrix )
105+ # Sort by eigenvalues in descending order
106+ sorted_indices = np .argsort (eigenvalues )[::- 1 ]
107+ eigenvalues = eigenvalues [sorted_indices ]
108+ eigenvectors = eigenvectors [:, sorted_indices ]
109+
110+ # Ensure right-handed coordinate system
111+ if np .linalg .det (eigenvectors ) < 0 :
112+ eigenvectors [:, 2 ] *= - 1
113+
114+ # Project vertices onto principal axes
115+ projected = centered_vertices @ eigenvectors
116+
117+ # Compute extents
118+ min_coords = np .min (projected , axis = 0 )
119+ max_coords = np .max (projected , axis = 0 )
120+ extents = (max_coords - min_coords ) / 2.0
121+
122+ # Calculate the center in the projected coordinate system
123+ center_offset = (max_coords + min_coords ) / 2.0
124+ # Transform the center offset back to the original coordinate system
125+ center = center + center_offset @ eigenvectors .T
126+
127+ # Convert rotation matrix to quaternion
128+ # The rotation matrix should transform from the original coordinate system to the principal axes
129+ # eigenvectors is the rotation matrix from original to principal axes
130+ rotation_matrix = eigenvectors
131+
132+ # Convert to quaternion using Warp's quat_from_matrix function
133+ # First convert numpy array to Warp matrix
134+ orientation = wp .quat_from_matrix (wp .mat33 (rotation_matrix ))
135+
136+ return wp .transform (wp .vec3 (center ), orientation ), wp .vec3 (extents )
137+
138+
62139def load_mesh (filename : str , method : str | None = None ):
63140 """
64141 Loads a 3D triangular surface mesh from a file.
@@ -339,6 +416,13 @@ def remesh_convex_hull(vertices, maxhullvert: int = 0):
339416 if np .dot (normal , a - centre ) < 0 :
340417 faces [i ] = tri [[0 , 2 , 1 ]]
341418
419+ # trim vertices to only those that are used in the faces
420+ unique_verts = np .unique (faces .flatten ())
421+ verts = verts [unique_verts ]
422+ # update face indices to use the new vertex indices
423+ mapping = {v : i for i , v in enumerate (unique_verts )}
424+ faces = np .array ([mapping [v ] for v in faces .flatten ()], dtype = np .int32 ).reshape (faces .shape )
425+
342426 return verts , faces
343427
344428
@@ -384,23 +468,105 @@ def remesh(
384468 return new_vertices , new_faces
385469
386470
387- def remesh_mesh (mesh : Mesh , method : RemeshingMethod = "quadratic" , recompute_inertia = False , ** remeshing_kwargs ) -> Mesh :
471+ def remesh_mesh (
472+ mesh : Mesh ,
473+ method : RemeshingMethod = "quadratic" ,
474+ recompute_inertia : bool = False ,
475+ inplace : bool = False ,
476+ ** remeshing_kwargs ,
477+ ) -> Mesh :
388478 """Remesh a mesh using the specified method.
389479 Args:
390480 mesh: The mesh to remesh.
391481 method: The remeshing method to use. One of "ftetwild", "quadratic", "convex_hull", or "alphashape".
392482 recompute_inertia: Whether to recompute the inertia of the mesh.
483+ inplace: Whether to modify the mesh in place or return a new mesh.
393484 **remeshing_kwargs: Additional keyword arguments passed to the remeshing function.
394485 Returns:
395486 The remeshed mesh.
396487 """
397488 if method == "convex_hull" :
398489 remeshing_kwargs ["maxhullvert" ] = mesh .maxhullvert
399- mesh .vertices , mesh .indices = remesh (mesh .vertices , mesh .indices .reshape (- 1 , 3 ), method = method , ** remeshing_kwargs )
400- mesh .indices = mesh .indices .flatten ()
401- if recompute_inertia :
402- mesh .mass , mesh .com , mesh .I , _ = compute_mesh_inertia (1.0 , mesh .vertices , mesh .indices , is_solid = mesh .is_solid )
490+ vertices , indices = remesh (mesh .vertices , mesh .indices .reshape (- 1 , 3 ), method = method , ** remeshing_kwargs )
491+ if inplace :
492+ mesh .vertices = vertices
493+ mesh .indices = indices .flatten ()
494+ if recompute_inertia :
495+ mesh .mass , mesh .com , mesh .I , _ = compute_mesh_inertia (1.0 , vertices , indices , is_solid = mesh .is_solid )
496+ else :
497+ return mesh .copy (vertices = vertices , indices = indices , recompute_inertia = recompute_inertia )
403498 return mesh
404499
405500
501+ def create_box_mesh (half_extents : Vec3 ) -> tuple [nparray , nparray ]:
502+ x_extent , y_extent , z_extent = half_extents
503+ vertices = np .array (
504+ [
505+ [- x_extent , - y_extent , - z_extent ],
506+ [x_extent , - y_extent , - z_extent ],
507+ [x_extent , y_extent , - z_extent ],
508+ [- x_extent , y_extent , - z_extent ],
509+ [- x_extent , - y_extent , z_extent ],
510+ [x_extent , - y_extent , z_extent ],
511+ [x_extent , y_extent , z_extent ],
512+ [- x_extent , y_extent , z_extent ],
513+ ],
514+ dtype = np .float32 ,
515+ )
516+ indices = np .array (
517+ [
518+ # Bottom face (z = -z_extent)
519+ 0 ,
520+ 2 ,
521+ 1 ,
522+ 0 ,
523+ 3 ,
524+ 2 ,
525+ # Top face (z = z_extent)
526+ 4 ,
527+ 5 ,
528+ 6 ,
529+ 4 ,
530+ 6 ,
531+ 7 ,
532+ # Front face (y = -y_extent)
533+ 0 ,
534+ 1 ,
535+ 5 ,
536+ 0 ,
537+ 5 ,
538+ 4 ,
539+ # Back face (y = y_extent)
540+ 2 ,
541+ 3 ,
542+ 7 ,
543+ 2 ,
544+ 7 ,
545+ 6 ,
546+ # Left face (x = -x_extent)
547+ 0 ,
548+ 4 ,
549+ 7 ,
550+ 0 ,
551+ 7 ,
552+ 3 ,
553+ # Right face (x = x_extent)
554+ 1 ,
555+ 2 ,
556+ 6 ,
557+ 1 ,
558+ 6 ,
559+ 5 ,
560+ ],
561+ dtype = np .int32 ,
562+ )
563+ return vertices , indices
564+
565+
566+ def transform_points (points : nparray , transform : wp .transform , scale : Vec3 | None = None ) -> nparray :
567+ if scale is not None :
568+ points = points * np .array (scale , dtype = np .float32 )
569+ return points @ np .array (wp .quat_to_matrix (transform .q )).reshape (3 , 3 ) + transform .p
570+
571+
406572__all__ = ["compute_shape_radius" , "load_mesh" , "visualize_meshes" ]
0 commit comments