Skip to content

Commit

Permalink
Merge pull request #339 from StanfordVL/cloth-prim-view
Browse files Browse the repository at this point in the history
Fix some bugs in cloth generation
  • Loading branch information
cgokmen authored Dec 13, 2023
2 parents 2341481 + af228de commit a18e858
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 21 deletions.
56 changes: 36 additions & 20 deletions omnigibson/systems/micro_particle_system.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uuid
import omnigibson as og
from omnigibson.macros import gm, create_module_macros
from omnigibson.prims.prim_base import BasePrim
Expand Down Expand Up @@ -43,6 +44,7 @@

# TODO: Tune these default values!
# TODO (eric): figure out whether one offset can fit all
m.MAX_CLOTH_PARTICLES = 20000 # Comes from a limitation in physx - do not increase
m.CLOTH_PARTICLE_CONTACT_OFFSET = 0.0075
m.CLOTH_REMESHING_ERROR_THRESHOLD = 0.05
m.CLOTH_STRETCH_STIFFNESS = 10000.0
Expand Down Expand Up @@ -1540,44 +1542,58 @@ def clothify_mesh_prim(cls, mesh_prim, remesh=True, particle_distance=None):
# we convert our mesh into a trimesh mesh, then export it to a temp file, then load it into pymeshlab
tm = mesh_prim_to_trimesh_mesh(mesh_prim=mesh_prim, include_normals=True, include_texcoord=True)
# Tmp file written to: {tmp_dir}/{tmp_fname}/{tmp_fname}.obj
tmp_name = f"{mesh_prim.GetName()}_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}"
tmp_name = str(uuid.uuid4())
tmp_dir = os.path.join(tempfile.gettempdir(), tmp_name)
tmp_fpath = os.path.join(tmp_dir, f"{tmp_name}.obj")
Path(tmp_dir).mkdir(parents=True, exist_ok=True)
tm.export(tmp_fpath)
ms = pymeshlab.MeshSet()
ms.load_new_mesh(tmp_fpath)

# Re-mesh based on @particle_distance - distance chosen such that at rest particles should be just touching
# each other. The 1.5 magic number comes from the particle cloth demo from omni
# Note that this means that the particles will overlap with each other, since at dist = 2 * contact_offset
# the particles are just touching each other at rest
# Start with the default particle distance
particle_distance = cls.particle_contact_offset * 2 / (1.5 * np.mean(mesh_prim.GetAttribute("xformOp:scale").Get())) \
if particle_distance is None else particle_distance
avg_edge_percentage_mismatch = 1.0
iters = 0
# Loop re-meshing until average edge percentage is within error threshold or we reach the max number of tries
while avg_edge_percentage_mismatch > m.CLOTH_REMESHING_ERROR_THRESHOLD:
ms.meshing_isotropic_explicit_remeshing(iterations=5, targetlen=pymeshlab.AbsoluteValue(particle_distance))
avg_edge_percentage_mismatch = abs(1.0 - particle_distance / ms.get_geometric_measures()["avg_edge_length"])
iters += 1
if iters > 5:

# Repetitively re-mesh at lower resolution until we have a mesh that has less than MAX_CLOTH_PARTICLES vertices
for _ in range(3):
ms = pymeshlab.MeshSet()
ms.load_new_mesh(tmp_fpath)

# Re-mesh based on @particle_distance - distance chosen such that at rest particles should be just touching
# each other. The 1.5 magic number comes from the particle cloth demo from omni
# Note that this means that the particles will overlap with each other, since at dist = 2 * contact_offset
# the particles are just touching each other at rest

avg_edge_percentage_mismatch = 1.0
# Loop re-meshing until average edge percentage is within error threshold or we reach the max number of tries
for _ in range(5):
if avg_edge_percentage_mismatch <= m.CLOTH_REMESHING_ERROR_THRESHOLD:
break

ms.meshing_isotropic_explicit_remeshing(iterations=5, targetlen=pymeshlab.AbsoluteValue(particle_distance))
avg_edge_percentage_mismatch = abs(1.0 - particle_distance / ms.get_geometric_measures()["avg_edge_length"])
else:
# Terminate anyways, but don't fail
log.warn("The generated cloth may not have evenly distributed particles.")

# Check if we have too many vertices
cm = ms.current_mesh()
if cm.vertex_number() > m.MAX_CLOTH_PARTICLES:
# We have too many vertices, so we will re-mesh again
particle_distance *= np.sqrt(2) # halve the number of vertices
else:
break
else:
raise ValueError(f"Could not remesh with less than MAX_CLOTH_PARTICLES ({m.MAX_CLOTH_PARTICLES}) vertices!")

# Re-write data to @mesh_prim
cm = ms.current_mesh()
new_face_vertex_ids = cm.face_matrix().flatten()
new_texcoord = cm.wedge_tex_coord_matrix()
new_vertices = cm.vertex_matrix()[new_face_vertex_ids]
new_normals = cm.vertex_normal_matrix()[new_face_vertex_ids]
n_vertices = len(new_vertices)
new_vertices = cm.vertex_matrix()
new_normals = cm.vertex_normal_matrix()
n_faces = len(cm.face_matrix())

mesh_prim.GetAttribute("faceVertexCounts").Set(np.ones(n_faces, dtype=int) * 3)
mesh_prim.GetAttribute("points").Set(Vt.Vec3fArray.FromNumpy(new_vertices))
mesh_prim.GetAttribute("faceVertexIndices").Set(np.arange(n_vertices))
mesh_prim.GetAttribute("faceVertexIndices").Set(new_face_vertex_ids)
mesh_prim.GetAttribute("normals").Set(Vt.Vec3fArray.FromNumpy(new_normals))
mesh_prim.GetAttribute("primvars:st").Set(Vt.Vec2fArray.FromNumpy(new_texcoord))

Expand Down
2 changes: 1 addition & 1 deletion omnigibson/utils/usd_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ def sample_mesh_keypoints(mesh_prim, n_keypoints, n_keyfaces, seed=None):
n_vertices = len(faces_flat)

# Sample vertices
unique_vertices = np.unique(faces_flat, return_index=True)[1]
unique_vertices = np.unique(faces_flat)
assert len(unique_vertices) == n_unique_vertices
keypoint_idx = np.random.choice(unique_vertices, size=n_keypoints, replace=False) if \
n_unique_vertices > n_keypoints else unique_vertices
Expand Down

0 comments on commit a18e858

Please sign in to comment.