diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..7819c587 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,58 @@ +name: docs + +# Build the Python-bindings documentation site. The API reference is generated +# from the installed package's .pyi stubs, so it always matches the bindings. +# On pushes to main a preview is deployed to this repo's gh-pages; the canonical +# home is the unified libigl.github.io site, which can consume the same generator. + +on: + push: + branches: [main] + pull_request: + paths: + - 'website/**' + - '.github/workflows/docs.yml' + - 'src/**' + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install the bindings and docs tooling + run: | + python -m pip install --upgrade pip + python -m pip install libigl + python -m pip install -r website/requirements.txt + + - name: Fetch libigl headers and Doxygen index (for C++ cross-links) + run: | + git clone --depth 1 https://github.com/libigl/libigl.git _libigl + curl -sSL https://libigl.github.io/dox/files.html -o _dox_files.html || true + + - name: Generate the API reference + run: | + python website/generate_api.py \ + --package "$(python -c 'import igl, os; print(os.path.dirname(igl.__file__))')" \ + --igl-include _libigl/include \ + --dox-index _dox_files.html \ + --out website/docs/api + + - name: Build the site + run: cd website && python -m mkdocs build --strict + + - name: Deploy preview to gh-pages + if: github.event_name != 'pull_request' + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: website/site diff --git a/.gitignore b/.gitignore index 782f7d80..868894d0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,9 @@ bin_rel build build-full -tutorial/data -tutorial/.ipynb_* -tutorial-old/.ipynb_* +# Generated documentation (api pages are produced by website/generate_api.py) +website/docs/api/ +website/site/ test.obj pyigl.cpython* diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 348c91c9..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,70 +0,0 @@ -site_name: igl -site_url: 'https://libigl.github.io/' -repo_name: 'libigl-python-bindings' -repo_url: 'https://github.com/libigl/libigl-python-bindings' -site_description: "Simple Python geometry processing library" -# strict: true -docs_dir: 'tutorial' -edit_uri: 'edit/master/tutorial/' -remote_branch: 'gh-pages' -theme: - name: material - favicon: 'favicon.ico' - icon: '' - palette: - primary: 'light blue' - accent: 'blue' -extra: - social: - - icon: fontawesome/brands/github-alt - link: 'https://github.com/libigl/libigl-python-bindings' -markdown_extensions: - - codehilite - - footnotes - - admonition - - toc: - permalink: true - - markdown.extensions.smarty - - markdown.extensions.toc: - permalink: true - - pymdownx.arithmatex - - pymdownx.betterem: - smart_enable: all - - pymdownx.caret - - pymdownx.critic - - pymdownx.details - - pymdownx.inlinehilite - - pymdownx.magiclink: - repo_url_shorthand: true - repo_url_shortener: true - user: libigl-python-bindings - repo: libigl-python-bindings - - pymdownx.mark - - pymdownx.smartsymbols - - pymdownx.superfences - - pymdownx.tasklist: - custom_checkbox: true - - pymdownx.tilde -plugins: - - mknotebooks: - execute: true - preamble: "tutorial/plot_to_md.py" - timeout: -1 - - exclude: - glob: - - data/* - - "tutorials.ipynb" -extra_javascript: - - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML' -nav: - - Home: index.md - - Tutorial: - - Chapter 0: tut-chapter0.ipynb - - Chapter 1: tut-chapter1.ipynb - - Chapter 2: tut-chapter2.ipynb - - Chapter 3: tut-chapter3.ipynb - - Chapter 4: tut-chapter4.ipynb - - Chapter 5: tut-chapter5.ipynb - - Chapter 6: tut-chapter6.ipynb - - Docs: igl_docs.md - - Contributing: contributing.md diff --git a/src/circumradius.cpp b/src/circumradius.cpp index ce21051a..6f3e4420 100644 --- a/src/circumradius.cpp +++ b/src/circumradius.cpp @@ -23,7 +23,7 @@ namespace pyigl void bind_circumradius(nb::module_ &m) { m.def("circumradius", &pyigl::circumradius, - ""_a, + "V"_a, "F"_a, R"(Compute the circumradius of each triangle in a mesh (V,F) @param[in] V #V by dim list of mesh vertex positions diff --git a/tutorial/exporter.py b/tutorial/exporter.py deleted file mode 100644 index 62c3c026..00000000 --- a/tutorial/exporter.py +++ /dev/null @@ -1,304 +0,0 @@ -import pydoc -import meshplot -import re -import queue -import tempfile - -packages = queue.Queue() -packages.put("igl") - -docs = "" - - -pattens_to_skip = [ - "R90 ", - "M(e,e)", - "for(int", - "if(", - "assert(", - "E(e,1))", - " F(f, :) ", - "F(f,:) ", - "cur_energy(OPTIONAL)", - "d(A, B) = max(", - "d(A,B)", - "per_face_normals(V,F,Vector3d(1,1,1)", - "the number of positively", - "trace(", - "Z(known,:)", - "kron(ones(", - "E(i,K(i))", - "I(i) == E(J(i),K(i", - "FF(I,:) = ", - "f(X)" -] - -def format_data(data): - global docs - docs += "\n| | |\n|-|-|\n" - if "Parameters" in data: - docs += "|Parameters| {} |\n".format(data["Parameters"].strip().replace("\n", "
").replace("#", r"\#")) - if "Returns" in data and len(data["Returns"].strip()) > 0: - docs += "|Returns| {} |\n".format(data["Returns"].strip().replace("\n", "
").replace("#", r"\#")) - if "See also" in data and len(data["See also"].strip()) > 0 and data["See also"].strip() != "None": - docs += "|See also| {} |\n".format(data["See also"].strip().replace("\n", "
").replace("#", r"\#")) - if "Notes" in data and len(data["Notes"].strip()) > 0 and data["Notes"].strip() != "None": - docs += "|Notes| {} |\n".format(data["Notes"].strip().replace("\n", "
").replace("#", r"\#")) - if "Examples" in data and len(data["Examples"].strip()) > 0: - docs += "\n**Examples**\n```python\n{}\n```\n".format(data["Examples"].strip().replace(">>> ", "").replace(">>>", "")) - - docs += "\n\n" - -prevvv = None - -while not packages.empty(): - package = packages.get() - - if prevvv == package: - continue - - prevvv = package - - with tempfile.NamedTemporaryFile(suffix=".md") as tmp_file: - with open(tmp_file.name, "w") as f: - pydoc.doc(package,output=f) - - with open(tmp_file.name, "r") as f: - lines = f.read() - - is_function = False - - if "PACKAGE CONTENTS" in lines: - process = False - - for line in iter(lines.splitlines()): - line = line.strip() - - if len(line) == 0: - continue - - if "PACKAGE CONTENTS" in line: - process = True - continue - - if "FUNCTIONS" in line: - is_function = True - continue - - if line[0] == '|': - continue - - if "FILE" in line: - is_function = False - break - - if "DATA" in line: - is_function = False - - if not process: - continue - if len(line) <= 0: - continue - - if is_function: - is_func = re.match(r'^\w+\(.*\)', line) - if is_func: - packages.put(package + "." + line.split('(', 1)[0]) - else: - if line != "pyigl": - packages.put(package + "." + line) - - continue - if "CLASSES" in lines: - process = False - - - for line in iter(lines.splitlines()): - line = line.strip() - - if "CLASSES" in line: - process = True - continue - - if "FILE" in line: - break - - if not process: - continue - if len(line) <= 0: - continue - - if "class " in line: - break - if "builtins.object" in line: - continue - - packages.put(package + "." + line) - - continue - - lines = lines.replace("pybind11_builtins.pybind11_object", "") - lines = lines.replace("builtins.object", "") - lines = lines.replace("|", "") - # lines = lines.replace("class ", "## class ") - lines = lines.replace("method of builtins.PyCapsule instance", "") - lines = lines.replace("self, ", "") - lines = lines.replace("self", "") - lines = lines.replace(" -> None", "") - lines = lines.replace("[float64[m, n]]", "") - lines = lines.replace("[int32[m, n]]", "") - - tmp = "" - - skipping = False - next_mark = False - skip_next = False - - for line in iter(lines.splitlines()): - line = line.strip() - if skip_next: - skip_next = False - continue - if len(line) <= 0: - continue - - if "Python Library Documentation" in line: - continue - - if "Methods inherited" in line: - continue - - if "Overloaded function." in line: - continue - - if "Method resolution order" in line: - skipping = True - - if "Methods defined here" in line: - skipping = False - continue - - if "Data and other attributes defi" in line: - continue - - if "Data descriptors defined" in line: - continue - - if "-----------------------------" in line: - continue - - if re.match(r"__\w+", line): - skip_next = True - continue - - if skipping: - continue - - if re.match(r'\w+\(\.\.\.\)', line): - next_mark = True - continue - - if "class" in line: - line = line.replace("()", "") - - if re.match(r"\d\. .+", line): - line = re.sub(r"\d\. ", "", line) - line.strip() - next_mark = True - - if "for(int" in line: - print(line) - - if next_mark or (re.match(r'\w+\(.*\)', line) and not any([line for pattern in pattens_to_skip if(pattern in line)])): - # (not "R90 " in line) and (not "M(e,e)" in line)) and (not "for(int" in line) and (not "if(" in line) and (not "assert(" in line) and (not "E(e,1))" in line) and (not " F(f, :) " in line) and (not "cur_energy(OPTIONAL)" in line) and (not "d(A, B) = max(" in line): - next_mark = False - line = "**`" + line + "`**" - - if "class " in line: - line = line.replace(package + " = ", "") - - - - - tmp += line + "\n\n" - - docs += tmp + "\n\n\n" - # break - - -index = docs.find("FUNCTIONS") -docs = docs[index+10:] - -index = docs.find("igl/helpers.py") -index = docs.find("\n\n**", index) -docs = docs[index:] - -docs = docs.replace("2/3", "2 / 3") -docs = docs.replace("3/4", "3 / 4") - -docs = docs.replace(" -> handle", "") -docs = docs.replace(" -> object", "") -docs = re.sub(r" -> Tuple\[.*\]", "", docs) -docs = docs.replace("numpy.dtype str type", "dtype") -docs = docs.replace( - "std::__1::vector >, std::__1::allocator > > >", "vector>") -docs = docs.replace( - "std::__1::function)>", "lambda function") - -docs = docs.replace("scipy.sparse.csr_matrix scipy.sparse.csc_matrix", "sparse_matrix") - -tmp = docs - -data = None -key = None - -docs = "" -for line in iter(tmp.splitlines()): - line = line.strip() - if len(line) <= 0: - continue - if line.startswith("----"): - continue - if re.match(r'.+\(\.\.\.\)', line): - continue - - if "class" in line: - if data: - format_data(data) - data = None - docs += "\n" + line +"\n" - continue - - if line.startswith("**`"): - if data: - format_data(data) - - data = None - title = line.replace("**`", "") - title = title[:title.find("(")] - docs += "### " + title + "\n" - docs += line + "\n\n" - continue - - if line == "Parameters" or line == "Returns" or line == "See also" or line == "Notes" or line == "Examples": - key = line - if data is None: - data = {} - data[key] = "" - continue - - if data is None: - docs += line + "\n" - continue - - data[key] += line + "\n" - - - - -docs = docs.replace("class ", "## class ") - -docs = "## Functions\n" + docs - -with open("igl_docs.md", "w") as f: - f.write(docs) diff --git a/tutorial/igl_docs.md b/tutorial/igl_docs.md deleted file mode 100644 index 221e2b54..00000000 --- a/tutorial/igl_docs.md +++ /dev/null @@ -1,3074 +0,0 @@ -## Functions -### active_set -**`active_set(A: sparse_matrix, B: array, known: array, Y: array, Aeq: sparse_matrix, Beq: array, Aieq: sparse_matrix, Bieq: array, lx: array, ux: array, Auu_pd: bool = False, max_iter: int = 100, inactive_threshold: float = 1e-14, constraint_threshold: float = 1e-14, solution_diff_threshold: float = 1e-14)`** - -ACTIVE_SET Minimize quadratic energy -0.5*Z'*A*Z + Z'*B + C with constraints -that Z(known) = Y, optionally also subject to the constraints Aeq*Z = Beq, -and further optionally subject to the linear inequality constraints that -Aieq*Z <= Bieq and constant inequality constraints lx <= x <= ux - -| | | -|-|-| -|Parameters| A n by n matrix of quadratic coefficients
B n by 1 column of linear coefficients
known list of indices to known rows in Z
Y list of fixed values corresponding to known rows in Z
Aeq meq by n list of linear equality constraint coefficients
Beq meq by 1 list of linear equality constraint constant values
Aieq mieq by n list of linear inequality constraint coefficients
Bieq mieq by 1 list of linear inequality constraint constant values
lx n by 1 list of lower bounds [] implies -Inf
ux n by 1 list of upper bounds [] implies Inf
params struct of additional parameters (see below)
Z if not empty, is taken to be an n by 1 list of initial guess values (see output) | -|Returns| Z n by 1 list of solution values
Returns SOLVER_STATUS_CONVERGED = 0, SOLVER_STATUS_MAX_ITER = 1, SOLVER_STATUS_ERROR = 2, | -|Notes| For a harmonic solve on a mesh with 325K facets, matlab 2.2 secs, igl / min_quad_with_fixed.h 7.1 secs
Known Bugs : rows of[Aeq; Aieq] **must **be linearly independent.Should be using QR decomposition otherwise : http : //www.okstate.edu/sas/v8/sashtml/ormp/chap5/sect32.htm | - - -### adjacency_list -**`adjacency_list(f: array)`** - -Constructs the graph adjacency list of a given mesh (v, f) - -| | | -|-|-| -|Parameters| f : \#f by dim array of fixed dimensional (e.g. triangle (\#f by 3),
tet (\#f by 4), quad (\#f by 4), etc...) mesh faces | -|Returns| list of lists containing at index i the adjacent vertices of vertex i | -|See also| adjacency_matrix | - -**Examples** -```python -# Mesh in (v, f) -a = mesh_adjacency_list(f) -``` - - -### adjacency_matrix -**`adjacency_matrix(f: array)`** - -Constructs the graph adjacency matrix of a given mesh (v, f). - -| | | -|-|-| -|Parameters| f : \#f by dim list of mesh simplices | -|Returns| a : max(f) by max(f) cotangent matrix, each row i corresponding to v(i, :) | -|See also| adjacency_list, edges, cotmatrix, diag | - -**Examples** -```python -# Mesh in (v, f) -a = adjacency_matrix(f) -# Sum each row -a_sum = np.sum(a, axis=1) -# Convert row sums into diagonal of sparse matrix -a_diag = diag(a_sum) -# Build uniform laplacian -u = a - a_diag -``` - - -### all_pairs_distances -**`all_pairs_distances(u: array, v: array, squared: bool)`** - -compute distances between each point i in V and point j in U - -| | | -|-|-| -|Parameters| V \#V by dim list of points
U \#U by dim list of points
squared whether to return squared distances | -|Returns| D \#V by \#U matrix of distances, where D(i,j) gives the distance or squareed distance between V(i,:) and U(j,:) | - -**Examples** -```python -D = all_pairs_distances(u,v) -``` - - -### ambient_occlusion -**`ambient_occlusion(v: array, f: array, p: array, n: array, num_samples: int)`** - - -| | | -|-|-| -|Parameters| V \#V by 3 list of mesh vertex positions
F \#F by 3 list of mesh face indices into V
P \#P by 3 list of origin points
N \#P by 3 list of origin normals | -|Returns| S \#P list of ambient occusion values between 1 (fully occluded) and 0 (not occluded) | - - -### arap_linear_block -**`arap_linear_block(v: array, f: array, d: int, energy: int)`** - -Constructs a block of the matrix which constructs the -linear terms of a given arap energy. When treating rotations as knowns -(arranged in a column), then this constructs Kd of K such that the linear -portion of the energy is as a column: -K * R = [Kx Z ... Ky Z ... -Z Kx ... Z Ky ... -... ] -These blocks are also used to build the "covariance scatter matrices". -Here we want to build a scatter matrix that multiplies against positions -(treated as known) producing covariance matrices to fit each rotation. -Notice that in the case of the RHS of the poisson solve the rotations are -known and the positions unknown, and vice versa for rotation fitting. -These linear block just relate the rotations to the positions, linearly in -each. - -| | | -|-|-| -|Parameters| v : \#v by dim list of initial domain positions
f : \#f by \#simplex size list of triangle indices into V
d : coordinate of linear constructor to build | -|Returns| \#v by \#v/\#f block of the linear constructor matrix corresponding to coordinate d | -|See also| arap, arap_dof | - - -### arap_linear_block_elements -**`arap_linear_block_elements(v: array, f: array, d: int)`** - -Constructs a block of the matrix which constructs the -linear terms of a given arap energy. When treating rotations as knowns -(arranged in a column), then this constructs Kd of K such that the linear -portion of the energy is as a column: -K * R = [Kx Z ... Ky Z ... -Z Kx ... Z Ky ... -... ] -These blocks are also used to build the "covariance scatter matrices". -Here we want to build a scatter matrix that multiplies against positions -(treated as known) producing covariance matrices to fit each rotation. -Notice that in the case of the RHS of the poisson solve the rotations are -known and the positions unknown, and vice versa for rotation fitting. -These linear block just relate the rotations to the positions, linearly in -each. - -| | | -|-|-| -|Parameters| v : \#v by dim list of initial domain positions
f : \#f by \#simplex size list of triangle indices into V
d : coordinate of linear constructor to build | -|Returns| \#v by \#v/\#f block of the linear constructor matrix corresponding to coordinate d | -|See also| arap, arap_dof | - - -### arap_linear_block_spokes -**`arap_linear_block_spokes(v: array, f: array, d: int)`** - -Constructs a block of the matrix which constructs the -linear terms of a given arap energy. When treating rotations as knowns -(arranged in a column), then this constructs Kd of K such that the linear -portion of the energy is as a column: -K * R = [Kx Z ... Ky Z ... -Z Kx ... Z Ky ... -... ] -These blocks are also used to build the "covariance scatter matrices". -Here we want to build a scatter matrix that multiplies against positions -(treated as known) producing covariance matrices to fit each rotation. -Notice that in the case of the RHS of the poisson solve the rotations are -known and the positions unknown, and vice versa for rotation fitting. -These linear block just relate the rotations to the positions, linearly in -each. - -| | | -|-|-| -|Parameters| v : \#v by dim list of initial domain positions
f : \#f by \#simplex size list of triangle indices into V
d : coordinate of linear constructor to build | -|Returns| \#v by \#v/\#f block of the linear constructor matrix corresponding to coordinate d | -|See also| arap, arap_dof | - - -### arap_linear_block_spokes_and_rims -**`arap_linear_block_spokes_and_rims(v: array, f: array, d: int)`** - -Constructs a block of the matrix which constructs the -linear terms of a given arap energy. When treating rotations as knowns -(arranged in a column), then this constructs Kd of K such that the linear -portion of the energy is as a column: -K * R = [Kx Z ... Ky Z ... -Z Kx ... Z Ky ... -... ] -These blocks are also used to build the "covariance scatter matrices". -Here we want to build a scatter matrix that multiplies against positions -(treated as known) producing covariance matrices to fit each rotation. -Notice that in the case of the RHS of the poisson solve the rotations are -known and the positions unknown, and vice versa for rotation fitting. -These linear block just relate the rotations to the positions, linearly in -each. - -| | | -|-|-| -|Parameters| v : \#v by dim list of initial domain positions
f : \#f by \#simplex size list of triangle indices into V
d : coordinate of linear constructor to build | -|Returns| \#v by \#v/\#f block of the linear constructor matrix corresponding to coordinate d | -|See also| arap, arap_dof | - - -### arap_rhs -**`arap_rhs(v: array, f: array, d: int, energy: int)`** - -Guild right-hand side constructor of global poisson solve for various ARAP energies -Inputs: -Outputs: -K #V*dim by #(FV)*dim*dim matrix such that: -b = K * reshape(permute(R,[3 1 2]),size(VF,1)*size(V,2)*size(V,2),1); - -| | | -|-|-| -|Parameters| v : \#v by Vdim list of initial domain positions
f : \#f by 3 list of triangle indices into v
d : dimension being used at solve time. For deformation usually dim = V.cols(), for surface parameterization V.cols() = 3 and dim = 2
energy : ARAPEnergyType enum value defining which energy is being used. See igl.ARAPEnergyType for valid options and explanations. | -|Returns| \#v*d by \#(fv)*dim*dim matrix such that: b = K * reshape(permute(R,[3 1 2]),size(VF,1)*size(V,2)*size(V,2),1); | -|See also| arap_linear_block, arap | - - -### average_onto_faces -**`average_onto_faces(f: array, s: array)`** - -Move a scalar field defined on vertices to faces by averaging - -| | | -|-|-| -|Parameters| f : \#f by ss list of simplexes/faces
s : \#v by dim list of per-vertex values | -|Returns| \#f by dim list of per-face values | -|See also| average_onto_vertices | - - -### average_onto_vertices -**`average_onto_vertices(v: array, f: array, s: array)`** - -Move a scalar field defined on faces to vertices by averaging - -| | | -|-|-| -|Parameters| v : \#v by vdim array of mesh vertices
f : \#f by simplex_count array of simplex indices
s : \#f by dim scalar field defined on simplices | -|Returns| sv: \#v by dim scalar field defined on vertices | -|See also| average_onto_faces | - - -### avg_edge_length -**`avg_edge_length(v: array, f: array) -> float`** - -Compute the average edge length for the given triangle mesh. - -| | | -|-|-| -|Parameters| v : array_like \#v by 3 vertex array
f : f \#f by simplex-size list of mesh faces (must be simplex) | -|Returns| l : average edge length | -|See also| adjacency_matrix | - -**Examples** -```python -# Mesh in (v, f) -length = avg_edge_length(v, f) -``` - - -### barycenter -**`barycenter(v: array, f: array)`** - -Compute the barycenter of every simplex - -| | | -|-|-| -|Parameters| v : \#v x dim matrix of vertex coordinates
f : \#f x simplex_size matrix of indices of simplex corners into V | -|Returns| A \#f x dim matrix where each row is the barycenter of each simplex | - - -### barycentric_coordinates_tet -**`barycentric_coordinates_tet(p: array, a: array, b: array, c: array, d: array)`** - -Compute barycentric coordinates in a tet corresponding to the Euclidean coordinates in `p`. -The input arrays `a`, `b`, `c` and `d` are the vertices of each tet. I.e. one tet is -`a[i, :], b[i, :], c[i, :], d[:, i]`. - -| | | -|-|-| -|Parameters| p : \#P by 3 Query points in 3d
a : \#P by 3 Tet corners in 3d
b : \#P by 3 Tet corners in 3d
c : \#P by 3 Tet corners in 3d
d : \#P by 3 Tet corners in 3d | -|Returns| \#P by 4 list of barycentric coordinates | - - -### barycentric_coordinates_tri -**`barycentric_coordinates_tri(p: array, a: array, b: array, c: array)`** - -Compute barycentric coordinates in a triangle corresponding to the Euclidean coordinates in `p`. -The input arrays `a`, `b`, and `c` are the vertices of each triangle. I.e. one triangle is -`a[i, :], b[i, :], c[i, :]`. - -| | | -|-|-| -|Parameters| p : \#P by 3 Query points in 3d
a : \#P by 3 Tri corners in 3d
b : \#P by 3 Tri corners in 3d
c : \#P by 3 Tri corners in 3d | -|Returns| \#P by 3 list of barycentric coordinates | - - -### bfs -**`bfs(A: sparse_matrix, s: int)`** - -Construct an array indexing into a **directed** graph represented by an adjacency list using -breadth first search. I.e. the output is an array of vertices in breadth-first order. - -| | | -|-|-| -|Parameters| A : \#V list of adjacency lists or \#V by \#V adjacency matrix
s : starting node (index into A) | -|Returns| A tuple, (d, p) where:
* d is a \#V list of indices into rows of A in the order in which graph nodes are discovered
* p is a \#V list of indices of A of predecsors where -1 indicates root/not discovered. I.e.
p[i] is the index of the vertex v which preceded d[i] in the breadth first traversal.
Note that together, (d, p) form a spanning tree of the input graph | - -**Examples** -```python -V, F, _ = igl.read_off("test.off") -A = igl.adjacency_matrix(V, F) -d, p = igl.bfs(A, V[0]) -``` - - -### bfs_orient -**`bfs_orient(f: array)`** - -Consistently orient faces in orientable patches using BFS. - -| | | -|-|-| -|Parameters| f : \#F by 3 list of faces | -|Returns| A tuple, (ff, c) where:
* ff is a \#F by 3 list of faces which are consistently oriented with
* c is a \#F array of connected component ids | - -**Examples** -```python -v, f, _ = igl.read_off("test.off") -ff, c = igl.bfs_orient(f) -``` - - -### biharmonic_coordinates -**`biharmonic_coordinates(v: array, t: array, s: List[List[int]], k: int = 2)`** - -Compute "discrete biharmonic generalized barycentric coordinates" as -described in "Linear Subspace Design for Real-Time Shape Deformation" -[Wang et al. 2015]. Not to be confused with "Bounded Biharmonic Weights -for Real-Time Deformation" [Jacobson et al. 2011] or "Biharmonic -Coordinates" (2D complex barycentric coordinates) [Weber et al. 2012]. -These weights minimize a discrete version of the squared Laplacian energy -subject to positional interpolation constraints at selected vertices -(point handles) and transformation interpolation constraints at regions -(region handles). - -| | | -|-|-| -|Parameters| Templates: HType should be a simple index type e.g. `int`,`size_t`
V \#V by dim list of mesh vertex positions
T \#T by dim+1 list of / triangle indices into V if dim=2
\ tetrahedron indices into V if dim=3
S \#point-handles+\#region-handles list of lists of selected vertices for
each handle. Point handles should have singleton lists and region
handles should have lists of size at least dim+1 (and these points
should be in general position).
k 2-->biharmonic, 3-->triharmonic | -|Returns| W \#V by \#points-handles+(\#region-handles * dim+1) matrix of weights so
that columns correspond to each handles generalized barycentric
coordinates (for point-handles) or animation space weights (for region
handles).
returns true only on success | - -**Examples** -```python -MatrixXd W; -igl::biharmonic_coordinates(V,F,S,W); -const size_t dim = T.cols()-1; -MatrixXd H(W.cols(),dim); -{ -int c = 0; -for(int h = 0;hF \#F by 3 list of triangle indices into V
b \#b list of boundary indices into V
bc \#b by 2 list of boundary conditions corresponding to b | -|Returns| U \#V by 2 list of output mesh vertex locations
Returns true if and only if U contains a successful bijectie mapping | - - -### bijective_composite_harmonic_mapping_with_steps -**`bijective_composite_harmonic_mapping_with_steps(v: array, f: array, b: array, bc: array, min_steps: int, max_steps: int, num_inner_iters: int, test_for_flips: bool)`** - - -| | | -|-|-| -|Parameters| min_steps minimum number of steps to take from V(b,:) to bc
max_steps minimum number of steps to take from V(b,:) to bc (if max_steps == min_steps then no further number of steps will be tried)
num_inner_iters number of iterations of harmonic solves to run after for each morph step (to try to push flips back in)
test_for_flips whether to check if flips occurred (and trigger more steps). if test_for_flips = false then this function always returns
true | - - -### bone_parents -**`bone_parents(be: array)`** - -BONE_PARENTS Recover "parent" bones from directed graph representation. - -| | | -|-|-| -|Parameters| BE \#BE by 2 list of directed bone edges | -|Returns| P \#BE by 1 list of parent indices into BE, -1 means root. | - - -### boundary_conditions -**`boundary_conditions(v: array, ele: array, c: array, p: array, be: array, ce: array)`** - -Compute boundary conditions for automatic weights computation. This -function expects that the given mesh (V,Ele) has sufficient samples -(vertices) exactly at point handle locations and exactly along bone and -cage edges. - -| | | -|-|-| -|Parameters| V \#V by dim list of domain vertices
Ele \#Ele by simplex-size list of simplex indices
C \#C by dim list of handle positions
P \#P by 1 list of point handle indices into C
BE \#BE by 2 list of bone edge indices into C
CE \#CE by 2 list of cage edge indices into *P* | -|Returns| b \#b list of boundary indices (indices into V of vertices which have
known, fixed values)
bc \#b by \#weights list of known/fixed values for boundary vertices
(notice the \#b != \#weights in general because \#b will include all the
intermediary samples along each bone, etc.. The ordering of the
weights corresponds to [P;BE]
Returns false if boundary conditions are suspicious:
P and BE are empty
bc is empty
some column of bc doesn't have a 0 (assuming bc has >1 columns)
some column of bc doesn't have a 1 (assuming bc has >1 columns) | - - -### boundary_facets -**`boundary_facets(t: array)`** - -Determine boundary faces (edges) of tetrahedra (triangles). - -| | | -|-|-| -|Parameters| t : tetrahedron or triangle index list, m by 4/3, where m is the number of tetrahedra/triangles | -|Returns| f : list of boundary faces, n by 3/2, where n is the number of boundary faces/edges | - -**Examples** -```python -# Mesh in (v, f) -b = boundary_facets(f) -``` - - -### boundary_loop -**`boundary_loop(f: array)`** - -Compute ordered boundary loops for a manifold mesh and return the longest loop in terms of vertices. - -| | | -|-|-| -|Parameters| f : \#v by dim array of mesh faces | -|Returns| l : ordered list of boundary vertices of longest boundary loop | - -**Examples** -```python -# Mesh in (v, f) -l = boundary_loop(f) -``` - - -### bounding_box -**`bounding_box(*args, **kwargs)`** - -### bounding_box -**`bounding_box(v: array)`** - -Build a triangle mesh of the bounding box of a given list of vertices - -| | | -|-|-| -|Parameters| V \#V by dim list of rest domain positions | -|Returns| BV 2^dim by dim list of bounding box corners positions
BF \#BF by dim list of simplex facets | - - -### bounding_box -**`bounding_box(v: array, pad: float)`** - -Build a triangle mesh of the bounding box of a given list of vertices - -| | | -|-|-| -|Parameters| V \#V by dim list of rest domain positions | -|Returns| BV 2^dim by dim list of bounding box corners positions
BF \#BF by dim list of simplex facets | - - -### bounding_box_diagonal -**`bounding_box_diagonal(v: array) -> float`** - -Compute the length of the diagonal of a given meshes axis-aligned bounding - -| | | -|-|-| -|Parameters| V \#V by 3 list of vertex positions | -|Returns| Returns length of bounding box diagonal | - - -### circulation -**`circulation(e: int, ccw: bool, emap: array, ef: array, ei: array) -> List[int]`** - -Return list of faces around the end point of an edge. Assumes -data-structures are built from an edge-manifold **closed** mesh. - -| | | -|-|-| -|Parameters| e index into E of edge to circulate
ccw whether to _continue_ in ccw direction of edge (circulate around
E(e,1))
EMAP \#F*3 list of indices into E, mapping each directed edge to unique
unique edge in E
EF \#E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of
F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) "
e=(j->i)
EI \#E by 2 list of edge flap corners (see above). | -|Returns| Returns list of faces touched by circulation (in cyclically order). | - - -### circumradius -**`circumradius(v: array, f: array)`** - -Compute the circumradius of each triangle in a mesh (V,F) - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by 3 list of triangle indices into V | -|Returns| R \#F list of circumradii | - -**Examples** -```python -R = circumradius(V, F) -``` - - -### collapse_small_triangles -**`collapse_small_triangles(v: array, f: array, eps: float)`** - -Given a triangle mesh (V,F) compute a new mesh (VV,FF) which contains the -original faces and vertices of (V,F) except any small triangles have been -removed via collapse. -We are *not* following the rules in "Mesh Optimization" [Hoppe et al] -Section 4.2. But for our purposes we don't care about this criteria. - -| | | -|-|-| -|Parameters| V \#V by 3 list of vertex positions
F \#F by 3 list of triangle indices into V
eps epsilon for smallest allowed area treated as fraction of squared bounding box
diagonal | -|Returns| FF \#FF by 3 list of triangle indices into V | - - -### comb_cross_field -**`comb_cross_field(v: array, f: array, pd1in: array, pd2in: array)`** - - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face indices
PD1in \#F by 3 eigen Matrix of the first per face cross field vector
PD2in \#F by 3 eigen Matrix of the second per face cross field vector | -|Returns| PD1out \#F by 3 eigen Matrix of the first combed cross field vector
PD2out \#F by 3 eigen Matrix of the second combed cross field vector | - - -### comb_frame_field -**`comb_frame_field(v: array, f: array, pd1: array, pd2: array, bis1_combed: array, bis2_combed: array)`** - - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face indices
PD1 \#F by 3 eigen Matrix of the first per face cross field vector
PD2 \#F by 3 eigen Matrix of the second per face cross field vector
BIS1_combed \#F by 3 eigen Matrix of the first combed bisector field vector
BIS2_combed \#F by 3 eigen Matrix of the second combed bisector field vector | -|Returns| PD1_combed \#F by 3 eigen Matrix of the first combed cross field vector
PD2_combed \#F by 3 eigen Matrix of the second combed cross field vector | - - -### comb_line_field -**`comb_line_field(v: array, f: array, pd1in: array)`** - - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face indices
PD1in \#F by 3 eigen Matrix of the first per face cross field vector | -|Returns| PD1out \#F by 3 eigen Matrix of the first combed cross field vector | - - -### compute_frame_field_bisectors -**`compute_frame_field_bisectors(v: array, f: array, b1: array, b2: array, pd1: array, pd2: array)`** - -Compute bisectors of a frame field defined on mesh faces - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face (triangle) indices
B1 \#F by 3 eigen Matrix of face (triangle) base vector 1
B2 \#F by 3 eigen Matrix of face (triangle) base vector 2
PD1 \#F by 3 eigen Matrix of the first per face frame field vector
PD2 \#F by 3 eigen Matrix of the second per face frame field vector | -|Returns| BIS1 \#F by 3 eigen Matrix of the first per face frame field bisector
BIS2 \#F by 3 eigen Matrix of the second per face frame field bisector | - - -### compute_frame_field_bisectors_no_basis -**`compute_frame_field_bisectors_no_basis(v: array, f: array, pd1: array, pd2: array)`** - -Wrapper without given basis vectors. - -| | | -|-|-| -|Parameters| | - - -### connect_boundary_to_infinity -**`connect_boundary_to_infinity(f: array)`** - -Connect all boundary edges to a fictitious point at infinity. - -| | | -|-|-| -|Parameters| F \#F by 3 list of face indices into some V | -|Returns| FO \#F+\#O by 3 list of face indices into [V;inf inf inf], original F are
guaranteed to come first. If (V,F) was a manifold mesh, now it is
closed with a possibly non-manifold vertex at infinity (but it will be
edge-manifold). | - - -### connect_boundary_to_infinity_face -**`connect_boundary_to_infinity_face(v: array, f: array)`** - - -| | | -|-|-| -|Parameters| F \#F by 3 list of face indices into some V | -|Returns| FO \#F+\#O by 3 list of face indices into VO | - - -### connect_boundary_to_infinity_index -**`connect_boundary_to_infinity_index(f: array, inf_index: int)`** - - -| | | -|-|-| -|Parameters| inf_index index of point at infinity (usually V.rows() or F.maxCoeff()) | - - -### connected_components -**`connected_components(a: sparse_matrix)`** - -Determine the connected components of a graph described by the input -adjacency matrix (similar to MATLAB's graphconncomp). - -| | | -|-|-| -|Parameters| A \#A by \#A adjacency matrix (treated as describing an undirected graph) | -|Returns| Returns number of connected components
C \#A list of component indices into [0,\#K-1]
K \#K list of sizes of each component | - - -### cotmatrix -**`cotmatrix(v: array, f: array)`** - -Constructs the cotangent stiffness matrix (discrete laplacian) for a given mesh -(v, f). - -| | | -|-|-| -|Parameters| v : \#v by dim list of mesh vertex positions
f : \#f by simplex_size list of mesh faces (must be triangles) | -|Returns| l : \#v by \#v cotangent matrix, each row i corresponding to v(i, :) | -|See also| adjacency_matrix | -|Notes| This Laplacian uses the convention that diagonal entries are
**minus** the sum of off-diagonal entries. The diagonal entries are
therefore in general negative and the matrix is **negative** semi-definite
(immediately, -L is **positive** semi-definite) | - -**Examples** -```python -# Mesh in (v, f) -l = cotmatrix(v, f) -``` - - -### cotmatrix_entries -**`cotmatrix_entries(v: array, f: array)`** - -COTMATRIX_ENTRIES compute the cotangents of each angle in mesh (V,F) - -| | | -|-|-| -|Parameters| V \#V by dim list of rest domain positions
F \#F by {34} list of {triangletetrahedra} indices into V | -|Returns| C \#F by 3 list of 1/2*cotangents corresponding angles
for triangles, columns correspond to edges [1,2],[2,0],[0,1]
OR
C \#F by 6 list of 1/6*cotangents of dihedral angles*edge lengths
for tets, columns along edges [1,2],[2,0],[0,1],[3,0],[3,1],[3,2] | - - -### cotmatrix_intrinsic -**`cotmatrix_intrinsic(l: array, f: array)`** - -Constructs the cotangent stiffness matrix (discrete laplacian) for a given -mesh with faces F and edge lengths l. - -| | | -|-|-| -|Parameters| l \#F by 3 list of (half-)edge lengths
F \#F by 3 list of face indices into some (not necessarily
determined/embedable) list of vertex positions V. It is assumed \#V ==
F.maxCoeff()+1 | -|Returns| L \#V by \#V sparse Laplacian matrix | -|See also| cotmatrix, intrinsic_delaunay_cotmatrix | - - -### cross_field_mismatch -**`cross_field_mismatch(v: array, f: array, pd1: array, pd2: array, is_combed: bool)`** - - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face indices
PD1 \#F by 3 eigen Matrix of the first per face cross field vector
PD2 \#F by 3 eigen Matrix of the second per face cross field vector
isCombed boolean, specifying whether the field is combed (i.e. matching has been precomputed.
If not, the field is combed first. | -|Returns| Handle_MMatch \#F by 3 eigen Matrix containing the integer mismatch of the cross field
across all face edges | - - -### crouzeix_raviart_cotmatrix -**`crouzeix_raviart_cotmatrix(v: array, f: array)`** - -CROUZEIX_RAVIART_COTMATRIX Compute the Crouzeix-Raviart cotangent -stiffness matrix. - -| | | -|-|-| -|Parameters| V \#V by dim list of vertex positions
F \#F by 3 / 4 list of triangle/tetrahedron indices | -|Returns| L \#E by \#E edge/face-based diagonal cotangent matrix
E \#E by 2 / 3 list of edges/faces
EMAP \#F*3 / 4 list of indices mapping allE to E | -|See also| See also: crouzeix_raviart_massmatrix | - -**Examples** -```python -See for example "Discrete Quadratic Curvature Energies" [Wardetzky, Bergou, -Harmon, Zorin, Grinspun 2007] -``` - - -### crouzeix_raviart_cotmatrix_known_e -**`crouzeix_raviart_cotmatrix_known_e(v: array, f: array, e: array, emap: array)`** - -wrapper if E and EMAP are already computed (better match!) - -| | | -|-|-| -|Parameters| | - - -### crouzeix_raviart_massmatrix -**`crouzeix_raviart_massmatrix(v: array, f: array)`** - -CROUZEIX_RAVIART_MASSMATRIX Compute the Crouzeix-Raviart mass matrix where -M(e,e) is just the sum of the areas of the triangles on either side of an -edge e. - -| | | -|-|-| -|Parameters| V \#V by dim list of vertex positions
F \#F by 3 / 4 list of triangle/tetrahedron indices | -|Returns| M \#E by \#E edge/face-based diagonal mass matrix
E \#E by 2 / 3 list of edges/faces
EMAP \#F*3 / 4 list of indices mapping allE to E | -|See also| crouzeix_raviart_cotmatrix | -|Notes| See for example "Discrete Quadratic Curvature Energies" [Wardetzky, Bergou,
Harmon, Zorin, Grinspun 2007] | - - -### crouzeix_raviart_massmatrix_known_e -**`crouzeix_raviart_massmatrix_known_e(v: array, f: array, e: array, emap: array)`** - -wrapper if E and EMAP are already computed (better match!) - -| | | -|-|-| -|Parameters| | - - -### cut_mesh -**`cut_mesh(v: array, f: array, cuts: array)`** - -Compute the barycenter of every simplex - -| | | -|-|-| -|Parameters| v : \#v x dim matrix of vertex coordinates
f : \#f x simplex_size matrix of indices of simplex corners into V
cuts : \#F by 3 list of boolean flags, indicating the edges that need to
be cut (has 1 at the face edges that are to be cut, 0 otherwise) | -|Returns| A pair (vcut, fcut) where:
* vcut is a \#v by 3 list of the vertex positions
of the cut mesh. This matrix will be similar to the original vertices except
some rows will be duplicated.
* fcut is a \#f by 3 list of the faces of the cut mesh (must be triangles). This
matrix will be similar to the original face matrix except some indices
will be redirected to point to the newly duplicated vertices. | - - -### cut_mesh_from_singularities -**`cut_mesh_from_singularities(v: array, f: array, mismatch: array)`** - -Given a mesh (v,f) and the integer mismatch of a cross field per edge -(mismatch), finds and returns the cut_graph connecting the singularities -(seams) - -| | | -|-|-| -|Parameters| v : \#v by 3 array of triangle vertices (each row is a vertex)
f : \#f by 3 array of triangle indices into v
mismatch : \#f by 3 array of per-corner integer mismatches | -|Returns| seams : \#f by 3 array of per corner booleans that denotes if an edge is a
seam or not | -|See also| cut_mesh | - - -### cut_to_disk -**`cut_to_disk(f: array) -> List[List[int]]`** - -Given a triangle mesh, computes a set of edge cuts sufficient to carve the -mesh into a topological disk, without disconnecting any connected components. -Nothing else about the cuts (including number, total length, or smoothness) -is guaranteed to be optimal. -Simply-connected components without boundary (topological spheres) are left -untouched (delete any edge if you really want a disk). -All other connected components are cut into disks. Meshes with boundary are -supported; boundary edges will be included as cuts. -The cut mesh it can be materialized using cut_mesh(). -Implements the triangle-deletion approach described by Gu et al's -"Geometry Images." - -| | | -|-|-| -|Parameters| F \#F by 3 list of the faces (must be triangles) | -|Returns| cuts List of cuts. Each cut is a sequence of vertex indices (where
pairs of consecutive vertices share a face), is simple, and is either
a closed loop (in which the first and last indices are identical) or
an open curve. Cuts are edge-disjoint. | - - -### cylinder -**`cylinder(axis_devisions: int, height_devisions: int)`** - -Construct a triangle mesh of a cylinder (without caps) - -| | | -|-|-| -|Parameters| axis_devisions number of vertices _around the cylinder_
height_devisions number of vertices _up the cylinder_ | -|Returns| V \#V by 3 list of mesh vertex positions
F \#F by 3 list of triangle indices into V | - - -### decimate -**`decimate(v: array, f: array, max_m: int)`** - -Assumes (V,F) is a manifold mesh (possibly with boundary) Collapses edges -until desired number of faces is achieved. This uses default edge cost and -merged vertex placement functions {edge length, edge midpoint}. - -| | | -|-|-| -|Parameters| V \#V by dim list of vertex positions
F \#F by 3 list of face indices into V.
max_m desired number of output faces | -|Returns| U \#U by dim list of output vertex posistions (can be same ref as V)
G \#G by 3 list of output face indices into U (can be same ref as G)
J \#G list of indices into F of birth face
I \#U list of indices into V of birth vertices
Returns true if m was reached (otherwise \#G > m) | - - -### deform_skeleton -**`deform_skeleton(c: array, be: array, t: array)`** - -Deform a skeleton. - -| | | -|-|-| -|Parameters| C \#C by 3 list of joint positions
BE \#BE by 2 list of bone edge indices
T \#BE*4 by 3 list of stacked transformation matrix | -|Returns| CT \#BE*2 by 3 list of deformed joint positions
BET \#BE by 2 list of bone edge indices (maintains order) | - - -### delaunay_triangulation -**`delaunay_triangulation(v: array)`** - -Given a set of points in 2D, return a Delaunay triangulation of these -points. - -| | | -|-|-| -|Parameters| V \#V by 2 list of vertex positions | -|Returns| F \#F by 3 of faces in Delaunay triangulation. | - - -### dihedral_angles -**`dihedral_angles(v: array, t: array)`** - -Compute dihedral angles for all tets of a given tet mesh (v, t). - -| | | -|-|-| -|Parameters| v : \#v by 3 list of vertex positions
t : \#v by 4 list of tet indices | -|Returns| theta : \#t by 6 list of dihedral angles (in radians)
cos_theta : \#t by 6 list of cosine of dihedral angles (in radians) | - -**Examples** -```python -# TetMesh in (v, t) -theta, cos_theta = dihedral_angles(v, t) -``` - - -### dihedral_angles_intrinsic -**`dihedral_angles_intrinsic(l: array, a: array)`** - -See dihedral_angles for the documentation. -### directed_edge_orientations -**`directed_edge_orientations(c: array, e: array)`** - -Determine rotations that take each edge from the x-axis to its given rest -orientation. - -| | | -|-|-| -|Parameters| C \#C by 3 list of edge vertex positions
E \#E by 2 list of directed edges | -|Returns| Q \#E list of quaternions | - - -### directed_edge_parents -**`directed_edge_parents(e: array)`** - -Recover "parents" (preceding edges) in a tree given just directed edges. - -| | | -|-|-| -|Parameters| e : \#e by 2 list of directed edges | -|Returns| p : \#e list of parent indices into e. (-1) means root | - -**Examples** -```python -e.np.random.randint(0, 10, size=(10, 2)) -p = directed_edge_parents(e) -``` - - -### doublearea -**`doublearea(v: array, f: array)`** - -Computes twice the area for each input triangle[quad] - -| | | -|-|-| -|Parameters| v : \#v by dim array of mesh vertex positions
f : \#f by simplex_size array of mesh faces (must be triangles or quads) | -|Returns| d_area : \#f list of triangle[quad] double areas (SIGNED only for 2D input) | -|Notes| Known bug: For dim==3 complexity is O(\#V + \#F)!! Not just O(\#F). This is a big deal if you have 1million unreferenced vertices and 1 face | - -**Examples** -```python -# Mesh in (v, f) -dbl_area = doublearea(v, f) -``` - - -### dqs -**`dqs(v: array, w: array, v_q: array, v_t: array)`** - -Dual quaternion skinning - -| | | -|-|-| -|Parameters| V \#V by 3 list of rest positions
W \#W by \#C list of weights
vQ \#C list of rotation quaternions
vT \#C list of translation vectors | -|Returns| U \#V by 3 list of new positions | - - -### ears -**`ears(f: array)`** - -FIND_EARS Find all ears (faces with two boundary edges) in a given mesh - -| | | -|-|-| -|Parameters| F \#F by 3 list of triangle mesh indices | -|Returns| ears \#ears list of indices into F of ears
ear_opp \#ears list of indices indicating which edge is non-boundary
(connecting to flops) | - -**Examples** -```python -ears,ear_opp = find_ears(F) -``` - - -### edge_collapse_is_valid -**`edge_collapse_is_valid(edge: int, F: array, E: array, EMAP: array, EF: array, EI: array) -> bool`** - -Assumes (V,F) is a closed manifold mesh (except for previouslly collapsed faces which should be set to: -[IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL IGL_COLLAPSE_EDGE_NULL]. -Tests whether collapsing exactly two faces and exactly 3 edges from E (e -and one side of each face gets collapsed to the other) will result in a -mesh with the same topology. - -| | | -|-|-| -|Parameters| e index into E of edge to try to collapse. E(e,:) = [s d] or [d s] so that sF \#F by 3 list of face indices into V.
E \#E by 2 list of edge indices into V.
EMAP \#F*3 list of indices into E, mapping each directed edge to unique unique edge in E
EF \#E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) "e=(j->i)
EI \#E by 2 list of edge flap corners (see above). | -|Returns| Returns true if edge collapse is valid | - - -### edge_flaps -**`edge_flaps(f: array)`** - -Determine "edge flaps": two faces on either side of a unique edge (assumes edge-manifold mesh) - -| | | -|-|-| -|Parameters| F \#F by 3 list of face indices | -|Returns| E \#E by 2 list of edge indices into V.
EMAP \#F*3 list of indices into E, mapping each directed edge to unique
unique edge in E
EF \#E by 2 list of edge flaps, EF(e,0)=f means e=(i-->j) is the edge of
F(f,:) opposite the vth corner, where EI(e,0)=v. Similarly EF(e,1) "
e=(j->i)
EI \#E by 2 list of edge flap corners (see above). | - - -### edge_lengths -**`edge_lengths(v: array, f: array)`** - -Constructs a list of lengths of edges opposite each index in a face -(triangle/tet) list - -| | | -|-|-| -|Parameters| V eigen matrix \#V by 3
F \#F by 2 list of mesh edges or
F \#F by 3 list of mesh faces (must be triangles) or
T \#T by 4 list of mesh elements (must be tets) | -|Returns| L \#F by {136} list of edge lengths
for edges, column of lengths
for triangles, columns correspond to edges [1,2],[2,0],[0,1]
for tets, columns correspond to edges
[3 0],[3 1],[3 2],[1 2],[2 0],[0 1] | - - -### edge_topology -**`edge_topology(v: array, f: array)`** - -Initialize Edges and their topological relations (assumes an edge-manifold mesh) - -| | | -|-|-| -|Parameters| v : \#v by dim, list of mesh vertex positions (unused)
f : \#f by 3, list of triangle indices into V | -|Returns| ev : \#e by 2, list of edges described as pair of vertices.
fe : \#f by 3, list storing triangle-edge relation.
ef : \#e by w, list storing edge-triangle relation, uses -1 to indicate boundaries. | - -**Examples** -```python -# Mesh in (v, f) -ev, fe, ef = edge_topology(v, f) -``` - - -### edges -**`edges(f: array)`** - -Constructs a list of unique edges represented in a given mesh (v, f) - -| | | -|-|-| -|Parameters| f : \#F by dim list of mesh faces (must be triangles or tets) | -|Returns| \#e by 2 list of edges in no particular order | -|See also| adjacency_matrix | - -**Examples** -```python -V, F, _ = igl.read_off("test.off") -E = igl.edges(F) -``` - - -### edges_to_path -**`edges_to_path(e: array)`** - -EDGES_TO_PATH Given a set of undirected, unique edges such that all form a -single connected compoent with exactly 0 or 2 nodes with valence =1, -determine the/a path visiting all nodes. - -| | | -|-|-| -|Parameters| E \#E by 2 list of undirected edges | -|Returns| I \#E+1 list of nodes in order tracing the chain (loop), if the output
is a loop then I(1) == I(end)
J \#I-1 list of indices into E of edges tracing I
K \#I-1 list of indices into columns of E {1,2} so that K(i) means that
E(i,K(i)) comes before the other (i.e., E(i,3-K(i)) ). This means that
I(i) == E(J(i),K(i)) for i<\#I, or
I == E(sub2ind(size(E),J([1:end end]),[K;3-K(end)])))) | - - -### euler_characteristic -**`euler_characteristic(f: array) -> int`** - -Computes the Euler characteristic of a given mesh (V,F) - -| | | -|-|-| -|Parameters| F \#F by dim list of mesh faces (must be triangles) | -|Returns| Returns An int containing the Euler characteristic | - - -### euler_characteristic_complete -**`euler_characteristic_complete(v: array, f: array) -> int`** - - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions | - - -### exact_geodesic -**`exact_geodesic(v: array, f: array, vs: array, vt: array, fs: numpy.array None = None, ft: numpy.array None = None)`** - -Exact geodesic algorithm for the calculation of geodesics on a triangular mesh. - -| | | -|-|-| -|Parameters| v : \#v by 3 array of 3D vertex positions
f : \#f by 3 array of mesh faces
vs : \#vs by 1 array specifying indices of source vertices
fs : \#fs by 1 array specifying indices of source faces
vt : \#vt by 1 array specifying indices of target vertices
ft : \#ft by 1 array specifying indices of target faces | -|Returns| d : \#vt+\#ft by 1 array of geodesic distances of each target w.r.t. the nearest one in the source set | -|Notes| Specifying a face as target/source means its center.
Implementation from https:code.google.com/archive/p/geodesic/ with the algorithm first described by Mitchell, Mount and Papadimitriou in 1987. | - - -### exterior_edges -**`exterior_edges(f: array)`** - -EXTERIOR_EDGES Determines boundary "edges" and also edges with an -odd number of occurrences where seeing edge (i,j) counts as +1 and seeing -the opposite edge (j,i) counts as -1 - -| | | -|-|-| -|Parameters| F \#F by simplex_size list of "faces" | -|Returns| E \#E by simplex_size-1 list of exterior edges | - - -### extract_manifold_patches -**`extract_manifold_patches(f: array)`** - -Extract a set of maximal patches from a given mesh. -A maximal patch is a subset of the input faces that are connected via -manifold edges; a patch is as large as possible. - -| | | -|-|-| -|Parameters| F \#F by 3 list representing triangles. | -|Returns| number of manifold patches.
P \#F list of patch indices. | - - -### extract_non_manifold_edge_curves -**`extract_non_manifold_edge_curves(f: array, u_e2_e: List[List[int]]) -> List[List[int]]`** - -Extract non-manifold curves from a given mesh. -A non-manifold curves are a set of connected non-manifold edges that -does not touch other non-manifold edges except at the end points. -They are also maximal in the sense that they cannot be expanded by -including more edges. -Assumes the input mesh have all -intersection resolved. See ``igl::cgal::remesh__intersection`` for more details. - -| | | -|-|-| -|Parameters| F \#F by 3 list representing triangles.
uE2E \#uE list of lists of indices into E of coexisting edges. | -|Returns| curves An array of arries of unique edge indices. | - - -### facet_components -**`facet_components(f: array)`** - -Compute connected components of facets based on edge-edge adjacency, - -| | | -|-|-| -|Parameters| f : \#f x 3 array of triangle indices | -|Returns| An array, c, with shape (\#f,), of component ids | -|See also| vertex_components
vertex_components_from_adjacency_matrix | - - -### face_occurrences -**`face_occurrences(f: array)`** - -Count the occruances of each face (row) in a list of face indices (irrespecitive of order) - -| | | -|-|-| -|Parameters| F \#F by simplex-size | -|Returns| C \#F list of counts | -|Notes| Known bug: triangles/tets only (where ignoring order still gives simplex) | - - -### faces_first -**`faces_first(v: array, f: array)`** - -FACES_FIRST Reorder vertices so that vertices in face list come before -vertices that don't appear in the face list. This is especially useful if -the face list contains only surface faces and you want surface vertices -listed before internal vertices -[RV,RF,IM] = faces_first(V,T); - -| | | -|-|-| -|Parameters| V \# vertices by 3 vertex positions
F \# faces by 3 list of face indices | -|Returns| RV \# vertices by 3 vertex positions, order such that if the jth vertex is
some face in F, and the kth vertex is not then j comes before k
RF \# faces by 3 list of face indices, reindexed to use RV
IM \#V by 1 list of indices such that: RF = IM(F) and RT = IM(T)
and RV(IM,:) = V | - -**Examples** -```python -Tet mesh in (V,T,F) -``` - - -### faces_first -**`faces_first(V,F,IM);`** - -T = T.unaryExpr(bind1st(mem_fun( static_cast(&VectorXi::operator())), -&IM)).eval(); -### false_barycentric_subdivision -**`false_barycentric_subdivision(v: array, f: array)`** - -Refine the mesh by adding the barycenter of each face - -| | | -|-|-| -|Parameters| V \#V by 3 coordinates of the vertices
F \#F by 3 list of mesh faces (must be triangles) | -|Returns| VD \#V + \#F by 3 coordinate of the vertices of the dual mesh
The added vertices are added at the end of VD (should not be
same references as (V,F)
FD \#F*3 by 3 faces of the dual mesh | - - -### fast_winding_number_for_meshes -**`fast_winding_number_for_meshes(v: array, f: array, q: array)`** - -Compute approximate winding number of a triangle soup mesh according to -"Fast Winding Numbers for Soups and Clouds" [Barill et al. 2018]. - -| | | -|-|-| -|Parameters| V \#V by 3 list of mesh vertex positions
F \#F by 3 list of triangle mesh indices into rows of V
Q \#Q by 3 list of query points for the winding number | -|Returns| WN \#Q by 1 list of windinng number values at each query point | - - -### fast_winding_number_for_points -**`fast_winding_number_for_points(p: array, n: array, a: array, q: array)`** - -Evaluate the fast winding number for point data, with default expansion -order and beta (both are set to 2). -This function performes the precomputation and evaluation all in one. -If you need to acess the precomuptation for repeated evaluations, use the -two functions designed for exposed precomputation (described above). - -| | | -|-|-| -|Parameters| P \#P by 3 list of point locations
N \#P by 3 list of point normals
A \#P by 1 list of point areas
Q \#Q by 3 list of query points for the winding number | -|Returns| WN \#Q by 1 list of windinng number values at each query point | - - -### find_cross_field_singularities -**`find_cross_field_singularities(v: array, f: array, handle_m_match: array)`** - -Computes singularities of a cross field, assumed combed - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face indices
Handle_MMatch \#F by 3 eigen Matrix containing the integer missmatch of the cross field
across all face edges | -|Returns| isSingularity \#V by 1 boolean eigen Vector indicating the presence of a singularity on a vertex
singularityIndex \#V by 1 integer eigen Vector containing the singularity indices | - - -### find_cross_field_singularities_from_field -**`find_cross_field_singularities_from_field(v: array, f: array, pd1: array, pd2: array, is_combed: bool = False)`** - -Wrapper that calculates the missmatch if it is not provided. -Note that the field in PD1 and PD2 MUST BE combed (see igl::comb_cross_field). - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face (quad) indices
PD1 \#F by 3 eigen Matrix of the first per face cross field vector
PD2 \#F by 3 eigen Matrix of the second per face cross field vector | -|Returns| isSingularity \#V by 1 boolean eigen Vector indicating the presence of a singularity on a vertex
singularityIndex \#V by 1 integer eigen Vector containing the singularity indices | - - -### fit_plane -**`fit_plane(v: array)`** - -This function fits a plane to a point cloud. - -| | | -|-|-| -|Parameters| V \#Vx3 matrix. The 3D point cloud, one row for each vertex. | -|Returns| N 1x3 Vector. The normal of the fitted plane.
C 1x3 Vector. A point that lies in the fitted plane. | -|Notes| From http:missingbytes.blogspot.com/2012/06/fitting-plane-to-point-cloud.html | - - -### flip_avoiding_line_search -**`flip_avoiding_line_search(f: array, cur_v: array, dst_v: array, energy: Callable[[numpy.ndarray], float], cur_energy: float)`** - -A bisection line search for a mesh based energy that avoids triangle flips as suggested in -"Bijective Parameterization with Free Boundaries" (Smith J. and Schaefer S., 2015). -The user specifies an initial vertices position (that has no flips) and target one (that my have flipped triangles). -This method first computes the largest step in direction of the destination vertices that does not incur flips, and then minimizes a given energy using this maximal step and a bisection linesearch (see igl::line_search). -Supports both triangle and tet meshes. - -| | | -|-|-| -|Parameters| F \#F by 3 / 4 list of mesh faces or tets
cur_v \#V by dim list of variables
dst_v \#V by dim list of target vertices. This mesh may have flipped triangles
energy A function to compute the mesh-based energy (return an energy that is bigger than 0)
cur_energy(OPTIONAL) The energy at the given point. Helps save redundant c omputations. This is optional. If not specified, the function will compute it. | -|Returns| cur_v \#V by dim list of variables at the new location
Returns the energy at the new point | - - -### flipped_triangles -**`flipped_triangles(v: array, f: array)`** - -Finds the ids of the flipped triangles of the mesh V,F in the UV mapping uv - -| | | -|-|-| -|Parameters| V \#V by 2 list of mesh vertex positions
F \#F by 3 list of mesh faces (must be triangles) | -|Returns| X \#flipped list of containing the indices into F of the flipped triangles | - - -### forward_kinematics -**`forward_kinematics(c: array, be: array, p: array, d_q: array, d_t: array)`** - -Given a skeleton and a set of relative bone rotations compute absolute rigid transformations for each bone. - -| | | -|-|-| -|Parameters| C \#C by dim list of joint positions
BE \#BE by 2 list of bone edge indices
P \#BE list of parent indices into BE
dQ \#BE list of relative rotations
dT \#BE list of relative translations | -|Returns| vQ \#BE list of absolute rotations
vT \#BE list of absolute translations | - - -### gaussian_curvature -**`gaussian_curvature(v: array, f: array)`** - -Compute discrete local integral gaussian curvature (angle deficit, without -averaging by local area). - -| | | -|-|-| -|Parameters| v : \#v by 3 array of mesh vertex 3D positions
f : \#f by 3 array of face (triangle) indices | -|Returns| k : \#v by 1 array of discrete gaussian curvature values | -|See also| principal_curvature | - -**Examples** -```python -# Mesh in (v, f) -k = gaussian_curvature(v, f) -``` - - -### grad -**`grad(v: array, f: array, uniform: bool = False)`** - -Compute the numerical gradient operator. - -| | | -|-|-| -|Parameters| v : \#v by 3 list of mesh vertex positions
f : \#f by 3 list of mesh face indices [or a \#faces by 4 list of tetrahedral indices]
uniform : boolean (default false). Use a uniform mesh instead of the vertices v | -|Returns| g : \#faces * dim by \#v gradient operator | -|See also| cotmatrix, massmatrix | -|Notes| Gradient of a scalar function defined on piecewise linear elements (mesh)
is constant on each triangle [tetrahedron] i,j,k:
grad(Xijk) = (Xj-Xi) * (Vi - Vk)^R90 / 2A + (Xk-Xi) * (Vj - Vi)^R90 / 2A
where Xi is the scalar value at vertex i, Vi is the 3D position of vertex
i, and A is the area of triangle (i,j,k). ^R90 represent a rotation of
90 degrees. | - -**Examples** -```python -# Mesh in (v, f) -g = grad(v, f) -``` - - -### grad_intrinsic -**`grad_intrinsic(l: array, f: array)`** - -GRAD_INTRINSIC Construct an intrinsic gradient operator. - -| | | -|-|-| -|Parameters| l \#F by 3 list of edge lengths
F \#F by 3 list of triangle indices into some vertex list V | -|Returns| G \#F*2 by \#V gradient matrix: G=[Gx;Gy] where x runs along the 23 edge and
y runs in the counter-clockwise 90° rotation. | - - -### harmonic -**`harmonic(v: array, f: array, b: array, bc: array, k: int)`** - -Compute k-harmonic weight functions "coordinates". - -| | | -|-|-| -|Parameters| V \#V by dim vertex positions
F \#F by simplex-size list of element indices
b \#b boundary indices into V
bc \#b by \#W list of boundary values
k power of harmonic operation (1: harmonic, 2: biharmonic, etc) | -|Returns| W \#V by \#W list of weights | - - -### harmonic_from_laplacian_and_mass -**`harmonic_from_laplacian_and_mass(l: sparse_matrix, m: sparse_matrix, b: array, bc: array, k: int)`** - -Compute a harmonic map using a given Laplacian and mass matrix - -| | | -|-|-| -|Parameters| L \#V by \#V discrete (integrated) Laplacian
M \#V by \#V mass matrix
b \#b boundary indices into V
bc \#b by \#W list of boundary values
k power of harmonic operation (1: harmonic, 2: biharmonic, etc) | -|Returns| W \#V by \#V list of weights | - - -### harmonic_integrated -**`harmonic_integrated(v: array, f: array, k: int)`** - - -| | | -|-|-| -|Parameters| V \#V by dim vertex positions
F \#F by simplex-size list of element indices
k power of harmonic operation (1: harmonic, 2: biharmonic, etc) | -|Returns| Q \#V by \#V discrete (integrated) k-Laplacian | - - -### harmonic_integrated_from_laplacian_and_mass -**`harmonic_integrated_from_laplacian_and_mass(l: sparse_matrix, m: sparse_matrix, k: int)`** - -Build the discrete k-harmonic operator (computing integrated quantities). -That is, if the k-harmonic PDE is Q x = 0, then this minimizes x' Q x - -| | | -|-|-| -|Parameters| L \#V by \#V discrete (integrated) Laplacian
M \#V by \#V mass matrix
k power of harmonic operation (1: harmonic, 2: biharmonic, etc) | -|Returns| Q \#V by \#V discrete (integrated) k-Laplacian | - - -### harmonic_uniform_laplacian -**`harmonic_uniform_laplacian(f: array, b: array, bc: array, k: int)`** - -Compute harmonic map using uniform laplacian operator - -| | | -|-|-| -|Parameters| F \#F by simplex-size list of element indices
b \#b boundary indices into V
bc \#b by \#W list of boundary values
k power of harmonic operation (1: harmonic, 2: biharmonic, etc) | -|Returns| W \#V by \#W list of weights | - - -### hausdorff -**`hausdorff(va: array, fa: array, vb: array, fb: array) -> float`** - -HAUSDORFF compute the Hausdorff distance between mesh (VA,FA) and mesh -(VB,FB). This is the -d(A,B) = max ( max min d(a,b) , max min d(b,a) ) -a∈A b∈B b∈B a∈A - -| | | -|-|-| -|Parameters| VA \#VA by 3 list of vertex positions
FA \#FA by 3 list of face indices into VA
VB \#VB by 3 list of vertex positions
FB \#FB by 3 list of face indices into VB | -|Returns| d hausdorff distance
pair 2 by 3 list of "determiner points" so that pair(1,:) is from A
and pair(2,:) is from B | -|Notes| Known issue: This is only computing max(min(va,B),min(vb,A)). This is
better than max(min(va,Vb),min(vb,Va)). This (at least) is missing
"edge-edge" cases like the distance between the two different
triangulations of a non-planar quad in 3D. Even simpler, consider the
Hausdorff distance between the non-convex, block letter V polygon (with 7
vertices) in 2D and its convex hull. The Hausdorff distance is defined by
the midpoint in the middle of the segment across the concavity and some
non-vertex point _on the edge_ of the V. | - - -### heat_geodesic -**`heat_geodesic(v: array, f: array, t: float, gamma: array)`** - -Compute fast approximate geodesic distances using precomputed data from a set of selected source vertices (gamma) - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by 3 list of mesh face indices into V
t "heat" parameter (smaller --> more accurate, less stable)
gamma \#gamma list of indices into V of source vertices | -|Returns| D \#V list of distances to gamma | - - -### hessian -**`hessian(v: array, f: array)`** - -Constructs the finite element Hessian matrix -as described in https:arxiv.org/abs/1707.04348, -Natural Boundary Conditions for Smoothing in Geometry Processing -(Oded Stein, Eitan Grinspun, Max Wardetzky, Alec Jacobson) -The interior vertices are NOT set to zero yet. - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by 3 list of mesh faces (must be triangles) | -|Returns| H \#V by \#V Hessian energy matrix, each column i corresponding to V(i,:) | - - -### hessian_energy -**`hessian_energy(v: array, f: array)`** - -Constructs the Hessian energy matrix using mixed FEM -as described in https:arxiv.org/abs/1707.04348 -Natural Boundary Conditions for Smoothing in Geometry Processing -(Oded Stein, Eitan Grinspun, Max Wardetzky, Alec Jacobson) - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by 3 list of mesh faces (must be triangles) | -|Returns| Q \#V by \#V Hessian energy matrix, each row/column i
corresponding to V(i,:) | - - -### incircle -**`incircle(pa: array, pb: array, pc: array, pd: array) -> int`** - -Decide whether a point is inside/outside/on a circle. - -| | | -|-|-| -|Parameters| pa, pb, pc 2D points that defines an oriented circle.
pd 2D query point. | -|Returns| INSIDE=1 if pd is inside of the circle defined by pa, pb and pc.
OUSIDE=-1 if pd is outside of the circle.
COCIRCULAR=0 pd is exactly on the circle. | - - -### inradius -**`inradius(v: array, f: array)`** - -Compute the inradius of each triangle in a mesh (V,F) - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by 3 list of triangle indices into V | -|Returns| R \#F list of inradii | - - -### insphere -**`insphere(pa: array, pb: array, pc: array, pd: array, pe: array) -> int`** - -Decide whether a point is inside/outside/on a sphere. - -| | | -|-|-| -|Parameters| pa, pb, pc, pd 3D points that defines an oriented sphere.
pe 3D query point. | -|Returns| INSIDE=1 if pe is inside of the sphere defined by pa, pb, pc and pd.
OUSIDE=-1 if pe is outside of the sphere.
COSPHERICAL=0 pe is exactly on the sphere. | - - -### internal_angles -**`internal_angles(v: array, f: array)`** - -Computes internal angles for a triangle mesh. - -| | | -|-|-| -|Parameters| v : \#v by dim array of mesh vertex nD positions
f : \#f by poly-size array of face (triangle) indices | -|Returns| k : \#f by poly-size array of internal angles. For triangles, columns correspond to edges [1,2],[2,0],[0,1]. | -|Notes| If poly-size ≠ 3 then dim must equal 3. | - - -### intrinsic_delaunay_cotmatrix -**`intrinsic_delaunay_cotmatrix(v: array, f: array)`** - -INTRINSIC_DELAUNAY_COTMATRIX Computes the discrete cotangent Laplacian of a -mesh after converting it into its intrinsic Delaunay triangulation (see, -e.g., [Fisher et al. 2007]. - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by 3 list of mesh elements (triangles or tetrahedra) | -|Returns| L \#V by \#V cotangent matrix, each row i corresponding to V(i,:)
l_intrinsic \#F by 3 list of intrinsic edge-lengths used to compute L
F_intrinsic \#F by 3 list of intrinsic face indices used to compute L | -|See also| intrinsic_delaunay_triangulation, cotmatrix, cotmatrix_intrinsic | - - -### intrinsic_delaunay_triangulation -**`intrinsic_delaunay_triangulation(l_in: array, f_in: array)`** - -INTRINSIC_DELAUNAY_TRIANGULATION Flip edges _intrinsically_ until all are -"intrinsic Delaunay". See "An algorithm for the construction of intrinsic -delaunay triangulations with applications to digital geometry processing" -[Fisher et al. 2007]. - -| | | -|-|-| -|Parameters| l_in \#F_in by 3 list of edge lengths (see edge_lengths)
F_in \#F_in by 3 list of face indices into some unspecified vertex list V | -|Returns| l \#F by 3 list of edge lengths
F \#F by 3 list of new face indices. Note: Combinatorially F may contain
non-manifold edges, duplicate faces and -loops (e.g., an edge [1,1]
or a face [1,1,1]). However, the *intrinsic geometry* is still
well-defined and correct. See [Fisher et al. 2007] Figure 3 and 2nd to
last paragraph of 1st page. Since F may be "non-eddge-manifold" in the
usual combinatorial sense, it may be useful to call the more verbose
overload below if disentangling edges will be necessary later on.
Calling unique_edge_map on this F will give a _different_ result than
those outputs. | -|See also| is_intrinsic_delaunay | - - -### intrinsic_delaunay_triangulation_edges -**`intrinsic_delaunay_triangulation_edges(l_in: array, f_in: array)`** - -INTRINSIC_DELAUNAY_TRIANGULATION Flip edges _intrinsically_ until all are -"intrinsic Delaunay". See "An algorithm for the construction of intrinsic -delaunay triangulations with applications to digital geometry processing" -[Fisher et al. 2007]. - -| | | -|-|-| -|Parameters| l_in \#F_in by 3 list of edge lengths (see edge_lengths)
F_in \#F_in by 3 list of face indices into some unspecified vertex list V | -|Returns| E \#F*3 by 2 list of all directed edges, such that E.row(f+\#F*c) is the
edge opposite F(f,c)
uE \#uE by 2 list of unique undirected edges
EMAP \#F*3 list of indices into uE, mapping each directed edge to unique
undirected edge
uE2E \#uE list of lists of indices into E of coexisting edges | -|See also| unique_edge_map | - - -### is_border_vertex -**`is_border_vertex(v: array, f: array) -> List[bool]`** - -Determine vertices on open boundary of a (manifold) mesh with triangle faces F - -| | | -|-|-| -|Parameters| V \#V by dim list of vertex positions
F \#F by 3 list of triangle indices | -|Returns| \#V vector of bools revealing whether vertices are on boundary | -|Notes| Known Bugs:
- assumes mesh is edge manifold | - - -### is_delaunay -**`is_delaunay(v: array, f: array)`** - -IS_DELAUNAY Determine if each edge in the mesh (V,F) is Delaunay. - -| | | -|-|-| -|Parameters| V \#V by dim list of vertex positions
F \#F by 3 list of triangles indices | -|Returns| D \#F by 3 list of bools revealing whether edges corresponding 23 31 12
are locally Delaunay. Boundary edges are by definition Delaunay.
Non-Manifold edges are by definition not Delaunay. | - - -### is_edge_manifold -**`is_edge_manifold(f: array) -> bool`** - -Check if the mesh is edge-manifold (every edge is incident one one face (boundary) or two oppositely oriented faces). - -| | | -|-|-| -|Parameters| F: \#F by 3 list of triangle indices | -|Returns| True iff all edges are manifold | - -### is_vertex_manifold -**`is_vertex_manifold(f: array) -> bool`** - -Check if a mesh is vertex-manifold. This only checks whether the faces incident on each vertex form exactly one connected component. Vertices incident on non-manifold edges are not consider non-manifold by this function (see is_edge_manifold). Unreferenced verties are considered non-manifold (zero components). - -| | | -|-|-| -|Parameters| F \#F by 3 list of triangle indices | -|Returns| B \#V list indicate whether each vertex is locally manifold.
The mesh is vertex manifold if `all(B) == True`. | - -### is_intrinsic_delaunay -**`is_intrinsic_delaunay(l: array, f: array)`** - -IS_INTRINSIC_DELAUNAY Determine if each edge in the mesh (V,F) is Delaunay. - -| | | -|-|-| -|Parameters| l \#l by dim list of edge lengths
F \#F by 3 list of triangles indices | -|Returns| D \#F by 3 list of bools revealing whether edges corresponding 23 31 12
are locally Delaunay. Boundary edges are by definition Delaunay.
Non-Manifold edges are by definition not Delaunay. | - - -### is_irregular_vertex -**`is_irregular_vertex(v: array, f: array) -> List[bool]`** - -Determine if a vertex is irregular, i.e. it has more than 6 (triangles) or 4 (quads) incident edges. Vertices on the boundary are ignored. - -| | | -|-|-| -|Parameters| v : \#v by dim array of vertex positions
f : \#f by 3[4] array of triangle[quads] indices | -|Returns| s : \#v list of bools revealing whether vertices are singular | - - -### isolines -**`isolines(v: array, f: array, z: array, n: int)`** - -Constructs isolines for a function z given on a mesh (V,F) - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by 3 list of mesh faces (must be triangles)
z \#V by 1 list of function values evaluated at vertices
n the number of desired isolines | -|Returns| isoV \#isoV by dim list of isoline vertex positions
isoE \#isoE by 2 list of isoline edge positions | - - -### iterative_closest_point -**`iterative_closest_point(vx: array, fx: array, vy: array, fy: array, num_samples: int, max_iters: int)`** - -Solve for the rigid transformation that places mesh X onto mesh Y using the -iterative closest point method. In particular, optimize: -min ∫_X inf ‖x*R+t - y‖² dx -R∈SO(3) y∈Y -t∈R³ -Typically optimization strategies include using Gauss Newton -("point-to-plane" linearization) and stochastic descent (sparse random -sampling each iteration). - -| | | -|-|-| -|Parameters| VX \#VX by 3 list of mesh X vertices
FX \#FX by 3 list of mesh X triangle indices into rows of VX
VY \#VY by 3 list of mesh Y vertices
FY \#FY by 3 list of mesh Y triangle indices into rows of VY
num_samples number of random samples to use (larger --> more accurate,
but also more suceptible to sticking to local minimum) | -|Returns| R 3x3 rotation matrix so that (VX*R+t,FX) ~~ (VY,FY)
t 1x3 translation row vector | - - -### lbs_matrix -**`lbs_matrix(v: array, w: array)`** - -LBS_MATRIX Linear blend skinning can be expressed by V' = M * T where V' is -a #V by dim matrix of deformed vertex positions (one vertex per row), M is a #V by (dim+1)*#T (composed of weights and rest positions) and T is a #T*(dim+1) by dim matrix of #T stacked transposed transformation matrices. -See equations (1) and (2) in "Fast Automatic Skinning Transformations" [Jacobson et al 2012] - -| | | -|-|-| -|Parameters| V \#V by dim list of rest positions
W \#V+ by \#T list of weights | -|Returns| M \#V by \#T*(dim+1) | - -**Examples** -```python -In MATLAB: -kron(ones(1,size(W,2)),[V ones(size(V,1),1)]).*kron(W,ones(1,size(V,2)+1)) -``` - - -### lexicographic_triangulation -**`lexicographic_triangulation(p: array)`** - -Given a set of points in 2D, return a lexicographic triangulation of these points. - -| | | -|-|-| -|Parameters| P \#P by 2 list of vertex positions | -|Returns| F \#F by 3 of faces in lexicographic triangulation. | - - -### line_segment_in_rectangle -**`line_segment_in_rectangle(s: array, d: array, a: array, b: array) -> bool`** - -Determine whether a line segment overlaps with a rectangle. - -| | | -|-|-| -|Parameters| s source point of line segment
d dest point of line segment
A first corner of rectangle
B opposite corner of rectangle | -|Returns| Returns true if line segment is at all inside rectangle | - - -### local_basis -**`local_basis(v: array, f: array)`** - -Compute a local orthogonal reference system for each triangle in the given mesh. - -| | | -|-|-| -|Parameters| v : \#v by 3 vertex array
f : \#f by 3 array of mesh faces (must be triangles) | -|Returns| b1 : \#f by 3 array, each vector is tangent to the triangle
b2 : \#f by 3 array, each vector is tangent to the triangle and perpendicular to B1
b3 : \#f by 3 array, normal of the triangle | -|See also| adjacency_matrix | - - -### look_at -**`look_at(eye: array, center: array, up: array)`** - -Implementation of the deprecated gluLookAt function. - -| | | -|-|-| -|Parameters| eye 3-vector of eye position
center 3-vector of center reference point
up 3-vector of up vector | -|Returns| R 4x4 rotation matrix | - - -### loop -**`loop(v: array, f: array, number_of_subdivs: int = 1)`** - -LOOP Given the triangle mesh [V, F], where n_verts = V.rows(), computes -newV and a sparse matrix S s.t. [newV, newF] is the subdivided mesh where -newV = S*V. - -| | | -|-|-| -|Parameters| V an n by 3 matrix of vertices
F an m by 3 matrix of integers of triangle faces
number_of_subdivs an integer that specifies how many subdivision steps to do | -|Returns| NV a matrix containing the new vertices
NF a matrix containing the new faces | - - -### loop_subdivision_matrix -**`loop_subdivision_matrix(n_verts: int, f: array)`** - -LOOP Given the triangle mesh [V, F], where n_verts = V.rows(), computes -newV and a sparse matrix S s.t. [newV, newF] is the subdivided mesh where -newV = S*V. - -| | | -|-|-| -|Parameters| n_verts an integer (number of mesh vertices)
F an m by 3 matrix of integers of triangle faces | -|Returns| S a sparse matrix (will become the subdivision matrix)
newF a matrix containing the new faces | - - -### lscm -**`lscm(v: array, f: array, b: array, bc: array)`** - -Compute a Least-squares conformal map parametrization. - -| | | -|-|-| -|Parameters| v : \#v by 3 array of mesh vertex positions
f : \#f by 3 array of mesh faces (must be triangles)
b : \#b boundary indices into v
bc : \#b by 2 list of boundary values | -|Returns| uv \#v by 2 list of 2D mesh vertex positions in UV space | -|Notes| Derived in "Intrinsic Parameterizations of Surface Meshes" [Desbrun et al.
2002] and "Least Squares Conformal Maps for Automatic Texture Atlas
Generation" [Lévy et al. 2002]), though this implementation follows the
derivation in: "Spectral Conformal Parameterization" [Mullen et al. 2008]
(note, this does **not** implement the Eigen-decomposition based method in
[Mullen et al. 2008], which is not equivalent. Input should be a manifold
mesh (also no unreferenced vertices) and "boundary" (fixed vertices) `b`
should contain at least two vertices per connected component.
Returns true only on solver success. | - - -### map_vertices_to_circle -**`map_vertices_to_circle(v: array, bnd: array)`** - -Map the vertices whose indices are in a given boundary loop (bnd) on the unit circle with spacing proportional to the original boundary edge lengths. - -| | | -|-|-| -|Parameters| v : \#v by dim array of mesh vertex positions
b : \#w list of vertex ids | -|Returns| uv : \#w by 2 list of 2D positions on the unit circle for the vertices in b | - - -### marching_tets -**`marching_tets(TV: array, TT: array, S: array, isovalue: float)`** - -Performs the marching tetrahedra algorithm on a tet mesh defined by TV and -TT with scalar values defined at each vertex in TV. The output is a -triangle mesh approximating the isosurface coresponding to the value -isovalue. - -| | | -|-|-| -|Parameters| TV \#tet_vertices x 3 array -- The vertices of the tetrahedral mesh
TT \#tets x 4 array -- The indexes of each tet in the tetrahedral mesh
S \#tet_vertices x 1 array -- The values defined on each tet vertex
isovalue scalar -- The isovalue of the level set we want to compute | -|Returns| SV : \#SV x 3 array -- The vertices of the output level surface mesh
SF : \#SF x 3 array -- The face indexes of the output level surface mesh
J : \#SF list of indices into TT revealing which tet each face comes from
BC : \#SV x \#TV list of barycentric coordinates so that SV = BC*TV | - -**Examples** -```python -sv, sf, j, bc = igl.marching_tets(tv, tt, s, isovalue) -``` - - -### massmatrix -**`massmatrix(v: array, f: array, type: int = 1)`** - -Constructs the mass (area) matrix for a given mesh (V,F). - -| | | -|-|-| -|Parameters| v : \#v by dim list of mesh vertex positions
f : \#f by simplex_size list of mesh faces (must be triangles)
type : one of the following types:
-igl.MASSMATRIX_TYPE_BARYCENTRIC barycentric
-igl.MASSMATRIX_TYPE_VORONOI voronoi-hybrid (default)
-igl.MASSMATRIX_TYPE_FULL full (not implemented) | -|Returns| m : \#v by \#v mass matrix | -|See also| adjacency_matrix, cotmatrix, grad | - - -### massmatrix_intrinsic -**`massmatrix_intrinsic(l: array, f: array, type: int = 1)`** - -Constructs the mass (area) matrix for a given mesh (V,F). - -| | | -|-|-| -|Parameters| l \#l by simplex_size list of mesh edge lengths
F \#F by simplex_size list of mesh elements (triangles or tetrahedra)
type one of the following ints:
-igl.MASSMATRIX_TYPE_BARYCENTRIC barycentric
-igl.MASSMATRIX_TYPE_VORONOI voronoi-hybrid (default)
-igl.MASSMATRIX_TYPE_FULL full (not implemented) | -|Returns| M \#V by \#V mass matrix | -|See also| adjacency_matrix | - - -### min_quad_with_fixed -**`min_quad_with_fixed(A: sparse_matrix, B: array, known: array, Y: array, Aeq: sparse_matrix, Beq: array, is_A_pd: bool)`** - -MIN_QUAD_WITH_FIXED Minimize a quadratic energy of the form -trace( 0.5*Z'*A*Z + Z'*B + constant ) -subject to -Z(known,:) = Y, and -Aeq*Z = Beq - -| | | -|-|-| -|Parameters| A n by n matrix of quadratic coefficients
B n by 1 column of linear coefficients
known list of indices to known rows in Z
Y list of fixed values corresponding to known rows in Z
Aeq m by n list of linear equality constraint coefficients
Beq m by 1 list of linear equality constraint constant values
is_A_pd flag specifying whether A(unknown,unknown) is positive definite | -|Returns| Z n by k solution | - - -### mvc -**`mvc(v: array, c: array)`** - -MEAN VALUE COORDINATES - -| | | -|-|-| -|Parameters| V \#V x dim list of vertex positions (dim = 2 or dim = 3)
C \#C x dim list of polygon vertex positions in counter-clockwise order (dim = 2 or dim = 3) | -|Returns| W weights, \#V by \#C matrix of weights | -|Notes| Known Bugs: implementation is listed as "Broken" | - -**Examples** -```python -W = mvc(V,C) -``` - - -### normal_derivative -**`normal_derivative(v: array, f: array)`** - -NORMAL_DERIVATIVE Computes the directional derivative **normal** to -**all** (half-)edges of a triangle mesh (not just boundary edges). These -are integrated along the edge: they're the per-face constant gradient dot -the rotated edge vector (unit rotated edge vector for direction then -magnitude for integration). - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by 34 list of triangletetrahedron indices into V | -|Returns| DD \#F*34 by \#V sparse matrix representing operator to compute
directional derivative with respect to each facet of each element. | - - -### offset_surface -**`offset_surface(v: array, f: array, isolevel: int, s: int, signed_distance_type: int)`** - -Compute a triangulated offset surface using matching cubes on a grid of -signed distance values from the input triangle mesh. - -| | | -|-|-| -|Parameters| V \#V by 3 list of mesh vertex positions
F \#F by 3 list of mesh triangle indices into V
isolevel iso level to extract (signed distance: negative inside)
s number of grid cells along longest side (controls resolution)
signed_distance_type type of signing to use one of SIGNED_DISTANCE_TYPE_PSEUDONORMAL, SIGNED_DISTANCE_TYPE_WINDING_NUMBER, SIGNED_DISTANCE_TYPE_DEFAULT, SIGNED_DISTANCE_TYPE_UNSIGNED | -|Returns| SV \#SV by 3 list of output surface mesh vertex positions
SF \#SF by 3 list of output mesh triangle indices into SV
GV \#GV=side(0)*side(1)*side(2) by 3 list of grid cell centers
side list of number of grid cells in x, y, and z directions
So \#GV by 3 list of signed distance values _near_ `isolevel` ("far" from `isolevel` these values are incorrect) | - - -### orient2d -**`orient2d(pa: array, pb: array, pc: array) -> int`** - -Compute the orientation of the triangle formed by pa, pb, pc. - -| | | -|-|-| -|Parameters| pa, pb, pc 2D points. | -|Returns| POSITIVE=1 if pa, pb, pc are counterclockwise oriented.
NEGATIVE=-1 if they are clockwise oriented.
COLLINEAR=0 if they are collinear. | - - -### orient3d -**`orient3d(pa: array, pb: array, pc: array, pd: array) -> int`** - -Compute the orientation of the tetrahedron formed by pa, pb, pc, pd. - -| | | -|-|-| -|Parameters| pa, pb, pc, pd 3D points. | -|Returns| POSITIVE=1 if pd is "below" the oriented plane formed by pa, pb and pc.
NEGATIVE=-1 if pd is "above" the plane.
COPLANAR=0 if pd is on the plane. | - - -### orient_outward -**`orient_outward(v: array, f: array, c: array)`** - -Orient each component (identified by C) of a mesh (V,F) so the normals on -average point away from the patch's centroid. - -| | | -|-|-| -|Parameters| V \#V by 3 list of vertex positions
F \#F by 3 list of triangle indices
C \#F list of components (output of orientable_patches) | -|Returns| FF \#F by 3 list of new triangle indices such that FF(~I,:) = F(~I,:) and
FF(I,:) = fliplr(F(I,:)) (OK if &FF = &F)
I max(C)+1 list of whether face has been flipped | - - -### orientable_patches -**`orientable_patches(f: array)`** - -Compute connected components of facets connected by manifold edges. - -| | | -|-|-| -|Parameters| f : n by dim array of face ids | -|Returns| A tuple (c, A) where c is an array of component ids (starting with 0)
and A is a \#f x \#f adjacency matri | -|See also| components | -|Notes| Known bugs: This will detect a moebius strip as a single patch (manifold, non-orientable) and also non-manfiold, yet orientable patches. | - - -### oriented_facets -**`oriented_facets(f: array)`** - -Determines all 'directed [facets](https:en.wikipedia.org/wiki/Simplex#Elements)' of a given set -of simplicial elements. For a manifold triangle mesh, this computes all half-edges. -For a manifold tetrahedral mesh, this computes all half-faces. - -| | | -|-|-| -|Parameters| f : \#F by simplex_size list of simplices | -|Returns| \#E : by simplex_size-1 list of half-edges/facets | -|See also| edges | -|Notes| This is not the same as igl::edges because this includes every
directed edge including repeats (meaning interior edges on a surface will
show up once for each direction and non-manifold edges may appear more than
once for each direction). | - - -### outer_edge -**`outer_edge(v: array, f: array, i: array)`** - -Find an edge that is reachable from infinity without crossing any faces. -Such edge is called "outer edge." -Precondition: The input mesh must have all -intersection resolved and -no duplicated vertices. The correctness of the output depends on the fact -that there is no edge overlap. See cgal::remesh__intersections.h for -how to obtain such input. - -| | | -|-|-| -|Parameters| V \#V by 3 list of vertex positions
F \#F by 3 list of triangle indices into V
I \#I list of facets to consider | -|Returns| v1 index of the first end point of outer edge
v2 index of the second end point of outer edge
A \#A list of facets incident to the outer edge | - - -### outer_facet -**`outer_facet(v: array, f: array, n: array, i: array)`** - -Find a facet that is reachable from infinity without crossing any faces. -Such facet is called "outer facet." -Precondition: The input mesh must have all -intersection resolved. I.e -there is no duplicated vertices, no overlapping edge and no intersecting -faces (the only exception is there could be topologically duplicated faces). -See cgal::remesh__intersections.h for how to obtain such input. - -| | | -|-|-| -|Parameters| V \#V by 3 list of vertex positions
F \#F by 3 list of triangle indices into V
N \#N by 3 list of face normals
I \#I list of facets to consider | -|Returns| f Index of the outer facet.
flipped true iff the normal of f points inwards. | - - -### outer_vertex -**`outer_vertex(v: array, f: array, i: array)`** - -Find a vertex that is reachable from infinite without crossing any faces. -Such vertex is called "outer vertex." -Precondition: The input mesh must have all -intersection resolved and -no duplicated vertices. See cgal::remesh__intersections.h for how to -obtain such input. - -| | | -|-|-| -|Parameters| V \#V by 3 list of vertex positions
F \#F by 3 list of triangle indices into V
I \#I list of facets to consider | -|Returns| v_index index of outer vertex
A \#A list of facets incident to the outer vertex | - - -### partition -**`partition(w: array, k: int)`** - -PARTITION partition vertices into groups based on each -vertex's vector: vertices with similar coordinates (close in -space) will be put in the same group. - -| | | -|-|-| -|Parameters| W \#W by dim coordinate matrix
k desired number of groups default is dim | -|Returns| G \#W list of group indices (1 to k) for each vertex, such that vertex i is assigned to group G(i)
S k list of seed vertices
D \#W list of squared distances for each vertex to it's corresponding closest seed | - - -### path_to_edges -**`path_to_edges(i: array, make_loop: bool = False)`** - -Given a path as an ordered list of N>=2 vertex indices I[0], I[1], ..., I[N-1] -construct a list of edges [[I[0],I[1]], [I[1],I[2]], ..., [I[N-2], I[N-1]]] -connecting each sequential pair of vertices. - -| | | -|-|-| -|Parameters| I \#I list of vertex indices
make_loop bool If true, include an edge connecting I[N-1] to I[0] | -|Returns| E \#I-1 by 2 list of edges | - - -### per_edge_normals -**`per_edge_normals(v: array, f: array, weight: int = 0, fn: array)`** - -Compute face normals via vertex position list, face list - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face (triangle) indices
weight weighting type
FN \#F by 3 matrix of 3D face normals per face | -|Returns| N \#2 by 3 matrix of mesh edge 3D normals per row
E \#E by 2 matrix of edge indices per row
EMAP \#E by 1 matrix of indices from all edges to E | - - -### per_face_normals -**`per_face_normals(v: array, f: array, z: array)`** - -Compute face normals via vertex position list, face list - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of mesh vertex 3D positions
F \#F by 3 eigen Matrix of face (triangle) indices
Z 3 vector normal given to faces with degenerate normal. | -|Returns| N \#F by 3 eigen Matrix of mesh face (triangle) 3D normals | - -**Examples** -```python -Give degenerate faces (1/3,1/3,1/3)^0.5 -per_face_normals(V,F,Vector3d(1,1,1).normalized(),N); -``` - - -### per_vertex_attribute_smoothing -**`per_vertex_attribute_smoothing(ain: array, f: array)`** - -Smooth vertex attributes using uniform Laplacian - -| | | -|-|-| -|Parameters| Ain \#V by \#A eigen Matrix of mesh vertex attributes (each vertex has \#A attributes)
F \#F by 3 eigne Matrix of face (triangle) indices | -|Returns| Aout \#V by \#A eigen Matrix of mesh vertex attributes | - - -### per_vertex_normals -**`per_vertex_normals(v: array, f: array, weighting: int = 0)`** - -Compute vertex normals via vertex position list, face list. - -| | | -|-|-| -|Parameters| v : \#v by 3 array of mesh vertex 3D positions
f : \#f by 3 array of face (triangle) indices
weighting : Weighting type, one of the following
-igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM uniform influence
-igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA area weighted
-igl.PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE angle weighted | -|Returns| n \#v by 3 array of mesh vertex 3D normals | -|See also| per_face_normals, per_edge_normals | - -**Examples** -```python -# Mesh in (v, f) -n = per_vertex_normals(v, f) -``` - - -### piecewise_constant_winding_number -**`piecewise_constant_winding_number(f: array) -> bool`** - -PIECEWISE_CONSTANT_WINDING_NUMBER Determine if a given mesh induces a -piecewise constant winding number field: Is this mesh valid input to solid -set operations. **Assumes** that `(V,F)` contains no -intersections -(including degeneracies and co-incidences). If there are co-planar and -co-incident vertex placements, a mesh could _fail_ this combinatorial test -but still induce a piecewise-constant winding number _geometrically_. For -example, consider a hemisphere with boundary and then pinch the boundary -"shut" along a line segment. The **_bullet-proof_** check is to first -resolve all -intersections in `(V,F) -> (SV,SF)` (i.e. what the -`igl::copyleft::cgal::piecewise_constant_winding_number` overload does). - -| | | -|-|-| -|Parameters| F \#F by 3 list of triangle indices into some (abstract) list of
vertices V | -|Returns| Returns true if the mesh _combinatorially_ induces a piecewise constant
winding number field. | - - -### planarize_quad_mesh -**`planarize_quad_mesh(v: array, f: array, max_iter: int, threshold: float)`** - -Planarize a quad mesh. - -| | | -|-|-| -|Parameters| v : \#v by 3 array of mesh vertex 3D positions
f : \#f by 4 array of face (quad) indices
max_iter : maximum numbers of iterations
threshold : minimum allowed threshold for non-planarity | -|Returns| out : \#v by 3 array of planar mesh vertex 3D positions | - - -### point_in_circle -**`point_in_circle(qx: float, qy: float, cx: float, cy: float, r: float) -> bool`** - -Determine if 2d point is in a circle - -| | | -|-|-| -|Parameters| qx x-coordinate of query point
qy y-coordinate of query point
cx x-coordinate of circle center
cy y-coordinate of circle center
r radius of circle | -|Returns| Returns true if query point is in circle, false otherwise | - - -### point_in_poly -**`point_in_poly(poly: List[List[int]], xt: int, yt: int) -> bool`** - -Determine if 2d point is inside a 2D polygon - -| | | -|-|-| -|Parameters| poly vector of polygon points, [0]=x, [1]=y. Polyline need not be closed (i.e. first point != last point), the line segment between last and first selected points is constructed within this function.
x x-coordinate of query point
y y-coordinate of query point | -|Returns| Returns true if query point is in polygon, false otherwise | -|Notes| From http:www.visibone.com/inpoly/ | - - -### point_mesh_squared_distance -**`point_mesh_squared_distance(p: array, v: array, ele: array)`** - -Compute distances from a set of points P to a triangle mesh (V,F) - -| | | -|-|-| -|Parameters| P \#P by 3 list of query point positions
V \#V by 3 list of vertex positions
Ele \#Ele by (321) list of (triangleedgepoint) indices | -|Returns| sqrD \#P list of smallest squared distances
I \#P list of primitive indices corresponding to smallest distances
C \#P by 3 list of closest points | -|Notes| Known bugs: This only computes distances to given primitivess. So
unreferenced vertices are ignored. However, degenerate primitives are
handled correctly: triangle [1 2 2] is treated as a segment [1 2], and
triangle [1 1 1] is treated as a point. So one _could_ add extra
combinatorially degenerate rows to Ele for all unreferenced vertices to
also get distances to points. | - - -### point_simplex_squared_distance -**`point_simplex_squared_distance(p: array, v: array, ele: array, i: int)`** - -Determine squared distance from a point to linear simplex. -Also return barycentric coordinate of closest point. - -| | | -|-|-| -|Parameters| p d-long query point
V \#V by d list of vertices
Ele \#Ele by ss<=d+1 list of simplex indices into V
i index into Ele of simplex | -|Returns| sqr_d squared distance of Ele(i) to p
c closest point on Ele(i)
b barycentric coordinates of closest point on Ele(i) | - - -### polar_dec -**`polar_dec(a: array)`** - -Computes the polar decomposition (R,T) of a matrix A - -| | | -|-|-| -|Parameters| A 3 by 3 matrix to be decomposed | -|Returns| R 3 by 3 orthonormal matrix part of decomposition
T 3 by 3 stretch matrix part of decomposition | - - -### principal_curvature -**`principal_curvature(v: array, f: array, radius: int = 5, use_k_ring: bool = True)`** - -Compute the principal curvature directions and magnitude of the given triangle mesh. - -| | | -|-|-| -|Parameters| v : vertex array of size \#V by 3
f : face index array \#F by 3 list of mesh faces (must be triangles)
radius : controls the size of the neighbourhood used, 1 = average edge length (default: 5)
use_k_ring : (default: True) | -|Returns| pd1 : \#v by 3 maximal curvature direction for each vertex
pd2 : \#v by 3 minimal curvature direction for each vertex
pv1 : \#v by 1 maximal curvature value for each vertex
pv2 : \#v by 1 minimal curvature value for each vertex | -|See also| average_onto_faces, average_onto_vertices | -|Notes| This function has been developed by: Nikolas De Giorgis, Luigi Rocca and Enrico Puppo.
The algorithm is based on: Efficient Multi-scale Curvature and Crease Estimation
Daniele Panozzo, Enrico Puppo, Luigi Rocca GraVisMa, 2010 | - -**Examples** -```python -# Mesh in (v, f) -pd1, pd2, pv1, pv2 = principal_curvature(v, f) -``` - - -### procrustes -**`procrustes(x: array, y: array, include_scaling: bool, include_reflections: bool)`** - -Solve Procrustes problem in d dimensions. Given two point sets X,Y in R^d -find best scale s, orthogonal R and translation t s.t. s*X*R + t - Y^2 -is minimized. - -| | | -|-|-| -|Parameters| X \#V by DIM first list of points
Y \#V by DIM second list of points
includeScaling if scaling should be allowed
includeReflections if R is allowed to be a reflection | -|Returns| scale scaling
R orthogonal matrix
t translation | - -**Examples** -```python -MatrixXd X, Y; (containing 3d points as rows) -double scale; -MatrixXd R; -VectorXd t; -igl::procrustes(X,Y,true,false,scale,R,t); -R *= scale; -MatrixXd Xprime = (X * R).rowwise() + t.transpose(); -``` - - -### project -**`project(v: array, model: array, proj: array, viewport: array)`** - -Project - -| | | -|-|-| -|Parameters| V \#V by 3 list of object points
model model matrix
proj projection matrix
viewport viewport vector | -|Returns| P \#V by 3 list of screen space points | - - -### project_isometrically_to_plane -**`project_isometrically_to_plane(v: array, f: array)`** - -Project each triangle to the plane - -| | | -|-|-| -|Parameters| V \#V by 3 list of vertex positions
F \#F by 3 list of mesh indices | -|Returns| U \#F*3 by 2 list of triangle positions
UF \#F by 3 list of mesh indices into U
I \#V by \#F*3 such that I(i,j) = 1 implies U(j,:) corresponds to V(i,:) | - -**Examples** -```python -[U,UF,I] = project_isometrically_to_plane(V,F) -``` - - -### project_to_line -**`project_to_line(p: array, s: array, d: array)`** - -PROJECT_TO_LINE project points onto vectors, that is find the parameter -t for a point p such that proj_p = (y-x).*t, additionally compute the -squared distance from p to the line of the vector, such that -p - proj_p² = sqr_d - -| | | -|-|-| -|Parameters| P \#P by dim list of points to be projected
S size dim start position of line vector
D size dim destination position of line vector | -|Returns| T \#P by 1 list of parameters
sqrD \#P by 1 list of squared distances | - -**Examples** -```python -[T,sqrD] = project_to_line(P,S,D) -``` - - -### project_to_line_segment -**`project_to_line_segment(p: array, s: array, d: array)`** - -PROJECT_TO_LINE_SEGMENT project points onto vectors, that is find the parameter -t for a point p such that proj_p = (y-x).*t, additionally compute the -squared distance from p to the line of the vector, such that -p - proj_p² = sqr_d - -| | | -|-|-| -|Parameters| P \#P by dim list of points to be projected
S size dim start position of line vector
D size dim destination position of line vector | -|Returns| T \#P by 1 list of parameters
sqrD \#P by 1 list of squared distances | - -**Examples** -```python -[T,sqrD] = project_to_line_segment(P,S,D) -``` - - -### pso -**`pso(f: Callable[[numpy.ndarray[float64[m, 1]]], float], lb: numpy.ndarray, ub: numpy.ndarray, max_iters: int, population: int)`** - -Solve the problem: -minimize f(x) -subject to lb ≤ x ≤ ub -by particle swarm optimization (PSO). - -| | | -|-|-| -|Parameters| f function that evaluates the objective for a given "particle" location
LB \#X vector of lower bounds
UB \#X vector of upper bounds
max_iters maximum number of iterations
population number of particles in swarm | -|Returns| f(X) objective corresponding to best particle seen so far
X best particle seen so far | - - -### qslim -**`qslim(v: array, f: array, max_m: int)`** - -Decimate (simplify) a triangle mesh in nD according to the paper -"Simplifying Surfaces with Color and Texture using Quadric Error Metrics" -by [Garland and Heckbert, 1987] (technically a followup to qslim). The -mesh can have open boundaries but should be edge-manifold. - -| | | -|-|-| -|Parameters| V \#V by dim list of vertex positions. Assumes that vertices w
F \#F by 3 list of triangle indices into V
max_m desired number of output faces | -|Returns| U \#U by dim list of output vertex posistions (can be same ref as V)
G \#G by 3 list of output face indices into U (can be same ref as G)
J \#G list of indices into F of birth face
I \#U list of indices into V of birth vertices | - - -### quad_grid -**`quad_grid(nx: int, ny: int)`** - -Generate a quad mesh over a regular grid. - -| | | -|-|-| -|Parameters| nx number of vertices in the x direction
ny number of vertices in the y direction | -|Returns| V nx*ny by 2 list of vertex positions
Q (nx-1)*(ny-1) by 4 list of quad indices into V
E (nx-1)*ny+(ny-1)*nx by 2 list of undirected quad edge indices into V | -|See also| grid, triangulated_grid | - - -### quad_planarity -**`quad_planarity(v: array, f: array)`** - -Compute planarity of the faces of a quad mesh. - -| | | -|-|-| -|Parameters| v : \#v by 3 array of mesh vertex 3D positions
f : \#f by 4 array of face (quad) indices | -|Returns| p : \#f by 1 array of mesh face (quad) planarities | - - -### ramer_douglas_peucker -**`ramer_douglas_peucker(p: array, tol: float)`** - -Run (Ramer-)Duglass-Peucker curve simplification but keep track of where -every point on the original curve maps to on the simplified curve. - -| | | -|-|-| -|Parameters| P \#P by dim list of points, (use P([1:end 1],:) for loops)
tol DP tolerance | -|Returns| S \#S by dim list of points along simplified curve
J \#S indices into P of simplified points
Q \#P by dim list of points mapping along simplified curve | - - -### random_points_on_mesh -**`random_points_on_mesh(n: int, v: array, f: array)`** - -RANDOM_POINTS_ON_MESH Randomly sample a mesh (V,F) n times. - -| | | -|-|-| -|Parameters| n number of samples
V \#V by dim list of mesh vertex positions
F \#F by 3 list of mesh triangle indices | -|Returns| B n by 3 list of barycentric coordinates, ith row are coordinates of
ith sampled point in face FI(i)
FI n list of indices into F | - - -### random_search -**`random_search(f: Callable[[numpy.ndarray[float64[m, 1]]], float], lb: numpy.ndarray, ub: numpy.ndarray, iters: int)`** - -Solve the problem: -minimize f(x) -subject to lb ≤ x ≤ ub -by uniform random search. - -| | | -|-|-| -|Parameters| f function to minimize
LB \#X vector of finite lower bounds
UB \#X vector of finite upper bounds
iters number of iterations | -|Returns| f(X)
X \#X optimal parameter vector | - - -### ray_box_intersect -**`ray_box_intersect(source: array, dir: array, box_min: array, box_max: array, t0: float, t1: float)`** - -Determine whether a ray origin+t*dir and box intersect within the ray's parameterized -range (t0,t1) - -| | | -|-|-| -|Parameters| source 3-vector origin of ray
dir 3-vector direction of ray
box_min min axis aligned box
box_max max axis aligned box
t0 hit only if hit.t less than t0
t1 hit only if hit.t greater than t1 | -|Returns| true if hit
tmin minimum of interval of overlap within [t0,t1]
tmax maximum of interval of overlap within [t0,t1] | - - -### ray_mesh_intersect -**`ray_mesh_intersect(source: array, dir: array, v: array, f: array) -> List[Tuple[int, int, float, float, float]]`** - -Shoot a ray against a mesh (V,F) and collect the first hit. - -| | | -|-|-| -|Parameters| source 3-vector origin of ray
dir 3-vector direction of ray
V \#V by 3 list of mesh vertex positions
F \#F by 3 list of mesh face indices into V | -|Returns| hits **sorted** list of hits: id, gid, u, v, t | - - -### ray_sphere_intersect -**`ray_sphere_intersect(source: array, dir: array, center: array, r: float)`** - -Compute the intersection between a ray from O in direction D and a sphere centered at C with radius r - -| | | -|-|-| -|Parameters| source origin of ray
dir direction of ray
center center of sphere
r radius of sphere | -|Returns| Returns the number of hits
t0 parameterization of first hit (set only if exists) so that hit position = o + t0*d
t1 parameterization of second hit (set only if exists) | - - -### read_dmat -**`read_dmat(filename: str, dtype: dtype = 'float64')`** - -Read a matrix from an ascii dmat file, a simple ascii matrix file type, defined as follows. The first line is always: -<#columns> <#rows> -Then the coefficients of the matrix are given separated by whitespace with columns running fastest. - -| | | -|-|-| -|Parameters| filename : string, path to .dmat file
dtype : data-type of the returned matrix. Default is `float64`.
(returned faces always have type int32.) | -|Returns| w : array containing read-in coefficients | -|See also| read_triangle_mesh, read_off | - -**Examples** -```python -w = read_dmat("my_model.dmat") -``` - - -### read_mesh -**`read_mesh(filename: str, dtypef: dtype = 'float')`** - -Load a tetrahedral volume mesh from a .mesh file. - -| | | -|-|-| -|Parameters| filename : path of .mesh file
dtype : data-type of the returned vertices, optional. Default is `float64`.
(returned faces always have type int32.) | -|Returns| v : array of vertex positions \#v by 3
t : \#t by 4 array of tet indices into vertex positions
f : \#f by 3 array of face indices into vertex positions | -|Notes| Known bugs: Holes and regions are not supported | - -**Examples** -```python -v, t, f = read_mesh("my_mesh.mesh") -``` - - -### read_msh -**`read_msh(filename: str, dtypef: dtype = 'float')`** - -Read a mesh (e.g., tet mesh) from a gmsh .msh file - -| | | -|-|-| -|Parameters| filename path to .msh file
dtype : data-type of the returned vertices, optional. Default is `float64`.
(returned faces always have type int32.) | -|Returns| V \#V by 3 list of 3D mesh vertex positions
T \#T by ss list of 3D ss-element indices into V (e.g., ss=4 for tets) | - - -### read_obj -**`read_obj(filename: str, dtype: dtype = 'float64')`** - -Read a mesh from an ascii obj file, filling in vertex positions, normals -and texture coordinates. Mesh may have faces of any number of degree. - -| | | -|-|-| -|Parameters| filename : string, path to .obj file
dtype : data-type of the returned faces, texture coordinates and normals, optional. Default is `float64`.
(returned faces always have type int32.) | -|Returns| v : array of vertex positions \#v by 3
tc : array of texture coordinats \#tc by 2
n : array of corner normals \#n by 3
f : \#f array of face indices into vertex positions
ftc : \#f array of face indices into vertex texture coordinates
fn : \#f array of face indices into vertex normals | -|See also| read_triangle_mesh, read_off | - -**Examples** -```python -v, _, n, f, _, _ = read_obj("my_model.obj") -``` - - -### read_off -**`read_off(filename: str, read_normals: bool = True, dtype: dtype = 'float64')`** - -Read a mesh from an ascii off file, filling in vertex positions, normals -and texture coordinates. Mesh may have faces of any number of degree. - -| | | -|-|-| -|Parameters| filename : string, path to .off file
read_normals : bool, determines whether normals are read. If false, returns []
dtype : data-type of the returned vertices, faces, and normals, optional. Default is `float64`.
(returned faces always have type int32.) | -|Returns| v : array of vertex positions \#v by 3
f : \#f list of face indices into vertex positions
n : list of vertex normals \#v by 3 | -|See also| read_triangle_mesh, read_obj | - -**Examples** -```python -v, f, n, c = read_off("my_model.off") -``` - - -### read_tgf -**`read_tgf(tgf_filename: str)`** - -Read a graph from a .tgf file - -| | | -|-|-| -|Parameters| filename .tgf file name | -|Returns| V \# vertices by 3 list of vertex positions
E \# edges by 2 list of edge indices
P \# point-handles list of point handle indices
BE \# bone-edges by 2 list of bone-edge indices
CE \# cage-edges by 2 list of cage-edge indices
PE \# pseudo-edges by 2 list of pseudo-edge indices | -|Notes| Assumes that graph vertices are 3 dimensional | - -**Examples** -```python -V,E,P,BE,CE,PE = igl.read_tgf(filename) -``` - - -### read_triangle_mesh -**`read_triangle_mesh(filename: str, dtypef: dtype = 'float')`** - -Read mesh from an ascii file with automatic detection of file format. -Supported: obj, off, stl, wrl, ply, mesh. - -| | | -|-|-| -|Parameters| filename : string, path to mesh file
dtype : data-type of the returned vertices, optional. Default is `float64`.
(returned faces always have type int32.) | -|Returns| v : array of vertex positions \#v by 3
f : \#f list of face indices into vertex positions | -|See also| read_obj, read_off, read_stl | - -**Examples** -```python -v, f = read_triangle_mesh("my_model.obj") -``` - - -### remove_duplicate_vertices -**`remove_duplicate_vertices(v: array, f: array, epsilon: float)`** - -REMOVE_DUPLICATE_VERTICES Remove duplicate vertices upto a uniqueness -tolerance (epsilon) - -| | | -|-|-| -|Parameters| V \#V by dim list of vertex positions
epsilon uniqueness tolerance (significant digit), can probably think of
this as a tolerance on L1 distance | -|Returns| SV \#SV by dim new list of vertex positions
SVI \#V by 1 list of indices so SV = V(SVI,:)
SVJ \#SV by 1 list of indices so V = SV(SVJ,:)
Wrapper that also remaps given faces (F) --> (SF) so that SF index SV | - -**Examples** -```python -% Mesh in (V,F) -[SV,SVI,SVJ] = remove_duplicate_vertices(V,1e-7); -% remap faces -SF = SVJ(F); -``` - - -### remove_duplicates -**`remove_duplicates(v: array, f: array, epsilon: float)`** - -Merge the duplicate vertices from V, fixing the topology accordingly - -| | | -|-|-| -|Parameters| V,F mesh description
epsilon minimal distance to consider two vertices identical | -|Returns| NV, NF new mesh without duplicate vertices | - - -### remove_unreferenced -**`remove_unreferenced(v: array, f: array)`** - -Remove unreferenced vertices from V, updating F accordingly - -| | | -|-|-| -|Parameters| V \#V by dim list of mesh vertex positions
F \#F by ss list of simplices (Values of -1 are quitely skipped) | -|Returns| NV \#NV by dim list of mesh vertex positions
NF \#NF by ss list of simplices
IM \#V by 1 list of indices such that: NF = IM(F) and NT = IM(T)
and V(find(IM<=size(NV,1)),:) = NV
J \#RV by 1 list, such that RV = V(J,:) | - - -### resolve_duplicated_faces -**`resolve_duplicated_faces(f1: array)`** - -Resolve duplicated faces according to the following rules per unique face: -- If the number of positively oriented faces equals the number of -negatively oriented faces, remove all duplicated faces at this triangle. -- If the number of positively oriented faces equals the number of -negatively oriented faces plus 1, keeps one of the positively oriented -face. -- If the number of positively oriented faces equals the number of -negatively oriented faces minus 1, keeps one of the negatively oriented -face. -- If the number of postively oriented faces differ with the number of -negativley oriented faces by more than 1, the mesh is not orientable. -An exception will be thrown. - -| | | -|-|-| -|Parameters| F1 \#F1 by 3 array of input faces. | -|Returns| F2 \#F2 by 3 array of output faces without duplicated faces.
J \#F2 list of indices into F1. | - - -### rigid_alignment -**`rigid_alignment(x: array, p: array, n: array)`** - -Find the rigid transformation that best aligns the 3D points X to their -corresponding points P with associated normals N. -min ‖(X*R+t-P)'N‖² -R∈SO(3) -t∈R³ - -| | | -|-|-| -|Parameters| X \#X by 3 list of query points
P \#X by 3 list of corresponding (e.g., closest) points
N \#X by 3 list of unit normals for each row in P | -|Returns| R 3 by 3 rotation matrix
t 1 by 3 translation vector | -|See also| icp | - - -### rotate_vectors -**`rotate_vectors(v: array, a: array, b1: array, b2: array)`** - -Rotate the vectors V by A radiants on the tangent plane spanned by B1 and B2 - -| | | -|-|-| -|Parameters| V \#V by 3 eigen Matrix of vectors
A \#V eigen vector of rotation angles or a single angle to be applied to all vectors
B1 \#V by 3 eigen Matrix of base vector 1
B2 \#V by 3 eigen Matrix of base vector 2 | -|Returns| Returns the rotated vectors | - - -### sample_edges -**`sample_edges(v: array, e: array, k: int)`** - -Compute samples_per_edge extra points along each edge in E defined over -vertices of V. - -| | | -|-|-| -|Parameters| V vertices over which edges are defined, \# vertices by dim
E edge list, \# edges by 2
k number of extra samples to be computed along edge not including start and end points | -|Returns| S sampled vertices, size less than \# edges * (2+k) by dim always begins
with V so that E is also defined over S | - - -### segments_intersect -**`segments_intersect(p: array, r: array, q: array, s: array)`** - -Determine whether two line segments A,B intersect -A: p + t*r : t \in [0,1] -B: q + u*s : u \in [0,1] - -| | | -|-|-| -|Parameters| p 3-vector origin of segment A
r 3-vector direction of segment A
q 3-vector origin of segment B
s 3-vector direction of segment B
eps precision | -|Returns| t scalar point of intersection along segment A, t \in [0,1]
u scalar point of intersection along segment B, u \in [0,1]
Returns true if intersection | - - -### shape_diameter_function -**`shape_diameter_function(v: array, f: array, p: array, n: array, num_samples: int)`** - -Compute shape diamater function per given point. In the parlence of the -paper "Consistent Mesh Partitioning and Skeletonisation using the Shape -Diameter Function" [Shapiro et al. 2008], this implementation uses a 180° -cone and a _uniform_ average (_not_ a average weighted by inverse angles). - -| | | -|-|-| -|Parameters| V \#V by 3 list of mesh vertex positions
F \#F by 3 list of mesh face indices into V
P \#P by 3 list of origin points
N \#P by 3 list of origin normals | -|Returns| S \#P list of shape diamater function values between bounding box
diagonal (perfect sphere) and 0 (perfect needle hook) | - - -### sharp_edges -**`sharp_edges(v: array, f: array, angle: float)`** - -SHARP_EDGES Given a mesh, compute sharp edges. - -| | | -|-|-| -|Parameters| V \#V by 3 list of vertex positions
F \#F by 3 list of triangle mesh indices into V
angle dihedral angle considered to sharp (e.g., igl::PI * 0.11) | -|Returns| SE \#SE by 2 list of edge indices into V
E \#e by 2 list of edges in no particular order
uE \#uE by 2 list of unique undirected edges
EMAP \#F*3 list of indices into uE, mapping each directed edge to unique
undirected edge so that uE(EMAP(f+\#F*c)) is the unique edge
corresponding to E.row(f+\#F*c)
uE2E \#uE list of lists of indices into E of coexisting edges, so that
E.row(uE2E[i][j]) corresponds to uE.row(i) for all j in
0..uE2E[i].size()-1.
sharp \#SE list of indices into uE revealing sharp undirected edges | - - -### signed_angle -**`signed_angle(a: array, b: array, p: array) -> float`** - -Compute the signed angle subtended by the oriented 3d triangle (A,B,C) at some point P - -| | | -|-|-| -|Parameters| A 2D position of corner
B 2D position of corner
P 2D position of query point | -|Returns| returns signed angle | - - -### signed_distance -**`signed_distance(p: array, v: array, f: array, return_normals: bool = False) -> tuple`** - -SIGNED_DISTANCE computes signed distance to a mesh - -| | | -|-|-| -|Parameters| P \#P by 3 list of query point positions
V \#V by 3 list of vertex positions
F \#F by ss list of triangle indices, ss should be 3 unless sign_type
return_normals (Optional, defaults to False) If set to True, will return pseudonormals of
closest points to each query point in P | -|Returns| S \#P list of smallest signed distances
I \#P list of facet indices corresponding to smallest distances
C \#P by 3 list of closest points | -|Notes| Known issue: This only computes distances to triangles. So unreferenced
vertices and degenerate triangles are ignored. | - -**Examples** -```python -S, I, C = signed_distance(P, V, F, return_normals=False) -``` - - -### simplify_polyhedron -**`simplify_polyhedron(ov: array, of: array)`** - -Simplify a polyhedron represented as a triangle mesh (OV,OF) by collapsing -any edge that doesn't contribute to defining surface's pointset. This -_would_ also make sense for open and non-manifold meshes, but the current -implementation only works with closed manifold surfaces with well defined -triangle normals. - -| | | -|-|-| -|Parameters| OV \#OV by 3 list of input mesh vertex positions
OF \#OF by 3 list of input mesh triangle indices into OV | -|Returns| V \#V by 3 list of output mesh vertex positions
F \#F by 3 list of input mesh triangle indices into V
J \#F list of indices into OF of birth parents | - - -### snap_points -**`snap_points(c: array, v: array)`** - -SNAP_POINTS snap list of points C to closest of another list of points V -[I,minD,VI] = snap_points(C,V) - -| | | -|-|-| -|Parameters| C \#C by dim list of query point positions
V \#V by dim list of data point positions | -|Returns| I \#C list of indices into V of closest points to C
minD \#C list of squared (^p) distances to closest points
VI \#C by dim list of new point positions, VI = V(I,:) | - - -### solid_angle -**`solid_angle(a: array, b: array, c: array, p: array) -> float`** - -Compute the signed solid angle subtended by the oriented 3d triangle (A,B,C) at some point P - -| | | -|-|-| -|Parameters| A 3D position of corner
B 3D position of corner
C 3D position of corner
P 3D position of query point | -|Returns| Returns signed solid angle | - - -### sort_angles -**`sort_angles(m: array)`** - -Sort angles in ascending order in a numerically robust way. -Instead of computing angles using atan2(y, x), sort directly on (y, x). - -| | | -|-|-| -|Parameters| M: m by n matrix of scalars. (n >= 2). Assuming the first column of M
contains values for y, and the second column is x. Using the rest
of the columns as tie-breaker. | -|Returns| R: an array of m indices. M.row(R[i]) contains the i-th smallest
angle. | -|Notes| None. | - - -### sparse_voxel_grid -**`sparse_voxel_grid(p0: numpy.ndarray, scalar_func: Callable[[numpy.ndarray[float64[1, 3]]], float], eps: float, expected_number_of_cubes: int)`** - -Given a point, p0, on an isosurface, construct a shell of epsilon sized cubes surrounding the surface. -These cubes can be used as the input to marching cubes. - -| | | -|-|-| -|Parameters| p0 A 3D point on the isosurface surface defined by scalarFunc(x) = 0
scalarFunc A scalar function from R^3 to R -- points which map to 0 lie
on the surface, points which are negative lie inside the surface,
and points which are positive lie outside the surface
eps The edge length of the cubes surrounding the surface
expected_number_of_cubes This pre-allocates internal data structures to speed things up | -|Returns| CS \#cube-vertices by 1 list of scalar values at the cube vertices
CV \#cube-vertices by 3 list of cube vertex positions
CI \#number of cubes by 8 list of indexes into CS and CV. Each row represents a cube | - - -### swept_volume_bounding_box -**`swept_volume_bounding_box(n: int, v: Callable[[int, float], numpy.ndarray[float64[1, 3]]], steps: int)`** - -Construct an axis-aligned bounding box containing a shape undergoing a -motion sampled at `steps` discrete momements. - -| | | -|-|-| -|Parameters| n number of mesh vertices
V function handle so that V(i,t) returns the 3d position of vertex i at time t, for t∈[0,1]
steps number of time steps: steps=3 --> t∈{0,0.5,1} | -|Returns| min,max corners of box containing mesh under motion | - - -### tet_tet_adjacency -**`tet_tet_adjacency(t: array)`** - -Constructs the tet_tet adjacency matrix for a given tet mesh with tets T - -| | | -|-|-| -|Parameters| T \#T by 4 list of tets | -|Returns| TT \#T by \#4 adjacency matrix, the element i,j is the id of the tet adjacent to the j face of tet i
TTi \#T by \#4 adjacency matrix, the element i,j is the id of face of the tet TT(i,j) that is adjacent to tet i | -|Notes| the first face of a tet is [0,1,2], the second [0,1,3], the third [1,2,3], and the fourth [2,0,3]. | - - -### topological_hole_fill -**`topological_hole_fill(f: array, b: array, holes: List[List[int]])`** - -Topological fill hole on a mesh, with one additional vertex each hole -Index of new abstract vertices will be F.maxCoeff() + (index of hole) - -| | | -|-|-| -|Parameters| F \#F by simplex-size list of element indices
b \#b boundary indices to preserve
holes vector of hole loops to fill | -|Returns| F_filled input F stacked with filled triangles. | - - -### triangle_fan -**`triangle_fan(e: array)`** - -Given a list of faces tessellate all of the "exterior" edges forming another -list of - -| | | -|-|-| -|Parameters| E \#E by 2 list of exterior edges (see exterior_edges.h) | -|Returns| cap \#cap by simplex_size list of "faces" tessellating the boundary edges | - - -### triangle_triangle_adjacency -**`triangle_triangle_adjacency(f: array)`** - -Constructs the triangle-triangle adjacency matrix for a given -mesh (V,F). - -| | | -|-|-| -|Parameters| F \#F by simplex_size list of mesh faces (must be triangles) | -|Returns| TT \#F by \#3 adjacent matrix, the element i,j is the id of the triangle
adjacent to the j edge of triangle i
TTi \#F by \#3 adjacent matrix, the element i,j is the id of edge of the
triangle TT(i,j) that is adjacent with triangle i | -|Notes| NOTE: the first edge of a triangle is [0,1] the second [1,2] and the third
[2,3]. this convention is DIFFERENT from cotmatrix_entries.h | - - -### triangles_from_strip -**`triangles_from_strip(s: array)`** - -TRIANGLES_FROM_STRIP Create a list of triangles from a stream of indices -along a strip. - -| | | -|-|-| -|Parameters| S \#S list of indices | -|Returns| F \#S-2 by 3 list of triangle indices | - - -### triangulated_grid -**`triangulated_grid(nx: int, ny: int)`** - -Create a regular grid of elements (only 2D supported, currently) -Vertex position order is compatible with `igl::grid` - -| | | -|-|-| -|Parameters| nx number of vertices in the x direction
ny number of vertices in the y direction | -|Returns| GV nx*ny by 2 list of mesh vertex positions.
GF 2*(nx-1)*(ny-1) by 3 list of triangle indices | -|See also| grid, quad_grid | - - -### two_axis_valuator_fixed_up -**`two_axis_valuator_fixed_up(w: int, h: int, speed: float, down_quat: array, down_x: int, down_y: int, mouse_x: int, mouse_y: int)`** - -Applies a two-axis valuator drag rotation (as seen in Maya/Studio max) to a given rotation. - -| | | -|-|-| -|Parameters| w width of the trackball context
h height of the trackball context
speed controls how fast the trackball feels, 1 is normal
down_quat rotation at mouse down, i.e. the rotation we're applying the
trackball motion to (as quaternion). **Note:** Up-vector that is fixed
is with respect to this rotation.
down_x position of mouse down
down_y position of mouse down
mouse_x current x position of mouse
mouse_y current y position of mouse | -|Returns| quat the resulting rotation (as quaternion) | -|See also| snap_to_fixed_up | - - -### uniformly_sample_two_manifold_at_vertices -**`uniformly_sample_two_manifold_at_vertices(ow: array, k: int, push: float)`** - -Find uniform sampling up to placing samples on mesh vertices - -| | | -|-|-| -|Parameters| | - - -### uniformly_sample_two_manifold_internal -**`uniformly_sample_two_manifold_internal(w: array, f: array, k: int, push: float)`** - -UNIFORMLY_SAMPLE_TWO_MANIFOLD Attempt to sample a mesh uniformly by -furthest point relaxation as described in "Fast Automatic Skinning -Transformations" -[Jacobson et al. 12] Section 3.3. - -| | | -|-|-| -|Parameters| W \#W by dim positions of mesh in weight space
F \#F by 3 indices of triangles
k number of samplse
push factor by which corners should be pushed away | -|Returns| WS k by dim locations in weights space | - - -### unique_edge_map -**`unique_edge_map(f: array)`** - -Construct relationships between facet "half"-(or rather "viewed")-edges E -to unique edges of the mesh seen as a graph. - -| | | -|-|-| -|Parameters| F \#F by 3 list of simplices | -|Returns| E \#F*3 by 2 list of all directed edges, such that E.row(f+\#F*c) is the
edge opposite F(f,c)
uE \#uE by 2 list of unique undirected edges
EMAP \#F*3 list of indices into uE, mapping each directed edge to unique
undirected edge so that uE(EMAP(f+\#F*c)) is the unique edge
corresponding to E.row(f+\#F*c)
uE2E \#uE list of lists of indices into E of coexisting edges, so that
E.row(uE2E[i][j]) corresponds to uE.row(i) for all j in
0..uE2E[i].size()-1. | - - -### unique_simplices -**`unique_simplices(f: array)`** - -Find *combinatorially* unique simplices in F. **Order independent** - -| | | -|-|-| -|Parameters| F \#F by simplex-size list of simplices | -|Returns| FF \#FF by simplex-size list of unique simplices in F
IA \#FF index vector so that FF == sort(F(IA,:),2);
IC \#F index vector so that sort(F,2) == FF(IC,:); | - - -### unproject -**`unproject(win: array, model: array, proj: array, viewport: array)`** - -Reimplementation of gluUnproject - -| | | -|-|-| -|Parameters| win \#P by 3 or 3-vector (\#P=1) of screen space x, y, and z coordinates
model 4x4 model-view matrix
proj 4x4 projection matrix
viewport 4-long viewport vector | -|Returns| scene \#P by 3 or 3-vector (\#P=1) the unprojected x, y, and z coordinates | - - -### unproject_in_mesh -**`unproject_in_mesh(pos: array, model: array, proj: array, viewport: array, v: array, f: array)`** - -Unproject a screen location (using current opengl viewport, projection, and -model view) to a 3D position _inside_ a given mesh. If the ray through the -given screen location (x,y) _hits_ the mesh more than twice then the 3D -midpoint between the first two hits is return. If it hits once, then that -point is return. If it does not hit the mesh then obj is not set. - -| | | -|-|-| -|Parameters| pos screen space coordinates
model model matrix
proj projection matrix
viewport vieweport vector
V \#V by 3 list of mesh vertex positions
F \#F by 3 list of mesh triangle indices into V | -|Returns| obj 3d unprojected mouse point in mesh
hits vector of hits
Returns number of hits | - - -### unproject_on_line -**`unproject_on_line(uv: array, m: array, vp: array, origin: array, dir: array)`** - -Given a screen space point (u,v) and the current projection matrix (e.g. -gl_proj * gl_modelview) and viewport, _unproject_ the point into the scene -so that it lies on given line (origin and dir) and projects as closely as -possible to the given screen space point. - -| | | -|-|-| -|Parameters| UV 2-long uv-coordinates of screen space point
M 4 by 4 projection matrix
VP 4-long viewport: (corner_u, corner_v, width, height)
origin point on line
dir vector parallel to line | -|Returns| t line parameter so that closest poin on line to viewer ray through UV
lies at origin+t*dir
Z 3d position of closest point on line to viewing ray through UV | - - -### unproject_on_plane -**`unproject_on_plane(uv: array, m: array, vp: array, p: array)`** - -Given a screen space point (u,v) and the current projection matrix (e.g. -gl_proj * gl_modelview) and viewport, _unproject_ the point into the scene -so that it lies on given plane. - -| | | -|-|-| -|Parameters| UV 2-long uv-coordinates of screen space point
M 4 by 4 projection matrix
VP 4-long viewport: (corner_u, corner_v, width, height)
P 4-long plane equation coefficients: P*(X 1) = 0 | -|Returns| Z 3-long world coordinate | - - -### unproject_onto_mesh -**`unproject_onto_mesh(pos: array, model: array, proj: array, viewport: array, v: array, f: array)`** - -Unproject a screen location (using current opengl viewport, projection, and -model view) to a 3D position _onto_ a given mesh, if the ray through the -given screen location (x,y) _hits_ the mesh. - -| | | -|-|-| -|Parameters| pos screen space coordinates
model model matrix
proj projection matrix
viewport vieweport vector
V \#V by 3 list of mesh vertex positions
F \#F by 3 list of mesh triangle indices into V | -|Returns| fid id of the first face hit
bc barycentric coordinates of hit
Returns true if there's a hit | - - -### unproject_ray -**`unproject_ray(pos: array, model: array, proj: array, viewport: array)`** - -Construct a ray (source point + direction vector) given a screen space -positions (e.g. mouse) and a model-view projection constellation. - -| | | -|-|-| -|Parameters| pos 2d screen-space position (x,y)
model 4x4 model-view matrix
proj 4x4 projection matrix
viewport 4-long viewport vector | -|Returns| s source of ray (pos unprojected with z=0)
dir direction of ray (d - s) where d is pos unprojected with z=1 | - - -### upsample -**`upsample(v: array, f: array, number_of_subdivs: int = 1)`** - -Subdivide a mesh without moving vertices: loop subdivision but odd -vertices stay put and even vertices are just edge midpoints - -| | | -|-|-| -|Parameters| V \#V by dim mesh vertices
F \#F by 3 mesh triangles | -|Returns| NV new vertex positions, V is guaranteed to be at top
NF new list of face indices | -|Notes| - assumes (V,F) is edge-manifold. | - - -### vector_area_matrix -**`vector_area_matrix(f: array)`** - -Constructs the symmetric area matrix A, s.t. [V.col(0)' V.col(1)'] * A * -[V.col(0); V.col(1)] is the **vector area** of the mesh (V,F). - -| | | -|-|-| -|Parameters| f : \#f by 3 array of mesh faces (must be triangles) | -|Returns| a : \#vx2 by \#vx2 area matrix | - - -### vertex_components -**`vertex_components(f: array)`** - -Compute connected components of the vertices of a mesh given the mesh' face indices. - -| | | -|-|-| -|Parameters| f : \#f x dim array of face indices | -|Returns| An array of component ids (starting with 0) | -|See also| vertex_components_from_adjacency_matrix
facet_components | - - -### vertex_components_from_adjacency_matrix -**`vertex_components_from_adjacency_matrix(a: sparse_matrix)`** - -Compute connected components of a graph represented by a sparse adjacency -matrix. - -| | | -|-|-| -|Parameters| a : n by n sparse adjacency matrix | -|Returns| A tuple (c, counts) where c is an array of component ids (starting with 0)
and counts is a \#components array of counts for each component | -|See also| vertex_components
facet_components | - - -### vertex_triangle_adjacency -**`vertex_triangle_adjacency(f: array, n: int)`** - -vertex_face_adjacency constructs the vertex-face topology of a given mesh (V,F) - -| | | -|-|-| -|Parameters| F \#F by 3 list of triangle indices into some vertex list V
n number of vertices, \#V (e.g., F.maxCoeff()+1) | -|Returns| VF 3*\#F list List of faces indice on each vertex, so that VF(NI(i)+j) =
f, means that face f is the jth face (in no particular order) incident
on vertex i.
NI \#V+1 list cumulative sum of vertex-triangle degrees with a
preceeding zero. "How many faces" have been seen before visiting this
vertex and its incident faces. | - - -### volume -**`volume(v: array, t: array)`** - -Computes volume for all tets of a given tet mesh (V,T) - -| | | -|-|-| -|Parameters| V \#V by dim list of vertex positions
T \#V by 4 list of tet indices | -|Returns| vol \#T list of dihedral angles (in radians) | - -**Examples** -```python -vol = volume(V,T) -``` - - -### volume_from_edges -**`volume_from_edges(l: array)`** - -Computes volume for all tets from edge lengths - -| | | -|-|-| -|Parameters| L \#V by 6 list of edge lengths (see edge_lengths) | -|Returns| vol volume of the tets | - - -### volume_from_vertices -**`volume_from_vertices(a: array, b: array, c: array, d: array)`** - -Compute volumes of a list of tets defined by a, b, c, d - -| | | -|-|-| -|Parameters| a,b,c,d list of vertices vertices of the tets | -|Returns| vol volume of the tets | - - -### volume_single -**`volume_single(a: array, b: array, c: array, d: array) -> float`** - -Volume of a single tet - -| | | -|-|-| -|Parameters| a,b,c,d vertices | -|Returns| volume | - -**Examples** -```python -Single tet -``` - - -### winding_number -**`winding_number(v: array, f: array, o: array)`** - -WINDING_NUMBER Compute the sum of solid angles of a triangle/tetrahedron -described by points (vectors) V - -| | | -|-|-| -|Parameters| V n by 3 list of vertex positions
F \#F by 3 list of triangle indices, minimum index is 0
O no by 3 list of origin positions | -|Returns| S no by 1 list of winding numbers | - - -### winding_number_for_point -**`winding_number_for_point(v: array, f: array, p: array) -> float`** - -Compute winding number of a single point - -| | | -|-|-| -|Parameters| V n by dim list of vertex positions
F \#F by dim list of triangle indices, minimum index is 0
p single origin position | -|Returns| w winding number of this point | - - -### write_obj -**`write_obj(filename: str, v: array, f: array) -> bool`** - -Write a mesh in an ascii obj file. - -| | | -|-|-| -|Parameters| filename : path to outputfile
v : array of vertex positions \#v by 3
f : \#f list of face indices into vertex positions | -|Returns| ret : bool if output was successful | -|See also| read_obj | - -**Examples** -```python -# Mesh in (v, f) -success = write_obj(v, f) -``` - - -### write_off -**`write_off(str: str, v: array, f: array, c: array) -> bool`** - -Export geometry and colors-by-vertex -Export a mesh from an ascii OFF file, filling in vertex positions. -Only triangle meshes are supported - -| | | -|-|-| -|Parameters| str path to .off output file
V \#V by 3 mesh vertex positions
F \#F by 3 mesh indices into V
C double matrix of rgb values per vertex \#V by 3 | -|Returns| Returns true on success, false on errors | - - -### write_triangle_mesh -**`write_triangle_mesh(str: str, v: array, f: array, force_ascii: bool = True) -> bool`** - -write mesh to a file with automatic detection of file format. supported: obj, off, stl, wrl, ply, mesh). - -| | | -|-|-| -|Parameters| str path to file
V double matrix \#V by 3
F int matrix \#F by 3
force_ascii=True force ascii format even if binary is available | -|Returns| Returns true iff success | - - - -## class ARAP - -**`solve(: igl.pyigl_classes.ARAP, bc: numpy.ndarray, initial_guess: numpy.ndarray)`** - -## class BBW - -**`solve(: igl.pyigl_classes.BBW, V: numpy.ndarray, F: numpy.ndarray, b: numpy.ndarray[int32[m, 1]], bc: numpy.ndarray)`** - -## class SLIM - -**`energy(: igl.pyigl_classes.SLIM) -> float`** - -**`solve(: igl.pyigl_classes.SLIM, num_iters: int)`** - -**`vertices(: igl.pyigl_classes.SLIM)`** - -## class shapeup - -**`solve(: igl.pyigl_classes.shapeup, bc: numpy.ndarray, P0: numpy.ndarray, local_projection: str = 'regular_face_projection', quietIterations: bool = True)`** diff --git a/tutorial/images/102_DrawMesh.png b/tutorial/images/102_DrawMesh.png deleted file mode 100644 index c936e798..00000000 Binary files a/tutorial/images/102_DrawMesh.png and /dev/null differ diff --git a/tutorial/images/104_Colors.png b/tutorial/images/104_Colors.png deleted file mode 100644 index f3849d85..00000000 Binary files a/tutorial/images/104_Colors.png and /dev/null differ diff --git a/tutorial/images/105_Overlays.png b/tutorial/images/105_Overlays.png deleted file mode 100644 index c81c264b..00000000 Binary files a/tutorial/images/105_Overlays.png and /dev/null differ diff --git a/tutorial/images/106_ViewerMenu.png b/tutorial/images/106_ViewerMenu.png deleted file mode 100644 index af40ffb8..00000000 Binary files a/tutorial/images/106_ViewerMenu.png and /dev/null differ diff --git a/tutorial/images/501_HarmonicParam.png b/tutorial/images/501_HarmonicParam.png deleted file mode 100644 index c83e722e..00000000 Binary files a/tutorial/images/501_HarmonicParam.png and /dev/null differ diff --git a/tutorial/images/502_LSCMParam.png b/tutorial/images/502_LSCMParam.png deleted file mode 100644 index 81f66133..00000000 Binary files a/tutorial/images/502_LSCMParam.png and /dev/null differ diff --git a/tutorial/images/503_ARAPParam.png b/tutorial/images/503_ARAPParam.png deleted file mode 100644 index c853fa15..00000000 Binary files a/tutorial/images/503_ARAPParam.png and /dev/null differ diff --git a/tutorial/images/504_nrosy_field.png b/tutorial/images/504_nrosy_field.png deleted file mode 100644 index 1686bacf..00000000 Binary files a/tutorial/images/504_nrosy_field.png and /dev/null differ diff --git a/tutorial/images/504_vector_field.png b/tutorial/images/504_vector_field.png deleted file mode 100644 index f50949f1..00000000 Binary files a/tutorial/images/504_vector_field.png and /dev/null differ diff --git a/tutorial/images/505_MIQ_1.png b/tutorial/images/505_MIQ_1.png deleted file mode 100644 index 433c4822..00000000 Binary files a/tutorial/images/505_MIQ_1.png and /dev/null differ diff --git a/tutorial/images/505_MIQ_2.png b/tutorial/images/505_MIQ_2.png deleted file mode 100644 index 2a722b5a..00000000 Binary files a/tutorial/images/505_MIQ_2.png and /dev/null differ diff --git a/tutorial/images/505_MIQ_3.png b/tutorial/images/505_MIQ_3.png deleted file mode 100644 index 78bb8ce2..00000000 Binary files a/tutorial/images/505_MIQ_3.png and /dev/null differ diff --git a/tutorial/images/505_MIQ_4.png b/tutorial/images/505_MIQ_4.png deleted file mode 100644 index 0d63d20e..00000000 Binary files a/tutorial/images/505_MIQ_4.png and /dev/null differ diff --git a/tutorial/images/505_MIQ_5.png b/tutorial/images/505_MIQ_5.png deleted file mode 100644 index 3a80d5a5..00000000 Binary files a/tutorial/images/505_MIQ_5.png and /dev/null differ diff --git a/tutorial/images/505_MIQ_6.png b/tutorial/images/505_MIQ_6.png deleted file mode 100644 index e63976e7..00000000 Binary files a/tutorial/images/505_MIQ_6.png and /dev/null differ diff --git a/tutorial/images/505_MIQ_7.png b/tutorial/images/505_MIQ_7.png deleted file mode 100644 index 124ada80..00000000 Binary files a/tutorial/images/505_MIQ_7.png and /dev/null differ diff --git a/tutorial/images/505_MIQ_8.png b/tutorial/images/505_MIQ_8.png deleted file mode 100644 index e0a6eb07..00000000 Binary files a/tutorial/images/505_MIQ_8.png and /dev/null differ diff --git a/tutorial/images/506_FrameField_1.png b/tutorial/images/506_FrameField_1.png deleted file mode 100644 index 33e082f2..00000000 Binary files a/tutorial/images/506_FrameField_1.png and /dev/null differ diff --git a/tutorial/images/506_FrameField_2.png b/tutorial/images/506_FrameField_2.png deleted file mode 100644 index b75e297a..00000000 Binary files a/tutorial/images/506_FrameField_2.png and /dev/null differ diff --git a/tutorial/images/506_FrameField_3.png b/tutorial/images/506_FrameField_3.png deleted file mode 100644 index a8c51e8c..00000000 Binary files a/tutorial/images/506_FrameField_3.png and /dev/null differ diff --git a/tutorial/images/506_FrameField_4.png b/tutorial/images/506_FrameField_4.png deleted file mode 100644 index 43ee5e14..00000000 Binary files a/tutorial/images/506_FrameField_4.png and /dev/null differ diff --git a/tutorial/images/507_PolyVectorField.png b/tutorial/images/507_PolyVectorField.png deleted file mode 100644 index 7938096b..00000000 Binary files a/tutorial/images/507_PolyVectorField.png and /dev/null differ diff --git a/tutorial/images/508_ConjugateField.png b/tutorial/images/508_ConjugateField.png deleted file mode 100644 index 251c8335..00000000 Binary files a/tutorial/images/508_ConjugateField.png and /dev/null differ diff --git a/tutorial/images/509_Planarization.png b/tutorial/images/509_Planarization.png deleted file mode 100644 index 10b1d8bd..00000000 Binary files a/tutorial/images/509_Planarization.png and /dev/null differ diff --git a/tutorial/images/510_Integrable.png b/tutorial/images/510_Integrable.png deleted file mode 100644 index 3a795cc7..00000000 Binary files a/tutorial/images/510_Integrable.png and /dev/null differ diff --git a/tutorial/images/511_PolyVectorFieldGeneral.png b/tutorial/images/511_PolyVectorFieldGeneral.png deleted file mode 100644 index 9a6cb4e3..00000000 Binary files a/tutorial/images/511_PolyVectorFieldGeneral.png and /dev/null differ diff --git a/tutorial/images/602_Matlab_1.png b/tutorial/images/602_Matlab_1.png deleted file mode 100644 index 0d0e8518..00000000 Binary files a/tutorial/images/602_Matlab_1.png and /dev/null differ diff --git a/tutorial/images/602_Matlab_2.png b/tutorial/images/602_Matlab_2.png deleted file mode 100644 index 2beb0ca7..00000000 Binary files a/tutorial/images/602_Matlab_2.png and /dev/null differ diff --git a/tutorial/images/604_Triangle.png b/tutorial/images/604_Triangle.png deleted file mode 100644 index 36007271..00000000 Binary files a/tutorial/images/604_Triangle.png and /dev/null differ diff --git a/tutorial/images/605_Tetgen.png b/tutorial/images/605_Tetgen.png deleted file mode 100644 index 9573b35d..00000000 Binary files a/tutorial/images/605_Tetgen.png and /dev/null differ diff --git a/tutorial/images/606_AmbientOcclusion.png b/tutorial/images/606_AmbientOcclusion.png deleted file mode 100644 index 24cf1f05..00000000 Binary files a/tutorial/images/606_AmbientOcclusion.png and /dev/null differ diff --git a/tutorial/images/607_Picking.png b/tutorial/images/607_Picking.png deleted file mode 100644 index 82a2caf8..00000000 Binary files a/tutorial/images/607_Picking.png and /dev/null differ diff --git a/tutorial/images/608_LIM.png b/tutorial/images/608_LIM.png deleted file mode 100644 index 8e3efaad..00000000 Binary files a/tutorial/images/608_LIM.png and /dev/null differ diff --git a/tutorial/images/712_beetles.jpg b/tutorial/images/712_beetles.jpg deleted file mode 100644 index 55132408..00000000 Binary files a/tutorial/images/712_beetles.jpg and /dev/null differ diff --git a/tutorial/images/713_ShapeUp.png b/tutorial/images/713_ShapeUp.png deleted file mode 100644 index 7d8e22cf..00000000 Binary files a/tutorial/images/713_ShapeUp.png and /dev/null differ diff --git a/tutorial/images/VF.pdf b/tutorial/images/VF.pdf deleted file mode 100644 index e37343c2..00000000 Binary files a/tutorial/images/VF.pdf and /dev/null differ diff --git a/tutorial/images/VF.png b/tutorial/images/VF.png deleted file mode 100644 index 8df9bef0..00000000 Binary files a/tutorial/images/VF.png and /dev/null differ diff --git a/tutorial/images/arm-dqs.jpg b/tutorial/images/arm-dqs.jpg deleted file mode 100644 index 7f3e75bd..00000000 Binary files a/tutorial/images/arm-dqs.jpg and /dev/null differ diff --git a/tutorial/images/armadillo-fast.jpg b/tutorial/images/armadillo-fast.jpg deleted file mode 100644 index 15c64de2..00000000 Binary files a/tutorial/images/armadillo-fast.jpg and /dev/null differ diff --git a/tutorial/images/armadillo-marching-cubes.jpg b/tutorial/images/armadillo-marching-cubes.jpg deleted file mode 100644 index 69b7f8aa..00000000 Binary files a/tutorial/images/armadillo-marching-cubes.jpg and /dev/null differ diff --git a/tutorial/images/background.gif b/tutorial/images/background.gif deleted file mode 100644 index 013c4d15..00000000 Binary files a/tutorial/images/background.gif and /dev/null differ diff --git a/tutorial/images/beetle-eigen-decomposition.gif b/tutorial/images/beetle-eigen-decomposition.gif deleted file mode 100644 index 31e086f0..00000000 Binary files a/tutorial/images/beetle-eigen-decomposition.gif and /dev/null differ diff --git a/tutorial/images/big-sigcat-winding-number.gif b/tutorial/images/big-sigcat-winding-number.gif deleted file mode 100644 index 848269f5..00000000 Binary files a/tutorial/images/big-sigcat-winding-number.gif and /dev/null differ diff --git a/tutorial/images/bump-k-harmonic.jpg b/tutorial/images/bump-k-harmonic.jpg deleted file mode 100644 index e2b983b8..00000000 Binary files a/tutorial/images/bump-k-harmonic.jpg and /dev/null differ diff --git a/tutorial/images/bumpy-gaussian-curvature.jpg b/tutorial/images/bumpy-gaussian-curvature.jpg deleted file mode 100644 index bd2c02b7..00000000 Binary files a/tutorial/images/bumpy-gaussian-curvature.jpg and /dev/null differ diff --git a/tutorial/images/bunny-signed-distance.gif b/tutorial/images/bunny-signed-distance.gif deleted file mode 100644 index 28f9e039..00000000 Binary files a/tutorial/images/bunny-signed-distance.gif and /dev/null differ diff --git a/tutorial/images/bunny-swept-volume.gif b/tutorial/images/bunny-swept-volume.gif deleted file mode 100644 index 9e6709cd..00000000 Binary files a/tutorial/images/bunny-swept-volume.gif and /dev/null differ diff --git a/tutorial/images/camelhead-laplace-equation.jpg b/tutorial/images/camelhead-laplace-equation.jpg deleted file mode 100644 index 39ddde65..00000000 Binary files a/tutorial/images/camelhead-laplace-equation.jpg and /dev/null differ diff --git a/tutorial/images/cheburashka-biharmonic-leq.jpg b/tutorial/images/cheburashka-biharmonic-leq.jpg deleted file mode 100644 index 3254fdfb..00000000 Binary files a/tutorial/images/cheburashka-biharmonic-leq.jpg and /dev/null differ diff --git a/tutorial/images/cheburashka-gradient.jpg b/tutorial/images/cheburashka-gradient.jpg deleted file mode 100644 index f4234467..00000000 Binary files a/tutorial/images/cheburashka-gradient.jpg and /dev/null differ diff --git a/tutorial/images/cheburashka-knight-boolean.jpg b/tutorial/images/cheburashka-knight-boolean.jpg deleted file mode 100644 index 687b6cd2..00000000 Binary files a/tutorial/images/cheburashka-knight-boolean.jpg and /dev/null differ diff --git a/tutorial/images/cheburashka-multiscale-biharmonic-kernels.jpg b/tutorial/images/cheburashka-multiscale-biharmonic-kernels.jpg deleted file mode 100644 index e81844bd..00000000 Binary files a/tutorial/images/cheburashka-multiscale-biharmonic-kernels.jpg and /dev/null differ diff --git a/tutorial/images/cow-curvature-flow.jpg b/tutorial/images/cow-curvature-flow.jpg deleted file mode 100644 index e95f80c2..00000000 Binary files a/tutorial/images/cow-curvature-flow.jpg and /dev/null differ diff --git a/tutorial/images/cube-sphere-cylinders-csg-tree.jpg b/tutorial/images/cube-sphere-cylinders-csg-tree.jpg deleted file mode 100644 index d612c4b2..00000000 Binary files a/tutorial/images/cube-sphere-cylinders-csg-tree.jpg and /dev/null differ diff --git a/tutorial/images/cube-sphere-cylinders-csg.gif b/tutorial/images/cube-sphere-cylinders-csg.gif deleted file mode 100644 index 67d4e3cd..00000000 Binary files a/tutorial/images/cube-sphere-cylinders-csg.gif and /dev/null differ diff --git a/tutorial/images/decimated-knight-arap.jpg b/tutorial/images/decimated-knight-arap.jpg deleted file mode 100644 index 6d8a702c..00000000 Binary files a/tutorial/images/decimated-knight-arap.jpg and /dev/null differ diff --git a/tutorial/images/decimated-knight-slice-color.jpg b/tutorial/images/decimated-knight-slice-color.jpg deleted file mode 100644 index 48015175..00000000 Binary files a/tutorial/images/decimated-knight-slice-color.jpg and /dev/null differ diff --git a/tutorial/images/decimated-knight-sort-color.jpg b/tutorial/images/decimated-knight-sort-color.jpg deleted file mode 100644 index 7172cceb..00000000 Binary files a/tutorial/images/decimated-knight-sort-color.jpg and /dev/null differ diff --git a/tutorial/images/decimated-knight-subdivision.gif b/tutorial/images/decimated-knight-subdivision.gif deleted file mode 100644 index c334c086..00000000 Binary files a/tutorial/images/decimated-knight-subdivision.gif and /dev/null differ diff --git a/tutorial/images/edge-collapse.jpg b/tutorial/images/edge-collapse.jpg deleted file mode 100644 index 36d4a303..00000000 Binary files a/tutorial/images/edge-collapse.jpg and /dev/null differ diff --git a/tutorial/images/edge-collapse.pdf b/tutorial/images/edge-collapse.pdf deleted file mode 100644 index 8bf78140..00000000 Binary files a/tutorial/images/edge-collapse.pdf and /dev/null differ diff --git a/tutorial/images/fandisk-normals.jpg b/tutorial/images/fandisk-normals.jpg deleted file mode 100644 index f2cb86a6..00000000 Binary files a/tutorial/images/fandisk-normals.jpg and /dev/null differ diff --git a/tutorial/images/fertility-edge-collapse.gif b/tutorial/images/fertility-edge-collapse.gif deleted file mode 100644 index 9c5fd841..00000000 Binary files a/tutorial/images/fertility-edge-collapse.gif and /dev/null differ diff --git a/tutorial/images/fertility-principal-curvature.jpg b/tutorial/images/fertility-principal-curvature.jpg deleted file mode 100644 index 54d3ee53..00000000 Binary files a/tutorial/images/fertility-principal-curvature.jpg and /dev/null differ diff --git a/tutorial/images/geodesicdistance.jpg b/tutorial/images/geodesicdistance.jpg deleted file mode 100644 index eefc4b9c..00000000 Binary files a/tutorial/images/geodesicdistance.jpg and /dev/null differ diff --git a/tutorial/images/hand-bbw.jpg b/tutorial/images/hand-bbw.jpg deleted file mode 100644 index 65841362..00000000 Binary files a/tutorial/images/hand-bbw.jpg and /dev/null differ diff --git a/tutorial/images/hat-function.jpg b/tutorial/images/hat-function.jpg deleted file mode 100644 index 82b39257..00000000 Binary files a/tutorial/images/hat-function.jpg and /dev/null differ diff --git a/tutorial/images/heat-geodesic-beetle.gif b/tutorial/images/heat-geodesic-beetle.gif deleted file mode 100644 index e97e7fa2..00000000 Binary files a/tutorial/images/heat-geodesic-beetle.gif and /dev/null differ diff --git a/tutorial/images/heat-geodesic-peaks.png b/tutorial/images/heat-geodesic-peaks.png deleted file mode 100644 index cd8390cc..00000000 Binary files a/tutorial/images/heat-geodesic-peaks.png and /dev/null differ diff --git a/tutorial/images/libigl-logo.jpg b/tutorial/images/libigl-logo.jpg deleted file mode 100644 index c55d7d3d..00000000 Binary files a/tutorial/images/libigl-logo.jpg and /dev/null differ diff --git a/tutorial/images/max-biharmonic.jpg b/tutorial/images/max-biharmonic.jpg deleted file mode 100644 index 309580da..00000000 Binary files a/tutorial/images/max-biharmonic.jpg and /dev/null differ diff --git a/tutorial/images/multiple-meshes.png b/tutorial/images/multiple-meshes.png deleted file mode 100644 index 539df38e..00000000 Binary files a/tutorial/images/multiple-meshes.png and /dev/null differ diff --git a/tutorial/images/octopus-biharmonic-coordinates-physics.gif b/tutorial/images/octopus-biharmonic-coordinates-physics.gif deleted file mode 100644 index 7f343ba9..00000000 Binary files a/tutorial/images/octopus-biharmonic-coordinates-physics.gif and /dev/null differ diff --git a/tutorial/images/simplicial_complex_augmentation_framework.png b/tutorial/images/simplicial_complex_augmentation_framework.png deleted file mode 100644 index 0d5f21bc..00000000 Binary files a/tutorial/images/simplicial_complex_augmentation_framework.png and /dev/null differ diff --git a/tutorial/images/slim.png b/tutorial/images/slim.png deleted file mode 100644 index 9fcca738..00000000 Binary files a/tutorial/images/slim.png and /dev/null differ diff --git a/tutorial/images/streamlines.jpg b/tutorial/images/streamlines.jpg deleted file mode 100644 index 8a716f52..00000000 Binary files a/tutorial/images/streamlines.jpg and /dev/null differ diff --git a/tutorial/images/truck-facet-orientation.jpg b/tutorial/images/truck-facet-orientation.jpg deleted file mode 100644 index b4a33aa4..00000000 Binary files a/tutorial/images/truck-facet-orientation.jpg and /dev/null differ diff --git a/tutorial/index.md b/tutorial/index.md deleted file mode 100644 index 4abe8aa2..00000000 --- a/tutorial/index.md +++ /dev/null @@ -1,21 +0,0 @@ -igl python bindings -=================== - -[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/) -[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain) - - -!!! warning - The igl python binding are in development, consider this a **beta** version. - -[libigl](https://libigl.github.io) is a simple C++ geometry processing library. We have a wide functionality including construction of sparse discrete differential geometry operators and finite-elements matrices such as the cotangent Laplacian and diagonalized mass matrix, simple facet, and edge-based topology data structures. - -All these functionalities are now available through python and can be easily installed with pip: -```bash -python -m pip install libigl -``` - -We provide a complete jupyter version of the tutorials and the full [documentation](igl_docs.md). - -The full tutorial can be interactively run through mybinder -[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/libigl/libigl-python-bindings/master?filepath=tutorial%2Ftutorials.ipynb) diff --git a/tutorial/marching_cubes.py b/tutorial/marching_cubes.py deleted file mode 100644 index cbeb8069..00000000 --- a/tutorial/marching_cubes.py +++ /dev/null @@ -1,23 +0,0 @@ -import igl -import meshplot -meshplot.offline() - -import numpy as np - -import os -root_folder = os.getcwd() - -v, f = igl.read_triangle_mesh(os.path.join(root_folder, "data", "armadillo.obj")) - -#sample points on a 64x64x64 grid -n = 64 -K = np.linspace( -1.0, 1.0, n) -pts = np.array([[x,y,z] for x in K for y in K for z in K]) - -S, _, _ = igl.signed_distance(pts, v, f, sign_type=igl.SIGNED_DISTANCE_TYPE_FAST_WINDING_NUMBER) - -nV, nF = igl.marching_cubes(S, pts, n, n, n, 0.0) - -meshplot.plot(nV, nF) - - diff --git a/tutorial/plot_to_md.py b/tutorial/plot_to_md.py deleted file mode 100644 index a3b2829f..00000000 --- a/tutorial/plot_to_md.py +++ /dev/null @@ -1,38 +0,0 @@ -import meshplot -import json - -first = True -meshplot.website() - -def mp_to_md(self): - global first - if first: - first = False - res = self.to_html(imports=True, html_frame=False) - else: - res = self.to_html(imports=False, html_frame=False) - - return res - -def lis_to_md(self): - res = "" - for row in self: - for e in row: - res += e.to_html() - return res - - -def sp_to_md(self): - global first - if first: - first = False - res = self.to_html(imports=True, html_frame=False) - else: - res = self.to_html(imports=False, html_frame=False) - - return res - -get_ipython().display_formatter.formatters["text/html"].for_type(meshplot.Viewer, mp_to_md) -get_ipython().display_formatter.formatters["text/html"].for_type(meshplot.Subplot, sp_to_md) - -#get_ipython().display_formatter.formatters["text/html"].for_type(list, lis_to_md) diff --git a/tutorial/tut-chapter0.ipynb b/tutorial/tut-chapter0.ipynb deleted file mode 100644 index fe064ab7..00000000 --- a/tutorial/tut-chapter0.ipynb +++ /dev/null @@ -1,193 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Chapter 0\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)\n", - "\n", - "![](images/libigl-logo.jpg)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We introduce libigl with a series of self-contained examples. The purpose of\n", - "each example is to showcase a feature of libigl while applying to a practical\n", - "problem in geometry processing. In this chapter, we will present the basic\n", - "concepts of libigl.\n", - "\n", - "## Libigl design principles\n", - "\n", - "Before getting into the examples, we summarize the two main design principles in\n", - "libigl:\n", - "\n", - "1. **No complex data types.** We mostly use `numpy` or `scipy` matrices and vectors. This greatly\n", - " favors code reusability and interoperability and forces the function authors to expose all the\n", - " parameters used by the algorithm.\n", - "\n", - "2. **Function encapsulation.** Every function is contained in a unique Python function.\n", - "\n", - "\n", - "## Downloading Libigl\n", - "Libigl can be downloaded from [PyPI](https://pypi.org/project/libigl/):\n", - "```\n", - "python -m pip install libigl \n", - "```\n", - "\n", - "\n", - "All of libigl functionality depends only on `numpy` and `scipy`. For the visualization in this tutorial we use [meshplot](https://github.com/skoch9/meshplot) which can be easily installed from Conda:\n", - "```\n", - "pip install https://github.com/skoch9/meshplot/archive/0.4.0.tar.gz \n", - "```\n", - "\n", - "\n", - "To start using libigl (with the plots) you just need to import it together with the `numpy`, `scipy`, and `meshplot`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "from meshplot import plot, subplot, interact\n", - "\n", - "import os\n", - "root_folder = os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Mesh representation\n", - "\n", - "Libigl uses `numpy` to encode vectors and matrices and `scipy` for sparse matrices.\n", - "\n", - "A triangular mesh is encoded as a pair of matrices:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v: np.array\n", - "f: np.array" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`v` is a #N by 3 matrix which stores the coordinates of the vertices. Each\n", - "row stores the coordinate of a vertex, with its x, y and z coordinates in the first,\n", - "second and third column, respectively. The matrix `f` stores the triangle\n", - "connectivity: each line of `f` denotes a triangle whose 3 vertices are\n", - "represented as indices pointing to rows of `f`.\n", - "\n", - "![A simple mesh made of 2 triangles and 4 vertices.](images/VF.png )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "V = np.array([\n", - " [0., 0, 0],\n", - " [1, 0, 0],\n", - " [1, 1, 1],\n", - " [2, 1, 0]\n", - "])\n", - "\n", - "F = np.array([\n", - " [0, 1, 2],\n", - " [1, 3, 2]\n", - "])\n", - "\n", - "plot(V, F)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that the order of the vertex indices in `f` determines the orientation of\n", - "the triangles and it should thus be consistent for the entire surface.\n", - "This simple representation has many advantages:\n", - "\n", - "1. It is memory efficient and cache friendly\n", - "2. The use of indices instead of pointers greatly simplifies debugging\n", - "3. The data can be trivially copied and serialized\n", - "\n", - "Libigl provides input and output functions to read and write many common mesh formats.\n", - "The IO functions are igl.read_\\* and igl.write_\\*.\n", - "\n", - "Reading a mesh from a file requires a single libigl function call:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Load a mesh in OFF format\n", - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"bunny.off\"))\n", - "\n", - "## Print the vertices and faces matrices \n", - "print(\"Vertices: \", len(v))\n", - "print(\"Faces: \", len(f))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The function reads the mesh bumpy.off and returns the `v` and `f` matrices.\n", - "Similarly, a mesh can be written to an OBJ file using:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Save the mesh in OBJ format\n", - "ret = igl.write_triangle_mesh(os.path.join(root_folder, \"data\", \"bunny_out.obj\"), v, f)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/tut-chapter1.ipynb b/tutorial/tut-chapter1.ipynb deleted file mode 100644 index 65f3647b..00000000 --- a/tutorial/tut-chapter1.ipynb +++ /dev/null @@ -1,443 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Chapter 1: Discrete Geometric Quantities and Operators\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "from meshplot import plot, subplot, interact\n", - "\n", - "import os\n", - "root_folder = os.getcwd()\n", - "#root_folder = os.path.join(os.getcwd(), \"tutorial\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This chapter illustrates a few discrete quantities that libigl can compute on a mesh and the libigl functions that construct popular discrete differential geometry operators. It also provides an introduction to basic drawing and coloring routines of our viewer." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Gaussian curvature\n", - "\n", - "Gaussian curvature on a continuous surface is defined as the product of the\n", - "principal curvatures:\n", - "\n", - " $k_G = k_1 k_2.$\n", - "\n", - "As an _intrinsic_ measure, it depends on the metric and\n", - "not the surface's embedding.\n", - "\n", - "Intuitively, Gaussian curvature tells how locally spherical or _elliptic_ the\n", - "surface is ( $k_G>0$ ), how locally saddle-shaped or _hyperbolic_ the surface\n", - "is ( $k_G<0$ ), or how locally cylindrical or _parabolic_ ( $k_G=0$ ) the\n", - "surface is.\n", - "\n", - "In the discrete setting, one definition for a \"discrete Gaussian curvature\"\n", - "on a triangle mesh is via a vertex's _angular deficit_:\n", - "\n", - " $k_G(v_i) = 2π - \\sum\\limits_{j\\in N(i)}θ_{ij},$\n", - "\n", - "where $N(i)$ are the triangles incident on vertex $i$ and $θ_{ij}$ is the angle\n", - "at vertex $i$ in triangle $j$ (Meyer, 2003).\n", - "\n", - "Just like the continuous analog, our discrete Gaussian curvature reveals\n", - "elliptic, hyperbolic and parabolic vertices on the domain.\n", - "\n", - "Let's compute Gaussian curvature and visualize it in pseudocolor. First, calculate the curvature with libigl and then plot it in pseudocolors." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"bumpy.off\"))\n", - "k = igl.gaussian_curvature(v, f)\n", - "plot(v, f, k)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, compute the massmatrix and divide the gaussian curvature values by area to get the integral average." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "minv = sp.sparse.diags(1 / m.diagonal())\n", - "kn = minv.dot(k)\n", - "plot(v, f, kn)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Curvature directions\n", - "The two principal curvatures $(k_1,k_2)$ at a point on a surface measure how\n", - "much the surface bends in different directions. The directions of maximum and\n", - "minimum (signed) bending are called principal directions and are always\n", - "orthogonal.\n", - "\n", - "Mean curvature is defined as the average of principal curvatures:\n", - "\n", - " $H = \\frac{1}{2}(k_1 + k_2).$\n", - "\n", - "One way to extract mean curvature is by examining the Laplace-Beltrami operator\n", - "applied to the surface positions. The result is a so-called mean-curvature\n", - "normal:\n", - "\n", - " $-\\Delta \\mathbf{x} = H \\mathbf{n}.$\n", - "\n", - "It is easy to compute this on a discrete triangle mesh in libigl using the\n", - "cotangent Laplace-Beltrami operator (Meyer, 2003). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "l = igl.cotmatrix(v, f)\n", - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "\n", - "minv = sp.sparse.diags(1 / m.diagonal())\n", - "\n", - "hn = -minv.dot(l.dot(v))\n", - "h = np.linalg.norm(hn, axis=1)\n", - "plot(v, f, h)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Combined with the angle defect definition of discrete Gaussian curvature, one\n", - "can define principal curvatures and use least squares fitting to find\n", - "directions (Meyer, 2003).\n", - "\n", - "Alternatively, a robust method for determining principal curvatures is via\n", - "quadric fitting (Panozzo, 2010). In the neighborhood around every vertex, a\n", - "best-fit quadric is found and principal curvature values and directions are\n", - "analytically computed on this quadric." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v1, v2, k1, k2 = igl.principal_curvature(v, f)\n", - "h2 = 0.5 * (k1 + k2)\n", - "p = plot(v, f, h2, shading={\"wireframe\": False}, return_plot=True)\n", - "\n", - "avg = igl.avg_edge_length(v, f) / 2.0\n", - "p.add_lines(v + v1 * avg, v - v1 * avg, shading={\"line_color\": \"red\"})\n", - "p.add_lines(v + v2 * avg, v - v2 * avg, shading={\"line_color\": \"green\"})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Gradient\n", - "Scalar functions on a surface can be discretized as a piecewise linear function\n", - "with values defined at each mesh vertex:\n", - "\n", - " $f(\\mathbf{x}) \\approx \\sum\\limits_{i=1}^n \\phi_i(\\mathbf{x})\\, f_i,$\n", - "\n", - "where $\\phi_i$ is a piecewise linear hat function defined by the mesh so that\n", - "for each triangle $\\phi_i$ is _the_ linear function which is one only at\n", - "vertex $i$ and zero at the other corners.\n", - "\n", - "![Hat function $\\phi_i$ is one at vertex $i$, zero at all other vertices, and linear on incident triangles.](images/hat-function.jpg)\n", - "\n", - "Thus gradients of such piecewise linear functions are simply sums of gradients\n", - "of the hat functions:\n", - "\n", - " $\\nabla f(\\mathbf{x}) \\approx\n", - " \\nabla \\sum\\limits_{i=1}^n \\phi_i(\\mathbf{x})\\, f_i =\n", - " \\sum\\limits_{i=1}^n \\nabla \\phi_i(\\mathbf{x})\\, f_i.$\n", - "\n", - "This reveals that the gradient is a linear function of the vector of $f_i$\n", - "values. Because the $\\phi_i$ are linear in each triangle, their gradients are\n", - "_constant_ in each triangle. Thus our discrete gradient operator can be written\n", - "as a matrix multiplication taking vertex values to triangle values:\n", - "\n", - " $\\nabla f \\approx \\mathbf{G}\\,\\mathbf{f},$\n", - "\n", - "where $\\mathbf{f}$ is $n\\times 1$ and $\\mathbf{G}$ is an $md\\times n$ sparse\n", - "matrix. This matrix $\\mathbf{G}$ can be derived geometrically (Jacobson, 2013).\n", - "\n", - "Libigl's `grad` function computes $\\mathbf{G}$ for\n", - "triangle and tetrahedral meshes. \n", - "Let's see how this works. First load a mesh and a corresponding surface function.\n", - "Next, compute the gradient operator g (#F*3 x #V) on the triangle mesh, apply it to the surface function and extract the magnitude." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"cheburashka.off\"))\n", - "u = igl.read_dmat(os.path.join(root_folder, \"data\", \"cheburashka-scalar.dmat\"))\n", - "\n", - "g = igl.grad(v, f)\n", - "gu = g.dot(u).reshape(f.shape, order=\"F\")\n", - "\n", - "gu_mag = np.linalg.norm(gu, axis=1)\n", - "p = plot(v, f, u, shading={\"wireframe\":False}, return_plot=True)\n", - "\n", - "max_size = igl.avg_edge_length(v, f) / np.mean(gu_mag)\n", - "bc = igl.barycenter(v, f)\n", - "bcn = bc + max_size * gu\n", - "p.add_lines(bc, bcn, shading={\"line_color\": \"black\"})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Laplacian\n", - "\n", - "The discrete Laplacian is an essential geometry processing tool. Many\n", - "interpretations and flavors of the Laplace and Laplace-Beltrami operator exist.\n", - "\n", - "In open Euclidean space, the _Laplace_ operator is the usual divergence of\n", - "gradient (or equivalently the Laplacian of a function is the trace of its\n", - "Hessian):\n", - "\n", - " $\\Delta f =\n", - " \\frac{\\partial^2 f}{\\partial x^2} +\n", - " \\frac{\\partial^2 f}{\\partial y^2} +\n", - " \\frac{\\partial^2 f}{\\partial z^2}.$\n", - "\n", - "The _Laplace-Beltrami_ operator generalizes this to surfaces.\n", - "\n", - "When considering piecewise-linear functions on a triangle mesh, a discrete\n", - "Laplacian may be derived in a variety of ways. The most popular in geometry\n", - "processing is the so-called \"cotangent Laplacian\" $\\mathbf{L}$, arising\n", - "simultaneously from FEM, DEC and applying divergence theorem to vertex\n", - "one-rings. As a linear operator taking vertex values to vertex values, the\n", - "Laplacian $\\mathbf{L}$ is a $n\\times n$ matrix with elements:\n", - "\n", - "$L_{ij} = \\begin{cases}j \\in N(i) &\\cot \\alpha_{ij} + \\cot \\beta_{ij},\\\\\n", - "j \\notin N(i) & 0,\\\\\n", - "i = j & -\\sum\\limits_{k\\neq i} L_{ik},\n", - "\\end{cases}$\n", - "\n", - "where $N(i)$ are the vertices adjacent to (neighboring) vertex $i$, and\n", - "$\\alpha_{ij},\\beta_{ij}$ are the angles opposite to edge ${ij}$.\n", - "\n", - "Libigl implements discrete \"cotangent Laplacians\" for triangles meshes and\n", - "tetrahedral meshes, building both with fast geometric rules rather than \"by the\n", - "book\" FEM construction which involves many (small) matrix inversions (Sharf, 2007).\n", - "\n", - "First, load a triangle mesh and then calculate the Laplace-Beltrami operator, visualize the normals as pseudocolors." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.sparse.linalg import spsolve\n", - "\n", - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"cow.off\"))\n", - "l = igl.cotmatrix(v, f)\n", - "\n", - "n = igl.per_vertex_normals(v, f)*0.5+0.5\n", - "c = np.linalg.norm(n, axis=1)\n", - "\n", - "\n", - "vs = [v]\n", - "cs = [c]\n", - "for i in range(10):\n", - " m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_BARYCENTRIC)\n", - " s = (m - 0.001 * l)\n", - " b = m.dot(v)\n", - " v = spsolve(s, m.dot(v))\n", - " n = igl.per_vertex_normals(v, f)*0.5+0.5\n", - " c = np.linalg.norm(n, axis=1)\n", - " vs.append(v)\n", - " cs.append(c)\n", - " \n", - " \n", - "p = subplot(vs[0], f, c, shading={\"wireframe\": False}, s=[1, 4, 0])\n", - "subplot(vs[3], f, c, shading={\"wireframe\": False}, s=[1, 4, 1], data=p)\n", - "subplot(vs[6], f, c, shading={\"wireframe\": False}, s=[1, 4, 2], data=p)\n", - "subplot(vs[9], f, c, shading={\"wireframe\": False}, s=[1, 4, 3], data=p)\n", - "\n", - "# @interact(level=(0, 9))\n", - "# def mcf(level=0):\n", - "# p.update_object(vertices=vs[level], colors=cs[level])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The operator applied to mesh vertex positions amounts to smoothing by _flowing_\n", - "the surface along the mean curvature normal direction. Note that this is equivalent to minimizing surface area. The following example computes conformalized mean curvature flow using the cotangent Laplacian (Kazhdan, 2012) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Mass matrix\n", - "The mass matrix $\\mathbf{M}$ is another $n \\times n$ matrix which takes vertex\n", - "values to vertex values. From an FEM point of view, it is a discretization of\n", - "the inner-product: it accounts for the area around each vertex. Consequently,\n", - "$\\mathbf{M}$ is often a diagonal matrix, such that $M_{ii}$ is the barycentric\n", - "or voronoi area around vertex $i$ in the mesh (Meyer, 2003). The inverse of this matrix is also very useful as it transforms integrated quantities into point-wise quantities, e.g.:\n", - "\n", - " $\\Delta f \\approx \\mathbf{M}^{-1} \\mathbf{L} \\mathbf{f}.$\n", - "\n", - "In general, when encountering squared quantities integrated over the surface,\n", - "the mass matrix will be used as the discretization of the inner product when\n", - "sampling function values at vertices:\n", - "\n", - " $\\int_S x\\, y\\ dA \\approx \\mathbf{x}^T\\mathbf{M}\\,\\mathbf{y}.$\n", - "\n", - "An alternative mass matrix $\\mathbf{T}$ is a $md \\times md$ matrix which takes\n", - "triangle vector values to triangle vector values. This matrix represents an\n", - "inner-product accounting for the area associated with each triangle (i.e. the\n", - "triangles true area).\n", - "\n", - "## Alternative construction of Laplacian\n", - "\n", - "An alternative construction of the discrete cotangent Laplacian is by\n", - "\"squaring\" the discrete gradient operator. This may be derived by applying\n", - "Green's identity (ignoring boundary conditions for the moment):\n", - "\n", - " $\\int_S \\|\\nabla f\\|^2 dA = \\int_S f \\Delta f dA$\n", - "\n", - "Or in matrix form which is immediately translatable to code:\n", - "\n", - " $\\mathbf{f}^T \\mathbf{G}^T \\mathbf{T} \\mathbf{G} \\mathbf{f} =\n", - " \\mathbf{f}^T \\mathbf{M} \\mathbf{M}^{-1} \\mathbf{L} \\mathbf{f} =\n", - " \\mathbf{f}^T \\mathbf{L} \\mathbf{f}.$\n", - "\n", - "So we have that $\\mathbf{L} = \\mathbf{G}^T \\mathbf{T} \\mathbf{G}$. This also\n", - "hints that we may consider $\\mathbf{G}^T$ as a discrete _divergence_ operator,\n", - "since the Laplacian is the divergence of the gradient. Naturally, $\\mathbf{G}^T$ is\n", - "a $n \\times md$ sparse matrix which takes vector values stored at triangle faces\n", - "to scalar divergence values at vertices." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"cow.off\"))\n", - "l = igl.cotmatrix(v, f)\n", - "g = igl.grad(v, f)\n", - "\n", - "d_area = igl.doublearea(v, f)\n", - "t = sp.sparse.diags(np.hstack([d_area, d_area, d_area]) * 0.5)\n", - "\n", - "k = -g.T.dot(t).dot(g)\n", - "print(\"|k-l|: %s\"%sp.sparse.linalg.norm(k-l))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Exact Discrete Geodesic Distances\n", - "\n", - "The discrete geodesic distance between two points is the length of the shortest\n", - "path between then restricted to the surface. For triangle meshes, such a path is\n", - "made of a set of segments which can be either edges of the mesh or crossing a\n", - "triangle.\n", - "\n", - "Libigl includes a wrapper for the exact geodesic algorithm (Mitchell, 1987)\n", - "developed by Danil Kirsanov (https://code.google.com/archive/p/geodesic/),\n", - "exposing it through an Eigen-based API. The function \n", - "```python\n", - "d = igl.exact_geodesic(v, f, vs, fs, vt, ft)\n", - "```\n", - "computes the closest geodesic distances of each vertex in vt or face in ft, from\n", - "the source vertices vs or faces fs of the input mesh v, f. The output is written\n", - "in the vector d, which lists first the distances for the vertices in vt, and\n", - "then for the faces in ft. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"bunny_small.off\"))\n", - "\n", - "## Select a vertex from which the distances should be calculated\n", - "vs = np.array([0])\n", - "##All vertices are the targets\n", - "vt = np.arange(v.shape[0])\n", - "\n", - "d = igl.exact_geodesic(v, f, vs, vt)#, fs, ft)\n", - "\n", - "strip_size = 0.02\n", - "##The function should be 1 on each integer coordinate\n", - "c = np.abs(np.sin((d / strip_size * np.pi)))\n", - "plot(v, f, c, shading={\"wireframe\": False})" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/tut-chapter2.ipynb b/tutorial/tut-chapter2.ipynb deleted file mode 100644 index 297687a3..00000000 --- a/tutorial/tut-chapter2.ipynb +++ /dev/null @@ -1,535 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Chapter 2: Matrices and Linear Algebra\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "from meshplot import plot, subplot, interact\n", - "\n", - "import os\n", - "root_folder = os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Laplace equation\n", - "A common linear system in geometry processing is the Laplace equation:\n", - "\n", - " $∆z = 0$\n", - "\n", - "subject to some boundary conditions, for example Dirichlet boundary conditions\n", - "(fixed value):\n", - "\n", - " $\\left.z\\right|_{\\partial{S}} = z_{bc}$\n", - "\n", - "In the discrete setting, the linear system can be written as:\n", - "\n", - " $\\mathbf{L} \\mathbf{z} = \\mathbf{0}$\n", - "\n", - "where $\\mathbf{L}$ is the $n \\times n$ discrete Laplacian and $\\mathbf{z}$ is a\n", - "vector of per-vertex values. Most of $\\mathbf{z}$ correspond to interior\n", - "vertices and are unknown, but some of $\\mathbf{z}$ represent values at boundary\n", - "vertices. Their values are known so we may move their corresponding terms to\n", - "the right-hand side.\n", - "\n", - "Conceptually, this is very easy if we have sorted $\\mathbf{z}$ so that interior\n", - "vertices come first and then boundary vertices:\n", - "\n", - "$$\n", - " \\left(\\begin{array}{cc}\n", - " \\mathbf{L}_{in,in} & \\mathbf{L}_{in,b}\\\\\n", - " \\mathbf{L}_{b,in} & \\mathbf{L}_{b,b}\\end{array}\\right)\n", - " \\left(\\begin{array}{c}\n", - " \\mathbf{z}_{in}\\\\\n", - " \\mathbf{z}_{b}\\end{array}\\right) =\n", - " \\left(\\begin{array}{c}\n", - " \\mathbf{0}_{in}\\\\\n", - " \\mathbf{z}_{bc}\\end{array}\\right)\n", - "$$\n", - "\n", - "The bottom block of equations is no longer meaningful so we'll only consider\n", - "the top block:\n", - "\n", - "$$\n", - " \\left(\\begin{array}{cc}\n", - " \\mathbf{L}_{in,in} & \\mathbf{L}_{in,b}\\end{array}\\right)\n", - " \\left(\\begin{array}{c}\n", - " \\mathbf{z}_{in}\\\\\n", - " \\mathbf{z}_{b}\\end{array}\\right) =\n", - " \\mathbf{0}_{in}\n", - "$$\n", - "\n", - "We can move the known values to the right-hand side:\n", - "\n", - "$$\n", - " \\mathbf{L}_{in,in}\n", - " \\mathbf{z}_{in} = -\n", - " \\mathbf{L}_{in,b}\n", - " \\mathbf{z}_{b}\n", - "$$\n", - "\n", - "Finally we can solve this equation for the unknown values at interior vertices\n", - "$\\mathbf{z}_{in}$.\n", - "\n", - "However, our vertices will often not be sorted in this way. One option would be to sort `V`,\n", - "then proceed as above and then _unsort_ the solution `Z` to match `V`. However,\n", - "this solution is not very general.\n", - "\n", - "With array slicing no explicit sort is needed. Instead we can _slice-out_\n", - "submatrix blocks ($\\mathbf{L}_{in,in}$, $\\mathbf{L}_{in,b}$, etc.) and follow\n", - "the linear algebra above directly. Then we can slice the solution _into_ the\n", - "rows of `Z` corresponding to the interior vertices." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.sparse.linalg import spsolve\n", - "\n", - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"camelhead.off\"))\n", - "\n", - "## Find boundary vertices\n", - "e = igl.boundary_facets(f)\n", - "v_b = np.unique(e)\n", - "\n", - "## List of all vertex indices\n", - "v_all = np.arange(v.shape[0])\n", - "\n", - "## List of interior indices\n", - "v_in = np.setdiff1d(v_all, v_b)\n", - "\n", - "## Construct and slice up Laplacian\n", - "l = igl.cotmatrix(v, f)\n", - "l_ii = l[v_in, :]\n", - "l_ii = l_ii[:, v_in]\n", - "\n", - "l_ib = l[v_in, :]\n", - "l_ib = l_ib[:, v_b]\n", - "\n", - "## Dirichlet boundary conditions from z-coordinate\n", - "z = v[:, 2]\n", - "bc = z[v_b]\n", - "\n", - "## Solve PDE\n", - "z_in = spsolve(-l_ii, l_ib.dot(bc))\n", - "\n", - "plot(v, f, z)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Quadratic energy minimization\n", - "\n", - "The same Laplace equation may be equivalently derived by minimizing Dirichlet\n", - "energy subject to the same boundary conditions:\n", - "\n", - " $\\mathop{\\text{minimize }}_z \\frac{1}{2}\\int\\limits_S \\|\\nabla z\\|^2 dA$\n", - "\n", - "On our discrete mesh, recall that this becomes\n", - "\n", - " $\\mathop{\\text{minimize }}_\\mathbf{z} \\frac{1}{2}\\mathbf{z}^T \\mathbf{G}^T \\mathbf{D}\n", - " \\mathbf{G} \\mathbf{z} \\rightarrow \\mathop{\\text{minimize }}_\\mathbf{z} \\mathbf{z}^T \\mathbf{L} \\mathbf{z}$\n", - "\n", - "The general problem of minimizing some energy over a mesh subject to fixed\n", - "value boundary conditions is so wide spread that libigl has a dedicated api for\n", - "solving such systems.\n", - "\n", - "Let us consider a general quadratic minimization problem subject to different\n", - "common constraints:\n", - "\n", - "$$\n", - " \\mathop{\\text{minimize }}_\\mathbf{z} \\frac{1}{2}\\mathbf{z}^T \\mathbf{Q} \\mathbf{z} +\n", - " \\mathbf{z}^T \\mathbf{B} + \\text{constant},\n", - "$$\n", - "\n", - " subject to\n", - "\n", - "$$\n", - " \\mathbf{z}_b = \\mathbf{z}_{bc} \\text{ and } \\mathbf{A}_{eq} \\mathbf{z} =\n", - " \\mathbf{B}_{eq},\n", - "$$\n", - "\n", - "where\n", - "\n", - " - $\\mathbf{Q}$ is a (usually sparse) $n \\times n$ positive semi-definite\n", - " matrix of quadratic coefficients (Hessian),\n", - " - $\\mathbf{B}$ is a $n \\times 1$ vector of linear coefficients,\n", - " - $\\mathbf{z}_b$ is a $|b| \\times 1$ portion of\n", - "$\\mathbf{z}$ corresponding to boundary or _fixed_ vertices,\n", - " - $\\mathbf{z}_{bc}$ is a $|b| \\times 1$ vector of known values corresponding to\n", - " $\\mathbf{z}_b$,\n", - " - $\\mathbf{A}_{eq}$ is a (usually sparse) $m \\times n$ matrix of linear\n", - " equality constraint coefficients (one row per constraint), and\n", - " - $\\mathbf{B}_{eq}$ is a $m \\times 1$ vector of linear equality constraint\n", - " right-hand side values.\n", - "\n", - "This specification is overly general as we could write $\\mathbf{z}_b =\n", - "\\mathbf{z}_{bc}$ as rows of $\\mathbf{A}_{eq} \\mathbf{z} =\n", - "\\mathbf{B}_{eq}$, but these fixed value constraints appear so often that they\n", - "merit a dedicated function: `min_quad_with_fixed`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Linear equality constraints\n", - "We saw above that `min_quad_with_fixed` in libigl provides a compact way to\n", - "solve general quadratic programs. Let's consider another example, this time\n", - "with active linear equality constraints. Specifically let's solve the\n", - "`bi-Laplace equation` or equivalently minimize the Laplace energy:\n", - "\n", - "$$\n", - " \\Delta^2 z = 0 \\leftrightarrow \\mathop{\\text{minimize }}\\limits_z \\frac{1}{2}\n", - " \\int\\limits_S (\\Delta z)^2 dA\n", - "$$\n", - "\n", - "subject to fixed value constraints and a linear equality constraint:\n", - "\n", - " $z_{a} = 1, z_{b} = -1$ and $z_{c} = z_{d}$.\n", - "\n", - "Notice that we can rewrite the last constraint in the familiar form from above:\n", - "\n", - " $z_{c} - z_{d} = 0.$\n", - "\n", - "Now we can assembly `Aeq` as a $1 \\times n$ sparse matrix with a coefficient\n", - "$1$ in the column corresponding to vertex $c$ and a $-1$ at $d$. The right-hand\n", - "side `Beq` is simply zero.\n", - "\n", - "Internally, `min_quad_with_fixed` solves using the Lagrange Multiplier\n", - "method. This method adds additional variables for each linear constraint (in\n", - "general a $m \\times 1$ vector of variables $\\lambda$) and then solves the\n", - "saddle problem:\n", - "\n", - "$$\n", - " \\mathop{\\text{find saddle }}_{\\mathbf{z},\\lambda}\\, \\frac{1}{2}\\mathbf{z}^T \\mathbf{Q} \\mathbf{z} +\n", - " \\mathbf{z}^T \\mathbf{B} + \\text{constant} + \\lambda^T\\left(\\mathbf{A}_{eq}\n", - " \\mathbf{z} - \\mathbf{B}_{eq}\\right)\n", - "$$\n", - "\n", - "This can be rewritten in a more familiar form by stacking $\\mathbf{z}$ and\n", - "$\\lambda$ into one $(m+n) \\times 1$ vector of unknowns:\n", - "\n", - "$$\n", - " \\mathop{\\text{find saddle }}_{\\mathbf{z},\\lambda}\\,\n", - " \\frac{1}{2}\n", - " \\left(\n", - " \\mathbf{z}^T\n", - " \\lambda^T\n", - " \\right)\n", - " \\left(\n", - " \\begin{array}{cc}\n", - " \\mathbf{Q} & \\mathbf{A}_{eq}^T\\\\\n", - " \\mathbf{A}_{eq} & 0\n", - " \\end{array}\n", - " \\right)\n", - " \\left(\n", - " \\begin{array}{c}\n", - " \\mathbf{z}\\\\\n", - " \\lambda\n", - " \\end{array}\n", - " \\right) +\n", - " \\left(\n", - " \\mathbf{z}^T\n", - " \\lambda^T\n", - " \\right)\n", - " \\left(\n", - " \\begin{array}{c}\n", - " \\mathbf{B}\\\\\n", - " -\\mathbf{B}_{eq}\n", - " \\end{array}\n", - " \\right)\n", - " + \\text{constant}\n", - "$$\n", - "\n", - "Differentiating with respect to $\\left( \\mathbf{z}^T \\lambda^T \\right)$ reveals\n", - "a linear system and we can solve for $\\mathbf{z}$ and $\\lambda$. The only\n", - "difference from the straight quadratic _minimization_ system, is that this\n", - "saddle problem system will not be positive definite. Thus, we must use a\n", - "different factorization technique (LDLT rather than LLT): libigl's\n", - "`min_quad_with_fixed` automatically chooses the correct solver in\n", - "the presence of linear equality constraints.\n", - "\n", - "The following example first solves with just fixed value constraints (left: 1 and -1 on the left hand and foot respectively), then solves with an additional linear equality constraint (right: points on right hand and foot constrained to be equal).\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"cheburashka.off\"))\n", - "\n", - "## Two fixed points: Left hand, left foot should have values 1 and -1\n", - "b = np.array([4331, 5957])\n", - "bc = np.array([1., -1.])\n", - "B = np.zeros((v.shape[0], 1))\n", - "\n", - "## Construct Laplacian and mass matrix\n", - "L = igl.cotmatrix(v, f)\n", - "M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "Minv = sp.sparse.diags(1 / M.diagonal())\n", - "\n", - "## Bi-Laplacian\n", - "Q = L @ (Minv @ L)\n", - "\n", - "## Solve with only equality constraints\n", - "Aeq = sp.sparse.csc_matrix((0, 0))\n", - "Beq = np.array([])\n", - "_, z1 = igl.min_quad_with_fixed(Q, B, b, bc, Aeq, Beq, True)\n", - "\n", - "## Solve with equality and linear constraints\n", - "Aeq = sp.sparse.csc_matrix((1, v.shape[0]))\n", - "Aeq[0,6074] = 1\n", - "Aeq[0, 6523] = -1\n", - "Beq = np.array([0.])\n", - "_, z2 = igl.min_quad_with_fixed(Q, B, b, bc, Aeq, Beq, True)\n", - "\n", - "## Normalize colors to same range\n", - "min_z = min(np.min(z1), np.min(z2))\n", - "max_z = max(np.max(z1), np.max(z2))\n", - "z = [(z1 - min_z) / (max_z - min_z), (z2 - min_z) / (max_z - min_z)]\n", - "\n", - "## Plot the functions\n", - "p = subplot(v, f, z[0], shading={\"wireframe\":False}, s=[1, 2, 0])\n", - "subplot(v, f, z[1], shading={\"wireframe\":False}, s=[1, 2, 1], data=p)\n", - "\n", - "# @interact(function=[('z0', 0), ('z1', 1)])\n", - "# def sf(function):\n", - "# p.update_object(colors=z[function])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Quadratic programming\n", - "\n", - "We can generalize the quadratic optimization in the previous section even more\n", - "by allowing inequality constraints. Specifically box constraints (lower and\n", - "upper bounds):\n", - "\n", - " $\\mathbf{l} \\le \\mathbf{z} \\le \\mathbf{u},$\n", - "\n", - "where $\\mathbf{l},\\mathbf{u}$ are $n \\times 1$ vectors of lower and upper\n", - "bounds\n", - "and general linear inequality constraints:\n", - "\n", - " $\\mathbf{A}_{ieq} \\mathbf{z} \\le \\mathbf{B}_{ieq},$\n", - "\n", - "where $\\mathbf{A}_{ieq}$ is a $k \\times n$ matrix of linear coefficients and\n", - "$\\mathbf{B}_{ieq}$ is a $k \\times 1$ matrix of constraint right-hand sides.\n", - "\n", - "Again, we are overly general as the box constraints could be written as\n", - "rows of the linear inequality constraints, but bounds appear frequently enough\n", - "to merit a dedicated api.\n", - "\n", - "Libigl implements its own active set routine for solving _quadratric programs_\n", - "(QPs). This algorithm works by iteratively \"activating\" violated inequality\n", - "constraints by enforcing them as equalities and \"deactivating\" constraints\n", - "which are no longer needed.\n", - "\n", - "After deciding which constraints are active at each iteration, the problem\n", - "reduces to a quadratic minimization subject to linear _equality_ constraints,\n", - "and the method from the previous section is invoked. This is repeated until convergence.\n", - "\n", - "Currently the implementation is efficient for box constraints and sparse\n", - "non-overlapping linear inequality constraints.\n", - "\n", - "Unlike alternative interior-point methods, the active set method benefits from\n", - "a warm-start (initial guess for the solution vector $\\mathbf{z}$).\n", - "\n", - "The following example uses an active set solver to optimize discrete biharmonic kernels (Rustamov, 2011) at multiple scales:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#TODO: Check why results differ, add interactivity\n", - "\n", - "v, f, _ = igl.read_off(os.path.join(root_folder, \"data\", \"cheburashka.off\"))\n", - "\n", - "# One fixed point on belly\n", - "b = np.array([[2556]])\n", - "bc = np.array([[1.0]])\n", - "\n", - "# Construct Laplacian and mass matrix\n", - "l = igl.cotmatrix(v, f)\n", - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "minv = sp.sparse.diags(1 / m.diagonal())\n", - "\n", - "# Bi-Laplacian\n", - "q = l @ (minv @ l)\n", - "\n", - "# Zero linear term\n", - "bz = np.zeros((v.shape[0], 1))\n", - "\n", - "# Lower and upper bound\n", - "lx = np.zeros((v.shape[0], 1))\n", - "ux = np.ones((v.shape[0], 1))\n", - "\n", - "# Equality constraint constrains solution to sum to 1\n", - "beq = np.array([[0.08]])\n", - "aeq = sp.sparse.csc_matrix(m.diagonal())\n", - "\n", - "# Empty inequality constraints\n", - "aieq = sp.sparse.csc_matrix((0, 0))\n", - "bieq = np.array([])\n", - "\n", - "z = igl.active_set(q, bz, b, bc, aeq, beq, aieq, bieq, lx, ux, max_iter=8)\n", - "plot(v, f, z[1])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Eigen Decomposition\n", - "\n", - "Libigl has rudimentary support for extracting eigen pairs of a generalized\n", - "eigen value problem:\n", - "\n", - " $Ax = \\lambda B x$\n", - "\n", - "where $A$ is a sparse symmetric matrix and $B$ is a sparse positive definite\n", - "matrix. Most commonly in geometry processing, we let $A=L$ the cotangent\n", - "Laplacian and $B=M$ the per-vertex mass matrix (Vallet, 2008).\n", - "Typically applications will make use of the _low frequency_ eigen modes.\n", - "Analogous to the Fourier decomposition, a function $f$ on a surface can be\n", - "represented via its spectral decomposition of the eigen modes of the\n", - "Laplace-Beltrami:\n", - "\n", - " $f = \\sum\\limits_{i=1}^\\infty a_i \\phi_i$\n", - "\n", - "where each $\\phi_i$ is an eigen function satisfying: $\\Delta \\phi_i = \\lambda_i\n", - "\\phi_i$ and $a_i$ are scalar coefficients. For a discrete triangle mesh, a\n", - "completely analogous decomposition exists, albeit with finite sum:\n", - "\n", - " $\\mathbf{f} = \\sum\\limits_{i=1}^n a_i \\phi_i$\n", - "\n", - "where now a column vector of values at vertices $\\mathbf{f} \\in \\mathcal{R}^n$\n", - "specifies a piecewise linear function and $\\phi_i \\in \\mathcal{R}^n$ is an\n", - "eigen vector satisfying:\n", - "\n", - "$\\mathbf{L} \\phi_i = \\lambda_i \\mathbf{M} \\phi_i$.\n", - "\n", - "Note that Vallet & Levy (Vallet, 2008) propose solving a symmetrized\n", - "_standard_ eigen problem $\\mathbf{M}^{-1/2}\\mathbf{L}\\mathbf{M}^{-1/2} \\phi_i\n", - "= \\lambda_i \\phi_i$. Libigl implements a generalized eigen problem solver so\n", - "this unnecessary symmetrization can be avoided.\n", - "\n", - "Often the sum above is _truncated_ to the first $k$ eigen vectors. If the low\n", - "frequency modes are chosen, i.e. those corresponding to small $\\lambda_i$\n", - "values, then this truncation effectively _regularizes_ $\\mathbf{f}$ to smooth,\n", - "slowly changing functions over the mesh (Hildebrandt, 2011). Modal\n", - "analysis and model subspaces have been used frequently in real-time deformation\n", - "(Barbic, 2005)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the following example, the first k eigen vectors of the discrete Laplace-Beltrami operator are computed and displayed in\n", - "pseudocolors atop the beetle. \n", - "Low frequency eigen vectors of the discrete Laplace-Beltrami operator vary smoothly and slowly over the model.\n", - "At first, calculate the Laplace-Betrami operator and solve the generalized Eigen problem with scipy/arpack. \n", - "Then, rescale the Eigen vectors and visualize them." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"beetle.off\"))\n", - "l = -igl.cotmatrix(v, f)\n", - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "\n", - "k = 10\n", - "d, u = sp.sparse.linalg.eigsh(l, k, m, sigma=0, which=\"LM\")\n", - "\n", - "u = (u - np.min(u)) / (np.max(u) - np.min(u))\n", - "bbd = 0.5 * np.linalg.norm(np.max(v, axis=0) - np.min(v, axis=0))\n", - "\n", - "p = subplot(v, f, bbd * u[:, 0], shading={\"wireframe\":False, \"flat\": False}, s=[1, 2, 0])\n", - "subplot(v, f, bbd * u[:, 1], shading={\"wireframe\":False, \"flat\": False}, s=[1, 2, 1], data=p)\n", - "\n", - "# @interact(ev=[(\"EV %i\"%i, i) for i in range(k)])\n", - "# def sf(ev):\n", - "# p.update_object(colors=u[:, ev])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "\n", - "\n", - "[^jacobson_thesis_2013]: Alec Jacobson, [_Algorithms and Interfaces for Real-Time Deformation of 2D and 3D Shapes_](https://www.google.com/search?q=Algorithms+and+Interfaces+for+Real-Time+Deformation+of+2D+and+3D+Shapes), 2013.\n", - "[^kazhdan_2012]: Michael Kazhdan, Jake Solomon, Mirela Ben-Chen, [Can Mean-Curvature Flow Be Made Non-Singular](https://www.google.com/search?q=Can+Mean-Curvature+Flow+Be+Made+Non-Singular), 2012.\n", - "[^meyer_2003]: Mark Meyer, Mathieu Desbrun, Peter Schröder and Alan H. Barr, [Discrete Differential-Geometry Operators for Triangulated 2-Manifolds](https://www.google.com/search?q=Discrete+Differential-Geometry+Operators+for+Triangulated+2-Manifolds), 2003.\n", - "[^mitchell_1987]: Joseph S. B. Mitchell, David M. Mount, Christos H. Papadimitriou. [The Discrete Geodesic Problem](https://www.google.com/search?q=The+Discrete+Geodesic+Problem), 1987\n", - "[^panozzo_2010]: Daniele Panozzo, Enrico Puppo, Luigi Rocca, [Efficient Multi-scale Curvature and Crease Estimation](https://www.google.com/search?q=Efficient+Multi-scale+Curvature+and+Crease+Estimation), 2010.\n", - "[^sharf_2007]: Andrei Sharf, Thomas Lewiner, Gil Shklarski, Sivan Toledo, and Daniel Cohen-Or. [Interactive topology-aware surface reconstruction](https://www.google.com/search?q=Interactive+topology-aware+surface+reconstruction), 2007." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/tut-chapter3.ipynb b/tutorial/tut-chapter3.ipynb deleted file mode 100644 index 57f6a77b..00000000 --- a/tutorial/tut-chapter3.ipynb +++ /dev/null @@ -1,459 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Chapter 3: Shape deformation\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "from meshplot import plot, subplot, interact\n", - "\n", - "import os\n", - "root_folder = os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Modern mesh-based shape deformation methods satisfy user deformation\n", - "constraints at handles (selected vertices or regions on the mesh) and propagate\n", - "these handle deformations to the rest of the shape _smoothly_ and _without removing\n", - "or distorting details_. Libigl provides implementations of a variety of\n", - "state-of-the-art deformation techniques, ranging from quadratic mesh-based\n", - "energy minimizers, to skinning methods, to non-linear elasticity-inspired\n", - "techniques." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Biharmonic deformation\n", - "The period of research between 2000 and 2010 produced a collection of\n", - "techniques that cast the problem of handle-based shape deformation as a\n", - "quadratic energy minimization problem or equivalently the solution to a linear\n", - "partial differential equation.\n", - "\n", - "There are many flavors of these techniques, but a prototypical subset are those\n", - "that consider solutions to the bi-Laplace equation, that is a biharmonic\n", - "function (Botsch, 2004). This fourth-order PDE provides sufficient\n", - "flexibility in boundary conditions to ensure $C^1$ continuity at handle\n", - "constraints in the limit under refinement (Jacobson, 2010).\n", - "\n", - "### Biharmonic surfaces\n", - "Let us first begin our discussion of biharmonic _deformation_, by considering\n", - "biharmonic _surfaces_. We will casually define biharmonic surfaces as surface\n", - "whose _position functions_ are biharmonic with respect to some initial\n", - "parameterization:\n", - "\n", - " $\\Delta^2 \\mathbf{x}' = 0$\n", - "\n", - "and subject to some handle constraints, conceptualized as \"boundary\n", - "conditions\":\n", - "\n", - " $\\mathbf{x}'_{b} = \\mathbf{x}_{bc}.$\n", - "\n", - "where $\\mathbf{x}'$ is the unknown 3D position of a point on the surface. So we\n", - "are asking that the bi-Laplacian of each of spatial coordinate function to be\n", - "zero.\n", - "\n", - "In libigl, one can solve a biharmonic problem with `harmonic`\n", - "and setting $k=2$ (_bi_-harmonic).\n", - "\n", - "This produces a smooth surface that interpolates the handle constraints, but all\n", - "original details on the surface will be _smoothed away_. Most obviously, if the\n", - "original surface is not already biharmonic, then giving all handles the\n", - "identity deformation (keeping them at their rest positions) will **not**\n", - "reproduce the original surface. Rather, the result will be the biharmonic\n", - "surface that does interpolate those handle positions.\n", - "\n", - "Thus, we may conclude that this is not an intuitive technique for shape\n", - "deformation.\n", - "\n", - "### Biharmonic deformation fields\n", - "Now we know that one useful property for a deformation technique is \"rest pose\n", - "reproduction\": applying no deformation to the handles should apply no\n", - "deformation to the shape.\n", - "\n", - "To guarantee this by construction we can work with _deformation fields_ (ie.\n", - "displacements)\n", - "$\\mathbf{d}$ rather\n", - "than directly with positions $\\mathbf{x}$. Then the deformed positions can be\n", - "recovered as\n", - "\n", - " $\\mathbf{x}' = \\mathbf{x}+\\mathbf{d}.$\n", - "\n", - "A smooth deformation field $\\mathbf{d}$ which interpolates the deformation\n", - "fields of the handle constraints will impose a smooth deformed shape\n", - "$\\mathbf{x}'$. Naturally, we consider _biharmonic deformation fields_:\n", - "\n", - " $\\Delta^2 \\mathbf{d} = 0$\n", - "\n", - "subject to the same handle constraints, but rewritten in terms of their implied\n", - "deformation field at the boundary (handles).\n", - "\n", - " $\\mathbf{d}_b = \\mathbf{x}_{bc} - \\mathbf{x}_b.$\n", - "\n", - "Again we can use `harmonic` with $k=2$, but this time solve for the\n", - "deformation field and then recover the deformed positions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"decimated-max.obj\"))\n", - "v[:,[0, 2]] = v[:,[2, 0]] # Swap X and Z axes\n", - "u = v.copy()\n", - "\n", - "s = igl.read_dmat(os.path.join(root_folder, \"data\", \"decimated-max-selection.dmat\"))\n", - "b = np.array([[t[0] for t in [(i, s[i]) for i in range(0, v.shape[0])] if t[1] >= 0]]).T\n", - "\n", - "## Boundary conditions directly on deformed positions\n", - "u_bc = np.zeros((b.shape[0], v.shape[1]))\n", - "v_bc = np.zeros((b.shape[0], v.shape[1]))\n", - "\n", - "for bi in range(b.shape[0]):\n", - " v_bc[bi] = v[b[bi]]\n", - "\n", - " if s[b[bi]] == 0: # Don't move handle 0\n", - " u_bc[bi] = v[b[bi]]\n", - " elif s[b[bi]] == 1: # Move handle 1 down\n", - " u_bc[bi] = v[b[bi]] + np.array([[0, -50, 0]])\n", - " else: # Move other handles forward\n", - " u_bc[bi] = v[b[bi]] + np.array([[-25, 0, 0]])\n", - "\n", - "p = subplot(v, f, s, shading={\"wireframe\": False, \"colormap\": \"tab10\"}, s=[1, 4, 0])\n", - "for i in range(3):\n", - " u_bc_anim = v_bc + i*0.6 * (u_bc - v_bc)\n", - " d_bc = u_bc_anim - v_bc\n", - " d = igl.harmonic(v, f, b, d_bc, 2)\n", - " u = v + d\n", - " subplot(u, f, s, shading={\"wireframe\": False, \"colormap\": \"tab10\"}, s=[1, 4, i+1], data=p)\n", - "p\n", - "\n", - "# @interact(deformation_field=True, step=(0.0, 2.0))\n", - "# def update(deformation_field, step=0.0):\n", - "# # Determine boundary conditions\n", - "# u_bc_anim = v_bc + step * (u_bc - v_bc)\n", - "\n", - "# if deformation_field:\n", - "# d_bc = u_bc_anim - v_bc\n", - "# d = igl.harmonic(v, f, b, d_bc, 2)\n", - "# u = v + d\n", - "# else:\n", - "# u = igl.harmonic(v, f, b, u_bc_anim, 2)\n", - "# p.update_object(vertices=u)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Relationship to \"differential coordinates\" and Laplacian surface editing\n", - "Biharmonic functions (whether positions or displacements) are solutions to the\n", - "bi-Laplace equation, but also minimizers of the \"Laplacian energy\". For\n", - "example, for displacements $\\mathbf{d}$, the energy reads\n", - "\n", - " $\\int\\limits_S \\|\\Delta \\mathbf{d}\\|^2 dA,$\n", - "\n", - "where we define $\\Delta \\mathbf{d}$ to simply apply the Laplacian\n", - "coordinate-wise.\n", - "\n", - "By linearity of the Laplace(-Beltrami) operator we can reexpress this energy in\n", - "terms of the original positions $\\mathbf{x}$ and the unknown positions\n", - "$\\mathbf{x}' = \\mathbf{x} - \\mathbf{d}$:\n", - "\n", - " $\\int\\limits_S \\|\\Delta (\\mathbf{x}' - \\mathbf{x})\\|^2 dA = \\int\\limits_S\n", - " \\|\\Delta \\mathbf{x}' - \\Delta \\mathbf{x})\\|^2 dA.$\n", - "\n", - "In the early work of Sorkine et al., the quantities $\\Delta \\mathbf{x}'$ and\n", - "$\\Delta \\mathbf{x}$ were dubbed \"differential coordinates\" (Sorkine, 2004).\n", - "Their deformations (without linearized rotations) is thus equivalent to\n", - "biharmonic deformation fields." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Polyharmonic deformation\n", - "We can generalize biharmonic deformation by considering different powers of\n", - "the Laplacian, resulting in a series of PDEs of the form:\n", - "\n", - " $\\Delta^k \\mathbf{d} = 0.$\n", - "\n", - "with $k\\in{1,2,3,\\dots}$. The choice of $k$ determines the level of continuity\n", - "at the handles. In particular, $k=1$ implies $C^0$ at the boundary, $k=2$\n", - "implies $C^1$, $k=3$ implies $C^2$ and in general $k$ implies $C^{k-1}$.\n", - "\n", - "The following example deforms a flat domain (left) into a bump as a solution to various $k$-harmonic PDEs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"bump-domain.obj\"))\n", - "u = v.copy()\n", - "\n", - "# Find boundary vertices outside annulus\n", - "vrn = np.linalg.norm(v, axis = 1)\n", - "is_outer = [vrn[i] - 1.00 > -1e-15 for i in range(v.shape[0])]\n", - "is_inner = [vrn[i] - 0.15 < 1e-15 for i in range(v.shape[0])]\n", - "in_b = [is_outer[i] or is_inner[i] for i in range(len(is_outer))]\n", - "\n", - "b = np.array([i for i in range(v.shape[0]) if (in_b[i])]).T\n", - "bc = np.zeros(b.size)\n", - "\n", - "for bi in range(b.size):\n", - " bc[bi] = 0.0 if is_outer[b[bi]] else 1.0\n", - "\n", - "c = np.array(is_outer)\n", - "\n", - "for i in range(1,5):\n", - " z = igl.harmonic(v, f, b, bc, int(i))\n", - " u[:, 2] = z\n", - " if i == 1:\n", - " p = subplot(u, f, c, shading={\"wire_width\": 0.01, \"colormap\": \"tab10\"}, s=[1, 4, i-1])\n", - " else:\n", - " subplot(u, f, c, shading={\"wire_width\": 0.01, \"colormap\": \"tab10\"}, s=[1, 4, i-1], data=p)\n", - "p\n", - " \n", - "# p = plot(v, f, c, shading={\"wire_width\": 0.01, \"colormap\": \"tab10\"})\n", - "# @interact(z_max=(0.0, 1.0), k=(1, 4))\n", - "# def update(z_max, k):\n", - "# print(k)\n", - "# z = igl.harmonic(v, f, b, bc, int(k))\n", - "# u[:, 2] = z_max * z\n", - "# p.update_object(vertices=u)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## As-rigid-as-possible\n", - "\n", - "Skinning and other linear methods for deformation are inherently limited.\n", - "Difficulties arise especially when large rotations are imposed by the handle constraints.\n", - "\n", - "In the context of energy-minimization approaches, the problem stems from\n", - "comparing positions (our displacements) in the coordinate frame of the\n", - "undeformed shape. These quadratic energies are at best invariant to global\n", - "rotations of the entire shape, but not smoothly varying local rotations. Thus\n", - "linear techniques will not produce non-trivial bending and twisting.\n", - "\n", - "Furthermore, when considering solid shapes (e.g. discretized with tetrahedral\n", - "meshes) linear methods struggle to maintain local volume, and they often suffer from\n", - "shrinking and bulging artifacts.\n", - "\n", - "Non-linear deformation techniques present a solution to these problems.\n", - "They work by comparing the deformation of a mesh\n", - "vertex to its rest position _rotated_ to a new coordinate frame which best\n", - "matches the deformation. The non-linearity stems from the mutual dependence of\n", - "the deformation and the best-fit rotation. These techniques are often labeled\n", - "\"as-rigid-as-possible\" as they penalize the sum of all local deformations'\n", - "deviations from rotations.\n", - "\n", - "To arrive at such an energy, let's consider a simple per-triangle energy:\n", - "\n", - " $E_\\text{linear}(\\mathbf{X}') = \\sum\\limits_{t \\in T} a_t \\sum\\limits_{\\{i,j\\}\n", - " \\in t} w_{ij} \\left\\|\n", - " \\left(\\mathbf{x}'_i - \\mathbf{x}'_j\\right) -\n", - " \\left(\\mathbf{x}_i - \\mathbf{x}_j\\right)\\right\\|^2$\n", - "\n", - "where $\\mathbf{X}'$ are the mesh's unknown deformed vertex positions, $t$ is a\n", - "triangle in a list of triangles $T$, $a_t$ is the area of triangle $t$ and\n", - "$\\{i,j\\}$ is an edge in triangle $t$. Thus, this energy measures the norm of\n", - "change between an edge vector in the original mesh $\\left(\\mathbf{x}_i -\n", - "\\mathbf{x}_j\\right)$ and the unknown mesh $\\left(\\mathbf{x}'_i -\n", - "\\mathbf{x}'_j\\right)$.\n", - "\n", - "This energy is **not** rotation invariant. If we rotate the mesh by 90 degrees\n", - "the change in edge vectors not aligned with the axis of rotation will be large,\n", - "despite the overall deformation being perfectly rigid.\n", - "\n", - "So, the \"as-rigid-as-possible\" solution is to append auxiliary variables\n", - "$\\mathbf{R}_t$\n", - "for each triangle $t$ which are constrained to be rotations. Then the energy is\n", - "rewritten, this time comparing deformed edge vectors to their rotated rest\n", - "counterparts:\n", - "\n", - "\n", - " $E_\\text{arap}(\\mathbf{X}',\\{\\mathbf{R}_1,\\dots,\\mathbf{R}_{|T|}\\}) = \\sum\\limits_{t \\in T} a_t \\sum\\limits_{\\{i,j\\}\n", - " \\in t} w_{ij} \\left\\|\n", - " \\left(\\mathbf{x}'_i - \\mathbf{x}'_j\\right)-\n", - " \\mathbf{R}_t\\left(\\mathbf{x}_i - \\mathbf{x}_j\\right)\\right\\|^2.$\n", - "\n", - "The separation into the primary vertex position variables $\\mathbf{X}'$ and the\n", - "rotations $\\{\\mathbf{R}_1,\\dots,\\mathbf{R}_{|T|}\\}$ lead to strategy for\n", - "optimization, too. If the rotations $\\{\\mathbf{R}_1,\\dots,\\mathbf{R}_{|T|}\\}$\n", - "are held fixed then the energy is quadratic in the remaining variables\n", - "$\\mathbf{X}'$ and can be optimized by solving a (sparse) global linear system.\n", - "Alternatively, if $\\mathbf{X}'$ are held fixed then each rotation is the\n", - "solution to a localized _Procrustes_ problem (found via $3 \\times 3$ SVD or\n", - "polar decompostion). These two steps---local and global---each weakly decrease\n", - "the energy, thus we may safely iterate them until convergence.\n", - "\n", - "The different flavors of \"as-rigid-as-possible\" depend on the dimension and\n", - "codimension of the domain and the edge-sets $T$. The proposed surface\n", - "manipulation technique by Sorkine and Alexa (Sorkine, 2007), considers $T$ to\n", - "be the set of sets of edges emanating from each vertex (spokes). Later, Chao et\n", - "al. derived the relationship between \"as-rigid-as-possible\" mesh energies and\n", - "co-rotational elasticity considering 0-codimension elements as edge-sets:\n", - "triangles in 2D and tetrahedra in 3D (Chao, 2010). They also showed how\n", - "Sorkine and Alexa's edge-sets are not a discretization of a continuous energy,\n", - "proposing instead edge-sets for surfaces containing all edges of elements\n", - "incident on a vertex (spokes and rims). They show that this amounts to\n", - "measuring bending, albeit in a discretization-dependent way.\n", - "\n", - "Libigl, supports these common flavors. Selecting one is a matter of setting the energy type before the precompuation phase.\n", - "\n", - "```python\n", - "#arap_data.energy = igl::ARAP_ENERGY_TYPE_SPOKES;\n", - "#arap_data.energy = igl::ARAP_ENERGY_TYPE_SPOKES_AND_RIMS;\n", - "#arap_data.energy = igl::ARAP_ENERGY_TYPE_ELEMENTS;\n", - "arap = igl.ARAP(v, f, dimension, b)\n", - "```\n", - "Just like `igl.min_quad_with_fixed_*`, this precomputation phase only depends on the mesh, fixed vertex indices `b` and the energy parameters. To solve with certain constraints on the positions of vertices in `b`, we may call:\n", - "\n", - "```python\n", - "vn = arap.solve(bc, v)\n", - "```\n", - "\n", - "which uses `v` as an initial guess and then computes the solution into it.\n", - "\n", - "Libigl's implementation of as-rigid-as-possible deformation takes advantage of the highly optimized singular value decomposition code from McAdams et al. (McAdams, 2011) which leverages SSE intrinsics.\n", - "\n", - "The following example deforms a surface as if it were made of an elastic material. The concept of local rigidity will be revisited shortly in the context of surface parameterization." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"decimated-knight.off\"))\n", - "s = igl.read_dmat(os.path.join(root_folder, \"data\", \"decimated-knight-selection.dmat\"))\n", - "\n", - "# Vertices in selection\n", - "b = np.array([[t[0] for t in [(i, s[i]) for i in range(0, v.shape[0])] \n", - " if t[1] >= 0]]).T\n", - "\n", - "# Centroid\n", - "mid = 0.5 * (np.max(v, axis=0) + np.min(v, axis=0))\n", - "\n", - "# Precomputation\n", - "arap = igl.ARAP(v, f, 3, b)\n", - "\n", - "# Set color based on selection\n", - "c = np.ones_like(f) * np.array([1.0, 228/255, 58/255])\n", - "for fi in range(0, f.shape[0]):\n", - " if s[f[fi, 0]] >= 0 and s[f[fi, 1]] >= 0 and s[f[fi, 2]] >= 0:\n", - " c[fi] = np.array([80/255, 64/255, 1.0])\n", - "\n", - "# Plot the mesh with pseudocolors\n", - "p = subplot(v, f, c, s=[1, 4, 0])\n", - "for k in range(3):\n", - " t= 1 + k*3\n", - " bc = np.zeros((b.size, v.shape[1]))\n", - " for i in range(0, b.size):\n", - " bc[i] = v[b[i]]\n", - " if s[b[i]] == 0:\n", - " r = mid[0] * 0.25\n", - " bc[i, 0] += r * np.sin(0.5 * t * 2 * np.pi)\n", - " bc[i, 1] = bc[i, 1] - r + r * np.cos(np.pi + 0.5 * t * 2 * np.pi)\n", - " elif s[b[i]] == 1:\n", - " r = mid[1] * 0.15\n", - " bc[i, 1] = bc[i, 1] + r + r * np.cos(np.pi + 0.15 * t * 2 * np.pi)\n", - " bc[i, 2] -= r * np.sin(0.15 * t * 2 * np.pi)\n", - " elif s[b[i]] == 2:\n", - " r = mid[1] * 0.15\n", - " bc[i, 2] = bc[i, 2] + r + r * np.cos(np.pi + 0.35 * t * 2 * np.pi)\n", - " bc[i, 0] += r * np.sin(0.35 * t * 2 * np.pi)\n", - "\n", - " vn = arap.solve(bc, v)\n", - " subplot(vn, f, c, s=[1, 4, k+1], data=p)\n", - "p\n", - "\n", - "\n", - "# p = plot(v, f, c, return_plot=True)\n", - "\n", - "# @interact(t=(0.0, 10.0))\n", - "# def update(t=1.0):\n", - "# bc = np.zeros((b.size, v.shape[1]))\n", - "# for i in range(0, b.size):\n", - "# bc[i] = v[b[i]]\n", - "# if s[b[i]] == 0:\n", - "# r = mid[0] * 0.25\n", - "# bc[i, 0] += r * np.sin(0.5 * t * 2 * np.pi)\n", - "# bc[i, 1] = bc[i, 1] - r + r * np.cos(np.pi + 0.5 * t * 2 * np.pi)\n", - "# elif s[b[i]] == 1:\n", - "# r = mid[1] * 0.15\n", - "# bc[i, 1] = bc[i, 1] + r + r * np.cos(np.pi + 0.15 * t * 2 * np.pi)\n", - "# bc[i, 2] -= r * np.sin(0.15 * t * 2 * np.pi)\n", - "# elif s[b[i]] == 2:\n", - "# r = mid[1] * 0.15\n", - "# bc[i, 2] = bc[i, 2] + r + r * np.cos(np.pi + 0.35 * t * 2 * np.pi)\n", - "# bc[i, 0] += r * np.sin(0.35 * t * 2 * np.pi)\n", - "\n", - "# vn = arap.solve(bc, v)\n", - "# p.update_object(vertices=vn)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "\n", - "\n", - "[^barbic_2005]: Jernej Barbic and Doug James. [Real-Time Subspace Integration for St.Venant-Kirchhoff Deformable Models](https://www.google.com/search?q=Real-Time+Subspace+Integration+for+St.Venant-Kirchhoff+Deformable+Models), 2005.\n", - "[^hildebrandt_2011]: Klaus Hildebrandt, Christian Schulz, Christoph von Tycowicz, and Konrad Polthier. [Interactive Surface Modeling using Modal Analysis](https://www.google.com/search?q=Interactive+Surface+Modeling+using+Modal+Analysis), 2011.\n", - "[^rustamov_2011]: Raid M. Rustamov, [Multiscale Biharmonic Kernels](https://www.google.com/search?q=Multiscale+Biharmonic+Kernels), 2011.\n", - "[^vallet_2008]: Bruno Vallet and Bruno Lévy. [Spectral Geometry Processing with Manifold Harmonics](https://www.google.com/search?q=Spectral+Geometry+Processing+with+Manifold+Harmonics), 2008." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/tut-chapter4.ipynb b/tutorial/tut-chapter4.ipynb deleted file mode 100644 index c058ed08..00000000 --- a/tutorial/tut-chapter4.ipynb +++ /dev/null @@ -1,313 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Chapter 4: Parametrization\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "from meshplot import plot, subplot, interact\n", - "\n", - "import os\n", - "root_folder = os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In computer graphics, we denote as surface parametrization a map from the\n", - "surface to \\\\(\\mathbf{R}^2\\\\). It is usually encoded by a new set of 2D\n", - "coordinates for each vertex of the mesh (and possibly also by a new set of\n", - "faces in one to one correspondence with the faces of the original surface).\n", - "Note that\n", - "this definition is the *inverse* of the classical differential geometry\n", - "definition.\n", - "\n", - "A parametrization has many applications, ranging from texture mapping to\n", - "surface remeshing. Many algorithms have been proposed, and they can be broadly\n", - "divided in four families:\n", - "\n", - "1. **Single patch, fixed boundary**: these algorithm can parametrize a\n", - "disk-like part of the surface given fixed 2D positions for its boundary. These\n", - "algorithms are efficient and simple, but they usually produce high-distortion maps due to the fixed boundary.\n", - "\n", - "2. **Single patch, free boundary:** these algorithms let the boundary\n", - "deform freely, greatly reducing the map distortion. Care should be taken to\n", - "prevent the border to self-intersect.\n", - "\n", - "3. **Global parametrization**: these algorithms work on meshes with arbitrary\n", - "genus. They initially cut the mesh in multiple patches that can be separately parametrized. The generated maps are discontinuous on the cuts (often referred as *seams*).\n", - "\n", - "4. **Global seamless parametrization**: these are global parametrization algorithm that hides the seams, making the parametrization \"continuous\", under specific assumptions that we will discuss later." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Harmonic parametrization\n", - "\n", - "Harmonic parametrization (Eck, 2005) is a single patch, fixed boundary parametrization\n", - "algorithm that computes the 2D coordinates of the flattened mesh as two\n", - "harmonic functions.\n", - "\n", - "The algorithm is divided in 3 steps:\n", - "\n", - "1. Detection of the boundary vertices\n", - "2. Map the boundary vertices to a circle\n", - "3. Compute two harmonic functions (one for u and one for the v coordinate). The harmonic functions use the fixed vertices on the circle as boundary constraints.\n", - "\n", - "The algorithm is coded with libigl in the following example. `bnd` contains the indices of the boundary vertices, bnd_uv their position on the UV plane, and \"1\" denotes that we want to compute an harmonic function (2 will be for biharmonic, 3 for triharmonic, etc.). Note that each of the three\n", - "functions is designed to be reusable in other parametrization algorithms.\n", - "The UV coordinates are then used to apply a procedural checkerboard texture to the mesh." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"camelhead.off\"))\n", - "## Find the open boundary\n", - "bnd = igl.boundary_loop(f)\n", - "\n", - "## Map the boundary to a circle, preserving edge proportions\n", - "bnd_uv = igl.map_vertices_to_circle(v, bnd)\n", - "\n", - "## Harmonic parametrization for the internal vertices\n", - "uv = igl.harmonic(v, f, bnd, bnd_uv, 1)\n", - "v_p = np.hstack([uv, np.zeros((uv.shape[0],1))])\n", - "\n", - "p = subplot(v, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, s=[1, 2, 0])\n", - "subplot(v_p, f, uv=uv, shading={\"wireframe\": True, \"flat\": False}, s=[1, 2, 1], data=p)\n", - "\n", - "# @interact(mode=['3D','2D'])\n", - "# def switch(mode):\n", - "# if mode == \"3D\":\n", - "# plot(v, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, plot=p)\n", - "# if mode == \"2D\":\n", - "# plot(v_p, f, uv=uv, shading={\"wireframe\": True, \"flat\": False}, plot=p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Least squares conformal maps\n", - "\n", - "Least squares conformal maps parametrization (Levy, 2002) minimizes the\n", - "conformal (angular) distortion of the parametrization. Differently from\n", - "harmonic parametrization, it does not need to have a fixed boundary.\n", - "\n", - "LSCM minimizes the following energy:\n", - "\n", - "\\\\[ E_{LSCM}(\\mathbf{u},\\mathbf{v}) = \\int_X \\frac{1}{2}| \\nabla \\mathbf{u}^{\\perp} - \\nabla \\mathbf{v} |^2 dA \\\\]\n", - "\n", - "which can be rewritten in matrix form as (Mullen, 2008):\n", - "\n", - "\\\\[ E_{LSCM}(\\mathbf{u},\\mathbf{v}) = \\frac{1}{2} [\\mathbf{u},\\mathbf{v}]^t (L_c - 2A) [\\mathbf{u},\\mathbf{v}] \\\\]\n", - "\n", - "where $L_c$ is the cotangent Laplacian matrix and $A$ is a matrix such that\n", - "$[\\mathbf{u},\\mathbf{v}]^t A [\\mathbf{u},\\mathbf{v}]$ is equal to the [vector\n", - "area](http://en.wikipedia.org/wiki/Vector_area) of the mesh.\n", - "\n", - "Using libigl, this matrix energy can be written in a few lines of code. The\n", - "cotangent matrix can be computed using `igl.cotmatrix`. Note that we want to apply the Laplacian matrix to the u and v coordinates at the same time, thus we need to extend it taking the left Kronecker product with a 2x2 identity matrix. The area matrix is computed with `igl.vector_area_matrix`.\n", - "\n", - "The final energy matrix is $L_{flat} - 2A$. Note that in this\n", - "case we do not need to fix the boundary. To remove the null space of the energy and make the minimum unique, it is sufficient to fix two arbitrary\n", - "vertices to two arbitrary positions. The full source code is provided in the following LSCM parametrization example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"camelhead.off\"))\n", - "\n", - "# Fix two points on the boundary\n", - "b = np.array([2, 1])\n", - "\n", - "bnd = igl.boundary_loop(f)\n", - "b[0] = bnd[0]\n", - "b[1] = bnd[int(bnd.size / 2)]\n", - "\n", - "bc = np.array([[0.0, 0.0], [1.0, 0.0]])\n", - "\n", - "# LSCM parametrization\n", - "_, uv = igl.lscm(v, f, b, bc)\n", - "\n", - "p = subplot(v, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, s=[1, 2, 0])\n", - "subplot(uv, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, s=[1, 2, 1], data=p)\n", - "\n", - "# @interact(mode=['3D','2D'])\n", - "# def switch(mode):\n", - "# if mode == \"3D\":\n", - "# plot(v, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, plot=p)\n", - "# if mode == \"2D\":\n", - "# plot(uv, f, uv=uv, shading={\"wireframe\": True, \"flat\": False}, plot=p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## As-rigid-as-possible parametrization\n", - "\n", - "As-rigid-as-possible parametrization (Liu, 2008) is a powerful single-patch, non-linear algorithm to compute a parametrization that strives to preserve\n", - "distances (and thus angles). The idea is very similar to ARAP surface\n", - "deformation: each triangle is mapped to the plane trying to preserve its\n", - "original shape, up to a rigid rotation.\n", - "\n", - "The algorithm can be implemented reusing the functions discussed in the\n", - "deformation chapter: `igl.ARAP` and `arap.solve`. The only\n", - "difference is that the optimization has to be done in 2D instead of 3D and that\n", - "we need to compute a starting point. While for 3D deformation the optimization\n", - "is bootstrapped with the original mesh, this is not the case for ARAP\n", - "parametrization since the starting point must be a 2D mesh. \n", - "\n", - "In the following example, we initialize the optimization with harmonic\n", - "parametrization. Similarly to LSCM, the boundary is free to deform to minimize\n", - "the distortion." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"camelhead.off\"))\n", - "\n", - "## Find the open boundary\n", - "bnd = igl.boundary_loop(f)\n", - "\n", - "## Map the boundary to a circle, preserving edge proportions\n", - "bnd_uv = igl.map_vertices_to_circle(v, bnd)\n", - "\n", - "## Harmonic parametrization for the internal vertices\n", - "uv = igl.harmonic(v, f, bnd, bnd_uv, 1)\n", - "\n", - "arap = igl.ARAP(v, f, 2, np.zeros(0))\n", - "uva = arap.solve(np.zeros((0, 0)), uv)\n", - "\n", - "p = subplot(v, f, uv=uva, shading={\"wireframe\": False, \"flat\": False}, s=[1, 2, 0])\n", - "p = subplot(uva, f, uv=uva, shading={\"wireframe\": False, \"flat\": False}, s=[1, 2, 1], data=p)\n", - "\n", - "# @interact(mode=['3D','2D'])\n", - "# def switch(mode):\n", - "# if mode == \"3D\":\n", - "# plot(v, f, uv=uva, shading={\"wireframe\": False, \"flat\": False}, plot=p)\n", - "# if mode == \"2D\":\n", - "# plot(uva, f, uv=uva, shading={\"wireframe\": True, \"flat\": False}, plot=p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Planarization\n", - "\n", - "A quad mesh can be transformed in a planar quad mesh with Shape-Up (Bouaziz, 2012), a local/global approach that uses the global step to enforce surface continuity and the local step to enforce planarity.\n", - "\n", - "The following example planarizes a quad mesh until it satisfies a user-given planarity threshold. The colors represent the planarity of the quads." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load a quad mesh generated by a conjugate field\n", - "vqc, fqc, _ = igl.read_off(os.path.join(root_folder, \"data\", \"inspired_mesh_quads_Conjugate.off\"))\n", - "\n", - "# Convert it to a triangle mesh\n", - "fqc_tri = np.zeros((fqc.shape[0] * 2, 3), dtype=\"int64\")\n", - "fqc_tri[:fqc.shape[0]] = fqc[:, :3]\n", - "fqc_tri[fqc.shape[0]:, 0] = fqc[:, 2]\n", - "fqc_tri[fqc.shape[0]:, 1] = fqc[:, 3]\n", - "fqc_tri[fqc.shape[0]:, 2] = fqc[:, 0]\n", - "\n", - "# Planarize it\n", - "vqc_p = igl.planarize_quad_mesh(vqc, fqc, 10, 0.005)\n", - "\n", - "# Calculate a color to each quad that corresponds to its planarity\n", - "planarity = igl.quad_planarity(vqc, fqc)\n", - "planarity_p = igl.quad_planarity(vqc_p, fqc)\n", - "\n", - "c = np.concatenate([planarity, planarity])\n", - "c_p = np.concatenate([planarity_p, planarity_p])\n", - "c_range = [min(np.min(c), np.min(c_p)), max(np.max(c), np.max(c_p))]\n", - "\n", - "p = subplot(vqc, fqc_tri, c, shading={\"normalize\": c_range}, s=[1, 2, 0])\n", - "subplot(vqc_p, fqc_tri, c_p, shading={\"normalize\": c_range}, s=[1, 2, 1], data=p)\n", - "\n", - "# @interact(mode=['Curved','Planar'])\n", - "# def switch(mode):\n", - "# if mode == \"Curved\":\n", - "# p.update_object(colors=c)\n", - "# if mode == \"Planar\":\n", - "# p.update_object(vertices=vqc_p, colors=c_p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "\n", - "\n", - "[^botsch_2004]: Matrio Botsch and Leif Kobbelt. [An Intuitive Framework for Real-Time Freeform Modeling](https://www.google.com/search?q=An+Intuitive+Framework+for+Real-Time+Freeform+Modeling), 2004.\n", - "[^chao_2010]: Isaac Chao, Ulrich Pinkall, Patrick Sanan, Peter Schröder. [A Simple Geometric Model for Elastic Deformations](https://www.google.com/search?q=A+Simple+Geometric+Model+for+Elastic+Deformations), 2010.\n", - "[^jacobson_2011]: Alec Jacobson, Ilya Baran, Jovan Popović, and Olga Sorkine. [Bounded Biharmonic Weights for Real-Time Deformation](https://www.google.com/search?q=Bounded+biharmonic+weights+for+real-time+deformation), 2011.\n", - "[^jacobson_2012]: Alec Jacobson, Ilya Baran, Ladislav Kavan, Jovan Popović, and Olga Sorkine. [Fast Automatic Skinning Transformations](https://www.google.com/search?q=Fast+Automatic+Skinning+Transformations), 2012.\n", - "[^jacobson_mixed_2010]: Alec Jacobson, Elif Tosun, Olga Sorkine, and Denis Zorin. [Mixed Finite Elements for Variational Surface Modeling](https://www.google.com/search?q=Mixed+Finite+Elements+for+Variational+Surface+Modeling), 2010.\n", - "[^jacobson_skinning_course_2014]: Alec Jacobson, Zhigang Deng, Ladislav Kavan, J.P. Lewis. [_Skinning: Real-Time Shape Deformation_](https://www.google.com/search?q=Skinning+Real-Time+Shape+Deformation), 2014.\n", - "[^kavan_2008]: Ladislav Kavan, Steven Collins, Jiri Zara, and Carol O'Sullivan. [Geometric Skinning with Approximate Dual Quaternion Blending](https://www.google.com/search?q=Geometric+Skinning+with+Approximate+Dual+Quaternion+Blending), 2008.\n", - "[^mcadams_2011]: Alexa McAdams, Andrew Selle, Rasmus Tamstorf, Joseph Teran, Eftychios Sifakis. [Computing the Singular Value Decomposition of 3x3 matrices with minimal branching and elementary floating point operations](https://www.google.com/search?q=Computing+the+Singular+Value+Decomposition+of+3x3+matrices+with+minimal+branching+and+elementary+floating+point+operations), 2011.\n", - "[^sorkine_2004]: Olga Sorkine, Yaron Lipman, Daniel Cohen-Or, Marc Alexa, Christian Rössl and Hans-Peter Seidel. [Laplacian Surface Editing](https://www.google.com/search?q=Laplacian+Surface+Editing), 2004.\n", - "[^sorkine_2007]: Olga Sorkine and Marc Alexa. [As-rigid-as-possible Surface Modeling](https://www.google.com/search?q=As-rigid-as-possible+Surface+Modeling), 2007.\n", - "[^wang_bc_2015]: Yu Wang, Alec Jacobson, Jernej Barbic, Ladislav Kavan. [Linear Subspace Design for Real-Time Shape Deformation](https://www.google.com/search?q=Linear+Subspace+Design+for+Real-Time+Shape+Deformation), 2015" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/tut-chapter5.ipynb b/tutorial/tut-chapter5.ipynb deleted file mode 100644 index 739a230e..00000000 --- a/tutorial/tut-chapter5.ipynb +++ /dev/null @@ -1,128 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Chapter 5: External libraries\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "from meshplot import plot, subplot, interact\n", - "\n", - "import os\n", - "root_folder = os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "An additional positive side effect of using matrices as basic types is that it\n", - "is easy to exchange data between libigl and other software and libraries." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Baking ambient occlusion\n", - "\n", - "[Ambient occlusion](http://en.wikipedia.org/wiki/Ambient_occlusion) is a\n", - "rendering technique used to calculate the exposure of each point in a surface\n", - "to ambient lighting. It is usually encoded as a scalar (normalized between 0\n", - "and 1) associated with the vertice of a mesh.\n", - "\n", - "Formally, ambient occlusion is defined as:\n", - "\n", - "\\\\[ A_p = \\frac{1}{\\pi} \\int_\\omega V_{p,\\omega}(n \\cdot \\omega) d\\omega \\\\]\n", - "\n", - "where $V_{p,\\omega}$ is the visibility function at p, defined to be zero if p\n", - "is occluded in the direction $\\omega$ and one otherwise, and $d\\omega$ is the\n", - "infinitesimal solid angle step of the integration variable $\\omega$.\n", - "\n", - "The integral is usually approximated by casting rays in random directions\n", - "around each vertex. This approximation can be computed using the function:\n", - "\n", - "```\n", - "ao = igl.ambient_occlusion(v, f, v_samples, n_samples, 500)\n", - "```\n", - "\n", - "that given a scene described in `v` and `f`, computes the ambient occlusion of\n", - "the points in `v_samples` whose associated normals are `n_samples`. The\n", - "number of casted rays can be controlled (usually at least 300-500 rays are\n", - "required to get a smooth result) and the result is returned in `ao`, as a\n", - "single scalar for each sample.\n", - "\n", - "Ambient occlusion can be used to darken the surface colors, as shown in the following example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"fertility.off\"))\n", - "\n", - "n = igl.per_vertex_normals(v, f)\n", - "\n", - "# Compute ambient occlusion factor using embree\n", - "ao = igl.ambient_occlusion(v, f, v, n, 20)\n", - "ao = 1.0 - ao\n", - "\n", - "plot(v, f, ao, shading={\"colormap\": \"gist_gray\"})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "\n", - "\n", - "[^bommes_2009]: David Bommes, Henrik Zimmer, Leif Kobbelt. [Mixed-integer quadrangulation](http://www-sop.inria.fr/members/David.Bommes/publications/miq.pdf), 2009.\n", - "[^bouaziz_2012]: Sofien Bouaziz, Mario Deuss, Yuliy Schwartzburg, Thibaut Weise, Mark Pauly [Shape-Up: Shaping Discrete Geometry with Projections](http://lgg.epfl.ch/publications/2012/shapeup.pdf), 2012\n", - "[^eck_2005]: Matthias Eck, Tony DeRose, Tom Duchamp, Hugues Hoppe, Michael Lounsbery, Werner Stuetzle. [Multiresolution Analysis of Arbitrary Meshes](http://research.microsoft.com/en-us/um/people/hoppe/mra.pdf), 2005.\n", - "[^levy_2002]: Bruno Lévy, Sylvain Petitjean, Nicolas Ray, Jérome Maillot. [Least Squares Conformal Maps, for Automatic Texture Atlas Generation](http://www.cs.jhu.edu/~misha/Fall09/Levy02.pdf), 2002.\n", - "[^levy_2008]: Nicolas Ray, Bruno Vallet, Wan Chiu Li, Bruno Lévy. [N-Symmetry Direction Field Design](http://alice.loria.fr/publications/papers/2008/DGF/NSDFD-TOG.pdf), 2008.\n", - "[^liu_2008]: Ligang Liu, Lei Zhang, Yin Xu, Craig Gotsman, Steven J. Gortler. [A Local/Global Approach to Mesh Parameterization](http://cs.harvard.edu/~sjg/papers/arap.pdf), 2008.\n", - "[^mullen_2008]: Patrick Mullen, Yiying Tong, Pierre Alliez, Mathieu Desbrun. [Spectral Conformal Parameterization](http://www.geometry.caltech.edu/pubs/MTAD08.pdf), 2008.\n", - "[^panozzo_2014]: Daniele Panozzo, Enrico Puppo, Marco Tarini, Olga Sorkine-Hornung. [Frame Fields: Anisotropic and Non-Orthogonal Cross Fields](http://cs.nyu.edu/~panozzo/papers/frame-fields-2014.pdf), 2014.\n", - "[^vaxman_2016]: Amir Vaxman, Marcel Campen, Olga Diamanti, Daniele Panozzo, David Bommes, Klaus Hildebrandt, Mirela Ben-Chen. [Directional Field Synthesis, Design, and Processing](https://www.google.com/search?q=Directional+Field+Synthesis+Design+and+Processing), 2016" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/tut-chapter6.ipynb b/tutorial/tut-chapter6.ipynb deleted file mode 100644 index f4b48a22..00000000 --- a/tutorial/tut-chapter6.ipynb +++ /dev/null @@ -1,397 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Chapter 6: Miscellaneous\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "from meshplot import plot, subplot, interact\n", - "\n", - "import os\n", - "root_folder = os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Libigl contains a _wide_ variety of geometry processing tools and functions for\n", - "dealing with meshes and the linear algebra related to them: far too many to\n", - "discuss in this introductory tutorial. We've pulled out a couple of the\n", - "interesting functions in this chapter to highlight." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Mesh Statistics\n", - "\n", - "Libigl contains various mesh statistics, including face angles, face areas and\n", - "the detection of singular vertices, which are vertices with more or less than 6\n", - "neighbours in triangulations or 4 in quadrangulations.\n", - "\n", - "The example computes these quantities and\n", - "does a basic statistic analysis that allows to estimate the isometry and\n", - "regularity of a mesh:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"horse_quad.obj\"))\n", - "\n", - "## Count the number of irregular vertices, the border is ignored\n", - "irregular = igl.is_irregular_vertex(v, f) \n", - "v_count = v.shape[0]\n", - "irregular_v_count = np.sum(irregular)\n", - "irregular_ratio = irregular_v_count / v_count\n", - "\n", - "print(\"Irregular vertices: \\n%d/%d (%.2f%%)\\n\"%(irregular_v_count, v_count, irregular_ratio * 100))\n", - "\n", - "## Compute areas, min, max and standard deviation\n", - "area = igl.doublearea(v, f) / 2.0\n", - "\n", - "area_avg = np.mean(area)\n", - "area_min = np.min(area) / area_avg\n", - "area_max = np.max(area) / area_avg\n", - "area_ns = (area - area_avg) / area_avg\n", - "area_sigma = np.sqrt(np.mean(np.square(area_ns)))\n", - "\n", - "print(\"Areas (Min/Max)/Avg_Area Sigma: \\n%.2f/%.2f (%.2f)\\n\"%(area_min, area_max, area_sigma))\n", - "\n", - "## Compute per face angles, min, max and standard deviation\n", - "angles = igl.internal_angles(v, f)\n", - "angles = 360.0 * (angles / (2 * np.pi))\n", - "\n", - "angle_avg = np.mean(angles)\n", - "angle_min = np.min(angles)\n", - "angle_max = np.max(angles)\n", - "angle_ns = angles - angle_avg\n", - "angle_sigma = np.sqrt(np.mean(np.square(angle_ns)))\n", - "\n", - "print(\"Angles in degrees (Min/Max) Sigma: \\n%.2f/%.2f (%.2f)\\n\"%(angle_min, angle_max, angle_sigma))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The first row contains the number and percentage of irregular vertices, which\n", - "is particularly important for quadrilateral meshes when they are used to define\n", - "subdivision surfaces: every singular point will result in a point of the\n", - "surface that is only C^1.\n", - "\n", - "The second row reports the area of the minimal element, maximal element and the\n", - "standard deviation. These numbers are normalized by the mean area, so in the\n", - "example above 5.33 max area means that the biggest face is 5 times larger than\n", - "the average face. An ideal isotropic mesh would have both min and max area\n", - "close to 1.\n", - "\n", - "The third row measures the face angles, which should be close to 60 degrees (90\n", - "for quads) in a perfectly regular triangulation. For FEM purposes, the closer\n", - "the angles are to 60 degrees the more stable will the optimization be. In this\n", - "case, it is clear that the mesh is of bad quality and it will probably result\n", - "in artifacts if used for solving PDEs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Subdivision surfaces\n", - "\n", - "Given a coarse mesh (aka cage) with vertices `V` and faces `F`, one can createa\n", - "higher-resolution mesh with more vertices and faces by _subdividing_ every\n", - "face. That is, each coarse triangle in the input is replaced by many smaller\n", - "triangles. Libigl has three different methods for subdividing a triangle mesh.\n", - "\n", - "An \"in plane\" subdivision method will not change the point set or carrier\n", - "surface of the mesh. New vertices are added on the planes of existing triangles\n", - "and vertices surviving from the original mesh are not moved.\n", - "\n", - "By adding new faces, a subdivision algorithm changes the _combinatorics_ of the\n", - "mesh. The change in combinatorics and the formula for positioning the\n", - "high-resolution vertices is called the \"subdivision rule\".\n", - "\n", - "For example, in the _in plane_ subdivision method of `igl.upsample`, vertices\n", - "are added at the midpoint of every edge: $v_{ab} = \\frac{1}{2}(v_a + v_b)$ and\n", - "each triangle $(i_a,i_b,i_c)$ is replaced with four triangles:\n", - "$(i_a,i_{ab},i_{ca})$, $(i_b,i_{bc},i_{ab})$, $(i_{ab},i_{bc},i_{ca})$, and\n", - "$(i_{bc},i_{c},i_{ca})$. This process may be applied recursively, resulting in\n", - "a finer and finer mesh.\n", - "\n", - "The subdivision method of `igl.loop` is not in plane. The vertices of the\n", - "refined mesh are moved to weight combinations of their neighbors: the mesh is\n", - "smoothed as it is refined (Loop, 1987). This and other _smooth subdivision_\n", - "methods can be understood as generalizations of spline curves to surfaces. In\n", - "particular the Loop subdivision method will converge to a $C^1$ surface as we\n", - "consider the limit of recursive applications of subdivision. Away from\n", - "\"irregular\" or \"extraordinary\" vertices (vertices of the original cage with\n", - "valence not equal to 6), the surface is $C^2$. The combinatorics (connectivity\n", - "and number of faces) of `igl.loop` and `igl.upsample` are identical: the only\n", - "difference is that the vertices have been smoothed in `igl.loop`.\n", - "\n", - "Finally, libigl also implements a form of _in plane_ \"false barycentric\n", - "subdivision\" in `igl.false_barycentric_subdivision`. This method simply adds\n", - "the barycenter of every triangle as a new vertex $v_{abc}$ and replaces each\n", - "triangle with three triangles $(i_a,i_b,i_{abc})$, $(i_b,i_c,i_{abc})$, and\n", - "$(i_c,i_a,i_{abc})$. In contrast to `igl.upsample`, this method will create\n", - "triangles with smaller and smaller internal angles and new vertices will sample\n", - "the carrier surfaces with extreme bias." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ov, of = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"decimated-knight.off\"))\n", - "uv, uf = igl.upsample(ov, of)\n", - "lv, lf = igl.loop(ov, of)\n", - "\n", - "p = subplot(ov, of, shading={\"wireframe\": True}, s=[1, 3, 0])\n", - "subplot(uv, uf, shading={\"wireframe\": True}, s=[1, 3, 1], data=p)\n", - "subplot(lv, lf, shading={\"wireframe\": True}, s=[1, 3, 2], data=p)\n", - "p\n", - "\n", - "# @interact(mode=['Coarse','Upsample', 'Loop'])\n", - "# def switch(mode):\n", - "# if mode == \"Coarse\":\n", - "# plot(ov, of, shading={\"wireframe\": True}, plot=p)\n", - "# if mode == \"Upsample\":\n", - "# plot(uv, uf, shading={\"wireframe\": True}, plot=p)\n", - "# #p.update_object(vertices=uv, faces=uf)\n", - "# if mode == \"Loop\":\n", - "# plot(lv, lf, shading={\"wireframe\": True}, plot=p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Data smoothing\n", - "\n", - "A noisy function $f$ defined on a surface $\\Omega$ can be smoothed using an energy minimization that balances a smoothing term $E_S$ with a quadratic fitting term:\n", - "\n", - "$u = \\operatorname{argmin}_u \\alpha E_S(u) + (1-\\alpha)\\int_\\Omega ||u-f||^2 dx$\n", - "\n", - "The parameter $\\alpha$ determines how aggressively the function is smoothed.\n", - "\n", - "A classical choice for the smoothness energy is the Laplacian energy of the function with zero Neumann boundary conditions, which is a form of the biharmonic energy. It is constructed using the cotangent Laplacian `L` and\n", - "the mass matrix `M`: `QL = L'*(M\\L)`. Because of the implicit zero Neumann boundary conditions however, the function behavior is significantly warped at the boundary if $f$ does not have zero normal gradient at the boundary.\n", - "\n", - "In (Stein, 2017) it is suggested to use the Biharmonic energy with natural\n", - "Hessian boundary conditions instead, which corresponds to the hessian energy with the matrix `QH = H'*(M2\\H)`, where `H` is a finite element Hessian and `M2` is a stacked mass matrix. The matrices `H` and `QH` are implemented in\n", - "libigl as `igl.hessian` and `igl.hessian_energy` respectively. \n", - "\n", - "In the following example the differences between the Laplacian energy with zero Neumann boundary conditions and the Hessian energy can be clearly seen: whereas the zero Neumann boundary condition in the third image bias the isolines\n", - "of the function to be perpendicular to the boundary, the Hessian energy gives an unbiased result.\n", - "\n", - "The following example shows a function on the beetle mesh, the function with added noise, the result of smoothing with the Laplacian energy and zero Neumann boundary conditions, and the result of smoothing with the Hessian energy." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"beetle.off\"))\n", - "e = igl.edges(f)\n", - "\n", - "# Constructing an exact function to smooth\n", - "z_exact = v[0:, 2] + 0.5 * v[0:, 1] + v[0:, 1] * v[0:, 1] + v[0:, 2] * v[0:, 2] * v[0:, 2]\n", - " \n", - "# Make the exact function noisy\n", - "s = 0.2 * (np.max(z_exact) - np.min(z_exact))\n", - "np.random.seed(5)\n", - "z_noisy = z_exact + s * np.random.rand(*z_exact.shape)\n", - "\n", - "# Constructing the squared Laplacian and squared Hessian energy\n", - "l = igl.cotmatrix(v, f)\n", - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_BARYCENTRIC)\n", - "\n", - "m_inv_l = sp.sparse.linalg.spsolve(m, l)\n", - "ql = l.T @ m_inv_l\n", - "qh = igl.hessian_energy(v, f)\n", - "\n", - "# Solve to find Laplacian-smoothed and Hessian-smoothed solutions\n", - "al = 8e-4;\n", - "zl = sp.sparse.linalg.spsolve(al * ql + (1 - al) * m, al * m.dot(z_noisy))\n", - "ah = 5e-6;\n", - "zh = sp.sparse.linalg.spsolve(ah * qh + (1 - ah) * m, ah * m.dot(z_noisy))\n", - "\n", - "# Calculate isolines\n", - "ilx_v, ilx_e = igl.isolines(v, f, z_exact, 30)\n", - "iln_v, iln_e = igl.isolines(v, f, z_noisy, 30)\n", - "ill_v, ill_e = igl.isolines(v, f, zl, 30)\n", - "ilh_v, ilh_e = igl.isolines(v, f, zh, 30)\n", - "\n", - "\n", - "#TODO add edges to subplot\n", - "p = subplot(v, f, z_exact, s=[2, 2, 0])\n", - "# p.view.add_edges(ilx_v, ilx_e)\n", - "\n", - "subplot(v, f, z_noisy, s=[2, 2, 1], data=p)\n", - "# p.view.add_edges(iln_v, iln_e)\n", - "\n", - "subplot(v, f, zl, s=[2, 2, 2], data=p)\n", - "# p.view.add_edges(ill_v, ill_e)\n", - "\n", - "subplot(v, f, zh, s=[2, 2, 3], data=p)\n", - "# p.view.add_edges(ilh_v, ilh_e)\n", - "p\n", - "\n", - "# e_id = p.add_edges(ilx_v, ilx_e)\n", - "# @interact(mode=['Original', 'Noisy', 'Biharmonic smoothing (0-Neumann)', 'Biharmonic smoothing (Natural Hessian)'])\n", - "# def switch(mode):\n", - "# global e_id\n", - "# p.remove_object(e_id)\n", - "# if mode == \"Original\":\n", - "# p.update_object(colors=z_exact)\n", - "# e_id = p.add_edges(ilx_v, ilx_e)\n", - "# if mode == \"Noisy\":\n", - "# p.update_object(colors=z_noisy)\n", - "# e_id = p.add_edges(iln_v, iln_e)\n", - "# if mode == \"Biharmonic smoothing (0-Neumann)\":\n", - "# p.update_object(colors=zl)\n", - "# e_id = p.add_edges(ill_v, ill_e)\n", - "# if mode == \"Biharmonic smoothing (Natural Hessian)\":\n", - "# p.update_object(colors=zh)\n", - "# e_id = p.add_edges(ilh_v, ilh_e)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Marching Tetrahedra\n", - "\n", - "Often 3D data is captured as scalar field defined over space $f(\\mathbf{x}) : \\mathcal{R}^3 \\rightarrow \\mathcal{R}$. Lurking within this field, _iso-surfaces_ of the scalar field are often salient geometric objects. The\n", - "iso-surface at value $v$ is composed of all points $\\mathbf{x}$ in $\\mathcal{R}^3$ such that $f(\\mathbf{x}) = v$. A core problem in geometry processing is to extract an iso-surface as a triangle mesh for further mesh-based processing or visualization. This is referred to as iso-contouring.\n", - "\n", - "\"Marching Tetrahedra\" (Treece, 1999) is a [famous method](https://en.wikipedia.org/wiki/Marching_tetrahedra) for iso-contouring tri-linear functions $f$ on a 3D simplicial complex (aka a tet mesh). The core idea of this method is to contour the iso-surface passing through each cell (if it does at all) with a predefined topology (aka connectivity) chosen from a look up tabledepending on the function values at each vertex of the cell. The method\n", - "iterates (\"marches\") over all cells (\"tetrahedra\") in the complex and stitches together the final mesh.\n", - "\n", - "In libigl, `igl.marching_tets` constructs a triangle mesh `(v,f)` approximating the iso-level set for the value `isovalue` from an input scalar field `s` sampled at the vertices of a tet mesh locations `(tv, tt)`:\n", - "\n", - "```python\n", - "v, f = igl.marching_tets(tv, tt, s, isovalue)\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tv = np.load(os.path.join(root_folder, \"data\", \"marching_cube_tv.npy\"))\n", - "tt = np.load(os.path.join(root_folder, \"data\", \"marching_cube_tt.npy\"))\n", - "s = np.linalg.norm(tv, axis=1)\n", - "\n", - "svs = []\n", - "sfs = []\n", - "for i in np.linspace(0.05, 0.75, 15):\n", - " sv, sf, _, _ = igl.marching_tets(tv, tt, s, i)\n", - " svs.append(sv)\n", - " sfs.append(sf)\n", - "\n", - "\n", - "i = 0\n", - "for t in [3, 8, 11]:\n", - " if i == 0:\n", - " p = subplot(svs[t], sfs[t], s = [1, 3, i])\n", - " else:\n", - " subplot(svs[t], sfs[t], s = [1, 3, i], data=p)\n", - " i += 1\n", - "\n", - "p\n", - "\n", - " \n", - "# @interact(t=(0, 14))\n", - "# def update(t=0):\n", - "# global oid\n", - "# p.remove_object(oid)\n", - "# oid = p.add_mesh(svs[t], sfs[t]) \n", - "\n", - "# p = plot(sv, sf, return_plot=True)\n", - "# oid = 0\n", - "\n", - "# @interact(t=(0, 14))\n", - "# def update(t=0):\n", - "# global oid\n", - "# p.remove_object(oid)\n", - "# oid = p.add_mesh(svs[t], sfs[t])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "\n", - "\n", - "[^schuller_2013]: Christian Schüller, Ladislav Kavan, Daniele Panozzo, Olga Sorkine-Hornung. [Locally Injective Mappings](http://igl.ethz.ch/projects/LIM/), 2013.\n", - "[^zhou_2016]: Qingnan Zhou, Eitan Grinspun, Denis Zorin. [Mesh Arrangements for Solid Geometry](https://www.google.com/search?q=Mesh+Arrangements+for+Solid+Geometry), 2016\n", - "\n", - "\n", - "\n", - "[^baerentzen_2005]: J Andreas Baerentzen and Henrik Aanaes. [Signed distance computation using the angle weighted pseudonormal](https://www.google.com/search?q=Signed+distance+computation+using+the+angle+weighted+pseudonormal), 2005.\n", - "[^bouaziz_2012]: Sofien Bouaziz, Mario Deuss, Yuliy Schwartzburg, Thibaut Weise, Mark Pauly [Shape-Up: Shaping Discrete Geometry with Projections](http://lgg.epfl.ch/publications/2012/shapeup.pdf), 2012\n", - "[^garg_2016]: Akash Garg, Alec Jacobson, Eitan Grinspun. [Computational Design of Reconfigurables](https://www.google.com/search?q=Computational+Design+of+Reconfigurables), 2016\n", - "[^hoppe_1996]: Hugues Hoppe. [Progressive Meshes](https://www.google.com/search?q=Progressive+meshes), 1996\n", - "[^jacobson_2013]: Alec Jacobson, Ladislav Kavan, and Olga Sorkine. [Robust Inside-Outside Segmentation using Generalized Winding Numbers](https://www.google.com/search?q=Robust+Inside-Outside+Segmentation+using+Generalized+Winding+Numbers), 2013.\n", - "[^loop_1987]: Charles Loop. [Smooth Subdivision Surfaces Based on Triangles](https://www.google.com/search?q=smooth+subdivision+surfaces+based+on+triangles), 1987.\n", - "[^lorensen_1987]: W.E. Lorensen and Harvey E. Cline. [Marching cubes: A high resolution 3d surface construction algorithm](https://www.google.com/search?q=Marching+cubes:+A+high+resolution+3d+surface+construction+algorithm), 1987.\n", - "[^rabinovich_2016]: Michael Rabinovich, Roi Poranne, Daniele Panozzo, Olga Sorkine-Hornung. [Scalable Locally Injective Mappings](http://cs.nyu.edu/~panozzo/papers/SLIM-2016.pdf), 2016.\n", - "[^schroeder_1994]: William J. Schroeder, William E. Lorensen, and Steve Linthicum. [Implicit Modeling of Swept Surfaces and Volumes](https://www.google.com/search?q=implicit+modeling+of+swept+surfaces+and+volumes), 1994.\n", - "[^takayama14]: Kenshi Takayama, Alec Jacobson, Ladislav Kavan, Olga Sorkine-Hornung. [A Simple Method for Correcting Facet Orientations in Polygon Meshes Based on Ray Casting](https://www.google.com/search?q=A+Simple+Method+for+Correcting+Facet+Orientations+in+Polygon+Meshes+Based+on+Ray+Casting), 2014.\n", - "[^treece_1999]: G.M. Treece, R.W. Prager, and A.H.Gee [Regularised marching tetrahedra: improved iso-surface extraction](https://www.sciencedirect.com/science/article/pii/S009784939900076X), 1999.\n", - "[^crane_2013]: Keenan Crane, Clarisse Weischedel, and Max Wardetzky. [Geodesics in Heat: A New Approach to Computing Distance Based on Heat Flow](https://www.google.com/search?q=geodesics+in+heat+a+new+approach+to+computing+distance+based+on+heat+flow), 2013.\n", - "[^bobenko_2005]: Alexander I. Bobenko and Boris A. Springborn. [A discrete Laplace-Beltrami operator for simplicial surfaces](https://www.google.com/search?q=a+discrete+laplace-beltrami+operator+for+simplicial+surfaces), 2005.\n", - "[^jiang_2017]: Zhongshi Jiang, Scott Schaefer, Daniele Panozzo. [SCAF: Simplicial Complex Augmentation Framework for Bijective Maps](https://doi.org/10.1145/3130800.3130895), 2017" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/tutorial/tut-chapter7.ipynb b/tutorial/tut-chapter7.ipynb deleted file mode 100644 index e8aa2c80..00000000 --- a/tutorial/tut-chapter7.ipynb +++ /dev/null @@ -1,530 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Chapter 7: Skinned Shape Deformation\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Overview\n", - "\n", - "In computer animation, shape deformation is often referred to as “skinning”. Constraints are posed as relative rotations of internal rigid “bones” inside a character. The deformation method, or skinning method, determines how the surface of the character (i.e. its skin) should move as a function of the bone rotations.\n", - "\n", - "In this chapter, we show 3 techniques\n", - "1. Rigid Skinning - the most basic shape deformation technique\n", - "2. Linear Blend Skinning (LBS) - the most commonly used shape deformation technique\n", - "3. Direct Delta Mush (DDM) - one of many advanced deformation techniques to address limitations of LBS" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "import meshplot as mp\n", - "\n", - "import os\n", - "\n", - "root_folder = os.getcwd()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Mesh Loading\n", - "\n", - "Load the elephant mesh along with it's skeleton structure, the weights necessary to control the mesh using the skeleton, and an animation sequence." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Array Shapes\n", - "Vertices: (6034, 3), Faces: (12064, 3), Bones: (25, 3), Parents: (24, 2), Weights: (6034, 24), Anim: (288, 457)\n" - ] - } - ], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"elephant.obj\"))\n", - "\n", - "bones, parents, _, _, _, _ = igl.read_tgf(os.path.join(root_folder, \"data\", \"elephant.tgf\"))\n", - "\n", - "w = igl.read_dmat(os.path.join(root_folder, \"data\", \"elephant-weights.dmat\"))\n", - "\n", - "anim = igl.read_dmat(os.path.join(root_folder, \"data\", \"elephant-anim.dmat\"))\n", - "\n", - "num_frames = anim.shape[1]\n", - "\n", - "print(f\"Array Shapes\\nVertices: {v.shape}, Faces: {f.shape}, Bones: {bones.shape}, Parents: {parents.shape}, Weights: {w.shape}, Anim: {anim.shape}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First line loads the elephant mesh which contains the vertices and faces as demonstrated in [chapter 0](https://github.com/libigl/libigl-python-bindings/blob/master/tutorial/tut-chapter0.ipynb)\n", - "\n", - "Second line loads a set of bones and a skeleton heirarchy described by parents.\n", - "\n", - "Third line loads a set of weights $W$ describing how much each vertex ($i$) will be influenced by the bones loaded above ($w_i$). These weights are meant to be used with Linear Blend Skinning.\n", - "\n", - "Fourth line loads an animation sequence where each column describes a pose $\\theta$. $\\theta$ is a stack of vectorized affine transforms, one for each bone describing the translation and rotation of the bone. In this elephant object example, there are a total of 24 affectable bones. Hence, each column has $3 * 4 * 24 = 288$ elements where the affine transform for each bone is $3$ rows and $4$ columns." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Rigid Skinning\n", - "\n", - "In the rigid (or simple) skinning approach, each vertex in the mesh is attached to exactly one bone in the skeleton. When the skeleton is posed, the vertices are transformed by their joint’s world space matrix. Every vertex $i$ is transformed by exactly one matrix using the equation $u_i = v_i . W$, where $W$ is the skinning weights matrix loaded earlier." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Example of Conversion at vertex 10\n", - "LBS Weights: [9.99997436e-01 0.00000000e+00 2.40198315e-06 0.00000000e+00]\n", - "Rigid Weights: [1. 0. 0. 0.]\n" - ] - } - ], - "source": [ - "def convert_lbs_weights_rigid_weights(lbs_w):\n", - " rigid_w = np.zeros(w.shape)\n", - " for i in range(0, w.shape[0]):\n", - " maxj = w[i].argmax()\n", - " for j in range(0, w.shape[1]):\n", - " rigid_w[i, j] = float(maxj == j)\n", - " return rigid_w\n", - " \n", - "rigid_w = convert_lbs_weights_rigid_weights(w)\n", - " \n", - "rigid_lbs_matrix = igl.lbs_matrix(v, rigid_w)\n", - " \n", - "print(f\"Example of Conversion at vertex 10\\nLBS Weights: {w[10, 5:9]}\\nRigid Weights: {rigid_w[10, 5:9]}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The function `convert_lbs_weights_rigid_weights` is just a helper function to help us convert the LBS weights we loaded into rigid skinning weights. For rigid skinning, each vertex can be inluenced by one bone. So all we do is find the maximum influence for each vertex and set the weight there to 1 and the rest to 0.\n", - "\n", - "The output shows an example of how bones $5, 6, 7$ and $8$ affect vertex $10$ through LBS and Rigid skinning. A value of $1.0$ implies that the respective bone fully controls the vertex and a value of $0.0$ implies that the bone has no inluence on the vertex.\n", - "\n", - "Finally, we use `igl.lbs_matrix` function to produce matrix $M$ as described in the [Fast Automatic Skinning Transformations tutorial](https://libigl.github.io/tutorial/#fast-automatic-skinning-transformations). " - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "tags": [] - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "ec240c69cb66464398ec7b3b2199073f", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(IntSlider(value=229, description='frame', max=457, min=1), Output()), _dom_classes=('wid…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "c56a26105d19475997cd08e415e277d7", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(0.1529960…" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def rigid_deform_mesh(pose):\n", - " rigid_v = rigid_lbs_matrix @ pose\n", - " return rigid_v\n", - "\n", - "viewer_rigid = mp.Viewer({})\n", - "rigid = viewer_rigid.add_mesh(v, f, np.array([0.0, 0.5, 0.0]))\n", - "\n", - "@mp.interact(frame=(1, num_frames))\n", - "def update_frame(frame):\n", - " frame = frame - 1\n", - " pose = anim[:, frame].reshape(parents.shape[0] * 4, 3, order='F')\n", - " deformed_mesh = rigid_deform_mesh(pose)\n", - " \n", - " viewer_rigid.update_object(oid=rigid, vertices=deformed_mesh)\n", - " \n", - "viewer_rigid._renderer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The function `rigid_deform_mesh` just multiplies the matrix $M$ with the pose $\\theta$ to produce the deformed mesh." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Linear Blend Skinning\n", - "\n", - "In the linear blend skinning approach, each vertex in the mesh can be affected by one or more bones in the skeleton. When the skeleton is posed, the vertices are transformed by doing a weighted sum of joints' world space matrices. The influence of a bone on a vertex can be weighted between $0.0$ and $1.0$ and the sum of influences of bones on each vertex should sum to 1. That is, $\\sum_{j=1}^{J} w_i = 1.0, \\forall i \\in V$ where $V$ is the set of vertices and $J$ is the number of bones." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7c878a5d35124479b4ae2e96cd31149b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(IntSlider(value=229, description='frame', max=457, min=1), Output()), _dom_classes=('wid…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "45d4a8b34cfd4d1c92a25922d416941b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(0.1529960…" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "lbs_matrix = igl.lbs_matrix(v, w)\n", - "\n", - "def lbs_deform_mesh(pose):\n", - " lbs_v = lbs_matrix @ pose\n", - " return lbs_v\n", - "\n", - "viewer_lbs = mp.Viewer({})\n", - "lbs = viewer_lbs.add_mesh(v, f, np.array([0.5, 0.0, 0.0]))\n", - "\n", - "@mp.interact(frame=(1, num_frames))\n", - "def update_frame(frame):\n", - " frame = frame - 1\n", - " pose = anim[:, frame].reshape(parents.shape[0] * 4, 3, order='F')\n", - " deformed_mesh = lbs_deform_mesh(pose)\n", - " \n", - " viewer_lbs.update_object(oid=lbs, vertices=deformed_mesh)\n", - " \n", - "viewer_lbs._renderer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The function `lbs_deform_mesh` just multiplies the matrix $M$ with the pose $\\theta$ to produce the deformed mesh similar to `rigid_deform_mesh`. The key difference being that `lbs_deform_mesh` uses the matrix $M$ produced by the originally loaded LBS weights $W$." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Direct Delta Mush Skinning\n", - "\n", - "Linear blend skinning suffers from shrinkage and collapse artifacts due to its inherent linearity. Direct Delta Mush skinning attempts to solve both of these issues by providing a direct skinning method that takes as input a rig with piecewise-constant weight functions (weights are either $=0$ or $=1$ everywhere, i.e weights used for rigid skinning above). Direct delta mush is an adaptation of a less performant method called simply **Delta Mush**. The computation of Delta Mush separates into **“bind pose” precomputation** and **runtime evaluation**.\n", - "\n", - "## \"Bind Pose\" Precomputation\n", - "\n", - "At bind time, Laplacian smoothing is conducted on the bind pose, moving each vertex from its rest position $v_i$ to a new position $\\tilde{v_i}$. The “delta” describing undoing this smoothing procedure, is computed and stored in a local coordinate frame associated with the vertex:\n", - "\n", - "$\\delta_i = T_i^{−1}(v_i − \\tilde{v_i})$\n", - "\n", - "The result is “vector-valued” skinning weights per-vertex per-bone. This can be stored in a matrix $\\Omega$ (i.e `omega`)." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Num Frames: 457, Omega: (6034, 240)\n" - ] - } - ], - "source": [ - "p = 20\n", - "l = 3\n", - "k = 1\n", - "a = 0.8\n", - "omega = igl.direct_delta_mush_precomputation(v, f, rigid_w, p, l, k, a)\n", - "\n", - "print(f\"Num Frames: {num_frames}, Omega: {omega.shape}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `igl.direct_delta_mush_precomputation` function generates $\\Omega$ that has 10 skinning weights per vertex per bone provided the rest pose vertices, faces, piecewise-constant weights (here we use rigid skinning weight `rigid_w`) and a set of smoothness control parameters. For this example that is 10 weights for each of the 24 bones of the elephant's skeleton for each of 6034 vertices of the mesh.\n", - "\n", - "The smoothness can be controlled through parameters \n", - "- `p` ($ > 0$)\n", - "- `l` or $\\lambda$ ($> 0$) : \n", - "-`k` or $\\kappa$ ($> 0 and < \\lambda$):\n", - "- 'a' or $\\alpha$ ($> 0 and < 1$):\n", - "\n", - "Here, `p` is the number of iterations. The values here were used from [Example 408](https://github.com/libigl/libigl/blob/main/tutorial/408_DirectDeltaMush/main.cpp) of the libigl C++ tutorials.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Direct Delta Mush Runtime Evaluation\n", - "\n", - "At runtime, $\\Omega$ is used to deform the mesh to its final locations. The mesh is deformed using linear blend skinning and piecewise-constant weights. Near bones, the deformation is perfectly rigid, while near joints where bones meet, the mesh tears apart with a sudden change to the next rigid transformation. A local frame $S_i$ is computed at this location and the cached deltas are added in this resolved frame to restore the shape’s original details:\n", - "\n", - "$u_i = \\tilde{u_i} + S_i . \\delta_i$\n", - "\n", - "The key insight of “Delta Mush” is that Laplacian smoothing acts similarly on the rest and posed models." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "8e8f2a83a8764eb68fc250a1123655d9", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(IntSlider(value=229, description='frame', max=457, min=1), Output()), _dom_classes=('wid…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "6a7db55685ac42b8b6e6fa68c3680904", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(0.1529960…" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def ddm_deform_mesh(pose):\n", - " ddm_v = igl.direct_delta_mush(v.copy(), pose, omega)\n", - " return ddm_v\n", - "\n", - "viewer_ddm = mp.Viewer({})\n", - "ddm = viewer_ddm.add_mesh(v, f, np.array([0.0, 0.5, 0.5]))\n", - "\n", - "@mp.interact(frame=(1, num_frames))\n", - "def update_frame(frame):\n", - " frame = frame - 1\n", - " pose = anim[:, frame].reshape(parents.shape[0] * 4, 3, order='F')\n", - " deformed_mesh = ddm_deform_mesh(pose)\n", - " \n", - " viewer_ddm.update_object(oid=ddm, vertices=deformed_mesh)\n", - " \n", - "viewer_ddm._renderer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `igl.direct_delta_mush` consumes the rest pose vertices `v`, the pose $\\theta$, and the matrix $\\Omega$ we precomputed above to deform the mesh. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Comparison between the Skinning Techniques" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b40a4a7a4b544f3686f1dc3d38d3e338", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(IntSlider(value=229, description='frame', max=457, min=1), Output()), _dom_classes=('wid…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7d41cad22ae54e5cb01bdb132e1267a6", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Renderer(camera=PerspectiveCamera(children=(DirectionalLight(color='white', intensity=0.6, position=(0.1529998…" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "viewer_comp = mp.Viewer({})\n", - "# Rigid Blue\n", - "offset_rigid = np.array([-90.0, 0.0, 0.0])\n", - "rigid = viewer_comp.add_mesh(v + offset_rigid, f, np.array([0.0, 0.5, 0.0]))\n", - "\n", - "# LBS Red\n", - "offset_lbs = np.array([0.0, 0.0, 0.0])\n", - "lbs = viewer_comp.add_mesh(v + offset_lbs, f, np.array([0.5, 0.0, 0.0]))\n", - "\n", - "# DDM Green\n", - "offset_ddm = np.array([90.0, 0.0, 0.0])\n", - "ddm = viewer_comp.add_mesh(v + offset_ddm, f, np.array([0.0, 0.5, 0.5]))\n", - "\n", - "\n", - "@mp.interact(frame=(1, num_frames))\n", - "def update_frame(frame):\n", - " frame = frame - 1\n", - " pose = anim[:, frame].reshape(parents.shape[0] * 4, 3, order='F')\n", - " rigid_deformed_mesh = rigid_deform_mesh(pose)\n", - " lbs_deformed_mesh = lbs_deform_mesh(pose)\n", - " ddm_deformed_mesh = ddm_deform_mesh(pose)\n", - " \n", - " viewer_comp.update_object(oid=rigid, vertices=rigid_deformed_mesh + offset_rigid)\n", - " viewer_comp.update_object(oid=lbs, vertices=lbs_deformed_mesh + offset_lbs)\n", - " viewer_comp.update_object(oid=ddm, vertices=ddm_deformed_mesh + offset_ddm)\n", - " \n", - "viewer_comp._renderer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The visualization shows a comparison between the 3 techniques\n", - "1. Rigid Skinning: Green.\n", - "2. Linear Blend Skinning: Red.\n", - "3. Direct Delta Mush Skinning: Teal.\n", - "\n", - "Frame 181 presents a good case where rigid skinning fails with a ton of artificats, where as linear blend skinning does slightly better. LBS, however, ends up with volume loss at the limbs and Direct Delta Mush skinning does a good job of cleaning this up." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# References\n", - "\n", - "- *UCSD Reading (https://cseweb.ucsd.edu/classes/sp16/cse169-a/readings/3-Skin.html)*\n", - "- *Binh Huy Le, J.P. Lewis. [Direct delta mush skinning and variants](https://binh.graphics/papers/2019s-DDM/Direct_Delta_Mush_and_Variants.pdf), 2019*\n", - "- *Joe Mancewicz, Matt L. Derksen, Hans Rijpkema, and Cyrus A. Wilson. [Delta Mush: smoothing deformations while preserving detail](https://dl.acm.org/doi/10.1145/2633374.2633376), 2014.*\n", - "- *Alec Jacobson, Ilya Baran, Ladislav Kavan, Jovan Popović, and Olga Sorkine. [Fast Automatic Skinning Transformations](https://igl.ethz.ch/projects/fast/fast-automatic-skinning-transformations-siggraph-2012-jacobson-et-al.pdf), 2012*" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - }, - "vscode": { - "interpreter": { - "hash": "ce7907e81cf2b9da16cd02e98dafe35a67eae35daae8f394751b5721196d6988" - } - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/tutorial/tutorials.ipynb b/tutorial/tutorials.ipynb deleted file mode 100644 index aa5b8bd8..00000000 --- a/tutorial/tutorials.ipynb +++ /dev/null @@ -1,2186 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Libigl Tutorials\n", - "[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/)\n", - "\n", - "[![buildwheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain)\n", - "\n", - "![](images/libigl-logo.jpg)\n", - "\n", - "Libigl is an open source C++ library for geometry processing research and development. The python bindings combine the rapid prototyping familiar to Matlab to Python programmers with the performance and versatility of C++. The tutorial is a self-contained, hands-on introduction to libigl in Python. Via interactive, step-by-step examples, we demonstrate how to accomplish common geometry processing tasks such as computation of differential quantities and operators, real-time deformation, parametrization, numerical optimization and remeshing. Each section of the lecture notes contains a simple Python example application." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Chapter 0\n", - "\n", - "We introduce libigl with a series of self-contained examples. The purpose of\n", - "each example is to showcase a feature of libigl while applying to a practical\n", - "problem in geometry processing. In this chapter, we will present the basic\n", - "concepts of libigl.\n", - "\n", - "### Libigl design principles\n", - "\n", - "Before getting into the examples, we summarize the two main design principles in\n", - "libigl:\n", - "\n", - "1. **No complex data types.** We mostly use `numpy` or `scipy` matrices and vectors. This greatly\n", - " favors code reusability and interoperability and forces the function authors to expose all the\n", - " parameters used by the algorithm.\n", - "\n", - "2. **Function encapsulation.** Every function is contained in a unique Python function.\n", - "\n", - "\n", - "### Downloading Libigl\n", - "Libigl can be downloaded from [PyPI](https://pypi.org/project/libigl/):\n", - "```\n", - "python -m pip install libigl \n", - "```\n", - "\n", - "\n", - "All of libigl functionality depends only on `numpy` and `scipy`. For the visualization in this tutorial we use [meshplot](https://github.com/skoch9/meshplot) which can be easily installed from Conda:\n", - "```\n", - "python -m pip install https://github.com/skoch9/meshplot/archive/0.4.0.tar.gz \n", - "```\n", - "\n", - "\n", - "To start using libigl (with the plots) you just need to import it together with the `numpy`, `scipy`, and `meshplot`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import igl\n", - "import scipy as sp\n", - "import numpy as np\n", - "from meshplot import plot, subplot, interact\n", - "\n", - "import os\n", - "root_folder = os.getcwd()\n", - "#root_folder = os.path.join(os.getcwd(), \"tutorial\")\n", - "data_folder = os.path.join(root_folder,\"/data\")\n", - "# pip install gitpython\n", - "from git import Repo\n", - "if not os.path.isdir(data_folder):\n", - " Repo.clone_from(\"https://github.com/libigl/libigl-tutorial-data.git\", data_folder)\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Mesh representation\n", - "\n", - "Libigl uses `numpy` to encode vectors and matrices and `scipy` for sparse matrices.\n", - "\n", - "A triangular mesh is encoded as a pair of matrices:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v: np.array\n", - "f: np.array" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`v` is a #N by 3 matrix which stores the coordinates of the vertices. Each\n", - "row stores the coordinate of a vertex, with its x, y and z coordinates in the first,\n", - "second and third column, respectively. The matrix `f` stores the triangle\n", - "connectivity: each line of `f` denotes a triangle whose 3 vertices are\n", - "represented as indices pointing to rows of `f`.\n", - "\n", - "![A simple mesh made of 2 triangles and 4 vertices.](images/VF.png )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "V = np.array([\n", - " [0., 0, 0],\n", - " [1, 0, 0],\n", - " [1, 1, 1],\n", - " [2, 1, 0]\n", - "])\n", - "\n", - "F = np.array([\n", - " [0, 1, 2],\n", - " [1, 3, 2]\n", - "])\n", - "\n", - "plot(V, F)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that the order of the vertex indices in `f` determines the orientation of\n", - "the triangles and it should thus be consistent for the entire surface.\n", - "This simple representation has many advantages:\n", - "\n", - "1. It is memory efficient and cache friendly\n", - "2. The use of indices instead of pointers greatly simplifies debugging\n", - "3. The data can be trivially copied and serialized\n", - "\n", - "Libigl provides input and output functions to read and write many common mesh formats.\n", - "The IO functions are igl.read_\\* and igl.write_\\*.\n", - "\n", - "Reading a mesh from a file requires a single libigl function call:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Load a mesh in OFF format\n", - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"bunny.off\"))\n", - "\n", - "## Print the vertices and faces matrices \n", - "print(\"Vertices: \", len(v))\n", - "print(\"Faces: \", len(f))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The function reads the mesh bumpy.off and returns the `v` and `f` matrices.\n", - "Similarly, a mesh can be written to an OBJ file using:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Save the mesh in OBJ format\n", - "ret = igl.write_triangle_mesh(os.path.join(root_folder, \"data\", \"bunny_out.obj\"), v, f)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Chapter 1: Discrete Geometric Quantities and Operators\n", - "This chapter illustrates a few discrete quantities that libigl can compute on a mesh and the libigl functions that construct popular discrete differential geometry operators. It also provides an introduction to basic drawing and coloring routines of our viewer." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Gaussian curvature\n", - "\n", - "Gaussian curvature on a continuous surface is defined as the product of the\n", - "principal curvatures:\n", - "\n", - " $k_G = k_1 k_2.$\n", - "\n", - "As an _intrinsic_ measure, it depends on the metric and\n", - "not the surface's embedding.\n", - "\n", - "Intuitively, Gaussian curvature tells how locally spherical or _elliptic_ the\n", - "surface is ( $k_G>0$ ), how locally saddle-shaped or _hyperbolic_ the surface\n", - "is ( $k_G<0$ ), or how locally cylindrical or _parabolic_ ( $k_G=0$ ) the\n", - "surface is.\n", - "\n", - "In the discrete setting, one definition for a \"discrete Gaussian curvature\"\n", - "on a triangle mesh is via a vertex's _angular deficit_:\n", - "\n", - " $k_G(v_i) = 2π - \\sum\\limits_{j\\in N(i)}θ_{ij},$\n", - "\n", - "where $N(i)$ are the triangles incident on vertex $i$ and $θ_{ij}$ is the angle\n", - "at vertex $i$ in triangle $j$ (Meyer, 2003).\n", - "\n", - "Just like the continuous analog, our discrete Gaussian curvature reveals\n", - "elliptic, hyperbolic and parabolic vertices on the domain.\n", - "\n", - "Let's compute Gaussian curvature and visualize it in pseudocolor. First, calculate the curvature with libigl and then plot it in pseudocolors." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"bumpy.off\"))\n", - "k = igl.gaussian_curvature(v, f)\n", - "plot(v, f, k)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, compute the massmatrix and divide the gaussian curvature values by area to get the integral average." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "minv = sp.sparse.diags(1 / m.diagonal())\n", - "kn = minv.dot(k)\n", - "plot(v, f, kn)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Curvature directions\n", - "The two principal curvatures $(k_1,k_2)$ at a point on a surface measure how\n", - "much the surface bends in different directions. The directions of maximum and\n", - "minimum (signed) bending are called principal directions and are always\n", - "orthogonal.\n", - "\n", - "Mean curvature is defined as the average of principal curvatures:\n", - "\n", - " $H = \\frac{1}{2}(k_1 + k_2).$\n", - "\n", - "One way to extract mean curvature is by examining the Laplace-Beltrami operator\n", - "applied to the surface positions. The result is a so-called mean-curvature\n", - "normal:\n", - "\n", - " $-\\Delta \\mathbf{x} = H \\mathbf{n}.$\n", - "\n", - "It is easy to compute this on a discrete triangle mesh in libigl using the\n", - "cotangent Laplace-Beltrami operator (Meyer, 2003). " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "l = igl.cotmatrix(v, f)\n", - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "\n", - "minv = sp.sparse.diags(1 / m.diagonal())\n", - "\n", - "hn = -minv.dot(l.dot(v))\n", - "h = np.linalg.norm(hn, axis=1)\n", - "plot(v, f, h)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Combined with the angle defect definition of discrete Gaussian curvature, one\n", - "can define principal curvatures and use least squares fitting to find\n", - "directions (Meyer, 2003).\n", - "\n", - "Alternatively, a robust method for determining principal curvatures is via\n", - "quadric fitting (Panozzo, 2010). In the neighborhood around every vertex, a\n", - "best-fit quadric is found and principal curvature values and directions are\n", - "analytically computed on this quadric." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v1, v2, k1, k2 = igl.principal_curvature(v, f)\n", - "h2 = 0.5 * (k1 + k2)\n", - "p = plot(v, f, h2, shading={\"wireframe\": False}, return_plot=True)\n", - "\n", - "avg = igl.avg_edge_length(v, f) / 2.0\n", - "p.add_lines(v + v1 * avg, v - v1 * avg, shading={\"line_color\": \"red\"})\n", - "p.add_lines(v + v2 * avg, v - v2 * avg, shading={\"line_color\": \"green\"});" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Gradient\n", - "Scalar functions on a surface can be discretized as a piecewise linear function\n", - "with values defined at each mesh vertex:\n", - "\n", - " $f(\\mathbf{x}) \\approx \\sum\\limits_{i=1}^n \\phi_i(\\mathbf{x})\\, f_i,$\n", - "\n", - "where $\\phi_i$ is a piecewise linear hat function defined by the mesh so that\n", - "for each triangle $\\phi_i$ is _the_ linear function which is one only at\n", - "vertex $i$ and zero at the other corners.\n", - "\n", - "![Hat function $\\phi_i$ is one at vertex $i$, zero at all other vertices, and linear on incident triangles.](images/hat-function.jpg)\n", - "\n", - "Thus gradients of such piecewise linear functions are simply sums of gradients\n", - "of the hat functions:\n", - "\n", - " $\\nabla f(\\mathbf{x}) \\approx\n", - " \\nabla \\sum\\limits_{i=1}^n \\phi_i(\\mathbf{x})\\, f_i =\n", - " \\sum\\limits_{i=1}^n \\nabla \\phi_i(\\mathbf{x})\\, f_i.$\n", - "\n", - "This reveals that the gradient is a linear function of the vector of $f_i$\n", - "values. Because the $\\phi_i$ are linear in each triangle, their gradients are\n", - "_constant_ in each triangle. Thus our discrete gradient operator can be written\n", - "as a matrix multiplication taking vertex values to triangle values:\n", - "\n", - " $\\nabla f \\approx \\mathbf{G}\\,\\mathbf{f},$\n", - "\n", - "where $\\mathbf{f}$ is $n\\times 1$ and $\\mathbf{G}$ is an $md\\times n$ sparse\n", - "matrix. This matrix $\\mathbf{G}$ can be derived geometrically (Jacobson, 2013).\n", - "\n", - "Libigl's `grad` function computes $\\mathbf{G}$ for\n", - "triangle and tetrahedral meshes. \n", - "Let's see how this works. First load a mesh and a corresponding surface function.\n", - "Next, compute the gradient operator g (#F*3 x #V) on the triangle mesh, apply it to the surface function and extract the magnitude." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"cheburashka.off\"))\n", - "u = igl.read_dmat(os.path.join(root_folder, \"data\", \"cheburashka-scalar.dmat\"))\n", - "\n", - "g = igl.grad(v, f)\n", - "gu = g.dot(u).reshape(f.shape, order=\"F\")\n", - "\n", - "gu_mag = np.linalg.norm(gu, axis=1)\n", - "p = plot(v, f, u, shading={\"wireframe\":False}, return_plot=True)\n", - "\n", - "max_size = igl.avg_edge_length(v, f) / np.mean(gu_mag)\n", - "bc = igl.barycenter(v, f)\n", - "bcn = bc + max_size * gu\n", - "p.add_lines(bc, bcn, shading={\"line_color\": \"black\"});" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Laplacian\n", - "\n", - "The discrete Laplacian is an essential geometry processing tool. Many\n", - "interpretations and flavors of the Laplace and Laplace-Beltrami operator exist.\n", - "\n", - "In open Euclidean space, the _Laplace_ operator is the usual divergence of\n", - "gradient (or equivalently the Laplacian of a function is the trace of its\n", - "Hessian):\n", - "\n", - " $\\Delta f =\n", - " \\frac{\\partial^2 f}{\\partial x^2} +\n", - " \\frac{\\partial^2 f}{\\partial y^2} +\n", - " \\frac{\\partial^2 f}{\\partial z^2}.$\n", - "\n", - "The _Laplace-Beltrami_ operator generalizes this to surfaces.\n", - "\n", - "When considering piecewise-linear functions on a triangle mesh, a discrete\n", - "Laplacian may be derived in a variety of ways. The most popular in geometry\n", - "processing is the so-called \"cotangent Laplacian\" $\\mathbf{L}$, arising\n", - "simultaneously from FEM, DEC and applying divergence theorem to vertex\n", - "one-rings. As a linear operator taking vertex values to vertex values, the\n", - "Laplacian $\\mathbf{L}$ is a $n\\times n$ matrix with elements:\n", - "\n", - "$L_{ij} = \\begin{cases}j \\in N(i) &\\cot \\alpha_{ij} + \\cot \\beta_{ij},\\\\\n", - "j \\notin N(i) & 0,\\\\\n", - "i = j & -\\sum\\limits_{k\\neq i} L_{ik},\n", - "\\end{cases}$\n", - "\n", - "where $N(i)$ are the vertices adjacent to (neighboring) vertex $i$, and\n", - "$\\alpha_{ij},\\beta_{ij}$ are the angles opposite to edge ${ij}$.\n", - "\n", - "Libigl implements discrete \"cotangent Laplacians\" for triangles meshes and\n", - "tetrahedral meshes, building both with fast geometric rules rather than \"by the\n", - "book\" FEM construction which involves many (small) matrix inversions (Sharf, 2007).\n", - "\n", - "First, load a triangle mesh and then calculate the Laplace-Beltrami operator, visualize the normals as pseudocolors." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.sparse.linalg import spsolve\n", - "\n", - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"cow.off\"))\n", - "l = igl.cotmatrix(v, f)\n", - "\n", - "n = igl.per_vertex_normals(v, f)*0.5+0.5\n", - "c = np.linalg.norm(n, axis=1)\n", - "p = plot(v, f, c, shading={\"wireframe\": False}, return_plot=True)\n", - "\n", - "vs = [v]\n", - "cs = [c]\n", - "for i in range(10):\n", - " m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_BARYCENTRIC)\n", - " s = (m - 0.001 * l)\n", - " b = m.dot(v)\n", - " v = spsolve(s, m.dot(v))\n", - " n = igl.per_vertex_normals(v, f)*0.5+0.5\n", - " c = np.linalg.norm(n, axis=1)\n", - " vs.append(v)\n", - " cs.append(c)\n", - "\n", - "@interact(level=(0, 9))\n", - "def mcf(level=0):\n", - " p.update_object(vertices=vs[level], colors=cs[level])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The operator applied to mesh vertex positions amounts to smoothing by _flowing_\n", - "the surface along the mean curvature normal direction. Note that this is equivalent to minimizing surface area. The following example computes conformalized mean curvature flow using the cotangent Laplacian (Kazhdan, 2012) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Mass matrix\n", - "The mass matrix $\\mathbf{M}$ is another $n \\times n$ matrix which takes vertex\n", - "values to vertex values. From an FEM point of view, it is a discretization of\n", - "the inner-product: it accounts for the area around each vertex. Consequently,\n", - "$\\mathbf{M}$ is often a diagonal matrix, such that $M_{ii}$ is the barycentric\n", - "or voronoi area around vertex $i$ in the mesh (Meyer, 2003). The inverse of this matrix is also very useful as it transforms integrated quantities into point-wise quantities, e.g.:\n", - "\n", - " $\\Delta f \\approx \\mathbf{M}^{-1} \\mathbf{L} \\mathbf{f}.$\n", - "\n", - "In general, when encountering squared quantities integrated over the surface,\n", - "the mass matrix will be used as the discretization of the inner product when\n", - "sampling function values at vertices:\n", - "\n", - " $\\int_S x\\, y\\ dA \\approx \\mathbf{x}^T\\mathbf{M}\\,\\mathbf{y}.$\n", - "\n", - "An alternative mass matrix $\\mathbf{T}$ is a $md \\times md$ matrix which takes\n", - "triangle vector values to triangle vector values. This matrix represents an\n", - "inner-product accounting for the area associated with each triangle (i.e. the\n", - "triangles true area).\n", - "\n", - "### Alternative construction of Laplacian\n", - "\n", - "An alternative construction of the discrete cotangent Laplacian is by\n", - "\"squaring\" the discrete gradient operator. This may be derived by applying\n", - "Green's identity (ignoring boundary conditions for the moment):\n", - "\n", - " $\\int_S \\|\\nabla f\\|^2 dA = \\int_S f \\Delta f dA$\n", - "\n", - "Or in matrix form which is immediately translatable to code:\n", - "\n", - " $\\mathbf{f}^T \\mathbf{G}^T \\mathbf{T} \\mathbf{G} \\mathbf{f} =\n", - " \\mathbf{f}^T \\mathbf{M} \\mathbf{M}^{-1} \\mathbf{L} \\mathbf{f} =\n", - " \\mathbf{f}^T \\mathbf{L} \\mathbf{f}.$\n", - "\n", - "So we have that $\\mathbf{L} = \\mathbf{G}^T \\mathbf{T} \\mathbf{G}$. This also\n", - "hints that we may consider $\\mathbf{G}^T$ as a discrete _divergence_ operator,\n", - "since the Laplacian is the divergence of the gradient. Naturally, $\\mathbf{G}^T$ is\n", - "a $n \\times md$ sparse matrix which takes vector values stored at triangle faces\n", - "to scalar divergence values at vertices." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"cow.off\"))\n", - "l = igl.cotmatrix(v, f)\n", - "g = igl.grad(v, f)\n", - "\n", - "d_area = igl.doublearea(v, f)\n", - "t = sp.sparse.diags(np.hstack([d_area, d_area, d_area]) * 0.5)\n", - "\n", - "k = -g.T.dot(t).dot(g)\n", - "print(\"|k-l|: %s\"%sp.sparse.linalg.norm(k-l))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exact Discrete Geodesic Distances\n", - "\n", - "The discrete geodesic distance between two points is the length of the shortest\n", - "path between then restricted to the surface. For triangle meshes, such a path is\n", - "made of a set of segments which can be either edges of the mesh or crossing a\n", - "triangle.\n", - "\n", - "Libigl includes a wrapper for the exact geodesic algorithm (Mitchell, 1987)\n", - "developed by Danil Kirsanov (https://code.google.com/archive/p/geodesic/),\n", - "exposing it through an Eigen-based API. The function \n", - "```python\n", - "d = igl.exact_geodesic(v, f, vs, fs, vt, ft)\n", - "```\n", - "computes the closest geodesic distances of each vertex in vt or face in ft, from\n", - "the source vertices vs or faces fs of the input mesh v, f. The output is written\n", - "in the vector d, which lists first the distances for the vertices in vt, and\n", - "then for the faces in ft. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"armadillo.obj\"))\n", - "\n", - "## Select a vertex from which the distances should be calculated\n", - "vs = np.array([0])\n", - "##All vertices are the targets\n", - "vt = np.arange(v.shape[0])\n", - "\n", - "d = igl.exact_geodesic(v, f, vs, vt)#, fs, ft)\n", - "\n", - "strip_size = 0.1\n", - "##The function should be 1 on each integer coordinate\n", - "c = np.abs(np.sin((d / strip_size * np.pi)))\n", - "plot(v, f, c, shading={\"wireframe\": False})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Chapter 2: Matrices and Linear Algebra" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Laplace equation\n", - "A common linear system in geometry processing is the Laplace equation:\n", - "\n", - " $∆z = 0$\n", - "\n", - "subject to some boundary conditions, for example Dirichlet boundary conditions\n", - "(fixed value):\n", - "\n", - " $\\left.z\\right|_{\\partial{S}} = z_{bc}$\n", - "\n", - "In the discrete setting, the linear system can be written as:\n", - "\n", - " $\\mathbf{L} \\mathbf{z} = \\mathbf{0}$\n", - "\n", - "where $\\mathbf{L}$ is the $n \\times n$ discrete Laplacian and $\\mathbf{z}$ is a\n", - "vector of per-vertex values. Most of $\\mathbf{z}$ correspond to interior\n", - "vertices and are unknown, but some of $\\mathbf{z}$ represent values at boundary\n", - "vertices. Their values are known so we may move their corresponding terms to\n", - "the right-hand side.\n", - "\n", - "Conceptually, this is very easy if we have sorted $\\mathbf{z}$ so that interior\n", - "vertices come first and then boundary vertices:\n", - "\n", - "$$\n", - " \\left(\\begin{array}{cc}\n", - " \\mathbf{L}_{in,in} & \\mathbf{L}_{in,b}\\\\\n", - " \\mathbf{L}_{b,in} & \\mathbf{L}_{b,b}\\end{array}\\right)\n", - " \\left(\\begin{array}{c}\n", - " \\mathbf{z}_{in}\\\\\n", - " \\mathbf{z}_{b}\\end{array}\\right) =\n", - " \\left(\\begin{array}{c}\n", - " \\mathbf{0}_{in}\\\\\n", - " \\mathbf{z}_{bc}\\end{array}\\right)\n", - "$$\n", - "\n", - "The bottom block of equations is no longer meaningful so we'll only consider\n", - "the top block:\n", - "\n", - "$$\n", - " \\left(\\begin{array}{cc}\n", - " \\mathbf{L}_{in,in} & \\mathbf{L}_{in,b}\\end{array}\\right)\n", - " \\left(\\begin{array}{c}\n", - " \\mathbf{z}_{in}\\\\\n", - " \\mathbf{z}_{b}\\end{array}\\right) =\n", - " \\mathbf{0}_{in}\n", - "$$\n", - "\n", - "We can move the known values to the right-hand side:\n", - "\n", - "$$\n", - " \\mathbf{L}_{in,in}\n", - " \\mathbf{z}_{in} = -\n", - " \\mathbf{L}_{in,b}\n", - " \\mathbf{z}_{b}\n", - "$$\n", - "\n", - "Finally we can solve this equation for the unknown values at interior vertices\n", - "$\\mathbf{z}_{in}$.\n", - "\n", - "However, our vertices will often not be sorted in this way. One option would be to sort `V`,\n", - "then proceed as above and then _unsort_ the solution `Z` to match `V`. However,\n", - "this solution is not very general.\n", - "\n", - "With array slicing no explicit sort is needed. Instead we can _slice-out_\n", - "submatrix blocks ($\\mathbf{L}_{in,in}$, $\\mathbf{L}_{in,b}$, etc.) and follow\n", - "the linear algebra above directly. Then we can slice the solution _into_ the\n", - "rows of `Z` corresponding to the interior vertices." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.sparse.linalg import spsolve\n", - "\n", - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"camelhead.off\"))\n", - "\n", - "## Find boundary vertices\n", - "e = igl.boundary_facets(f)\n", - "v_b = np.unique(e)\n", - "\n", - "## List of all vertex indices\n", - "v_all = np.arange(v.shape[0])\n", - "\n", - "## List of interior indices\n", - "v_in = np.setdiff1d(v_all, v_b)\n", - "\n", - "## Construct and slice up Laplacian\n", - "l = igl.cotmatrix(v, f)\n", - "l_ii = l[v_in, :]\n", - "l_ii = l_ii[:, v_in]\n", - "\n", - "l_ib = l[v_in, :]\n", - "l_ib = l_ib[:, v_b]\n", - "\n", - "## Dirichlet boundary conditions from z-coordinate\n", - "z = v[:, 2]\n", - "bc = z[v_b]\n", - "\n", - "## Solve PDE\n", - "z_in = spsolve(-l_ii, l_ib.dot(bc))\n", - "\n", - "plot(v, f, z)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Quadratic energy minimization\n", - "\n", - "The same Laplace equation may be equivalently derived by minimizing Dirichlet\n", - "energy subject to the same boundary conditions:\n", - "\n", - " $\\mathop{\\text{minimize }}_z \\frac{1}{2}\\int\\limits_S \\|\\nabla z\\|^2 dA$\n", - "\n", - "On our discrete mesh, recall that this becomes\n", - "\n", - " $\\mathop{\\text{minimize }}_\\mathbf{z} \\frac{1}{2}\\mathbf{z}^T \\mathbf{G}^T \\mathbf{D}\n", - " \\mathbf{G} \\mathbf{z} \\rightarrow \\mathop{\\text{minimize }}_\\mathbf{z} \\mathbf{z}^T \\mathbf{L} \\mathbf{z}$\n", - "\n", - "The general problem of minimizing some energy over a mesh subject to fixed\n", - "value boundary conditions is so wide spread that libigl has a dedicated api for\n", - "solving such systems.\n", - "\n", - "Let us consider a general quadratic minimization problem subject to different\n", - "common constraints:\n", - "\n", - "$$\n", - " \\mathop{\\text{minimize }}_\\mathbf{z} \\frac{1}{2}\\mathbf{z}^T \\mathbf{Q} \\mathbf{z} +\n", - " \\mathbf{z}^T \\mathbf{B} + \\text{constant},\n", - "$$\n", - "\n", - " subject to\n", - "\n", - "$$\n", - " \\mathbf{z}_b = \\mathbf{z}_{bc} \\text{ and } \\mathbf{A}_{eq} \\mathbf{z} =\n", - " \\mathbf{B}_{eq},\n", - "$$\n", - "\n", - "where\n", - "\n", - " - $\\mathbf{Q}$ is a (usually sparse) $n \\times n$ positive semi-definite\n", - " matrix of quadratic coefficients (Hessian),\n", - " - $\\mathbf{B}$ is a $n \\times 1$ vector of linear coefficients,\n", - " - $\\mathbf{z}_b$ is a $|b| \\times 1$ portion of\n", - "$\\mathbf{z}$ corresponding to boundary or _fixed_ vertices,\n", - " - $\\mathbf{z}_{bc}$ is a $|b| \\times 1$ vector of known values corresponding to\n", - " $\\mathbf{z}_b$,\n", - " - $\\mathbf{A}_{eq}$ is a (usually sparse) $m \\times n$ matrix of linear\n", - " equality constraint coefficients (one row per constraint), and\n", - " - $\\mathbf{B}_{eq}$ is a $m \\times 1$ vector of linear equality constraint\n", - " right-hand side values.\n", - "\n", - "This specification is overly general as we could write $\\mathbf{z}_b =\n", - "\\mathbf{z}_{bc}$ as rows of $\\mathbf{A}_{eq} \\mathbf{z} =\n", - "\\mathbf{B}_{eq}$, but these fixed value constraints appear so often that they\n", - "merit a dedicated function: `min_quad_with_fixed`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Linear equality constraints\n", - "We saw above that `min_quad_with_fixed` in libigl provides a compact way to\n", - "solve general quadratic programs. Let's consider another example, this time\n", - "with active linear equality constraints. Specifically let's solve the\n", - "`bi-Laplace equation` or equivalently minimize the Laplace energy:\n", - "\n", - "$$\n", - " \\Delta^2 z = 0 \\leftrightarrow \\mathop{\\text{minimize }}\\limits_z \\frac{1}{2}\n", - " \\int\\limits_S (\\Delta z)^2 dA\n", - "$$\n", - "\n", - "subject to fixed value constraints and a linear equality constraint:\n", - "\n", - " $z_{a} = 1, z_{b} = -1$ and $z_{c} = z_{d}$.\n", - "\n", - "Notice that we can rewrite the last constraint in the familiar form from above:\n", - "\n", - " $z_{c} - z_{d} = 0.$\n", - "\n", - "Now we can assembly `Aeq` as a $1 \\times n$ sparse matrix with a coefficient\n", - "$1$ in the column corresponding to vertex $c$ and a $-1$ at $d$. The right-hand\n", - "side `Beq` is simply zero.\n", - "\n", - "Internally, `min_quad_with_fixed` solves using the Lagrange Multiplier\n", - "method. This method adds additional variables for each linear constraint (in\n", - "general a $m \\times 1$ vector of variables $\\lambda$) and then solves the\n", - "saddle problem:\n", - "\n", - "$$\n", - " \\mathop{\\text{find saddle }}_{\\mathbf{z},\\lambda}\\, \\frac{1}{2}\\mathbf{z}^T \\mathbf{Q} \\mathbf{z} +\n", - " \\mathbf{z}^T \\mathbf{B} + \\text{constant} + \\lambda^T\\left(\\mathbf{A}_{eq}\n", - " \\mathbf{z} - \\mathbf{B}_{eq}\\right)\n", - "$$\n", - "\n", - "This can be rewritten in a more familiar form by stacking $\\mathbf{z}$ and\n", - "$\\lambda$ into one $(m+n) \\times 1$ vector of unknowns:\n", - "\n", - "$$\n", - " \\mathop{\\text{find saddle }}_{\\mathbf{z},\\lambda}\\,\n", - " \\frac{1}{2}\n", - " \\left(\n", - " \\mathbf{z}^T\n", - " \\lambda^T\n", - " \\right)\n", - " \\left(\n", - " \\begin{array}{cc}\n", - " \\mathbf{Q} & \\mathbf{A}_{eq}^T\\\\\n", - " \\mathbf{A}_{eq} & 0\n", - " \\end{array}\n", - " \\right)\n", - " \\left(\n", - " \\begin{array}{c}\n", - " \\mathbf{z}\\\\\n", - " \\lambda\n", - " \\end{array}\n", - " \\right) +\n", - " \\left(\n", - " \\mathbf{z}^T\n", - " \\lambda^T\n", - " \\right)\n", - " \\left(\n", - " \\begin{array}{c}\n", - " \\mathbf{B}\\\\\n", - " -\\mathbf{B}_{eq}\n", - " \\end{array}\n", - " \\right)\n", - " + \\text{constant}\n", - "$$\n", - "\n", - "Differentiating with respect to $\\left( \\mathbf{z}^T \\lambda^T \\right)$ reveals\n", - "a linear system and we can solve for $\\mathbf{z}$ and $\\lambda$. The only\n", - "difference from the straight quadratic _minimization_ system, is that this\n", - "saddle problem system will not be positive definite. Thus, we must use a\n", - "different factorization technique (LDLT rather than LLT): libigl's\n", - "`min_quad_with_fixed` automatically chooses the correct solver in\n", - "the presence of linear equality constraints.\n", - "\n", - "The following example first solves with just fixed value constraints (left: 1 and -1 on the left hand and foot respectively), then solves with an additional linear equality constraint (right: points on right hand and foot constrained to be equal).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"cheburashka.off\"))\n", - "\n", - "## Two fixed points: Left hand, left foot should have values 1 and -1\n", - "b = np.array([4331, 5957])\n", - "bc = np.array([1., -1.])\n", - "B = np.zeros((v.shape[0], 1))\n", - "\n", - "## Construct Laplacian and mass matrix\n", - "L = igl.cotmatrix(v, f)\n", - "M = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "Minv = sp.sparse.diags(1 / M.diagonal())\n", - "\n", - "## Bi-Laplacian\n", - "Q = L @ (Minv @ L)\n", - "\n", - "## Solve with only equality constraints\n", - "Aeq = sp.sparse.csc_matrix((0, 0))\n", - "Beq = np.array([])\n", - "_, z1 = igl.min_quad_with_fixed(Q, B, b, bc, Aeq, Beq, True)\n", - "\n", - "## Solve with equality and linear constraints\n", - "Aeq = sp.sparse.csc_matrix((1, v.shape[0]))\n", - "Aeq[0,6074] = 1\n", - "Aeq[0, 6523] = -1\n", - "Beq = np.array([0.])\n", - "_, z2 = igl.min_quad_with_fixed(Q, B, b, bc, Aeq, Beq, True)\n", - "\n", - "## Normalize colors to same range\n", - "min_z = min(np.min(z1), np.min(z2))\n", - "max_z = max(np.max(z1), np.max(z2))\n", - "z = [(z1 - min_z) / (max_z - min_z), (z2 - min_z) / (max_z - min_z)]\n", - "\n", - "## Plot the functions\n", - "p = plot(v, f, z1, shading={\"wireframe\":False}, return_plot=True)\n", - "\n", - "@interact(function=[('z0', 0), ('z1', 1)])\n", - "def sf(function):\n", - " p.update_object(colors=z[function])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Quadratic programming\n", - "\n", - "We can generalize the quadratic optimization in the previous section even more\n", - "by allowing inequality constraints. Specifically box constraints (lower and\n", - "upper bounds):\n", - "\n", - " $\\mathbf{l} \\le \\mathbf{z} \\le \\mathbf{u},$\n", - "\n", - "where $\\mathbf{l},\\mathbf{u}$ are $n \\times 1$ vectors of lower and upper\n", - "bounds\n", - "and general linear inequality constraints:\n", - "\n", - " $\\mathbf{A}_{ieq} \\mathbf{z} \\le \\mathbf{B}_{ieq},$\n", - "\n", - "where $\\mathbf{A}_{ieq}$ is a $k \\times n$ matrix of linear coefficients and\n", - "$\\mathbf{B}_{ieq}$ is a $k \\times 1$ matrix of constraint right-hand sides.\n", - "\n", - "Again, we are overly general as the box constraints could be written as\n", - "rows of the linear inequality constraints, but bounds appear frequently enough\n", - "to merit a dedicated api.\n", - "\n", - "Libigl implements its own active set routine for solving _quadratric programs_\n", - "(QPs). This algorithm works by iteratively \"activating\" violated inequality\n", - "constraints by enforcing them as equalities and \"deactivating\" constraints\n", - "which are no longer needed.\n", - "\n", - "After deciding which constraints are active at each iteration, the problem\n", - "reduces to a quadratic minimization subject to linear _equality_ constraints,\n", - "and the method from the previous section is invoked. This is repeated until convergence.\n", - "\n", - "Currently the implementation is efficient for box constraints and sparse\n", - "non-overlapping linear inequality constraints.\n", - "\n", - "Unlike alternative interior-point methods, the active set method benefits from\n", - "a warm-start (initial guess for the solution vector $\\mathbf{z}$).\n", - "\n", - "The following example uses an active set solver to optimize discrete biharmonic kernels (Rustamov, 2011) at multiple scales:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#TODO: Check why results differ, add interactivity\n", - "\n", - "v, f, _ = igl.read_off(os.path.join(root_folder, \"data\", \"cheburashka.off\"))\n", - "\n", - "# One fixed point on belly\n", - "b = np.array([[2556]])\n", - "bc = np.array([[1.0]])\n", - "\n", - "# Construct Laplacian and mass matrix\n", - "l = igl.cotmatrix(v, f)\n", - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "minv = sp.sparse.diags(1 / m.diagonal())\n", - "\n", - "# Bi-Laplacian\n", - "q = l @ (minv @ l)\n", - "\n", - "# Zero linear term\n", - "bz = np.zeros((v.shape[0], 1))\n", - "\n", - "# Lower and upper bound\n", - "lx = np.zeros((v.shape[0], 1))\n", - "ux = np.ones((v.shape[0], 1))\n", - "\n", - "# Equality constraint constrains solution to sum to 1\n", - "beq = np.array([[0.08]])\n", - "aeq = sp.sparse.csc_matrix(m.diagonal())\n", - "\n", - "# Empty inequality constraints\n", - "aieq = sp.sparse.csc_matrix((0, 0))\n", - "bieq = np.array([])\n", - "\n", - "z = igl.active_set(q, bz, b, bc, aeq, beq, aieq, bieq, lx, ux, max_iter=8)\n", - "plot(v, f, z[1])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Eigen Decomposition\n", - "\n", - "Libigl has rudimentary support for extracting eigen pairs of a generalized\n", - "eigen value problem:\n", - "\n", - " $Ax = \\lambda B x$\n", - "\n", - "where $A$ is a sparse symmetric matrix and $B$ is a sparse positive definite\n", - "matrix. Most commonly in geometry processing, we let $A=L$ the cotangent\n", - "Laplacian and $B=M$ the per-vertex mass matrix (Vallet, 2008).\n", - "Typically applications will make use of the _low frequency_ eigen modes.\n", - "Analogous to the Fourier decomposition, a function $f$ on a surface can be\n", - "represented via its spectral decomposition of the eigen modes of the\n", - "Laplace-Beltrami:\n", - "\n", - " $f = \\sum\\limits_{i=1}^\\infty a_i \\phi_i$\n", - "\n", - "where each $\\phi_i$ is an eigen function satisfying: $\\Delta \\phi_i = \\lambda_i\n", - "\\phi_i$ and $a_i$ are scalar coefficients. For a discrete triangle mesh, a\n", - "completely analogous decomposition exists, albeit with finite sum:\n", - "\n", - " $\\mathbf{f} = \\sum\\limits_{i=1}^n a_i \\phi_i$\n", - "\n", - "where now a column vector of values at vertices $\\mathbf{f} \\in \\mathcal{R}^n$\n", - "specifies a piecewise linear function and $\\phi_i \\in \\mathcal{R}^n$ is an\n", - "eigen vector satisfying:\n", - "\n", - "$\\mathbf{L} \\phi_i = \\lambda_i \\mathbf{M} \\phi_i$.\n", - "\n", - "Note that Vallet & Levy (Vallet, 2008) propose solving a symmetrized\n", - "_standard_ eigen problem $\\mathbf{M}^{-1/2}\\mathbf{L}\\mathbf{M}^{-1/2} \\phi_i\n", - "= \\lambda_i \\phi_i$. Libigl implements a generalized eigen problem solver so\n", - "this unnecessary symmetrization can be avoided.\n", - "\n", - "Often the sum above is _truncated_ to the first $k$ eigen vectors. If the low\n", - "frequency modes are chosen, i.e. those corresponding to small $\\lambda_i$\n", - "values, then this truncation effectively _regularizes_ $\\mathbf{f}$ to smooth,\n", - "slowly changing functions over the mesh (Hildebrandt, 2011). Modal\n", - "analysis and model subspaces have been used frequently in real-time deformation\n", - "(Barbic, 2005)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the following example, the first k eigen vectors of the discrete Laplace-Beltrami operator are computed and displayed in\n", - "pseudocolors atop the beetle. \n", - "Low frequency eigen vectors of the discrete Laplace-Beltrami operator vary smoothly and slowly over the model.\n", - "At first, calculate the Laplace-Betrami operator and solve the generalized Eigen problem with scipy/arpack. \n", - "Then, rescale the Eigen vectors and visualize them." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"beetle.off\"))\n", - "l = -igl.cotmatrix(v, f)\n", - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_VORONOI)\n", - "\n", - "k = 10\n", - "d, u = sp.sparse.linalg.eigsh(l, k, m, sigma=0, which=\"LM\")\n", - "\n", - "u = (u - np.min(u)) / (np.max(u) - np.min(u))\n", - "bbd = 0.5 * np.linalg.norm(np.max(v, axis=0) - np.min(v, axis=0))\n", - "\n", - "p = plot(v, f, bbd * u[:, 0], shading={\"wireframe\":False, \"flat\": False}, return_plot=True)\n", - "\n", - "@interact(ev=[(\"EV %i\"%i, i) for i in range(k)])\n", - "def sf(ev):\n", - " p.update_object(colors=u[:, ev])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Chapter 3: Shape deformation\n", - "Modern mesh-based shape deformation methods satisfy user deformation\n", - "constraints at handles (selected vertices or regions on the mesh) and propagate\n", - "these handle deformations to the rest of the shape _smoothly_ and _without removing\n", - "or distorting details_. Libigl provides implementations of a variety of\n", - "state-of-the-art deformation techniques, ranging from quadratic mesh-based\n", - "energy minimizers, to skinning methods, to non-linear elasticity-inspired\n", - "techniques." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Biharmonic deformation\n", - "The period of research between 2000 and 2010 produced a collection of\n", - "techniques that cast the problem of handle-based shape deformation as a\n", - "quadratic energy minimization problem or equivalently the solution to a linear\n", - "partial differential equation.\n", - "\n", - "There are many flavors of these techniques, but a prototypical subset are those\n", - "that consider solutions to the bi-Laplace equation, that is a biharmonic\n", - "function (Botsch, 2004). This fourth-order PDE provides sufficient\n", - "flexibility in boundary conditions to ensure $C^1$ continuity at handle\n", - "constraints in the limit under refinement (Jacobson, 2010).\n", - "\n", - "#### Biharmonic surfaces\n", - "Let us first begin our discussion of biharmonic _deformation_, by considering\n", - "biharmonic _surfaces_. We will casually define biharmonic surfaces as surface\n", - "whose _position functions_ are biharmonic with respect to some initial\n", - "parameterization:\n", - "\n", - " $\\Delta^2 \\mathbf{x}' = 0$\n", - "\n", - "and subject to some handle constraints, conceptualized as \"boundary\n", - "conditions\":\n", - "\n", - " $\\mathbf{x}'_{b} = \\mathbf{x}_{bc}.$\n", - "\n", - "where $\\mathbf{x}'$ is the unknown 3D position of a point on the surface. So we\n", - "are asking that the bi-Laplacian of each of spatial coordinate function to be\n", - "zero.\n", - "\n", - "In libigl, one can solve a biharmonic problem with `harmonic`\n", - "and setting $k=2$ (_bi_-harmonic).\n", - "\n", - "This produces a smooth surface that interpolates the handle constraints, but all\n", - "original details on the surface will be _smoothed away_. Most obviously, if the\n", - "original surface is not already biharmonic, then giving all handles the\n", - "identity deformation (keeping them at their rest positions) will **not**\n", - "reproduce the original surface. Rather, the result will be the biharmonic\n", - "surface that does interpolate those handle positions.\n", - "\n", - "Thus, we may conclude that this is not an intuitive technique for shape\n", - "deformation.\n", - "\n", - "#### Biharmonic deformation fields\n", - "Now we know that one useful property for a deformation technique is \"rest pose\n", - "reproduction\": applying no deformation to the handles should apply no\n", - "deformation to the shape.\n", - "\n", - "To guarantee this by construction we can work with _deformation fields_ (ie.\n", - "displacements)\n", - "$\\mathbf{d}$ rather\n", - "than directly with positions $\\mathbf{x}$. Then the deformed positions can be\n", - "recovered as\n", - "\n", - " $\\mathbf{x}' = \\mathbf{x}+\\mathbf{d}.$\n", - "\n", - "A smooth deformation field $\\mathbf{d}$ which interpolates the deformation\n", - "fields of the handle constraints will impose a smooth deformed shape\n", - "$\\mathbf{x}'$. Naturally, we consider _biharmonic deformation fields_:\n", - "\n", - " $\\Delta^2 \\mathbf{d} = 0$\n", - "\n", - "subject to the same handle constraints, but rewritten in terms of their implied\n", - "deformation field at the boundary (handles).\n", - "\n", - " $\\mathbf{d}_b = \\mathbf{x}_{bc} - \\mathbf{x}_b.$\n", - "\n", - "Again we can use `harmonic` with $k=2$, but this time solve for the\n", - "deformation field and then recover the deformed positions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"decimated-max.obj\"))\n", - "v[:,[0, 2]] = v[:,[2, 0]] # Swap X and Z axes\n", - "u = v.copy()\n", - "\n", - "s = igl.read_dmat(os.path.join(root_folder, \"data\", \"decimated-max-selection.dmat\"))\n", - "b = np.array([[t[0] for t in [(i, s[i]) for i in range(0, v.shape[0])] if t[1] >= 0]]).T\n", - "\n", - "## Boundary conditions directly on deformed positions\n", - "u_bc = np.zeros((b.shape[0], v.shape[1]))\n", - "v_bc = np.zeros((b.shape[0], v.shape[1]))\n", - "\n", - "for bi in range(b.shape[0]):\n", - " v_bc[bi] = v[b[bi]]\n", - "\n", - " if s[b[bi]] == 0: # Don't move handle 0\n", - " u_bc[bi] = v[b[bi]]\n", - " elif s[b[bi]] == 1: # Move handle 1 down\n", - " u_bc[bi] = v[b[bi]] + np.array([[0, -50, 0]])\n", - " else: # Move other handles forward\n", - " u_bc[bi] = v[b[bi]] + np.array([[-25, 0, 0]])\n", - "\n", - "p = plot(v, f, s, shading={\"wireframe\": False, \"colormap\": \"tab10\"}, return_plot=True)\n", - "\n", - "@interact(deformation_field=True, step=(0.0, 2.0))\n", - "def update(deformation_field, step=0.0):\n", - " # Determine boundary conditions\n", - " u_bc_anim = v_bc + step * (u_bc - v_bc)\n", - "\n", - " if deformation_field:\n", - " d_bc = u_bc_anim - v_bc\n", - " d = igl.harmonic(v, f, b, d_bc, 2)\n", - " u = v + d\n", - " else:\n", - " u = igl.harmonic(v, f, b, u_bc_anim, 2)\n", - " p.update_object(vertices=u)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Relationship to \"differential coordinates\" and Laplacian surface editing\n", - "Biharmonic functions (whether positions or displacements) are solutions to the\n", - "bi-Laplace equation, but also minimizers of the \"Laplacian energy\". For\n", - "example, for displacements $\\mathbf{d}$, the energy reads\n", - "\n", - " $\\int\\limits_S \\|\\Delta \\mathbf{d}\\|^2 dA,$\n", - "\n", - "where we define $\\Delta \\mathbf{d}$ to simply apply the Laplacian\n", - "coordinate-wise.\n", - "\n", - "By linearity of the Laplace(-Beltrami) operator we can reexpress this energy in\n", - "terms of the original positions $\\mathbf{x}$ and the unknown positions\n", - "$\\mathbf{x}' = \\mathbf{x} - \\mathbf{d}$:\n", - "\n", - " $\\int\\limits_S \\|\\Delta (\\mathbf{x}' - \\mathbf{x})\\|^2 dA = \\int\\limits_S\n", - " \\|\\Delta \\mathbf{x}' - \\Delta \\mathbf{x})\\|^2 dA.$\n", - "\n", - "In the early work of Sorkine et al., the quantities $\\Delta \\mathbf{x}'$ and\n", - "$\\Delta \\mathbf{x}$ were dubbed \"differential coordinates\" (Sorkine, 2004).\n", - "Their deformations (without linearized rotations) is thus equivalent to\n", - "biharmonic deformation fields." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Polyharmonic deformation\n", - "We can generalize biharmonic deformation by considering different powers of\n", - "the Laplacian, resulting in a series of PDEs of the form:\n", - "\n", - " $\\Delta^k \\mathbf{d} = 0.$\n", - "\n", - "with $k\\in{1,2,3,\\dots}$. The choice of $k$ determines the level of continuity\n", - "at the handles. In particular, $k=1$ implies $C^0$ at the boundary, $k=2$\n", - "implies $C^1$, $k=3$ implies $C^2$ and in general $k$ implies $C^{k-1}$.\n", - "\n", - "The following example deforms a flat domain (left) into a bump as a solution to various $k$-harmonic PDEs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"bump-domain.obj\"))\n", - "u = v.copy()\n", - "\n", - "# Find boundary vertices outside annulus\n", - "vrn = np.linalg.norm(v, axis = 1)\n", - "is_outer = [vrn[i] - 1.00 > -1e-15 for i in range(v.shape[0])]\n", - "is_inner = [vrn[i] - 0.15 < 1e-15 for i in range(v.shape[0])]\n", - "in_b = [is_outer[i] or is_inner[i] for i in range(len(is_outer))]\n", - "\n", - "b = np.array([i for i in range(v.shape[0]) if (in_b[i])]).T\n", - "bc = np.zeros(b.size)\n", - "\n", - "for bi in range(b.size):\n", - " bc[bi] = 0.0 if is_outer[b[bi]] else 1.0\n", - "\n", - "c = np.array(is_outer)\n", - "\n", - "p = plot(u, f, c, shading={\"wire_width\": 0.01, \"colormap\": \"tab10\"}, return_plot=True) \n", - "\n", - "@interact(z_max=(0.0, 1.0), k=(1, 4))\n", - "def update(z_max, k):\n", - " z = igl.harmonic(v, f, b, bc, int(k))\n", - " u[:, 2] = z_max * z\n", - " p.update_object(vertices=u)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### As-rigid-as-possible\n", - "\n", - "Skinning and other linear methods for deformation are inherently limited.\n", - "Difficulties arise especially when large rotations are imposed by the handle constraints.\n", - "\n", - "In the context of energy-minimization approaches, the problem stems from\n", - "comparing positions (our displacements) in the coordinate frame of the\n", - "undeformed shape. These quadratic energies are at best invariant to global\n", - "rotations of the entire shape, but not smoothly varying local rotations. Thus\n", - "linear techniques will not produce non-trivial bending and twisting.\n", - "\n", - "Furthermore, when considering solid shapes (e.g. discretized with tetrahedral\n", - "meshes) linear methods struggle to maintain local volume, and they often suffer from\n", - "shrinking and bulging artifacts.\n", - "\n", - "Non-linear deformation techniques present a solution to these problems.\n", - "They work by comparing the deformation of a mesh\n", - "vertex to its rest position _rotated_ to a new coordinate frame which best\n", - "matches the deformation. The non-linearity stems from the mutual dependence of\n", - "the deformation and the best-fit rotation. These techniques are often labeled\n", - "\"as-rigid-as-possible\" as they penalize the sum of all local deformations'\n", - "deviations from rotations.\n", - "\n", - "To arrive at such an energy, let's consider a simple per-triangle energy:\n", - "\n", - " $E_\\text{linear}(\\mathbf{X}') = \\sum\\limits_{t \\in T} a_t \\sum\\limits_{\\{i,j\\}\n", - " \\in t} w_{ij} \\left\\|\n", - " \\left(\\mathbf{x}'_i - \\mathbf{x}'_j\\right) -\n", - " \\left(\\mathbf{x}_i - \\mathbf{x}_j\\right)\\right\\|^2$\n", - "\n", - "where $\\mathbf{X}'$ are the mesh's unknown deformed vertex positions, $t$ is a\n", - "triangle in a list of triangles $T$, $a_t$ is the area of triangle $t$ and\n", - "$\\{i,j\\}$ is an edge in triangle $t$. Thus, this energy measures the norm of\n", - "change between an edge vector in the original mesh $\\left(\\mathbf{x}_i -\n", - "\\mathbf{x}_j\\right)$ and the unknown mesh $\\left(\\mathbf{x}'_i -\n", - "\\mathbf{x}'_j\\right)$.\n", - "\n", - "This energy is **not** rotation invariant. If we rotate the mesh by 90 degrees\n", - "the change in edge vectors not aligned with the axis of rotation will be large,\n", - "despite the overall deformation being perfectly rigid.\n", - "\n", - "So, the \"as-rigid-as-possible\" solution is to append auxiliary variables\n", - "$\\mathbf{R}_t$\n", - "for each triangle $t$ which are constrained to be rotations. Then the energy is\n", - "rewritten, this time comparing deformed edge vectors to their rotated rest\n", - "counterparts:\n", - "\n", - "\n", - " $E_\\text{arap}(\\mathbf{X}',\\{\\mathbf{R}_1,\\dots,\\mathbf{R}_{|T|}\\}) = \\sum\\limits_{t \\in T} a_t \\sum\\limits_{\\{i,j\\}\n", - " \\in t} w_{ij} \\left\\|\n", - " \\left(\\mathbf{x}'_i - \\mathbf{x}'_j\\right)-\n", - " \\mathbf{R}_t\\left(\\mathbf{x}_i - \\mathbf{x}_j\\right)\\right\\|^2.$\n", - "\n", - "The separation into the primary vertex position variables $\\mathbf{X}'$ and the\n", - "rotations $\\{\\mathbf{R}_1,\\dots,\\mathbf{R}_{|T|}\\}$ lead to strategy for\n", - "optimization, too. If the rotations $\\{\\mathbf{R}_1,\\dots,\\mathbf{R}_{|T|}\\}$\n", - "are held fixed then the energy is quadratic in the remaining variables\n", - "$\\mathbf{X}'$ and can be optimized by solving a (sparse) global linear system.\n", - "Alternatively, if $\\mathbf{X}'$ are held fixed then each rotation is the\n", - "solution to a localized _Procrustes_ problem (found via $3 \\times 3$ SVD or\n", - "polar decompostion). These two steps---local and global---each weakly decrease\n", - "the energy, thus we may safely iterate them until convergence.\n", - "\n", - "The different flavors of \"as-rigid-as-possible\" depend on the dimension and\n", - "codimension of the domain and the edge-sets $T$. The proposed surface\n", - "manipulation technique by Sorkine and Alexa (Sorkine, 2007), considers $T$ to\n", - "be the set of sets of edges emanating from each vertex (spokes). Later, Chao et\n", - "al. derived the relationship between \"as-rigid-as-possible\" mesh energies and\n", - "co-rotational elasticity considering 0-codimension elements as edge-sets:\n", - "triangles in 2D and tetrahedra in 3D (Chao, 2010). They also showed how\n", - "Sorkine and Alexa's edge-sets are not a discretization of a continuous energy,\n", - "proposing instead edge-sets for surfaces containing all edges of elements\n", - "incident on a vertex (spokes and rims). They show that this amounts to\n", - "measuring bending, albeit in a discretization-dependent way.\n", - "\n", - "Libigl, supports these common flavors. Selecting one is a matter of setting the energy type before the precompuation phase.\n", - "\n", - "```python\n", - "#arap_data.energy = igl::ARAP_ENERGY_TYPE_SPOKES;\n", - "#arap_data.energy = igl::ARAP_ENERGY_TYPE_SPOKES_AND_RIMS;\n", - "#arap_data.energy = igl::ARAP_ENERGY_TYPE_ELEMENTS;\n", - "arap = igl.ARAP(v, f, dimension, b)\n", - "```\n", - "Just like `igl.min_quad_with_fixed_*`, this precomputation phase only depends on the mesh, fixed vertex indices `b` and the energy parameters. To solve with certain constraints on the positions of vertices in `b`, we may call:\n", - "\n", - "```python\n", - "vn = arap.solve(bc, v)\n", - "```\n", - "\n", - "which uses `v` as an initial guess and then computes the solution into it.\n", - "\n", - "Libigl's implementation of as-rigid-as-possible deformation takes advantage of the highly optimized singular value decomposition code from McAdams et al. (McAdams, 2011) which leverages SSE intrinsics.\n", - "\n", - "The following example deforms a surface as if it were made of an elastic material. The concept of local rigidity will be revisited shortly in the context of surface parameterization." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"decimated-knight.off\"))\n", - "s = igl.read_dmat(os.path.join(root_folder, \"data\", \"decimated-knight-selection.dmat\"))\n", - "\n", - "# Vertices in selection\n", - "b = np.array([[t[0] for t in [(i, s[i]) for i in range(0, v.shape[0])] \n", - " if t[1] >= 0]]).T\n", - "\n", - "# Centroid\n", - "mid = 0.5 * (np.max(v, axis=0) + np.min(v, axis=0))\n", - "\n", - "# Precomputation\n", - "arap = igl.ARAP(v, f, 3, b)\n", - "\n", - "# Set color based on selection\n", - "c = np.ones_like(f) * np.array([1.0, 228/255, 58/255])\n", - "for fi in range(0, f.shape[0]):\n", - " if s[f[fi, 0]] >= 0 and s[f[fi, 1]] >= 0 and s[f[fi, 2]] >= 0:\n", - " c[fi] = np.array([80/255, 64/255, 1.0])\n", - "\n", - "# Plot the mesh with pseudocolors\n", - "p = plot(v, f, c, return_plot=True)\n", - "\n", - "@interact(t=(0.0, 10.0))\n", - "def update(t=1.0):\n", - " bc = np.zeros((b.size, v.shape[1]))\n", - " for i in range(0, b.size):\n", - " bc[i] = v[b[i]]\n", - " if s[b[i]] == 0:\n", - " r = mid[0] * 0.25\n", - " bc[i, 0] += r * np.sin(0.5 * t * 2 * np.pi)\n", - " bc[i, 1] = bc[i, 1] - r + r * np.cos(np.pi + 0.5 * t * 2 * np.pi)\n", - " elif s[b[i]] == 1:\n", - " r = mid[1] * 0.15\n", - " bc[i, 1] = bc[i, 1] + r + r * np.cos(np.pi + 0.15 * t * 2 * np.pi)\n", - " bc[i, 2] -= r * np.sin(0.15 * t * 2 * np.pi)\n", - " elif s[b[i]] == 2:\n", - " r = mid[1] * 0.15\n", - " bc[i, 2] = bc[i, 2] + r + r * np.cos(np.pi + 0.35 * t * 2 * np.pi)\n", - " bc[i, 0] += r * np.sin(0.35 * t * 2 * np.pi)\n", - "\n", - " vn = arap.solve(bc, v)\n", - " p.update_object(vertices=vn)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Chapter 4: Parametrization\n", - "\n", - "In computer graphics, we denote as surface parametrization a map from the\n", - "surface to \\\\(\\mathbf{R}^2\\\\). It is usually encoded by a new set of 2D\n", - "coordinates for each vertex of the mesh (and possibly also by a new set of\n", - "faces in one to one correspondence with the faces of the original surface).\n", - "Note that\n", - "this definition is the *inverse* of the classical differential geometry\n", - "definition.\n", - "\n", - "A parametrization has many applications, ranging from texture mapping to\n", - "surface remeshing. Many algorithms have been proposed, and they can be broadly\n", - "divided in four families:\n", - "\n", - "1. **Single patch, fixed boundary**: these algorithm can parametrize a\n", - "disk-like part of the surface given fixed 2D positions for its boundary. These\n", - "algorithms are efficient and simple, but they usually produce high-distortion maps due to the fixed boundary.\n", - "\n", - "2. **Single patch, free boundary:** these algorithms let the boundary\n", - "deform freely, greatly reducing the map distortion. Care should be taken to\n", - "prevent the border to self-intersect.\n", - "\n", - "3. **Global parametrization**: these algorithms work on meshes with arbitrary\n", - "genus. They initially cut the mesh in multiple patches that can be separately parametrized. The generated maps are discontinuous on the cuts (often referred as *seams*).\n", - "\n", - "4. **Global seamless parametrization**: these are global parametrization algorithm that hides the seams, making the parametrization \"continuous\", under specific assumptions that we will discuss later." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Harmonic parametrization\n", - "\n", - "Harmonic parametrization (Eck, 2005) is a single patch, fixed boundary parametrization\n", - "algorithm that computes the 2D coordinates of the flattened mesh as two\n", - "harmonic functions.\n", - "\n", - "The algorithm is divided in 3 steps:\n", - "\n", - "1. Detection of the boundary vertices\n", - "2. Map the boundary vertices to a circle\n", - "3. Compute two harmonic functions (one for u and one for the v coordinate). The harmonic functions use the fixed vertices on the circle as boundary constraints.\n", - "\n", - "The algorithm is coded with libigl in the following example. `bnd` contains the indices of the boundary vertices, bnd_uv their position on the UV plane, and \"1\" denotes that we want to compute an harmonic function (2 will be for biharmonic, 3 for triharmonic, etc.). Note that each of the three\n", - "functions is designed to be reusable in other parametrization algorithms.\n", - "The UV coordinates are then used to apply a procedural checkerboard texture to the mesh." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"camelhead.off\"))\n", - "## Find the open boundary\n", - "bnd = igl.boundary_loop(f)\n", - "\n", - "## Map the boundary to a circle, preserving edge proportions\n", - "bnd_uv = igl.map_vertices_to_circle(v, bnd)\n", - "\n", - "## Harmonic parametrization for the internal vertices\n", - "uv = igl.harmonic(v, f, bnd, bnd_uv, 1)\n", - "v_p = np.hstack([uv, np.zeros((uv.shape[0],1))])\n", - "\n", - "p = plot(v, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, return_plot=True)\n", - "\n", - "@interact(mode=['3D','2D'])\n", - "def switch(mode):\n", - " if mode == \"3D\":\n", - " plot(v, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, plot=p)\n", - " if mode == \"2D\":\n", - " plot(v_p, f, uv=uv, shading={\"wireframe\": True, \"flat\": False}, plot=p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Least squares conformal maps\n", - "\n", - "Least squares conformal maps parametrization (Levy, 2002) minimizes the\n", - "conformal (angular) distortion of the parametrization. Differently from\n", - "harmonic parametrization, it does not need to have a fixed boundary.\n", - "\n", - "LSCM minimizes the following energy:\n", - "\n", - "\\\\[ E_{LSCM}(\\mathbf{u},\\mathbf{v}) = \\int_X \\frac{1}{2}| \\nabla \\mathbf{u}^{\\perp} - \\nabla \\mathbf{v} |^2 dA \\\\]\n", - "\n", - "which can be rewritten in matrix form as (Mullen, 2008):\n", - "\n", - "\\\\[ E_{LSCM}(\\mathbf{u},\\mathbf{v}) = \\frac{1}{2} [\\mathbf{u},\\mathbf{v}]^t (L_c - 2A) [\\mathbf{u},\\mathbf{v}] \\\\]\n", - "\n", - "where $L_c$ is the cotangent Laplacian matrix and $A$ is a matrix such that\n", - "$[\\mathbf{u},\\mathbf{v}]^t A [\\mathbf{u},\\mathbf{v}]$ is equal to the [vector\n", - "area](http://en.wikipedia.org/wiki/Vector_area) of the mesh.\n", - "\n", - "Using libigl, this matrix energy can be written in a few lines of code. The\n", - "cotangent matrix can be computed using `igl.cotmatrix`. Note that we want to apply the Laplacian matrix to the u and v coordinates at the same time, thus we need to extend it taking the left Kronecker product with a 2x2 identity matrix. The area matrix is computed with `igl.vector_area_matrix`.\n", - "\n", - "The final energy matrix is $L_{flat} - 2A$. Note that in this\n", - "case we do not need to fix the boundary. To remove the null space of the energy and make the minimum unique, it is sufficient to fix two arbitrary\n", - "vertices to two arbitrary positions. The full source code is provided in the following LSCM parametrization example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"camelhead.off\"))\n", - "\n", - "# Fix two points on the boundary\n", - "b = np.array([2, 1])\n", - "\n", - "bnd = igl.boundary_loop(f)\n", - "b[0] = bnd[0]\n", - "b[1] = bnd[int(bnd.size / 2)]\n", - "\n", - "bc = np.array([[0.0, 0.0], [1.0, 0.0]])\n", - "\n", - "# LSCM parametrization\n", - "_, uv = igl.lscm(v, f, b, bc)\n", - "\n", - "p = plot(v, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, return_plot=True)\n", - "\n", - "@interact(mode=['3D','2D'])\n", - "def switch(mode):\n", - " if mode == \"3D\":\n", - " plot(v, f, uv=uv, shading={\"wireframe\": False, \"flat\": False}, plot=p)\n", - " if mode == \"2D\":\n", - " plot(uv, f, uv=uv, shading={\"wireframe\": True, \"flat\": False}, plot=p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### As-rigid-as-possible parametrization\n", - "\n", - "As-rigid-as-possible parametrization (Liu, 2008) is a powerful single-patch, non-linear algorithm to compute a parametrization that strives to preserve\n", - "distances (and thus angles). The idea is very similar to ARAP surface\n", - "deformation: each triangle is mapped to the plane trying to preserve its\n", - "original shape, up to a rigid rotation.\n", - "\n", - "The algorithm can be implemented reusing the functions discussed in the\n", - "deformation chapter: `igl.ARAP` and `arap.solve`. The only\n", - "difference is that the optimization has to be done in 2D instead of 3D and that\n", - "we need to compute a starting point. While for 3D deformation the optimization\n", - "is bootstrapped with the original mesh, this is not the case for ARAP\n", - "parametrization since the starting point must be a 2D mesh. \n", - "\n", - "In the following example, we initialize the optimization with harmonic\n", - "parametrization. Similarly to LSCM, the boundary is free to deform to minimize\n", - "the distortion." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"camelhead.off\"))\n", - "\n", - "## Find the open boundary\n", - "bnd = igl.boundary_loop(f)\n", - "\n", - "## Map the boundary to a circle, preserving edge proportions\n", - "bnd_uv = igl.map_vertices_to_circle(v, bnd)\n", - "\n", - "## Harmonic parametrization for the internal vertices\n", - "uv = igl.harmonic(v, f, bnd, bnd_uv, 1)\n", - "\n", - "arap = igl.ARAP(v, f, 2, np.zeros(0))\n", - "uva = arap.solve(np.zeros((0, 0)), uv)\n", - "\n", - "p = plot(v, f, uv=uva, shading={\"wireframe\": False, \"flat\": False}, return_plot=True)\n", - "\n", - "@interact(mode=['3D','2D'])\n", - "def switch(mode):\n", - " if mode == \"3D\":\n", - " plot(v, f, uv=uva, shading={\"wireframe\": False, \"flat\": False}, plot=p)\n", - " if mode == \"2D\":\n", - " plot(uva, f, uv=uva, shading={\"wireframe\": True, \"flat\": False}, plot=p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### N-rotationally symmetric tangent fields\n", - "\n", - "The design of tangent fields is a basic tool used to design guidance fields for uniform quadrilateral and hexahedral remeshing. Libigl contains an implementation of all the state-of-the-art algorithms to design N-RoSy fields and their generalizations.\n", - "\n", - "In libigl, tangent unit-length vector fields are piece-wise constant on the faces of a triangle mesh, and they are described by one or more vectors per-face. The function\n", - "\n", - "```python\n", - "output_field, output_singularities = igl.nrosy(v, f, b, bc, b_soft, b_soft_weight, bc_soft, n, 0.5)\n", - "```\n", - "\n", - "creates a smooth unit-length vector field (n=1) starting from a sparse set of constrained faces, whose indices are listed in b and their constrained value is specified in bc. The functions supports soft_constraints (b_soft, b_soft_weight, bc_soft), and returns the interpolated field for each face of the triangle mesh (output_field), plus the singularities of the field (output_singularities).\n", - "\n", - "The singularities are vertices where the field vanishes (highlighted in red in the figure above). `igl.nrosy` can also generate N-RoSy fields (Levy, 2008), which are a generalization of vector fields where in every face the vector is defined up to a constant rotation of $2\\pi / N$. As can be observed in the following figure, the singularities of the fields generated with different N are of different types and they appear in different positions.\n", - "\n", - "We demonstrate how to call and plot N-RoSy fields in the following example, where the degree of the field can be changed. `igl.nrosy` implements the algorithm proposed in (Bommes, 2009). N-RoSy fields can also be interpolated with many other algorithms, see the library [libdirectional](https://github.com/avaxman/libdirectional) for a reference implementation of the most popular ones. For a complete categorization of fields used in various applications see (Vaxman, 2016)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# # Converts a representative vector per face in the full set of vectors that describe an N-RoSy field\n", - "# def representative_to_nrosy(v, f, r, n):\n", - "# b1, b2, b3 = igl.local_basis(v, f)\n", - "# ym = np.zeros((f.shape[0] * n, 3))\n", - "\n", - "# for i in range(f.shape[0]):\n", - "# x = r[i] * b1[i].T\n", - "# y = r[i] * b2[i].T\n", - "# angle = np.arctan2(y[0], x[0])\n", - "\n", - "# for j in range(0, n):\n", - "# anglej = angle + 2 * np.pi * j / float(n)\n", - "# xj = np.cos(anglej)\n", - "# yj = np.sin(anglej)\n", - "# ym[i * n + j] = xj * b1[i] + yj * b2[i]\n", - "# return ym\n", - "\n", - "# # Load a mesh in OFF format and plot it\n", - "# v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"bumpy.off\"))\n", - "# #print(points_n)\n", - " \n", - "# # Constrained faces id\n", - "# b = np.array([[0]])\n", - "\n", - "# # Highlight in red the constrained faces\n", - "# c = np.ones((f.shape[0], 3))\n", - "# for i in range(b.size):\n", - "# c[b[i]] = np.array([1, 0, 0])\n", - "\n", - "# p = plot(v, f, c)\n", - "# pn_id = pp_id = e_id = None\n", - "\n", - "\n", - "# # Constrained faces representative vector\n", - "# bcv = np.array([[1.0, 1.0, 1.0]])\n", - "# avg = igl.avg_edge_length(v, f)\n", - "# bc = igl.barycenter(v, f)\n", - "\n", - "# # Plots the mesh with an N-RoSy field and its singularities on top\n", - "# # The constrained faces (b) are colored in red.\n", - "# @interact(n=(1, 10))\n", - "# def plot_mesh_nrosy(n=1):\n", - "# global pn_id, pp_id, e_id, bcv, avg, b, bc\n", - "\n", - "# r, s = igl.nrosy(v, f, b, bcv, np.array([[]], dtype=np.int64), np.array([[]]), np.array([[]]), n, 0.5)\n", - " \n", - "# # Expand the representative vectors in the full vector set and plot them as lines\n", - "# y = representative_to_nrosy(v, f, r, n)\n", - "# be = np.zeros((bc.shape[0] * n, 3))\n", - "# for i in range(bc.shape[0]):\n", - "# for j in range(n):\n", - "# be[i * n + j] = bc[i]\n", - "\n", - "# if e_id:\n", - "# p.remove_object(e_id)\n", - "# e_id = p.add_lines(be, be + y * (avg / 2))\n", - "\n", - "# # Plot the singularities as colored dots (red for negative, blue for positive)\n", - "# points_n = []\n", - "# points_p = []\n", - "# for i in range(0, s.size):\n", - "# if s[i] < -0.001:\n", - "# points_n.append(v[i])\n", - "# elif s[i] > 0.001:\n", - "# points_p.append(v[i])\n", - " \n", - "# if pp_id and pn_id:\n", - "# p.remove_object(pn_id)\n", - "# p.remove_object(pp_id)\n", - "# if len(points_n) > 0:\n", - "# pn_id = p.add_points(np.array(points_n), c=\"red\", shading={\"point_size\": 2.0})\n", - "# if len(points_p) > 0:\n", - "# pp_id = p.add_points(np.array(points_p), c=\"blue\", shading={\"point_size\": 2.0})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Planarization\n", - "\n", - "A quad mesh can be transformed in a planar quad mesh with Shape-Up (Bouaziz, 2012), a local/global approach that uses the global step to enforce surface continuity and the local step to enforce planarity.\n", - "\n", - "The following example planarizes a quad mesh until it satisfies a user-given planarity threshold. The colors represent the planarity of the quads." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Load a quad mesh generated by a conjugate field\n", - "vqc, fqc, _ = igl.read_off(os.path.join(root_folder, \"data\", \"inspired_mesh_quads_Conjugate.off\"))\n", - "\n", - "# Convert it to a triangle mesh\n", - "fqc_tri = np.zeros((fqc.shape[0] * 2, 3), dtype=\"int64\")\n", - "fqc_tri[:fqc.shape[0]] = fqc[:, :3]\n", - "fqc_tri[fqc.shape[0]:, 0] = fqc[:, 2]\n", - "fqc_tri[fqc.shape[0]:, 1] = fqc[:, 3]\n", - "fqc_tri[fqc.shape[0]:, 2] = fqc[:, 0]\n", - "\n", - "# Planarize it\n", - "vqc_p = igl.planarize_quad_mesh(vqc, fqc, 100, 0.005)\n", - "\n", - "# Calculate a color to each quad that corresponds to its planarity\n", - "planarity = igl.quad_planarity(vqc, fqc)\n", - "planarity_p = igl.quad_planarity(vqc_p, fqc)\n", - "\n", - "c = np.concatenate([planarity, planarity])\n", - "c_p = np.concatenate([planarity_p, planarity_p])\n", - "\n", - "p = plot(vqc, fqc_tri, c, shading={\"normalize\":[min(np.min(c), np.min(c_p)), max(np.max(c), np.max(c_p))]})\n", - "\n", - "@interact(mode=['Curved','Planar'])\n", - "def switch(mode):\n", - " if mode == \"Curved\":\n", - " p.update_object(colors=c)\n", - " if mode == \"Planar\":\n", - " p.update_object(vertices=vqc_p, colors=c_p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Chapter 5: External libraries\n", - "\n", - "An additional positive side effect of using matrices as basic types is that it\n", - "is easy to exchange data between libigl and other software and libraries." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Baking ambient occlusion\n", - "\n", - "[Ambient occlusion](http://en.wikipedia.org/wiki/Ambient_occlusion) is a\n", - "rendering technique used to calculate the exposure of each point in a surface\n", - "to ambient lighting. It is usually encoded as a scalar (normalized between 0\n", - "and 1) associated with the vertice of a mesh.\n", - "\n", - "Formally, ambient occlusion is defined as:\n", - "\n", - "\\\\[ A_p = \\frac{1}{\\pi} \\int_\\omega V_{p,\\omega}(n \\cdot \\omega) d\\omega \\\\]\n", - "\n", - "where $V_{p,\\omega}$ is the visibility function at p, defined to be zero if p\n", - "is occluded in the direction $\\omega$ and one otherwise, and $d\\omega$ is the\n", - "infinitesimal solid angle step of the integration variable $\\omega$.\n", - "\n", - "The integral is usually approximated by casting rays in random directions\n", - "around each vertex. This approximation can be computed using the function:\n", - "\n", - "```\n", - "ao = igl.ambient_occlusion(v, f, v_samples, n_samples, 500)\n", - "```\n", - "\n", - "that given a scene described in `v` and `f`, computes the ambient occlusion of\n", - "the points in `v_samples` whose associated normals are `n_samples`. The\n", - "number of casted rays can be controlled (usually at least 300-500 rays are\n", - "required to get a smooth result) and the result is returned in `ao`, as a\n", - "single scalar for each sample.\n", - "\n", - "Ambient occlusion can be used to darken the surface colors, as shown in the following example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"fertility.off\"))\n", - "\n", - "n = igl.per_vertex_normals(v, f)\n", - "\n", - "# Compute ambient occlusion factor using embree\n", - "ao = igl.ambient_occlusion(v, f, v, n, 50)\n", - "ao = 1.0 - ao\n", - "\n", - "plot(v, f, ao, shading={\"colormap\": \"gist_gray\"})" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Chapter 6: Miscellaneous\n", - "\n", - "Libigl contains a _wide_ variety of geometry processing tools and functions for\n", - "dealing with meshes and the linear algebra related to them: far too many to\n", - "discuss in this introductory tutorial. We've pulled out a couple of the\n", - "interesting functions in this chapter to highlight." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Mesh Statistics\n", - "\n", - "Libigl contains various mesh statistics, including face angles, face areas and\n", - "the detection of singular vertices, which are vertices with more or less than 6\n", - "neighbours in triangulations or 4 in quadrangulations.\n", - "\n", - "The example computes these quantities and\n", - "does a basic statistic analysis that allows to estimate the isometry and\n", - "regularity of a mesh:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"horse_quad.obj\"))\n", - "\n", - "## Count the number of irregular vertices, the border is ignored\n", - "irregular = igl.is_irregular_vertex(v, f) \n", - "v_count = v.shape[0]\n", - "irregular_v_count = np.sum(irregular)\n", - "irregular_ratio = irregular_v_count / v_count\n", - "\n", - "print(\"Irregular vertices: \\n%d/%d (%.2f%%)\\n\"%(irregular_v_count, v_count, irregular_ratio * 100))\n", - "\n", - "## Compute areas, min, max and standard deviation\n", - "area = igl.doublearea(v, f) / 2.0\n", - "\n", - "area_avg = np.mean(area)\n", - "area_min = np.min(area) / area_avg\n", - "area_max = np.max(area) / area_avg\n", - "area_ns = (area - area_avg) / area_avg\n", - "area_sigma = np.sqrt(np.mean(np.square(area_ns)))\n", - "\n", - "print(\"Areas (Min/Max)/Avg_Area Sigma: \\n%.2f/%.2f (%.2f)\\n\"%(area_min, area_max, area_sigma))\n", - "\n", - "## Compute per face angles, min, max and standard deviation\n", - "angles = igl.internal_angles(v, f)\n", - "angles = 360.0 * (angles / (2 * np.pi))\n", - "\n", - "angle_avg = np.mean(angles)\n", - "angle_min = np.min(angles)\n", - "angle_max = np.max(angles)\n", - "angle_ns = angles - angle_avg\n", - "angle_sigma = np.sqrt(np.mean(np.square(angle_ns)))\n", - "\n", - "print(\"Angles in degrees (Min/Max) Sigma: \\n%.2f/%.2f (%.2f)\\n\"%(angle_min, angle_max, angle_sigma))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The first row contains the number and percentage of irregular vertices, which\n", - "is particularly important for quadrilateral meshes when they are used to define\n", - "subdivision surfaces: every singular point will result in a point of the\n", - "surface that is only C^1.\n", - "\n", - "The second row reports the area of the minimal element, maximal element and the\n", - "standard deviation. These numbers are normalized by the mean area, so in the\n", - "example above 5.33 max area means that the biggest face is 5 times larger than\n", - "the average face. An ideal isotropic mesh would have both min and max area\n", - "close to 1.\n", - "\n", - "The third row measures the face angles, which should be close to 60 degrees (90\n", - "for quads) in a perfectly regular triangulation. For FEM purposes, the closer\n", - "the angles are to 60 degrees the more stable will the optimization be. In this\n", - "case, it is clear that the mesh is of bad quality and it will probably result\n", - "in artifacts if used for solving PDEs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Subdivision surfaces\n", - "\n", - "Given a coarse mesh (aka cage) with vertices `V` and faces `F`, one can createa\n", - "higher-resolution mesh with more vertices and faces by _subdividing_ every\n", - "face. That is, each coarse triangle in the input is replaced by many smaller\n", - "triangles. Libigl has three different methods for subdividing a triangle mesh.\n", - "\n", - "An \"in plane\" subdivision method will not change the point set or carrier\n", - "surface of the mesh. New vertices are added on the planes of existing triangles\n", - "and vertices surviving from the original mesh are not moved.\n", - "\n", - "By adding new faces, a subdivision algorithm changes the _combinatorics_ of the\n", - "mesh. The change in combinatorics and the formula for positioning the\n", - "high-resolution vertices is called the \"subdivision rule\".\n", - "\n", - "For example, in the _in plane_ subdivision method of `igl.upsample`, vertices\n", - "are added at the midpoint of every edge: $v_{ab} = \\frac{1}{2}(v_a + v_b)$ and\n", - "each triangle $(i_a,i_b,i_c)$ is replaced with four triangles:\n", - "$(i_a,i_{ab},i_{ca})$, $(i_b,i_{bc},i_{ab})$, $(i_{ab},i_{bc},i_{ca})$, and\n", - "$(i_{bc},i_{c},i_{ca})$. This process may be applied recursively, resulting in\n", - "a finer and finer mesh.\n", - "\n", - "The subdivision method of `igl.loop` is not in plane. The vertices of the\n", - "refined mesh are moved to weight combinations of their neighbors: the mesh is\n", - "smoothed as it is refined (Loop, 1987). This and other _smooth subdivision_\n", - "methods can be understood as generalizations of spline curves to surfaces. In\n", - "particular the Loop subdivision method will converge to a $C^1$ surface as we\n", - "consider the limit of recursive applications of subdivision. Away from\n", - "\"irregular\" or \"extraordinary\" vertices (vertices of the original cage with\n", - "valence not equal to 6), the surface is $C^2$. The combinatorics (connectivity\n", - "and number of faces) of `igl.loop` and `igl.upsample` are identical: the only\n", - "difference is that the vertices have been smoothed in `igl.loop`.\n", - "\n", - "Finally, libigl also implements a form of _in plane_ \"false barycentric\n", - "subdivision\" in `igl.false_barycentric_subdivision`. This method simply adds\n", - "the barycenter of every triangle as a new vertex $v_{abc}$ and replaces each\n", - "triangle with three triangles $(i_a,i_b,i_{abc})$, $(i_b,i_c,i_{abc})$, and\n", - "$(i_c,i_a,i_{abc})$. In contrast to `igl.upsample`, this method will create\n", - "triangles with smaller and smaller internal angles and new vertices will sample\n", - "the carrier surfaces with extreme bias." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ov, of = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"decimated-knight.off\"))\n", - "uv, uf = igl.upsample(ov, of)\n", - "lv, lf = igl.loop(ov, of)\n", - "\n", - "p = plot(ov, of, shading={\"wireframe\": True})\n", - "\n", - "@interact(mode=['Coarse','Upsample', 'Loop'])\n", - "def switch(mode):\n", - " if mode == \"Coarse\":\n", - " plot(ov, of, shading={\"wireframe\": True}, plot=p)\n", - " if mode == \"Upsample\":\n", - " plot(uv, uf, shading={\"wireframe\": True}, plot=p)\n", - " #p.update_object(vertices=uv, faces=uf)\n", - " if mode == \"Loop\":\n", - " plot(lv, lf, shading={\"wireframe\": True}, plot=p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Data smoothing\n", - "\n", - "A noisy function $f$ defined on a surface $\\Omega$ can be smoothed using an energy minimization that balances a smoothing term $E_S$ with a quadratic fitting term:\n", - "\n", - "$u = \\operatorname{argmin}_u \\alpha E_S(u) + (1-\\alpha)\\int_\\Omega ||u-f||^2 dx$\n", - "\n", - "The parameter $\\alpha$ determines how aggressively the function is smoothed.\n", - "\n", - "A classical choice for the smoothness energy is the Laplacian energy of the function with zero Neumann boundary conditions, which is a form of the biharmonic energy. It is constructed using the cotangent Laplacian `L` and\n", - "the mass matrix `M`: `QL = L'*(M\\L)`. Because of the implicit zero Neumann boundary conditions however, the function behavior is significantly warped at the boundary if $f$ does not have zero normal gradient at the boundary.\n", - "\n", - "In (Stein, 2017) it is suggested to use the Biharmonic energy with natural\n", - "Hessian boundary conditions instead, which corresponds to the hessian energy with the matrix `QH = H'*(M2\\H)`, where `H` is a finite element Hessian and `M2` is a stacked mass matrix. The matrices `H` and `QH` are implemented in\n", - "libigl as `igl.hessian` and `igl.hessian_energy` respectively. \n", - "\n", - "In the following example the differences between the Laplacian energy with zero Neumann boundary conditions and the Hessian energy can be clearly seen: whereas the zero Neumann boundary condition in the third image bias the isolines\n", - "of the function to be perpendicular to the boundary, the Hessian energy gives an unbiased result.\n", - "\n", - "The following example shows a function on the beetle mesh, the function with added noise, the result of smoothing with the Laplacian energy and zero Neumann boundary conditions, and the result of smoothing with the Hessian energy." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "v, f = igl.read_triangle_mesh(os.path.join(root_folder, \"data\", \"beetle.off\"))\n", - "e = igl.edges(f)\n", - "\n", - "# Constructing an exact function to smooth\n", - "z_exact = v[0:, 2] + 0.5 * v[0:, 1] + v[0:, 1] * v[0:, 1] + v[0:, 2] * v[0:, 2] * v[0:, 2]\n", - " \n", - "# Make the exact function noisy\n", - "s = 0.2 * (np.max(z_exact) - np.min(z_exact))\n", - "np.random.seed(5)\n", - "z_noisy = z_exact + s * np.random.rand(*z_exact.shape)\n", - "\n", - "# Constructing the squared Laplacian and squared Hessian energy\n", - "l = igl.cotmatrix(v, f)\n", - "m = igl.massmatrix(v, f, igl.MASSMATRIX_TYPE_BARYCENTRIC)\n", - "\n", - "m_inv_l = sp.sparse.linalg.spsolve(m, l)\n", - "ql = l.T @ m_inv_l\n", - "qh = igl.hessian_energy(v, f)\n", - "\n", - "# Solve to find Laplacian-smoothed and Hessian-smoothed solutions\n", - "al = 8e-4;\n", - "zl = sp.sparse.linalg.spsolve(al * ql + (1 - al) * m, al * m.dot(z_noisy))\n", - "ah = 5e-6;\n", - "zh = sp.sparse.linalg.spsolve(ah * qh + (1 - ah) * m, ah * m.dot(z_noisy))\n", - "\n", - "# Calculate isolines\n", - "ilx_v, ilx_e = igl.isolines(v, f, z_exact, 30)\n", - "iln_v, iln_e = igl.isolines(v, f, z_noisy, 30)\n", - "ill_v, ill_e = igl.isolines(v, f, zl, 30)\n", - "ilh_v, ilh_e = igl.isolines(v, f, zh, 30)\n", - "\n", - "p = plot(v, f, z_exact, return_plot=True)\n", - "e_id = p.add_edges(ilx_v, ilx_e)\n", - "\n", - "@interact(mode=['Original', 'Noisy', 'Biharmonic smoothing (0-Neumann)', 'Biharmonic smoothing (Natural Hessian)'])\n", - "def switch(mode):\n", - " global e_id\n", - " p.remove_object(e_id)\n", - " if mode == \"Original\":\n", - " p.update_object(colors=z_exact)\n", - " e_id = p.add_edges(ilx_v, ilx_e)\n", - " if mode == \"Noisy\":\n", - " p.update_object(colors=z_noisy)\n", - " e_id = p.add_edges(iln_v, iln_e)\n", - " if mode == \"Biharmonic smoothing (0-Neumann)\":\n", - " p.update_object(colors=zl)\n", - " e_id = p.add_edges(ill_v, ill_e)\n", - " if mode == \"Biharmonic smoothing (Natural Hessian)\":\n", - " p.update_object(colors=zh)\n", - " e_id = p.add_edges(ilh_v, ilh_e)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Marching Tetrahedra\n", - "\n", - "Often 3D data is captured as scalar field defined over space $f(\\mathbf{x}) : \\mathcal{R}^3 \\rightarrow \\mathcal{R}$. Lurking within this field, _iso-surfaces_ of the scalar field are often salient geometric objects. The\n", - "iso-surface at value $v$ is composed of all points $\\mathbf{x}$ in $\\mathcal{R}^3$ such that $f(\\mathbf{x}) = v$. A core problem in geometry processing is to extract an iso-surface as a triangle mesh for further mesh-based processing or visualization. This is referred to as iso-contouring.\n", - "\n", - "\"Marching Tetrahedra\" (Treece, 1999) is a [famous method](https://en.wikipedia.org/wiki/Marching_tetrahedra) for iso-contouring tri-linear functions $f$ on a 3D simplicial complex (aka a tet mesh). The core idea of this method is to contour the iso-surface passing through each cell (if it does at all) with a predefined topology (aka connectivity) chosen from a look up tabledepending on the function values at each vertex of the cell. The method\n", - "iterates (\"marches\") over all cells (\"tetrahedra\") in the complex and stitches together the final mesh.\n", - "\n", - "In libigl, `igl.marching_tets` constructs a triangle mesh `(v,f)` approximating the iso-level set for the value `isovalue` from an input scalar field `s` sampled at the vertices of a tet mesh locations `(tv, tt)`:\n", - "\n", - "```python\n", - "v, f = igl.marching_tets(tv, tt, s, isovalue)\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "tv = np.load(os.path.join(root_folder, \"data\", \"marching_cube_tv.npy\"))\n", - "tt = np.load(os.path.join(root_folder, \"data\", \"marching_cube_tt.npy\"))\n", - "s = np.linalg.norm(tv, axis=1)\n", - "\n", - "svs = []\n", - "sfs = []\n", - "for i in np.linspace(0.05, 0.75, 15):\n", - " sv, sf, _, _ = igl.marching_tets(tv, tt, s, i)\n", - " svs.append(sv)\n", - " sfs.append(sf)\n", - "\n", - "p = plot(sv, sf, return_plot=True)\n", - "oid = 0\n", - "\n", - "@interact(t=(0, 14))\n", - "def update(t=0):\n", - " global oid\n", - " p.remove_object(oid)\n", - " oid = p.add_mesh(svs[t], sfs[t])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "\n", - "\n", - "[^jacobson_thesis_2013]: Alec Jacobson, [_Algorithms and Interfaces for Real-Time Deformation of 2D and 3D Shapes_](https://www.google.com/search?q=Algorithms+and+Interfaces+for+Real-Time+Deformation+of+2D+and+3D+Shapes), 2013.\n", - "[^kazhdan_2012]: Michael Kazhdan, Jake Solomon, Mirela Ben-Chen, [Can Mean-Curvature Flow Be Made Non-Singular](https://www.google.com/search?q=Can+Mean-Curvature+Flow+Be+Made+Non-Singular), 2012.\n", - "[^meyer_2003]: Mark Meyer, Mathieu Desbrun, Peter Schröder and Alan H. Barr, [Discrete Differential-Geometry Operators for Triangulated 2-Manifolds](https://www.google.com/search?q=Discrete+Differential-Geometry+Operators+for+Triangulated+2-Manifolds), 2003.\n", - "[^mitchell_1987]: Joseph S. B. Mitchell, David M. Mount, Christos H. Papadimitriou. [The Discrete Geodesic Problem](https://www.google.com/search?q=The+Discrete+Geodesic+Problem), 1987\n", - "[^panozzo_2010]: Daniele Panozzo, Enrico Puppo, Luigi Rocca, [Efficient Multi-scale Curvature and Crease Estimation](https://www.google.com/search?q=Efficient+Multi-scale+Curvature+and+Crease+Estimation), 2010.\n", - "[^sharf_2007]: Andrei Sharf, Thomas Lewiner, Gil Shklarski, Sivan Toledo, and Daniel Cohen-Or. [Interactive topology-aware surface reconstruction](https://www.google.com/search?q=Interactive+topology-aware+surface+reconstruction), 2007.\n", - "\n", - "\n", - "\n", - "[^barbic_2005]: Jernej Barbic and Doug James. [Real-Time Subspace Integration for St.Venant-Kirchhoff Deformable Models](https://www.google.com/search?q=Real-Time+Subspace+Integration+for+St.Venant-Kirchhoff+Deformable+Models), 2005.\n", - "[^hildebrandt_2011]: Klaus Hildebrandt, Christian Schulz, Christoph von Tycowicz, and Konrad Polthier. [Interactive Surface Modeling using Modal Analysis](https://www.google.com/search?q=Interactive+Surface+Modeling+using+Modal+Analysis), 2011.\n", - "[^rustamov_2011]: Raid M. Rustamov, [Multiscale Biharmonic Kernels](https://www.google.com/search?q=Multiscale+Biharmonic+Kernels), 2011.\n", - "[^vallet_2008]: Bruno Vallet and Bruno Lévy. [Spectral Geometry Processing with Manifold Harmonics](https://www.google.com/search?q=Spectral+Geometry+Processing+with+Manifold+Harmonics), 2008.\n", - "\n", - "\n", - "\n", - "[^botsch_2004]: Matrio Botsch and Leif Kobbelt. [An Intuitive Framework for Real-Time Freeform Modeling](https://www.google.com/search?q=An+Intuitive+Framework+for+Real-Time+Freeform+Modeling), 2004.\n", - "[^chao_2010]: Isaac Chao, Ulrich Pinkall, Patrick Sanan, Peter Schröder. [A Simple Geometric Model for Elastic Deformations](https://www.google.com/search?q=A+Simple+Geometric+Model+for+Elastic+Deformations), 2010.\n", - "[^jacobson_2011]: Alec Jacobson, Ilya Baran, Jovan Popović, and Olga Sorkine. [Bounded Biharmonic Weights for Real-Time Deformation](https://www.google.com/search?q=Bounded+biharmonic+weights+for+real-time+deformation), 2011.\n", - "[^jacobson_2012]: Alec Jacobson, Ilya Baran, Ladislav Kavan, Jovan Popović, and Olga Sorkine. [Fast Automatic Skinning Transformations](https://www.google.com/search?q=Fast+Automatic+Skinning+Transformations), 2012.\n", - "[^jacobson_mixed_2010]: Alec Jacobson, Elif Tosun, Olga Sorkine, and Denis Zorin. [Mixed Finite Elements for Variational Surface Modeling](https://www.google.com/search?q=Mixed+Finite+Elements+for+Variational+Surface+Modeling), 2010.\n", - "[^jacobson_skinning_course_2014]: Alec Jacobson, Zhigang Deng, Ladislav Kavan, J.P. Lewis. [_Skinning: Real-Time Shape Deformation_](https://www.google.com/search?q=Skinning+Real-Time+Shape+Deformation), 2014.\n", - "[^kavan_2008]: Ladislav Kavan, Steven Collins, Jiri Zara, and Carol O'Sullivan. [Geometric Skinning with Approximate Dual Quaternion Blending](https://www.google.com/search?q=Geometric+Skinning+with+Approximate+Dual+Quaternion+Blending), 2008.\n", - "[^mcadams_2011]: Alexa McAdams, Andrew Selle, Rasmus Tamstorf, Joseph Teran, Eftychios Sifakis. [Computing the Singular Value Decomposition of 3x3 matrices with minimal branching and elementary floating point operations](https://www.google.com/search?q=Computing+the+Singular+Value+Decomposition+of+3x3+matrices+with+minimal+branching+and+elementary+floating+point+operations), 2011.\n", - "[^sorkine_2004]: Olga Sorkine, Yaron Lipman, Daniel Cohen-Or, Marc Alexa, Christian Rössl and Hans-Peter Seidel. [Laplacian Surface Editing](https://www.google.com/search?q=Laplacian+Surface+Editing), 2004.\n", - "[^sorkine_2007]: Olga Sorkine and Marc Alexa. [As-rigid-as-possible Surface Modeling](https://www.google.com/search?q=As-rigid-as-possible+Surface+Modeling), 2007.\n", - "[^wang_bc_2015]: Yu Wang, Alec Jacobson, Jernej Barbic, Ladislav Kavan. [Linear Subspace Design for Real-Time Shape Deformation](https://www.google.com/search?q=Linear+Subspace+Design+for+Real-Time+Shape+Deformation), 2015\n", - "\n", - "\n", - "\n", - "[^bommes_2009]: David Bommes, Henrik Zimmer, Leif Kobbelt. [Mixed-integer quadrangulation](http://www-sop.inria.fr/members/David.Bommes/publications/miq.pdf), 2009.\n", - "[^bouaziz_2012]: Sofien Bouaziz, Mario Deuss, Yuliy Schwartzburg, Thibaut Weise, Mark Pauly [Shape-Up: Shaping Discrete Geometry with Projections](http://lgg.epfl.ch/publications/2012/shapeup.pdf), 2012\n", - "[^eck_2005]: Matthias Eck, Tony DeRose, Tom Duchamp, Hugues Hoppe, Michael Lounsbery, Werner Stuetzle. [Multiresolution Analysis of Arbitrary Meshes](http://research.microsoft.com/en-us/um/people/hoppe/mra.pdf), 2005.\n", - "[^levy_2002]: Bruno Lévy, Sylvain Petitjean, Nicolas Ray, Jérome Maillot. [Least Squares Conformal Maps, for Automatic Texture Atlas Generation](http://www.cs.jhu.edu/~misha/Fall09/Levy02.pdf), 2002.\n", - "[^levy_2008]: Nicolas Ray, Bruno Vallet, Wan Chiu Li, Bruno Lévy. [N-Symmetry Direction Field Design](http://alice.loria.fr/publications/papers/2008/DGF/NSDFD-TOG.pdf), 2008.\n", - "[^liu_2008]: Ligang Liu, Lei Zhang, Yin Xu, Craig Gotsman, Steven J. Gortler. [A Local/Global Approach to Mesh Parameterization](http://cs.harvard.edu/~sjg/papers/arap.pdf), 2008.\n", - "[^mullen_2008]: Patrick Mullen, Yiying Tong, Pierre Alliez, Mathieu Desbrun. [Spectral Conformal Parameterization](http://www.geometry.caltech.edu/pubs/MTAD08.pdf), 2008.\n", - "[^panozzo_2014]: Daniele Panozzo, Enrico Puppo, Marco Tarini, Olga Sorkine-Hornung. [Frame Fields: Anisotropic and Non-Orthogonal Cross Fields](http://cs.nyu.edu/~panozzo/papers/frame-fields-2014.pdf), 2014.\n", - "[^vaxman_2016]: Amir Vaxman, Marcel Campen, Olga Diamanti, Daniele Panozzo, David Bommes, Klaus Hildebrandt, Mirela Ben-Chen. [Directional Field Synthesis, Design, and Processing](https://www.google.com/search?q=Directional+Field+Synthesis+Design+and+Processing), 2016\n", - "\n", - "\n", - "\n", - "[^schuller_2013]: Christian Schüller, Ladislav Kavan, Daniele Panozzo, Olga Sorkine-Hornung. [Locally Injective Mappings](http://igl.ethz.ch/projects/LIM/), 2013.\n", - "[^zhou_2016]: Qingnan Zhou, Eitan Grinspun, Denis Zorin. [Mesh Arrangements for Solid Geometry](https://www.google.com/search?q=Mesh+Arrangements+for+Solid+Geometry), 2016\n", - "\n", - "\n", - "\n", - "[^baerentzen_2005]: J Andreas Baerentzen and Henrik Aanaes. [Signed distance computation using the angle weighted pseudonormal](https://www.google.com/search?q=Signed+distance+computation+using+the+angle+weighted+pseudonormal), 2005.\n", - "[^bouaziz_2012]: Sofien Bouaziz, Mario Deuss, Yuliy Schwartzburg, Thibaut Weise, Mark Pauly [Shape-Up: Shaping Discrete Geometry with Projections](http://lgg.epfl.ch/publications/2012/shapeup.pdf), 2012\n", - "[^garg_2016]: Akash Garg, Alec Jacobson, Eitan Grinspun. [Computational Design of Reconfigurables](https://www.google.com/search?q=Computational+Design+of+Reconfigurables), 2016\n", - "[^hoppe_1996]: Hugues Hoppe. [Progressive Meshes](https://www.google.com/search?q=Progressive+meshes), 1996\n", - "[^jacobson_2013]: Alec Jacobson, Ladislav Kavan, and Olga Sorkine. [Robust Inside-Outside Segmentation using Generalized Winding Numbers](https://www.google.com/search?q=Robust+Inside-Outside+Segmentation+using+Generalized+Winding+Numbers), 2013.\n", - "[^loop_1987]: Charles Loop. [Smooth Subdivision Surfaces Based on Triangles](https://www.google.com/search?q=smooth+subdivision+surfaces+based+on+triangles), 1987.\n", - "[^lorensen_1987]: W.E. Lorensen and Harvey E. Cline. [Marching cubes: A high resolution 3d surface construction algorithm](https://www.google.com/search?q=Marching+cubes:+A+high+resolution+3d+surface+construction+algorithm), 1987.\n", - "[^rabinovich_2016]: Michael Rabinovich, Roi Poranne, Daniele Panozzo, Olga Sorkine-Hornung. [Scalable Locally Injective Mappings](http://cs.nyu.edu/~panozzo/papers/SLIM-2016.pdf), 2016.\n", - "[^schroeder_1994]: William J. Schroeder, William E. Lorensen, and Steve Linthicum. [Implicit Modeling of Swept Surfaces and Volumes](https://www.google.com/search?q=implicit+modeling+of+swept+surfaces+and+volumes), 1994.\n", - "[^takayama14]: Kenshi Takayama, Alec Jacobson, Ladislav Kavan, Olga Sorkine-Hornung. [A Simple Method for Correcting Facet Orientations in Polygon Meshes Based on Ray Casting](https://www.google.com/search?q=A+Simple+Method+for+Correcting+Facet+Orientations+in+Polygon+Meshes+Based+on+Ray+Casting), 2014.\n", - "[^treece_1999]: G.M. Treece, R.W. Prager, and A.H.Gee [Regularised marching tetrahedra: improved iso-surface extraction](https://www.sciencedirect.com/science/article/pii/S009784939900076X), 1999.\n", - "[^crane_2013]: Keenan Crane, Clarisse Weischedel, and Max Wardetzky. [Geodesics in Heat: A New Approach to Computing Distance Based on Heat Flow](https://www.google.com/search?q=geodesics+in+heat+a+new+approach+to+computing+distance+based+on+heat+flow), 2013.\n", - "[^bobenko_2005]: Alexander I. Bobenko and Boris A. Springborn. [A discrete Laplace-Beltrami operator for simplicial surfaces](https://www.google.com/search?q=a+discrete+laplace-beltrami+operator+for+simplicial+surfaces), 2005.\n", - "[^jiang_2017]: Zhongshi Jiang, Scott Schaefer, Daniele Panozzo. [SCAF: Simplicial Complex Augmentation Framework for Bijective Maps](https://doi.org/10.1145/3130800.3130895), 2017" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.2" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/website/README.md b/website/README.md new file mode 100644 index 00000000..c92e2e98 --- /dev/null +++ b/website/README.md @@ -0,0 +1,24 @@ +# Documentation site + +The Python-bindings documentation is a [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) +site. The API reference is generated from the compiled package's `.pyi` type +stubs, so it always matches what is actually bound. + +## Build locally + +```bash +python -m pip install . # build/install the bindings (produces the .pyi stubs) +python -m pip install -r website/requirements.txt +python website/generate_api.py \ + --package "$(python -c 'import igl, os; print(os.path.dirname(igl.__file__))')" \ + --igl-include /libigl/include \ + --out website/docs/api +cd website && python -m mkdocs serve +``` + +`--igl-include` points at a libigl `include/` tree and is optional: it is used +only to decide which functions get a C++ Doxygen cross-link. When building the +bindings from source, CMake fetches libigl under `build*/_deps/libigl-src`. + +The generated `website/docs/api/*.md` pages and the built `website/site/` are +not committed; CI regenerates them. diff --git a/tutorial/contributing.md b/website/docs/contributing.md similarity index 100% rename from tutorial/contributing.md rename to website/docs/contributing.md diff --git a/website/docs/index.md b/website/docs/index.md new file mode 100644 index 00000000..0bf6688a --- /dev/null +++ b/website/docs/index.md @@ -0,0 +1,50 @@ +# libigl Python bindings + +[![PyPI version](https://badge.fury.io/py/libigl.svg)](https://pypi.org/project/libigl/) +[![build wheels](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml/badge.svg)](https://github.com/libigl/libigl-python-bindings/actions/workflows/wheels.yml?query=branch%3Amain) + +Python bindings for [libigl](https://libigl.github.io) — a simple C++ geometry +processing library. The bindings are NumPy-native: meshes are plain arrays +(`V` an `#V by 3` float array of vertices, `F` an `#F by 3` int array of faces), +and every function returns NumPy arrays or SciPy sparse matrices. + +```bash +python -m pip install libigl +``` + +```python +import igl +import numpy as np + +V, F = igl.read_triangle_mesh("bunny.obj") +L = igl.cotmatrix(V, F) # sparse cotangent Laplacian +N = igl.per_vertex_normals(V, F) # per-vertex normals +K = igl.gaussian_curvature(V, F) # discrete Gaussian curvature +``` + +## Where to go next + +- **[API Reference](api/index.md)** — every bound function and class, grouped by + module, generated directly from the installed package. Functions that also + exist in C++ link straight to the [C++ Doxygen reference](https://libigl.github.io/dox/). +- **[Installation](install.md)** — pip, optional modules, and building from + source. + +## Modules + +The core functionality lives in `igl`. Additional functionality that depends on +third-party libraries is grouped into submodules: + +| Module | Contents | +| --- | --- | +| `igl` | core geometry processing (Laplacians, curvature, distances, parametrization, deformation, …) | +| `igl.predicates` | exact geometric predicates (orientation, incircle, winding numbers) | +| `igl.cycodebase` | cubic Bézier / spline distance and root-finding | +| `igl.copyleft`, `igl.copyleft.cgal`, `igl.copyleft.tetgen` | GPL-licensed functionality (booleans, meshing) | +| `igl.embree` | Embree-accelerated ray casting and ambient occlusion | +| `igl.triangle` | Triangle-based 2D meshing | +| `igl.spectra` | Spectra-based sparse eigensolves | + +!!! note + These bindings are under active development. If a function you need is + missing, please [open an issue](https://github.com/libigl/libigl-python-bindings/issues). diff --git a/website/docs/install.md b/website/docs/install.md new file mode 100644 index 00000000..f31d5cf9 --- /dev/null +++ b/website/docs/install.md @@ -0,0 +1,40 @@ +# Installation + +## From PyPI (recommended) + +```bash +python -m pip install libigl +``` + +Pre-built wheels are published for Linux, macOS, and Windows across the actively +supported CPython versions, so no compiler is required. + +## Importing + +```python +import igl +``` + +Submodules are imported explicitly: + +```python +import igl.predicates +import igl.copyleft.cgal +import igl.embree +``` + +## Building from source + +Building from source requires a C++17 compiler and CMake. libigl and its +dependencies are fetched automatically by CMake. + +```bash +git clone https://github.com/libigl/libigl-python-bindings.git +cd libigl-python-bindings +python -m pip install . +``` + +To build only a subset of the (heavier) optional modules, pass the corresponding +CMake options (`LIBIGL_COPYLEFT_CGAL`, `LIBIGL_COPYLEFT_TETGEN`, +`LIBIGL_RESTRICTED_TRIANGLE`, `LIBIGL_EMBREE`, `LIBIGL_SPECTRA`, +`LIBIGL_PREDICATES`, `LIBIGL_CYCODEBASE`). diff --git a/website/docs/stylesheets/extra.css b/website/docs/stylesheets/extra.css new file mode 100644 index 00000000..6583fe35 --- /dev/null +++ b/website/docs/stylesheets/extra.css @@ -0,0 +1,21 @@ +/* C++ cross-reference chips linking to the Doxygen reference. */ +.cpp-xref { + font-size: 0.68em; + font-weight: 600; + vertical-align: middle; + padding: 0.05em 0.5em; + margin-left: 0.4em; + border-radius: 1em; + border: 1px solid var(--md-default-fg-color--lightest); + color: var(--md-primary-fg-color); + white-space: nowrap; +} +.cpp-xref:hover { + background: var(--md-primary-fg-color); + color: var(--md-primary-bg-color); +} + +/* Tighten the per-function API headings. */ +.md-typeset h3 { + margin-top: 1.6em; +} diff --git a/website/generate_api.py b/website/generate_api.py new file mode 100644 index 00000000..45d2f597 --- /dev/null +++ b/website/generate_api.py @@ -0,0 +1,410 @@ +"""Generate the Python API reference from the nanobind ``.pyi`` type stubs. + +The stubs (emitted by ``nanobind_add_stub`` during the build) are the reliable +source of truth for compiled-module signatures: statically parsing them avoids +importing the extension and sidesteps the introspection quirks of compiled +modules. For every module we emit one Material-for-MkDocs page, and every +function that has a matching ``igl/.h`` header is cross-linked to its C++ +Doxygen page on libigl.github.io/dox so the Python and C++ references stay +coherent. + +Usage: + python website/generate_api.py \ + --package igl \ + --igl-include /libigl/include \ + --out website/docs/api +""" +from __future__ import annotations + +import argparse +import ast +import os +import re +from collections import Counter +from pathlib import Path + +DOX_BASE = "https://libigl.github.io/dox" + +# Human-facing titles for each module (path -> title). Anything not listed +# falls back to the dotted module name. +MODULE_TITLES = { + "igl": "igl (core)", + "igl.copyleft": "igl.copyleft", + "igl.copyleft.cgal": "igl.copyleft.cgal", + "igl.copyleft.tetgen": "igl.copyleft.tetgen", + "igl.cycodebase": "igl.cycodebase", + "igl.embree": "igl.embree", + "igl.predicates": "igl.predicates", + "igl.spectra": "igl.spectra", + "igl.triangle": "igl.triangle", +} + + +def clean_annotation(node: ast.AST) -> str: + """Turn a stub type annotation AST into a compact, readable string. + + nanobind array parameters are annotated as + ``Annotated[ArrayLike, dict(dtype='float64', shape=(None, None), ...)]``; + we render those as e.g. ``float64[m, n]``. + """ + if node is None: + return "" + if isinstance(node, ast.Name): + return node.id + if isinstance(node, ast.Attribute): + return f"{clean_annotation(node.value)}.{node.attr}" + if isinstance(node, ast.Constant): + return "None" if node.value is None else repr(node.value) + if isinstance(node, ast.Tuple): + return ", ".join(clean_annotation(e) for e in node.elts) + if isinstance(node, ast.Subscript): + base = clean_annotation(node.value) + if base == "Annotated": + return _clean_annotated(node.slice) + return f"{base}[{clean_annotation(node.slice)}]" + # Fallback: best-effort unparse. + try: + return ast.unparse(node) + except Exception: + return "Any" + + +def _clean_annotated(slice_node: ast.AST) -> str: + """Render the payload of an ``Annotated[Base, dict(...)]`` array type.""" + elts = slice_node.elts if isinstance(slice_node, ast.Tuple) else [slice_node] + dtype = None + dims = None + for elt in elts: + if isinstance(elt, ast.Call): # the dict(...) metadata + for kw in elt.keywords: + if kw.arg == "dtype" and isinstance(kw.value, ast.Constant): + dtype = kw.value.value + elif kw.arg == "shape": + letters = ["m", "n", "p", "q"] + # A 1-D shape is emitted as `shape=(None)` (a bare value), + # a 2-D+ shape as a real tuple `shape=(None, None)`. + shape_elts = (kw.value.elts if isinstance(kw.value, ast.Tuple) + else [kw.value]) + dims = ", ".join( + letters[i] if isinstance(d, ast.Constant) and d.value is None + else clean_annotation(d) + for i, d in enumerate(shape_elts)) + if dtype and dims is not None: + return f"{dtype}[{dims}]" + if dtype: + return f"{dtype}[...]" + return "ArrayLike" + + +def render_signature(fn: ast.FunctionDef) -> str: + """Render a def's argument list, dropping ``self`` and cleaning types.""" + a = fn.args + parts = [] + defaults = [None] * (len(a.args) - len(a.defaults)) + list(a.defaults) + for arg, default in zip(a.args, defaults): + if arg.arg == "self": + continue + s = arg.arg + if arg.annotation is not None: + s += f": {clean_annotation(arg.annotation)}" + if default is not None: + s += f" = {clean_annotation(default)}" + parts.append(s) + ret = f" -> {clean_annotation(fn.returns)}" if fn.returns else "" + return f"{fn.name}({', '.join(parts)}){ret}" + + +def _esc(text: str) -> str: + """Escape a leading '#' so libigl's '#V'/'#E'/'#F' ("number of") notation + isn't parsed as a Markdown heading (python-markdown accepts space-less + '#heading'), which would otherwise pollute the table of contents.""" + return re.sub(r"^(\s*)(#+)", r"\1\\\2", text) + + +def format_docstring(doc: str) -> str: + """Turn a doxygen-style docstring into Material markdown. + + The leading prose becomes the description; ``@param[in|out] name text`` + lines become a Parameters list and ``@return`` becomes Returns. + """ + if not doc: + return "_No description available._\n" + lines = [l.rstrip() for l in doc.strip("\n").splitlines()] + desc, params, returns = [], [], [] + bucket = desc + for line in lines: + stripped = line.strip() + if stripped.startswith("@param"): + rest = stripped.split(None, 1)[1] if " " in stripped else "" + params.append(rest) + bucket = params + elif stripped.startswith(("@return", "@returns")): + rest = stripped.split(None, 1)[1] if " " in stripped else "" + returns.append(rest) + bucket = returns + elif stripped.startswith("\\see") or stripped.startswith("@see"): + bucket = None # drop see-also cross-refs for now + elif bucket is not None: + if bucket is desc: + bucket.append(line) + elif stripped: # continuation of a param/return entry + bucket[-1] += " " + stripped + + out = [] + text = "\n".join(_esc(l) for l in desc).strip() + if text: + out.append(text + "\n") + if params: + out.append("**Parameters**\n") + for p in params: + name, _, rest = p.partition(" ") + out.append(f"- `{name}` — {_esc(rest.strip())}") + out.append("") + if returns: + out.append("**Returns**\n") + for r in returns: + out.append(f"- {_esc(r.strip())}") + out.append("") + return "\n".join(out) + "\n" + + +def module_for(stub: Path, package_parent: Path) -> str: + """Dotted module name from a stub path (its parent dir under the package).""" + rel = stub.parent.relative_to(package_parent) + return ".".join(rel.parts) + + +def _split_top_level(argstr: str): + """Split a signature argument list on top-level commas (bracket-aware).""" + parts, depth, cur = [], 0, "" + for ch in argstr: + if ch in "([{": + depth += 1 + elif ch in ")]}": + depth -= 1 + if ch == "," and depth == 0: + parts.append(cur) + cur = "" + else: + cur += ch + if cur.strip(): + parts.append(cur) + return parts + + +def _repair_arg_order(src: str) -> str: + """Give every argument a default once any earlier one has one. + + nanobind occasionally emits a required argument after an optional one + (e.g. mesh_boolean's ``type_str``), which is not valid Python. Adding a + synthetic ``= ...`` makes the stub parseable; such args render with ``...``. + """ + open_i = src.find("(") + if open_i < 0: + return src + depth, close_i = 0, -1 + for i in range(open_i, len(src)): + if src[i] in "([{": + depth += 1 + elif src[i] in ")]}": + depth -= 1 + if depth == 0: + close_i = i + break + if close_i < 0: + return src + args = _split_top_level(src[open_i + 1:close_i]) + seen_default = False + fixed = [] + for i, a in enumerate(args): + # Name any unnamed parameter (nanobind sometimes drops the name, e.g. + # "(: Annotated[...]" when a binding omits the argument label). + if a.lstrip().startswith(":"): + lead = a[:len(a) - len(a.lstrip())] + a = f"{lead}arg{i}{a.lstrip()}" + has_default = "=" in _strip_brackets(a) + if seen_default and not has_default and a.strip(): + a = a + " = ..." + has_default = True + seen_default = seen_default or has_default + fixed.append(a) + return src[:open_i + 1] + ",".join(fixed) + src[close_i:] + + +def _strip_brackets(s: str) -> str: + """Remove bracketed spans so a top-level '=' can be detected.""" + out, depth = "", 0 + for ch in s: + if ch in "([{": + depth += 1 + elif ch in ")]}": + depth -= 1 + elif depth == 0: + out += ch + return out + + +def _parse_tolerant(text: str): + """Parse a stub, repairing the invalid signatures nanobind sometimes emits. + + Each stub signature is a single line, so we repair argument ordering on + every ``def`` line (see _repair_arg_order) before parsing the whole file, + which keeps ``@overload`` decorators attached to their functions. + """ + try: + return ast.parse(text).body + except SyntaxError: + pass + repaired = [] + for line in text.splitlines(keepends=True): + if line.lstrip().startswith("def ") and line.rstrip().endswith(":"): + repaired.append(_repair_arg_order(line)) + else: + repaired.append(line) + try: + return ast.parse("".join(repaired)).body + except SyntaxError as e: + print(f" warning: stub still unparseable after repair: {e}") + return [] + + +def collect(stub: Path): + """Return (functions, classes) from a stub, merging @overload groups.""" + functions, classes = {}, [] + for node in _parse_tolerant(stub.read_text()): + if isinstance(node, ast.FunctionDef): + functions.setdefault(node.name, []).append(node) + elif isinstance(node, ast.ClassDef): + methods = [n for n in node.body if isinstance(n, ast.FunctionDef) + and not n.name.startswith("__")] + classes.append((node, methods, ast.get_docstring(node))) + return functions, classes + + +def dox_filename(name: str) -> str: + """Doxygen file-reference page filename for igl/.h. + + Doxygen mangles the output name by turning '.h' into '_8h' and doubling + every underscore; case is preserved (e.g. marching_cubes.h -> + marching__cubes_8h.html, AABB.h -> AABB_8h.html).""" + return f"{name.replace('_', '__')}_8h.html" + + +def cpp_chip(name: str, linkable: dict) -> str: + """A standalone C++ cross-link line placed under a heading. + + Kept out of the heading itself so it does not leak into the table of + contents. `linkable` maps a Python function/class name to its verified + Doxygen page filename; only names present there are linked, so we never + emit a 404 (see build of `linkable` in main).""" + page = linkable.get(name) + if page: + return f"[C++ reference]({DOX_BASE}/{page}){{ .cpp-xref }}\n" + return "" + + +def emit_function(name, defs, linkable) -> str: + out = [f"### {name}\n"] + chip = cpp_chip(name, linkable) + if chip: + out.append(chip) + seen = set() + for fn in defs: + sig = render_signature(fn) + out.append(f"```python\n{sig}\n```\n") + doc = ast.get_docstring(fn) + if doc and doc not in seen: + seen.add(doc) + out.append(format_docstring(doc)) + return "\n".join(out) + + +def emit_class(node, methods, doc, linkable) -> str: + out = [f"### {node.name}\n"] + chip = cpp_chip(node.name, linkable) + if chip: + out.append(chip) + if doc: + out.append(format_docstring(doc)) + for m in methods: + out.append(f"#### {node.name}.{m.name}\n") + out.append(f"```python\n{render_signature(m)}\n```\n") + mdoc = ast.get_docstring(m) + if mdoc: + out.append(format_docstring(mdoc)) + return "\n".join(out) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--package", default="igl", help="path to the igl package dir") + ap.add_argument("--igl-include", default="", help="path to libigl include/ dir") + ap.add_argument("--dox-index", default="", + help="Doxygen files.html (or any page listing) for the " + "target /dox/ site; links are validated against it so " + "none 404. Omit to link by header-name heuristic only.") + ap.add_argument("--out", default="website/docs/api") + args = ap.parse_args() + + package = Path(args.package).resolve() + package_parent = package.parent + out = Path(args.out) + out.mkdir(parents=True, exist_ok=True) + + # Cross-link a function to its C++ Doxygen page only when its header basename + # is unique across the whole libigl tree — duplicated names (e.g. + # marching_cubes exists in both igl/ and igl/copyleft/) get disambiguated by + # Doxygen into non-derivable page names, so we skip them. + unique = set() + if args.igl_include: + counts = Counter(h.stem for h in Path(args.igl_include).glob("igl/**/*.h")) + unique = {stem for stem, c in counts.items() if c == 1} + + # When a Doxygen index is supplied, additionally require that the derived + # page actually exists there. This keeps the preview free of 404s even when + # the deployed /dox/ lags the bindings' libigl version (new/renamed headers). + dox_pages = None + if args.dox_index: + dox_pages = set(re.findall(r"[A-Za-z0-9_]+_8h\.html", + Path(args.dox_index).read_text())) + + linkable = {} + for name in unique: + page = dox_filename(name) + if dox_pages is None or page in dox_pages: + linkable[name] = page + print(f" cross-linkable functions: {len(linkable)}" + + (f" (validated against {len(dox_pages)} dox pages)" if dox_pages else "")) + + stubs = sorted(package.rglob("pyigl_*.pyi")) + index_rows = [] + for stub in stubs: + module = module_for(stub, package_parent) + title = MODULE_TITLES.get(module, module) + functions, classes = collect(stub) + page = [f"# {title}\n"] + page.append(f"Python API reference for `{module}`.\n") + for name in sorted(functions): + page.append(emit_function(name, functions[name], linkable)) + for node, methods, doc in sorted(classes, key=lambda c: c[0].name): + page.append(emit_class(node, methods, doc, linkable)) + fname = module.replace(".", "_") + ".md" + (out / fname).write_text("\n".join(page)) + n = len(functions) + len(classes) + index_rows.append((title, fname, n)) + print(f" {module}: {len(functions)} functions, {len(classes)} classes -> {fname}") + + # An index page listing every module. + idx = ["# API Reference\n", + "Auto-generated from the compiled bindings' type stubs. Functions with " + "a C++ counterpart link to the [C++ Doxygen reference]" + f"({DOX_BASE}/).\n", + "| Module | Symbols |", "| --- | --- |"] + for title, fname, n in index_rows: + idx.append(f"| [{title}]({fname}) | {n} |") + (out / "index.md").write_text("\n".join(idx) + "\n") + print(f"Wrote {len(stubs)} module pages + index to {out}") + + +if __name__ == "__main__": + main() diff --git a/website/mkdocs.yml b/website/mkdocs.yml new file mode 100644 index 00000000..4a1f075c --- /dev/null +++ b/website/mkdocs.yml @@ -0,0 +1,73 @@ +site_name: libigl Python bindings +site_url: 'https://libigl.github.io/libigl-python-bindings/' +repo_name: 'libigl/libigl-python-bindings' +repo_url: 'https://github.com/libigl/libigl-python-bindings' +site_description: "Python bindings for libigl, a simple geometry processing library" +docs_dir: 'docs' +remote_branch: 'gh-pages' + +theme: + name: material + palette: + - scheme: default + primary: 'light blue' + accent: 'blue' + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + primary: 'light blue' + accent: 'blue' + toggle: + icon: material/weather-sunny + name: Switch to light mode + features: + - navigation.instant + - navigation.tabs + - navigation.top + - search.highlight + - content.code.copy + - toc.follow + +extra_css: + - stylesheets/extra.css + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - footnotes + - toc: + permalink: true + toc_depth: 3 + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.superfences + - pymdownx.details + - pymdownx.tabbed: + alternate_style: true + - pymdownx.arithmatex: + generic: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + +extra_javascript: + - https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.js + +nav: + - Home: index.md + - Installation: install.md + - API Reference: + - Overview: api/index.md + - igl (core): api/igl.md + - igl.predicates: api/igl_predicates.md + - igl.cycodebase: api/igl_cycodebase.md + - igl.copyleft: api/igl_copyleft.md + - igl.copyleft.cgal: api/igl_copyleft_cgal.md + - igl.copyleft.tetgen: api/igl_copyleft_tetgen.md + - igl.embree: api/igl_embree.md + - igl.triangle: api/igl_triangle.md + - igl.spectra: api/igl_spectra.md + - Contributing: contributing.md diff --git a/website/requirements.txt b/website/requirements.txt new file mode 100644 index 00000000..ff07bec5 --- /dev/null +++ b/website/requirements.txt @@ -0,0 +1,2 @@ +# Dependencies for building the Python-bindings documentation site. +mkdocs-material