Spaces:
Sleeping
Sleeping
File size: 6,035 Bytes
ada4b81 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
"""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
|