Spaces:
Running
on
Zero
Running
on
Zero
"""Mesh data utilities.""" | |
import random | |
import networkx as nx | |
import numpy as np | |
# import pyrr | |
from six.moves import range | |
import trimesh | |
from scipy.spatial.transform import Rotation | |
def to_mesh(vertices, faces, transpose=True, post_process=False): | |
if transpose: | |
vertices = vertices[:, [1, 2, 0]] | |
if faces.min() == 1: | |
faces = (np.array(faces) - 1).tolist() | |
mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=False) | |
if post_process: | |
mesh.merge_vertices() | |
mesh.update_faces(mesh.unique_faces()) | |
mesh.fix_normals() | |
return mesh | |
def center_vertices(vertices): | |
"""Translate the vertices so that bounding box is centered at zero.""" | |
vert_min = vertices.min(axis=0) | |
vert_max = vertices.max(axis=0) | |
vert_center = 0.5 * (vert_min + vert_max) | |
# vert_center = np.mean(vertices, axis=0) | |
return vertices - vert_center | |
def face_to_cycles(face): | |
"""Find cycles in face.""" | |
g = nx.Graph() | |
for v in range(len(face) - 1): | |
g.add_edge(face[v], face[v + 1]) | |
g.add_edge(face[-1], face[0]) | |
return list(nx.cycle_basis(g)) | |
def block_index(vertex, block_size=32): | |
return (vertex[2] // block_size, vertex[1] // block_size, vertex[0] // block_size) | |
def block_id(block_index, num_blocks=4): | |
return block_index[0] * num_blocks**2 + block_index[1] * num_blocks + block_index[2] | |
def normalize_vertices_scale(vertices, scale=0.95): | |
"""Scale the vertices so that the long axis of the bounding box is one.""" | |
vert_min = vertices.min(axis=0) | |
vert_max = vertices.max(axis=0) | |
extents = (vert_max - vert_min).max() | |
return 2.0 * scale * vertices / (extents + 1e-6) | |
def quantize_process_mesh(vertices, faces, quantization_bits=8, block_first_order=True, block_size=32, num_blocks=4): | |
"""Quantize vertices, remove resulting duplicates and reindex faces.""" | |
vertices = discretize(vertices, num_discrete=2**quantization_bits) | |
vertices, inv = np.unique(vertices, axis=0, return_inverse=True) | |
if block_first_order: | |
block_indices = np.array([block_index(v, block_size) for v in vertices]) | |
block_ids = np.array([block_id(b, num_blocks) for b in block_indices]) | |
sort_inds = np.lexsort((vertices[:, 0], vertices[:, 1], vertices[:, 2], block_ids)) | |
else: | |
# Sort vertices by z then y then x. | |
sort_inds = np.lexsort(vertices.T) | |
vertices = vertices[sort_inds] | |
faces = [np.argsort(sort_inds)[inv[f]] for f in faces] | |
sub_faces = [] | |
for f in faces: | |
cliques = face_to_cycles(f) | |
for c in cliques: | |
c_length = len(c) | |
if c_length > 2: | |
d = np.argmin(f) | |
sub_faces.append([f[(d + i) % c_length] for i in range(c_length)]) | |
faces = sub_faces | |
# Sort faces by lowest vertex indices. If two faces have the same lowest | |
# index then sort by next lowest and so on. | |
faces.sort(key=lambda f: tuple(sorted(f))) | |
num_verts = vertices.shape[0] | |
vert_connected = np.equal( | |
np.arange(num_verts)[:, None], np.hstack(faces)[None] | |
).any(axis=-1) | |
vertices = vertices[vert_connected] | |
# Re-index faces to re-ordered vertices. | |
vert_indices = np.arange(num_verts) - np.cumsum(1 - vert_connected.astype("int")) | |
faces = [vert_indices[f].tolist() for f in faces] | |
return vertices, faces | |
def process_mesh(vertices, faces, quantization_bits=8, augment=True, augment_dict=None): | |
"""Process mesh vertices and faces.""" | |
# Transpose so that z-axis is vertical. | |
vertices = vertices[:, [2, 0, 1]] | |
# Translate the vertices so that bounding box is centered at zero. | |
vertices = center_vertices(vertices) | |
if augment: | |
vertices = augment_mesh(vertices, **augment_dict) | |
# Scale the vertices so that the long diagonal of the bounding box is equal | |
# to one. | |
vertices = normalize_vertices_scale(vertices) | |
# Quantize and sort vertices, remove resulting duplicates, sort and reindex | |
# faces. | |
vertices, faces = quantize_process_mesh( | |
vertices, faces, quantization_bits=quantization_bits | |
) | |
vertices = undiscretize(vertices, num_discrete=2**quantization_bits) | |
# Discard degenerate meshes without faces. | |
return { | |
"vertices": vertices, | |
"faces": faces, | |
} | |
def load_process_mesh(mesh_obj_path, quantization_bits=8, augment=False, augment_dict=None): | |
"""Load obj file and process.""" | |
# Load mesh | |
mesh = trimesh.load(mesh_obj_path, force='mesh', process=False) | |
return process_mesh(mesh.vertices, mesh.faces, quantization_bits, augment=augment, augment_dict=augment_dict) | |
def augment_mesh(vertices, scale_min=0.95, scale_max=1.05, rotation=0., jitter_strength=0.): | |
'''scale vertices by a factor in [0.75, 1.25]''' | |
# vertices [nv, 3] | |
for i in range(3): | |
# Generate a random scale factor | |
scale = random.uniform(scale_min, scale_max) | |
# independently applied scaling across each axis of vertices | |
vertices[:, i] *= scale | |
if rotation != 0.: | |
axis = [random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)] | |
radian = np.pi / 180 * rotation | |
rotation = Rotation.from_rotvec(radian * np.array(axis)) | |
vertices =rotation.apply(vertices) | |
if jitter_strength != 0.: | |
jitter_amount = np.random.uniform(-jitter_strength, jitter_strength) | |
vertices += jitter_amount | |
return vertices | |
def discretize( | |
t, | |
continuous_range = (-1, 1), | |
num_discrete: int = 128 | |
): | |
lo, hi = continuous_range | |
assert hi > lo | |
t = (t - lo) / (hi - lo) | |
t *= num_discrete | |
t -= 0.5 | |
return t.round().astype(np.int32).clip(min = 0, max = num_discrete - 1) | |
def undiscretize( | |
t, | |
continuous_range = (-1, 1), | |
num_discrete: int = 128 | |
): | |
lo, hi = continuous_range | |
assert hi > lo | |
t = t.astype(np.float32) | |
t += 0.5 | |
t /= num_discrete | |
return t * (hi - lo) + lo | |