import torch from pytorch3d.structures import Meshes, Pointclouds from lib.common.render_utils import face_vertices from lib.dataset.Evaluator import point_mesh_distance from lib.dataset.mesh_util import SMPLX, barycentric_coordinates_of_projection class PointFeat: def __init__(self, verts, faces): # verts [B, N_vert, 3] # faces [B, N_face, 3] # triangles [B, N_face, 3, 3] self.Bsize = verts.shape[0] self.device = verts.device self.faces = faces # SMPL has watertight mesh, but SMPL-X has two eyeballs and open mouth # 1. remove eye_ball faces from SMPL-X: 9928-9383, 10474-9929 # 2. fill mouth holes with 30 more faces if verts.shape[1] == 10475: faces = faces[:, ~SMPLX().smplx_eyeball_fid_mask] mouth_faces = ( torch.as_tensor(SMPLX().smplx_mouth_fid).unsqueeze(0).repeat(self.Bsize, 1, 1).to(self.device) ) self.faces = torch.cat([faces, mouth_faces], dim=1).long() self.verts = verts.float() self.triangles = face_vertices(self.verts, self.faces) self.mesh = Meshes(self.verts, self.faces).to(self.device) def query(self, points): points = points.float() residues, pts_ind = point_mesh_distance(self.mesh, Pointclouds(points), weighted=False) closest_triangles = torch.gather( self.triangles, 1, pts_ind[None, :, None, None].expand(-1, -1, 3, 3) ).view(-1, 3, 3) bary_weights = barycentric_coordinates_of_projection(points.view(-1, 3), closest_triangles) feat_normals = face_vertices(self.mesh.verts_normals_padded(), self.faces) closest_normals = torch.gather( feat_normals, 1, pts_ind[None, :, None, None].expand(-1, -1, 3, 3) ).view(-1, 3, 3) shoot_verts = ((closest_triangles * bary_weights[:, :, None]).sum(1).unsqueeze(0)) pts2shoot_normals = points - shoot_verts pts2shoot_normals = pts2shoot_normals / torch.norm(pts2shoot_normals, dim=-1, keepdim=True) shoot_normals = ((closest_normals * bary_weights[:, :, None]).sum(1).unsqueeze(0)) shoot_normals = shoot_normals / torch.norm(shoot_normals, dim=-1, keepdim=True) angles = (pts2shoot_normals * shoot_normals).sum(dim=-1).abs() return (torch.sqrt(residues).unsqueeze(0), angles)