def qslim_decimator_transformer(mesh, factor=None, n_verts_desired=None): """Return a simplified version of this mesh. A Qslim-style approach is used here. :param factor: fraction of the original vertices to retain :param n_verts_desired: number of the original vertices to retain :returns: new_faces: An Fx3 array of faces, mtx: Transformation matrix """ if factor is None and n_verts_desired is None: raise Exception('Need either factor or n_verts_desired.') if n_verts_desired is None: n_verts_desired = math.ceil(len(mesh.v) * factor) Qv = vertex_quadrics(mesh) # fill out a sparse matrix indicating vertex-vertex adjacency # from psbody.mesh.topology.connectivity import get_vertices_per_edge vert_adj = get_vertices_per_edge(mesh.v, mesh.f) # vert_adj = sp.lil_matrix((len(mesh.v), len(mesh.v))) # for f_idx in range(len(mesh.f)): # vert_adj[mesh.f[f_idx], mesh.f[f_idx]] = 1 vert_adj = sp.csc_matrix((vert_adj[:, 0] * 0 + 1, (vert_adj[:, 0], vert_adj[:, 1])), shape=(len(mesh.v), len(mesh.v))) vert_adj = vert_adj + vert_adj.T vert_adj = vert_adj.tocoo() def collapse_cost(Qv, r, c, v): Qsum = Qv[r, :, :] + Qv[c, :, :] p1 = np.vstack((v[r].reshape(-1, 1), np.array([1]).reshape(-1, 1))) p2 = np.vstack((v[c].reshape(-1, 1), np.array([1]).reshape(-1, 1))) destroy_c_cost = p1.T.dot(Qsum).dot(p1) destroy_r_cost = p2.T.dot(Qsum).dot(p2) result = { 'destroy_c_cost': destroy_c_cost, 'destroy_r_cost': destroy_r_cost, 'collapse_cost': min([destroy_c_cost, destroy_r_cost]), 'Qsum': Qsum} return result # construct a queue of edges with costs queue = [] for k in range(vert_adj.nnz): r = vert_adj.row[k] c = vert_adj.col[k] if r > c: continue cost = collapse_cost(Qv, r, c, mesh.v)['collapse_cost'] heapq.heappush(queue, (cost, (r, c))) # decimate collapse_list = [] nverts_total = len(mesh.v) faces = mesh.f.copy() while nverts_total > n_verts_desired: e = heapq.heappop(queue) r = e[1][0] c = e[1][1] if r == c: continue cost = collapse_cost(Qv, r, c, mesh.v) if cost['collapse_cost'] > e[0]: heapq.heappush(queue, (cost['collapse_cost'], e[1])) # print 'found outdated cost, %.2f < %.2f' % (e[0], cost['collapse_cost']) continue else: # update old vert idxs to new one, # in queue and in face list if cost['destroy_c_cost'] < cost['destroy_r_cost']: to_destroy = c to_keep = r else: to_destroy = r to_keep = c collapse_list.append([to_keep, to_destroy]) # in our face array, replace "to_destroy" vertidx with "to_keep" vertidx np.place(faces, faces == to_destroy, to_keep) # same for queue which1 = [idx for idx in range(len(queue)) if queue[idx][1][0] == to_destroy] which2 = [idx for idx in range(len(queue)) if queue[idx][1][1] == to_destroy] for k in which1: queue[k] = (queue[k][0], (to_keep, queue[k][1][1])) for k in which2: queue[k] = (queue[k][0], (queue[k][1][0], to_keep)) Qv[r, :, :] = cost['Qsum'] Qv[c, :, :] = cost['Qsum'] a = faces[:, 0] == faces[:, 1] b = faces[:, 1] == faces[:, 2] c = faces[:, 2] == faces[:, 0] # remove degenerate faces def logical_or3(x, y, z): return np.logical_or(x, np.logical_or(y, z)) faces_to_keep = np.logical_not(logical_or3(a, b, c)) faces = faces[faces_to_keep, :].copy() nverts_total = (len(np.unique(faces.flatten()))) new_faces, mtx = _get_sparse_transform(faces, len(mesh.v)) return new_faces, mtx
def primitives_per_edge(self): v = self.v.r.reshape((-1, 3)) f = self.f vpe = get_vertices_per_edge(v, f) fpe = get_faces_per_edge(v, f, vpe) return fpe, vpe