├── .gitignore ├── .gitmodules ├── 3rdparty └── StripePatterns │ ├── CMakeLists.txt │ ├── LICENSE.txt │ ├── README.txt │ ├── include │ ├── Complex.h │ ├── DenseMatrix.h │ ├── DenseMatrix.inl │ ├── Edge.h │ ├── Face.h │ ├── HalfEdge.h │ ├── LinearContext.h │ ├── LinearEquation.h │ ├── LinearPolynomial.h │ ├── LinearSystem.h │ ├── Mesh.h │ ├── MeshIO.h │ ├── Quaternion.h │ ├── Real.h │ ├── SparseMatrix.h │ ├── SparseMatrix.inl │ ├── Types.h │ ├── Utility.h │ ├── Variable.h │ ├── Vector.h │ └── Vertex.h │ └── src │ ├── Complex.cpp │ ├── DenseMatrix.cpp │ ├── Edge.cpp │ ├── Face.cpp │ ├── HalfEdge.cpp │ ├── LinearContext.cpp │ ├── LinearEquation.cpp │ ├── LinearPolynomial.cpp │ ├── LinearSystem.cpp │ ├── Mesh.cpp │ ├── MeshIO.cpp │ ├── Quaternion.cpp │ ├── Real.cpp │ ├── SparseMatrix.cpp │ ├── Variable.cpp │ ├── Vector.cpp │ └── Vertex.cpp ├── BendingEnergy.hh ├── CMakeLists.txt ├── CollapseBarrier.hh ├── CollapsePreventionEnergy.hh ├── CompressionPenalty.hh ├── DualLaplacianStencil.cc ├── DualLaplacianStencil.hh ├── DualMesh.hh ├── EigSensitivity.hh ├── FusingCurveSmoothness.hh ├── IncompressibleBalloonEnergy.hh ├── IncompressibleBalloonEnergyWithHessProjection.hh ├── InflatableSheet.cc ├── InflatableSheet.hh ├── InflatedSurfaceAnalysis.cc ├── InflatedSurfaceAnalysis.hh ├── LICENSE ├── MetricFitter.cc ├── MetricFitter.hh ├── MetricFittingEnergy.hh ├── Nondimensionalization.hh ├── README.md ├── ReducedSheetOptimizer.hh ├── SVDSensitivity.hh ├── SheetOptimizer.cc ├── SheetOptimizer.hh ├── SurfaceSampler.cc ├── SurfaceSampler.hh ├── TargetAttractedInflation.cc ├── TargetAttractedInflation.hh ├── TargetSurfaceFitter.cc ├── TargetSurfaceFitter.hh ├── TensionFieldEnergy.hh ├── TubeRemesh.hh ├── WallPositionInterpolator.hh ├── circular_mean.hh ├── cmake ├── FindKnitro.cmake ├── FindLIBIGL.cmake ├── FindMPFR.cmake └── UseColors.cmake ├── curvature.cc ├── curvature.hh ├── examples ├── full_sphere.msh ├── half_sphere.msh ├── julius.msh ├── lilium.msh ├── octagon.obj ├── paraboloid.hh └── single_tri.obj ├── fit_metric_newton.cc ├── fit_metric_newton.hh ├── inflation_newton.cc ├── inflation_newton.hh ├── parametrization.cc ├── parametrization.hh ├── parametrization_newton.cc ├── parametrization_newton.hh ├── python ├── Demos │ ├── ConcentricCircles.ipynb │ ├── ForwardDesign.ipynb │ ├── Lilium.ipynb │ ├── LogSpiral.ipynb │ └── data │ │ └── ForwardDesign │ │ ├── example.obj │ │ ├── example_fusedPts.txt │ │ └── example_holePts.txt ├── boundaries.py ├── fabrication.py ├── fd_validation.py ├── hpc_optimization_job.py ├── inflation_pressure_analysis.py ├── io_redirection.py ├── measurements.py ├── opt_config.py ├── optimize_sheet_cli.py ├── parametric_pillows.py ├── register_inflation_frames.py ├── reinflate.py ├── remeshing_utils.py ├── sheet_meshing.py ├── sheet_optimizer.py ├── utils.py ├── visualization.py ├── wall_width_formulas.py └── write_line_mesh.py ├── python_bindings ├── CMakeLists.txt ├── extended_precision.hh ├── inflation.cc ├── mesh_utilities.cc ├── metric_fit.cc ├── parametrization.cc └── wall_generation.cc ├── subdivide_triangle.hh ├── tests ├── CMakeLists.txt ├── catch2_main.cc ├── catch2_svd_tests.cc ├── catch2_tf_validation.cc ├── test_balloon_energy.cc ├── test_eig_sensitivity.cc ├── test_newton.cc ├── test_subdivide.cc ├── test_svd_sensitivity.cc └── test_tension_field_energy.cc └── wall_generation ├── CMakeLists.txt ├── evaluate_stripe_field.cc ├── evaluate_stripe_field.hh ├── extract_contours.cc └── extract_contours.hh /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | build_* 3 | *.cpython-*.so 4 | .ipynb_checkpoints 5 | __pycache__ 6 | /python/MeshFEM 7 | .DS_Store 8 | .nfs* 9 | results* 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/libigl"] 2 | path = 3rdparty/libigl 3 | url = https://github.com/jpanetta/libigl 4 | [submodule "3rdparty/MeshFEM"] 5 | path = 3rdparty/MeshFEM 6 | url = https://github.com/MeshFEM/MeshFEM 7 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(StripePatterns 2 | src/Complex.cpp 3 | src/DenseMatrix.cpp 4 | src/Edge.cpp 5 | src/Face.cpp 6 | src/HalfEdge.cpp 7 | src/LinearContext.cpp 8 | src/LinearEquation.cpp 9 | src/LinearPolynomial.cpp 10 | src/LinearSystem.cpp 11 | src/Mesh.cpp 12 | src/MeshIO.cpp 13 | src/Quaternion.cpp 14 | src/Real.cpp 15 | src/SparseMatrix.cpp 16 | src/Variable.cpp 17 | src/Vector.cpp 18 | src/Vertex.cpp 19 | ) 20 | target_link_libraries(StripePatterns MeshFEM) 21 | set_target_properties (StripePatterns PROPERTIES CXX_STANDARD 14) 22 | set_target_properties (StripePatterns PROPERTIES CXX_STANDARD_REQUIRED ON) 23 | target_include_directories(StripePatterns PUBLIC include) 24 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Keenan Crane 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | The views and conclusions contained in the software and documentation are those 25 | of the authors and should not be interpreted as representing official policies, 26 | either expressed or implied, of the FreeBSD Project. 27 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/README.txt: -------------------------------------------------------------------------------- 1 | Note: this directory contains a stripped-down version of Keenan's code from: 2 | https://www.cs.cmu.edu/~kmcrane/Projects/StripePatterns/ 3 | 4 | StripePatterns - Keenan Crane (2015) 5 | ------------------------------------ 6 | 7 | This code implements the algorithm described in 8 | 9 | Knöppel, Crane, Pinkall, Schröder, "Stripe Patterns on Surfaces" (ACM Transactions on Graphics 2015) 10 | 11 | It is not particularly well-written, well-documented, or well-optimized, but it does the right thing. 12 | The basic function of this program is to compute a pattern of evenly-spaced stripes aligned with a 13 | given direction field. In principal the direction field could be computed using any method, but for 14 | convenience the code implements a couple techniques that make it easy to generate a nice direction 15 | field on pretty much any surface. In particular, it implements variants of the algorithms described in 16 | 17 | 1. Knöppel, Crane, Pinkall, Schröder, "Globally Optimal Direction Fields" (ACM Transactions on Graphics 2013) 18 | 2. Crane, Desbrun, and Schröder, "Trivial Connections on Discrete Surfaces" (SGP 2010) 19 | 20 | The first algorithm is used to initialize the field with either (a) the smoothest possible line field on 21 | the surface or (b) a field aligned with principal curvature directions. The second method allows one to 22 | manually change the placement of singularities in the initial field, if desired. The stripe pattern is 23 | updated interactively with changes in the field. 24 | 25 | 26 | BUILDING 27 | ====================================================================================================== 28 | 29 | The code has two dependencies: 30 | 31 | 1. the SuiteSparse library for numerical linear algebra (http://faculty.cse.tamu.edu/davis/suitesparse.html), and 32 | 2. OpenGL/GLUT. 33 | 34 | Both of these libraries are quite standard, but building and linking them can vary quite a bit on different platforms. The current Makefile assumes that the platform is Mac OS X, and that SuiteSparse has been installed via Macports. But modifying the Makefile for other platforms should not be difficult, and there are some (commented out) lines that may be helpful for Linux or Windows/Cygwin. Alternatively, if you want to use your own linear algebra library (like Eigen), you can simply change the implementation of methods like solvePositiveDefinite() in DDG::SparseMatrix and DDG::DenseMatrix, which currently serve as wrappers around SuiteSparse. OpenGL and GLUT should be available by default on many platforms. 35 | 36 | Once the dependencies have been installed/built, the code can be built by simply typing 37 | 38 | ./make 39 | 40 | in the root directory. The result will be an executable called “stripe” (or possibly “stripe.exe” on Windows). 41 | 42 | 43 | RUNNING 44 | ====================================================================================================== 45 | 46 | To run the code from the command line, type 47 | 48 | ./stripe input.obj 49 | 50 | where “input.obj” is a path to a Wavefront OBJ file. The mesh must be connected and manifold, possibly with boundary. You will then be presented with a window displaying your mesh; hitting the spacebar will automatically generate a stripe pattern. Further commands are listed below. 51 | 52 | 53 | 54 | 55 | USER INTERFACE 56 | ====================================================================================================== 57 | 58 | SPACE - compute a stripe pattern aligned with the globally smoothest direction field 59 | c - compute a stripe pattern aligned with the minimum principal curvature direction 60 | d - toggle display of input direction field 61 | s - set draw mode to smooth shaded 62 | f - set draw mode to wireframe 63 | w - write mesh to the file "out.obj"; the stripe pattern is stored in the texture coordinates 64 | 1 - compute just a 1D stripe pattern 65 | 2 - compute two orthogonal coordinates to get a 2D parameterization (not visualized, but will be saved to disk) 66 | e - edit the input direction field 67 | * - toggle singularities 68 | - - decrease stripe frequency 69 | + - increase stripe frequency 70 | ( - rotate field left 71 | ) - rotate field right 72 | TAB - animate rotating stripe pattern 73 | r - reload the input mesh from disk 74 | \ - write a screenshot to the frames/ directory 75 | ESC - exit 76 | 77 | ALT-CLICK: in edit mode, alt-clicking on a pair of triangles will adjust the singularities; in particular: 78 | --clicking on a pair of nonsingular triangles will create an equal and opposite pair 79 | --clicking on an equal and opposite pair will remove both singularities 80 | --clicking on a singularity and a nonsingular triangle will move the singularity 81 | 82 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Complex.h: -------------------------------------------------------------------------------- 1 | #ifndef DDG_COMPLEX_H 2 | #define DDG_COMPLEX_H 3 | 4 | #include 5 | 6 | namespace DDG 7 | { 8 | class Complex 9 | { 10 | public: 11 | Complex( double a=0., double b=0. ); 12 | // constructs number a+bi 13 | 14 | double& operator[]( int p ); 15 | // returns reference to the pth component (0=real, 1=imaginary) 16 | 17 | const double& operator[]( int p ) const; 18 | // returns const reference to the pth component (0=real, 1=imaginary) 19 | 20 | void operator+=( const Complex& z ); 21 | // add z 22 | 23 | void operator-=( const Complex& z ); 24 | // subtract z 25 | 26 | void operator*=( const Complex& z ); 27 | // Complex multiply by z 28 | 29 | void operator*=( double r ); 30 | // scalar multiply by r 31 | 32 | void operator/=( double r ); 33 | // scalar divide by r 34 | 35 | void operator/=( const Complex& z ); 36 | // complex divide by r 37 | 38 | Complex operator-( void ) const; 39 | // returns the additive inverse 40 | 41 | Complex bar( void ) const; 42 | // returns Complex conjugate 43 | 44 | Complex inv( void ) const; 45 | // returns inverse 46 | 47 | double arg( void ) const; 48 | // returns argument 49 | 50 | double norm( void ) const; 51 | // returns norm 52 | 53 | double norm2( void ) const; 54 | // returns norm squared 55 | 56 | Complex unit( void ) const; 57 | // returns complex number with unit norm and same modulus 58 | 59 | void normalize( void ); 60 | // divides by norm 61 | 62 | Complex exp( void ) const; 63 | // returns e^z 64 | 65 | Complex root( double k, double n ) const; 66 | // returns the ith kth root 67 | 68 | double re; 69 | // real part 70 | 71 | double im; 72 | // imaginary part 73 | }; 74 | 75 | Complex operator+( const Complex& z1, const Complex& z2 ); 76 | // binary addition 77 | 78 | Complex operator-( const Complex& z1, const Complex& z2 ); 79 | // binary subtraction 80 | 81 | Complex operator*( const Complex& z1, const Complex& z2 ); 82 | // binary Complex multiplication 83 | 84 | Complex operator*( const Complex& z, double r ); 85 | // right scalar multiplication 86 | 87 | Complex operator*( double r, const Complex& z ); 88 | // left scalar multiplication 89 | 90 | Complex operator/( const Complex& z, double r ); 91 | // scalar division 92 | 93 | Complex operator/( const Complex& z1, const Complex& z2 ); 94 | // complex division 95 | 96 | double dot( const Complex& z1, const Complex& z2 ); 97 | // inner product 98 | 99 | double cross( const Complex& z1, const Complex& z2 ); 100 | // cross product 101 | 102 | std::ostream& operator<<( std::ostream& os, const Complex& o ); 103 | // prints components 104 | } 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Edge.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- Edge.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // Edge stores attributes associated with a mesh edge. The iterator he points 6 | // to one of its two associated halfedges. (See the documentation for a more 7 | // in-depth discussion of the halfedge data structure.) 8 | // 9 | 10 | #ifndef DDG_EDGE_H 11 | #define DDG_EDGE_H 12 | 13 | #include "Types.h" 14 | 15 | namespace DDG 16 | { 17 | class Edge 18 | { 19 | public: 20 | HalfEdgeIter he; 21 | // points to one of the two halfedges associated with this edge 22 | 23 | double length( void ) const; 24 | // returns the edge length 25 | 26 | double dihedralAngle( void ) const; 27 | // returns signed dihedral angle 28 | 29 | int index; 30 | // unique ID in the range [0,nE-1] 31 | 32 | double omega; 33 | // 1-form guiding parameterization 34 | 35 | bool crossesSheets; 36 | // whether the target coordinate is conjugated 37 | }; 38 | } 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Face.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- Face.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // Face stores attributes associated with a mesh edge. The iterator he points 6 | // to one of its associated halfedges. (See the documentation for a more 7 | // in-depth discussion of the halfedge data structure.) 8 | // 9 | 10 | #ifndef DDG_FACE_H 11 | #define DDG_FACE_H 12 | 13 | #include "Types.h" 14 | #include 15 | 16 | namespace DDG 17 | { 18 | class Face 19 | { 20 | public: 21 | HalfEdgeIter he; 22 | // points to one of the halfedges associated with this face 23 | 24 | bool visited; 25 | // flag for traversal 26 | 27 | bool isBoundary( void ) const; 28 | // returns true if this face corresponds to a 29 | // boundary loop; false otherwise 30 | 31 | Vector normal( void ) const; 32 | // returns the unit normal associated with this face; normal 33 | // orientation is determined by the circulation order of halfedges 34 | 35 | double curvature( void ) const; 36 | // returns the intrinsic curvature, integrated over this triangle 37 | 38 | double fieldIndex( double fieldDegree ) const; 39 | // returns the degree of the direction field around this triangle 40 | 41 | int index; 42 | // unique ID in range [0,nF-1] 43 | 44 | double singularIndex; 45 | // index for trivial connection 46 | 47 | double paramIndex[2]; 48 | // degree of the parameterization around this triangle, for each 49 | // coorinate function (computed by Mesh::assignTextureCoordinates) 50 | 51 | double area( void ) const; 52 | // returns the triangle area 53 | 54 | Vector barycenter( void ) const; 55 | // returns arithmetic mean of vertex coordinates 56 | 57 | void orientTexCoords( void ); 58 | // flip texture coordinates if they are negatively oriented 59 | 60 | void getLocalSheet( std::vector& psi, std::vector& omega ); 61 | // returns three parameter values psi and transport coefficients 62 | // consistent with the canonical sheet at f->he->vertex 63 | }; 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/HalfEdge.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- HalfEdge.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // HalfEdge is used to define mesh connectivity. (See the documentation for a 6 | // more in-depth discussion of the halfedge data structure.) 7 | // 8 | 9 | #ifndef DDG_HALFEDGE_H 10 | #define DDG_HALFEDGE_H 11 | 12 | #include "Vector.h" 13 | #include "Types.h" 14 | #include "Complex.h" 15 | #include "Utility.h" 16 | 17 | namespace DDG 18 | { 19 | class HalfEdge 20 | { 21 | public: 22 | HalfEdgeIter next; 23 | // points to the next halfedge around the current face 24 | 25 | HalfEdgeIter flip; 26 | // points to the other halfedge associated with this edge 27 | 28 | VertexIter vertex; 29 | // points to the vertex at the "tail" of this halfedge 30 | 31 | EdgeIter edge; 32 | // points to the edge associated with this halfedge 33 | 34 | FaceIter face; 35 | // points to the face containing this halfedge 36 | 37 | HalfEdgeIter self( void ); 38 | HalfEdgeCIter self( void ) const; 39 | // returns an iterator to this halfedge 40 | 41 | bool isCanonical( void ) const; 42 | // returns true iff this half edge is the canonical halfedge for its associated edge 43 | 44 | bool crossesSheets( void ) const; 45 | // returns true iff the canonical vectors at the two endpoints have opposite sign 46 | 47 | bool onBoundary; 48 | // true if this halfedge is contained in a boundary 49 | // loop; false otherwise 50 | 51 | double height; 52 | // used to define embedding 53 | 54 | Complex texcoord; 55 | #ifdef SP_FLAT_TORUS 56 | Complex origTexCoord; 57 | #endif 58 | // texture coordinates associated with the triangle corner at the 59 | // "tail" of this halfedge 60 | 61 | double angularCoordinate; 62 | // angle of this half edge relative to this->vertex->he, 63 | // normalized by 2π times the angle sum around this->vertex 64 | 65 | Complex connectionCoefficient; 66 | // apples *half* the rotation from this->vertex to this->flip->vertex 67 | 68 | Vector vector( void ) const; 69 | // returns the vector along this halfedge 70 | 71 | double angle( void ) const; 72 | // returns the interior angle of the opposite corner 73 | 74 | double cotan( void ) const; 75 | // returns the cotangent of the opposite angle 76 | 77 | double omega( void ) const; 78 | // returns the (properly-oriented) value of the 1-form omega 79 | 80 | void updateTexCoord( int coordinate ); 81 | void updateTexCoordFromIToJ( int coordinate, HalfEdgeCIter hij ); 82 | // methods for computing angular texture coordinates from complex texture coordinates 83 | }; 84 | } 85 | 86 | #endif 87 | 88 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/LinearContext.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- LinearContext.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // LinearContext is the global solver context needed to interface with the 6 | // SuiteSparse library. It is essentially a wrapper around cholmod_common. A 7 | // single static instance of LinearContext is declared in LinearContext.cpp and 8 | // is shared by all instances of DenseMatrix, SparseMatrix, and LinearSystem. 9 | // In other words, you shouldn't have to instantiate LinearContext yourself 10 | // unless you're doing something really fancy! 11 | // 12 | 13 | #ifndef DDG_LINEARSOLVERCONTEXT 14 | #define DDG_LINEARSOLVERCONTEXT 15 | 16 | #include 17 | 18 | namespace DDG 19 | { 20 | class LinearContext 21 | { 22 | public: 23 | LinearContext( void ); 24 | // constructor 25 | 26 | ~LinearContext( void ); 27 | // destructor 28 | 29 | operator cholmod_common*( void ); 30 | // allows LinearContext to be treated as a cholmod_common* 31 | 32 | protected: 33 | cholmod_common context; 34 | }; 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/LinearEquation.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- LinearEquation.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // LinearEquation represents an equation with an arbitrary linear polynomial on 6 | // both the left- and right-hand side. It is primarily used while building a 7 | // LinearSystem. For convenience, operator== is overloaded so that the user 8 | // can construct a LinearEquation by writing something that looks much like the 9 | // usual mathematical syntax for a linear equation. For example, 10 | // 11 | // LinearEquation eqn = ( x + 2*y == 3*z ); 12 | // 13 | // builds the linear equation x + 2y = 3z. 14 | // 15 | 16 | #ifndef DDG_LINEAREQUATION_H 17 | #define DDG_LINEAREQUATION_H 18 | 19 | #include "LinearPolynomial.h" 20 | 21 | namespace DDG 22 | { 23 | class LinearEquation 24 | { 25 | public: 26 | LinearPolynomial lhs; 27 | // left-hand side 28 | 29 | LinearPolynomial rhs; 30 | // right-hand side 31 | }; 32 | 33 | LinearEquation operator==( const LinearPolynomial& lhs, const LinearPolynomial& rhs ); 34 | // constructs a linear equation with the specified left- and right-hand side 35 | } 36 | 37 | #endif 38 | 39 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/LinearPolynomial.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- LinearPolynomial.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // LinearPolynomial represents an affine function of the form 6 | // 7 | // f(x1,x2,...,xn) = c1 x1 + c2 x2 + ... + cn xn + d 8 | // 9 | // where the xi are real-valued variables with real coefficients ci, and d is 10 | // a real constant. The variables and their coefficients are represented using 11 | // instances of the Variable class. LinearPolynomial implements all the usual 12 | // algebraic operations on affine functions, as well as type conversions from 13 | // more elementary types (scalars, single variables, etc.). 14 | // 15 | // Importantly, variables used in a LinearPolynomial should *not* be deallocated 16 | // while the polynomial is still in use -- LinearPolynomial stores only a 17 | // reference to these variables so that the solution to a linear system can be 18 | // automatically copied back into the variables. 19 | // 20 | 21 | #ifndef DDG_LINEARPOLYNOMIAL_H 22 | #define DDG_LINEARPOLYNOMIAL_H 23 | 24 | #include 25 | #include 26 | #include "Variable.h" 27 | 28 | namespace DDG 29 | { 30 | class LinearPolynomial 31 | { 32 | public: 33 | LinearPolynomial( void ); 34 | // constructs the zero function 35 | 36 | LinearPolynomial( double c ); 37 | // constructs the constant function with value c 38 | 39 | LinearPolynomial( Variable& v ); 40 | // constructs a function with a single variable v 41 | 42 | const LinearPolynomial& operator=( double c ); 43 | // assigns the constant function with value c 44 | 45 | const LinearPolynomial& operator=( Variable& v ); 46 | // assigns a function with a single variable v 47 | 48 | void operator+=( double c ); 49 | void operator-=( double c ); 50 | void operator*=( double c ); 51 | void operator/=( double c ); 52 | // adds, subtract, multiplies, or divides by a constant 53 | 54 | void operator+=( Variable& v ); 55 | void operator-=( Variable& v ); 56 | // increments or decrements by a single variable v 57 | 58 | void operator+=( const LinearPolynomial& p ); 59 | void operator-=( const LinearPolynomial& p ); 60 | // increments or decrements by an affine function 61 | 62 | LinearPolynomial operator-( void ) const; 63 | // returns the additive inverse (i.e., negation) 64 | 65 | double evaluate( void ) const; 66 | // evaluates the function using the current values of its variables 67 | 68 | std::map linearTerms; 69 | // list of linear terms 70 | 71 | double constantTerm; 72 | // constant term 73 | }; 74 | 75 | LinearPolynomial operator+( double c, Variable& v ); // sum 76 | LinearPolynomial operator+( Variable& v, double c ); // sum 77 | LinearPolynomial operator-( double c, Variable& v ); // difference 78 | LinearPolynomial operator-( Variable& v, double c ); // difference 79 | LinearPolynomial operator*( double c, Variable& v ); // product 80 | LinearPolynomial operator*( Variable& v, double c ); // product 81 | LinearPolynomial operator/( Variable& v, double c ); // quotient 82 | // algebraic operations between single variables and constants 83 | 84 | LinearPolynomial operator+( Variable& v1, Variable& v2 ); // sum 85 | LinearPolynomial operator-( Variable& v1, Variable& v2 ); // difference 86 | // algebraic operations between pairs of variables 87 | 88 | LinearPolynomial operator+( const LinearPolynomial& p, double c ); // sum 89 | LinearPolynomial operator+( double c, const LinearPolynomial& p ); // sum 90 | LinearPolynomial operator-( const LinearPolynomial& p, double c ); // difference 91 | LinearPolynomial operator-( double c, const LinearPolynomial& p ); // difference 92 | LinearPolynomial operator*( const LinearPolynomial& p, double c ); // product 93 | LinearPolynomial operator*( double c, const LinearPolynomial& p ); // product 94 | LinearPolynomial operator/( const LinearPolynomial& p, double c ); // quotient 95 | // algebraic operations between polynomials and constants 96 | 97 | LinearPolynomial operator+( const LinearPolynomial& p, Variable& v ); // sum 98 | LinearPolynomial operator+( Variable& v, const LinearPolynomial& p ); // sum 99 | LinearPolynomial operator-( const LinearPolynomial& p, Variable& v ); // difference 100 | LinearPolynomial operator-( Variable& v, const LinearPolynomial& p ); // difference 101 | // algebraic operations between polynomials and single variables 102 | 103 | LinearPolynomial operator+( const LinearPolynomial& p, const LinearPolynomial& q ); // sum 104 | LinearPolynomial operator-( const LinearPolynomial& p, const LinearPolynomial& q ); // difference 105 | // algebraic operations between pairs of polynomials 106 | 107 | std::ostream& operator<<( std::ostream& os, const LinearPolynomial& p ); 108 | // prints the symbolic representation of a polynomial (all variables must be named) 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/LinearSystem.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- LinearSystem.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // LinearSystem represents a system of linear equations expressed in terms of 6 | // instances of the Variable class. The main idea is to make it easy to 7 | // construct and solve linear systems without explicitly think about variable 8 | // indices, matrix layout, etc. (This kind of abstraction is particularly 9 | // useful for debugging and rapid prototyping.) See the documentation for 10 | // examples of building linear and solving systems. 11 | // 12 | // Importantly, any variable used by a LinearSystem should not be deallocated 13 | // while the system is still in use, because the method LinearSystem::solve() 14 | // automatically copies the solution back into the variables used to define the 15 | // equations. (In the future variables may become reference-counted in order 16 | // to avoid this issue.) 17 | // 18 | // Note that LinearSystem::solve() uses a general-purpose linear solver (namely 19 | // the sparse QR factorization found in SuiteSparse) that is quite fast but may 20 | // not always be your best option. To improve performance you may want to 21 | // build the system explicitly using an instance of SparseMatrix and call a 22 | // more specialized solver. (In the future there may be options for specifying 23 | // that a LinearSystem is, e.g., symmetric and positive-definite.) 24 | // 25 | 26 | #ifndef DDG_LINEARSYSTEM_H 27 | #define DDG_LINEARSYSTEM_H 28 | 29 | #include 30 | #include "LinearEquation.h" 31 | #include "SparseMatrix.h" 32 | #include "DenseMatrix.h" 33 | #include "Real.h" 34 | 35 | namespace DDG 36 | { 37 | class LinearSystem 38 | { 39 | public: 40 | void clear( void ); 41 | // removes all equations from the system 42 | 43 | void push_back( const LinearEquation& e ); 44 | // appends the equation e to the sytem 45 | 46 | void solve( void ); 47 | // solves the system and automatically stores the result in the variables 48 | // for an overdetermined system, computes a least-squares solution 49 | 50 | std::vector equations; 51 | // the collection of equations defining the system 52 | 53 | protected: 54 | void convertEquations( void ); 55 | void indexVariables( void ); 56 | void buildSparseMatrix( void ); 57 | void buildRightHandSide( void ); 58 | void computeSolution( void ); 59 | 60 | int nEquations; 61 | int nVariables; 62 | std::vector currentEquations; 63 | std::map index; 64 | SparseMatrix A; 65 | DenseMatrix x; 66 | DenseMatrix b; 67 | }; 68 | } 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/MeshIO.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- MeshIO.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // MeshIO handles input/output operations for Mesh objects. Currently the only 6 | // supported mesh format is Wavefront OBJ -- for a format specification see 7 | // 8 | // http://en.wikipedia.org/wiki/Wavefront_.obj_file 9 | // 10 | // Note that vertex normals and material properties are currently ignored. 11 | // 12 | 13 | #ifndef DDG_MESHIO_H 14 | #define DDG_MESHIO_H 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "Vector.h" 21 | #include "Complex.h" 22 | 23 | namespace DDG 24 | { 25 | class Mesh; 26 | 27 | struct Index { 28 | Index( void ) 29 | {} 30 | 31 | Index( int p, int t, int n ) 32 | : position( p ), texcoord( t ), normal( n ) 33 | {} 34 | 35 | bool operator<( const Index& i ) const 36 | { 37 | if( position < i.position ) return true; 38 | if( position > i.position ) return false; 39 | if( texcoord < i.texcoord ) return true; 40 | if( texcoord > i.texcoord ) return false; 41 | if( normal < i.normal ) return true; 42 | if( normal > i.normal ) return false; 43 | return false; 44 | } 45 | 46 | int position; 47 | int texcoord; 48 | int normal; 49 | }; 50 | 51 | struct MeshData { 52 | std::vector positions; 53 | std::vector texcoords; 54 | std::vector normals; 55 | std::vector tangents; 56 | std::vector< std::vector< Index > > indices; 57 | }; 58 | 59 | 60 | class MeshIO 61 | { 62 | public: 63 | static int read( std::istream& in, Mesh& mesh ); 64 | // reads a mesh from a valid, open input stream in 65 | 66 | // Build a mesh from a MeshData object 67 | static int buildMesh( const MeshData& data, Mesh& mesh ); 68 | 69 | static void write( std::ostream& out, const Mesh& mesh ); 70 | // writes a mesh to a valid, open output stream out 71 | 72 | protected: 73 | static int readMeshData( std::istream& in, MeshData& data ); 74 | static void readPosition( std::stringstream& ss, MeshData& data ); 75 | static void readTexCoord( std::stringstream& ss, MeshData& data ); 76 | static void readNormal ( std::stringstream& ss, MeshData& data ); 77 | static void readTangent ( std::stringstream& ss, MeshData& data ); 78 | static void readFace ( std::stringstream& ss, MeshData& data ); 79 | static Index parseFaceIndex( const std::string& token ); 80 | static void preallocateMeshElements( const MeshData& data, Mesh& mesh ); 81 | static void checkIsolatedVertices( const Mesh& Mesh ); 82 | static void checkNonManifoldVertices( const Mesh& Mesh ); 83 | }; 84 | } 85 | 86 | #endif 87 | 88 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Quaternion.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- Quaternion.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // Quaternion represents an element of the quaternions, along with all the usual 6 | // vectors space operations (addition, multiplication by scalars, etc.). The 7 | // Hamilton product is expressed using the * operator: 8 | // 9 | // Quaternion p, q, r; 10 | // r = q * p; 11 | // 12 | // and conjugation is expressed using the method Quaternion::bar(): 13 | // 14 | // Quaternion q; 15 | // double normQSquared = -q.bar()*q; 16 | // 17 | // Individual components can be accessed in several ways: the real and imaginary 18 | // parts can be accessed using the methods Quaternion::re() and Quaternion::im(): 19 | // 20 | // Quaternion q; 21 | // double a = q.re(); 22 | // Vector b = q.im(); 23 | // 24 | // or by index: 25 | // 26 | // Quaternion q; 27 | // double a = q[0]; 28 | // double bi = q[1]; 29 | // double bj = q[2]; 30 | // double bk = q[3]; 31 | // 32 | 33 | #ifndef DDG_QUATERNION_H 34 | #define DDG_QUATERNION_H 35 | 36 | #include "Vector.h" 37 | #include "Complex.h" 38 | #include 39 | 40 | namespace DDG 41 | { 42 | class Quaternion 43 | { 44 | public: 45 | Quaternion( void ); 46 | // initializes all components to zero 47 | 48 | Quaternion( const Quaternion& q ); 49 | // initializes from existing quaternion 50 | 51 | Quaternion( double s, double vi, double vj, double vk ); 52 | // initializes with specified real (s) and imaginary (v) components 53 | 54 | Quaternion( double s, const Vector& v ); 55 | // initializes with specified real (s) and imaginary (v) components 56 | 57 | Quaternion( double s ); 58 | // initializes purely real quaternion with specified real (s) component (imaginary part is zero) 59 | 60 | Quaternion( const Vector& v ); 61 | // initializes purely imaginary quaternion with specified imaginary (v) component (real part is zero) 62 | 63 | Quaternion( const Complex& z ); 64 | // for a complex number z=a+bi, initializes quaternion to a+bi+0j+0k 65 | 66 | const Quaternion& operator=( double s ); 67 | // assigns a purely real quaternion with real value s 68 | 69 | const Quaternion& operator=( const Vector& v ); 70 | // assigns a purely real quaternion with imaginary value v 71 | 72 | double& operator[]( int index ); 73 | // returns reference to the specified component (0-based indexing: r, i, j, k) 74 | 75 | const double& operator[]( int index ) const; 76 | // returns const reference to the specified component (0-based indexing: r, i, j, k) 77 | 78 | void toMatrix( double Q[4][4] ) const; 79 | // builds 4x4 matrix Q representing (left) quaternion multiplication 80 | 81 | double& re( void ); 82 | // returns reference to double part 83 | 84 | const double& re( void ) const; 85 | // returns const reference to double part 86 | 87 | Vector& im( void ); 88 | // returns reference to imaginary part 89 | 90 | const Vector& im( void ) const; 91 | // returns const reference to imaginary part 92 | 93 | Quaternion operator+( const Quaternion& q ) const; 94 | // addition 95 | 96 | Quaternion operator-( const Quaternion& q ) const; 97 | // subtraction 98 | 99 | Quaternion operator-( void ) const; 100 | // negation 101 | 102 | Quaternion operator*( double c ) const; 103 | // right scalar multiplication 104 | 105 | Quaternion operator/( double c ) const; 106 | // scalar division 107 | 108 | void operator+=( const Quaternion& q ); 109 | // addition / assignment 110 | 111 | void operator+=( double c ); 112 | // addition / assignment of pure real 113 | 114 | void operator-=( const Quaternion& q ); 115 | // subtraction / assignment 116 | 117 | void operator-=( double c ); 118 | // subtraction / assignment of pure real 119 | 120 | void operator*=( double c ); 121 | // scalar multiplication / assignment 122 | 123 | void operator/=( double c ); 124 | // scalar division / assignment 125 | 126 | Quaternion operator*( const Quaternion& q ) const; 127 | // Hamilton product 128 | 129 | void operator*=( const Quaternion& q ); 130 | // Hamilton product / assignment 131 | 132 | Quaternion bar( void ) const; 133 | // conjugation 134 | 135 | Quaternion inv( void ) const; 136 | // inverse 137 | 138 | double norm( void ) const; 139 | // returns Euclidean length 140 | 141 | double norm2( void ) const; 142 | // returns Euclidean length squared 143 | 144 | Quaternion unit( void ) const; 145 | // returns unit quaternion 146 | 147 | void normalize( void ); 148 | // divides by Euclidean length 149 | 150 | protected: 151 | double s; 152 | // scalar (double) part 153 | 154 | Vector v; 155 | // vector (imaginary) part 156 | }; 157 | 158 | Quaternion operator*( double c, const Quaternion& q ); 159 | // left scalar multiplication 160 | 161 | std::ostream& operator<<( std::ostream& os, const Quaternion& q ); 162 | // prints components 163 | } 164 | 165 | #endif 166 | 167 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Real.h: -------------------------------------------------------------------------------- 1 | #ifndef DDG_REAL_H 2 | #define DDG_REAL_H 3 | 4 | namespace DDG 5 | { 6 | class Real 7 | { 8 | public: 9 | Real( double x = 0. ); 10 | // constructs real number with value x 11 | 12 | operator double( void ) const; 13 | // type cast to double 14 | 15 | void operator+=( double x ); 16 | // increment 17 | 18 | void operator-=( double x ); 19 | // decrement 20 | 21 | void operator*=( double x ); 22 | // multiply 23 | 24 | void operator/=( double x ); 25 | // divide 26 | 27 | Real bar( void ) const; 28 | // simply returns the value (for compatibility w/ complex numbers) 29 | 30 | Real inv( void ) const; 31 | // returns inverse 32 | 33 | double norm( void ) const; 34 | // returns norm 35 | 36 | double norm2( void ) const; 37 | // returns norm squared 38 | 39 | Real unit( void ) const; 40 | // returns number with unit norm and same sign 41 | 42 | protected: 43 | double value; 44 | // value 45 | }; 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Types.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- Types.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // This file contains forward declarations of common types and definitions of 6 | // convenience types for standard iterators. 7 | // 8 | 9 | #ifndef DDG_TYPES_H 10 | #define DDG_TYPES_H 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace DDG 17 | { 18 | // forward declarations 19 | class Camera; 20 | class Complex; 21 | class Edge; 22 | class Face; 23 | class HalfEdge; 24 | class Image; 25 | class LinearContext; 26 | class LinearEquation; 27 | class LinearPolynomial; 28 | class LinearSystem; 29 | class Mesh; 30 | class MeshIO; 31 | class Quaternion; 32 | class Real; 33 | class Shader; 34 | class Variable; 35 | class Vector; 36 | class Vertex; 37 | class Viewer; 38 | 39 | template 40 | class DenseMatrix; 41 | 42 | template 43 | class SparseMatrix; 44 | 45 | // convenience types for iterators 46 | typedef std::map::iterator TermIter; 47 | typedef std::map::const_iterator TermCIter; 48 | typedef std::vector::iterator PolyIter; 49 | typedef std::vector::const_iterator PolyCIter; 50 | typedef std::vector::iterator EqnIter; 51 | typedef std::vector::const_iterator EqnCIter; 52 | typedef std::map::iterator IndexIter; 53 | typedef std::map::const_iterator IndexCIter; 54 | typedef std::vector::iterator HalfEdgeIter; 55 | typedef std::vector::const_iterator HalfEdgeCIter; 56 | typedef std::vector::iterator VertexIter; 57 | typedef std::vector::const_iterator VertexCIter; 58 | typedef std::vector::iterator EdgeIter; 59 | typedef std::vector::const_iterator EdgeCIter; 60 | typedef std::vector::iterator FaceIter; 61 | typedef std::vector::const_iterator FaceCIter; 62 | } 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Utility.h: -------------------------------------------------------------------------------- 1 | #ifndef DDG_UTILITY_H 2 | #define DDG_UTILITY_H 3 | 4 | #include 5 | #include 6 | #include "Utility.h" 7 | #include "Complex.h" 8 | 9 | #undef SP_FLAT_TORUS 10 | #undef SP_DEBUG 11 | 12 | namespace DDG 13 | { 14 | inline double sqr( double x ) 15 | { 16 | return x*x; 17 | } 18 | 19 | inline double sgn( double x ) 20 | { 21 | if( x > 0. ) return 1.; 22 | if( x < 0. ) return -1.; 23 | return 0.; 24 | } 25 | 26 | inline double unitRand( void ) 27 | { 28 | const double rRandMax = 1. / (double) RAND_MAX; 29 | 30 | return rRandMax * (double) random(); 31 | } 32 | 33 | inline double randomReal( double minVal, double maxVal ) 34 | { 35 | return unitRand()*(maxVal-minVal) + minVal; 36 | } 37 | 38 | inline double seconds( int t0, int t1 ) 39 | { 40 | return (double)(t1-t0) / (double) CLOCKS_PER_SEC; 41 | } 42 | 43 | inline double angle( const Vector& u, const Vector& v ) 44 | { 45 | return acos( std::max( -1., std::min( 1., dot( u.unit(), v.unit() )))); 46 | } 47 | 48 | inline double fmodPI( const double theta ) 49 | { 50 | return theta - (2.*M_PI) * floor( (theta+M_PI) / (2.*M_PI) ); 51 | } 52 | } 53 | 54 | namespace DDGConstants 55 | { 56 | static DDG::Complex ii( 0., 1. ); 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Variable.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- Variable.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // Variable represents a variable that can be used to define a (linear or 6 | // nonlinear) system of equations. Its main feature is that it can be used 7 | // both as an abstract variable (e.g., when used to define an equation) but can 8 | // also store a definite numerical value. For instance, suppose we define a 9 | // linear polynomial 10 | // 11 | // Variable x, y; 12 | // LinearPolynomial p = x + 2*y; 13 | // 14 | // Now by assigning different numerical values to x and y, we can evaluate the 15 | // polynomial at different points: 16 | // 17 | // *x = 1; 18 | // *y = 2; 19 | // cout << p.evaluate() << endl; 20 | // 21 | // *x = 3; 22 | // *y = 4; 23 | // cout << p.evaluate() << endl; 24 | // 25 | // In general the dereference operator (*) accesses the numerical value. 26 | // Variables can also be named in order to aid with debugging. For instance, 27 | // 28 | // Variable x( "x" ); 29 | // Variable y( "y" ); 30 | // Polynomial p = x + 2*y; 31 | // cout << p << endl; 32 | // 33 | // will print out something like "x+2*y". 34 | // 35 | // The "fixed" flag in a variable refers to whether it is held constant while 36 | // solving a system of equations -- see the documentation for further discussion. 37 | // 38 | 39 | #ifndef DDG_VARIABLE_H 40 | #define DDG_VARIABLE_H 41 | 42 | #include 43 | 44 | namespace DDG 45 | { 46 | class Variable 47 | { 48 | public: 49 | Variable( double value = 0., bool fixed = false ); 50 | // initialize a variable which has value zero and is not fixed by default 51 | 52 | Variable( std::string name, double value = 0., bool fixed = false ); 53 | // initialize a named variable which has value zero and is not fixed by default 54 | 55 | double& operator*( void ); 56 | // returns a reference to the numerical value 57 | 58 | const double& operator*( void ) const; 59 | // returns a const reference to the numerical value 60 | 61 | std::string name; 62 | // names the variable (for display output) 63 | 64 | double value; 65 | // numerical value 66 | 67 | bool fixed; 68 | // true if a variable is held constant while solving a system of equations 69 | }; 70 | } 71 | 72 | #endif 73 | 74 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Vector.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- Vector.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // Vector represents an element of Euclidean 3-space, along with all the usual 6 | // vectors space operations (addition, multiplication by scalars, etc.). The 7 | // inner product (i.e., scalar or dot product) is expressed using the global 8 | // method dot(): 9 | // 10 | // Vector u, v; 11 | // double cosTheta = dot( u, v ); 12 | // 13 | // and the cross product is expressed using the global method cross(): 14 | // 15 | // Vector u, v, w; 16 | // w = cross( u, v ); 17 | // 18 | // Individual components can be accessed in two ways: either directly via the 19 | // members x, y, and z: 20 | // 21 | // Vector v; 22 | // cout << v.x << endl; 23 | // cout << v.y << endl; 24 | // cout << v.z << endl; 25 | // 26 | // or by index: 27 | // 28 | // Vector v; 29 | // for( int i = 0; i < 3; i++ ) 30 | // { 31 | // cout << v[i] << endl; 32 | // } 33 | // 34 | 35 | #ifndef DDG_VECTOR_H 36 | #define DDG_VECTOR_H 37 | 38 | #include 39 | 40 | namespace DDG 41 | { 42 | class Vector 43 | { 44 | public: 45 | Vector(); 46 | // initializes all components to zero 47 | 48 | Vector( double x, double y, double z); 49 | // initializes with specified components 50 | 51 | Vector( const Vector& v ); 52 | // initializes from existing vector 53 | 54 | double& operator[] ( const int& index ); 55 | // returns reference to the specified component (0-based indexing: x, y, z) 56 | 57 | const double& operator[] ( const int& index ) const; 58 | // returns const reference to the specified component (0-based indexing: x, y, z) 59 | 60 | Vector operator+( const Vector& v ) const; 61 | // addition 62 | 63 | Vector operator-( const Vector& v ) const; 64 | // subtraction 65 | 66 | Vector operator-( void ) const; 67 | // negation 68 | 69 | Vector operator*( const double& c ) const; 70 | // right scalar multiplication 71 | 72 | Vector operator/( const double& c ) const; 73 | // scalar division 74 | 75 | void operator+=( const Vector& v ); 76 | // addition / assignment 77 | 78 | void operator-=( const Vector& v ); 79 | // subtraction / assignment 80 | 81 | void operator*=( const double& c ); 82 | // scalar multiplication / assignment 83 | 84 | void operator/=( const double& c ); 85 | // scalar division / assignment 86 | 87 | double norm( void ) const; 88 | // returns Euclidean length 89 | 90 | double norm2( void ) const; 91 | // returns Euclidean length squared 92 | 93 | double normInf( void ) const; 94 | // returns maximum magnitude of any entry 95 | 96 | Vector unit( void ) const; 97 | // returns unit vector 98 | 99 | void normalize( void ); 100 | // divides by Euclidean length 101 | 102 | Vector abs( void ) const; 103 | // returns vector containing magnitude of each component 104 | 105 | static Vector randSphere( void ); 106 | // returns a random vector uniformly sampled from the unit sphere 107 | 108 | double x, y, z; 109 | // components 110 | }; 111 | 112 | Vector operator* ( const double& c, const Vector& v ); 113 | // left scalar multiplication 114 | 115 | double dot( const Vector& u, const Vector& v ); 116 | // dot product (a.k.a. inner or scalar product) 117 | 118 | Vector cross( const Vector& u, const Vector& v ); 119 | // cross product 120 | 121 | std::ostream& operator << (std::ostream& os, const Vector& o); 122 | // prints components 123 | } 124 | 125 | #endif 126 | 127 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/include/Vertex.h: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // libDDG -- Vertex.h 3 | // ----------------------------------------------------------------------------- 4 | // 5 | // Vertex stores attributes associated with a mesh edge. The iterator he 6 | // points to its "outgoing" halfedge. (See the documentation for a more 7 | // in-depth discussion of the halfedge data structure.) 8 | // 9 | 10 | #ifndef DDG_VERTEX_H 11 | #define DDG_VERTEX_H 12 | 13 | #include "Complex.h" 14 | #include "Vector.h" 15 | #include "Types.h" 16 | 17 | namespace DDG 18 | { 19 | class Vertex 20 | { 21 | public: 22 | HalfEdgeIter he; 23 | // points to the "outgoing" halfedge 24 | 25 | Vector position; 26 | // location of vertex in Euclidean 3-space 27 | 28 | Vector normal( void ) const; 29 | // returns the vertex normal 30 | 31 | double dualArea( void ) const; 32 | // returns the barycentric dual area, equal to one-third 33 | // the sum of the areas of all incident triangles 34 | 35 | bool onBoundary( void ) const; 36 | // returns true if the vertex is contained in the domain boundary 37 | 38 | bool isIsolated( void ) const; 39 | // returns true if the vertex is not contained in any face or edge; false otherwise 40 | 41 | int valence( void ) const; 42 | // returns the number of incident faces / edges 43 | 44 | double angleSum( void ) const; 45 | // returns sum of interior angles incident on this vertex 46 | 47 | int index; 48 | // unique integer ID in the range [0,nVertices-1] 49 | 50 | Vector fieldVector( double fieldDegree, double whichVector ) const; 51 | // vector representing direction field in world coordinates 52 | 53 | Complex canonicalVector( void ) const; 54 | // canonical choice of one of the 2-vectors 55 | 56 | Complex principalDirection( void ) const; 57 | // principal curvature direction, expressed as an intrinsic tangent vector relative to the outgoing halfedge 58 | 59 | Complex directionField, oldDirectionField; 60 | Complex parameterization; 61 | Complex embedding; 62 | 63 | double constantValue; 64 | bool visited; 65 | // for traversal 66 | 67 | Vector tangent; 68 | }; 69 | } 70 | 71 | #endif 72 | 73 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/Complex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | namespace DDG 8 | { 9 | Complex::Complex( double a, double b ) 10 | // constructs number a+bi 11 | : re( a ), im( b ) 12 | {} 13 | 14 | double& Complex :: operator[]( int p ) 15 | { 16 | return ( &re )[ p ]; 17 | } 18 | 19 | const double& Complex :: operator[]( int p ) const 20 | { 21 | return ( &re )[ p ]; 22 | } 23 | 24 | void Complex::operator+=( const Complex& z ) 25 | // add z 26 | { 27 | re += z.re; 28 | im += z.im; 29 | } 30 | 31 | void Complex::operator-=( const Complex& z ) 32 | // subtract z 33 | { 34 | re -= z.re; 35 | im -= z.im; 36 | } 37 | 38 | void Complex::operator*=( const Complex& z ) 39 | // Complex multiply by z 40 | { 41 | double a = re; 42 | double b = im; 43 | double c = z.re; 44 | double d = z.im; 45 | 46 | re = a*c-b*d; 47 | im = a*d+b*c; 48 | } 49 | 50 | void Complex::operator*=( double r ) 51 | // scalar multiply by r 52 | { 53 | re *= r; 54 | im *= r; 55 | } 56 | 57 | void Complex::operator/=( double r ) 58 | // scalar divide by r 59 | { 60 | re /= r; 61 | im /= r; 62 | } 63 | 64 | void Complex::operator/=( const Complex& z ) 65 | // scalar divide by r 66 | { 67 | *this *= z.inv(); 68 | } 69 | 70 | Complex Complex::operator-( void ) const 71 | { 72 | return Complex( -re, -im ); 73 | } 74 | 75 | Complex Complex::bar( void ) const 76 | // returns Complex conjugate 77 | { 78 | return Complex( re, -im ); 79 | } 80 | 81 | Complex Complex::inv( void ) const 82 | // returns inverse 83 | { 84 | return this->bar() / this->norm2(); 85 | } 86 | 87 | double Complex::arg( void ) const 88 | // returns argument 89 | { 90 | return atan2( im, re ); 91 | } 92 | 93 | double Complex::norm( void ) const 94 | // returns norm 95 | { 96 | return sqrt( re*re + im*im ); 97 | } 98 | 99 | double Complex::norm2( void ) const 100 | // returns norm squared 101 | { 102 | return re*re + im*im; 103 | } 104 | 105 | Complex Complex::unit( void ) const 106 | // returns complex number with unit norm and same modulus 107 | { 108 | return *this / this->norm(); 109 | } 110 | 111 | void Complex :: normalize( void ) 112 | { 113 | *this /= norm(); 114 | } 115 | 116 | Complex Complex::exp( void ) const 117 | // complex exponentiation 118 | { 119 | return ::exp( re ) * Complex( cos( im ), sin( im )); 120 | } 121 | 122 | Complex Complex::root( double k, double n ) const 123 | { 124 | double r = norm(); 125 | double theta = arg() / k; 126 | double phi = theta + 2.*n*M_PI/k; 127 | return Complex( r*cos(phi), r*sin(phi) ); 128 | } 129 | 130 | Complex operator+( const Complex& z1, const Complex& z2 ) 131 | // binary addition 132 | { 133 | Complex z = z1; 134 | z += z2; 135 | return z; 136 | } 137 | 138 | Complex operator-( const Complex& z1, const Complex& z2 ) 139 | // binary subtraction 140 | { 141 | Complex z = z1; 142 | z -= z2; 143 | return z; 144 | } 145 | 146 | Complex operator*( const Complex& z1, const Complex& z2 ) 147 | // binary Complex multiplication 148 | { 149 | Complex z = z1; 150 | z *= z2; 151 | return z; 152 | } 153 | 154 | Complex operator*( const Complex& z, double r ) 155 | // right scalar multiplication 156 | { 157 | Complex zr = z; 158 | zr *= r; 159 | return zr; 160 | } 161 | 162 | Complex operator*( double r, const Complex& z ) 163 | // left scalar multiplication 164 | { 165 | return z*r; 166 | } 167 | 168 | Complex operator/( const Complex& z, double r ) 169 | // scalar division 170 | { 171 | Complex zr = z; 172 | zr /= r; 173 | return zr; 174 | } 175 | 176 | Complex operator/( const Complex& z1, const Complex& z2 ) 177 | // complex division 178 | { 179 | Complex z = z1; 180 | z /= z2; 181 | return z; 182 | } 183 | 184 | double dot( const Complex& z1, const Complex& z2 ) 185 | { 186 | return z1.re*z2.re + z1.im*z2.im; 187 | } 188 | 189 | double cross( const Complex& z1, const Complex& z2 ) 190 | { 191 | return z1.re*z2.im - z1.im*z2.re; 192 | } 193 | 194 | std::ostream& operator<<( std::ostream& os, const Complex& z ) 195 | // prints components 196 | { 197 | if( z.im > 0 ) 198 | { 199 | os << z.re << " + " << z.im << "i"; 200 | } 201 | else if( z.im < 0 ) 202 | { 203 | os << z.re << " - " << -z.im << "i"; 204 | } 205 | else 206 | { 207 | os << z.re; 208 | } 209 | 210 | return os; 211 | } 212 | } 213 | 214 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/Edge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace DDG 6 | { 7 | double Edge :: length( void ) const 8 | { 9 | #ifdef SP_FLAT_TORUS 10 | return 1.; 11 | #endif 12 | return ( he->flip->vertex->position - he->vertex->position ).norm(); 13 | } 14 | 15 | double Edge :: dihedralAngle( void ) const 16 | { 17 | #ifdef SP_FLAT_TORUS 18 | return 0.; 19 | #endif 20 | Vector N1 = he->face->normal(); 21 | Vector N2 = he->flip->face->normal(); 22 | Vector e = ( he->flip->vertex->position - he->vertex->position ).unit(); 23 | 24 | return atan2( dot(e,cross(N1,N2)), dot(N1,N2) ); 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/Face.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace DDG 7 | { 8 | Vector Face::normal( void ) const 9 | { 10 | Vector p0 = he->vertex->position; 11 | Vector p1 = he->next->vertex->position; 12 | Vector p2 = he->next->next->vertex->position; 13 | 14 | return cross( p1-p0, p2-p0 ).unit(); 15 | } 16 | 17 | double Face::area( void ) const 18 | { 19 | Vector p0 = he->vertex->position; 20 | Vector p1 = he->next->vertex->position; 21 | Vector p2 = he->next->next->vertex->position; 22 | 23 | return cross( p1-p0, p2-p0 ).norm() / 2.; 24 | } 25 | 26 | bool Face::isBoundary( void ) const 27 | { 28 | return he->onBoundary; 29 | } 30 | 31 | double Face :: curvature( void ) const 32 | { 33 | double Omega = 0.; 34 | 35 | HalfEdgeCIter h = he; 36 | do 37 | { 38 | double thetaI = h->angularCoordinate; 39 | double thetaJ = h->flip->angularCoordinate + M_PI; 40 | 41 | Omega += thetaJ - thetaI; 42 | 43 | h = h->next; 44 | } 45 | while( h != he ); 46 | 47 | return fmodPI( Omega ); 48 | } 49 | 50 | double Face :: fieldIndex( double k ) const 51 | { 52 | if( isBoundary() ) return 0.; 53 | 54 | double Omega = 0.; 55 | double index = 0.; 56 | HalfEdgeCIter hi = he; 57 | do 58 | { 59 | double phiI = hi->vertex->directionField.arg(); 60 | double phiJ = hi->flip->vertex->directionField.arg(); 61 | double thetaI = hi->angularCoordinate; 62 | double thetaJ = hi->flip->angularCoordinate + M_PI; 63 | double dTheta = thetaI - thetaJ; 64 | 65 | Omega += dTheta; 66 | index += fmodPI( phiJ - phiI + k*dTheta ); 67 | 68 | hi = hi->next; 69 | } 70 | while( hi != he ); 71 | 72 | index -= k * fmodPI(Omega); 73 | 74 | return lround( index / (2.*M_PI) ); 75 | } 76 | 77 | // double Face :: paramIndex( void ) const 78 | // { 79 | // // TODO 80 | // HalfEdgeCIter hij = he; 81 | // HalfEdgeCIter hjk = hij->next; 82 | // HalfEdgeCIter hkl = hjk->next; 83 | 84 | // double omegaIJ = hij->edge->omega; 85 | // double omegaJK = hjk->edge->omega; 86 | // double omegaKL = hkl->edge->omega; 87 | 88 | // Complex phiI = hij->vertex->parameterization; 89 | // Complex phiJ = hjk->vertex->parameterization; 90 | // Complex phiK = hkl->vertex->parameterization; 91 | // Complex phiL = phiI; 92 | 93 | // // TODO clean up the logic here... 94 | // if( hij->edge->crossesSheets ) { phiJ = phiJ.bar(); phiK = phiK.bar(); omegaJK = -omegaJK; phiL = phiL.bar(); omegaKL = -omegaKL; } else if( hij->edge->he == hij ) { omegaIJ = -omegaIJ; } 95 | // if( hjk->edge->crossesSheets ) { phiK = phiK.bar(); phiL = phiL.bar(); omegaKL = -omegaKL; } else if( hjk->edge->he == hjk ) { omegaJK = -omegaJK; } 96 | // if( hkl->edge->crossesSheets ) { phiL = phiL.bar(); } else if( hkl->edge->he == hkl ) { omegaKL = -omegaKL; } 97 | 98 | // Complex rij( cos(omegaIJ), sin(omegaIJ) ); 99 | // Complex rjk( cos(omegaJK), sin(omegaJK) ); 100 | // Complex rkl( cos(omegaKL), sin(omegaKL) ); 101 | 102 | // double sigmaIJ = omegaIJ + ((rij*phiI)*(phiJ.inv())).arg(); 103 | // double sigmaJK = omegaJK + ((rjk*phiJ)*(phiK.inv())).arg(); 104 | // double sigmaKL = omegaKL + ((rkl*phiK)*(phiL.inv())).arg(); 105 | // double sigmaIJK = sigmaIJ + sigmaJK + sigmaKL; 106 | 107 | // return lround( sigmaIJK / (2.*M_PI) ); 108 | // } 109 | 110 | Vector Face :: barycenter( void ) const 111 | { 112 | return ( he->vertex->position + 113 | he->next->vertex->position + 114 | he->next->next->vertex->position ) / 3.; 115 | } 116 | 117 | void Face :: orientTexCoords( void ) 118 | { 119 | Complex& p0 = he->texcoord; 120 | Complex& p1 = he->next->texcoord; 121 | Complex& p2 = he->next->next->texcoord; 122 | 123 | Complex u = p1-p0; 124 | Complex v = p2-p0; 125 | 126 | if( cross( u, v ) < 0. ) 127 | { 128 | p0 = p0.bar(); 129 | p1 = p1.bar(); 130 | p2 = p2.bar(); 131 | } 132 | } 133 | 134 | void Face::getLocalSheet( std::vector& psi, std::vector& omega ) 135 | { 136 | psi.resize( 3 ); 137 | omega.resize( 3 ); 138 | 139 | HalfEdgeCIter hij = he; 140 | HalfEdgeCIter hjk = hij->next; 141 | HalfEdgeCIter hki = hjk->next; 142 | 143 | Complex psiI = hij->vertex->parameterization; 144 | Complex psiJ = hjk->vertex->parameterization; 145 | Complex psiK = hki->vertex->parameterization; 146 | 147 | double omegaIJ = hij->omega(); 148 | double omegaJK = hjk->omega(); 149 | double omegaKI = hki->omega(); 150 | 151 | if( hij->edge->crossesSheets ) 152 | { 153 | psiJ = -psiJ; 154 | 155 | if( hij->edge->he != hij ) omegaIJ = -omegaIJ; 156 | if( hjk->edge->he == hjk ) omegaJK = -omegaJK; 157 | } 158 | 159 | if( hjk->edge->crossesSheets ) 160 | { 161 | psiK = -psiK; 162 | 163 | if( hki->edge->he == hki ) omegaKI = -omegaKI; 164 | if( hjk->edge->he != hjk ) omegaJK = -omegaJK; 165 | } 166 | 167 | psi[0] = psiI; 168 | psi[1] = psiJ; 169 | psi[2] = psiK; 170 | 171 | omega[0] = omegaIJ; 172 | omega[1] = omegaJK; 173 | omega[2] = omegaKI; 174 | } 175 | } 176 | 177 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/HalfEdge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace DDG 6 | { 7 | HalfEdgeIter HalfEdge :: self( void ) 8 | { 9 | return flip->flip; 10 | } 11 | 12 | HalfEdgeCIter HalfEdge :: self( void ) const 13 | { 14 | return flip->flip; 15 | } 16 | 17 | bool HalfEdge :: isCanonical( void ) const 18 | { 19 | return edge->he == self(); 20 | } 21 | 22 | bool HalfEdge :: crossesSheets( void ) const 23 | { 24 | return edge->crossesSheets; 25 | } 26 | 27 | Vector HalfEdge :: vector( void ) const 28 | { 29 | return flip->vertex->position - vertex->position; 30 | } 31 | 32 | double HalfEdge :: angle( void ) const 33 | { 34 | Vector a = next->next->vertex->position; 35 | Vector b = vertex->position; 36 | Vector c = next->vertex->position; 37 | 38 | Vector u = (b-a).unit(); 39 | Vector v = (c-a).unit(); 40 | 41 | return acos( max( -1., min( 1., dot( u, v )))); 42 | } 43 | 44 | double HalfEdge :: cotan( void ) const 45 | { 46 | if( onBoundary ) return 0.; 47 | 48 | Vector a = next->next->vertex->position; 49 | Vector b = vertex->position; 50 | Vector c = next->vertex->position; 51 | 52 | Vector u = b-a; 53 | Vector v = c-a; 54 | 55 | const double epsilon = 1e-7; 56 | double cotTheta = dot( u, v ) / cross( u, v ).norm(); 57 | if( cotTheta < epsilon ) 58 | { 59 | cotTheta = epsilon; 60 | } 61 | return cotTheta; 62 | } 63 | 64 | void HalfEdge :: updateTexCoord( int p ) 65 | { 66 | const double infty = numeric_limits::max(); 67 | HalfEdgeCIter hij; 68 | 69 | if( next->texcoord[p] != infty ) 70 | { 71 | hij = flip; 72 | updateTexCoordFromIToJ( p, hij ); 73 | } 74 | else if( next->next->texcoord[p] != infty ) 75 | { 76 | hij = next->next; 77 | updateTexCoordFromIToJ( p, hij ); 78 | } 79 | else 80 | { 81 | texcoord[p] = vertex->parameterization.arg(); 82 | } 83 | } 84 | 85 | void HalfEdge :: updateTexCoordFromIToJ( int p, HalfEdgeCIter hij ) 86 | { 87 | Complex phiI = hij->vertex->parameterization; 88 | Complex phiJ = vertex->parameterization; 89 | double omegaIJ = hij->edge->omega; 90 | 91 | if( hij->edge->crossesSheets ) 92 | { 93 | phiJ = phiJ.bar(); 94 | } 95 | else if( hij->edge->he == hij ) 96 | { 97 | omegaIJ = -omegaIJ; 98 | } 99 | 100 | Complex rij( cos(omegaIJ), sin(omegaIJ) ); 101 | texcoord[p] = phiI.arg() + ( (rij*phiI).inv()*(phiJ) ).arg() + omegaIJ; 102 | } 103 | 104 | double HalfEdge :: omega( void ) const 105 | { 106 | if( isCanonical() ) 107 | { 108 | return edge->omega; 109 | } 110 | return -edge->omega; 111 | } 112 | } 113 | 114 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/LinearContext.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace DDG 4 | { 5 | // global context for linear solvers 6 | LinearContext context; 7 | 8 | LinearContext :: LinearContext( void ) 9 | // constructor 10 | { 11 | cholmod_l_start( &context ); 12 | } 13 | 14 | LinearContext :: ~LinearContext( void ) 15 | // destructor 16 | { 17 | cholmod_l_finish( &context ); 18 | } 19 | 20 | LinearContext :: operator cholmod_common*( void ) 21 | // allows LinearContext to be treated as a cholmod_common* 22 | { 23 | return &context; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/LinearEquation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace DDG 4 | { 5 | LinearEquation operator==( const LinearPolynomial& lhs, 6 | const LinearPolynomial& rhs ) 7 | // constructs a linear equation with the specified left- and right-hand side 8 | { 9 | LinearEquation eqn; 10 | 11 | eqn.lhs = lhs; 12 | eqn.rhs = rhs; 13 | 14 | return eqn; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/LinearSystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace DDG 11 | { 12 | extern LinearContext context; 13 | 14 | void LinearSystem::clear( void ) 15 | // removes all equations from the system 16 | { 17 | equations.clear(); 18 | } 19 | 20 | void LinearSystem::push_back( const LinearEquation& e ) 21 | // appends the equation e to the sytem 22 | { 23 | equations.push_back( e ); 24 | } 25 | 26 | void LinearSystem::solve( void ) 27 | // solves the system and automatically stores the result in the variables 28 | // for an overdetermined system, computes a least-squares solution 29 | { 30 | convertEquations(); 31 | indexVariables(); 32 | buildSparseMatrix(); 33 | buildRightHandSide(); 34 | computeSolution(); 35 | } 36 | 37 | void LinearSystem::convertEquations( void ) 38 | // converts each equation to its polynomial representation 39 | { 40 | currentEquations.clear(); 41 | 42 | for( EqnIter eqn = equations.begin(); 43 | eqn != equations.end(); 44 | eqn ++ ) 45 | { 46 | // move right-hand side to left-hand side 47 | LinearPolynomial p = eqn->lhs - eqn->rhs; 48 | 49 | // convert fixed variables to constants 50 | LinearPolynomial q( p.constantTerm ); 51 | 52 | for( TermIter t = p.linearTerms.begin(); 53 | t != p.linearTerms.end(); 54 | t ++ ) 55 | { 56 | const double& coefficient( t->second ); 57 | Variable& variable( *(t->first) ); 58 | 59 | // skip zeros 60 | if( coefficient == 0. ) continue; 61 | 62 | if( t->first->fixed ) 63 | { 64 | q += coefficient * variable.value; 65 | } 66 | else 67 | { 68 | q += coefficient * variable; 69 | } 70 | } 71 | 72 | if( q.linearTerms.size() > 0 ) 73 | { 74 | currentEquations.push_back( q ); 75 | } 76 | } 77 | 78 | nEquations = currentEquations.size(); 79 | } 80 | 81 | void LinearSystem::indexVariables( void ) 82 | // assign a unique index to each variable remaining in one of the polynomials 83 | { 84 | index.clear(); 85 | nVariables = 0; 86 | 87 | for( PolyCIter p = currentEquations.begin(); 88 | p != currentEquations.end(); 89 | p ++ ) 90 | { 91 | for( TermCIter t = p->linearTerms.begin(); 92 | t != p->linearTerms.end(); 93 | t ++ ) 94 | { 95 | IndexIter j = index.find( t->first ); 96 | 97 | // if we haven't seen this variable 98 | // before, assign it a unique index 99 | if( j == index.end() ) 100 | { 101 | index[ t->first ] = nVariables; 102 | nVariables++; 103 | } 104 | } 105 | } 106 | } 107 | 108 | void LinearSystem::buildSparseMatrix( void ) 109 | // build the sparse matrix representation of our current system 110 | { 111 | A = SparseMatrix( nEquations, nVariables ); 112 | 113 | for( int i = 0; i < nEquations; i++ ) 114 | { 115 | for( TermCIter t = currentEquations[i].linearTerms.begin(); 116 | t != currentEquations[i].linearTerms.end(); 117 | t ++ ) 118 | { 119 | int j = index[ t->first ]; 120 | 121 | A(i,j) = t->second; 122 | } 123 | } 124 | } 125 | 126 | void LinearSystem::buildRightHandSide( void ) 127 | // build the data vector for our current system 128 | { 129 | b = DenseMatrix( nEquations, 1 ); 130 | 131 | for( int i = 0; i < nEquations; i++ ) 132 | { 133 | b(i) = -currentEquations[i].constantTerm; 134 | } 135 | } 136 | 137 | void LinearSystem::computeSolution( void ) 138 | { 139 | // solve linear system Ax=b 140 | x = SuiteSparseQR( A.to_cholmod(), b.to_cholmod(), context ); 141 | 142 | // put solution values in variables 143 | for( IndexIter i = index.begin(); 144 | i != index.end(); 145 | i ++ ) 146 | { 147 | i->first->value = x( i->second ); 148 | } 149 | } 150 | } 151 | 152 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/Real.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace DDG 5 | { 6 | Real :: Real( double x ) 7 | // constructs real number with value x 8 | : value( x ) 9 | {} 10 | 11 | Real :: operator double( void ) const 12 | // type cast to double 13 | { 14 | return value; 15 | } 16 | 17 | void Real :: operator+=( double x ) 18 | // increment 19 | { 20 | value += x; 21 | } 22 | 23 | void Real :: operator-=( double x ) 24 | // decrement 25 | { 26 | value -= x; 27 | } 28 | 29 | void Real :: operator*=( double x ) 30 | // multiply 31 | { 32 | value *= x; 33 | } 34 | 35 | void Real :: operator/=( double x ) 36 | // divide 37 | { 38 | value /= x; 39 | } 40 | 41 | Real Real :: bar( void ) const 42 | // simply returns the value (for compatibility w/ complex numbers) 43 | { 44 | return value; 45 | } 46 | 47 | Real Real :: inv( void ) const 48 | // returns inverse 49 | { 50 | return 1. / value; 51 | } 52 | 53 | double Real :: norm( void ) const 54 | // returns norm 55 | { 56 | return fabs( value ); 57 | } 58 | 59 | double Real :: norm2( void ) const 60 | // returns norm squared 61 | { 62 | return value * value; 63 | } 64 | 65 | Real Real :: unit( void ) const 66 | // returns number with unit norm and same sign 67 | { 68 | return value / norm(); 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/SparseMatrix.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace DDG 4 | { 5 | template <> 6 | const SparseMatrix& SparseMatrix :: operator=( cholmod_sparse* B ) 7 | { 8 | assert( B ); 9 | assert( B->xtype == CHOLMOD_REAL ); 10 | 11 | if( cData ) 12 | { 13 | cholmod_l_free_sparse( &cData, context ); 14 | } 15 | cData = B; 16 | 17 | m = cData->nrow; 18 | n = cData->ncol; 19 | resize( m, n ); 20 | 21 | double* pr = (double*) cData->x; 22 | SuiteSparse_long* ir = (SuiteSparse_long*) cData->i; 23 | SuiteSparse_long* jc = (SuiteSparse_long*) cData->p; 24 | 25 | // iterate over columns 26 | for( int col = 0; col < n; col++ ) 27 | { 28 | // iterate over nonzero rows 29 | for( int k = jc[col]; k < jc[col+1]; k++ ) 30 | { 31 | int row = ir[k]; 32 | 33 | (*this)( row, col ) = pr[k]; 34 | } 35 | } 36 | 37 | return *this; 38 | } 39 | 40 | template <> 41 | const SparseMatrix& SparseMatrix :: operator=( cholmod_sparse* B ) 42 | { 43 | assert( B ); 44 | assert( B->xtype == CHOLMOD_COMPLEX ); 45 | 46 | if( cData ) 47 | { 48 | cholmod_l_free_sparse( &cData, context ); 49 | } 50 | cData = B; 51 | 52 | m = cData->nrow; 53 | n = cData->ncol; 54 | resize( m, n ); 55 | 56 | double* pr = (double*) cData->x; 57 | SuiteSparse_long* ir = (SuiteSparse_long*) cData->i; 58 | SuiteSparse_long* jc = (SuiteSparse_long*) cData->p; 59 | 60 | // iterate over columns 61 | for( int col = 0; col < n; col++ ) 62 | { 63 | // iterate over nonzero rows 64 | for( int k = jc[col]; k < jc[col+1]; k++ ) 65 | { 66 | int row = ir[k]; 67 | 68 | (*this)( row, col ) = Complex( pr[k*2+0], pr[k*2+1] ); 69 | } 70 | } 71 | 72 | return *this; 73 | } 74 | 75 | template <> 76 | cholmod_sparse* SparseMatrix :: to_cholmod( void ) 77 | { 78 | SparseMatrix A( m*4, n*4 ); 79 | 80 | for( const_iterator e = begin(); 81 | e != end(); 82 | e ++ ) 83 | { 84 | int i = e->first.second; 85 | int j = e->first.first; 86 | const Quaternion& q( e->second ); 87 | 88 | A(i*4+0,j*4+0) = q[0]; A(i*4+0,j*4+1) = -q[1]; A(i*4+0,j*4+2) = -q[2]; A(i*4+0,j*4+3) = -q[3]; 89 | A(i*4+1,j*4+0) = q[1]; A(i*4+1,j*4+1) = q[0]; A(i*4+1,j*4+2) = -q[3]; A(i*4+1,j*4+3) = q[2]; 90 | A(i*4+2,j*4+0) = q[2]; A(i*4+2,j*4+1) = q[3]; A(i*4+2,j*4+2) = q[0]; A(i*4+2,j*4+3) = -q[1]; 91 | A(i*4+3,j*4+0) = q[3]; A(i*4+3,j*4+1) = -q[2]; A(i*4+3,j*4+2) = q[1]; A(i*4+3,j*4+3) = q[0]; 92 | } 93 | 94 | if( cData != NULL ) 95 | { 96 | cholmod_l_free_sparse( &cData, context ); 97 | } 98 | cData = cholmod_l_copy_sparse( A.to_cholmod(), context ); 99 | return cData; 100 | } 101 | 102 | template <> 103 | void SparseMatrix :: allocateSparse( void ) 104 | { 105 | int nzmax = data.size(); 106 | int sorted = true; 107 | int packed = true; 108 | int stype = 0; 109 | cData = cholmod_l_allocate_sparse( m, n, nzmax, sorted, packed, stype, CHOLMOD_REAL, context ); 110 | } 111 | 112 | template <> 113 | void SparseMatrix :: allocateSparse( void ) 114 | { 115 | int nzmax = data.size(); 116 | int sorted = true; 117 | int packed = true; 118 | int stype = 0; 119 | cData = cholmod_l_allocate_sparse( m, n, nzmax, sorted, packed, stype, CHOLMOD_COMPLEX, context ); 120 | } 121 | 122 | template <> 123 | void SparseMatrix :: allocateSparse( void ) 124 | { 125 | int nzmax = data.size(); 126 | int sorted = true; 127 | int packed = true; 128 | int stype = 0; 129 | cData = cholmod_l_allocate_sparse( m*4, n*4, nzmax, sorted, packed, stype, CHOLMOD_REAL, context ); 130 | } 131 | 132 | template <> 133 | void SparseMatrix :: setEntry( const_iterator e, int i, double* pr ) 134 | { 135 | pr[i] = e->second; 136 | } 137 | 138 | template <> 139 | void SparseMatrix :: setEntry( const_iterator e, int i, double* pr ) 140 | { 141 | pr[i*2+0] = e->second.re; 142 | pr[i*2+1] = e->second.im; 143 | } 144 | 145 | template <> 146 | void solveSymmetric( SparseMatrix& A, 147 | DenseMatrix& x, 148 | DenseMatrix& b ) 149 | // solves the sparse linear system Ax = b using sparse LU factorization 150 | { 151 | #ifdef SP_DEBUG 152 | int t0 = clock(); 153 | #endif 154 | cholmod_sparse* Ac = A.to_cholmod(); 155 | int n = Ac->nrow; 156 | SuiteSparse_long* Ap = (SuiteSparse_long*) Ac->p; 157 | SuiteSparse_long* Ai = (SuiteSparse_long*) Ac->i; 158 | double* Ax = (double*) Ac->x; 159 | void* Symbolic; 160 | void* Numeric; 161 | 162 | umfpack_zl_symbolic( n, n, Ap, Ai, Ax, NULL, &Symbolic, NULL, NULL ); 163 | umfpack_zl_numeric( Ap, Ai, Ax, NULL, Symbolic, &Numeric, NULL, NULL ); 164 | umfpack_zl_solve( UMFPACK_A, Ap, Ai, Ax, NULL, (double*) &x(0), NULL, (double*) &b(0), NULL, Numeric, NULL, NULL ); 165 | umfpack_zl_free_symbolic( &Symbolic ); 166 | umfpack_zl_free_numeric( &Numeric ); 167 | 168 | #ifdef SP_DEBUG 169 | int t1 = clock(); 170 | cout << "[lu] time: " << seconds( t0, t1 ) << "s" << "\n"; 171 | cout << "[lu] max residual: " << residual( A, x, b ) << "\n"; 172 | #endif 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/Variable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace DDG 4 | { 5 | Variable :: Variable( double value_, 6 | bool fixed_ ) 7 | // initialize a variable which has value zero and is not fixed by default 8 | : value( value_ ), 9 | fixed( fixed_ ) 10 | {} 11 | 12 | Variable :: Variable( std::string name_, 13 | double value_, 14 | bool fixed_ ) 15 | // initialize a named variable which has value zero and is not fixed by default 16 | : name( name_ ), 17 | value( value_ ), 18 | fixed( fixed_ ) 19 | {} 20 | 21 | double& Variable :: operator*( void ) 22 | // returns a reference to the numerical value 23 | { 24 | return value; 25 | } 26 | 27 | const double& Variable :: operator*( void ) const 28 | // returns a const reference to the numerical value 29 | { 30 | return value; 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/Vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace DDG 7 | { 8 | Vector :: Vector( void ) 9 | : x( 0. ), 10 | y( 0. ), 11 | z( 0. ) 12 | {} 13 | 14 | Vector :: Vector( double x0, 15 | double y0, 16 | double z0 ) 17 | : x( x0 ), 18 | y( y0 ), 19 | z( z0 ) 20 | {} 21 | 22 | Vector :: Vector( const Vector& v ) 23 | : x( v.x ), 24 | y( v.y ), 25 | z( v.z ) 26 | {} 27 | 28 | double& Vector :: operator[]( const int& index ) 29 | { 30 | return ( &x )[ index ]; 31 | } 32 | 33 | const double& Vector :: operator[]( const int& index ) const 34 | { 35 | return ( &x )[ index ]; 36 | } 37 | 38 | Vector Vector :: operator+( const Vector& v ) const 39 | { 40 | return Vector( x + v.x, 41 | y + v.y, 42 | z + v.z ); 43 | } 44 | 45 | Vector Vector :: operator-( const Vector& v ) const 46 | { 47 | return Vector( x - v.x, 48 | y - v.y, 49 | z - v.z ); 50 | } 51 | 52 | Vector Vector :: operator-( void ) const 53 | { 54 | return Vector( -x, 55 | -y, 56 | -z ); 57 | } 58 | 59 | Vector Vector :: operator*( const double& c ) const 60 | { 61 | return Vector( x*c, 62 | y*c, 63 | z*c ); 64 | } 65 | 66 | Vector operator*( const double& c, const Vector& v ) 67 | { 68 | return v*c; 69 | } 70 | 71 | Vector Vector :: operator/( const double& c ) const 72 | { 73 | return (*this) * ( 1./c ); 74 | } 75 | 76 | void Vector :: operator+=( const Vector& v ) 77 | { 78 | x += v.x; 79 | y += v.y; 80 | z += v.z; 81 | } 82 | 83 | void Vector :: operator-=( const Vector& v ) 84 | { 85 | x -= v.x; 86 | y -= v.y; 87 | z -= v.z; 88 | } 89 | 90 | void Vector :: operator*=( const double& c ) 91 | { 92 | x *= c; 93 | y *= c; 94 | z *= c; 95 | } 96 | 97 | void Vector :: operator/=( const double& c ) 98 | { 99 | (*this) *= ( 1./c ); 100 | } 101 | 102 | double Vector :: norm( void ) const 103 | { 104 | return sqrt( norm2()); 105 | } 106 | 107 | double Vector :: norm2( void ) const 108 | { 109 | return dot( *this, *this ); 110 | } 111 | 112 | double Vector :: normInf( void ) const 113 | { 114 | return std::max( std::max( fabs(x), fabs(y) ), fabs(z) ); 115 | } 116 | 117 | void Vector :: normalize( void ) 118 | { 119 | (*this) /= norm(); 120 | } 121 | 122 | Vector Vector :: unit( void ) const 123 | { 124 | return (*this) / norm(); 125 | } 126 | 127 | Vector Vector :: abs( void ) const 128 | { 129 | return Vector( fabs( x ), 130 | fabs( y ), 131 | fabs( z ) ); 132 | } 133 | 134 | Vector Vector :: randSphere( void ) 135 | { 136 | double z1 = unitRand()*2.; 137 | double z2 = unitRand(); 138 | 139 | return Vector( 140 | sqrt( z1*(2.-z1)) * cos(2.*M_PI*z2), 141 | sqrt( z1*(2.-z1)) * sin(2.*M_PI*z2), 142 | 1.-z1 143 | ); 144 | } 145 | 146 | double dot( const Vector& u, const Vector& v ) 147 | { 148 | return u.x*v.x + 149 | u.y*v.y + 150 | u.z*v.z ; 151 | } 152 | 153 | Vector cross( const Vector& u, const Vector& v ) 154 | { 155 | return Vector( u.y*v.z - u.z*v.y, 156 | u.z*v.x - u.x*v.z, 157 | u.x*v.y - u.y*v.x ); 158 | } 159 | 160 | std::ostream& operator << (std::ostream& os, const Vector& o) 161 | { 162 | os << "[ " 163 | << o.x << " " 164 | << o.y << " " 165 | << o.z 166 | << " ]"; 167 | 168 | return os; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /3rdparty/StripePatterns/src/Vertex.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace DDG 10 | { 11 | Vector Vertex::normal( void ) const 12 | // returns the vertex normal 13 | { 14 | Vector N; 15 | 16 | HalfEdgeCIter h = he; 17 | do 18 | { 19 | if( !h->onBoundary ) 20 | { 21 | N += h->face->normal(); 22 | } 23 | 24 | h = h->flip->next; 25 | } 26 | while( h != he ); 27 | 28 | return N.unit(); 29 | } 30 | 31 | double Vertex::dualArea( void ) const 32 | { 33 | double A = 0.; 34 | 35 | HalfEdgeCIter h = he; 36 | do 37 | { 38 | if( !h->onBoundary ) 39 | { 40 | A += h->face->area(); 41 | } 42 | 43 | h = h->flip->next; 44 | } 45 | while( h != he ); 46 | 47 | return A/3.; 48 | } 49 | 50 | vector isolated; // all isolated vertices point to isolated.begin() 51 | 52 | bool Vertex::onBoundary( void ) const 53 | { 54 | return he->onBoundary; 55 | } 56 | 57 | bool Vertex::isIsolated( void ) const 58 | // returns true if the vertex is not contained in any face or edge; false otherwise 59 | { 60 | return he == isolated.begin(); 61 | } 62 | 63 | int Vertex :: valence( void ) const 64 | // returns the number of incident faces 65 | { 66 | int n = 0; 67 | 68 | HalfEdgeCIter h = he; 69 | do 70 | { 71 | n++; 72 | h = h->flip->next; 73 | } 74 | while( h != he ); 75 | 76 | return n; 77 | } 78 | 79 | double Vertex :: angleSum( void ) const 80 | { 81 | double sum = 0.; 82 | 83 | HalfEdgeCIter h = he; 84 | do 85 | { 86 | sum += h->next->angle(); 87 | h = h->flip->next; 88 | } 89 | while( h != he ); 90 | 91 | return sum; 92 | } 93 | 94 | Vector Vertex :: fieldVector( double k, double n ) const 95 | { 96 | #ifdef SP_FLAT_TORUS 97 | Complex p = he->origTexCoord; 98 | Complex X = he->next->origTexCoord - p; 99 | X /= X.norm(); 100 | double r = directionField.norm(); 101 | double theta = directionField.arg() / k; 102 | double phi = theta + (2.*M_PI/k)*n; 103 | Complex Y = Complex( cos(phi), sin(phi) ) * X; 104 | return r * Vector( Y.re, Y.im, 0. ); 105 | #else 106 | Vector p = position; 107 | Vector N = normal(); 108 | Vector X = he->flip->vertex->position - p; 109 | X -= dot(X,N)*N; 110 | X.normalize(); 111 | Vector JX = cross( N, X ); 112 | double r = directionField.norm(); 113 | double theta = directionField.arg() / k; 114 | double phi = theta + (2.*M_PI/k)*n + M_PI/2.; 115 | return r * ( cos(phi)*X + sin(phi)*JX ); 116 | #endif 117 | } 118 | 119 | Complex Vertex :: canonicalVector( void ) const 120 | { 121 | double r = directionField.norm(); 122 | double theta = directionField.arg() / 2.; 123 | return r * Complex( cos(theta), sin(theta) ); 124 | } 125 | 126 | Complex Vertex :: principalDirection( void ) const 127 | // This is literally the only routine in the entire algorithm 128 | // that does anything involving principal curvatures/curvature estimates. 129 | // No thresholds, no parameters... 130 | { 131 | Complex X( 0., 0. ); 132 | 133 | HalfEdgeCIter h = he; 134 | do 135 | { 136 | double l = h->edge->length(); 137 | double alpha = h->edge->dihedralAngle(); 138 | double theta = h->angularCoordinate; 139 | Complex r( cos(2.*theta), sin(2.*theta) ); 140 | 141 | X += l * alpha * r; 142 | 143 | h = h->flip->next; 144 | } 145 | while( h != he ); 146 | 147 | return -X / 4.; 148 | } 149 | } 150 | 151 | -------------------------------------------------------------------------------- /DualLaplacianStencil.cc: -------------------------------------------------------------------------------- 1 | #include "DualLaplacianStencil.hh" 2 | 3 | #include 4 | 5 | void igl_intrinsic_delaunay_cotmatrix(const Eigen::MatrixX3d &V, 6 | const Eigen::MatrixXi &F, 7 | Eigen::SparseMatrix &L) { 8 | igl::intrinsic_delaunay_cotmatrix(V, F, L); 9 | } 10 | -------------------------------------------------------------------------------- /DualMesh.hh: -------------------------------------------------------------------------------- 1 | #ifndef DUAL_MESH_HH 2 | #define DUAL_MESH_HH 3 | 4 | #include 5 | 6 | template 7 | void barycentricDual(const Mesh &m, 8 | std::vector &dualVertices, 9 | std::vector &dualPolygons) { 10 | const size_t ne = m.numElements(); 11 | dualVertices.clear(); 12 | dualVertices.reserve(ne); 13 | for (size_t ei = 0; ei < ne; ++ei) 14 | dualVertices.push_back(m.elementBarycenter(ei)); 15 | 16 | const size_t nv = m.numVertices(); 17 | dualPolygons.clear(); 18 | dualPolygons.reserve(nv); // There will be fewer than nv polygons, since we we only have them for internal vertices. 19 | for (const auto v : m.vertices()) { 20 | if (v.isBoundary()) continue; 21 | dualPolygons.emplace_back(); 22 | // Circulate counter-clockwise. 23 | for (const auto he : v.incidentHalfEdges()) { 24 | assert(!he.isBoundary()); 25 | dualPolygons.back().push_back(he.tri().index()); 26 | } 27 | } 28 | } 29 | 30 | // Assumes the dual region is convex... 31 | template 32 | void triangulatedBarycentricDual(const Mesh &m, 33 | std::vector &dualVertices, 34 | std::vector &dualTriangles, 35 | std::vector &originatingPolygon) { 36 | std::vector dualPolygons; 37 | barycentricDual(m, dualVertices, dualPolygons); 38 | 39 | dualTriangles.clear(); 40 | originatingPolygon.clear(); 41 | 42 | bool toggle = false; 43 | for (size_t pi = 0; pi < dualPolygons.size(); ++pi) { 44 | const auto &p = dualPolygons[pi]; 45 | 46 | size_t start = 0, end = p.size() - 1; 47 | while (end - start + 1 >= 3) { 48 | if (toggle) { 49 | dualTriangles.emplace_back(p[start], p[start + 1], p[end]); 50 | ++start; 51 | } 52 | else { 53 | dualTriangles.emplace_back(p[start], p[end - 1], p[end]); 54 | --end; 55 | } 56 | originatingPolygon.push_back(pi); 57 | toggle = !toggle; 58 | } 59 | } 60 | } 61 | 62 | #endif /* end of include guard: DUAL_MESH_HH */ 63 | -------------------------------------------------------------------------------- /EigSensitivity.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // EigSensitivity.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // First and second derivatives of symmetric 2x2 matrix eigenvalues. 6 | */ 7 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 8 | // Created: 04/05/2019 10:32:17 9 | //////////////////////////////////////////////////////////////////////////////// 10 | #ifndef EIGSENSITIVITY_HH 11 | #define EIGSENSITIVITY_HH 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | // 2x2 case only for now, also can be sped up. 18 | template 19 | struct EigSensitivity { 20 | using V2d = Eigen::Matrix; 21 | using M2d = Eigen::Matrix; 22 | 23 | EigSensitivity() { } 24 | 25 | template 26 | EigSensitivity(const Eigen::MatrixBase &A) { setMatrix(A); } 27 | 28 | template 29 | void setMatrix(const Eigen::MatrixBase &A) { 30 | static_assert((Derived::RowsAtCompileTime == 2) && (Derived::ColsAtCompileTime == 2), "Only 2x2 supported for now"); 31 | 32 | const Real a_minus_c = A(0, 0) - A(1, 1); 33 | const Real b = A(0, 1); 34 | if (std::abs(b - A(1, 0)) > 1e-15) throw std::runtime_error("Only symmetric matrices are supported"); 35 | // d := descriminant of characteristic quadratic 36 | const Real sqrt_d = std::sqrt(a_minus_c * a_minus_c + 4 * b * b); 37 | m_Lambda << 0.5 * (A.trace() + sqrt_d), 38 | 0.5 * (A.trace() - sqrt_d); 39 | 40 | V2d q0(a_minus_c + sqrt_d, 2 * b); 41 | q0.normalize(); 42 | m_Q.col(0) = q0; 43 | m_Q.col(1) << -q0[1], q0[0]; 44 | 45 | if (sqrt_d < 1e-14) m_degenerate = true; 46 | else m_degenerate = false; 47 | } 48 | 49 | auto q(size_t i) const { return m_Q.col(i); } 50 | Real lambda(size_t i) const { return m_Lambda[i]; } 51 | 52 | // Access Eigendecomposition 53 | const M2d & Q() const { return m_Q; } 54 | const V2d &Lambda() const { return m_Lambda; } 55 | 56 | V2d dLambda(const M2d &dA) const { return (m_Q.transpose() * dA * m_Q).diagonal(); } 57 | M2d dLambda(size_t i) const { return q(i) * q(i).transpose(); } 58 | Real dLambda(size_t i, const M2d &dA) const { return q(i).dot(dA * q(i)); } 59 | 60 | // Only correct for symmetric dA_b! 61 | V2d d2Lambda(const M2d &dA_a, const M2d &dA_b) const { 62 | if (m_degenerate) return V2d(0.0, 0.0); 63 | Real d2lambda_0 = (2.0 / (m_Lambda[0] - m_Lambda[1])) * (q(0).dot(dA_a * q(1))) * (q(0).dot(dA_b * q(1))); 64 | return V2d(d2lambda_0, -d2lambda_0); 65 | } 66 | 67 | // Only correct for symmetric dA! 68 | M2d delta_dLambda(size_t i, const M2d &dA) const { 69 | if (m_degenerate) return M2d::Zero(); 70 | double sign = (i == 0) ? 1.0 : -1.0; 71 | M2d result = ((sign * (2.0 / (m_Lambda[0] - m_Lambda[1])) * (q(0).dot(dA * q(1)))) * q(0)) * q(1).transpose(); 72 | symmetrize(result); 73 | return result; 74 | } 75 | 76 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 77 | private: 78 | M2d m_Q; 79 | V2d m_Lambda; 80 | 81 | bool m_degenerate; 82 | }; 83 | 84 | #endif /* end of include guard: EIGSENSITIVITY_HH */ 85 | -------------------------------------------------------------------------------- /IncompressibleBalloonEnergy.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // IncompressibleBalloonEnergy.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // Implementation of the incompressible neo-Hookean-based strain energy 6 | // density used in Skouras 2014: Designing Inflatable Structures (before 7 | // homogenizing away the wrinkles using a relaxed energy density). 8 | // 9 | // This energy is implemented as a function of the right Green-Green 10 | // deformation tensor. 11 | */ 12 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 13 | // Created: 04/04/2019 18:19:10 14 | //////////////////////////////////////////////////////////////////////////////// 15 | #ifndef INCOMPRESSIBLEBALLOONENERGY_HH 16 | #define INCOMPRESSIBLEBALLOONENERGY_HH 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | template 23 | struct IncompressibleBalloonEnergy { 24 | using V2d = Eigen::Matrix; 25 | using M2d = Eigen::Matrix; 26 | 27 | IncompressibleBalloonEnergy() { } 28 | 29 | template 30 | IncompressibleBalloonEnergy(const Eigen::MatrixBase &C) { setMatrix(C); } 31 | 32 | template 33 | void setMatrix(const Eigen::MatrixBase &C) { 34 | static_assert((Derived::RowsAtCompileTime == 2) && (Derived::ColsAtCompileTime == 2), "Only 2x2 supported for now"); 35 | 36 | Real a = C(0, 0), 37 | b = C(0, 1), 38 | c = C(1, 1); 39 | if (std::abs(b - C(1, 0)) > 1e-15) throw std::runtime_error("Asymmetric matrix"); 40 | 41 | m_C = C; 42 | m_trace_C = C.trace(); 43 | m_det_C = a * c - b * b; 44 | m_grad_det_C << c, -b, 45 | -b, a; 46 | } 47 | 48 | Real energy() const { 49 | return stiffness * (m_trace_C + 1.0 / m_det_C - 3.0); 50 | } 51 | 52 | Real denergy(const M2d &dC) const { 53 | return stiffness * (dC.trace() - (1.0 / (m_det_C * m_det_C)) * doubleContract(m_grad_det_C, dC)); 54 | } 55 | 56 | M2d denergy() const { 57 | return stiffness * (M2d::Identity() - (1.0 / (m_det_C * m_det_C)) * m_grad_det_C); 58 | } 59 | 60 | Real d2energy(const M2d &dC_a, const M2d &dC_b) const { 61 | return stiffness * ((2.0 / (m_det_C * m_det_C * m_det_C)) * doubleContract(m_grad_det_C, dC_a) * doubleContract(m_grad_det_C, dC_b) 62 | - (1.0 / (m_det_C * m_det_C)) * (dC_a(1, 1) * dC_b(0, 0) + dC_a(0, 0) * dC_b(1, 1) - 2 * dC_a(0, 1) * dC_b(0, 1))); 63 | } 64 | 65 | M2d delta_denergy(const M2d &dC) const { 66 | M2d adj_dC; 67 | adj_dC << dC(1, 1), -dC(0, 1), 68 | -dC(1, 0), dC(0, 0); 69 | return (((2.0 * stiffness / (m_det_C * m_det_C * m_det_C)) * doubleContract(m_grad_det_C, dC)) * m_grad_det_C 70 | - (stiffness / (m_det_C * m_det_C)) * adj_dC); 71 | } 72 | 73 | // Second derivatives evaluated at the reference configuration 74 | M2d delta_denergy_undeformed(const M2d &dC) const { 75 | return stiffness * (dC.trace() * M2d::Identity() + dC); 76 | } 77 | 78 | Real stiffness = 1.0; 79 | 80 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 81 | private: 82 | Real m_trace_C, m_det_C; 83 | M2d m_grad_det_C; 84 | M2d m_C; 85 | }; 86 | 87 | #endif /* end of include guard: INCOMPRESSIBLEBALLOONENERGY_HH */ 88 | -------------------------------------------------------------------------------- /InflatedSurfaceAnalysis.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // InflatedSurfaceAnalysis.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // Analyze properties of the inflated structure by extracting an interpolating 6 | // surface. 7 | */ 8 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 9 | // Created: 05/13/2019 11:49:15 10 | //////////////////////////////////////////////////////////////////////////////// 11 | #ifndef INFLATEDSURFACEANALYSIS_HH 12 | #define INFLATEDSURFACEANALYSIS_HH 13 | 14 | #include "InflatableSheet.hh" 15 | #include 16 | #include 17 | #include "curvature.hh" 18 | 19 | struct InflatedSurfaceAnalysis { 20 | using Real = InflatableSheet::Real; 21 | using V3d = InflatableSheet::V3d; 22 | using M3d = Eigen::Matrix; 23 | using MX3d = Eigen::Matrix; 24 | using VXd = Eigen::VectorXd; 25 | using Mesh = InflatableSheet::Mesh; 26 | 27 | struct MetricInfo { 28 | MX3d left_stretch; 29 | MX3d right_stretch; 30 | VXd sigma_1, sigma_2; 31 | 32 | MetricInfo(const Mesh &m, const MX3d &deformedPositions); 33 | }; 34 | 35 | InflatedSurfaceAnalysis(const InflatableSheet &sheet, const bool useWallTriCentroids = true); 36 | 37 | const Mesh &mesh() const { return *m_analysisMesh; } 38 | Mesh inflatedSurface() const { 39 | Mesh imesh = mesh(); 40 | imesh.setNodePositions(inflatedPositions); 41 | return imesh; 42 | } 43 | 44 | CurvatureInfo curvature() const; 45 | MetricInfo metric() const { return MetricInfo(mesh(), inflatedPositions); } 46 | 47 | MX3d inflatedPositions; 48 | private: 49 | std::unique_ptr m_analysisMesh; 50 | }; 51 | 52 | #endif /* end of include guard: INFLATEDSURFACEANALYSIS_HH */ 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Julian Panetta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MetricFittingEnergy.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // MetricFittingEnergy.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // Sheet material energy that is a function of the **deformation gradient**. 6 | */ 7 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 8 | // Created: 05/30/2019 17:15:18 9 | //////////////////////////////////////////////////////////////////////////////// 10 | #ifndef METRICFITTINGENERGY_HH 11 | #define METRICFITTINGENERGY_HH 12 | 13 | #include 14 | #include 15 | 16 | struct MetricFittingEnergy { 17 | using M2d = Eigen::Matrix2d; 18 | 19 | M2d targetMetric; 20 | 21 | void setMatrix(Eigen::Ref C) { 22 | m_C = C; 23 | } 24 | 25 | double energy() const { 26 | return 0.5 * (m_C - targetMetric).squaredNorm(); 27 | } 28 | 29 | M2d denergy() const { return m_C - targetMetric; } 30 | 31 | auto delta_denergy(Eigen::Ref dC) const { return dC; } // 4th order identity tensor 32 | 33 | M2d currMetric() const { 34 | return m_C; 35 | } 36 | 37 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 38 | private: 39 | M2d m_C, m_diff; 40 | }; 41 | 42 | #endif /* end of include guard: METRICFITTINGENERGY_HH */ 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Inflatables 2 | =========== 3 | 4 | 5 | 6 | This is the codebase for our Siggraph paper, 7 | [Computational Inverse Design of Surface-based Inflatables](http://julianpanetta.com/publication/inflatables/). 8 | The code is written primarily in C++, but it is meant to be used through the Python 9 | bindings. 10 | 11 | # Getting Started 12 | 13 | ## C++ Code Dependencies 14 | The C++ code relies on `Boost` and `CHOLMOD/UMFPACK`, which must be installed 15 | separately. 16 | 17 | The code also relies on several dependencies that are included as submodules: 18 | [MeshFEM](https://github.com/MeshFEM/MeshFEM), 19 | [libigl](https://github.com/libigl/libigl), 20 | 21 | Finally, it includes a version of Keenan Crane's [stripe patterns code](https://www.cs.cmu.edu/~kmcrane/Projects/StripePatterns/) 22 | modified to generate fusing curve patterns and fix a few issues with boundary handling. 23 | 24 | ### macOS 25 | You can install all the mandatory dependencies on macOS with [MacPorts](https://www.macports.org). When installing SuiteSparse, be sure to get a version linked against `Accelerate.framework` rather than `OpenBLAS`; on MacPorts this is achieved by requesting the `accelerate` variant, which is no longer the default. Simulations will run over 2x slower under `OpenBLAS`. 26 | 27 | ```bash 28 | # Build/version control tools, C++ code dependencies 29 | sudo port install cmake boost ninja 30 | sudo port install SuiteSparse +accelerate 31 | # Dependencies for jupyterlab/notebooks 32 | sudo port install python39 33 | # Dependencies for `shapely` module 34 | sudo port install geos 35 | # Install nodejs/npm using nvm 36 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash 37 | nvm install 17 && nvm use 17 38 | ``` 39 | 40 | ### Ubuntu 20.04 41 | A few more packages need to be installed on a fresh Ubuntu 20.04 install: 42 | ```bash 43 | # Build/version control tools 44 | sudo apt install git cmake ninja-build 45 | # Dependencies for C++ code 46 | sudo apt install libboost-filesystem-dev libboost-system-dev libboost-program-options-dev libsuitesparse-dev 47 | # Dependencies (pybind11, jupyterlab/notebooks) 48 | sudo apt install python3-pip npm 49 | sudo npm install npm@latest -g 50 | # Dependencies for `shapely` module 51 | sudo apt install libgeos-dev 52 | ``` 53 | 54 | ## Obtaining and Building 55 | 56 | Clone this repository *recursively* so that its submodules are also downloaded: 57 | 58 | ```bash 59 | git clone --recursive https://github.com/jpanetta/Inflatables 60 | ``` 61 | 62 | Build the C++ code and its Python bindings using `cmake` and your favorite 63 | build system. For example, with [`ninja`](https://ninja-build.org): 64 | 65 | ```bash 66 | cd Inflatables 67 | mkdir build && cd build 68 | cmake .. -GNinja 69 | ninja 70 | ``` 71 | 72 | ## Running the Jupyter Notebooks 73 | The preferred way to interact with the inflatables code is in a Jupyter notebook, 74 | using the Python bindings. 75 | We recommend that you install the Python dependencies and JupyterLab itself in a 76 | virtual environment (e.g., with [venv](https://docs.python.org/3/library/venv.html)). 77 | 78 | ```bash 79 | pip3 install wheel # Needed if installing in a virtual environment 80 | # Recent versions of jupyterlab and related packages cause problems: 81 | # JupyerLab 3.4 and later has a bug where the tab and status bar GUI 82 | # remains visible after taking a viewer fullscreen 83 | # ipykernel > 5.5.5 clutters the notebook with stdout content 84 | # ipywidgets 8 and juptyerlab-widgets 3.0 break pythreejs 85 | pip3 install jupyterlab==3.3.4 ipykernel==5.5.5 ipywidgets==7.7.2 jupyterlab-widgets==1.1.1 86 | # If necessary, follow the instructions in the warnings to add the Python user 87 | # bin directory (containing the 'jupyter' binary) to your PATH... 88 | 89 | git clone https://github.com/jpanetta/pythreejs 90 | cd pythreejs 91 | pip3 install -e . 92 | cd js 93 | jupyter labextension install . 94 | 95 | pip3 install matplotlib scipy 96 | pip3 install shapely # dependency of the fabrication file generation 97 | ``` 98 | 99 | You may need to add the following to your shell startup script for the installation of `pythreejs`'s dependencies during `pip3 install -e .` to succeed: 100 | ``` 101 | export NODE_OPTIONS=--openssl-legacy-provider; 102 | ``` 103 | 104 | Launch JupyterLab from the root python directory: 105 | ```bash 106 | cd python 107 | jupyter lab 108 | ``` 109 | 110 | Now try opening and running an demo notebook, e.g., 111 | [`python/Demos/ConcentricCircles.ipynb`](https://github.com/jpanetta/Inflatables/blob/master/python/Demos/ConcentricCircles.ipynb). 112 | 113 | For an example of the full inverse design pipeline--from input surface to fabrication file output--please see 114 | [`python/Demos/Lilium.ipynb`](https://github.com/jpanetta/Inflatables/blob/master/python/Demos/Lilium.ipynb). 115 | -------------------------------------------------------------------------------- /SheetOptimizer.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // SheetOptimizer.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // A class for optimizing the rest vertex positions of the inflatable sheet 6 | // to better fit a target surface. 7 | // 8 | // We assume that the sheet design is flat and lies on the z = 0 plane. 9 | */ 10 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 11 | // Created: 07/13/2019 19:27:21 12 | //////////////////////////////////////////////////////////////////////////////// 13 | #ifndef SHEETOPTIMIZER_HH 14 | #define SHEETOPTIMIZER_HH 15 | 16 | #include "InflatableSheet.hh" 17 | #include "TargetSurfaceFitter.hh" 18 | #include "CollapseBarrier.hh" 19 | 20 | #include 21 | 22 | // variables: equilibrium vars (deformed positions), followed by 23 | // design parameters (rest positions) 24 | struct SheetOptimizer { 25 | enum class EnergyType { Full, Simulation, Fitting, Smoothing, CollapseBarrier }; 26 | using Mesh = InflatableSheet::Mesh; 27 | using Real = InflatableSheet::Real; 28 | 29 | using V2d = InflatableSheet:: V2d; 30 | using VXd = InflatableSheet:: VXd; 31 | using M2d = InflatableSheet:: M2d; 32 | using M3d = InflatableSheet:: M3d; 33 | using M23d = InflatableSheet::M23d; 34 | using M32d = InflatableSheet::M32d; 35 | using MX2d = Eigen::Matrix; 36 | using VSFJ = VectorizedShapeFunctionJacobian<3, V2d>; 37 | 38 | SheetOptimizer(std::shared_ptr s, const Mesh &targetSurface); 39 | 40 | InflatableSheet &sheet() { return *m_sheet; } 41 | const InflatableSheet &sheet() const { return *m_sheet; } 42 | 43 | Mesh &mesh() { return sheet().mesh(); } 44 | const Mesh &mesh() const { return sheet().mesh(); } 45 | 46 | const TargetSurfaceFitter &targetSurfaceFitter() const { return m_targetSurfaceFitter; } 47 | TargetSurfaceFitter &targetSurfaceFitter() { return m_targetSurfaceFitter; } 48 | 49 | size_t numVars() const { return numEquilibriumVars() + numDesignVars(); } 50 | const VXd &getVars() const { return m_currVars; } 51 | 52 | size_t numEquilibriumVars() const { return sheet().numVars(); } 53 | size_t numDesignVars() const { return 2 * mesh().numVertices(); } 54 | 55 | size_t equilibriumVarOffset() const { return 0; } 56 | size_t designVarOffset() const { return equilibriumVarOffset() + numEquilibriumVars(); } 57 | 58 | // Construct a view accessing the design variables part of the full 59 | // variables vector "allVars" as a |V|x2 matrix of rest vertex positions. 60 | template 61 | auto restPositionsFromVariables(Vector &allVars) const { 62 | using MapType = Eigen::Map>::value, const MX2d, MX2d>>; 63 | return MapType(allVars.tail(numDesignVars()).data(), mesh().numVertices(), 2); 64 | } 65 | 66 | M23d getTriRestPositions(size_t ti) const { 67 | M23d out; 68 | const auto &tri = mesh().element(ti); 69 | for (const auto &v : tri.vertices()) 70 | out.col(v.localIndex()) = restPositionsFromVariables(m_currVars).col(v.index()); 71 | return out; 72 | } 73 | 74 | void setVars(const VXd &vars) { 75 | m_currVars = vars; 76 | auto &s = sheet(); 77 | s.setRestVertexPositions(restPositionsFromVariables(vars), 78 | vars.head(s.numVars())); 79 | 80 | m_targetSurfaceFitter.updateClosestPoints(s.deformedWallVertexPositions(), m_wallVtxOnBoundary); 81 | m_collapseBarrier.setPositions(restPositionsFromVariables(vars)); 82 | } 83 | 84 | Real energy(EnergyType etype = EnergyType::Full) const; 85 | VXd gradient(EnergyType etype = EnergyType::Full) const; 86 | 87 | const Real &weight(EnergyType etype) const { 88 | if (etype == EnergyType::Simulation) return m_weights[0]; 89 | if (etype == EnergyType::Fitting ) return m_weights[1]; 90 | if (etype == EnergyType::Smoothing ) return m_weights[2]; 91 | throw std::runtime_error("Unexpected EnergyType"); 92 | } 93 | Real &weight(EnergyType etype) { return const_cast(const_cast(*this).weight(etype)); } 94 | 95 | size_t hessianNNZ() const { return hessianSparsityPattern().nz; } // TODO: predict without constructing 96 | SuiteSparseMatrix hessianSparsityPattern(Real val = 0.0) const; 97 | void hessian(SuiteSparseMatrix &H, EnergyType etype = EnergyType::Full) const; // accumulate Hessian to H 98 | SuiteSparseMatrix hessian( EnergyType etype = EnergyType::Full) const; // construct and return Hessian 99 | 100 | private: 101 | std::shared_ptr m_sheet; 102 | TargetSurfaceFitter m_targetSurfaceFitter; 103 | CollapseBarrier<> m_collapseBarrier; 104 | std::array m_weights{{1.0, 1.0, 1.0}}; 105 | VXd m_currVars; 106 | SuiteSparseMatrix m_restLaplacian; 107 | std::vector m_wallVtxOnBoundary; 108 | }; 109 | 110 | #endif /* end of include guard: SHEETOPTIMIZER_HH */ 111 | -------------------------------------------------------------------------------- /SurfaceSampler.cc: -------------------------------------------------------------------------------- 1 | #include "SurfaceSampler.hh" 2 | 3 | #include 4 | #include 5 | 6 | struct SurfaceAABB : public igl::AABB { 7 | using Base = igl::AABB; 8 | using Base::Base; 9 | }; 10 | 11 | SurfaceSampler::SurfaceSampler(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F) 12 | : m_V(V), m_F(F) 13 | { 14 | m_surfaceAABB = std::make_unique(); 15 | m_surfaceAABB->init(m_V, m_F); 16 | } 17 | 18 | void SurfaceSampler::closestTriAndBaryCoords(const Eigen::MatrixXd &P, Eigen::VectorXi &I, Eigen::MatrixX3d &B) const { 19 | if (P.cols() != 3) throw std::runtime_error("P must be an X by 3 matrix"); 20 | Eigen::VectorXd dists; 21 | Eigen::MatrixXd C; // closest points in 3D 22 | m_surfaceAABB->squared_distance(m_V, m_F, P, dists, I, C); 23 | 24 | const size_t np = P.rows(); 25 | B.resize(np, 3); 26 | 27 | for (size_t i = 0; i < np; ++i) { 28 | Eigen::RowVector3d pt, baryCoords; 29 | double dist; 30 | igl::point_simplex_squared_distance<3>(C.row(i), m_V, m_F, I[i], dist, pt, baryCoords); 31 | B.row(i) = baryCoords; 32 | } 33 | } 34 | 35 | // Needed because m_surfaceAABB is a smart pointer to an incomplete type. 36 | SurfaceSampler::~SurfaceSampler() { } 37 | -------------------------------------------------------------------------------- /SurfaceSampler.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // SurfaceSampler.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // Sample from piecewise linear fields on a triangulated surface by 6 | // evaluating the field at the closest point to the query. 7 | */ 8 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 9 | // Created: 05/01/2019 23:55:30 10 | //////////////////////////////////////////////////////////////////////////////// 11 | #ifndef SURFACE_SAMPLER_HH 12 | #define SURFACE_SAMPLER_HH 13 | #include 14 | #include 15 | #include 16 | 17 | // Forward declare AABB data structure 18 | struct SurfaceAABB; 19 | 20 | struct SurfaceSampler { 21 | SurfaceSampler(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F); 22 | 23 | // P: (#points x dim) matrix of stacked query point row vectors 24 | // fieldValues (X x fieldDim) matrix of stacked per-vertex or per-triangle field values 25 | template 26 | PLField sample(const Eigen::MatrixXd &P, PLField &fieldValues) const { 27 | bool isPerTri = fieldValues.rows() == m_F.rows(); 28 | if (!isPerTri && (fieldValues.rows() != m_V.rows())) throw std::runtime_error("Invalid piecewise linear/constant field size"); 29 | 30 | Eigen::VectorXi I; 31 | Eigen::MatrixX3d B; 32 | closestTriAndBaryCoords(P, I, B); 33 | 34 | const size_t np = P.rows(); 35 | PLField outSamples(np, fieldValues.cols()); 36 | for (size_t i = 0; i < np; ++i) { 37 | int tri = I[i]; 38 | if (isPerTri) 39 | outSamples.row(i) = fieldValues.row(tri); 40 | else { 41 | outSamples.row(i) = B(i, 0) * fieldValues.row(m_F(tri, 0)) 42 | + B(i, 1) * fieldValues.row(m_F(tri, 1)) 43 | + B(i, 2) * fieldValues.row(m_F(tri, 2)); 44 | } 45 | } 46 | 47 | return outSamples; 48 | } 49 | 50 | // P: (#points x dim) matrix of stacked query point row vectors 51 | // I: index of closest triangle for each query point 52 | // B: (#points x 3) matrix of stacked barycentric coordinate vectors for each query point 53 | void closestTriAndBaryCoords(const Eigen::MatrixXd &P, Eigen::VectorXi &I, Eigen::MatrixX3d &B) const; 54 | 55 | ~SurfaceSampler(); // Needed because m_surfaceAABB is a smart pointer to an incomplete type. 56 | private: 57 | std::unique_ptr m_surfaceAABB; 58 | Eigen::MatrixXd m_V; 59 | Eigen::MatrixXi m_F; 60 | }; 61 | 62 | 63 | #endif /* end of include guard: SURFACE_SAMPLER_HH */ 64 | -------------------------------------------------------------------------------- /TargetAttractedInflation.cc: -------------------------------------------------------------------------------- 1 | #include "TargetAttractedInflation.hh" 2 | 3 | #include 4 | 5 | TargetAttractedInflation::TargetAttractedInflation(std::shared_ptr s, const Mesh &targetSurface) 6 | : nondimensionalization(*s), m_sheet(s), m_targetSurfaceFitter(std::make_unique(targetSurface)) 7 | { 8 | const auto &m = mesh(); 9 | const size_t nv = m.numVertices(); 10 | 11 | // Area in the original (unoptimized) rest mesh. This is used for scaling the 12 | // vertex contributions to the fitting energy. 13 | VXd vertexArea(VXd::Zero(nv)); 14 | for (const auto &e : m.elements()) 15 | for (const auto &v : e.vertices()) 16 | vertexArea[v.index()] += e->volume() / 3.0; 17 | 18 | const auto &wallVtxs = sheet().wallVertices(); 19 | m_wallVtxOnBoundary.resize(wallVtxs.size()); 20 | VXd queryPtArea(wallVtxs.size()); 21 | for (size_t i = 0; i < wallVtxs.size(); ++i) { 22 | m_wallVtxOnBoundary[i] = m.vertex(wallVtxs[i]).isBoundary(); 23 | queryPtArea[i] = vertexArea[wallVtxs[i]]; 24 | } 25 | m_targetSurfaceFitter->setQueryPtWeights(queryPtArea); 26 | 27 | m_targetSurfaceFitter->updateClosestPoints(sheet().deformedWallVertexPositions(), m_wallVtxOnBoundary); 28 | m_targetSurfaceFitter->holdClosestPointsFixed = true; 29 | } 30 | 31 | TargetAttractedInflation::Real TargetAttractedInflation::energy(EnergyType etype) const { 32 | Real result = 0.0; 33 | if ((etype == EnergyType::Full) || (etype == EnergyType::Simulation)) 34 | result += nondimensionalization.potentialEnergyScale() * m_sheet->energy(); 35 | 36 | if ((etype == EnergyType::Full) || (etype == EnergyType::Fitting)) 37 | result += (fittingWeight * nondimensionalization.fittingEnergyScale()) * m_targetSurfaceFitter->energy(); 38 | 39 | return result; 40 | } 41 | 42 | TargetAttractedInflation::VXd TargetAttractedInflation::gradient(EnergyType etype) const { 43 | VXd g = VXd::Zero(numVars()); 44 | 45 | if ((etype == EnergyType::Full) || (etype == EnergyType::Simulation)) { 46 | g += (nondimensionalization.potentialEnergyScale() 47 | * nondimensionalization.equilibriumVarScale()) // Compute gradient with respect to our re-scaled equilibrium variables. 48 | * m_sheet->gradient(); 49 | } 50 | 51 | // Note: gradUnweightedTargetFit already computes the gradient with respect to the re-scaled equilibrium variables. 52 | if ((etype == EnergyType::Full) || (etype == EnergyType::Fitting)) 53 | g += (fittingWeight * nondimensionalization.fittingEnergyScale()) * gradUnweightedTargetFit(); 54 | 55 | return g; 56 | } 57 | 58 | TargetAttractedInflation::VXd TargetAttractedInflation::gradUnweightedTargetFit() const { 59 | const auto &s = sheet(); 60 | 61 | VXd g = VXd::Zero(numVars()); 62 | auto gradQueryPt = m_targetSurfaceFitter->gradient(); 63 | gradQueryPt *= nondimensionalization.equilibriumVarScale(); // Compute gradient with respect to our re-scaled equilibrium variables. 64 | const auto &wv = s.wallVertices(); 65 | for (size_t i = 0; i < wv.size(); ++i) 66 | g.segment<3>(s.varIdx(0, wv[i], 0)) += gradQueryPt.row(i).transpose(); 67 | return g; 68 | } 69 | 70 | SuiteSparseMatrix TargetAttractedInflation::hessianSparsityPattern(Real val) const { 71 | return sheet().hessianSparsityPattern(val); 72 | } 73 | 74 | SuiteSparseMatrix TargetAttractedInflation::hessian(EnergyType etype) const { 75 | SuiteSparseMatrix H = hessianSparsityPattern(); 76 | hessian(H, etype); 77 | return H; 78 | } 79 | 80 | void TargetAttractedInflation::hessian(SuiteSparseMatrix &H, EnergyType etype) const { 81 | const auto &s = sheet(); 82 | 83 | H.setZero(); 84 | if ((etype == EnergyType::Full) || (etype == EnergyType::Simulation)) 85 | s.hessian(H); 86 | H *= nondimensionalization.potentialEnergyScale(); 87 | 88 | if ((etype == EnergyType::Full) || (etype == EnergyType::Fitting)) { 89 | const auto &tsf = targetSurfaceFitter(); 90 | const auto &wv = s.wallVertices(); 91 | for (size_t i = 0; i < wv.size(); ++i) { 92 | const auto &vtxHess = ((fittingWeight * nondimensionalization.fittingEnergyScale()) * tsf.vtx_hessian(i)).eval(); 93 | const size_t varOffset = s.varIdx(0, wv[i], 0); 94 | 95 | size_t hint = std::numeric_limits::max(); // not size_t max which would be come -1 on cast to int! 96 | for (size_t comp_a = 0; comp_a < 3; ++comp_a) 97 | for (size_t comp_b = comp_a; comp_b < 3; ++comp_b) 98 | hint = H.addNZ(varOffset + comp_a, varOffset + comp_b, vtxHess(comp_a, comp_b), hint); 99 | } 100 | } 101 | // Compute Hessian with respect to our re-scaled equilibrium variables. 102 | H *= std::pow(nondimensionalization.equilibriumVarScale(), 2); 103 | } 104 | -------------------------------------------------------------------------------- /TargetSurfaceFitter.cc: -------------------------------------------------------------------------------- 1 | #include "TargetSurfaceFitter.hh" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct TargetSurfaceAABB : public igl::AABB, 3> { 11 | using Base = igl::AABB, 3>; 12 | using Base::Base; 13 | }; 14 | 15 | TargetSurfaceFitter::TargetSurfaceFitter(const TargetSurfaceMesh &targetMesh) { 16 | setTargetSurface(targetMesh); 17 | } 18 | 19 | void TargetSurfaceFitter::setTargetSurface(const TargetSurfaceMesh &targetMesh) { 20 | m_tgt_surf_V = getV(targetMesh); 21 | m_tgt_surf_F = getF(targetMesh); 22 | igl::per_face_normals(m_tgt_surf_V, m_tgt_surf_F, m_tgt_surf_N); 23 | 24 | m_bdryEdgeFitter = BoundaryEdgeFitter(targetMesh); 25 | 26 | m_tgt_surf_aabb_tree = std::make_unique(); 27 | m_tgt_surf_aabb_tree->init(m_tgt_surf_V, m_tgt_surf_F); 28 | updateClosestPoints(queryPoints, m_queryPtIsBoundary); 29 | } 30 | 31 | void TargetSurfaceFitter::updateClosestPoints(const MX3d &pts, const std::vector &isBoundary) { 32 | if (pts.rows() != m_queryPtWeights.rows()) 33 | throw std::runtime_error("Number of query points does not match query point weight size"); 34 | queryPoints = pts; 35 | m_queryPtIsBoundary = isBoundary; 36 | if (isBoundary.size() != size_t(pts.rows())) throw std::runtime_error("Invalid isBoundary array"); 37 | const size_t npts = pts.rows(); 38 | 39 | if (holdClosestPointsFixed) return; 40 | 41 | BENCHMARK_SCOPED_TIMER_SECTION timer("Update closest points"); 42 | closestSurfPts.resize(npts, 3); 43 | closestSurfPtSensitivities.resize(npts); 44 | closestSurfItems.resize(npts); 45 | 46 | for (size_t pi = 0; pi < npts; ++pi) { 47 | // Boundary vertex: find the closest point on the target boundary. 48 | if (isBoundary[pi]) { 49 | Real lambda = 0.0; 50 | size_t closestEdge = 0; 51 | V3d p; 52 | m_bdryEdgeFitter.closestBarycoordsAndPt(queryPoints.row(pi), lambda, p, closestEdge); 53 | closestSurfPts.row(pi) = p.transpose(); 54 | 55 | if ((lambda == 0.0) || (lambda == 1.0)) 56 | closestSurfPtSensitivities[pi].setZero(); 57 | else { 58 | const auto &e = m_bdryEdgeFitter.edge(closestEdge).e; 59 | closestSurfPtSensitivities[pi] = e * e.transpose(); 60 | } 61 | closestSurfItems[pi] = closestEdge; 62 | 63 | continue; 64 | } 65 | 66 | // Interior vertex: find the closest point on the target surface. 67 | // Could be parallelized (libigl does this internally for multi-point queries) 68 | RowV3d p, query; 69 | query = queryPoints.row(pi); 70 | int closest_idx; 71 | 72 | Real sqdist = m_tgt_surf_aabb_tree->squared_distance(m_tgt_surf_V, m_tgt_surf_F, query, closest_idx, p); 73 | closestSurfPts.row(pi) = p; 74 | closestSurfItems [pi] = closest_idx; 75 | 76 | // Compute the sensitivity of the closest point projection with respect to the query point (dp_dx). 77 | // There are three cases depending on whether the closest point lies in the target surface's 78 | // interior, on one of its boundary edges, or on a boundary vertex. 79 | RowV3d barycoords; 80 | igl::point_simplex_squared_distance<3>(query, m_tgt_surf_V, m_tgt_surf_F, closest_idx, sqdist, p, barycoords); 81 | 82 | std::array nonzeroLoc; 83 | int numNonzero = 0; 84 | for (int i = 0; i < 3; ++i) { 85 | if (barycoords[i] == 0.0) continue; 86 | // It is extremely unlikely a vertex will be closest to a point/edge if this is not a stable association. 87 | // Therefore we assume even for smoothish surfaces that points are constrained to lie on their closest 88 | // simplex. 89 | nonzeroLoc[numNonzero++] = i; 90 | } 91 | assert(numNonzero >= 1); 92 | 93 | if (numNonzero == 3) { 94 | // If the closest point lies in the interior, the sensitivity is (I - n n^T) (the query point perturbation is projected onto the tangent plane). 95 | closestSurfPtSensitivities[pi] = M3d::Identity() - m_tgt_surf_N.row(closest_idx).transpose() * m_tgt_surf_N.row(closest_idx); 96 | } 97 | else if (numNonzero == 2) { 98 | // If the closest point lies on a boundary edge, we assume it can only slide along this edge (i.e., the constraint is active) 99 | // (The edge orientation doesn't matter.) 100 | RowV3d e = m_tgt_surf_V.row(m_tgt_surf_F(closest_idx, nonzeroLoc[0])) - 101 | m_tgt_surf_V.row(m_tgt_surf_F(closest_idx, nonzeroLoc[1])); 102 | e.normalize(); 103 | closestSurfPtSensitivities[pi] = e.transpose() * e; 104 | } 105 | else if (numNonzero == 1) { 106 | // If the closest point coincides with a boundary vertex, we assume it is "stuck" there (i.e., the constraint is active) 107 | closestSurfPtSensitivities[pi].setZero(); 108 | } 109 | else { 110 | assert(false); 111 | } 112 | } 113 | } 114 | 115 | TargetSurfaceFitter::~TargetSurfaceFitter() = default; 116 | -------------------------------------------------------------------------------- /circular_mean.hh: -------------------------------------------------------------------------------- 1 | #ifndef CIRCULAR_MEAN_HH 2 | #define CIRCULAR_MEAN_HH 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Measure the smallest signed arclength between two angles plotted on a unit circle. 9 | template 10 | R_ circularDistance(R_ a, R_ b) { 11 | R_ result = std::fmod(a - b, 2 * M_PI); 12 | if (result > M_PI) result -= 2 * M_PI; 13 | if (result < -M_PI) result += 2 * M_PI; 14 | return result; 15 | } 16 | 17 | template 18 | R_ sumSquaredCircularDist(R_ x, const AngleCollection &angles) { 19 | R_ result = 0.0; 20 | for (R_ theta : angles) { 21 | R_ d = circularDistance(theta, x); 22 | result += d * d; 23 | } 24 | return result; 25 | } 26 | 27 | // Find the "circular mean" of a sequence of N angles "theta_i" by finding the angle "x" 28 | // that minimizes the total squared circular distance to each angle: 29 | // min_{x, k_i} 1/2 (theta_i + 2 * pi * k_i - x)^2 30 | // where k_i are integers chosen to ensure ensure the smaller of the two 31 | // possible arcs is used to measure the angular distance (so dist is always in 32 | // [-pi, pi]). Knowing the optimal k_i, we can easily solve for x: 33 | // N * x = (sum_i theta_i) + 2 * pi k 34 | // where integer "k" is the sum of the optimal k_i. 35 | // Since we really only care about the value of x mod 2 * pi, there are only N distinct 36 | // possible values for k ({0, ..., N - 1}). We simply check each value and pick 37 | // one that yields the minimal distance. 38 | // Note: the following implementation is O(N^2), but it is not too difficult to make 39 | // it O(N) by avoiding the full recalculation of the distance objective for each k. 40 | template 41 | auto circularMean(const AngleCollection &angles) -> std::remove_cv_t> { 42 | using R_ = std::remove_cv_t>; 43 | size_t N = angles.size(); 44 | R_ meanAngle = std::accumulate(angles.begin(), angles.end(), 0.0) / N; 45 | 46 | R_ minDist = std::numeric_limits::max(); 47 | R_ optimalAngle = 0; 48 | for (size_t n = 0; n < N; ++n) { 49 | R_ candidate = meanAngle + (2 * M_PI * n) / N; 50 | R_ candidateDist = sumSquaredCircularDist(candidate, angles); 51 | if (candidateDist < minDist) { 52 | minDist = candidateDist; 53 | optimalAngle = candidate; 54 | } 55 | 56 | } 57 | return std::fmod(optimalAngle, 2 * M_PI); 58 | } 59 | 60 | #endif /* end of include guard: CIRCULAR_MEAN_HH */ 61 | -------------------------------------------------------------------------------- /cmake/FindKnitro.cmake: -------------------------------------------------------------------------------- 1 | # - Find Knitro 2 | # Searches for includes/libraries using environment variable $KNITRO_PATH or $KNITRO_DIR 3 | # KNITRO_INCLUDE_DIRS - where to find knitro.h and (separately) the c++ interface 4 | # KNITRO_LIBRARIES - List of libraries needed to use knitro. 5 | # KNITRO_FOUND - True if knitro found. 6 | 7 | 8 | IF (KNITRO_INCLUDE_DIRS) 9 | # Already in cache, be silent 10 | SET (knitro_FIND_QUIETLY TRUE) 11 | ENDIF (KNITRO_INCLUDE_DIRS) 12 | 13 | FIND_PATH(KNITRO_INCLUDE_DIR knitro.h 14 | HINTS 15 | $ENV{KNITRO_PATH}/include 16 | $ENV{KNITRO_DIR}/include 17 | ) 18 | 19 | FIND_LIBRARY (KNITRO_LIBRARY NAMES knitro knitro1031 20 | HINTS 21 | $ENV{KNITRO_PATH}/lib 22 | $ENV{KNITRO_DIR}/lib 23 | ) 24 | 25 | # handle the QUIETLY and REQUIRED arguments and set KNITRO_FOUND to TRUE if 26 | # all listed variables are TRUE 27 | INCLUDE (FindPackageHandleStandardArgs) 28 | FIND_PACKAGE_HANDLE_STANDARD_ARGS (KNITRO DEFAULT_MSG 29 | KNITRO_LIBRARY 30 | KNITRO_INCLUDE_DIR) 31 | 32 | IF(KNITRO_FOUND) 33 | SET (KNITRO_LIBRARIES ${KNITRO_LIBRARY}) 34 | SET (KNITRO_INCLUDE_DIRS "${KNITRO_INCLUDE_DIR}" "${KNITRO_INCLUDE_DIR}/../examples/C++/include") 35 | ELSE (KNITRO_FOUND) 36 | SET (KNITRO_LIBRARIES) 37 | ENDIF (KNITRO_FOUND) 38 | 39 | MARK_AS_ADVANCED (KNITRO_LIBRARY KNITRO_INCLUDE_DIR KNITRO_INCLUDE_DIRS KNITRO_LIBRARIES) 40 | -------------------------------------------------------------------------------- /cmake/FindLIBIGL.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find the LIBIGL library 2 | # Once done this will define 3 | # 4 | # LIBIGL_FOUND - system has LIBIGL 5 | # LIBIGL_INCLUDE_DIR - **the** LIBIGL include directory 6 | if(LIBIGL_FOUND) 7 | return() 8 | endif() 9 | 10 | find_path(LIBIGL_INCLUDE_DIR igl/readOBJ.h 11 | HINTS 12 | ENV LIBIGL 13 | ENV LIBIGLROOT 14 | ENV LIBIGL_ROOT 15 | ENV LIBIGL_DIR 16 | PATHS 17 | ${CMAKE_SOURCE_DIR}/3rdparty/libigl 18 | ${CMAKE_SOURCE_DIR}/../.. 19 | ${CMAKE_SOURCE_DIR}/.. 20 | ${CMAKE_SOURCE_DIR} 21 | ${CMAKE_SOURCE_DIR}/libigl 22 | ${CMAKE_SOURCE_DIR}/../libigl 23 | ${CMAKE_SOURCE_DIR}/../../libigl 24 | /usr 25 | /usr/local 26 | /usr/local/igl/libigl 27 | PATH_SUFFIXES include 28 | ) 29 | 30 | include(FindPackageHandleStandardArgs) 31 | find_package_handle_standard_args(LIBIGL 32 | "\nlibigl not found --- You can download it using:\n\tgit clone --recursive https://github.com/libigl/libigl.git ${CMAKE_SOURCE_DIR}/../libigl" 33 | LIBIGL_INCLUDE_DIR) 34 | mark_as_advanced(LIBIGL_INCLUDE_DIR) 35 | 36 | list(APPEND CMAKE_MODULE_PATH "${LIBIGL_INCLUDE_DIR}/../cmake") 37 | include(libigl) 38 | -------------------------------------------------------------------------------- /cmake/FindMPFR.cmake: -------------------------------------------------------------------------------- 1 | # Try to find the MPFR library 2 | # See http://www.mpfr.org/ 3 | # 4 | # This module supports requiring a minimum version, e.g. you can do 5 | # find_package(MPFR 2.3.0) 6 | # to require version 2.3.0 to newer of MPFR. 7 | # 8 | # Once done this will define 9 | # 10 | # MPFR_FOUND - system has MPFR lib with correct version 11 | # MPFR_INCLUDES - the MPFR include directory 12 | # MPFR_LIBRARIES - the MPFR library 13 | # MPFR_VERSION - MPFR version 14 | 15 | # Copyright (c) 2006, 2007 Montel Laurent, 16 | # Copyright (c) 2008, 2009 Gael Guennebaud, 17 | # Copyright (c) 2010 Jitse Niesen, 18 | # Copyright (c) 2015 Jack Poulson, 19 | # Redistribution and use is allowed according to the terms of the BSD license. 20 | 21 | find_path(MPFR_INCLUDES NAMES mpfr.h PATHS $ENV{GMPDIR} $ENV{MPFRDIR} 22 | ${INCLUDE_INSTALL_DIR}) 23 | 24 | # Set MPFR_FIND_VERSION to 1.0.0 if no minimum version is specified 25 | if(NOT MPFR_FIND_VERSION) 26 | if(NOT MPFR_FIND_VERSION_MAJOR) 27 | set(MPFR_FIND_VERSION_MAJOR 1) 28 | endif() 29 | if(NOT MPFR_FIND_VERSION_MINOR) 30 | set(MPFR_FIND_VERSION_MINOR 0) 31 | endif() 32 | if(NOT MPFR_FIND_VERSION_PATCH) 33 | set(MPFR_FIND_VERSION_PATCH 0) 34 | endif() 35 | set(MPFR_FIND_VERSION 36 | "${MPFR_FIND_VERSION_MAJOR}.${MPFR_FIND_VERSION_MINOR}.${MPFR_FIND_VERSION_PATCH}") 37 | endif() 38 | 39 | if(MPFR_INCLUDES) 40 | # Query MPFR_VERSION 41 | file(READ "${MPFR_INCLUDES}/mpfr.h" _mpfr_version_header) 42 | 43 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_MAJOR[ \t]+([0-9]+)" 44 | _mpfr_major_version_match "${_mpfr_version_header}") 45 | set(MPFR_MAJOR_VERSION "${CMAKE_MATCH_1}") 46 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_MINOR[ \t]+([0-9]+)" 47 | _mpfr_minor_version_match "${_mpfr_version_header}") 48 | set(MPFR_MINOR_VERSION "${CMAKE_MATCH_1}") 49 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_PATCHLEVEL[ \t]+([0-9]+)" 50 | _mpfr_patchlevel_version_match "${_mpfr_version_header}") 51 | set(MPFR_PATCHLEVEL_VERSION "${CMAKE_MATCH_1}") 52 | 53 | set(MPFR_VERSION 54 | ${MPFR_MAJOR_VERSION}.${MPFR_MINOR_VERSION}.${MPFR_PATCHLEVEL_VERSION}) 55 | 56 | # Check whether found version exceeds minimum required 57 | if(${MPFR_VERSION} VERSION_LESS ${MPFR_FIND_VERSION}) 58 | set(MPFR_VERSION_OK FALSE) 59 | message(STATUS "MPFR version ${MPFR_VERSION} found in ${MPFR_INCLUDES}, " 60 | "but at least version ${MPFR_FIND_VERSION} is required") 61 | else() 62 | set(MPFR_VERSION_OK TRUE) 63 | endif() 64 | endif() 65 | 66 | find_library(MPFR_LIBRARIES mpfr 67 | PATHS $ENV{GMPDIR} $ENV{MPFRDIR} ${LIB_INSTALL_DIR}) 68 | 69 | include(FindPackageHandleStandardArgs) 70 | find_package_handle_standard_args(MPFR DEFAULT_MSG 71 | MPFR_INCLUDES MPFR_LIBRARIES MPFR_VERSION_OK) 72 | mark_as_advanced(MPFR_INCLUDES MPFR_LIBRARIES) 73 | -------------------------------------------------------------------------------- /cmake/UseColors.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # When using Clang, there is nothing to do: colors are enabled by default 3 | # When using GCC >= 4.9, colored diagnostics can be enabled natively 4 | # When using an older version, one can use gccfilter (a perl script) 5 | # 6 | # I do not recommend using gccfilter as of now (May 2014), because it seems to 7 | # be bugged. But if you still want to try, here is how to install it on Ubuntu: 8 | # 9 | # 10 | # 1) Download the perl script and add it to you $PATH 11 | # mkdir -p ~/.local/bin 12 | # wget -P ~/.local/bin http://www.mixtion.org/gccfilter/gccfilter 13 | # chmod +x ~/local/bin/gccfilter 14 | # echo 'PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc 15 | # 16 | # 2) Install the dependencies 17 | # * Term::ANSIColor 18 | # sudo cpan 19 | # cpan> install Term::ANSIColor 20 | # * The module "Getopt::Long" is included in "perl-base" 21 | # * For Getopt::ArgvFile and Regexp::Common ... 22 | # sudo apt-get install libgetopt-argvfile-perl libregexp-common-perl 23 | # 24 | ################################################################################ 25 | 26 | if(CMAKE_COMPILER_IS_GNUCXX) 27 | # If GCC >= 4.9, just activate the right option 28 | if(NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) 29 | message(STATUS "GCC >= 4.9 detected, enabling colored diagnostics") 30 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=auto") 31 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fdiagnostics-color=auto") 32 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fdiagnostics-color=auto") 33 | return() 34 | endif() 35 | # If GCC < 4.9, maybe we can use gccfilter 36 | find_program(GCC_FILTER gccfilter) 37 | if(GCC_FILTER) 38 | option(COLOR_GCC "Use GCCFilter to color compiler output messages" OFF) 39 | set(COLOR_GCC_OPTIONS "-c -r -w" CACHE STRING "Arguments that are passed to gccfilter when output coloring is switchend on. Defaults to -c -r -w.") 40 | if(COLOR_GCC) 41 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${GCC_FILTER} ${COLOR_GCC_OPTIONS}") 42 | message(STATUS "Using gccfilter for colored diagnostics") 43 | endif() 44 | endif() 45 | endif() 46 | -------------------------------------------------------------------------------- /curvature.cc: -------------------------------------------------------------------------------- 1 | #include // must come first so igl::Triplet doesn't get confused with ::Triplet from MeshFEM 2 | 3 | #include "curvature.hh" 4 | 5 | CurvatureInfo::CurvatureInfo(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F) { 6 | // Compute curvature directions via quadric fitting 7 | // Note: the comments in libigl are incorrect; reading the function 8 | // and example 203 makes it clear that the (algebraically) *minimal* 9 | // curvature quantities are output first, maximal second. 10 | igl::principal_curvature(V, F, d_2, d_1, kappa_2, kappa_1); 11 | } 12 | -------------------------------------------------------------------------------- /curvature.hh: -------------------------------------------------------------------------------- 1 | #ifndef CURVATURE_HH 2 | #define CURVATURE_HH 3 | 4 | #include 5 | #include 6 | 7 | struct CurvatureInfo { 8 | // Per vertex curvature quantities 9 | Eigen::MatrixX3d d_1, d_2; 10 | Eigen::VectorXd kappa_1, kappa_2; 11 | 12 | Eigen::VectorXd meanCurvature() const { return 0.5 * (kappa_1 + kappa_2); } 13 | Eigen::VectorXd gaussianCurvature() const { return kappa_1.array() * kappa_2.array(); } 14 | 15 | const Eigen::MatrixX3d &d(size_t i) const { 16 | if (i == 0) return d_1; 17 | if (i == 1) return d_2; 18 | throw std::runtime_error("Index i out of bounds"); 19 | } 20 | 21 | CurvatureInfo(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F); 22 | 23 | template 24 | CurvatureInfo(const Mesh &m) : CurvatureInfo(getV(m), getF(m)) { } 25 | }; 26 | 27 | #endif /* end of include guard: CURVATURE_HH */ 28 | -------------------------------------------------------------------------------- /examples/full_sphere.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeshFEM/Inflatables/950525b9dea668b4c6c6ae85be56a4ccd2d004b8/examples/full_sphere.msh -------------------------------------------------------------------------------- /examples/half_sphere.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeshFEM/Inflatables/950525b9dea668b4c6c6ae85be56a4ccd2d004b8/examples/half_sphere.msh -------------------------------------------------------------------------------- /examples/julius.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeshFEM/Inflatables/950525b9dea668b4c6c6ae85be56a4ccd2d004b8/examples/julius.msh -------------------------------------------------------------------------------- /examples/lilium.msh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeshFEM/Inflatables/950525b9dea668b4c6c6ae85be56a4ccd2d004b8/examples/lilium.msh -------------------------------------------------------------------------------- /examples/octagon.obj: -------------------------------------------------------------------------------- 1 | v 0 1 0 2 | v -0.70710700000000004 0.70710700000000004 0 3 | v -1 -0 0 4 | v -0.70710700000000004 -0.70710700000000004 0 5 | v 0 -1 0 6 | v 0.70710700000000004 -0.70710700000000004 0 7 | v 1 0 0 8 | v 0.70710700000000004 0.70710700000000004 0 9 | v -7.4707518349370616e-07 -7.4707518349370616e-07 0 10 | v -0.50000052826209174 -0.2071072188130918 0 11 | v -0.25000026413104587 -0.6035536094065459 0 12 | v -0.41421416571982922 0.29289296245808744 0 13 | v 0.042893117182491704 0.49999959441776987 0 14 | v 0.2499999359112251 -0.45710728290177083 0 15 | v 0.4999996439877582 -0.02345900722875954 0 16 | v 0.44236095942752529 0.3875202424334202 0 17 | f 9 10 11 18 | f 12 9 13 19 | f 11 5 14 20 | f 10 12 3 21 | f 4 10 3 22 | f 3 12 2 23 | f 13 2 12 24 | f 14 5 6 25 | f 11 10 4 26 | f 13 1 2 27 | f 12 10 9 28 | f 6 15 14 29 | f 4 5 11 30 | f 8 13 16 31 | f 16 9 15 32 | f 8 1 13 33 | f 15 6 7 34 | f 9 11 14 35 | f 16 7 8 36 | f 9 14 15 37 | f 9 16 13 38 | f 7 16 15 39 | -------------------------------------------------------------------------------- /examples/paraboloid.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // paraboloid.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // Generate a triangle mesh of a paraboloid quadric surface with specified 6 | // principal curvatures at the origin. 7 | */ 8 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 9 | // Created: 01/12/2018 18:05:49 10 | //////////////////////////////////////////////////////////////////////////////// 11 | #ifndef PARABOLOID_HH 12 | #define PARABOLOID_HH 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | inline 21 | void paraboloid(double triArea, double k1, double k2, 22 | std::vector &vertices, 23 | std::vector &elements, 24 | bool delaunay = true) 25 | { 26 | if (!delaunay) { 27 | std::vector gridVertices; 28 | std::vector gridElements; 29 | size_t nx = 64, ny = 64; 30 | gen_grid({nx, ny}, gridVertices, gridElements); 31 | 32 | std::vector quadIdx; 33 | quad_tri_subdiv(gridVertices, gridElements, vertices, elements, quadIdx); 34 | for (auto &v : vertices) { 35 | v[0] = 2.0 * v[0] / nx - 1.0; 36 | v[1] = 2.0 * v[1] / ny - 1.0; 37 | } 38 | } 39 | else { 40 | std::vector pts = { {-1, -1}, {1, -1}, {1, 1}, {-1, 1} }; 41 | std::vector> edges = { {0, 1}, {1, 2}, {2, 3}, {3, 0} }; 42 | triangulatePSLG(pts, edges, std::vector(), vertices, elements, triArea, "Dq32"); 43 | } 44 | 45 | // plot 1/2 (k1 x^2 + k2 y^2) over [-1, 1]^2 46 | for (auto &v : vertices) 47 | v[2] = 0.5 * (k1 * v[0] * v[0] + k2 * v[1] * v[1]); 48 | } 49 | 50 | #endif /* end of include guard: PARABOLOID_HH */ 51 | -------------------------------------------------------------------------------- /examples/single_tri.obj: -------------------------------------------------------------------------------- 1 | v 0 0 2 | v 1 0 3 | v 0 1 4 | f 1 2 3 5 | -------------------------------------------------------------------------------- /fit_metric_newton.cc: -------------------------------------------------------------------------------- 1 | #include "fit_metric_newton.hh" 2 | #include 3 | 4 | struct MetricFittingProblem : public NewtonProblem { 5 | MetricFittingProblem(MetricFitter &mfit) 6 | : m_mfit(mfit), m_hessianSparsity(mfit.hessianSparsityPattern()) { } 7 | 8 | virtual void setVars(const Eigen::VectorXd &vars) override { m_mfit.setVars(vars); } 9 | virtual const Eigen::VectorXd getVars() const override { return m_mfit.getVars(); } 10 | virtual size_t numVars() const override { return m_mfit.numVars(); } 11 | 12 | virtual Real energy() const override { return m_mfit.energy(); } 13 | 14 | virtual Eigen::VectorXd gradient(bool /* freshIterate */ = false) const override { 15 | auto result = m_mfit.gradient(); 16 | return result; 17 | } 18 | 19 | virtual SuiteSparseMatrix hessianSparsityPattern() const override { /* m_hessianSparsity.fill(1.0); */ return m_hessianSparsity; } 20 | 21 | void setCustomIterationCallback(const CallbackFunction &cb) { m_customCallback = cb; } 22 | 23 | protected: 24 | virtual void m_evalHessian(SuiteSparseMatrix &result, bool /* projectionMask */) const override { 25 | result.setZero(); 26 | m_mfit.hessian(result); 27 | } 28 | virtual void m_evalMetric(SuiteSparseMatrix &result) const override { 29 | // TODO: mass matrix? 30 | result.setIdentity(true); 31 | } 32 | 33 | virtual void m_iterationCallback(size_t i) override { if (m_customCallback) m_customCallback(i); } 34 | 35 | CallbackFunction m_customCallback; 36 | 37 | MetricFitter &m_mfit; 38 | mutable SuiteSparseMatrix m_hessianSparsity; 39 | }; 40 | 41 | ConvergenceReport fit_metric_newton(MetricFitter &mfit, const std::vector &fixedVars, const NewtonOptimizerOptions &opts, CallbackFunction customCallback) { 42 | auto problem = std::make_unique(mfit); 43 | problem->addFixedVariables(fixedVars); 44 | problem->setCustomIterationCallback(customCallback); 45 | NewtonOptimizer opt(std::move(problem)); 46 | opt.options = opts; 47 | return opt.optimize(); 48 | } 49 | -------------------------------------------------------------------------------- /fit_metric_newton.hh: -------------------------------------------------------------------------------- 1 | #ifndef FIT_METRIC_NEWTON_HH 2 | #define FIT_METRIC_NEWTON_HH 3 | 4 | #include 5 | #include "MetricFitter.hh" 6 | 7 | using CallbackFunction = std::function; 8 | 9 | ConvergenceReport fit_metric_newton(MetricFitter &mfit, const std::vector &fixedVars, const NewtonOptimizerOptions &opts, CallbackFunction = nullptr); 10 | 11 | #endif /* end of include guard: FIT_METRIC_NEWTON_HH */ 12 | -------------------------------------------------------------------------------- /inflation_newton.cc: -------------------------------------------------------------------------------- 1 | #include "inflation_newton.hh" 2 | #include 3 | 4 | template 5 | struct InflationNewtonProblem : public NewtonProblem { 6 | InflationNewtonProblem(ISheet &isheet) 7 | : m_sheet(isheet), m_hessianSparsity(isheet.hessianSparsityPattern()) { } 8 | 9 | virtual void setVars(const Eigen::VectorXd &vars) override { m_sheet.setVars(vars.cast()); } 10 | virtual const Eigen::VectorXd getVars() const override { return m_sheet.getVars().template cast(); } 11 | virtual size_t numVars() const override { return m_sheet.numVars(); } 12 | 13 | virtual Real energy() const override { return m_sheet.energy(); } 14 | 15 | virtual Eigen::VectorXd gradient(bool /* freshIterate */ = false) const override { 16 | auto result = m_sheet.gradient(); 17 | return result.template cast(); 18 | } 19 | 20 | void setCustomIterationCallback(const CallbackFunction &cb) { m_customCallback = cb; } 21 | 22 | virtual SuiteSparseMatrix hessianSparsityPattern() const override { /* m_hessianSparsity.fill(1.0); */ return m_hessianSparsity; } 23 | 24 | protected: 25 | virtual void m_evalHessian(SuiteSparseMatrix &result, bool /* projectionMask */) const override { 26 | result.setZero(); 27 | m_sheet.hessian(result); 28 | } 29 | virtual void m_evalMetric(SuiteSparseMatrix &result) const override { 30 | // TODO: mass matrix? 31 | result.setIdentity(true); 32 | } 33 | 34 | virtual void m_iterationCallback(size_t i) override { if (m_customCallback) m_customCallback(i); } 35 | 36 | CallbackFunction m_customCallback; 37 | 38 | ISheet &m_sheet; 39 | mutable SuiteSparseMatrix m_hessianSparsity; 40 | }; 41 | 42 | template 43 | std::unique_ptr get_inflation_optimizer(ISheet &isheet, const std::vector &fixedVars, const NewtonOptimizerOptions &opts, CallbackFunction customCallback) { 44 | auto problem = std::make_unique>(isheet); 45 | problem->addFixedVariables(fixedVars); 46 | problem->setCustomIterationCallback(customCallback); 47 | auto opt = std::make_unique(std::move(problem)); 48 | opt->options = opts; 49 | return opt; 50 | } 51 | 52 | template 53 | ConvergenceReport inflation_newton(ISheet &isheet, const std::vector &fixedVars, const NewtonOptimizerOptions &opts, CallbackFunction customCallback) { 54 | return get_inflation_optimizer(isheet, fixedVars, opts, customCallback)->optimize(); 55 | } 56 | 57 | // Explicit function template instantiations 58 | #include "InflatableSheet.hh" 59 | #include "TargetAttractedInflation.hh" 60 | template ConvergenceReport inflation_newton(InflatableSheet &, const std::vector &, const NewtonOptimizerOptions &, CallbackFunction); 61 | template ConvergenceReport inflation_newton(TargetAttractedInflation &, const std::vector &, const NewtonOptimizerOptions &, CallbackFunction); 62 | 63 | // The following shouldn't be necessary, but fix an undefined symbol error when loading the `inflation` Python module 64 | template std::unique_ptr get_inflation_optimizer(InflatableSheet &, const std::vector &, const NewtonOptimizerOptions &, CallbackFunction); 65 | template std::unique_ptr get_inflation_optimizer(TargetAttractedInflation &, const std::vector &, const NewtonOptimizerOptions &, CallbackFunction); 66 | -------------------------------------------------------------------------------- /inflation_newton.hh: -------------------------------------------------------------------------------- 1 | #ifndef INFLATION_NEWTON_HH 2 | #define INFLATION_NEWTON_HH 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using CallbackFunction = std::function; 9 | 10 | template 11 | std::unique_ptr get_inflation_optimizer(ISheet &isheet, const std::vector &fixedVars, const NewtonOptimizerOptions &opts = NewtonOptimizerOptions(), CallbackFunction = nullptr); 12 | 13 | template 14 | ConvergenceReport inflation_newton(ISheet &isheet, const std::vector &fixedVars, const NewtonOptimizerOptions &opts, CallbackFunction = nullptr); 15 | 16 | #endif /* end of include guard: INFLATION_NEWTON_HH */ 17 | -------------------------------------------------------------------------------- /parametrization_newton.cc: -------------------------------------------------------------------------------- 1 | #include "parametrization_newton.hh" 2 | #include 3 | 4 | #include "parametrization.hh" 5 | 6 | namespace parametrization { 7 | 8 | void applyBoundConstraints(const RegularizedParametrizer &rparam, std::vector &bc) { 9 | // Set bounds on alpha variables 10 | bc.reserve(rparam.numAlphaVars()); 11 | const size_t nvar = rparam.numVars(); 12 | const auto &vars = rparam.getVars(); 13 | for (size_t i = rparam.alphaOffset(); i < nvar; ++i) { 14 | if ((vars[i] < rparam.alphaMin()) || (vars[i] > rparam.alphaMax())) 15 | throw std::runtime_error("Alpha bound violated " + std::to_string(i - rparam.alphaOffset())); 16 | bc.emplace_back(i, rparam.alphaMin(), NewtonProblem::BoundConstraint::Type::LOWER); 17 | bc.emplace_back(i, rparam.alphaMax(), NewtonProblem::BoundConstraint::Type::UPPER); 18 | } 19 | } 20 | 21 | void applyBoundConstraints(const RegularizedParametrizerSVD &/* rparam */, NewtonProblem::BoundConstraint &/* bc */) { 22 | // No bound constraints... 23 | } 24 | 25 | template 26 | struct ParametrizationNewtonProblem : public NewtonProblem { 27 | ParametrizationNewtonProblem(ParametrizationEnergy &energy) 28 | : m_energy(energy), m_hessianSparsity(energy.hessianSparsityPattern()) { } 29 | 30 | virtual void setVars(const Eigen::VectorXd &vars) override { m_energy.setVars(vars); } 31 | virtual const Eigen::VectorXd getVars() const override { return m_energy.getVars(); } 32 | virtual size_t numVars() const override { return m_energy.numVars(); } 33 | 34 | virtual Real energy() const override { return m_energy.energy(); } 35 | 36 | virtual Eigen::VectorXd gradient(bool /* freshIterate */ = false) const override { 37 | auto result = m_energy.gradient(); 38 | return result; 39 | } 40 | 41 | virtual SuiteSparseMatrix hessianSparsityPattern() const override { /* m_hessianSparsity.fill(1.0); */ return m_hessianSparsity; } 42 | 43 | protected: 44 | virtual void m_evalHessian(SuiteSparseMatrix &result, bool projectionMask) const override { 45 | result.setZero(); 46 | m_energy.hessian(result, projectionMask); 47 | } 48 | virtual void m_evalMetric(SuiteSparseMatrix &result) const override { 49 | // TODO: mass matrix? 50 | result.setIdentity(true); 51 | } 52 | 53 | ParametrizationEnergy &m_energy; 54 | mutable SuiteSparseMatrix m_hessianSparsity; 55 | }; 56 | 57 | template 58 | ConvergenceReport regularized_parametrization_newton(RParam &rparam, const std::vector &fixedVars, const NewtonOptimizerOptions &opts) { 59 | auto problem = std::make_unique>(rparam); 60 | problem->addFixedVariables(fixedVars); 61 | NewtonOptimizer opt(std::move(problem)); 62 | opt.options = opts; 63 | return opt.optimize(); 64 | } 65 | 66 | //////////////////////////////////////////////////////////////////////////////// 67 | // Explicit instantiations 68 | //////////////////////////////////////////////////////////////////////////////// 69 | template ConvergenceReport regularized_parametrization_newton(RegularizedParametrizer &rparam, const std::vector &fixedVars, const NewtonOptimizerOptions &opts); 70 | template ConvergenceReport regularized_parametrization_newton(RegularizedParametrizerSVD &rparam, const std::vector &fixedVars, const NewtonOptimizerOptions &opts); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /parametrization_newton.hh: -------------------------------------------------------------------------------- 1 | #ifndef PARAMETRIZATION_NEWTON_HH 2 | #define PARAMETRIZATION_NEWTON_HH 3 | 4 | #include 5 | 6 | namespace parametrization { 7 | 8 | template 9 | ConvergenceReport regularized_parametrization_newton(RParam &rparam, const std::vector &fixedVars, const NewtonOptimizerOptions &opts); 10 | 11 | } 12 | 13 | #endif /* end of include guard: PARAMETRIZATION_NEWTON_HH */ 14 | -------------------------------------------------------------------------------- /python/Demos/ForwardDesign.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Basic forward-design example\n", 8 | "Demonstrate the basic input format that can be used for forward design.\n", 9 | "Designs can have fused regions and holes, specified by the `fusedPts` and `holePts`, respectively.\n", 10 | "\n", 11 | "The provided design doesn't do anything interesting..." 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import sys; sys.path.append('..')\n", 21 | "import inflation, mesh, sheet_meshing\n", 22 | "from tri_mesh_viewer import TriMeshViewer as Viewer\n", 23 | "import triangulation\n", 24 | "import numpy as np\n", 25 | "import utils, py_newton_optimizer, benchmark" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "V, E = mesh.load_raw('data/ForwardDesign/example.obj')\n", 35 | "fusedPts = list(np.loadtxt('data/ForwardDesign/example_fusedPts.txt').reshape((-1, 2)))\n", 36 | "holePts = list(np.loadtxt('data/ForwardDesign/example_holePts.txt').reshape((-1, 2)))" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "m, iwv, iwbv = sheet_meshing.forward_design_mesh(V, E, fusedPts, holePts, np.prod(utils.bbox_dims(V)[0:2]) / 1e4)\n", 46 | "isheet = inflation.InflatableSheet(m, iwv)" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "import visualization\n", 56 | "visualization.plot_2d_mesh(m, pointList=np.where(iwv))" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": null, 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "viewer = Viewer(isheet, wireframe=True)\n", 66 | "viewer.show()" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "import time\n", 76 | "benchmark.reset()\n", 77 | "isheet.setUseTensionFieldEnergy(True)\n", 78 | "isheet.setUseHessianProjectedEnergy(False)\n", 79 | "opts = py_newton_optimizer.NewtonOptimizerOptions()\n", 80 | "opts.niter = 500\n", 81 | "isheet.pressure = 1\n", 82 | "framerate = 5 # Update every 5 iterations\n", 83 | "def cb(it):\n", 84 | " if it % framerate == 0:\n", 85 | " viewer.update()\n", 86 | "cr = inflation.inflation_newton(isheet, isheet.rigidMotionPinVars, opts, callback=cb)\n", 87 | "benchmark.report()" 88 | ] 89 | } 90 | ], 91 | "metadata": { 92 | "kernelspec": { 93 | "display_name": "Python 3", 94 | "language": "python", 95 | "name": "python3" 96 | }, 97 | "language_info": { 98 | "codemirror_mode": { 99 | "name": "ipython", 100 | "version": 3 101 | }, 102 | "file_extension": ".py", 103 | "mimetype": "text/x-python", 104 | "name": "python", 105 | "nbconvert_exporter": "python", 106 | "pygments_lexer": "ipython3", 107 | "version": "3.9.6" 108 | } 109 | }, 110 | "nbformat": 4, 111 | "nbformat_minor": 4 112 | } 113 | -------------------------------------------------------------------------------- /python/Demos/LogSpiral.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Simple parametric log spiral example\n", 8 | "In theory, a logarithmic spiral pattern produces a cone/anticone surface (the Gaussian curvature is a delta function, vanishing everywhere away from the central singularity).\n", 9 | "The total curvature is controlled by the spiral angle `alpha`.\n", 10 | "In practice, the physical system smoothes out the curvature singularity." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "import sys; sys.path.append('..')\n", 20 | "import inflation, numpy as np, importlib\n", 21 | "import fd_validation, visualization, parametric_pillows, wall_generation\n", 22 | "import parallelism, benchmark\n", 23 | "from numpy.linalg import norm" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "parallelism.set_max_num_tbb_threads(4)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "from ipywidgets import interactive, widgets\n", 42 | "def plotForAlpha(alpha): visualization.plot_line_segments(*parametric_pillows.logSpiralPlot(alpha=alpha, edgeLength=0.02, minDist=0.05, margin=.01))\n", 43 | "iplot = interactive(plotForAlpha, alpha = widgets.FloatSlider(min=1, max=90, value=70, step=1))\n", 44 | "iplot.children[-1].layout.height = '500px'\n", 45 | "display(iplot)" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": null, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "importlib.reload(parametric_pillows)\n", 55 | "importlib.reload(visualization)\n", 56 | "m, fuseMarkers, fuseEdges = wall_generation.triangulate_channel_walls(*parametric_pillows.logSpiralPlot(\n", 57 | " alpha=iplot.children[0].value, edgeLength=0.02, minDist=0.075, margin=0.025), 0.0002)\n", 58 | "visualization.plot_2d_mesh(m, pointList=np.where(np.array(fuseMarkers) == 1)[0], width=12, height=12, )" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "isheet = inflation.InflatableSheet(m, np.array(fuseMarkers) != 0)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "import py_newton_optimizer\n", 77 | "isheet.setUseTensionFieldEnergy(True)\n", 78 | "niter = 5000\n", 79 | "iterations_per_output = 10\n", 80 | "opts = py_newton_optimizer.NewtonOptimizerOptions()\n", 81 | "opts.useIdentityMetric = True\n", 82 | "opts.beta = 1e-4\n", 83 | "opts.gradTol = 1e-10\n", 84 | "opts.niter = iterations_per_output" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "from tri_mesh_viewer import TriMeshViewer\n", 94 | "viewer = TriMeshViewer(isheet.visualizationMesh(), width=1024, height=768)\n", 95 | "viewer.showWireframe()\n", 96 | "viewer.show()" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "import time\n", 106 | "isheet.pressure = 50\n", 107 | "benchmark.reset()\n", 108 | "for step in range(int(niter / iterations_per_output)):\n", 109 | " cr = inflation.inflation_newton(isheet, isheet.rigidMotionPinVars, opts)\n", 110 | " if cr.numIters() < iterations_per_output: break\n", 111 | " #isheet.writeDebugMesh('log_spiral_alpha_20/inflation_tf_step_{}.msh'.format(step))\n", 112 | " viewer.update(False, isheet.visualizationMesh())\n", 113 | " time.sleep(0.05) # Allow some mesh synchronization time for pythreejs\n", 114 | "benchmark.report()" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "isheet.tensionStateHistogram()" 124 | ] 125 | } 126 | ], 127 | "metadata": { 128 | "kernelspec": { 129 | "display_name": "Python 3", 130 | "language": "python", 131 | "name": "python3" 132 | }, 133 | "language_info": { 134 | "codemirror_mode": { 135 | "name": "ipython", 136 | "version": 3 137 | }, 138 | "file_extension": ".py", 139 | "mimetype": "text/x-python", 140 | "name": "python", 141 | "nbconvert_exporter": "python", 142 | "pygments_lexer": "ipython3", 143 | "version": "3.9.6" 144 | } 145 | }, 146 | "nbformat": 4, 147 | "nbformat_minor": 4 148 | } 149 | -------------------------------------------------------------------------------- /python/Demos/data/ForwardDesign/example_fusedPts.txt: -------------------------------------------------------------------------------- 1 | 0.0 0.0 -------------------------------------------------------------------------------- /python/Demos/data/ForwardDesign/example_holePts.txt: -------------------------------------------------------------------------------- 1 | 0.0 1.0 -------------------------------------------------------------------------------- /python/boundaries.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def getBoundaryVars(isheet): 4 | return [isheet.varIdx(0, i, c) for i in isheet.mesh().boundaryVertices() for c in range(3)] 5 | 6 | def getOuterBoundaryVars(isheet): 7 | """ 8 | Variables for vertices on the *outer* boundary loop of the mesh (excluding inner hole boundaries). 9 | Uses a simple heuristic: we expect the outermost boundary loop to be the longest. 10 | This of course can break for particularly jagged inner boundaries. 11 | """ 12 | m = isheet.mesh() 13 | V = m.vertices() 14 | BV = m.boundaryVertices() 15 | arclen = lambda l: np.linalg.norm(np.diff(V[BV[np.array(l)]], axis=0), axis=1).sum() 16 | outerLoopBdryVertices = max(m.boundaryLoops(), key=arclen) 17 | return [isheet.varIdx(0, BV[bvi], c) for bvi in outerLoopBdryVertices for c in range(3)] 18 | 19 | def getBdryWallVars(isheet): 20 | """ 21 | Variables for wall boundary vertices that are also mesh boundary vertices. 22 | """ 23 | import numpy as np 24 | m = isheet.mesh() 25 | isTrueWallVertex = np.zeros(m.numVertices(), dtype=np.bool) 26 | isTrueWallVertex[isheet.trueWallVertices()] = True 27 | bv = m.boundaryVertices() 28 | bdryWallVertices = [v for v in bv if isTrueWallVertex[v]] 29 | return [isheet.varIdx(0, i, c) for i in bdryWallVertices for c in range(3)] 30 | -------------------------------------------------------------------------------- /python/fd_validation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.linalg import norm 3 | from MeshFEM import sparse_matrices 4 | 5 | def preamble(obj, xeval, perturb, etype, fixedVars = []): 6 | if (xeval is None): xeval = obj.getVars() 7 | if (perturb is None): perturb = np.random.uniform(low=-1,high=1, size=obj.numVars()) 8 | if (etype is None): etype = obj.__class__.EnergyType.Full 9 | xold = obj.getVars() 10 | perturb = np.copy(perturb) 11 | perturb[fixedVars] = 0.0 12 | return (xold, xeval, perturb, etype) 13 | 14 | def fdGrad(obj, fd_eps, xeval = None, perturb = None, etype = None, fixedVars = []): 15 | xold, xeval, perturb, etype = preamble(obj, xeval, perturb, etype, fixedVars) 16 | 17 | def evalAt(x): 18 | obj.setVars(x) 19 | val = obj.energy(etype) 20 | return val 21 | 22 | fd_delta_E = (evalAt(xeval + perturb * fd_eps) - evalAt(xeval - perturb * fd_eps)) / (2 * fd_eps) 23 | obj.setVars(xold) 24 | 25 | return fd_delta_E 26 | 27 | def validateGrad(obj, fd_eps = 1e-6, xeval = None, perturb = None, etype = None, fixedVars = []): 28 | xold, xeval, perturb, etype = preamble(obj, xeval, perturb, etype, fixedVars) 29 | 30 | obj.setVars(xeval) 31 | g = obj.gradient(etype) 32 | analytic_delta_E = g.dot(perturb) 33 | 34 | fd_delta_E = fdGrad(obj, fd_eps, xeval, perturb, etype, fixedVars) 35 | 36 | return (fd_delta_E, analytic_delta_E) 37 | 38 | def validateHessian(obj, fd_eps = 1e-6, xeval = None, perturb = None, etype = None, fixedVars = []): 39 | xold, xeval, perturb, etype = preamble(obj, xeval, perturb, etype, fixedVars) 40 | 41 | def gradAt(x): 42 | obj.setVars(x) 43 | val = obj.gradient(etype) 44 | return val 45 | 46 | obj.setVars(xeval) 47 | h = obj.hessian(etype) 48 | fd_delta_grad = (gradAt(xeval + perturb * fd_eps) - gradAt(xeval - perturb * fd_eps)) / (2 * fd_eps) 49 | analytic_delta_grad = h.apply(perturb) 50 | 51 | obj.setVars(xold) 52 | 53 | return (norm(analytic_delta_grad - fd_delta_grad) / norm(fd_delta_grad), fd_delta_grad, analytic_delta_grad) 54 | 55 | def gradConvergence(obj, perturb=None, energyType=None, fixedVars = []): 56 | epsilons = np.logspace(-9, -3, 100) 57 | errors = [] 58 | if (energyType is None): energyType = obj.EnergyType.Full 59 | if (perturb is None): perturb = np.random.uniform(-1, 1, size=obj.numVars()) 60 | for eps in epsilons: 61 | fd, an = validateGrad(obj, etype=energyType, perturb=perturb, fd_eps=eps, fixedVars = fixedVars) 62 | err = np.abs(an - fd) / np.abs(an) 63 | errors.append(err) 64 | return (epsilons, errors, an) 65 | 66 | def gradConvergencePlot(obj, perturb=None, energyType=None, fixedVars = []): 67 | from matplotlib import pyplot as plt 68 | eps, errors, ignore = gradConvergence(obj, perturb, energyType, fixedVars) 69 | plt.title('Directional derivative fd test for gradient') 70 | plt.ylabel('Relative error') 71 | plt.xlabel('Step size') 72 | plt.loglog(eps, errors) 73 | plt.grid() 74 | 75 | def hessConvergence(obj, perturb=None, energyType=None, fixedVars = []): 76 | epsilons = np.logspace(-9, -3, 100) 77 | errors = [] 78 | if (energyType is None): energyType = obj.EnergyType.Full 79 | if (perturb is None): perturb = np.random.uniform(-1, 1, size=obj.numVars()) 80 | for eps in epsilons: 81 | err, fd, an = validateHessian(obj, etype=energyType, perturb=perturb, fd_eps=eps, fixedVars = fixedVars) 82 | errors.append(err) 83 | return (epsilons, errors, an) 84 | 85 | def hessConvergencePlot(obj, perturb=None, energyType=None, fixedVars = []): 86 | from matplotlib import pyplot as plt 87 | eps, errors, ignore = hessConvergence(obj, perturb, energyType, fixedVars) 88 | plt.title('Directional derivative fd test for Hessian') 89 | plt.ylabel('Relative error') 90 | plt.xlabel('Step size') 91 | plt.loglog(eps, errors) 92 | plt.grid() 93 | -------------------------------------------------------------------------------- /python/inflation_pressure_analysis.py: -------------------------------------------------------------------------------- 1 | import inflation, remesh, mesh, os, py_newton_optimizer 2 | import numpy as np 3 | from matplotlib import pyplot as plt 4 | 5 | def inflateToPressure(p, isheet, opts = py_newton_optimizer.NewtonOptimizerOptions(), fixedVars = None): 6 | isheet.pressure = p 7 | opts.niter = 1000 8 | if (fixedVars is None): 9 | fixedVars = isheet.rigidMotionPinVars 10 | inflation.inflation_newton(isheet, fixedVars, opts) 11 | 12 | def inflationPressureAnalysis(isheet, pressures, opts = py_newton_optimizer.NewtonOptimizerOptions(), outDir = None, fixedVars = None): 13 | integratedGaussCurvatures = [] 14 | tensionStates = [] 15 | maxEigenvalues = [] 16 | contractions = [] 17 | for i, p in enumerate(pressures): 18 | print(f'{i + 1}/{len(pressures)}: {p}', flush=True) 19 | inflateToPressure(p, isheet, opts, fixedVars) 20 | isa = inflation.InflatedSurfaceAnalysis(isheet) 21 | metric = isa.metric() 22 | contractions.append(metric.sigma_2) 23 | tensionStates.append(isheet.tensionStateHistogram()) 24 | maxEigenvalues.append([ted.eigSensitivities().Lambda()[0] for ted in isheet.triEnergyDensities()]) 25 | # print("Remeshing", flush=True) 26 | isurf = remesh.remesh(isa.inflatedSurface()) 27 | # print("Computing angle deficits", flush=True) 28 | integratedGaussCurvatures.append(isurf.angleDeficits().sum()) 29 | # print("Constructing CurvatureInfo", flush=True) 30 | # isurf.save('debug.msh') 31 | ci = inflation.CurvatureInfo(isurf.vertices(), isurf.elements()) 32 | if (outDir is not None): 33 | os.makedirs(outDir, exist_ok=True) 34 | writer = mesh.MSHFieldWriter(f'{outDir}/surf_{p}.msh', isurf.vertices(), isurf.elements()) 35 | writer.addField('gaussCurvature', ci.gaussianCurvature()) 36 | vm = isheet.visualizationMesh() 37 | writer = mesh.MSHFieldWriter(f'{outDir}/infl_{p}.msh', vm.vertices(), vm.elements()) 38 | writer.addField('maxEig', maxEigenvalues[-1]) 39 | return {'pressures': pressures, 40 | 'integratedGaussCurvatures' : integratedGaussCurvatures, 41 | 'tensionStates' : tensionStates, 42 | 'maxEigenvalues' : maxEigenvalues, 43 | 'contractions' : contractions} 44 | 45 | def plot(analysisResult): 46 | plt.figure(figsize=(10, 8)) 47 | pressures = analysisResult['pressures'] 48 | 49 | plt.subplot(2, 2, 1) 50 | plt.plot(pressures, analysisResult['integratedGaussCurvatures']) 51 | plt.grid() 52 | plt.title('Integrated curvature') 53 | 54 | plt.subplot(2, 2, 2) 55 | plt.plot(pressures, [tsh[2] / np.sum(tsh) for tsh in analysisResult['tensionStates']]) 56 | plt.grid() 57 | plt.title('elements in full tension') 58 | 59 | for i, name in enumerate(['material stretch', 'metric contraction']): 60 | plt.subplot(2, 2, i + 3) 61 | for pct in [100, 95, 90, 80, 50]: 62 | data = [1.0 / c for c in analysisResult['contractions']] if i == 1 else analysisResult['maxEigenvalues'] 63 | plt.plot(pressures, [np.percentile(d, pct) for d in data], label=f'{pct}th percentile') 64 | if (i == 1): 65 | plt.axhline(np.pi / 2, color='black', linewidth=1, linestyle=(0, (5, 8)), label='pi / 2') 66 | plt.ylim(0.9, 1.8) 67 | else: 68 | plt.ylim(1.0, 3.0) 69 | plt.grid() 70 | plt.legend() 71 | plt.title(name) 72 | plt.xlabel('pressure') 73 | plt.tight_layout(pad=1.08) 74 | plt.show() 75 | 76 | def run(isheet, pressures, opts = py_newton_optimizer.NewtonOptimizerOptions(), outDir = None, fixedVars = None): 77 | result = inflationPressureAnalysis(isheet, pressures, opts, outDir, fixedVars) 78 | plot(result) 79 | -------------------------------------------------------------------------------- /python/io_redirection.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | import sys, os 3 | 4 | @contextmanager 5 | def suppress_stdout(): 6 | with open(os.devnull, "w") as devnull: 7 | old_stdout = sys.stdout 8 | sys.stdout = devnull 9 | try: yield 10 | finally: sys.stdout = old_stdout 11 | 12 | @contextmanager 13 | def redirect_stdout(path): 14 | with open(path, "w") as outfile: 15 | old_stdout = sys.stdout 16 | sys.stdout = outfile 17 | try: yield 18 | finally: sys.stdout = old_stdout 19 | 20 | @contextmanager 21 | def redirect_stdout_stderr(stdout_path, stderr_path): 22 | with open(stdout_path, "w") as stdout_file: 23 | with open(stderr_path, "w") as stderr_file: 24 | old_stdout = sys.stdout 25 | sys.stdout = stdout_file 26 | old_stderr = sys.stderr 27 | sys.stderr = stderr_file 28 | try: yield 29 | finally: 30 | sys.stdout = old_stdout 31 | sys.stderr = old_stderr 32 | -------------------------------------------------------------------------------- /python/measurements.py: -------------------------------------------------------------------------------- 1 | import inflation 2 | import numpy as np 3 | 4 | class DistMeasurer: 5 | def __init__(self, numPts, targetSurf, relative=False): 6 | self.tsf = inflation.TargetSurfaceFitter(targetSurf) 7 | self.tsf.setQueryPtWeights(np.ones(numPts)) 8 | self.scale = 1.0 / np.linalg.norm(targetSurf.bbox[1] - targetSurf.bbox[0]) if relative else 1.0 9 | self.isBoundary = np.zeros(numPts, dtype=bool) # don't treat boundary vertices specially (let them project to interior points) 10 | 11 | def __call__(self, q): 12 | self.tsf.updateClosestPoints(q, self.isBoundary) 13 | return np.linalg.norm(self.tsf.closestSurfPts - q, axis=1) * self.scale 14 | 15 | class MidsurfaceDistMeasurer(DistMeasurer): 16 | def __init__(self, isheet, targetSurf, relative=False): 17 | super().__init__(isheet.mesh().numVertices(), targetSurf, relative) 18 | 19 | def __call__(self, isheet): 20 | m = isheet.mesh() 21 | q = np.array([0.5 * (isheet.getDeformedVtxPosition(vi, 0) + isheet.getDeformedVtxPosition(vi, 1)) for vi in range(m.numVertices())]) 22 | return super().__call__(q) 23 | 24 | def getApproxMidsurfaceDistances(isheet, targetSurf, relative=False): 25 | """ 26 | Gets a scalar field over the full visualization mesh measuring the distance 27 | from the approximate midsurface (computed by averaging top and bottom tube 28 | vertices) to the target surface. 29 | 30 | If `relative` is True, distances are reported relative to the target 31 | surface's bounding box diagonal 32 | """ 33 | return MidsurfaceDistMeasurer(isheet, targetSurf, relative)(isheet) 34 | 35 | def getWallDistances(isheet, targetSurf, relative=False): 36 | """ 37 | Get the distances from wall vertices to `targetSurf`. 38 | If `relative` is True, distances are reported relative to the target 39 | surface's bounding box diagonal 40 | """ 41 | q = isheet.deformedWallVertexPositions() 42 | return DistMeasurer(len(q), targetSurf, relative)(q) 43 | -------------------------------------------------------------------------------- /python/opt_config.py: -------------------------------------------------------------------------------- 1 | import os as _os 2 | import boundaries 3 | INFLATABLES_PYROOT = _os.path.dirname(__file__) 4 | ################################################################################ 5 | # Helper classes for specifying/applying job configurations 6 | ################################################################################ 7 | class FixedVarsBoundary: 8 | name = "bdry" 9 | @staticmethod 10 | def get(isheet): 11 | return boundaries.getBoundaryVars(isheet) 12 | 13 | class FixedVarsBoundaryWall: 14 | name = "bdrywall" 15 | @staticmethod 16 | def get(isheet): 17 | return boundaries.getBoundaryVars(isheet) 18 | 19 | class FixedVarsNone: 20 | name = "none" 21 | @staticmethod 22 | def get(isheet): 23 | return [] 24 | 25 | class FusingCurveSmoothnessParams: 26 | def __init__(self, dirichletWeight, laplacianWeight, lengthScaleSmoothingWeight, curvatureWeight): 27 | self.dirichletWeight = dirichletWeight 28 | self.laplacianWeight = laplacianWeight 29 | self.lengthScaleSmoothingWeight = lengthScaleSmoothingWeight 30 | self.curvatureWeight = curvatureWeight 31 | 32 | def apply(self, rso): 33 | fcs = rso.fusingCurveSmoothness() 34 | fcs.dirichletWeight = self.dirichletWeight 35 | fcs.laplacianWeight = self.laplacianWeight 36 | fcs.lengthScaleSmoothingWeight = self.lengthScaleSmoothingWeight 37 | fcs.curvatureWeight = self.curvatureWeight 38 | 39 | def __repr__(self): 40 | return f'dirichlet_{self.dirichletWeight:g}_laplacian_{self.laplacianWeight:g}_scalesmoothing_{self.lengthScaleSmoothingWeight:g}_curvature_{self.curvatureWeight:g}' 41 | 42 | from enum import Enum 43 | class Solver(Enum): 44 | SCIPY = 0 45 | IPOPT = 1 46 | 47 | # Default settings that can be overriden 48 | redirect_io = True 49 | solver = Solver.IPOPT 50 | design_opt_iters = 1000 51 | blenderVisXF = lambda x: x # Additional transform to apply after the renderingNormalization transform 52 | debug = False 53 | relaxedStiffnessEpsilon = 1e-10 54 | -------------------------------------------------------------------------------- /python/register_inflation_frames.py: -------------------------------------------------------------------------------- 1 | import MeshFEM, mesh, registration, inflation, mesh_operations 2 | import numpy as np 3 | from glob import iglob, glob 4 | import sheet_meshing 5 | import os 6 | 7 | # Rigidly transform the geometry of each frame of an inflation sequence (frameDir/step_{}.msh) 8 | # to best match the target surface (represented by liftedSheetPositions). 9 | # We determine the best rigid transformation by only looking at the boundary points. 10 | def registerFrames(frameDir, sheetMesh, iwv, liftedSheetPositions, placeAtopFloor=False, registrationVtxIdxs = None, omitOverlappingTris = True): 11 | isheet = inflation.InflatableSheet(sheetMesh, iwv) 12 | if registrationVtxIdxs is None: 13 | registrationVtxIdxs = sheetMesh.boundaryVertices() 14 | 15 | nc, wallLabels = sheet_meshing.wallMeshComponents(isheet) 16 | numTopSheetTris = sheetMesh.numTris() 17 | # Include all triangles from the top sheet 18 | includeTri = np.ones(2 * numTopSheetTris, dtype=np.bool) 19 | if (omitOverlappingTris): 20 | # Omit wall triangles from the bottom sheet 21 | includeTri[numTopSheetTris:] = wallLabels == -1 22 | 23 | isheet.setUninflatedDeformation(liftedSheetPositions.T, prepareRigidMotionPinConstraints=False) 24 | tgt_pts = isheet.visualizationMesh().vertices()[registrationVtxIdxs] 25 | 26 | isRegistrationVtx = np.zeros(sheetMesh.numVertices(), dtype=np.bool) 27 | isRegistrationVtx[registrationVtxIdxs] = True 28 | BE = sheetMesh.boundaryElements() 29 | registrationBE = np.array([be for be in BE if isRegistrationVtx[be].all()]) 30 | 31 | for meshPath in [f'{frameDir}/lifted.msh'] + glob(f'{frameDir}/step_*.msh'): 32 | vm = mesh.Mesh(meshPath, embeddingDimension=3) 33 | V = vm.vertices() 34 | 35 | R, t = registration.register_points(tgt_pts, V[registrationVtxIdxs]) 36 | Vxf = V @ R.T + t 37 | if (placeAtopFloor): 38 | Vxf[:, 2] -= Vxf[:, 2].min() 39 | 40 | name = os.path.basename(meshPath) 41 | #mesh.save(f'{frameDir}/registrationBE_{name}', *mesh_operations.removeDanglingVertices(V, registrationBE)) 42 | outPath=f'{frameDir}/registered_{name}' 43 | mesh.save(outPath, *mesh_operations.removeDanglingVertices(Vxf, vm.triangles()[includeTri])) 44 | -------------------------------------------------------------------------------- /python/reinflate.py: -------------------------------------------------------------------------------- 1 | import MeshFEM, mesh, inflation, parametrization, mesh_utilities, py_newton_optimizer 2 | import utils, os 3 | import boundaries 4 | 5 | def _reinflate_impl(output_callback, targetAttractedInflation, uninflatedDefo, fixBoundary=True): 6 | isheet = targetAttractedInflation.sheet() 7 | 8 | fixedVars = boundaries.getBoundaryVars(isheet) if fixBoundary else [] 9 | isheet.setUninflatedDeformation(uninflatedDefo.T, prepareRigidMotionPinConstraints=False) 10 | opts = py_newton_optimizer.NewtonOptimizerOptions() 11 | opts.niter = 5000 12 | 13 | output_callback(-1) # Write out the initial deformation 14 | return inflation.inflation_newton(targetAttractedInflation, fixedVars, opts, callback=output_callback) 15 | 16 | def reinflate(frameOutDir, targetAttractedInflation, uninflatedDefo, fixBoundary=True, iterationsPerOutput = 5): 17 | os.makedirs(frameOutDir, exist_ok=True) 18 | 19 | frame = 0 20 | def cb(it): 21 | nonlocal frame 22 | if (((it + iterationsPerOutput) % iterationsPerOutput) == (iterationsPerOutput - 1)): 23 | targetAttractedInflation.sheet().visualizationMesh().save(f'{frameOutDir}/step_{frame}.obj') 24 | frame = frame + 1 25 | 26 | return _reinflate_impl(cb, targetAttractedInflation, uninflatedDefo, fixBoundary=fixBoundary) 27 | 28 | def reinflate_render(frameOutDir, offscreenViewer, targetAttractedInflation, uninflatedDefo, fixBoundary=True, iterationsPerOutput = 5, scalarField=None): 29 | offscreenViewer.recordStart(frameOutDir) 30 | frame = 0 31 | if scalarField is None: scalarField = visualization.ISheetScalarField.NONE 32 | def cb(it): 33 | nonlocal frame 34 | if (((it + iterationsPerOutput) % iterationsPerOutput) == (iterationsPerOutput - 1)): 35 | offscreenViewer.update(scalarField=scalarField(targetAttractedInflation.sheet())) 36 | frame = frame + 1 37 | 38 | cr = _reinflate_impl(cb, targetAttractedInflation, uninflatedDefo, fixBoundary=fixBoundary) 39 | offscreenViewer.recordStop() 40 | return cr 41 | 42 | import registration 43 | def register_frames(outDir, isheet): 44 | step_0 = mesh.Mesh(f'{outDir}/step_0.obj') 45 | registrationIndices = step_0.boundaryVertices() 46 | # top sheet only; we have spurious boundary vertices on the boundary sheet due to deduplication 47 | registrationIndices = registrationIndices[registrationIndices < isheet.mesh().numVertices()] 48 | firstBV = step_0.vertices()[registrationIndices] 49 | 50 | import glob 51 | for path in glob.glob(f'{outDir}/step_*.obj'): 52 | if (path.find('step_0.obj') >=0): continue 53 | m = mesh.Mesh(path) 54 | BV = m.vertices()[registrationIndices] 55 | R, t = registration.register_points(firstBV, BV) 56 | m.setVertices(m.vertices() @ R.T + t) 57 | m.save(path) 58 | -------------------------------------------------------------------------------- /python/wall_width_formulas.py: -------------------------------------------------------------------------------- 1 | # Wall widths are canonically in the range [0, 2pi] to match the 2 | # stripe distance field (which takes value 0 at the wall center and pi at the 3 | # air channel center). However, based on the physical spacing between 4 | # air channels, this canonical width corresponds to different physical wall 5 | # widths. 6 | import numpy as np 7 | 8 | def wallWidthForCanonicalWidth(canonicalWidth, channelSpacing): 9 | return canonicalWidth * channelSpacing / (2 * np.pi) 10 | 11 | def canonicalWallWidthForGeometry(wallWidth, channelSpacing): 12 | return wallWidth * 2 * np.pi / channelSpacing 13 | 14 | def spacingForCanonicalAndPhysicalWallWidths(canonicalWidth, physicalWidths): 15 | return physicalWidths * 2 * np.pi / canonicalWidth 16 | 17 | # Largest singular value of the mapping from 3D to the plane. 18 | def stretchFactorForCanonicalWallWidth(w): 19 | # stretched width / unstretched width 20 | return 2 * np.pi / (w + (2 / np.pi) * (2 * np.pi - w)) 21 | 22 | # Inverse of stretchFactorForCanonicalWallWidth 23 | def canonicalWallWidthForStretchFactor(s): 24 | return ((2 * np.pi / s) - 4) / (1 - 2 / np.pi) 25 | -------------------------------------------------------------------------------- /python/write_line_mesh.py: -------------------------------------------------------------------------------- 1 | def write_line_mesh(filename, pts, edges): 2 | f = open(filename, 'w') 3 | for p in pts: f.write("v\t{}\t{}\t{}\n".format(*p)) 4 | for e in edges: f.write("l\t{}\t{}\n".format(e[0] + 1, e[1] + 1)) 5 | -------------------------------------------------------------------------------- /python_bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | pybind11_add_module(mesh_utilities mesh_utilities.cc ../SurfaceSampler.cc) 2 | set_target_properties(mesh_utilities PROPERTIES CXX_STANDARD 14) 3 | set_target_properties(mesh_utilities PROPERTIES CXX_STANDARD_REQUIRED ON) 4 | set_target_properties(mesh_utilities PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/python) 5 | target_link_libraries(mesh_utilities PUBLIC MeshFEM igl::core intel_pybind_14_hack) 6 | 7 | pybind11_add_module(inflatables_parametrization parametrization.cc) 8 | set_target_properties(inflatables_parametrization PROPERTIES CXX_STANDARD 14) 9 | set_target_properties(inflatables_parametrization PROPERTIES CXX_STANDARD_REQUIRED ON) 10 | set_target_properties(inflatables_parametrization PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/python) 11 | target_link_libraries(inflatables_parametrization PUBLIC parametrization_lib intel_pybind_14_hack) 12 | 13 | pybind11_add_module (metric_fit metric_fit.cc) 14 | set_target_properties(metric_fit PROPERTIES CXX_STANDARD 14) 15 | set_target_properties(metric_fit PROPERTIES CXX_STANDARD_REQUIRED ON) 16 | set_target_properties(metric_fit PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/python) 17 | target_link_libraries(metric_fit PUBLIC metric_fit_lib intel_pybind_14_hack) 18 | 19 | pybind11_add_module(inflation inflation.cc) 20 | set_target_properties(inflation PROPERTIES CXX_STANDARD 14) 21 | set_target_properties(inflation PROPERTIES CXX_STANDARD_REQUIRED ON) 22 | set_target_properties(inflation PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/python) 23 | target_link_libraries(inflation PUBLIC inflation_lib intel_pybind_14_hack) 24 | 25 | pybind11_add_module(wall_generation wall_generation.cc) 26 | set_target_properties(wall_generation PROPERTIES CXX_STANDARD 14) 27 | set_target_properties(wall_generation PROPERTIES CXX_STANDARD_REQUIRED ON) 28 | set_target_properties(wall_generation PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/python) 29 | target_link_libraries(wall_generation PUBLIC wallgen_lib intel_pybind_14_hack) 30 | -------------------------------------------------------------------------------- /python_bindings/extended_precision.hh: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | //////////////////////////////////////////////////////////////////////////////// 4 | // For returning extended precision numbers (long doubles)... 5 | // From https://github.com/pybind/pybind11/issues/1785 6 | // https://github.com/eacousineau/repro/blob/5e6acf6/python/pybind11/custom_tests/test_numpy_issue1785.cc#L103-L105 7 | //////////////////////////////////////////////////////////////////////////////// 8 | namespace py = pybind11; 9 | 10 | using float128 = long double; 11 | static_assert(sizeof(float128) == 16, "Bad size"); 12 | 13 | template 14 | using Vector = Eigen::Matrix; 15 | 16 | namespace pybind11 { namespace detail { 17 | 18 | template 19 | struct npy_scalar_caster { 20 | PYBIND11_TYPE_CASTER(T, _("PleaseOverride")); 21 | using Array = array_t; 22 | 23 | bool load(handle src, bool convert) { 24 | // Taken from Eigen casters. Permits either scalar dtype or scalar array. 25 | handle type = dtype::of().attr("type"); // Could make more efficient. 26 | if (!convert && !isinstance(src) && !isinstance(src, type)) 27 | return false; 28 | Array tmp = Array::ensure(src); 29 | if (tmp && tmp.size() == 1 && tmp.ndim() == 0) { 30 | this->value = *tmp.data(); 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | static handle cast(T src, return_value_policy, handle) { 37 | Array tmp({1}); 38 | tmp.mutable_at(0) = src; 39 | tmp.resize({}); 40 | // You could also just return the array if you want a scalar array. 41 | object scalar = tmp[py::tuple()]; 42 | return scalar.release(); 43 | } 44 | }; 45 | 46 | template <> 47 | struct type_caster : npy_scalar_caster { 48 | static constexpr auto name = _("float128"); 49 | }; 50 | 51 | }} // namespace pybind11::detail 52 | 53 | -------------------------------------------------------------------------------- /python_bindings/wall_generation.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../InflatableSheet.hh" 12 | 13 | #include 14 | 15 | namespace py = pybind11; 16 | 17 | using Mesh = InflatableSheet::Mesh; // Piecewise linear triangle mesh embedded in R^3 18 | 19 | PYBIND11_MODULE(wall_generation, m) { 20 | m.doc() = "Air channel wall generation"; 21 | 22 | py::module::import("MeshFEM"); 23 | py::module::import("mesh"); 24 | 25 | //////////////////////////////////////////////////////////////////////////////// 26 | // Free-standing functions 27 | //////////////////////////////////////////////////////////////////////////////// 28 | m.def("evaluate_stripe_field", 29 | [](const Eigen::MatrixX3d &vertices, 30 | const Eigen::MatrixX3i &triangles, 31 | const std::vector &stretchAngles, 32 | const std::vector &wallWidths, 33 | const double frequency, 34 | const size_t nsubdiv, bool glue) { 35 | std::tuple result; 38 | std::vector stripeField; 39 | evaluate_stripe_field(vertices, triangles, stretchAngles, wallWidths, frequency, 40 | std::get<0>(result), std::get<1>(result), stripeField, 41 | nsubdiv, glue); 42 | std::get<2>(result) = Eigen::Map(stripeField.data(), stripeField.size()); 43 | return result; 44 | }, 45 | py::arg("vertices"), py::arg("triangles"), 46 | py::arg("stretchAngles"), py::arg("wallWidths"), py::arg("frequency") = 130.0 /* default frequency from Keenan's code */, 47 | py::arg("nsubdiv") = 3, py::arg("glue") = true); 48 | 49 | m.def("extract_contours", [](const Eigen::MatrixX3d &vertices, 50 | const Eigen::MatrixX3i &triangles, 51 | const std::vector &sdf, 52 | const double targetEdgeSpacing, 53 | const double minContourLen) { 54 | std::tuple>> result; 55 | std::vector ioVertices(vertices.rows()); 56 | std::vector ioElements(triangles.rows()); 57 | for (size_t i = 0; i < ioVertices.size(); ++i) ioVertices[i].point = vertices.row(i); 58 | for (size_t i = 0; i < ioElements.size(); ++i) ioElements[i] = MeshIO::IOElement(triangles(i, 0), triangles(i, 1), triangles(i, 2)); 59 | extract_contours(ioVertices, ioElements, sdf, targetEdgeSpacing, minContourLen, 60 | std::get<0>(result), std::get<1>(result)); 61 | return result; 62 | }, 63 | py::arg("vertices"), 64 | py::arg("triangles"), 65 | py::arg("sdf"), 66 | py::arg("targetEdgeSpacing") = std::numeric_limits::max(), 67 | py::arg("minContourLen") = 0); 68 | 69 | m.def("triangulate_channel_walls", [](const std::vector &pts, 70 | std::vector> &edges, 71 | double triArea, 72 | const std::string &flags, 73 | bool omitQualityFlag, 74 | const std::vector &holePoints) { 75 | std::vector vertices; 76 | std::vector elements; 77 | std::vector pointMarkers; 78 | std::vector> markedEdges; 79 | triangulatePSLG(pts, edges, holePoints, 80 | vertices, elements, triArea, flags, &pointMarkers, &markedEdges, omitQualityFlag); 81 | return py::make_tuple(std::make_shared(elements, vertices), pointMarkers, markedEdges); 82 | }, py::arg("pts"), py::arg("edges"), py::arg("triArea") = 0.01, py::arg("flags") = "", py::arg("omitQualityFlag") = false, py::arg("holePoints") = std::vector()); 83 | 84 | //////////////////////////////////////////////////////////////////////////////// 85 | // Enable output redirection from Python side 86 | //////////////////////////////////////////////////////////////////////////////// 87 | py::add_ostream_redirect(m, "ostream_redirect"); 88 | } 89 | -------------------------------------------------------------------------------- /subdivide_triangle.hh: -------------------------------------------------------------------------------- 1 | #ifndef SUBDIVIDE_TRIANGLE_HH 2 | #define SUBDIVIDE_TRIANGLE_HH 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using Pt = std::tuple; 9 | using PointGluingMap = std::map; 10 | 11 | // Example for nsubdiv == 2 12 | // + + 13 | // | . | . 14 | // | . +--. 15 | // | . |\ |. 16 | // | . ==> ^ +--+--. 17 | // | . | |\ |\ | . 18 | // +--+--+--+ i| +--+--+--+ 19 | // --> j 20 | // Grid index (i, j) ==> barycentric coordinates 21 | // ((nsubdiv + 1) - (i + j), j, i) / (nsubdiv + 1) 22 | // "newPt" is called for each new point insertion (called only once for edge/corner points); 23 | // It must do the actual point generation and return a unique index for the inserted point. 24 | // "newTri" is called for each newly triangle; it must do the actual triangle generation. 25 | template 26 | void subdivide_triangle(int nsubdiv, const Point3D_ &p0, const Point3D_ &p1, const Point3D_ &p2, PointGluingMap &indexForPoint, 27 | const NewPtCallback &newPt, const NewTriCallback &newTri) { 28 | using Real_ = typename Point3D_::Scalar; 29 | auto gridVertex = [&](int i, int j) { 30 | // Note: when we evaluate the point with the Eigen template expression 31 | // (((nsubdiv + 1) - i - j) * p0 + j * p1 + i * p2) / (nsubdiv + 1) 32 | // and compile with vectorization--at least on the Intel compiler--we 33 | // occasionally get different floating point values for the coordinates 34 | // on each halfedge of an edge. This is presumably due to the use of a 35 | // MAC instruction. 36 | // We guarantee consitent values by sorting the input points lexicographically 37 | // so that the multiply-accumulates always happen in the same order. 38 | std::array ilam{{(nsubdiv + 1) - i - j, j, i}}; 39 | Point3D_ p; 40 | 41 | { 42 | std::array idx {{ 0, 1, 2}}; 43 | std::array pts {{&p0, &p1, &p2}}; 44 | std::sort(idx.begin(), idx.end(), [&](const size_t &i0, const size_t &i1) { 45 | return std::lexicographical_compare(pts[i0]->data(), pts[i0]->data() + 3, 46 | pts[i1]->data(), pts[i1]->data() + 3); 47 | }); 48 | p = ilam[idx[0]] * (*pts[idx[0]]); 49 | p += ilam[idx[1]] * (*pts[idx[1]]); 50 | p += ilam[idx[2]] * (*pts[idx[2]]); 51 | } 52 | p /= (nsubdiv + 1); 53 | 54 | auto key = std::make_tuple(p[0], p[1], p[2]); 55 | 56 | auto it = indexForPoint.lower_bound(key); 57 | if ((it != indexForPoint.end()) && (it->first == key)) return it->second; 58 | 59 | Real_ lambda_1 = j / (nsubdiv + 1.0), 60 | lambda_2 = i / (nsubdiv + 1.0), 61 | lambda_0 = 1.0 - lambda_1 - lambda_2; 62 | size_t idx = newPt(p, lambda_0, lambda_1, lambda_2); 63 | indexForPoint.emplace_hint(it, key, idx); 64 | return idx; 65 | }; 66 | 67 | // Triangulate the square with lower-lefthand corner (i, j) 68 | for (int i = 0; i <= nsubdiv; ++i) { // Loop up to second-to-last vertex 69 | for (int j = 0; j <= nsubdiv - i; ++j) { // Loop up to second-to-last diagonal: i + j <= nsubdiv 70 | newTri(gridVertex(i , j ), 71 | gridVertex(i , j + 1), 72 | gridVertex(i + 1, j )); 73 | if ((i + 1) + (j + 1) > nsubdiv + 1) continue; // Upper subtriangle is out of bounds; discard 74 | newTri(gridVertex(i + 1, j ), 75 | gridVertex(i , j + 1), 76 | gridVertex(i + 1, j + 1)); 77 | } 78 | } 79 | } 80 | 81 | #endif /* end of include guard: SUBDIVIDE_TRIANGLE_HH */ 82 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test_newton test_newton.cc) 2 | set_target_properties(test_newton PROPERTIES CXX_STANDARD 14) 3 | set_target_properties(test_newton PROPERTIES CXX_STANDARD_REQUIRED ON) 4 | target_link_libraries(test_newton parametrization_lib) 5 | 6 | add_executable(test_svd_sensitivity test_svd_sensitivity.cc) 7 | set_target_properties(test_svd_sensitivity PROPERTIES CXX_STANDARD 14) 8 | set_target_properties(test_svd_sensitivity PROPERTIES CXX_STANDARD_REQUIRED ON) 9 | target_link_libraries(test_svd_sensitivity parametrization_lib) 10 | 11 | add_executable(test_eig_sensitivity test_eig_sensitivity.cc) 12 | set_target_properties(test_eig_sensitivity PROPERTIES CXX_STANDARD 14) 13 | set_target_properties(test_eig_sensitivity PROPERTIES CXX_STANDARD_REQUIRED ON) 14 | target_link_libraries(test_eig_sensitivity parametrization_lib) 15 | 16 | add_executable(test_balloon_energy test_balloon_energy.cc) 17 | set_target_properties(test_balloon_energy PROPERTIES CXX_STANDARD 14) 18 | set_target_properties(test_balloon_energy PROPERTIES CXX_STANDARD_REQUIRED ON) 19 | target_link_libraries(test_balloon_energy parametrization_lib) 20 | 21 | add_executable(test_tension_field_energy test_tension_field_energy.cc) 22 | set_target_properties(test_tension_field_energy PROPERTIES CXX_STANDARD 14) 23 | set_target_properties(test_tension_field_energy PROPERTIES CXX_STANDARD_REQUIRED ON) 24 | target_link_libraries(test_tension_field_energy parametrization_lib) 25 | 26 | ################################################################################ 27 | # Catch2 28 | ################################################################################ 29 | add_executable(unit_tests 30 | catch2_main.cc 31 | catch2_svd_tests.cc 32 | catch2_tf_validation.cc 33 | ) 34 | 35 | set_target_properties(unit_tests PROPERTIES CXX_STANDARD 14) 36 | set_target_properties(unit_tests PROPERTIES CXX_STANDARD_REQUIRED ON) 37 | 38 | target_link_libraries(unit_tests PUBLIC 39 | Catch2::Catch2 40 | parametrization_lib 41 | warnings::all 42 | ) 43 | 44 | # Register tests 45 | # Note: even though MeshFEM has added Catch2 to its CMAKE_MODULE_PATH, 46 | # those changes are not visible to this parent project... 47 | list(APPEND CMAKE_MODULE_PATH ${MESHFEM_EXTERNAL}/Catch2/contrib) 48 | include(CTest) 49 | include(Catch) 50 | catch_discover_tests(unit_tests) 51 | -------------------------------------------------------------------------------- /tests/catch2_main.cc: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Keep this file empty, and implement unit tests in separate compilation units! 3 | //////////////////////////////////////////////////////////////////////////////// 4 | #define CATCH_CONFIG_MAIN 5 | #include 6 | -------------------------------------------------------------------------------- /tests/catch2_svd_tests.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include "../SVDSensitivity.hh" 5 | 6 | using M2d = Eigen::Matrix2d; 7 | using V2d = Eigen::Vector2d; 8 | constexpr double fd_eps = 1e-5; 9 | constexpr double compare_eps = 1e-6; 10 | 11 | template 12 | bool approxEqual(const A &a, const B &b) { 13 | return (a - b).squaredNorm() < compare_eps * compare_eps * b.squaredNorm(); 14 | } 15 | 16 | void firstDerivativeTests() { 17 | M2d A(M2d::Random()), perturb(M2d::Random()); 18 | SVDSensitivity svdA(A); 19 | auto sigmaAt = [&](const M2d &X) { 20 | SVDSensitivity svdX(X); 21 | return svdX.Sigma(); 22 | }; 23 | 24 | auto uAt = [&](size_t i, const M2d &X) -> V2d { 25 | SVDSensitivity svdX(X); 26 | return svdX.u(i); 27 | }; 28 | 29 | auto vAt = [&](size_t i, const M2d &X) -> V2d { 30 | SVDSensitivity svdX(X); 31 | return svdX.v(i); 32 | }; 33 | 34 | V2d fd_delta_sigma = (sigmaAt(A + fd_eps * perturb) - sigmaAt(A - fd_eps * perturb)) / (2 * fd_eps); 35 | V2d an_delta_sigma = svdA.dSigma(perturb); 36 | 37 | V2d fd_delta_u0 = (uAt(0, A + fd_eps * perturb) - uAt(0, A - fd_eps * perturb)) / (2 * fd_eps); 38 | V2d an_delta_u0 = svdA.du0(perturb); 39 | 40 | V2d fd_delta_u1 = (uAt(1, A + fd_eps * perturb) - uAt(1, A - fd_eps * perturb)) / (2 * fd_eps); 41 | V2d an_delta_u1 = svdA.du1(perturb); 42 | 43 | V2d fd_delta_v0 = (vAt(0, A + fd_eps * perturb) - vAt(0, A - fd_eps * perturb)) / (2 * fd_eps); 44 | V2d an_delta_v0 = svdA.dv0(perturb); 45 | 46 | V2d fd_delta_v1 = (vAt(1, A + fd_eps * perturb) - vAt(1, A - fd_eps * perturb)) / (2 * fd_eps); 47 | V2d an_delta_v1 = svdA.dv1(perturb); 48 | 49 | if (!approxEqual(fd_delta_u0, an_delta_u0)) { 50 | std::cout << perturb << std::endl << std::endl; 51 | std::cout << A << std::endl << std::endl; 52 | std::cout << uAt(0, A + fd_eps * perturb) << std::endl; 53 | std::cout << uAt(0, A - fd_eps * perturb) << std::endl; 54 | } 55 | 56 | std::cout.precision(16); 57 | REQUIRE(approxEqual(fd_delta_sigma, an_delta_sigma)); 58 | REQUIRE(approxEqual(fd_delta_u0, an_delta_u0)); 59 | REQUIRE(approxEqual(fd_delta_u1, an_delta_u1)); 60 | REQUIRE(approxEqual(fd_delta_v0, an_delta_v0)); 61 | REQUIRE(approxEqual(fd_delta_v1, an_delta_v1)); 62 | REQUIRE(approxEqual(fd_delta_v1, an_delta_v1)); 63 | } 64 | 65 | void secondDerivativeTests() { 66 | M2d A(M2d::Random()), perturb(M2d::Random()); 67 | SVDSensitivity svdA(A); 68 | auto gradSigmaAt = [&](size_t i, const M2d &X) { 69 | SVDSensitivity svdX(X); 70 | return svdX.dsigma(i); 71 | }; 72 | 73 | auto gradU0At = [&](size_t i, const M2d &X) { 74 | SVDSensitivity svdX(X); 75 | return svdX.du0(i); 76 | }; 77 | 78 | auto gradV1At = [&](size_t i, const M2d &X) { 79 | SVDSensitivity svdX(X); 80 | return svdX.dv1(i); 81 | }; 82 | 83 | M2d fd_delta_grad_sigma_0 = (gradSigmaAt(0, A + fd_eps * perturb) - gradSigmaAt(0, A - fd_eps * perturb)) / (2 * fd_eps); 84 | M2d fd_delta_grad_sigma_1 = (gradSigmaAt(1, A + fd_eps * perturb) - gradSigmaAt(1, A - fd_eps * perturb)) / (2 * fd_eps); 85 | 86 | M2d fd_delta_grad_u0_0 = (gradU0At (0, A + fd_eps * perturb) - gradU0At (0, A - fd_eps * perturb)) / (2 * fd_eps); 87 | M2d fd_delta_grad_u0_1 = (gradU0At (1, A + fd_eps * perturb) - gradU0At (1, A - fd_eps * perturb)) / (2 * fd_eps); 88 | 89 | M2d fd_delta_grad_v1_0 = (gradV1At (0, A + fd_eps * perturb) - gradV1At (0, A - fd_eps * perturb)) / (2 * fd_eps); 90 | M2d fd_delta_grad_v1_1 = (gradV1At (1, A + fd_eps * perturb) - gradV1At (1, A - fd_eps * perturb)) / (2 * fd_eps); 91 | 92 | M2d an_delta_grad_sigma_0, an_delta_grad_sigma_1; 93 | M2d an_delta_grad_u0_0, an_delta_grad_u0_1; 94 | M2d an_delta_grad_v1_0, an_delta_grad_v1_1; 95 | for (size_t i = 0; i < 2; ++i) { 96 | for (size_t j = 0; j < 2; ++j) { 97 | M2d ei_otimes_ej(M2d::Zero()); 98 | ei_otimes_ej(i, j) = 1.0; 99 | auto d2Sigma = svdA.d2Sigma(ei_otimes_ej, perturb); 100 | an_delta_grad_sigma_0(i, j) = d2Sigma[0]; 101 | an_delta_grad_sigma_1(i, j) = d2Sigma[1]; 102 | 103 | auto d2u0 = svdA.d2u0(ei_otimes_ej, perturb); 104 | an_delta_grad_u0_0(i, j) = d2u0[0]; 105 | an_delta_grad_u0_1(i, j) = d2u0[1]; 106 | 107 | auto d2v1 = svdA.d2v1(ei_otimes_ej, perturb); 108 | an_delta_grad_v1_0(i, j) = d2v1[0]; 109 | an_delta_grad_v1_1(i, j) = d2v1[1]; 110 | } 111 | } 112 | 113 | // Note: d2sigma_0/(d_A d_B) and d2sigma_1/(d_A d_B) are closely related 114 | // (related by pi/2 rotation transformation) 115 | REQUIRE(approxEqual(fd_delta_grad_sigma_0, an_delta_grad_sigma_0)); 116 | REQUIRE(approxEqual(fd_delta_grad_sigma_1, an_delta_grad_sigma_1)); 117 | REQUIRE(approxEqual(fd_delta_grad_u0_0, an_delta_grad_u0_0)); 118 | REQUIRE(approxEqual(fd_delta_grad_u0_1, an_delta_grad_u0_1)); 119 | REQUIRE(approxEqual(fd_delta_grad_v1_0, an_delta_grad_v1_0)); 120 | REQUIRE(approxEqual(fd_delta_grad_v1_1, an_delta_grad_v1_1)); 121 | } 122 | 123 | TEST_CASE("svd_sensitivity", "[svd_sensitivity]") { 124 | for (size_t i = 0; i < 1000; ++i) { 125 | firstDerivativeTests(); 126 | secondDerivativeTests(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/catch2_tf_validation.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../TensionFieldEnergy.hh" 3 | #include 4 | #include 5 | 6 | using INeo_TFT = EnergyDensityFBasedFromCBased>, 3>; 7 | using M32d = Eigen::Matrix; 8 | using M2d = Eigen::Matrix; 9 | using OTFE = EnergyDensityFBasedFromCBased, 3>; 10 | 11 | template 12 | void requireApproxEqual(const A &a, const B &b) { 13 | for (int i = 0; i < a.rows(); i++) { 14 | for (int j = 0; j < a.cols(); j++) { 15 | REQUIRE(a(i, j) == Approx(b(i, j))); 16 | } 17 | } 18 | } 19 | 20 | template 21 | bool approxEqual(const A &a, const B &b, const double compare_eps = 2.5e-6) { 22 | return ((a - b).squaredNorm() < compare_eps * compare_eps * b.squaredNorm()) || 23 | ((a - b).squaredNorm() < compare_eps); 24 | } 25 | 26 | void runComparison(INeo_TFT &psi_new, OTFE &psi_old) { 27 | for (size_t i = 0; i < 1000; ++i) { 28 | M32d F = M32d::Identity() + 1e-3 * M32d::Random(); 29 | psi_new.setDeformationGradient(F); 30 | psi_old.setDeformationGradient(F); 31 | 32 | REQUIRE(psi_new.energy() == Approx(psi_old.energy())); 33 | REQUIRE(approxEqual(psi_new.denergy(), psi_old.denergy())); 34 | 35 | for (size_t j = 0; j < 100; ++j) { 36 | M32d dF = M32d::Random(); 37 | // M2d dC = dF.transpose() * F + F.transpose() * dF; 38 | REQUIRE(approxEqual(psi_new.delta_denergy(dF), psi_old.delta_denergy(dF))); 39 | } 40 | } 41 | } 42 | 43 | TEST_CASE("tft_comparison", "[tft_comparison]") { 44 | INeo_TFT psi_new; 45 | OTFE psi_old; 46 | psi_new.psi().stiffness = 1.0; 47 | psi_old.setStiffness(1.0); 48 | 49 | psi_new.setRelaxationEnabled(false); 50 | psi_old.useTensionField = false; 51 | runComparison(psi_new, psi_old); 52 | 53 | psi_new.setRelaxationEnabled(true); 54 | psi_old.useTensionField = true; 55 | runComparison(psi_new, psi_old); 56 | } 57 | -------------------------------------------------------------------------------- /tests/test_balloon_energy.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../IncompressibleBalloonEnergy.hh" 3 | 4 | using IBE = IncompressibleBalloonEnergy; 5 | using M2d = IBE::M2d; 6 | using V2d = IBE::V2d; 7 | using M32d = Eigen::Matrix; 8 | 9 | void runTest(const M2d &C, double fd_eps) { 10 | IBE balloon(C); 11 | 12 | auto energyAt = [&](const M2d &X) { 13 | return IBE(X).energy(); 14 | }; 15 | 16 | auto dEnergyAt = [&](const M2d &X, const M2d &perturb) { 17 | return IBE(X).denergy(perturb); 18 | }; 19 | 20 | std::cout.precision(16); 21 | 22 | //////////////////////////////////////////////////////////////////////////// 23 | // First derivative tests 24 | //////////////////////////////////////////////////////////////////////////// 25 | M2d perturb(M2d::Random()); 26 | perturb(0, 1) = perturb(1, 0); 27 | double fd_delta_energy = (energyAt(C + fd_eps * perturb) - energyAt(C - fd_eps * perturb)) / (2 * fd_eps); 28 | double an_delta_energy = balloon.denergy(perturb); 29 | 30 | std::cout.precision(16); 31 | std::cout << "fd_delta_energy:\t" << fd_delta_energy << std::endl; 32 | std::cout << "an_delta_energy:\t" << an_delta_energy << std::endl; 33 | 34 | //////////////////////////////////////////////////////////////////////////// 35 | // Second derivative tests 36 | //////////////////////////////////////////////////////////////////////////// 37 | M2d fd_delta_grad_energy, 38 | an_delta_grad_energy; 39 | 40 | for (size_t i = 0; i < 2; ++i) { 41 | for (size_t j = 0; j < 2; ++j) { 42 | M2d ei_otimes_ej(M2d::Zero()); 43 | if (i == j) ei_otimes_ej(i, j) = 1.0; 44 | else ei_otimes_ej(i, j) = ei_otimes_ej(j, i) = 0.5; 45 | 46 | an_delta_grad_energy(i, j) = balloon.d2energy(ei_otimes_ej, perturb); 47 | 48 | fd_delta_grad_energy(i, j) = (dEnergyAt(C + fd_eps * perturb, ei_otimes_ej) - dEnergyAt(C - fd_eps * perturb, ei_otimes_ej)) / (2 * fd_eps); 49 | } 50 | } 51 | 52 | std::cout << std::endl; 53 | std::cout << "fd_delta_grad_energy" << std::endl << fd_delta_grad_energy << std::endl; 54 | std::cout << "an_delta_grad_energy" << std::endl << an_delta_grad_energy << std::endl; 55 | } 56 | 57 | int main(int argc, const char *argv[]) { 58 | if (argc != 2) { 59 | std::cout << "usage: ./test_balloon_energy fd_eps" << std::endl; 60 | exit(-1); 61 | } 62 | const double fd_eps = std::stod(argv[1]); 63 | 64 | M2d C; 65 | C.setRandom(); 66 | C(1, 0) = C(0, 1); 67 | 68 | std::cout << "Around random C:" << std::endl; 69 | runTest(C, fd_eps); 70 | std::cout << std::endl; 71 | 72 | std::cout << "Around identity:" << std::endl; 73 | C.setIdentity(); 74 | runTest(C, fd_eps); 75 | 76 | M32d J; 77 | J << 1, 0, 78 | 0, 1, 79 | 0, 0; 80 | J.setRandom(); 81 | 82 | M32d J_perturb_a, J_perturb_b; 83 | J_perturb_a.setRandom(); J_perturb_b.setRandom(); 84 | 85 | auto evalAtJ = [&](const M32d &Jeval) { 86 | C = Jeval.transpose() * Jeval; 87 | return IBE(C).energy(); 88 | }; 89 | 90 | auto evalGradAtJ = [&](const M32d &Jeval, const M32d &perturb) { 91 | C = Jeval.transpose() * Jeval; 92 | M2d dC = 2 * Jeval.transpose() * perturb; // technically should be symmetrized, but denergy double contracts it with a symmetric tensor... 93 | return IBE(C).denergy(dC); 94 | }; 95 | 96 | auto evalHessAtJ = [&](const M32d &Jeval, const M32d &perturb_a, const M32d &perturb_b) { 97 | C = Jeval.transpose() * Jeval; 98 | M2d dC_a = 2.0 * Jeval.transpose() * perturb_a, // pre-symmetrization... 99 | dC_b = 2.0 * Jeval.transpose() * perturb_b; 100 | symmetrize(dC_a); 101 | symmetrize(dC_b); 102 | M2d d2C_ab = (perturb_b.transpose() * perturb_a + perturb_a.transpose() * perturb_b); 103 | IBE ibe(C); 104 | return ibe.d2energy(dC_a, dC_b) + ibe.denergy(d2C_ab); 105 | }; 106 | 107 | std::cout << std::endl; 108 | std::cout << std::endl; 109 | std::cout << (evalAtJ(J + fd_eps * J_perturb_a) - evalAtJ(J - fd_eps * J_perturb_a)) / (2 * fd_eps) << std::endl; 110 | std::cout << evalGradAtJ(J, J_perturb_a) << std::endl; 111 | 112 | std::cout << std::endl; 113 | std::cout << std::endl; 114 | std::cout << (evalGradAtJ(J + fd_eps * J_perturb_b, J_perturb_a) - evalGradAtJ(J - fd_eps * J_perturb_b, J_perturb_a)) / (2 * fd_eps) << std::endl; 115 | std::cout << evalHessAtJ(J, J_perturb_a, J_perturb_b) << std::endl; 116 | 117 | return 0; 118 | } 119 | -------------------------------------------------------------------------------- /tests/test_eig_sensitivity.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../EigSensitivity.hh" 3 | 4 | using ES = EigSensitivity; 5 | 6 | using M2d = ES::M2d; 7 | using V2d = ES::V2d; 8 | 9 | int main(int argc, const char *argv[]) { 10 | if (argc != 2) { 11 | std::cout << "usage: ./test_eig_sensitivity fd_eps" << std::endl; 12 | exit(-1); 13 | } 14 | const double fd_eps = std::stod(argv[1]); 15 | 16 | M2d A; 17 | A.setRandom(); 18 | A(1, 0) = A(0, 1); 19 | 20 | ES eigA(A); 21 | 22 | auto lambdaAt = [&](const M2d &X) { 23 | ES eigX(X); 24 | return eigX.Lambda(); 25 | }; 26 | 27 | auto dLambdaAt = [&](const M2d &X, const M2d &perturb) { 28 | ES eigX(X); 29 | return eigX.dLambda(perturb); 30 | }; 31 | 32 | std::cout.precision(16); 33 | 34 | std::cout << "A: " << std::endl << A << std::endl << std::endl;; 35 | 36 | std::cout << "A eigs: " << eigA.Lambda().transpose() << std::endl << std::endl;; 37 | std::cout << "Q(A): " << std::endl << eigA.Q() << std::endl << std::endl;; 38 | 39 | //////////////////////////////////////////////////////////////////////////// 40 | // First derivative tests 41 | //////////////////////////////////////////////////////////////////////////// 42 | M2d perturb(M2d::Random()); 43 | perturb(0, 1) = perturb(1, 0); 44 | V2d fd_delta_lambda = (lambdaAt(A + fd_eps * perturb) - lambdaAt(A - fd_eps * perturb)) / (2 * fd_eps); 45 | V2d an_delta_lambda = eigA.dLambda(perturb); 46 | 47 | std::cout.precision(16); 48 | std::cout << "fd_delta_lambda:\t" << fd_delta_lambda.transpose() << std::endl; 49 | std::cout << "an_delta_lambda:\t" << an_delta_lambda.transpose() << std::endl; 50 | 51 | //////////////////////////////////////////////////////////////////////////// 52 | // Second derivative tests 53 | //////////////////////////////////////////////////////////////////////////// 54 | M2d fd_delta_grad_lambda_0, fd_delta_grad_lambda_1, 55 | an_delta_grad_lambda_0, an_delta_grad_lambda_1; 56 | 57 | for (size_t i = 0; i < 2; ++i) { 58 | for (size_t j = 0; j < 2; ++j) { 59 | M2d ei_otimes_ej(M2d::Zero()); 60 | if (i == j) ei_otimes_ej(i, j) = 1.0; 61 | else ei_otimes_ej(i, j) = ei_otimes_ej(j, i) = 0.5; 62 | 63 | V2d d2Lambda = eigA.d2Lambda(ei_otimes_ej, perturb); 64 | an_delta_grad_lambda_0(i, j) = d2Lambda[0]; 65 | an_delta_grad_lambda_1(i, j) = d2Lambda[1]; 66 | 67 | V2d fd_d2Lambda = (dLambdaAt(A + fd_eps * perturb, ei_otimes_ej) - dLambdaAt(A - fd_eps * perturb, ei_otimes_ej)) / (2 * fd_eps); 68 | fd_delta_grad_lambda_0(i, j) = fd_d2Lambda[0]; 69 | fd_delta_grad_lambda_1(i, j) = fd_d2Lambda[1]; 70 | } 71 | } 72 | 73 | std::cout << std::endl; 74 | std::cout << "fd_delta_grad_lambda_0" << std::endl << fd_delta_grad_lambda_0 << std::endl; 75 | std::cout << "an_delta_grad_lambda_0" << std::endl << an_delta_grad_lambda_0 << std::endl; 76 | 77 | std::cout << std::endl; 78 | std::cout << "fd_delta_grad_lambda_1" << std::endl << fd_delta_grad_lambda_1 << std::endl; 79 | std::cout << "an_delta_grad_lambda_1" << std::endl << an_delta_grad_lambda_1 << std::endl; 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /tests/test_newton.cc: -------------------------------------------------------------------------------- 1 | #include "../parametrization_newton.hh" 2 | #include 3 | 4 | #include "../parametrization.hh" 5 | 6 | using Mesh = parametrization::Mesh; 7 | 8 | int main(int argc, const char *argv[]) { 9 | if (argc != 2) { 10 | std::cerr << "usage: shear_arap in_mesh.obj" << std::endl; 11 | exit(-1); 12 | } 13 | 14 | std::string inPath = argv[1]; 15 | 16 | std::vector vertices; 17 | std::vector elements; 18 | MeshIO::load(inPath, vertices, elements); 19 | 20 | auto mesh = std::make_shared(elements, vertices); 21 | 22 | auto f = parametrization::lscm(*mesh); 23 | 24 | parametrization::LocalGlobalParametrizer param(mesh, f); 25 | param.setAlphaMin(1.0); 26 | param.setAlphaMax(M_PI / 2); 27 | 28 | std::cout.precision(19); 29 | 30 | // Local-global iterations 31 | const size_t nit = 1000; 32 | for (size_t it = 0; it < nit; ++it) { 33 | std::cout << "Local-Global energy:\t" << param.energy() << std::endl; 34 | param.runIteration(); 35 | } 36 | std::cout << "Local-Global energy:\t" << param.energy() << std::endl; 37 | 38 | // Regularized parametrizer iterations 39 | parametrization::RegularizedParametrizer rparam(param); 40 | 41 | NewtonOptimizerOptions opts; 42 | opts.useIdentityMetric = true; 43 | 44 | const std::vector fixedVars{rparam.uOffset(), rparam.vOffset(), rparam.phiOffset()}; 45 | parametrization::regularized_parametrization_newton(rparam, fixedVars, opts); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /tests/test_subdivide.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../subdivide_triangle.hh" 3 | 4 | int main(int argc, const char *argv[]) { 5 | if (argc != 4) { 6 | std::cerr << "usage: test_subdivide in_mesh.obj nsubdiv out_mesh" << std::endl; 7 | exit(-1); 8 | } 9 | const std::string inPath = argv[1], 10 | outPath = argv[3]; 11 | const int nsubdiv = std::stoi(argv[2]); 12 | 13 | std::vector vertices; 14 | std::vector elements; 15 | MeshIO::load(inPath, vertices, elements); 16 | 17 | std::vector outVertices; 18 | std::vector outElements; 19 | PointGluingMap indexForPoint; 20 | 21 | auto newPt = [&](const Point3D &p, Real /* lambda_0 */, Real /* lambda_1 */, Real /* lambda_2 */) { 22 | outVertices.emplace_back(p); 23 | return outVertices.size() - 1; 24 | }; 25 | 26 | auto newTri = [&](size_t i0, size_t i1, size_t i2) { outElements.emplace_back(i0, i1, i2); }; 27 | 28 | for (const auto &e : elements) { 29 | if (e.size() != 3) throw std::runtime_error("Only triangles are supported"); 30 | subdivide_triangle(nsubdiv, vertices[e[0]].point, vertices[e[1]].point, vertices[e[2]].point, indexForPoint, newPt, newTri); 31 | } 32 | 33 | MeshIO::save(outPath, outVertices, outElements); 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /tests/test_tension_field_energy.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../TensionFieldEnergy.hh" 3 | 4 | using OTFE = OptionalTensionFieldEnergy; 5 | using M2d = OTFE::M2d; 6 | using V2d = OTFE::V2d; 7 | 8 | void runTest(const M2d &C, double fd_eps) { 9 | OTFE tfe(C); 10 | 11 | auto energyAt = [&](const M2d &X) { 12 | return OTFE(X).energy(); 13 | }; 14 | 15 | auto dEnergyAt = [&](const M2d &X, const M2d &perturb) { 16 | return OTFE(X).denergy(perturb); 17 | }; 18 | 19 | std::cout.precision(16); 20 | 21 | //////////////////////////////////////////////////////////////////////////// 22 | // First derivative tests 23 | //////////////////////////////////////////////////////////////////////////// 24 | M2d perturb(M2d::Random()); 25 | perturb(0, 1) = perturb(1, 0); 26 | double fd_delta_energy = (energyAt(C + fd_eps * perturb) - energyAt(C - fd_eps * perturb)) / (2 * fd_eps); 27 | double an_delta_energy = tfe.denergy(perturb); 28 | 29 | std::cout.precision(16); 30 | std::cout << "fd_delta_energy:\t" << fd_delta_energy << std::endl; 31 | std::cout << "an_delta_energy:\t" << an_delta_energy << std::endl; 32 | 33 | //////////////////////////////////////////////////////////////////////////// 34 | // Second derivative tests 35 | //////////////////////////////////////////////////////////////////////////// 36 | M2d fd_delta_grad_energy, 37 | an_delta_grad_energy; 38 | 39 | for (size_t i = 0; i < 2; ++i) { 40 | for (size_t j = 0; j < 2; ++j) { 41 | M2d ei_otimes_ej(M2d::Zero()); 42 | if (i == j) ei_otimes_ej(i, j) = 1.0; 43 | else ei_otimes_ej(i, j) = ei_otimes_ej(j, i) = 0.5; 44 | 45 | an_delta_grad_energy(i, j) = tfe.d2energy(ei_otimes_ej, perturb); 46 | fd_delta_grad_energy(i, j) = (dEnergyAt(C + fd_eps * perturb, ei_otimes_ej) - dEnergyAt(C - fd_eps * perturb, ei_otimes_ej)) / (2 * fd_eps); 47 | } 48 | } 49 | 50 | std::cout << std::endl; 51 | std::cout << "fd_delta_grad_energy" << std::endl << fd_delta_grad_energy << std::endl; 52 | std::cout << "an_delta_grad_energy" << std::endl << an_delta_grad_energy << std::endl; 53 | } 54 | 55 | int main(int argc, const char *argv[]) { 56 | if (argc != 2) { 57 | std::cout << "usage: ./test_tension_field_energy fd_eps" << std::endl; 58 | exit(-1); 59 | } 60 | const double fd_eps = std::stod(argv[1]); 61 | 62 | std::cout << "Full case" << std::endl; 63 | M2d C; 64 | C << 3.0, 0.0, 65 | 0.0, 2.0; 66 | runTest(C, fd_eps); 67 | 68 | std::cout << std::endl; 69 | std::cout << "Relaxed case" << std::endl; 70 | C << 3.0, 0.0, 71 | 0.0, 0.4; 72 | runTest(C, fd_eps); 73 | 74 | std::cout << std::endl; 75 | std::cout << "Perturbed relaxed case" << std::endl; 76 | C << 3.0, 0.0, 77 | 0.0, 0.4; 78 | C += 0.1 * M2d::Random(); 79 | C(1, 0) = C(0, 1); 80 | runTest(C, fd_eps); 81 | 82 | std::cout << std::endl; 83 | std::cout << "Test case from simulation" << std::endl; 84 | C << 1.00110208e+00, 5.49990066e-04, 85 | 5.49990066e-04, 9.99143867e-01; 86 | runTest(C, fd_eps); 87 | 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /wall_generation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Stripe-patterns-based wall generation library 3 | ################################################################################ 4 | add_library(wallgen_lib evaluate_stripe_field.cc extract_contours.cc) 5 | set_target_properties (wallgen_lib PROPERTIES CXX_STANDARD 14) 6 | set_target_properties (wallgen_lib PROPERTIES CXX_STANDARD_REQUIRED ON) 7 | target_include_directories(wallgen_lib PUBLIC .) 8 | target_link_libraries (wallgen_lib MeshFEM StripePatterns) 9 | -------------------------------------------------------------------------------- /wall_generation/evaluate_stripe_field.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // evaluate_stripe_field.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // Sampling of the stripe pattern on a subdivided mesh. 6 | */ 7 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 8 | // Created: 04/27/2019 15:36:32 9 | //////////////////////////////////////////////////////////////////////////////// 10 | #ifndef EVALUATE_STRIPE_FIELD_HH 11 | #define EVALUATE_STRIPE_FIELD_HH 12 | 13 | #include 14 | #include 15 | 16 | void evaluate_stripe_field(const Eigen::MatrixX3d &vertices, 17 | const Eigen::MatrixX3i &elements, 18 | const std::vector &stretchAngles, 19 | const std::vector &wallWidths, 20 | const double frequency, 21 | Eigen::MatrixX3d &outVerticesEigen, 22 | Eigen::MatrixX3i &outTrianglesEigen, 23 | std::vector &stripeField, 24 | const size_t nsubdiv = 3, 25 | const bool glue = true); 26 | 27 | #endif /* end of include guard: EVALUATE_STRIPE_FIELD_HH */ 28 | -------------------------------------------------------------------------------- /wall_generation/extract_contours.hh: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // extract_contours.hh 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /*! @file 5 | // Contour extraction routine to generate wall boundaries. 6 | */ 7 | // Author: Julian Panetta (jpanetta), julian.panetta@gmail.com 8 | // Created: 04/28/2019 19:46:10 9 | //////////////////////////////////////////////////////////////////////////////// 10 | #ifndef EXTRACT_CONTOURS_HH 11 | #define EXTRACT_CONTOURS_HH 12 | 13 | #include 14 | #include 15 | 16 | void extract_contours(const std::vector &vertices, 17 | const std::vector &triangles, 18 | const std::vector &sdf, 19 | const double targetEdgeSpacing, 20 | const double minContourLen, // length below which contours are removed 21 | Eigen::MatrixX3d &outVertices, 22 | std::vector> &outEdges); 23 | 24 | #endif /* end of include guard: EXTRACT_CONTOURS_HH */ 25 | --------------------------------------------------------------------------------