feat: que du visu
This commit is contained in:
parent
dff7ae111e
commit
fbaf17a81f
209
main.py
209
main.py
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue