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 numpy as np
import argparse
from time import time
from rich.progress import track, Progress
@ -34,7 +33,10 @@ class Edge:
self.curvature = 0.0
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:
@ -49,10 +51,10 @@ class Face:
return obja.Face(self.a, self.b, self.c)
def __eq__(self, __o: object) -> bool:
if __o is None:
return False
if isinstance(__o, Face):
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:
@ -89,6 +91,8 @@ class MAPS(obja.Model):
self.faces[i] = Face(face.a, face.b, face.c)
def update(self):
self.progress.reset(self.select_task, total=len(self.vertices))
self.progress.reset(self.compress_task)
self.update_rings()
self.update_edges()
self.update_normals()
@ -97,7 +101,7 @@ class MAPS(obja.Model):
def update_edges(self):
self.edges = {}
for face in track(self.faces, description='Update edges'):
for face in self.faces:
if face is None:
continue
@ -126,18 +130,17 @@ class MAPS(obja.Model):
continue
vertex.face_ring = []
for face_i in track(range(len(self.faces)), description='Finding rings'):
face = self.faces[face_i]
for i, face in enumerate(self.faces):
if face is None:
continue
for vertex_i in (face.a, face.b, face.c):
self.vertices[vertex_i].face_ring.append(face_i)
for vertex_i in track(range(len(self.vertices)), description='Truc'):
vertex = self.vertices[vertex_i]
self.vertices[vertex_i].face_ring.append(i)
for i, vertex in enumerate(self.vertices):
vertex = self.vertices[i]
if vertex is None:
continue
ring = self.one_ring(vertex_i)
ring = self.one_ring(i)
vertex.vertex_ring = ring
def update_area_curvature(self):
@ -192,8 +195,6 @@ class MAPS(obja.Model):
Returns:
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]
@ -222,6 +223,25 @@ class MAPS(obja.Model):
break
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(
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:
tuple[float, float]: area and curvature
"""
if self.vertices[index] is None:
return None, None
ring = self.vertices[index].vertex_ring
p1 = self.vertices[index].pos
@ -307,6 +325,7 @@ class MAPS(obja.Model):
Returns:
list[int]: selected vertices
"""
# Order vertices by priority
priorities = self.compute_priority()
vertices = [i[0]
@ -314,45 +333,43 @@ class MAPS(obja.Model):
selected_vertices = []
with Progress() as progress:
task = progress.add_task('Select vertices', total=len(vertices))
while not progress.finished:
# Select prefered vertex
vertex = vertices.pop(0) # remove it from remaining vertices
progress.advance(task)
while len(vertices) > 0:
# Select prefered vertex
vertex = vertices.pop(0) # remove it from remaining vertices
self.progress.advance(self.select_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
incident_count = 0
for feature_edge in self.feature_edges:
if vertex in (feature_edge.a, feature_edge.b):
incident_count += 1
face_vertices = (face.a, face.b, face.c)
if vertex in face_vertices:
if incident_count > 2:
continue
# 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)
self.progress.advance(self.select_task)
selected_vertices.append(vertex)
return selected_vertices
# Remove neighbors
# 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]:
def project_polar(self, index: int) -> tuple[list[np.ndarray], list[int]]:
""" Flatten the 1-ring to retriangulate
Args:
@ -398,7 +415,7 @@ class MAPS(obja.Model):
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
Args:
@ -408,7 +425,7 @@ class MAPS(obja.Model):
tuple[list[obja.Face], int]: list the triangles
"""
polygon_, ring_ = self.project_polar(index)
main_v = []
for i, r in enumerate(ring_):
for feature_edge in self.feature_edges:
@ -446,7 +463,7 @@ class MAPS(obja.Model):
polygon2.append(polygon_[start])
polygons_rings = [(polygon1, ring1), (polygon2, ring2)]
faces = [] # the final list of faces
for polygon, ring in polygons_rings:
@ -467,14 +484,15 @@ class MAPS(obja.Model):
curr_vert = polygon[local_j]
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
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
test_node_index = (node_index + 2) % len(indices)
while indices[test_node_index][0] != local_i and is_ear:
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,
next_vert,
test_vert)
@ -492,7 +510,11 @@ class MAPS(obja.Model):
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
Args:
@ -513,7 +535,12 @@ class MAPS(obja.Model):
internal_angle = angle
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
Args:
@ -600,46 +627,53 @@ class MAPS(obja.Model):
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
Args:
output (io.TextIOWrapper): Output file descriptor
"""
operations = []
# while len(self.vertices) > 64:
for _ in range(2):
self.update()
selected_vertices = self.select_vertices() # find the set of vertices to remove
with Progress() as progress:
self.global_task = progress.add_task('╓ Global compression')
self.select_task = progress.add_task('╟── Vertex selection')
self.compress_task = progress.add_task('╙── Compression')
self.progress = progress
for v_index in track(selected_vertices, description="Compression"):
operations = []
# Extract ring faces
ring_faces = self.vertices[v_index].face_ring
# while len(self.vertices) > 64:
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
faces = self.clip_ear(v_index)
for v_index in self.progress.track(selected_vertices, task_id=self.compress_task):
# Edit the first faces
for i in range(len(faces)):
# Extract ring 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:
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:
operations.append(
('av', v_index, self.vertices[v_index].to_obja()))
self.vertices[v_index] = None
('av', v_index, self.vertices[v_index].to_obja()))
self.vertices[v_index] = None
# Register remaining vertices and faces
for i, face in enumerate(self.faces):
@ -684,7 +718,7 @@ def main(args):
model.parse_file(args.input)
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:
# model.truc(output)
@ -694,6 +728,7 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', 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)
args = parser.parse_args()