feat: que du visu

This commit is contained in:
gdamms 2022-10-17 23:45:20 +02:00
parent dff7ae111e
commit fbaf17a81f

209
main.py
View file

@ -3,7 +3,6 @@ from math import floor
import obja.obja as obja import obja.obja as obja
import numpy as np import numpy as np
import argparse import argparse
from time import time
from rich.progress import track, Progress from rich.progress import track, Progress
@ -34,7 +33,10 @@ class Edge:
self.curvature = 0.0 self.curvature = 0.0
def __eq__(self, __o: object) -> bool: def __eq__(self, __o: object) -> bool:
return self.a == __o.a and self.b == __o.b if isinstance(__o, Edge):
return self.a == __o.a and self.b == __o.b
return False
class Face: class Face:
@ -49,10 +51,10 @@ class Face:
return obja.Face(self.a, self.b, self.c) return obja.Face(self.a, self.b, self.c)
def __eq__(self, __o: object) -> bool: def __eq__(self, __o: object) -> bool:
if __o is None: if isinstance(__o, Face):
return False return self.a == __o.a and self.b == __o.b and self.c == __o.c
return self.a == __o.a and self.b == __o.b and self.c == __o.c return False
class Vertex: class Vertex:
@ -89,6 +91,8 @@ class MAPS(obja.Model):
self.faces[i] = Face(face.a, face.b, face.c) self.faces[i] = Face(face.a, face.b, face.c)
def update(self): def update(self):
self.progress.reset(self.select_task, total=len(self.vertices))
self.progress.reset(self.compress_task)
self.update_rings() self.update_rings()
self.update_edges() self.update_edges()
self.update_normals() self.update_normals()
@ -97,7 +101,7 @@ class MAPS(obja.Model):
def update_edges(self): def update_edges(self):
self.edges = {} self.edges = {}
for face in track(self.faces, description='Update edges'): for face in self.faces:
if face is None: if face is None:
continue continue
@ -126,18 +130,17 @@ class MAPS(obja.Model):
continue continue
vertex.face_ring = [] vertex.face_ring = []
for face_i in track(range(len(self.faces)), description='Finding rings'): for i, face in enumerate(self.faces):
face = self.faces[face_i]
if face is None: if face is None:
continue continue
for vertex_i in (face.a, face.b, face.c): for vertex_i in (face.a, face.b, face.c):
self.vertices[vertex_i].face_ring.append(face_i) self.vertices[vertex_i].face_ring.append(i)
for vertex_i in track(range(len(self.vertices)), description='Truc'): for i, vertex in enumerate(self.vertices):
vertex = self.vertices[vertex_i] vertex = self.vertices[i]
if vertex is None: if vertex is None:
continue continue
ring = self.one_ring(vertex_i) ring = self.one_ring(i)
vertex.vertex_ring = ring vertex.vertex_ring = ring
def update_area_curvature(self): def update_area_curvature(self):
@ -192,8 +195,6 @@ class MAPS(obja.Model):
Returns: Returns:
list[int]: ordered list of the 1-ring vertices list[int]: ordered list of the 1-ring vertices
""" """
if self.vertices[index] is None:
return None, None
ring_faces = [self.faces[i] for i in self.vertices[index].face_ring] ring_faces = [self.faces[i] for i in self.vertices[index].face_ring]
@ -222,6 +223,25 @@ class MAPS(obja.Model):
break break
if not broke: if not broke:
output_file = open('obja/example/fail.obja', 'w')
output = obja.Output(output_file)
used = []
for i, x in enumerate(self.vertices[index].face_ring):
face = self.faces[x]
for y in (face.a, face.b, face.c):
if y in used:
continue
output.add_vertex(y, self.vertices[y].to_obja())
used.append(y)
output.add_face(x, face.to_obja())
print('fc {} {} {} {}'.format(
i + 1,
np.random.rand(),
np.random.rand(),
np.random.rand()),
file=output_file
)
raise ValueError( raise ValueError(
f"Vertex {prev_index} is not in the remaining faces {ring_faces}. Origin {ring} on {index}") f"Vertex {prev_index} is not in the remaining faces {ring_faces}. Origin {ring} on {index}")
@ -236,8 +256,6 @@ class MAPS(obja.Model):
Returns: Returns:
tuple[float, float]: area and curvature tuple[float, float]: area and curvature
""" """
if self.vertices[index] is None:
return None, None
ring = self.vertices[index].vertex_ring ring = self.vertices[index].vertex_ring
p1 = self.vertices[index].pos p1 = self.vertices[index].pos
@ -307,6 +325,7 @@ class MAPS(obja.Model):
Returns: Returns:
list[int]: selected vertices list[int]: selected vertices
""" """
# Order vertices by priority # Order vertices by priority
priorities = self.compute_priority() priorities = self.compute_priority()
vertices = [i[0] vertices = [i[0]
@ -314,45 +333,43 @@ class MAPS(obja.Model):
selected_vertices = [] selected_vertices = []
with Progress() as progress: while len(vertices) > 0:
task = progress.add_task('Select vertices', total=len(vertices)) # Select prefered vertex
while not progress.finished: vertex = vertices.pop(0) # remove it from remaining vertices
# Select prefered vertex self.progress.advance(self.select_task)
vertex = vertices.pop(0) # remove it from remaining vertices
progress.advance(task)
if priorities[vertex] == 2.0: if priorities[vertex] == 2.0:
continue
incident_count = 0
for feature_edge in self.feature_edges:
if vertex in (feature_edge.a, feature_edge.b):
incident_count += 1
if incident_count > 2:
continue
selected_vertices.append(vertex)
# Remove neighbors
# for face in remaining_faces:
for face in self.faces:
if face is None:
continue continue
incident_count = 0 face_vertices = (face.a, face.b, face.c)
for feature_edge in self.feature_edges: if vertex in face_vertices:
if vertex in (feature_edge.a, feature_edge.b):
incident_count += 1
if incident_count > 2: # Remove face and face's vertices form remainings
continue # remaining_faces.remove(face)
for face_vertex in face_vertices:
if face_vertex in vertices:
vertices.remove(face_vertex)
self.progress.advance(self.select_task)
selected_vertices.append(vertex) return selected_vertices
# Remove neighbors def project_polar(self, index: int) -> tuple[list[np.ndarray], list[int]]:
# for face in remaining_faces:
for face in self.faces:
if face is None:
continue
face_vertices = (face.a, face.b, face.c)
if vertex in face_vertices:
# Remove face and face's vertices form remainings
# remaining_faces.remove(face)
for face_vertex in face_vertices:
if face_vertex in vertices:
vertices.remove(face_vertex)
progress.advance(task)
return selected_vertices[:floor(1.0 * len(selected_vertices))]
def project_polar(self, index: int) -> list[np.ndarray]:
""" Flatten the 1-ring to retriangulate """ Flatten the 1-ring to retriangulate
Args: Args:
@ -398,7 +415,7 @@ class MAPS(obja.Model):
return np.arccos(np.clip(res, -1, 1)) return np.arccos(np.clip(res, -1, 1))
def clip_ear(self, index: int) -> tuple[list[obja.Face], int]: def clip_ear(self, index: int) -> list[obja.Face]:
""" Retriangulate a polygon using the ear clipping algorithm """ Retriangulate a polygon using the ear clipping algorithm
Args: Args:
@ -408,7 +425,7 @@ class MAPS(obja.Model):
tuple[list[obja.Face], int]: list the triangles tuple[list[obja.Face], int]: list the triangles
""" """
polygon_, ring_ = self.project_polar(index) polygon_, ring_ = self.project_polar(index)
main_v = [] main_v = []
for i, r in enumerate(ring_): for i, r in enumerate(ring_):
for feature_edge in self.feature_edges: for feature_edge in self.feature_edges:
@ -446,7 +463,7 @@ class MAPS(obja.Model):
polygon2.append(polygon_[start]) polygon2.append(polygon_[start])
polygons_rings = [(polygon1, ring1), (polygon2, ring2)] polygons_rings = [(polygon1, ring1), (polygon2, ring2)]
faces = [] # the final list of faces faces = [] # the final list of faces
for polygon, ring in polygons_rings: for polygon, ring in polygons_rings:
@ -467,14 +484,15 @@ class MAPS(obja.Model):
curr_vert = polygon[local_j] curr_vert = polygon[local_j]
next_vert = polygon[local_k] next_vert = polygon[local_k]
is_convex = MAPS.is_convex(prev_vert, curr_vert, next_vert) is_convex = self.is_convex(prev_vert, curr_vert, next_vert)
is_ear = True is_ear = True
if is_convex or cycle_counter > len(indices): # the triangle needs to be convext to be an ear # the triangle needs to be convext to be an ear
if is_convex or cycle_counter > len(indices):
# Begin with the point next to the triangle # Begin with the point next to the triangle
test_node_index = (node_index + 2) % len(indices) test_node_index = (node_index + 2) % len(indices)
while indices[test_node_index][0] != local_i and is_ear: while indices[test_node_index][0] != local_i and is_ear:
test_vert = polygon[indices[test_node_index][0]] test_vert = polygon[indices[test_node_index][0]]
is_ear = not MAPS.is_inside(prev_vert, is_ear = not self.is_inside(prev_vert,
curr_vert, curr_vert,
next_vert, next_vert,
test_vert) test_vert)
@ -492,7 +510,11 @@ class MAPS(obja.Model):
return faces return faces
def is_convex(prev_vert: np.ndarray, curr_vert: np.ndarray, next_vert: np.ndarray) -> bool: def is_convex(self,
prev_vert: np.ndarray[int, np.dtype[np.float64]],
curr_vert: np.ndarray[int, np.dtype[np.float64]],
next_vert: np.ndarray[int, np.dtype[np.float64]]
) -> bool:
""" Check if the angle less than pi """ Check if the angle less than pi
Args: Args:
@ -513,7 +535,12 @@ class MAPS(obja.Model):
internal_angle = angle internal_angle = angle
return internal_angle >= np.pi return internal_angle >= np.pi
def is_inside(a: np.ndarray, b: np.ndarray, c: np.ndarray, p: np.ndarray) -> bool: def is_inside(self,
a: np.ndarray[int, np.dtype[np.float64]],
b: np.ndarray[int, np.dtype[np.float64]],
c: np.ndarray[int, np.dtype[np.float64]],
p: np.ndarray[int, np.dtype[np.float64]]
) -> bool:
""" Check if p is in the triangle a b c """ Check if p is in the triangle a b c
Args: Args:
@ -600,46 +627,53 @@ class MAPS(obja.Model):
file=output file=output
) )
def compress(self, output: io.TextIOWrapper, final_only: bool) -> None: def compress(self, output: io.TextIOWrapper, level: int, final_only: bool) -> None:
""" Compress the 3d model """ Compress the 3d model
Args: Args:
output (io.TextIOWrapper): Output file descriptor output (io.TextIOWrapper): Output file descriptor
""" """
operations = []
# while len(self.vertices) > 64: with Progress() as progress:
for _ in range(2): self.global_task = progress.add_task('╓ Global compression')
self.update() self.select_task = progress.add_task('╟── Vertex selection')
selected_vertices = self.select_vertices() # find the set of vertices to remove self.compress_task = progress.add_task('╙── Compression')
self.progress = progress
for v_index in track(selected_vertices, description="Compression"): operations = []
# Extract ring faces # while len(self.vertices) > 64:
ring_faces = self.vertices[v_index].face_ring for _ in progress.track(range(level), task_id=self.global_task):
self.update()
selected_vertices = self.select_vertices() # find the set of vertices to remove
# Apply retriangulation algorithm for v_index in self.progress.track(selected_vertices, task_id=self.compress_task):
faces = self.clip_ear(v_index)
# Edit the first faces # Extract ring faces
for i in range(len(faces)): ring_faces = self.vertices[v_index].face_ring
# Apply retriangulation algorithm
faces = self.clip_ear(v_index)
# Edit the first faces
for i in range(len(faces)):
if not final_only:
operations.append(
('ef', ring_faces[i], self.faces[ring_faces[i]].to_obja()))
self.faces[ring_faces[i]] = faces[i]
# Remove the last faces
for i in range(len(faces), len(ring_faces)):
if not final_only:
operations.append(
('af', ring_faces[i], self.faces[ring_faces[i]].to_obja()))
self.faces[ring_faces[i]] = None
# Remove the vertex
if not final_only: if not final_only:
operations.append( operations.append(
('ef', ring_faces[i], self.faces[ring_faces[i]].to_obja())) ('av', v_index, self.vertices[v_index].to_obja()))
self.faces[ring_faces[i]] = faces[i] self.vertices[v_index] = None
# Remove the last faces
for i in range(len(faces), len(ring_faces)):
if not final_only:
operations.append(
('af', ring_faces[i], self.faces[ring_faces[i]].to_obja()))
self.faces[ring_faces[i]] = None
# Remove the vertex
if not final_only:
operations.append(
('av', v_index, self.vertices[v_index].to_obja()))
self.vertices[v_index] = None
# Register remaining vertices and faces # Register remaining vertices and faces
for i, face in enumerate(self.faces): for i, face in enumerate(self.faces):
@ -684,7 +718,7 @@ def main(args):
model.parse_file(args.input) model.parse_file(args.input)
with open(args.output, 'w') as output: with open(args.output, 'w') as output:
model.compress(output, args.final) model.compress(output, args.level, args.final)
# with open(args.output, 'w') as output: # with open(args.output, 'w') as output:
# model.truc(output) # model.truc(output)
@ -694,6 +728,7 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', type=str, required=True) parser.add_argument('-i', '--input', type=str, required=True)
parser.add_argument('-o', '--output', type=str, required=True) parser.add_argument('-o', '--output', type=str, required=True)
parser.add_argument('-l', '--level', type=int, required=True)
parser.add_argument('-f', '--final', type=bool, default=False) parser.add_argument('-f', '--final', type=bool, default=False)
args = parser.parse_args() args = parser.parse_args()