Skip to content

Repository files navigation

Conformal Willmore flow

Based on the work of Keenan Crane, this is an implementation of Conformal curvature flow using spin transformations.

Dependencies

  • Eigen (header only, version 3.3.7)
  • IGL (header only)
  • qmake (version 5)

Dependencies (Device code only)

  • CUDA (version 9.0 or greater)
  • Thrust (shipped with CUDA)
  • CUSP (header only)

Dependencies (Tests and benchmarks)

  • Google Test
  • Google Benchmark

Build

Configuration

The following environment variables are required for compilation:

CUDA_PATH : Needs to point to the base directory of your cuda lib and includes CUDA_ARCH : Your device compute capability (e.g. 30, 35, ... 50, 52 ...) HOST_COMPILER : Your local g++ compiler (compatible with cuda compiles, g++ 4.8.5)

> git submodule update --init --recursive
> qmake
> make -j

References:

[1] K. Crane, U. Pinkall, and P. Schröder, “Spin transformations of discrete surfaces,” ACM Transactions on Graphics, vol. 30, no. 4, p. 1, Jul. 2011.
[2] K. Crane, U. Pinkall, and P. Schröder, “Robust fairing via conformal curvature flow,” ACM Transactions on Graphics, vol. 32, no. 4, p. 1, Jul. 2013.

Project Structure

  • The library contains a host and a device implementation, separated by namspaces.
  • The host implementation is header only and was designed to feel like an extension of the popular IGL library (it follows their style guide too.)
  • The device implementation requires compiling into a shared library and has been R-pathed into the tests and benchmarks for convenience.
  • The project comes with 2 host samples, and one device sample. The first host, and only device sample is a demo implementation of willmore flow. The second host sample named matrix_bake, will export all matrices required by the tests and benchmarks in matrix market format.
  • The project comes with a full set of host and device tests and benchmarks.
  • Currently all subdirectories dump an executable in their directory.

Coding standards

  • The host side follows the same style as IGL using single capital letters for parameter names of common data, and snake case for function names.
  • The device side uses a similar style but due to the increased number of parameters in functions, I decided to provide full parameter names.
  • Both use view types where ever possible to avoid needless copies, but also to avoid enforcing specific data types on the user.

##NOTE

  • Spin positions test is failing for the DEVICE side implementation as it finds an equivalent but different solution to the linear system
  • Before testing and benchmarking you must place the matrices and models folders in the test directory

Development of parallel implementation

This project was developed as a final year programming assignment, with the goal of reimplementing a sequential algorithm on the GPU, and benchmarking the performance gains. I chose to implement conformal willmore flow using spin tranformations as nature of a geometry processing project lends itself to data oriented design. I have outlined some of the more noteworthy parts of the project below.

Adjacency Matrix Indices

Overview

One of the main aspects of the project was to assemble sparse matrices in parallel. One possible approach would be to compute lists of triplets, which could then be sorted by row and column index, before being reduced. The draw back of this approach is of course using two sorts. The two sparse matrices required for this project both share the same sparsity pattern (laplacian), and we know that, all entries in the matrix will only be written to a maximum of twice (except the diagonals), so it makes sense to try and use atomics here as the number of collisions will be very low. To facilitate this, each thread working on the matrix would require an index to write out it's value, and this function provides that.

The goal is to produce the matrix column and row indices, as well as diagonal indices (useful for later), and entry indices for parallel assembly. The first two are essentially the vertex-vertex adjacency lists with added diagonals. The adjacency lists can be found by creating a list of all possible half edges and then using unique to remove duplicates. Once we've obtained them, we can find the locations where our diagonal entries should be inserted by comparing each row index against each column index and writing a one if, it is less. If we follow this with a reduce, we've obtained the amount of rows per column before the diagonal, so to finish we can add an ascending list and the cumulative valence of each row (prior to diagonals being inserted) to these reduced values and voi-la.

row_index: 0 0 0 1 1 2 2 3 3 3
col_index: 1 2 3 0 3 0 3 0 1 2
--------------------------------
compared : 0 0 0 1 0 1 0 1 1 1
--------------------------------
index : 0 1 2 3
reduced : 0 1 1 3
--------------------------------
valence : 3 2 2 3
cumval : 0 3 5 7 10
sequence : 0 1 2 3
(reduced + cumval + sequence =)
diagonals: 0 5 8 13

Handy optimizations are to pack the comparisson and reduce into one kernel using a thrust transform iterator on the way in, and then pack the final sequence and valence add in to the end using a transform output iterator, leaving us with one single kernel launch.

Given the diagonals, it is simple to produce the row and column indices by copying across the vertex-vertex adjacency lists, but making sure to skip our diagonals. Then in a second pass, the diagonals indices are copied to using an ascending sequence. In practice this can all be done with thrust and permutation iterators.

row_index: 0 0 0 1 1 2 2 3 3 3
col_index: 1 2 3 0 3 0 3 0 1 2
diagonals: 0 5 8 13
--------------------------------------
1st pass : - 0 0 0 1 - 1 2 - 2 3 3 3 -
2nd pass : 0 0 0 0 1 1 1 2 2 2 3 3 3 3

As for the regular entry indices, we use a parallel binary search method on each in each face (same order as the threads in the sparse matrix assembly kernels.) Given an edge A -> B we find the upper bound and lower bounds as the cumulative valence of vertex A, and one past A. We then perform a binary search within these bounds for B. Unfortunately this can yeild unbalanced work-loads for threads in the kernel, however it need only be computed once and executes quick enough for this purpose, especially when combined with the gains in the sparse matrix assembly.

row_index : 0 0 0 1 1 2 2 3 3 3
col_index : 1 2 3 0 3 0 3 0 1 2
cumval : 0 3 5 7 10
------------------------------
f0 : 0 1 3
lower_bound : 0 3 7
upper_bound : 3 5 10
b-search : 0 4 7

Cotangent Laplacian

Overview

The cotangent laplacian is a very useful discrete representation of a surfaces mean curvature, used heavily for this algorithm. It will be recomputed at every iteration, and so it was essential that the assembly be quick. Initially I began by launching 6 threads per face, meaning 2 threads per face edge, which directly corresponds to the amount of writes into the matrix required. This allowed for one atomic add per thread, and hence a fairly quick assembly, however the uneven number of threads per block, and the fact that threads working on the same face could be in different warps limited my ability to combat bank conflicts. My original solution was to instead have 8 threads per face, with 2 dummy threads doing no work, but aligning the rest. However I then realised that once the alignment was in place, the entire computation could be performed using only interwarp communcation rather than block-wide. This would mean I could get rid of shared memory and potential back conflicts, and instead use warp shuffling to share data between my threads. At this point it seemed wasteful to have 2 dead threads, so I settled for 4 threads per face, with one dead thread. Each alive thread would write to global memory twice, once in the upper triangular part and once in the lower. The thread communication is fairly simple, each thread loads in a vertex positon (1-per thread), and then using a warp shuffle, we subtract the neighbours vertex value to produce 3 edges. With these edges I first compute the face area, and then the final cotangent laplacian value. Writing these value back to global memory is simple since we have computed the entry indices in the previous step.

if lane < 3:
read vertex_pos
shuffle (0 1 2 -) -> (1 2 0 1)
shuffle down subtract (1-2 2-0 0-1 1-2)
shuffle donw neighbor_edge
inv_area <- 0.5 * rsqrt(cross(edge, neighbor_edge))
result <- dot(edge, neigbor_edge) * inv_area

In the real implementation I don't just kill off the 4th thread, instead I use it as temporary storage to make shuffle downs easier. The main kernel leaves the diagonals untouched, as they are easier computed in a second pass, we need simply reduce each column and write the negation of the sum.

Intrinsic Dirac

Overview

The intrinsic dirac operator is the core of the algorithm and luckily has the same sparsity pattern as the cotangent laplacian, so we can reuse the row and column indices, as well as the diagonal and entry indices for computation. The resulting computation is more complex, involving quaternion hammilton products, and is not entirely symmetrical, however the resolution wasn't too bad. Another issue I faced (unresolved) is that we can't take advantage of vectorized writes to global memory when using atomics as they're only implemented for 4 byte primitives. The final diagonal computation was also more challenging to implement, involving more variables than the simple reduce for a cotangent laplacian. To optimize this I first noted that all contributions were coming from adjacent triangles, and so could probably be modelled using a reduce. In particular, the contribution would come from the case where we use the same vertex for both sides of our edge, which after a subtraction will result in zero, so we can therefor assume that all diagonals will be purely real quaternions with a zero imaginary vector. The real part can be computed as the dot product of the two edges stemming from our vertex, plus the regular real component computed from only our change in mean curvature. After all this we can model the diagonals as a reduce by vertex triangle adjacency, where at each adjacency pair we transform the two edges containing our vertex into the resulting diagonal.

Tri <- [v0, v1, v2]
find two edges:
e0 <- v1-v0
e1 <- v2-v0
result <- (rho^2 * area / 9) + dot(e0, e1) / (4 * area)

Solving the sparse Linear systems

Overview

This project involved solving two sparse linear systems. The first called Similarity transforms, produces a quaternion per vertex that best describes it's conformal deformation, using the intrinsic dirac matrix as a left hand side. The second called Spin positions, produces new vertex positions that best fit the edges calculated using the similarity transforms. This was probably the most challenging and important stage of the project. These two processes are the slowest host functions as well, meaning it was crucial to speed them up. I ended up implementing two methods for both, one using a direct solve via the cuSolverSp and cuSparse API, and the other using an iterative conjugate gradient solver via the CUSP API.

Interestingly the conjugate gradient solve out performs a direct cholesky decomposition drastically for the similarity transforms, and beats the host version for some of the smaller meshes. The direct solve wins for larger meshes in the Spin positions step, where the iterative solve performs poorly.

Future improvements

I ran out of time before I was able to implement the mean curvature calculation and projection on the device side, and would add this next. I would also work on moving duplicate code out of the sparse matrix assembly kernels which would allow the shuffle methods to be used by other functions, such as face area calculations. Finally I would like to have used streams more as there is definitely potential to overlap computation in this project.

Benchmarks

Host

Intel® Xeon(R) CPU E5-1650 v3 @ 3.50GHz × 12 processor

nameiterationsreal_timecpu_timetime_unit
HOST_cotangent_laplacian_cube1826273852.713852.37ns
HOST_cotangent_laplacian_spot907.68602e+067.65249e+06ns
HOST_cotangent_laplacian_bunny352.03471e+072e+07ns
HOST_cotangent_laplacian_tyra87.18162e+076.8106e+07ns
HOST_divergent_edges_cube4713921477.791477.58ns
HOST_divergent_edges_spot1005.24034e+065.2395e+06ns
HOST_divergent_edges_bunny501.39971e+071.39957e+07ns
HOST_divergent_edges_tyra125.04251e+075.04111e+07ns
HOST_face_area_cube4344953164.346164.308ns
HOST_face_area_spot2074339835339751ns
HOST_face_area_bunny723974836974671ns
HOST_face_area_tyra2263.1432e+063.14241e+06ns
HOST_intrinsic_dirac_cube445461599115988.7ns
HOST_intrinsic_dirac_spot87.53803e+077.53403e+07ns
HOST_intrinsic_dirac_bunny16.25203e+086.25151e+08ns
HOST_intrinsic_dirac_tyra11.83794e+091.83772e+09ns
HOST_mean_curvature_cube2044611365.646365.569ns
HOST_mean_curvature_spot1228559729559669ns
HOST_mean_curvature_bunny6231.14741e+061.14732e+06ns
HOST_mean_curvature_tyra1624.46348e+064.463e+06ns
HOST_orthonormalize_cube912301768.27768.2ns
HOST_orthonormalize_spot5326135622135611ns
HOST_orthonormalize_bunny2243310662310637ns
HOST_orthonormalize_tyra749934909934832ns
HOST_project_basis_cube1746431397.31397.274ns
HOST_project_basis_spot110596267062664.8ns
HOST_project_basis_bunny4811142988142976ns
HOST_project_basis_tyra1644419267419232ns
HOST_quaternion_matrix_cube6469510676.510675.6ns
HOST_quaternion_matrix_spot242.96319e+072.96293e+07ns
HOST_quaternion_matrix_bunny125.74237e+075.7419e+07ns
HOST_quaternion_matrix_tyra32.02537e+082.02519e+08ns
HOST_similarity_xform_cube2963623613.923611.8ns
HOST_similarity_xform_spot16.35867e+086.35806e+08ns
HOST_similarity_xform_bunny14.63868e+094.63829e+09ns
HOST_similarity_xform_tyra18.65426e+098.65345e+09ns
HOST_spin_positions_cube3823418642.518641ns
HOST_spin_positions_spot16.29275e+086.29218e+08ns
HOST_spin_positions_bunny14.65188e+094.65144e+09ns
HOST_spin_positions_tyra18.61795e+098.61715e+09ns
HOST_vertex_mass_cube3144441224.885224.867ns
HOST_vertex_mass_spot1726412928412894ns
HOST_vertex_mass_bunny5751.21601e+061.21591e+06ns
HOST_vertex_mass_tyra1803.92145e+063.9211e+06ns
HOST_vertex_normals_cube3981091786.481786.32ns
HOST_vertex_normals_spot3704.03304e+062.02852e+06ns
HOST_vertex_normals_bunny2625.30947e+062.79652e+06ns
HOST_vertex_normals_tyra1181.05496e+076.1032e+06ns
HOST_vertex_triangle_adjacency_cube2093122846.62811.03ns
HOST_vertex_triangle_adjacency_spot1168696248674157ns
HOST_vertex_triangle_adjacency_bunny3082.49694e+062.46715e+06ns
HOST_vertex_triangle_adjacency_tyra868.45339e+068.39191e+06ns
HOST_vertex_vertex_adjacency_cube3728041905.261901.45ns
HOST_vertex_vertex_adjacency_spot1724.13094e+064.12159e+06ns
HOST_vertex_vertex_adjacency_bunny551.28761e+071.28478e+07ns
HOST_vertex_vertex_adjacency_tyra164.38669e+074.37587e+07ns

Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz

nameiterationsreal_timecpu_timetime_unit
HOST_cotangent_laplacian_cube2710312576.692574.4ns
HOST_cotangent_laplacian_spot1036.9254e+066.71974e+06ns
HOST_cotangent_laplacian_bunny421.6869e+071.62396e+07ns
HOST_cotangent_laplacian_tyra125.43377e+075.20702e+07ns
HOST_divergent_edges_cube2617422713.792711.13ns
HOST_divergent_edges_spot907.78507e+067.77556e+06ns
HOST_divergent_edges_bunny342.00418e+072.00014e+07ns
HOST_divergent_edges_tyra79.34462e+079.31885e+07ns
HOST_face_area_cube6017563113.593113.481ns
HOST_face_area_spot2523280010279535ns
HOST_face_area_bunny878805238803422ns
HOST_face_area_tyra2702.583e+062.57878e+06ns
HOST_intrinsic_dirac_cube4765413423.813411.1ns
HOST_intrinsic_dirac_spot106.21724e+076.20564e+07ns
HOST_intrinsic_dirac_bunny15.33196e+085.32015e+08ns
HOST_intrinsic_dirac_tyra11.6005e+091.59639e+09ns
HOST_mean_curvature_cube3933690178.399178.214ns
HOST_mean_curvature_spot1458479014478333ns
HOST_mean_curvature_bunny756978688976697ns
HOST_mean_curvature_tyra1584.39168e+064.38173e+06ns
HOST_orthonormalize_cube2125151330.327329.96ns
HOST_orthonormalize_spot847783593.683457.2ns
HOST_orthonormalize_bunny3731189577189290ns
HOST_orthonormalize_tyra1255565450564393ns
HOST_project_basis_cube5539554128.969128.826ns
HOST_project_basis_spot1819040110.340037.7ns
HOST_project_basis_bunny860584039.383884.8ns
HOST_project_basis_tyra2904233260232939ns
HOST_quaternion_matrix_cube782288942.428933.33ns
HOST_quaternion_matrix_spot292.40185e+072.39839e+07ns
HOST_quaternion_matrix_bunny154.68857e+074.68207e+07ns
HOST_quaternion_matrix_tyra41.63473e+081.63205e+08ns
HOST_similarity_xform_cube3864518438.318414ns
HOST_similarity_xform_spot17.42056e+087.38969e+08ns
HOST_similarity_xform_bunny14.0992e+094.08846e+09ns
HOST_similarity_xform_tyra17.66838e+097.6493e+09ns
HOST_spin_positions_cube4968913943.913925.5ns
HOST_spin_positions_spot15.45467e+085.44281e+08ns
HOST_spin_positions_bunny14.34899e+094.33687e+09ns
HOST_spin_positions_tyra17.72303e+097.69932e+09ns
HOST_vertex_mass_cube4696537150.733150.515ns
HOST_vertex_mass_spot1984345572344981ns
HOST_vertex_mass_bunny730979333977439ns
HOST_vertex_mass_tyra2223.39383e+063.38649e+06ns
HOST_vertex_normals_cube6037131159.481158.22ns
HOST_vertex_normals_spot10185.08585e+061.03785e+06ns
HOST_vertex_normals_bunny4158.53295e+061.69286e+06ns
HOST_vertex_normals_tyra1472.08555e+075.71142e+06ns
HOST_vertex_triangle_adjacency_cube4501501691.171687.71ns
HOST_vertex_triangle_adjacency_spot957672810640440ns
HOST_vertex_triangle_adjacency_bunny3661.85957e+061.8288e+06ns
HOST_vertex_triangle_adjacency_tyra1026.66418e+066.64907e+06ns
HOST_vertex_vertex_adjacency_cube4543051478.191476.08ns
HOST_vertex_vertex_adjacency_spot1833.80413e+063.79766e+06ns
HOST_vertex_vertex_adjacency_bunny621.09962e+071.09678e+07ns
HOST_vertex_vertex_adjacency_tyra193.6735e+073.66314e+07ns

Device

Quadro K2200

nameiterationsreal_timecpu_timetime_unit
DEVICE_adjacency_matrix_indices_cube1859341886340272ns
DEVICE_adjacency_matrix_indices_spot952815516811983ns
DEVICE_adjacency_matrix_indices_bunny64810430001040070ns
DEVICE_adjacency_matrix_indices_tyra30323080402305360ns
DEVICE_cotangent_laplacian_cube2636266425264868ns
DEVICE_cotangent_laplacian_spot1822389558387782ns
DEVICE_cotangent_laplacian_bunny1180592784590895ns
DEVICE_cotangent_laplacian_tyra25027809102779790ns
DEVICE_divergent_edges_cube2755250017248657ns
DEVICE_divergent_edges_spot1829388904386151ns
DEVICE_divergent_edges_bunny1227573293571326ns
DEVICE_divergent_edges_tyra41616614501661310ns
DEVICE_face_area_cube1746404013.784013.42ns
DEVICE_face_area_spot3993118190.518189ns
DEVICE_face_area_bunny1000070404.270398.4ns
DEVICE_face_area_tyra10000228716228697ns
DEVICE_intrinsic_dirac_cube1197355211.155206.5ns
DEVICE_intrinsic_dirac_spot60511575201156930ns
DEVICE_intrinsic_dirac_bunny26527788102767580ns
DEVICE_intrinsic_dirac_tyra581169460011693600ns
DEVICE_quaternion_matrix_cube640051036110360.2ns
DEVICE_quaternion_matrix_spot1619432756432711ns
DEVICE_quaternion_matrix_bunny825844616844546ns
DEVICE_quaternion_matrix_tyra2782.40125e+062.40105e+06ns
DEVICE_similarity_xform_direct_cube4101.65968e+061.65722e+06ns
DEVICE_similarity_xform_direct_spot13.72199e+093.72164e+09ns
DEVICE_similarity_xform_direct_bunny11.48061e+101.48048e+10ns
DEVICE_similarity_xform_direct_tyra19.21995e+109.2191e+10ns
DEVICE_similarity_xform_iterative_cube3541.97964e+061.97947e+06ns
DEVICE_similarity_xform_iterative_spot18.37923e+088.35415e+08ns
DEVICE_similarity_xform_iterative_bunny11.60284e+091.59807e+09ns
DEVICE_similarity_xform_iterative_tyra14.12793e+094.12634e+09ns
DEVICE_spin_positions_direct_spot13.68252e+093.68218e+09ns
DEVICE_spin_positions_direct_bunny11.4813e+101.48117e+10ns
DEVICE_spin_positions_direct_tyra19.26087e+109.25993e+10ns
DEVICE_spin_positions_iterative_spot13.20449e+093.19223e+09ns
DEVICE_spin_positions_iterative_bunny12.43237e+102.42893e+10ns
DEVICE_spin_positions_iterative_tyra14.5125e+104.5104e+10ns
DEVICE_vertex_mass_cube1850382962381300ns
DEVICE_vertex_mass_spot774918686914777ns
DEVICE_vertex_mass_bunny715985748981242ns
DEVICE_vertex_mass_tyra5501.27652e+061.27083e+06ns
DEVICE_vertex_triangle_adjacency_cube756483150.483142.5ns
DEVICE_vertex_triangle_adjacency_spot975645653644782ns
DEVICE_vertex_triangle_adjacency_bunny687951790950354ns
DEVICE_vertex_triangle_adjacency_tyra3412.00665e+062.00445e+06ns
DEVICE_vertex_vertex_adjacency_cube1754402834401607ns
DEVICE_vertex_vertex_adjacency_spot3282.11945e+062.11526e+06ns
DEVICE_vertex_vertex_adjacency_bunny2053.42353e+063.41836e+06ns
DEVICE_vertex_vertex_adjacency_tyra788.39362e+068.38729e+06ns

Quadro M3000M

nameiterationsreal_timecpu_timetime_unit
DEVICE_adjacency_matrix_indices_cube78847908578952.7ns
DEVICE_adjacency_matrix_indices_spot3765186365186057ns
DEVICE_adjacency_matrix_indices_bunny1981354997354429ns
DEVICE_adjacency_matrix_indices_tyra6981.00454e+061.00289e+06ns
DEVICE_cotangent_laplacian_cube1902836727.936673.4ns
DEVICE_cotangent_laplacian_spot73189592795779ns
DEVICE_cotangent_laplacian_bunny3788185237184952ns
DEVICE_cotangent_laplacian_tyra6391.09654e+061.09491e+06ns
DEVICE_divergent_edges_cube2275831024.430975ns
DEVICE_divergent_edges_spot5095137633137418ns
DEVICE_divergent_edges_bunny2589270829270412ns
DEVICE_divergent_edges_tyra714882609881253ns
DEVICE_face_area_cube2425752905.482902.24ns
DEVICE_face_area_spot4365817166.917149ns
DEVICE_face_area_bunny1000059671.959607.6ns
DEVICE_face_area_tyra10000167694167522ns
DEVICE_intrinsic_dirac_cube1272048998.348921.5ns
DEVICE_intrinsic_dirac_spot1224571206570304ns
DEVICE_intrinsic_dirac_bunny5461.28079e+061.27895e+06ns
DEVICE_intrinsic_dirac_tyra1155.41181e+065.40575e+06ns
DEVICE_quaternion_matrix_cube739658841.748831.27ns
DEVICE_quaternion_matrix_spot2955237677237398ns
DEVICE_quaternion_matrix_bunny1530458183457665ns
DEVICE_quaternion_matrix_tyra5401.29391e+061.29254e+06ns
DEVICE_similarity_xform_direct_cube5851.18767e+061.18497e+06ns
DEVICE_similarity_xform_direct_spot18.58772e+088.57566e+08ns
DEVICE_similarity_xform_direct_bunny12.73471e+092.7312e+09ns
DEVICE_similarity_xform_direct_tyra17.14028e+097.13095e+09ns
DEVICE_similarity_xform_iterative_cube4221.63953e+061.63654e+06ns
DEVICE_similarity_xform_iterative_spot32.18979e+082.18621e+08ns
DEVICE_similarity_xform_iterative_bunny17.19855e+087.17361e+08ns
DEVICE_similarity_xform_iterative_tyra12.08458e+092.07955e+09ns
DEVICE_spin_positions_direct_spot18.20441e+088.1929e+08ns
DEVICE_spin_positions_direct_bunny12.71461e+092.71119e+09ns
DEVICE_spin_positions_direct_tyra16.88368e+096.87474e+09ns
DEVICE_spin_positions_iterative_spot11.39873e+091.39645e+09ns
DEVICE_spin_positions_iterative_bunny11.09265e+101.08924e+10ns
DEVICE_spin_positions_iterative_tyra12.23644e+102.23161e+10ns
DEVICE_vertex_mass_cube901772878.772766.8ns
DEVICE_vertex_mass_spot2724256301255283ns
DEVICE_vertex_mass_bunny2391294982293866ns
DEVICE_vertex_mass_tyra1370512385510719ns
DEVICE_vertex_triangle_adjacency_cube936468021.967925ns
DEVICE_vertex_triangle_adjacency_spot1339470203468639ns
DEVICE_vertex_triangle_adjacency_bunny1007647694645818ns
DEVICE_vertex_triangle_adjacency_tyra5081.35423e+061.35101e+06ns
DEVICE_vertex_vertex_adjacency_cube4683146918146687ns
DEVICE_vertex_vertex_adjacency_spot5011.36479e+061.36066e+06ns
DEVICE_vertex_vertex_adjacency_bunny3382.0472e+062.04122e+06ns
DEVICE_vertex_vertex_adjacency_tyra1324.88977e+064.88117e+06ns

About

Implementation of Conformal curvature flow using spin transformations, to achieve a spherical embedding

Topics

Resources

Stars

23 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages