def smooth(self, vertices: np.ndarray, faces: np.ndarray, diffusion: ChunkGrid[float], original_mesh: Meshes, max_iteration=50): assert max_iteration > -1 change = True iteration = 0 loss_mesh = original_mesh.clone() smooth_verts = loss_mesh.verts_packed().clone() neighbors = self.compute_neighbors(vertices, faces) neighbor_len = torch.IntTensor( [len(neighbors[i]) for i in range(len(vertices))]) neighbor_valences = torch.FloatTensor([ sum([1 / neighbor_len[n] for n in neighbors[i]]) for i in range(len(vertices)) ]) d = 1 + 1 / neighbor_len * neighbor_valences difference_max = torch.as_tensor(diffusion.get_values(vertices) + 1) while change and iteration < max_iteration: iteration += 1 change = False for i in range(2): with torch.no_grad(): L = loss_mesh.laplacian_packed() loss = L.mm(loss_mesh.verts_packed()) if i == 0: loss_mesh = Meshes([loss], loss_mesh.faces_list()) # new_vals = smooth_verts - (1/d).unsqueeze(1) * loss # difference = torch.sqrt(torch.sum(torch.pow(original_mesh.verts_packed() - new_vals, 2), dim=1)) new_val = smooth_verts - (loss.T * (1 / d)).T differences = torch.linalg.norm(original_mesh.verts_packed() - new_val, dim=1) cond = differences < difference_max if torch.any(cond): smooth_verts[cond] = new_val[cond] change = True # for i, v in enumerate(vertices): # new_val = smooth_verts[i] - (1 / d[i] * loss[i]) # difference = torch.dist(original_mesh.verts_packed()[i], new_val) # if difference < difference_max[i]: # smooth_verts[i] = new_val # change = True loss_mesh = Meshes([smooth_verts], original_mesh.faces_list()) return smooth_verts
def save( self, data: Meshes, path: Union[str, Path], path_manager: PathManager, binary: Optional[bool], decimal_places: Optional[int] = None, **kwargs, ) -> bool: if not endswith(path, self.known_suffixes): return False verts = data.verts_list()[0] faces = data.faces_list()[0] if data.has_verts_normals(): verts_normals = data.verts_normals_list()[0] else: verts_normals = None if isinstance(data.textures, TexturesVertex): mesh_verts_colors = data.textures.verts_features_list()[0] n_colors = mesh_verts_colors.shape[1] if n_colors == 3: verts_colors = mesh_verts_colors else: warnings.warn( f"Texture will not be saved as it has {n_colors} colors, not 3." ) verts_colors = None else: verts_colors = None with _open_file(path, path_manager, "wb") as f: _save_ply( f=f, verts=verts, faces=faces, verts_colors=verts_colors, verts_normals=verts_normals, ascii=binary is False, decimal_places=decimal_places, ) return True
def taubin_smoothing(meshes: Meshes, lambd: float = 0.53, mu: float = -0.53, num_iter: int = 10) -> Meshes: """ Taubin smoothing [1] is an iterative smoothing operator for meshes. At each iteration verts := (1 - λ) * verts + λ * L * verts verts := (1 - μ) * verts + μ * L * verts This function returns a new mesh with smoothed vertices. Args: meshes: Meshes input to be smoothed lambd, mu: float parameters for Taubin smoothing, lambd > 0, mu < 0 num_iter: number of iterations to execute smoothing Returns: mesh: Smoothed input Meshes [1] Curve and Surface Smoothing without Shrinkage, Gabriel Taubin, ICCV 1997 """ verts = meshes.verts_packed() # V x 3 edges = meshes.edges_packed() # E x 3 for _ in range(num_iter): L = norm_laplacian(verts, edges) total_weight = torch.sparse.sum(L, dim=1).to_dense().view(-1, 1) verts = (1 - lambd) * verts + lambd * torch.mm(L, verts) / total_weight # pyre-ignore L = norm_laplacian(verts, edges) total_weight = torch.sparse.sum(L, dim=1).to_dense().view(-1, 1) verts = (1 - mu) * verts + mu * torch.mm(L, verts) / total_weight verts_list = struct_utils.packed_to_list( verts, meshes.num_verts_per_mesh().tolist()) mesh = Meshes(verts=list(verts_list), faces=meshes.faces_list()) return mesh
def save( self, data: Meshes, path: Union[str, Path], path_manager: PathManager, binary: Optional[bool], decimal_places: Optional[int] = None, **kwargs, ) -> bool: if not endswith(path, self.known_suffixes): return False verts = data.verts_list()[0] faces = data.faces_list()[0] save_obj( f=path, verts=verts, faces=faces, decimal_places=decimal_places, path_manager=path_manager, ) return True
def save( self, data: Meshes, path: PathOrStr, path_manager: PathManager, binary: Optional[bool], decimal_places: Optional[int] = None, **kwargs, ) -> bool: if not endswith(path, self.known_suffixes): return False verts = data.verts_list()[0] faces = data.faces_list()[0] if isinstance(data.textures, TexturesVertex): [verts_colors] = data.textures.verts_features_list() else: verts_colors = None faces_colors = None if isinstance(data.textures, TexturesAtlas): [atlas] = data.textures.atlas_list() F, R, _, D = atlas.shape if R == 1: faces_colors = atlas[:, 0, 0, :] _save_off( file=path, verts=verts, faces=faces, verts_colors=verts_colors, faces_colors=faces_colors, decimal_places=decimal_places, path_manager=path_manager, ) return True
def save( self, data: Meshes, path: Union[str, Path], path_manager: PathManager, binary: Optional[bool], decimal_places: Optional[int] = None, **kwargs, ) -> bool: if not endswith(path, self.known_suffixes): return False # TODO: normals are not saved. We only want to save them if they already exist. verts = data.verts_list()[0] faces = data.faces_list()[0] save_ply( f=path, verts=verts, faces=faces, ascii=binary is False, decimal_places=decimal_places, path_manager=path_manager, ) return True