| 23 | |
| 24 | |
| 25 | class StrandsData(dict): |
| 26 | def __init__(self, npz_data, meta_data, scalp_mesh_data, nr_full_strands_per_hairstyle, compute_tbn=True, flip=False): |
| 27 | self.positions = npz_data["positions"] #nr_strands x nr_points_per_strand x 3 |
| 28 | self.root_normal = npz_data["root_normal"] #nr_strands x 3 |
| 29 | self.root_uv = npz_data["root_uv"] #nr_strands x 2 |
| 30 | self.root_position = npz_data["positions"][:,0,:] #nr_strands x 3 |
| 31 | # print("init positiosis ", npz_data["positions"].shape) |
| 32 | # print("inti root position",self.root_position.shape) |
| 33 | |
| 34 | #solving a bug with the root_uvs, when the hair is mirrored by the blender pipeline the uv's were not mirrored also so we need to do it here |
| 35 | if meta_data["mirror_hair"]: |
| 36 | self.root_uv[:,0] = 1.0 - self.root_uv[:,0] |
| 37 | |
| 38 | if flip: |
| 39 | self.positions[:,:,0]*=-1 |
| 40 | self.root_normal[:,0]*=-1 |
| 41 | self.root_uv[:,0] = 1 - self.root_uv[:,0] |
| 42 | self.root_position[:,0]*=-1 |
| 43 | |
| 44 | |
| 45 | chunked_values = [100,1000] |
| 46 | if nr_full_strands_per_hairstyle is not None and nr_full_strands_per_hairstyle not in chunked_values: |
| 47 | self.subsample_to_nr_strands(nr_full_strands_per_hairstyle) |
| 48 | |
| 49 | if compute_tbn: |
| 50 | self.compute_tbn(scalp_mesh_data["verts"], scalp_mesh_data["uv"],scalp_mesh_data["faces"], |
| 51 | scalp_mesh_data["v_tangents"], scalp_mesh_data["v_bitangents"],scalp_mesh_data["v_normals"]) |
| 52 | |
| 53 | |
| 54 | def subsample_to_nr_strands(self, nr_strands_to_select): |
| 55 | nr_strands_total = self.positions.shape[0] |
| 56 | indices_strands = np.random.randint(nr_strands_total, size=nr_strands_to_select) |
| 57 | |
| 58 | #new positions |
| 59 | self.positions = self.positions[indices_strands,:,:] |
| 60 | self.root_normal = self.root_normal[indices_strands,:] |
| 61 | self.root_uv = self.root_uv[indices_strands,:] |
| 62 | self.root_position = self.root_position[indices_strands,:] |
| 63 | |
| 64 | |
| 65 | def compute_tbn(self, mesh_verts, mesh_uv, mesh_faces, mesh_v_tangents, mesh_v_bitangents, mesh_v_normals): |
| 66 | mesh_faces=mesh_faces.astype(np.int32) |
| 67 | closest_points, barys, vertex_idxs, face_idxs=closest_point_barycentrics(self.root_position, mesh_verts, mesh_faces) |
| 68 | |
| 69 | root_tangent, root_bitangent, root_normal = interpolate_tbn(barys, vertex_idxs, mesh_v_tangents, mesh_v_bitangents, mesh_v_normals) |
| 70 | #replace the normals because it's smoother |
| 71 | self.root_normal=root_normal |
| 72 | tbn = np.stack((root_tangent,root_bitangent,root_normal),axis=2) |
| 73 | self.tbn=tbn |
| 74 | # print("tbn", tbn.shape) |
| 75 | |
| 76 | |
| 77 | |
| 78 | def to_dict(self): |
| 79 | d = {} |
| 80 | for key, value in self.__dict__.items(): |
| 81 | d[key]=value |
| 82 | return d |