diff --git a/maps.py b/maps.py index d47cd79..4d42e7d 100644 --- a/maps.py +++ b/maps.py @@ -1,4 +1,5 @@ import io +from requests import delete from rich.progress import Progress import obja.obja as obja @@ -6,6 +7,10 @@ from object import Edge, Face, Vertex from utils import * +class FixModel(Exception): + pass + + class MAPS(obja.Model): """MAPS compression model""" @@ -40,34 +45,32 @@ class MAPS(obja.Model): def fix(self) -> None: """Fix the model""" - fixed = True - # Find a vertex with less than 3 faces for i, vertex in enumerate(self.vertices): if vertex is None: continue - if len(vertex.face_ring) < 3: - # Remove the vertex and its faces - for face in vertex.face_ring: - if not self.final_only: - self.operations.append( - ('af', face, self.faces[face].to_obja())) - self.faces[face] = None - if not self.final_only: - self.operations.append( - ('av', i, vertex.to_obja())) - self.vertices[i] = None + # Remove lonely vertices + if len(vertex.face_ring) == 0: + self.delete_vertex(i) + raise FixModel - # Indicate that the model has to be fixed again - fixed = False + # Pass if vertex has only 2 same faces + if len(vertex.face_ring) != 2: + continue + if self.faces[vertex.face_ring[0]] != self.faces[vertex.face_ring[1]]: + continue - return fixed + # Remove the vertex and its faces + self.delete_vertex(i) + + # Indicate that the model has to be fixed again + raise FixModel def update_edges(self) -> None: """Update edges""" - self.edges = {} + self.edges: dict[str, Edge] = {} for face in self.faces: if face is None: @@ -98,14 +101,28 @@ class MAPS(obja.Model): # Add the new edge to the list self.edges[f"{new_edge.a}:{new_edge.b}"] = new_edge + + def delete_vertex(self, index: int) -> None: + """Delete a vertex and its faces + + Args: + index (int): index of the vertex to delete + """ + vertex = self.vertices[index] + + for face in vertex.face_ring: + if not self.final_only: + self.operations.append( + ('af', face, self.faces[face].to_obja())) + self.faces[face] = None + if not self.final_only: + self.operations.append( + ('av', index, vertex.to_obja())) + self.vertices[index] = None def update_rings(self) -> None: """Update vertex and face rings""" - - fixed = False - - # Wait till the model is fixed - while not fixed: + try: for vertex in self.vertices: # Reset vertex ring if vertex is None: @@ -120,16 +137,19 @@ class MAPS(obja.Model): self.vertices[vertex_i].face_ring.append(i) # Fix the model - fixed = self.fix() + self.fix() - for i, vertex in enumerate(self.vertices): - vertex = self.vertices[i] - if vertex is None: - continue + for i, vertex in enumerate(self.vertices): + vertex = self.vertices[i] + if vertex is None: + continue - # Compute rings - ring = self.one_ring(i) - vertex.vertex_ring = ring + # Compute rings + ring = self.one_ring(i) + vertex.vertex_ring = ring + + except FixModel: + self.update_rings() def update_area_curvature(self) -> None: """Update area and curvature""" @@ -146,6 +166,9 @@ class MAPS(obja.Model): # Find feature edges self.feature_edges = [] for edge in self.edges.values(): + if edge.face2 is None: + continue + edge.fold = np.dot(edge.face1.normal, edge.face2.normal) if edge.fold < 0.5: @@ -197,16 +220,21 @@ class MAPS(obja.Model): ring_faces = [self.faces[i] for i in self.vertices[index].face_ring] # Initialize the ring - start_index = (ring_faces[0].a if ring_faces[0].a != index and ring_faces[0].c != index else - ring_faces[0].b if ring_faces[0].a != index and ring_faces[0].b != index else - ring_faces[0].c) + start_index, revert_index = ( + (ring_faces[0].c, ring_faces[0].b) if ring_faces[0].a == index else + (ring_faces[0].a, ring_faces[0].c) if ring_faces[0].b == index else + (ring_faces[0].b, ring_faces[0].a) + ) + ring = [start_index] + ring_faces.pop(0) + revert_looking = False # Select the indexes of the ring in the right order while len(ring_faces) > 0: broke = False - prev_index = ring[-1] + prev_index = ring[-1] if not revert_looking else ring[0] for i, face in enumerate(ring_faces): if prev_index in (face.a, face.b, face.c): # Found the face that correspond to the next vertex @@ -215,13 +243,22 @@ class MAPS(obja.Model): face.b if face.b != index and face.b != prev_index else face.c ) - ring.append(current_index) + if not revert_looking: + ring.append(current_index) + else: + ring.insert(0, current_index) ring_faces.pop(i) broke = True break if not broke: - raise Exception('Ring corrupted') + if revert_looking: + self.delete_vertex(index) + raise FixModel + + revert_looking = True + ring.insert(0, revert_index) + self.vertices[index].border = True return ring @@ -284,7 +321,7 @@ class MAPS(obja.Model): # Compute priorities priorities = [] for vertex in self.vertices: - if vertex is not None and len(vertex.vertex_ring) < max_length: + if vertex is not None and 1 < len(vertex.vertex_ring) < max_length: # Compute priority priority = ( lamb * vertex.area / max_area + @@ -365,7 +402,10 @@ class MAPS(obja.Model): teta += self.compute_angle(index1, index, index2) # add new angle radius.append(r) angles.append(teta) - angles = [2 * np.pi * a / teta for a in angles] # normalize angles + + full_rotate = 2 * np.pi if not self.vertices[index].border else np.pi + + angles = [full_rotate * a / teta for a in angles] # normalize angles coordinates = [np.array([r * np.cos(a), r * np.sin(a)]) for r, a in zip(radius, angles)] # parse polar to cartesian diff --git a/object.py b/object.py index d8ea937..f84613d 100644 --- a/object.py +++ b/object.py @@ -101,6 +101,8 @@ class Vertex: self.area: float = 0.0 self.curvature: float = 0.0 + self.border: bool = False + def to_obja(self) -> np.ndarray: """Convert vertex to obja format