diff --git a/genesis/engine/entities/rigid_entity/rigid_entity.py b/genesis/engine/entities/rigid_entity/rigid_entity.py index ea8d0fa5fa..9dab3781f7 100644 --- a/genesis/engine/entities/rigid_entity/rigid_entity.py +++ b/genesis/engine/entities/rigid_entity/rigid_entity.py @@ -127,24 +127,27 @@ def _load_primitive(self, morph, surface): if isinstance(morph, gs.options.morphs.Box): extents = np.array(morph.size) tmesh = mu.create_box(extents=extents) + cmesh = tmesh geom_data = extents geom_type = gs.GEOM_TYPE.BOX link_name_prefix = "box" elif isinstance(morph, gs.options.morphs.Sphere): tmesh = mu.create_sphere(radius=morph.radius) + cmesh = tmesh geom_data = np.array([morph.radius]) geom_type = gs.GEOM_TYPE.SPHERE link_name_prefix = "sphere" elif isinstance(morph, gs.options.morphs.Cylinder): tmesh = mu.create_cylinder(radius=morph.radius, height=morph.height) + cmesh = tmesh geom_data = None geom_type = gs.GEOM_TYPE.MESH link_name_prefix = "cylinder" elif isinstance(morph, gs.options.morphs.Plane): - tmesh = mu.create_plane(normal=morph.normal) + tmesh, cmesh = mu.create_plane(normal=morph.normal) geom_data = np.array(morph.normal) geom_type = gs.GEOM_TYPE.PLANE link_name_prefix = "plane" @@ -167,7 +170,7 @@ def _load_primitive(self, morph, surface): dict( contype=1, conaffinity=1, - mesh=gs.Mesh.from_trimesh(tmesh, surface=gs.surfaces.Collision()), + mesh=gs.Mesh.from_trimesh(cmesh, surface=gs.surfaces.Collision()), type=geom_type, data=geom_data, sol_params=gu.default_solver_params(), diff --git a/genesis/utils/mesh.py b/genesis/utils/mesh.py index 3e8f5937b9..4ac6fc6400 100644 --- a/genesis/utils/mesh.py +++ b/genesis/utils/mesh.py @@ -863,18 +863,33 @@ def create_plane(size=1e3, color=None, normal=(0.0, 0.0, 1.0)): mesh = trimesh.creation.box(extents=[size, size, thickness]) mesh.vertices[:, 2] -= thickness / 2 mesh.vertices = gu.transform_by_R(mesh.vertices, gu.z_up_to_R(np.asarray(normal, dtype=np.float32))) + + half = size * 0.5 + verts = np.array( + [ + [-half, -half, 0.0], + [half, -half, 0.0], + [half, half, 0.0], + [-half, -half, 0.0], + [half, half, 0.0], + [-half, half, 0.0], + ], + dtype=np.float32, + ) + faces = np.arange(6, dtype=np.int32).reshape(-1, 3) + vmesh = trimesh.Trimesh(verts, faces, process=False) + vmesh.vertices[:, 2] -= thickness / 2 + vmesh.vertices = gu.transform_by_R(vmesh.vertices, gu.z_up_to_R(np.asarray(normal, dtype=np.float32))) if color is None: # use checkerboard texture - mesh.visual = trimesh.visual.TextureVisuals( + vmesh.visual = trimesh.visual.TextureVisuals( uv=np.array( [ [0, 0], - [0, 0], - [0, size], - [0, size], - [size, 0], [size, 0], [size, size], + [0, 0], [size, size], + [0, size], ], dtype=np.float32, ), @@ -883,10 +898,11 @@ def create_plane(size=1e3, color=None, normal=(0.0, 0.0, 1.0)): ), ) else: - mesh.visual = trimesh.visual.ColorVisuals( - vertex_colors=np.tile(np.asarray(color, dtype=np.float32), (len(mesh.vertices), 1)) + vmesh.visual = trimesh.visual.ColorVisuals( + vertex_colors=np.tile(np.asarray(color, dtype=np.float32), (len(vmesh.vertices), 1)) ) - return mesh + + return vmesh, mesh def generate_tetgen_config_from_morph(morph): diff --git a/genesis/utils/terrain.py b/genesis/utils/terrain.py index 6ac535450a..8938396fcd 100644 --- a/genesis/utils/terrain.py +++ b/genesis/utils/terrain.py @@ -155,6 +155,7 @@ def parse_terrain(morph: Terrain, surface): heightfield, horizontal_scale=morph.horizontal_scale, vertical_scale=morph.vertical_scale, + surface=surface, uv_scale=morph.uv_scale if need_uvs else None, ) @@ -214,7 +215,12 @@ def fractal_terrain(terrain, levels=8, scale=1.0): def convert_heightfield_to_watertight_trimesh( - height_field_raw, horizontal_scale, vertical_scale, slope_threshold=None, uv_scale=None + height_field_raw, + horizontal_scale, + vertical_scale, + surface, + slope_threshold=None, + uv_scale=None, ): """ Adapted from Issac Gym's `convert_heightfield_to_trimesh` function. @@ -285,6 +291,20 @@ def convert_heightfield_to_watertight_trimesh( triangles_top[start + 1 : stop : 2, 1] = ind2 triangles_top[start + 1 : stop : 2, 2] = ind3 + if not surface.double_sided: + if uv_scale is not None: + uv_top = np.column_stack( + ( + (xx.flat - xx.min()) / (xx.max() - xx.min()) * uv_scale, + (yy.flat - yy.min()) / (yy.max() - yy.min()) * uv_scale, + ) + ) + visual = trimesh.visual.TextureVisuals(uv=uv_top) + else: + visual = None + + vmesh_single = trimesh.Trimesh(vertices_top, triangles_top, visual=visual, process=False) + # bottom plane z_min = np.min(vertices_top[:, 2]) - 1.0 @@ -382,15 +402,16 @@ def convert_heightfield_to_watertight_trimesh( uv_simp = uvs[idx_map] - mesh = trimesh.Trimesh( + vmesh_full = trimesh.Trimesh( v_simp, f_simp, visual=trimesh.visual.TextureVisuals(uv=uv_simp), ) else: - mesh = trimesh.Trimesh(v_simp, f_simp) + vmesh_full = trimesh.Trimesh(v_simp, f_simp) - return mesh, sdf_mesh + vmesh_out = vmesh_single if not surface.double_sided else vmesh_full + return vmesh_out, sdf_mesh def mesh_to_heightfield(