Exemple #1
0
def get_rho(orig_v, vertices, faces):
    orig_v = normalize_area(orig_v, faces)
    a1 = igl.massmatrix(orig_v, faces,
                        igl.MASSMATRIX_TYPE_BARYCENTRIC).diagonal()
    a2 = igl.massmatrix(vertices, faces,
                        igl.MASSMATRIX_TYPE_BARYCENTRIC).diagonal()

    return np.divide(a1, a2)
 def test_massmatrix(self):
     a = igl.massmatrix(self.v, self.f)
     b = igl.massmatrix(self.v, self.f, type=igl.MASSMATRIX_TYPE_BARYCENTRIC)
     self.assertTrue(a.shape == (self.v.shape[0], self.v.shape[0]))
     self.assertTrue(b.shape == (self.v.shape[0], self.v.shape[0]))
     self.assertTrue(b.dtype == np.float64)
     self.assertTrue(a.dtype == np.float64)
     self.assertTrue(type(a) == type(b) == csc.csc_matrix)
Exemple #3
0
def makeLaplacianMatrixSolverIGLHard(VPos, ITris, anchorsIdx):
    VPosE = igl.eigen.MatrixXd(VPos)
    ITrisE = igl.eigen.MatrixXi(ITris)
    L = igl.eigen.SparseMatrixd()
    M = igl.eigen.SparseMatrixd()
    M_inv = igl.eigen.SparseMatrixd()
    igl.cotmatrix(VPosE,ITrisE,L)
    igl.massmatrix(VPosE,ITrisE,igl.MASSMATRIX_TYPE_VORONOI,M)
    igl.invert_diag(M,M_inv)
    L = M_inv*L
    deltaCoords = L*VPosE
    deltaCoords = np.array(deltaCoords)
    
    #Bi-laplacian
    Q = L.transpose()*L
def key_pressed(viewer, key, modifier):
    global V
    global U
    global F
    global L

    if key == ord('r') or key == ord('R'):
        U = V;
    elif key == ord(' '):

        # Recompute just mass matrix on each step
        M = igl.eigen.SparseMatrixd()

        igl.massmatrix(U,F,igl.MASSMATRIX_TYPE_BARYCENTRIC,M);

        # Solve (M-delta*L) U = M*U
        S = (M - 0.001*L)

        solver = igl.eigen.SimplicialLLTsparse(S)

        U = solver.solve(M*U)

        # Compute centroid and subtract (also important for numerics)
        dblA = igl.eigen.MatrixXd()
        igl.doublearea(U,F,dblA)

        print(dblA.sum())

        area = 0.5*dblA.sum()
        BC = igl.eigen.MatrixXd()
        igl.barycenter(U,F,BC)
        centroid = igl.eigen.MatrixXd([[0.0,0.0,0.0]])

        for i in range(0,BC.rows()):
            centroid += 0.5*dblA[i,0]/area*BC.row(i)

        U -= centroid.replicate(U.rows(),1)

        # Normalize to unit surface area (important for numerics)
        U = U / math.sqrt(area)
    else:
        return False

    # Send new positions, update normals, recenter
    viewer.data.set_vertices(U)
    viewer.data.compute_normals()
    viewer.core.align_camera_center(U,F)
    return True
Exemple #5
0
def conformal_flow(vertices, faces):
    vertices = normalize(np.copy(vertices))

    L = igl.cotmatrix(vertices, faces)

    itrs = 20
    time_step = .1

    for i in range(itrs):
        vertices = normalize(vertices)

        # this is a lumped mass matrix
        M = igl.massmatrix(vertices, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC)
        S = (M - time_step * L)
        b = M.dot(vertices)
        vertices = spsolve(S, b)

    vertices = normalize(vertices)

    print("flow error: " + str(get_error(vertices)))

    vertices = project_sphere(vertices)

    print("percentage of flipped faces: " +
          str(100 * get_flipped_normals(vertices, faces)))
    return vertices
 def test_harmonic(self):
     l = igl.cotmatrix(self.v1, self.f1)
     m = igl.massmatrix(self.v1, self.f1, igl.MASSMATRIX_TYPE_VORONOI)
     b = np.array([1, 2, 10, 7])
     bc = self.v1[b, :]
     k = 1
     w = igl.harmonic_weights_from_laplacian_and_mass(l, m, b, bc, k)
     self.assertTrue(w.flags.c_contiguous)
Exemple #7
0
def get_mass_matrix(v, f, area_type):
    if (area_type == 'voronoi') or (area_type == 'barycentric'):
        return igl.massmatrix(v, f, AREA_TYPES[area_type])
    elif area_type == 'mayer':
        return mayer_area(v, f)
    else:
        raise ValueError("Please choose one of the supported mass"
                        +f" matrix types:{list(AREA_TYPES.keys())}")
Exemple #8
0
def smooth_data(v, f, w):
    # Smoothing = Minimize curvature
    #
    # min_{p*}{E(p*)}
    #
    # E(p*) = sum_{p in points on mesh}{A_p * ((Lp*)^2 + w(p* - p)^2)},
    # where A_p is the voronoi region around point p
    #
    # Solve dE(p*)/dp* = 0
    # => (L'ML + wM)p* = (wM)p ~= Ax = b
    #
    # L = M^(-1)L_w, w = cotangent[c]/uniform[u]/...
    #
    # L_c defined in slides 5 page 66
    # M and L defined in slides 6 page 23

    # Constructing the squared Laplacian and squared Hessian energy
    # NOTE L_c is sparse
    # L_c = igl.cotmatrix(v, f) # discrete laplacian

    # L_c.data[np.isnan(L_c.data)] = 0
    # L_c.data[L_c.data > 1e7] = 1e7
    # L_c.data[L_c.data < -1e7] = -1e7

    # Uniform laplacian
    # adopted from here:
    # https://libigl.github.io/libigl-python-bindings/igl_docs/#cotmatrix
    adj = igl.adjacency_matrix(f)
    # Sum each row = number of neighbours
    adj_sum = np.squeeze(np.asarray(np.sum(adj, axis=1)))
    # Convert row sums into diagonal of sparse matrix
    adj_diag = sp.sparse.diags(adj_sum)
    # Build uniform laplacian
    L_u = adj - adj_diag

    # NOTE M is sparse
    M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)
    # Give corrupted triangles only very little weight
    mask = np.logical_or(np.isnan(M.data), M.data == 0)
    M.data[mask] = 1e-7

    M_inv = sp.sparse.diags(1 / M.diagonal())

    # NOTE L_c can become very weird when dealing with corrupted meshes
    # L = M_inv @ L_c
    L = M_inv @ L_u

    A = L.T @ M @ L + w * M
    # TODO lambda * ID ?
    A += sp.sparse.identity(A.shape[0])
    b = w * M @ v

    v_star = sp.sparse.linalg.spsolve(A, b)

    return v_star
Exemple #9
0
def canonical_rotation(orig_v, vertices, faces):
    weights, A = IRF(orig_v, vertices, faces, 3)

    Q = np.vstack([-weights[3], -weights[1], weights[2]]).T

    u, s, vh = np.linalg.svd(Q)

    # product here is reversed because the vertices are row vectors (ie: A * v -> v^T * A^T)
    orig_v = orig_v.dot(u)
    vertices = vertices.dot(vh.T)

    # project to sphere again because of numerical issues w rotation
    vertices = project_sphere(vertices)

    # pi rotations: yz, xz, xy
    rx = np.diag([1, -1, -1])
    ry = np.diag([-1, 1, -1])
    rz = np.diag([-1, -1, 1])

    M = igl.massmatrix(orig_v, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC)

    weights, A = IRF(orig_v, vertices, faces, 8)
    weights_x, A_x = IRF(orig_v.dot(rx), vertices.dot(rx), faces, 8)
    weights_y, A_y = IRF(orig_v.dot(ry), vertices.dot(ry), faces, 8)
    weights_z, A_z = IRF(orig_v.dot(rz), vertices.dot(rz), faces, 8)

    # l = [
    # sum(weights[4])+sum(weights[5])+sum(weights[7]),
    # sum(weights_x[4])+sum(weights_x[5])+sum(weights_x[7]),
    # sum(weights_y[4])+sum(weights_y[5])+sum(weights_y[7]),
    # sum(weights_z[4])+sum(weights_z[5])+sum(weights_z[7]),
    # ]

    l = [
        np.sum(np.sum(M.dot(A.dot(weights)), axis=0)),
        np.sum(np.sum(M.dot(A_x.dot(weights_x)), axis=0)),
        np.sum(np.sum(M.dot(A_y.dot(weights_y)), axis=0)),
        np.sum(np.sum(M.dot(A_z.dot(weights_z)), axis=0)),
    ]

    # print(np.sum(M.dot(A.dot(weights)), axis=0)),
    # print(np.sum(M.dot(A_x.dot(weights_x)), axis=0)),
    # print(np.sum(M.dot(A_y.dot(weights_y)), axis=0)),
    # print(np.sum(M.dot(A_z.dot(weights_z)), axis=0)),

    ind = l.index(max(l))

    if ind == 0:
        return orig_v, vertices
    elif ind == 1:
        return orig_v.dot(rx), vertices.dot(rx)
    elif ind == 2:
        return orig_v.dot(ry), vertices.dot(ry)
    elif ind == 3:
        return orig_v.dot(rz), vertices.dot(rz)
 def set_parameters(self, V, F):
     # reset
     self.reset()
     # compute property given V and F
     self.N = igl.per_vertex_normals(V, F)
     self.L = igl.cotmatrix(V, F)
     VA = igl.massmatrix(V, F, 0)
     self.VA = VA.diagonal()
     # get face adjacency list
     VF, NI = igl.vertex_triangle_adjacency(F, V.shape[0])
     adjFList = construct_adjacency_list(VF, NI)
     # arap
     self.K = igl.arap_rhs(V, F, d=3, energy=1)
     # they are all list since length can be different
     self.hEList = [None] * V.shape[0]
     self.WVecList = [None] * V.shape[0]
     self.dVList = [None] * V.shape[0]
     for i in range(0, V.shape[0]):
         adjF = adjFList[i]
         len_adjF = adjF.shape[0]
         self.hEList[i] = np.zeros((len_adjF * 3, 2), dtype=int)
         self.WVecList[i] = np.zeros(len_adjF * 3)
         self.dVList[i] = np.zeros((3, 3 * len_adjF))
         for j in range(0, len_adjF):
             vIdx = adjF[j]
             v0 = F[vIdx, 0]
             v1 = F[vIdx, 1]
             v2 = F[vIdx, 2]
             # half edge indices
             # hE = np.array([[v0, v1], [v1, v2], [v2, v0]])
             # self.hEList[i] = hE
             self.hEList[i][3 * j, 0] = v0
             self.hEList[i][3 * j, 1] = v1
             self.hEList[i][3 * j + 1, 0] = v1
             self.hEList[i][3 * j + 1, 1] = v2
             self.hEList[i][3 * j + 2, 0] = v2
             self.hEList[i][3 * j + 2, 1] = v0
             # weight vec
             self.WVecList[i][3 * j] = self.L[v0, v1]
             self.WVecList[i][3 * j + 1] = self.L[v1, v2]
             self.WVecList[i][3 * j + 2] = self.L[v2, v0]
         V_hE0 = V[self.hEList[i][:, 0], :]
         V_hE1 = V[self.hEList[i][:, 1], :]
         self.dVList[i] = np.transpose(V_hE1 - V_hE0)
         self.WVecList[i] = np.diag(self.WVecList[i])
     # other var
     numV = V.shape[0]
     self.zAll = np.random.rand(3, numV) * 2.0 - 1.0
     self.uAll = np.random.rand(3, numV) * 2.0 - 1.0
     self.zAll = np.zeros((3, numV))
     self.uAll = np.zeros((3, numV))
     self.rhoAll = np.full(numV, self.param.rhoInit)
Exemple #11
0
def normalize_area(vertices, faces, M=None):
    if M is None:
        M = igl.massmatrix(vertices, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC)
    A = get_area(vertices, faces)

    v_w = M.dot(vertices)

    centroid = np.sum(v_w, axis=0) / A
    vertices -= centroid

    vertices /= np.sqrt(A / (np.pi * 4))

    return vertices
Exemple #12
0
    def construct_matrices(self):
        """
        Construct FEM matrices
        """
        V = self.vertices
        F = self.faces
        # Compute gradient operator: #F*3 by #V
        G = igl.grad(V, F).tocoo()
        L = igl.cotmatrix(V, F).tocoo()
        N = igl.per_face_normals(V, F, np.array([0., 0., 0.]))
        A = igl.doublearea(V, F)
        A = A[:, np.newaxis]
        M = igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_VORONOI).tocoo()
        M = M.data
        # Compute latitude and longitude directional vector fields
        NS = np.reshape(G.dot(self.lat), [self.nf, 3], order='F')
        EW = np.cross(NS, N)
        # Compute F2V matrix (weigh by area)
        # adjacency
        i = self.faces.ravel()
        j = np.arange(self.nf).repeat(3)
        one = np.ones(self.nf * 3)
        adj = sparse.csc_matrix((one, (i, j)), shape=(self.nv, self.nf))
        tot_area = adj.dot(A)
        norm_area = A.ravel().repeat(3) / np.squeeze(tot_area[i])
        F2V = sparse.csc_matrix((norm_area, (i, j)), shape=(self.nv, self.nf))
        # Compute interpolation matrix
        if self.level > 0:
            intp = self.intp[self.nv_prev:]
            i = np.concatenate(
                (np.arange(self.nv), np.arange(self.nv_prev, self.nv)))
            j = np.concatenate((np.arange(self.nv_prev), intp[:, 0], intp[:,
                                                                          1]))
            ratio = np.concatenate(
                (np.ones(self.nv_prev), 0.5 * np.ones(2 * intp.shape[0])))
            intp = sparse.csc_matrix((ratio, (i, j)),
                                     shape=(self.nv, self.nv_prev))
        else:
            intp = sparse.csc_matrix(np.eye(self.nv))

        # Compute vertex mean matrix
        self.G = G  # gradient matrix
        self.L = L  # laplacian matrix
        self.N = N  # normal vectors (per-triangle)
        self.NS = NS  # north-south vectors (per-triangle)
        self.EW = EW  # east-west vectors (per-triangle)
        self.F2V = F2V  # map face quantities to vertices
        self.M = M  # mass matrix (area of voronoi cell around node. for integration)
        self.Seq = self._rotseq(self.vertices)
        self.Intp = intp
def features_extractor(classes,DNA_size=50):
    """Extract the features from the samples in the dataset. The type of features is the ShapeDNA.

    :param classes: list
        list of the classes
    :param DNA_size: int
        size of the feature vector
    """
    print("\nFeature extraction")
    
    for c in classes:
        print(c)
        for file in os.listdir(os.path.join('.', 'Dataset', c)):
            if file.endswith(".off"):
                print("\t", file, end=" ... ")
                # Load mesh
                v, f = igl.read_triangle_mesh(os.path.join('.', 'Dataset',c, file))
                M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)
                v = v / np.sqrt(M.sum())

                # Compute Laplacian
                L = -igl.cotmatrix(v, f)
                M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)
                # Compute EigenDecomposition
                try:
                    evals, evecs = sp.sparse.linalg.eigsh(L, DNA_size+2, M, sigma=0.0, which='LM', maxiter=1e9, tol=1.e-15)
                except:
                    evals, evecs = sp.sparse.linalg.eigsh(L + 1e-8 * sp.sparse.identity(v.shape[0]), DNA_size+2,
                                                          M, sigma=0.0, which='LM', maxiter=1e9, tol=1.e-15)
                # Shape DNA
                descriptors = evals[2:] / evals[1]
                # Save descriptor
                np.save(os.path.join('.', 'Dataset', c, file[:-4] + "_DNA"), descriptors, allow_pickle=False)

                print("done.")
    print("Finished")
def compute_cotan_laplacian(df):
    """
    compute mass matrix and cotan laplacian matrix
    :param df: pandas data frame
    :return L, M, in that order, CSR-sparse matrix
    """
    points = df2points(df).T
    assert points.shape[-1] == 3
    ch = ConvexHull(points)
    assert points.shape[0] == ch.points.shape[0]
    L = -csr_matrix(igl.cotmatrix(ch.points, ch.simplices))  # positive semidefiniteness
    # ISSUE: some areas are way too small such that igl ends up considering it exactly zero.
    # as a result, it's not even stored in the sparse matrix. coarsening needed
    M = csr_matrix(igl.massmatrix(ch.points, ch.simplices, igl.MASSMATRIX_TYPE_VORONOI))
    return L, M
Exemple #15
0
def IRF(orig_v, vertices, faces, max_degree=16):
    num_harm = max_degree**2
    sigma = 0.001

    theta = np.arccos(vertices[:, 2])
    phi = np.arctan2(vertices[:, 1], vertices[:, 0])

    orig_v = normalize_area(orig_v, faces)

    # weighted least squares with mass matrix to account for unequal mesh resolution.
    W = igl.massmatrix(orig_v, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC)

    weights = np.zeros((num_harm, 3))
    i = 0
    A = []

    residual = np.copy(orig_v)

    for l in range(0, max_degree):
        Y = []
        for m in range(-l, l + 1):
            y = sph_real(l, m, phi, theta)

            A.append(y)
            Y.append(y)

        smooth = 1  #np.exp(-sigma*l*(l+1))

        Y = np.vstack(Y).T

        #w = np.linalg.solve(Y.T.dot(Y),Y.T.dot(residual))
        w = np.linalg.solve(Y.T.dot(W.dot(Y)), Y.T.dot(W.dot(residual)))

        residual = residual - Y.dot(w)

        i1 = l**2
        i2 = (l + 1)**2
        weights[i1:i2] = smooth * w

    A = np.vstack(A).T

    #err = A.dot(weights) - orig_v
    #print("mean reconstruction error: " + str(np.mean(norm(err, axis=1))))

    return weights, A
Exemple #16
0
    def __init__(self, mesh, OUTPUT_PATH):
        utils.check_package_is_installed('igl')
        import igl

        logger.info('GeodesicsSolver')
        self.mesh = mesh
        self.OUTPUT_PATH = OUTPUT_PATH

        self.use_forwards_euler = True

        v, f = mesh.to_vertices_and_faces()
        v = np.array(v)
        f = np.array(f)

        # compute necessary data
        self.cotans = igl.cotmatrix_entries(v, f)  # compute_cotan_field(self.mesh)
        self.L = igl.cotmatrix(v, f)  # assemble_laplacian_matrix(self.mesh, self.cotans)
        self.M = igl.massmatrix(v, f)  # create_mass_matrix(mesh)
Exemple #17
0
def mobius_center(orig_v, vertices, faces):
    M = igl.massmatrix(orig_v, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC)

    for i in range(10):
        # center of mass
        mu = np.sum(M.dot(vertices), axis=0)

        err = norm(mu)
        print("mobius error: " + str(norm(mu)))
        if err < 1e-10:
            break

        # c = -J^-1 * mu
        J = compute_jacobian(M, vertices)
        c = -np.linalg.inv(J).dot(mu)

        # compute inversion
        vertices = np.divide((vertices + c),
                             norm(vertices + c, axis=1).reshape((-1, 1))**2)
        vertices = (1 - norm(c)**2) * vertices + c

    return vertices
Exemple #18
0
 def mass_matrix(self):
     if ('mass_matrix' not in self.cache):
         self.cache['mass_matrix'] = igl.massmatrix(
             self.vertices, self.faces, igl.MASSMATRIX_TYPE_VORONOI)
     return self.cache['mass_matrix']
Exemple #19
0
def get_area(vertices, faces):
    M = igl.massmatrix(vertices, faces, igl.MASSMATRIX_TYPE_BARYCENTRIC)
    return np.sum(M.dot(np.ones(vertices.shape[0])), axis=0)
viewer = igl.viewer.Viewer()
viewer.data.set_mesh(V, F)
viewer.core.show_lines = False
viewer.callback_key_down = key_down

# One fixed point on belly
b = igl.eigen.MatrixXi([[2556]])
bc = igl.eigen.MatrixXd([[1]])

# Construct Laplacian and mass matrix
L = igl.eigen.SparseMatrixd()
M = igl.eigen.SparseMatrixd()
Minv = igl.eigen.SparseMatrixd()

igl.cotmatrix(V, F, L)
igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_VORONOI, M)
igl.invert_diag(M, Minv)

# Bi-Laplacian
Q = L.transpose() * (Minv * L)

# Zero linear term
B = igl.eigen.MatrixXd.Zero(V.rows(), 1)

# Lower and upper bound
lx = igl.eigen.MatrixXd.Zero(V.rows(), 1)
ux = igl.eigen.MatrixXd.Ones(V.rows(), 1)

# Equality constraint constrain solution to sum to 1
Beq = igl.eigen.MatrixXd([[0.08]])
Aeq = M.diagonal().transpose().sparseView()
viewer = igl.viewer.Viewer()
viewer.data.set_mesh(V, F)
viewer.core.show_lines = False
viewer.callback_key_down = key_down

# One fixed point on belly
b  = igl.eigen.MatrixXi([[2556]])
bc = igl.eigen.MatrixXd([[1]])

# Construct Laplacian and mass matrix
L = igl.eigen.SparseMatrixd()
M = igl.eigen.SparseMatrixd()
Minv = igl.eigen.SparseMatrixd()

igl.cotmatrix(V,F,L)
igl.massmatrix(V,F,igl.MASSMATRIX_TYPE_VORONOI,M);
igl.invert_diag(M,Minv)

# Bi-Laplacian
Q = L.transpose() * (Minv * L)

# Zero linear term
B = igl.eigen.MatrixXd.Zero(V.rows(),1)

# Lower and upper bound
lx = igl.eigen.MatrixXd.Zero(V.rows(),1)
ux = igl.eigen.MatrixXd.Ones(V.rows(),1)

# Equality constraint constrain solution to sum to 1
Beq = igl.eigen.MatrixXd([[0.08]])
Aeq = M.diagonal().sparseView().transpose()
c = 0
bbd = 1.0
twod = False

if not igl.read_triangle_mesh("../tutorial/shared/beetle.off",V,F):
    print("failed to load mesh")

twod = V.col(2).minCoeff() == V.col(2).maxCoeff()
bbd = (V.colwiseMaxCoeff() - V.colwiseMinCoeff()).norm()

L = igl.eigen.SparseMatrixd()
M = igl.eigen.SparseMatrixd()

igl.cotmatrix(V,F,L)
L = -L
igl.massmatrix(V,F,igl.MASSMATRIX_TYPE_DEFAULT,M)
k = 5

D = igl.eigen.MatrixXd()
if not igl.eigs(L,M,k+1,igl.EIGS_TYPE_SM,U,D):
    print("Eigs failed.")

U = (U-U.minCoeff())/(U.maxCoeff()-U.minCoeff());

viewer = igl.viewer.Viewer()

def key_down(viewer,key,mod):
    global U, c
    
    if key == ord(' '):
        U = U.rightCols(k)
Exemple #23
0
import polyscope
import igl
import numpy as np
import polyscope as ps
import scipy

# https://www.cs.cmu.edu/~kmcrane/Projects/ModelRepository/spot.zip
path = "/Users/cbabraham/data/mesh/spot/spot_triangulated.obj"

# verts and faces
V,F = igl.read_triangle_mesh(path)

# cotan laplacian and mass matrix
L = igl.cotmatrix(V,F)
M = igl.massmatrix(V,F)

# eigenvectors of the laplacian
_, E = scipy.sparse.linalg.eigsh(L, 1000, M, which='BE')

C = V.T.dot(E) # inner product of each eigenvector and the mesh
R = np.einsum('km,nm->nk',C,E)

# V: n x k verts (k = 3)
# E: n x m eigenvectors, to use as bases
# C: k x m basis weights
# R: n x k synthesized vertices from weighted bases

ps.init()
original = ps.register_surface_mesh("mesh", V,F)
Exemple #24
0
def makeLaplacianMatrixSolverIGLSoft(VPos, ITris, anchorsIdx, anchorWeights, makeSolver = True):
    VPosE = igl.eigen.MatrixXd(VPos)
    ITrisE = igl.eigen.MatrixXi(ITris)
    '''
    #Doing this check slows things down by more than a factor of 2 (convert to numpy to make faster?)
    for f in range(ITrisE.rows()):
        v_list = ITrisE.row(f)
        v1 = VPosE.row(v_list[0])
        v2 = VPosE.row(v_list[1])
        v3 = VPosE.row(v_list[2])
        if (v1-v2).norm() < 1e-10 and (v1-v3).norm() < 1e-10 and (v2-v3).norm() < 1e-10:
            print 'zero area triangle!',f
    '''
    L = igl.eigen.SparseMatrixd()
    M = igl.eigen.SparseMatrixd()
    M_inv = igl.eigen.SparseMatrixd()
    igl.cotmatrix(VPosE,ITrisE,L)
    igl.massmatrix(VPosE,ITrisE,igl.MASSMATRIX_TYPE_VORONOI,M)
    #np.set_printoptions(threshold='nan')
    #print 'what is M?',M.diagonal()
    igl.invert_diag(M,M_inv)
    #L = M_inv*L
    deltaCoords = (M_inv*L)*VPosE

    #TODO: What to do with decaying_anchor_weights?
    '''
    anchor_dists = []
    for i in range(VPosE.rows()):
        anchor_dists.append(min([ (VPosE.row(i)-VPosE.row(j)).norm() for j in anchorsIdx ]))
    max_anchor_dist = max(anchor_dists)
    # assume linear weighting for anchor weights -> we are 0 at the anchors, anchorWeights at max_anchor_dist
    decaying_anchor_weights = []
    for anchor_dist in anchor_dists:
        decaying_anchor_weights.append(anchorWeights*(anchor_dist/max_anchor_dist))
    '''
    
    solver = None
    if makeSolver:
        Q = L*(M_inv*M_inv)*L
        #Now add in sparse constraints
        diagTerms = igl.eigen.SparseMatrixd(VPos.shape[0], VPos.shape[0])
        # anchor points
        for a in anchorsIdx:
            diagTerms.insert(a, a, anchorWeights)
        # off points
        '''
        for adx,decay_weight in enumerate(decaying_anchor_weights):
            if decay_weight == 0:
                diagTerms.insert(adx, adx, anchorWeights)
            else:
                diagTerms.insert(adx, adx, decay_weight)
        '''
        Q = Q + diagTerms
        Q.makeCompressed()
        start_time = time.time()
        solver = igl.eigen.SimplicialLLTsparse(Q)
        #solver = igl.eigen.CholmodSupernodalLLT(Q)
        end_time = time.time()
        print 'factorization elapsed time:',end_time-start_time,'seconds'
    

    return (L, M_inv, solver, np.array(deltaCoords))
c = 0
bbd = 1.0
twod = False

if not igl.read_triangle_mesh("../../tutorial/shared/beetle.off", V, F):
    print("failed to load mesh")

twod = V.col(2).minCoeff() == V.col(2).maxCoeff()
bbd = (V.colwiseMaxCoeff() - V.colwiseMinCoeff()).norm()

L = igl.eigen.SparseMatrixd()
M = igl.eigen.SparseMatrixd()

igl.cotmatrix(V, F, L)
L = -L
igl.massmatrix(V, F, igl.MASSMATRIX_TYPE_DEFAULT, M)
k = 5

D = igl.eigen.MatrixXd()
if not igl.eigs(L, M, k + 1, igl.EIGS_TYPE_SM, U, D):
    print("Eigs failed.")

U = (U - U.minCoeff()) / (U.maxCoeff() - U.minCoeff())

viewer = igl.viewer.Viewer()


def key_down(viewer, key, mod):
    global U, c

    if key == ord(' '):