Geometry Processing and Visualization¶
Installation¶
All libraries (igl, meshplot, wildmeshing, polyfempy) can be installed from Conda forge with conda install -c conda-forge <lib>
.
Import¶
import igl import meshplot as mp import numpy as np
Hello World¶
v, f = igl.read_triangle_mesh("data/bunny.obj") mp.plot(v, f, v[:, 0])
Data Representation¶
Meshes are represented by a numpy array of vertex coordinates (nx3) and a numpy array of face indices (mx3) and can be loaded from 3D file formats.
v: np.array # Array of vertex coordinates (nx3) f: np.array # Array of face indices (mx3) v, f = igl.read_triangle_mesh("data/bunny.obj") print(v) print(f)
[[-0.0260146 0.112578 0.0363871 ] [-0.0321783 0.174119 -0.00263321] [-0.080718 0.152855 0.0302446 ] ... [-0.023099 0.156978 -0.00584018] [-0.0713101 0.15068 -0.0435721 ] [-0.0396435 0.152397 -0.00721968]] [[2784 2497 2027] [1077 225 1060] [ 425 450 381] ... [3086 3203 3162] [3086 3162 3151] [3086 3151 3085]]
Visualizing Surfaces and Pointclouds¶
mp.plot(v, f, c=v[:, 0])
mp.plot(v, c=v[:, 0])
Scalar and Vector Field Visualization¶
# Calculating properties of the mesh nf = igl.per_face_normals(v, f, np.array([0.0, 0.0, 0.0])) nfv = np.linalg.norm(nf * 0.5 + 0.5, axis=1) nv = igl.per_vertex_normals(v, f) area = igl.doublearea(v, f) avg = igl.avg_edge_length(v, f) v1, v2, k1, k2 = igl.principal_curvature(v, f) mean_curv = 0.5 * (k1 + k2) # Face normals d = mp.subplot(v, f, c=nfv, s=[2, 2, 0]) # Vertex normals as lines mp.subplot(v, c=nv, s=[2, 2, 1], data=d) d.rows[0][1].add_lines(v, v + nv * avg) # Mean curvature + directions mp.subplot(v, f, c=mean_curv, s=[2, 2, 2], data=d) d.rows[1][0].add_lines(v + v1 * avg/3, v - v1 * avg/3, shading={"line_color": "red"}) d.rows[1][0].add_lines(v + v2 * avg/3, v - v2 * avg/3, shading={"line_color": "green"}) # Triangle area mp.subplot(v, f, c=-area, s=[2, 2, 3], shading={"metalness": 0.0, "roughness": 1.0}, data=d)
Texture Mapping¶
vc, fc = igl.read_triangle_mesh("data/camelhead.off") # Find the open boundary bnd = igl.boundary_loop(fc) # Map the boundary to a circle, preserving edge proportions bnd_uv = igl.map_vertices_to_circle(vc, bnd) # Harmonic parametrization for the internal vertices uv = igl.harmonic_weights(vc, fc, bnd, bnd_uv, 1) # Plotting the results p = mp.subplot(vc, fc, uv=uv, s=[1, 2, 0]) mp.subplot(uv, fc, uv=uv, shading={"wireframe": True, "wire_color": "red", "wire_width": 1.0}, s=[1, 2, 1], data=p) # Adding the boundary p.rows[0][0].add_points(vc[bnd], shading={"point_size": 0.1}); lines = np.vstack([bnd, np.roll(bnd, -1)]).T p.rows[0][0].add_edges(vc, lines, shading={"line_width": 1.0, "line_color": "red"}); p
Interactive Geometry Modification¶
from scipy.sparse.linalg import spsolve v, f = igl.read_triangle_mesh("data/cow.off") l = igl.cotmatrix(v, f) n = igl.per_vertex_normals(v, f) * 0.5 + 0.5 c = np.linalg.norm(n, axis=1) p = mp.plot(v, f, c, return_plot=True) # Precalculate intermediate states vs = [v] cs = [c] for i in range(10): m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_BARYCENTRIC) s = (m - 0.001 * l) b = m.dot(v) v = spsolve(s, m.dot(v)) n = igl.per_vertex_normals(v, f)*0.5+0.5 c = np.linalg.norm(n, axis=1) vs.append(v) cs.append(c) # Add interactive visulization @mp.interact(level=(0, 9)) def mcf(level=0): p.update_object(vertices=vs[level], colors=cs[level]) p
Saving Results and Offline Visualization¶
# Save the previous result p.save("test1.html") # Load a new mesh v, f = igl.read_triangle_mesh("data/bumpy.off") # Switch to offline plotting mp.offline() mp.plot(v, f, c=np.random.rand(*f.shape)) # Switch to jupyter plotting mp.jupyter() p = mp.plot(v, f, c=np.random.rand(*f.shape), shading={"width":900, "height": 1000}, return_plot=True) p.add_mesh(v + 5, f, c=v[:,1]); p.add_points(v - 5, c=v[:,2], shading={"point_size": 1.0}) p.save("test2.html") p
Plot saved to file test1.html. Plot saved to file a96186d9-a92c-45fb-8266-c10648ee498d.html. Renderer(camera=PerspectiveCamera(aspect=0.9, children=(DirectionalLight(color='white', intensity=0.6, positio… Plot saved to file test2.html.
Advanced Examples¶
import scipy as sp v, f = igl.read_triangle_mesh("data/beetle.off") l = -igl.cotmatrix(v, f) m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI) d, u = sp.sparse.linalg.eigsh(l, 10, m, sigma=0, which="LM") u = (u - np.min(u)) / (np.max(u) - np.min(u)) bbd = 0.5 * np.linalg.norm(np.max(v, axis=0) - np.min(v, axis=0)) p = mp.plot(v, f, bbd * u[:, 0], shading={"wireframe":False, "flat": False}, return_plot=True) @mp.interact(ev=[("EV %i"%i, i) for i in range(10)]) def sf(ev): p.update_object(colors=u[:, ev]) p
v, f = igl.read_triangle_mesh("data/bunny.obj") # Select a vertex from which the distances should be calculated vs = np.array([0]) # All vertices are the targets vt = np.arange(v.shape[0]) d = igl.exact_geodesic(v, f, vs, vt) # The function should be 1 on each integer coordinate c = np.abs(np.sin((d / 0.04 * np.pi))) mp.plot(v, f, c, shading={"wireframe": False}, return_plot=True)
Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(-0.016860…
v, f = igl.read_triangle_mesh("data/decimated-max.obj") v[:,[0, 2]] = v[:,[2, 0]] # Swap X and Z axes u = v.copy() s = igl.read_dmat("data/decimated-max-selection.dmat") b = np.array([[t[0] for t in [(i, s[i]) for i in range(0, v.shape[0])] if t[1] >= 0]]).T ## Boundary conditions directly on deformed positions u_bc = np.zeros((b.shape[0], v.shape[1])) v_bc = np.zeros((b.shape[0], v.shape[1])) for bi in range(b.shape[0]): v_bc[bi] = v[b[bi]] if s[b[bi]] == 0: # Don't move handle 0 u_bc[bi] = v[b[bi]] elif s[b[bi]] == 1: # Move handle 1 down u_bc[bi] = v[b[bi]] + np.array([[0, -50, 0]]) else: # Move other handles forward u_bc[bi] = v[b[bi]] + np.array([[-25, 0, 0]]) p = mp.plot(v, f, s, shading={"colormap": "tab10"}, return_plot=True) @mp.interact(deformation_field=True, step=(0.0, 2.0)) def update(deformation_field, step=0.0): # Determine boundary conditions u_bc_anim = v_bc + step * (u_bc - v_bc) if deformation_field: d_bc = u_bc_anim - v_bc d = igl.harmonic_weights(v, f, b, d_bc, 2) u = v + d else: u = igl.harmonic_weights(v, f, b, u_bc_anim, 2) p.update_object(vertices=u) p