├── .gitattributes ├── src ├── simLib │ ├── DirectSolver.cpp │ ├── BasicCGSolver.cpp │ ├── GravityField.cpp │ ├── ConstrainedCGSolver.cpp │ ├── ShearCondition.cpp │ ├── ClothMesh.cpp │ ├── StretchCondition.cpp │ └── BendCondition.cpp ├── test │ ├── EqualityTests.cpp │ ├── ShearConditionTest.cpp │ ├── StretchConditionTest.cpp │ ├── ClothMeshTest.cpp │ └── BendConditionTest.cpp └── mayaPlugin │ └── ClothSimMayaPlugin.cpp ├── include ├── simLib │ ├── DirectSolver.h │ ├── BasicCGSolver.h │ ├── GravityField.h │ ├── LinearSolver.h │ ├── ForceField.h │ ├── EnergyCondition.h │ ├── ConstrainedCGSolver.h │ ├── ShearCondition.h │ ├── StretchCondition.h │ ├── TangentTriangleQuantities.h │ ├── BendCondition.h │ └── ClothMesh.h ├── mayaPlugin │ └── ClothSimMayaPlugin.h └── test │ └── EqualityTests.h ├── mayaPlugin.vcxproj.filters ├── .gitignore ├── testing.runsettings ├── UnitTest1.vcxproj.filters ├── simLib.vcxproj.filters ├── simLib.sln ├── UnitTest1.vcxproj ├── mayaPlugin.vcxproj └── simLib.vcxproj /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /src/simLib/DirectSolver.cpp: -------------------------------------------------------------------------------- 1 | #include "simLib\DirectSolver.h" 2 | 3 | using namespace ClothSim; 4 | 5 | template 6 | void DirectSolver::solve(const SparseMatrix &A, const Vector &rhs, Vector &result) const 7 | { 8 | Eigen::SparseLU< SparseMatrix > solver; 9 | solver.analyzePattern(A); 10 | solver.factorize(A); 11 | result = solver.solve(rhs); 12 | } 13 | 14 | template class DirectSolver; 15 | template class DirectSolver; 16 | -------------------------------------------------------------------------------- /src/simLib/BasicCGSolver.cpp: -------------------------------------------------------------------------------- 1 | #include "simLib\BasicCGSolver.h" 2 | #include 3 | 4 | using namespace ClothSim; 5 | 6 | template 7 | void BasicCGSolver::solve(const SparseMatrix &A, const Vector &rhs, Vector &result) const 8 | { 9 | Eigen::ConjugateGradient cg; 10 | cg.compute(A); 11 | result = cg.solve(rhs); 12 | } 13 | 14 | template class BasicCGSolver; 15 | template class BasicCGSolver; 16 | -------------------------------------------------------------------------------- /include/simLib/DirectSolver.h: -------------------------------------------------------------------------------- 1 | #ifndef DIRECTSOLVER_H 2 | #define DIRECTSOLVER_H 3 | 4 | #include "LinearSolver.h" 5 | 6 | namespace ClothSim 7 | { 8 | 9 | // Solves the linear system using a direct method 10 | template 11 | class DirectSolver : public LinearSolver 12 | { 13 | 14 | public: 15 | 16 | // perform a linear solve (A * result = rhs): 17 | virtual void solve(const SparseMatrix &A, const Vector &rhs, Vector &result) const; 18 | 19 | }; 20 | 21 | } //namespace ClothSim 22 | 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/simLib/BasicCGSolver.h: -------------------------------------------------------------------------------- 1 | #ifndef BASICCGSOLVER_H 2 | #define BASICCGSOLVER_H 3 | 4 | #include "LinearSolver.h" 5 | 6 | namespace ClothSim 7 | { 8 | 9 | // Solves the linear system using a direct method 10 | template 11 | class BasicCGSolver : public LinearSolver 12 | { 13 | 14 | public: 15 | 16 | // perform a linear solve (A * result = rhs): 17 | virtual void solve(const SparseMatrix &A, const Vector &rhs, Vector &result) const; 18 | 19 | }; 20 | 21 | } //namespace ClothSim 22 | 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/simLib/GravityField.cpp: -------------------------------------------------------------------------------- 1 | #include "simLib\GravityField.h" 2 | 3 | using namespace ClothSim; 4 | 5 | template 6 | GravityField::GravityField(const Vector &m, Vector3 g) : m_m(m), m_g( g ) 7 | { 8 | } 9 | 10 | template 11 | void GravityField::forcesAndDerivatives(Vector& forces, SparseMatrix &dfdx) const 12 | { 13 | for (int i = 0; i < forces.size(); i += 3) 14 | { 15 | forces.segment<3>(i) += m_m[i / 3] * m_g; 16 | } 17 | } 18 | 19 | template class GravityField; 20 | template class GravityField; 21 | -------------------------------------------------------------------------------- /include/simLib/GravityField.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAVITYFIELD_H 2 | #define GRAVITYFIELD_H 3 | 4 | #include "ForceField.h" 5 | 6 | namespace ClothSim 7 | { 8 | 9 | template 10 | class GravityField : public ForceField 11 | { 12 | public: 13 | 14 | GravityField(const Vector &m, Vector3 g); 15 | 16 | // computes forces and their derivatives: 17 | virtual void forcesAndDerivatives(Vector& forces, SparseMatrix &dfdx) const; 18 | 19 | private: 20 | 21 | const Vector &m_m; 22 | Vector3 m_g; 23 | 24 | }; 25 | 26 | } //namespace ClothSim 27 | 28 | 29 | #endif // GRAVITYFIELD_H 30 | -------------------------------------------------------------------------------- /include/simLib/LinearSolver.h: -------------------------------------------------------------------------------- 1 | #ifndef LINEARSOLVER_H 2 | #define LINEARSOLVER_H 3 | 4 | #include "Eigen/Dense" 5 | #include "Eigen/Sparse" 6 | 7 | namespace ClothSim 8 | { 9 | 10 | // abstract class for solving linear systems 11 | template 12 | class LinearSolver 13 | { 14 | 15 | public: 16 | 17 | typedef Eigen::Matrix Vector; 18 | typedef Eigen::SparseMatrix SparseMatrix; 19 | 20 | // perform a linear solve (A * result = rhs): 21 | virtual void solve(const SparseMatrix &A, const Vector &rhs, Vector &result) const = 0; 22 | 23 | }; 24 | 25 | } //namespace ClothSim 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /mayaPlugin.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {829abf4e-0bbe-4494-b0c1-5d5c976e24cf} 6 | 7 | 8 | {c4952d6f-1097-48ca-aeb3-43e141a0da74} 9 | 10 | 11 | 12 | 13 | src 14 | 15 | 16 | 17 | 18 | include 19 | 20 | 21 | -------------------------------------------------------------------------------- /include/mayaPlugin/ClothSimMayaPlugin.h: -------------------------------------------------------------------------------- 1 | #ifndef ClothSimMayaPlugin_H 2 | #define ClothSimMayaPlugin_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "simLib\ClothMesh.h" 10 | 11 | class ClothSimMayaPlugin : public MPxNode 12 | { 13 | public: 14 | 15 | ClothSimMayaPlugin(); 16 | virtual ~ClothSimMayaPlugin(); 17 | 18 | virtual MStatus compute(const MPlug& plug, MDataBlock& data); 19 | 20 | static void* creator(); 21 | static MStatus initialize(); 22 | 23 | static MTypeId id; 24 | 25 | static MObject g_aTime; 26 | static MObject g_aOutputMesh; 27 | 28 | private: 29 | 30 | MObject createMesh(const MTime& time, MObject& outData, MStatus& stat); 31 | 32 | std::auto_ptr< ClothSim::ClothMesh > m_simMesh; 33 | double m_prevTime; 34 | 35 | }; 36 | 37 | #endif // ClothSimMayaPlugin_H -------------------------------------------------------------------------------- /include/simLib/ForceField.h: -------------------------------------------------------------------------------- 1 | #ifndef FORCEFIELD_H 2 | #define FORCEFIELD_H 3 | 4 | #include 5 | #include 6 | 7 | namespace ClothSim 8 | { 9 | 10 | // abstract class for defining force fields and adding to a vector of 11 | // forces/matrix of force derivatives: 12 | template 13 | class ForceField 14 | { 15 | public: 16 | 17 | virtual ~ForceField() {} 18 | 19 | typedef Eigen::Matrix Vector; 20 | typedef Eigen::Matrix Vector2; 21 | typedef Eigen::Matrix Vector3; 22 | typedef Eigen::Matrix Matrix3; 23 | typedef Eigen::SparseMatrix SparseMatrix; 24 | 25 | // computes forces and their derivatives: 26 | virtual void forcesAndDerivatives(Vector& forces, SparseMatrix &dfdx) const = 0; 27 | 28 | }; 29 | 30 | } //namespace ClothSim 31 | 32 | #endif // FORCEFIELD_H 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # visual c++ crap 2 | *.tlog 3 | *.obj 4 | *.pdb 5 | *.lib 6 | *.exp 7 | *.log 8 | *.sdf 9 | *.suo 10 | *.opensdf 11 | *.ipch 12 | *.user 13 | *.idb 14 | *.ilk 15 | 16 | # maya plugins etc 17 | *.mll 18 | *.dll 19 | 20 | # Windows image file caches 21 | Thumbs.db 22 | ehthumbs.db 23 | 24 | # Folder config file 25 | Desktop.ini 26 | 27 | # Recycle Bin used on file shares 28 | $RECYCLE.BIN/ 29 | 30 | # Windows Installer files 31 | *.cab 32 | *.msi 33 | *.msm 34 | *.msp 35 | 36 | # Windows shortcuts 37 | *.lnk 38 | 39 | # ========================= 40 | # Operating System Files 41 | # ========================= 42 | 43 | # OSX 44 | # ========================= 45 | 46 | .DS_Store 47 | .AppleDouble 48 | .LSOverride 49 | 50 | # Thumbnails 51 | ._* 52 | 53 | # Files that might appear on external disk 54 | .Spotlight-V100 55 | .Trashes 56 | 57 | # Directories potentially created on remote AFP share 58 | .AppleDB 59 | .AppleDesktop 60 | Network Trash Folder 61 | Temporary Items 62 | .apdisk 63 | -------------------------------------------------------------------------------- /testing.runsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | .*\.dll$ 13 | .*\.lib$ 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /include/simLib/EnergyCondition.h: -------------------------------------------------------------------------------- 1 | #ifndef ENERGYCONDITION_H 2 | #define ENERGYCONDITION_H 3 | 4 | #include 5 | #include 6 | 7 | namespace ClothSim 8 | { 9 | 10 | // abstract class for defining energy conditions and adding to a vector of 11 | // forces/matrix of force derivatives: 12 | template 13 | class EnergyCondition 14 | { 15 | public: 16 | 17 | virtual ~EnergyCondition() {} 18 | 19 | typedef Eigen::Matrix Vector; 20 | typedef Eigen::Matrix Vector2; 21 | typedef Eigen::Matrix Vector3; 22 | typedef Eigen::Matrix Matrix3; 23 | typedef Eigen::SparseMatrix SparseMatrix; 24 | 25 | // compute the vector condition function C for testing purposes: 26 | virtual Vector C(const Vector& x, const Vector& uv) const = 0; 27 | 28 | // computes forces and their derivatives: 29 | virtual void computeForces(const Vector& x, const Vector& uv, Real k, Vector& forces, SparseMatrix &dfdx, const Vector& v, Real d, Vector &dampingForces, SparseMatrix &dampingPseudoXDerivatives, SparseMatrix &dddv) const = 0; 30 | 31 | }; 32 | 33 | } //namespace ClothSim 34 | 35 | 36 | #endif // ENERGYCONDITION_H 37 | -------------------------------------------------------------------------------- /UnitTest1.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {0cadbf8e-ede3-4606-a467-0f9f378d56d5} 6 | 7 | 8 | {9d8492bb-d6fc-492b-993b-00f4c5108c2d} 9 | 10 | 11 | 12 | 13 | include 14 | 15 | 16 | 17 | 18 | src 19 | 20 | 21 | src 22 | 23 | 24 | src 25 | 26 | 27 | src 28 | 29 | 30 | src 31 | 32 | 33 | -------------------------------------------------------------------------------- /include/simLib/ConstrainedCGSolver.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTRAINEDCCGSOLVER_H 2 | #define CONSTRAINEDCCGSOLVER_H 3 | 4 | #include "LinearSolver.h" 5 | 6 | namespace ClothSim 7 | { 8 | 9 | // Solves the linear system using conjugate gradients, 10 | // projecting out the given constraints at every step: 11 | template 12 | class ConstrainedCGSolver : public LinearSolver 13 | { 14 | 15 | public: 16 | 17 | typedef Eigen::Matrix Vector; 18 | typedef Eigen::Matrix Matrix3; 19 | 20 | ConstrainedCGSolver( 21 | const std::vector &constraintIndices, 22 | const std::vector< Matrix3 > &constraintMatrices, 23 | const Vector &constraintVelocityDeltas, 24 | Real tol, 25 | int maxIterations 26 | ); 27 | 28 | // perform a linear solve (A * result = rhs): 29 | virtual void solve(const SparseMatrix &A, const Vector &rhs, Vector &result) const; 30 | 31 | private: 32 | 33 | void filter(Vector &x) const; 34 | void precondition(Vector &x, Vector &p, bool inverse) const; 35 | 36 | const std::vector &m_constraintIndices; 37 | const std::vector< Matrix3 > &m_constraintMatrices; 38 | const Vector &m_constraintVelocityDeltas; 39 | Real m_tol; 40 | int m_maxIterations; 41 | 42 | }; 43 | 44 | } //namespace ClothSim 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /include/test/EqualityTests.h: -------------------------------------------------------------------------------- 1 | #ifndef EQUALITYTESTS_H 2 | #define EQUALITYTESTS_H 3 | 4 | #include "simLib\EnergyCondition.h" 5 | 6 | void checkVectorEquality(Eigen::Vector3d v0, Eigen::Vector3d v1, double tol); 7 | void checkVectorEquality(Eigen::VectorXd v0, Eigen::VectorXd v1, double tol, bool relative = false); 8 | void checkMatrixEquality(Eigen::Matrix3d m0, Eigen::Matrix3d m1, double tol); 9 | void checkPseudoDerivatives(const Eigen::SparseMatrix &dampingPseudoDerivatives, const ClothSim::EnergyCondition &c, const Eigen::VectorXd &uv, Eigen::VectorXd &x, Eigen::VectorXd &v, double k, double d, double dx, double tol); 10 | 11 | double numericalForce(const ClothSim::EnergyCondition &c, const Eigen::VectorXd &uv, const Eigen::VectorXd &x, double k, int i, double dx); 12 | double numericalDampingForce(const ClothSim::EnergyCondition &c, const Eigen::VectorXd &uv, const Eigen::VectorXd &x, const Eigen::VectorXd &v, double d, int i, double dx); 13 | Eigen::VectorXd numericalForceDerivative(const ClothSim::EnergyCondition &c, const Eigen::VectorXd &uv, Eigen::VectorXd &x, Eigen::VectorXd &v, double k, double d, int i, double dx); 14 | Eigen::VectorXd numericalDampingForceDerivative(const ClothSim::EnergyCondition &c, const Eigen::VectorXd &uv, Eigen::VectorXd &x, Eigen::VectorXd &v, double k, double d, int i, double dx); 15 | 16 | #endif // EQUALITYTESTS_H -------------------------------------------------------------------------------- /include/simLib/ShearCondition.h: -------------------------------------------------------------------------------- 1 | #ifndef SHEARCONDITION_H 2 | #define SHEARCONDITION_H 3 | 4 | #include "EnergyCondition.h" 5 | #include "TangentTriangleQuantities.h" 6 | 7 | namespace ClothSim 8 | { 9 | 10 | template 11 | class ShearCondition : public EnergyCondition 12 | { 13 | 14 | public: 15 | 16 | ShearCondition(int i0, int i1, int i2); 17 | virtual ~ShearCondition() {} 18 | 19 | virtual Vector C(const Vector& x, const Vector& uv) const; 20 | 21 | virtual void computeForces(const Vector& x, const Vector& uv, Real k, Vector& forces, SparseMatrix &dfdx, const Vector& v, Real d, Vector &dampingForces, SparseMatrix &dampingPseudoXDerivatives, SparseMatrix &dddv) const; 22 | 23 | struct TriangleQuantities : public TangentTriangleQuantities 24 | { 25 | TriangleQuantities(); 26 | TriangleQuantities( 27 | const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, 28 | const Vector2 &uv0, const Vector2 &uv1, const Vector2 &uv2, 29 | const Vector3 &v0, const Vector3 &v1, const Vector3 &v2 30 | ); 31 | 32 | // normalized tangents: 33 | Vector3 wuHat, wvHat; 34 | 35 | // energy condition: 36 | Real C; 37 | 38 | // time derivative of energy condition: 39 | Real dCdt; 40 | 41 | // derivatives of the energy condition: 42 | Vector3 dCdP0, dCdP1, dCdP2; 43 | 44 | // second derivatives of the energy condition: 45 | Matrix3 d2CdP0dP0, d2CdP0dP1, d2CdP0dP2; 46 | Matrix3 d2CdP1dP0, d2CdP1dP1, d2CdP1dP2; 47 | Matrix3 d2CdP2dP0, d2CdP2dP1, d2CdP2dP2; 48 | 49 | }; 50 | 51 | int const *inds() const 52 | { 53 | return m_inds; 54 | } 55 | 56 | private: 57 | 58 | // indices of the vertices this shear condition is attached to: 59 | int m_inds[3]; 60 | 61 | }; 62 | 63 | } //namespace ClothSim 64 | 65 | #endif // SHEARCONDITION_H 66 | -------------------------------------------------------------------------------- /include/simLib/StretchCondition.h: -------------------------------------------------------------------------------- 1 | #ifndef STRETCHCONDITION_H 2 | #define STRETCHCONDITION_H 3 | 4 | #include "EnergyCondition.h" 5 | #include "TangentTriangleQuantities.h" 6 | 7 | namespace ClothSim 8 | { 9 | 10 | template 11 | class StretchCondition : public EnergyCondition 12 | { 13 | 14 | public: 15 | 16 | StretchCondition(int i0, int i1, int i2, Real restU, Real restV); 17 | virtual ~StretchCondition() {} 18 | 19 | virtual Vector C(const Vector& x, const Vector& uv) const; 20 | 21 | virtual void computeForces(const Vector& x, const Vector& uv, Real k, Vector& forces, SparseMatrix &dfdx, const Vector& v, Real d, Vector &dampingForces, SparseMatrix &dampingPseudoXDerivatives, SparseMatrix &dddv) const; 22 | 23 | struct TriangleQuantities : public TangentTriangleQuantities 24 | { 25 | TriangleQuantities(); 26 | TriangleQuantities( 27 | const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, 28 | const Vector2 &uv0, const Vector2 &uv1, const Vector2 &uv2, 29 | const Vector3 &v0, const Vector3 &v1, const Vector3 &v2, 30 | Real bu, Real bv 31 | ); 32 | 33 | // energy condition: 34 | Real C0, C1; 35 | 36 | // time derivatives of energy condition: 37 | Real dC0dt, dC1dt; 38 | 39 | // derivatives of the energy conditions: 40 | Vector3 dC0dP0, dC0dP1, dC0dP2; 41 | Vector3 dC1dP0, dC1dP1, dC1dP2; 42 | 43 | // second derivatives of the energy conditions: 44 | Matrix3 d2C0dP0dP0, d2C0dP0dP1, d2C0dP0dP2; 45 | Matrix3 d2C0dP1dP0, d2C0dP1dP1, d2C0dP1dP2; 46 | Matrix3 d2C0dP2dP0, d2C0dP2dP1, d2C0dP2dP2; 47 | 48 | Matrix3 d2C1dP0dP0, d2C1dP0dP1, d2C1dP0dP2; 49 | Matrix3 d2C1dP1dP0, d2C1dP1dP1, d2C1dP1dP2; 50 | Matrix3 d2C1dP2dP0, d2C1dP2dP1, d2C1dP2dP2; 51 | 52 | }; 53 | 54 | int const *inds() const 55 | { 56 | return m_inds; 57 | } 58 | 59 | private: 60 | 61 | // indices of the vertices this stretch condition is attached to: 62 | int m_inds[3]; 63 | 64 | // rest stretches: 65 | Real m_restU; 66 | Real m_restV; 67 | }; 68 | 69 | } //namespace ClothSim 70 | 71 | 72 | #endif // STRETCHCONDITION_H 73 | -------------------------------------------------------------------------------- /include/simLib/TangentTriangleQuantities.h: -------------------------------------------------------------------------------- 1 | #ifndef TANGENTTRIANGLEQUANTITIES_H 2 | #define TANGENTTRIANGLEQUANTITIES_H 3 | 4 | #include "EnergyCondition.h" 5 | 6 | namespace ClothSim 7 | { 8 | 9 | template 10 | struct TangentTriangleQuantities 11 | { 12 | typedef typename EnergyCondition::Vector2 Vector2; 13 | typedef typename EnergyCondition::Vector3 Vector3; 14 | typedef typename EnergyCondition::Matrix3 Matrix3; 15 | 16 | // area: 17 | Real a; 18 | 19 | // tangent vectors: 20 | Vector3 wu, wv; 21 | 22 | // derivatives of the tangent vectors: 23 | Matrix3 dwudP0, dwudP1, dwudP2; 24 | Matrix3 dwvdP0, dwvdP1, dwvdP2; 25 | 26 | Real dwudP0Scalar, dwudP1Scalar, dwudP2Scalar; 27 | Real dwvdP0Scalar, dwvdP1Scalar, dwvdP2Scalar; 28 | 29 | TangentTriangleQuantities() {} 30 | TangentTriangleQuantities( 31 | const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, 32 | const Vector2 &uv0, const Vector2 &uv1, const Vector2 &uv2 33 | ) 34 | { 35 | Vector2 duv1 = uv1 - uv0; 36 | Vector2 duv2 = uv2 - uv0; 37 | 38 | Real du1 = duv1[0]; 39 | Real dv1 = duv1[1]; 40 | Real du2 = duv2[0]; 41 | Real dv2 = duv2[1]; 42 | 43 | // triangle area in reference pose: 44 | a = Real(0.5) * (du1 * dv2 - du2 * dv1); 45 | 46 | // trangle tangents in reference directions: 47 | wu = ((p1 - p0) * dv2 - (p2 - p0) * dv1) / (2 * a); 48 | wv = (-(p1 - p0) * du2 + (p2 - p0) * du1) / (2 * a); 49 | 50 | // first derivatives of uv tangents: 51 | dwudP0Scalar = (dv1 - dv2) / (2 * a); 52 | dwudP1Scalar = dv2 / (2 * a); 53 | dwudP2Scalar = -dv1 / (2 * a); 54 | 55 | dwvdP0Scalar = (du2 - du1) / (2 * a); 56 | dwvdP1Scalar = -du2 / (2 * a); 57 | dwvdP2Scalar = du1 / (2 * a); 58 | 59 | 60 | dwudP0 = Matrix3::Identity() * dwudP0Scalar; 61 | dwudP1 = Matrix3::Identity() * dwudP1Scalar; 62 | dwudP2 = Matrix3::Identity() * dwudP2Scalar; 63 | 64 | dwvdP0 = Matrix3::Identity() * dwvdP0Scalar; 65 | dwvdP1 = Matrix3::Identity() * dwvdP1Scalar; 66 | dwvdP2 = Matrix3::Identity() * dwvdP2Scalar; 67 | 68 | } 69 | }; 70 | 71 | } //namespace ClothSim 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/simLib/ConstrainedCGSolver.cpp: -------------------------------------------------------------------------------- 1 | #include "simLib\ConstrainedCGSolver.h" 2 | 3 | using namespace ClothSim; 4 | 5 | template 6 | ConstrainedCGSolver::ConstrainedCGSolver( 7 | const std::vector &constraintIndices, 8 | const std::vector< Matrix3 > &constraintMatrices, 9 | const Vector &constraintVelocityDeltas, 10 | Real tol, 11 | int maxIterations 12 | ) : 13 | m_constraintIndices(constraintIndices), 14 | m_constraintMatrices(constraintMatrices), 15 | m_constraintVelocityDeltas(constraintVelocityDeltas), 16 | m_tol(tol), 17 | m_maxIterations(maxIterations) 18 | { 19 | } 20 | 21 | template 22 | void ConstrainedCGSolver::solve(const SparseMatrix &A, const Vector &b, Vector &deltaV) const 23 | { 24 | //\todo: add preconditioner 25 | Vector P(b.size()); 26 | for (int i = 0; i < b.size(); ++i) 27 | { 28 | P[i] = A.coeff(i,i); 29 | } 30 | 31 | deltaV = m_constraintVelocityDeltas; 32 | 33 | Real delta0; 34 | { 35 | Vector filterB = b * Real(1.0); 36 | filter(filterB); 37 | Vector filterBPrecond = filterB * Real(1.0); 38 | precondition(filterBPrecond, P, false); 39 | delta0 = filterB.dot(filterBPrecond); 40 | } 41 | 42 | Vector r = b - A * deltaV; 43 | filter(r); 44 | Vector c = r * Real(1.0); 45 | precondition(c, P, true); 46 | filter(c); 47 | 48 | Real deltaNew = r.dot(c); 49 | 50 | Real deltaOld; 51 | Vector q, s; 52 | 53 | for (int i = 0; i < m_maxIterations && deltaNew > m_tol * m_tol * delta0; ++i) 54 | { 55 | q = A * c; 56 | filter(q); 57 | 58 | Real alpha = deltaNew / c.dot(q); 59 | deltaV += alpha * c; 60 | r -= alpha * q; 61 | s = r * Real(1.0); 62 | precondition(s, P, true); 63 | 64 | deltaOld = deltaNew; 65 | deltaNew = r.dot(s); 66 | 67 | c = s + (deltaNew / deltaOld) * c; 68 | filter(c); 69 | } 70 | } 71 | 72 | template 73 | void ConstrainedCGSolver::filter(Vector &x) const 74 | { 75 | std::vector::const_iterator it = m_constraintIndices.begin(); 76 | std::vector::const_iterator matrixIt = m_constraintMatrices.begin(); 77 | for (; it != m_constraintIndices.end(); ++it, ++matrixIt) 78 | { 79 | int idx = *it; 80 | x.segment<3>(3 * idx) = (*matrixIt) * x.segment<3>(3 * idx); 81 | } 82 | } 83 | 84 | template 85 | void ConstrainedCGSolver::precondition(Vector &x, Vector &p, bool inverse) const 86 | { 87 | if (inverse) 88 | { 89 | x.array() /= p.array(); 90 | } 91 | else 92 | { 93 | x.array() *= p.array(); 94 | } 95 | } 96 | 97 | 98 | template class ConstrainedCGSolver; 99 | template class ConstrainedCGSolver; 100 | -------------------------------------------------------------------------------- /simLib.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | 7 | 8 | src 9 | 10 | 11 | src 12 | 13 | 14 | src 15 | 16 | 17 | src 18 | 19 | 20 | src 21 | 22 | 23 | src 24 | 25 | 26 | src 27 | 28 | 29 | 30 | 31 | include 32 | 33 | 34 | include 35 | 36 | 37 | include 38 | 39 | 40 | include 41 | 42 | 43 | include 44 | 45 | 46 | include 47 | 48 | 49 | include 50 | 51 | 52 | include 53 | 54 | 55 | include 56 | 57 | 58 | include 59 | 60 | 61 | include 62 | 63 | 64 | include 65 | 66 | 67 | 68 | 69 | {d6a5bde6-c49e-4407-8d0c-a0b4d8a0cef0} 70 | 71 | 72 | {fe4a2330-e0d9-4d2f-87a1-aa4be2a0ca9a} 73 | 74 | 75 | -------------------------------------------------------------------------------- /include/simLib/BendCondition.h: -------------------------------------------------------------------------------- 1 | #ifndef BENDCONDITION_H 2 | #define BENDCONDITION_H 3 | 4 | #include "EnergyCondition.h" 5 | 6 | namespace ClothSim 7 | { 8 | 9 | template 10 | class BendCondition : public EnergyCondition 11 | { 12 | 13 | public: 14 | 15 | BendCondition(int i0, int i1, int i2, int i3); 16 | virtual ~BendCondition() {} 17 | 18 | virtual Vector C(const Vector& x, const Vector& uv) const; 19 | 20 | virtual void computeForces(const Vector& x, const Vector& uv, Real k, Vector& forces, SparseMatrix &dfdx, const Vector& v, Real d, Vector &dampingForces, SparseMatrix &dampingPseudoXDerivatives, SparseMatrix &dddv) const; 21 | 22 | struct TriangleQuantities 23 | { 24 | TriangleQuantities(); 25 | TriangleQuantities(const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, const Vector3 &p3, const Vector3 &v0, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3); 26 | 27 | // edge vectors: 28 | Vector3 v01, v02, v32, v31, v21; 29 | 30 | // normalized edge vectors: 31 | Vector3 e01, e02, e32, e31, e21; 32 | 33 | // cosines of the angles at each of the points: 34 | Real c00, c01, c02, c13, c11, c12; 35 | 36 | // normalized triangle normals: 37 | Vector3 n0, n1; 38 | 39 | // normalized binormals for each triangle, pointing from a vertex to its opposite edge: 40 | Vector3 b00, b01, b02, b13, b12, b11; 41 | 42 | // vertex distances to opposite edges: 43 | Real d00, d01, d02, d11, d12, d13; 44 | 45 | // compute angle between triangles: 46 | Real theta; 47 | 48 | // derivatives of theta with respect to the different vertex positions: 49 | Vector3 dThetadP0, dThetadP1, dThetadP2, dThetadP3; 50 | 51 | // time derivative of angle: 52 | Real dThetadt; 53 | 54 | // derivatives of the normals: 55 | Matrix3 dn0dP0, dn0dP1, dn0dP2, dn0dP3, dn1dP0, dn1dP1, dn1dP2, dn1dP3; 56 | 57 | // derivatives of the cosines: 58 | Vector3 dc01dP0, dc01dP1, dc01dP2, dc01dP3, dc02dP0, dc02dP1, dc02dP2, dc02dP3, dc11dP0, dc11dP1, dc11dP2, dc11dP3, dc12dP0, dc12dP1, dc12dP2, dc12dP3; 59 | 60 | // derivatives of the perpendicular distances: 61 | Vector3 dd00dP0, dd00dP1, dd00dP2, dd00dP3, dd01dP0, dd01dP1, dd01dP2, dd01dP3, dd02dP0, dd02dP1, dd02dP2, dd02dP3, dd11dP0, dd11dP1, dd11dP2, dd11dP3, dd12dP0, dd12dP1, dd12dP2, dd12dP3, dd13dP0, dd13dP1, dd13dP2, dd13dP3; 62 | 63 | // second derivatives of theta with respect to the different vertex positions: 64 | Matrix3 d2ThetadP0dP0, d2ThetadP0dP1, d2ThetadP0dP2, d2ThetadP0dP3; 65 | Matrix3 d2ThetadP1dP0, d2ThetadP1dP1, d2ThetadP1dP2, d2ThetadP1dP3; 66 | Matrix3 d2ThetadP2dP0, d2ThetadP2dP1, d2ThetadP2dP2, d2ThetadP2dP3; 67 | Matrix3 d2ThetadP3dP0, d2ThetadP3dP1, d2ThetadP3dP2, d2ThetadP3dP3; 68 | }; 69 | 70 | int const *inds() const 71 | { 72 | return m_inds; 73 | } 74 | 75 | private: 76 | 77 | // bend condition is attached to two triangles, ie four vertices like so: 78 | // 79 | // 0 80 | // / \ 81 | // / \ 82 | // 1-----2 83 | // \ / 84 | // \ / 85 | // 3 86 | // 87 | int m_inds[4]; 88 | 89 | }; 90 | 91 | } //namespace ClothSim 92 | 93 | #endif // BENDCONDITION_H 94 | -------------------------------------------------------------------------------- /include/simLib/ClothMesh.h: -------------------------------------------------------------------------------- 1 | #ifndef CLOTHMESH_H 2 | #define CLOTHMESH_H 3 | 4 | 5 | #include "LinearSolver.h" 6 | 7 | #include "BendCondition.h" 8 | #include "ShearCondition.h" 9 | #include "StretchCondition.h" 10 | 11 | #include "ForceField.h" 12 | 13 | namespace ClothSim 14 | { 15 | 16 | template 17 | class ClothMesh 18 | { 19 | 20 | public: 21 | 22 | typedef Eigen::Matrix Vector; 23 | typedef Eigen::Matrix Vector2; 24 | typedef Eigen::Matrix Vector3; 25 | typedef Eigen::Matrix Matrix3; 26 | typedef Eigen::SparseMatrix SparseMatrix; 27 | 28 | ClothMesh( 29 | const Vector &x, 30 | const Vector &v, 31 | const Vector &uv, 32 | const std::vector &triangleIndices, 33 | Real kBend, Real kStretch, Real kShear, 34 | Real dBend, Real dStretch, Real dShear, 35 | Real density 36 | ); 37 | 38 | // accessor for positions: 39 | const Vector &x() const; 40 | 41 | // accessor for velocities: 42 | const Vector &v() const; 43 | 44 | // accessor for masses: 45 | const Vector &m() const; 46 | 47 | // accessor for indices: 48 | const std::vector &triangleIndices(); 49 | 50 | // accessors for the energy terms: 51 | const std::vector< BendCondition > &bendConditions() const; 52 | const std::vector< ShearCondition > &shearConditions() const; 53 | const std::vector< StretchCondition > &stretchConditions() const; 54 | 55 | // advance the simulation: 56 | void advance(std::vector< ForceField* > &forceFields, Real dt, const LinearSolver &solver); 57 | 58 | // calculate forces and derivatives: 59 | void forcesAndDerivatives(const Vector &x, const Vector &uv, const Vector &v, Vector &f, Vector &d, SparseMatrix &dfdx, SparseMatrix &dddx, SparseMatrix &dddv) const; 60 | 61 | // get the implicit update linear system ready: 62 | void assembleImplicitUpdateEquations(Real dt, const Vector &f, const Vector &v, const SparseMatrix &dfdx, const SparseMatrix &dfdv, SparseMatrix &implicitUpdateMatrix, Vector &implicitUpdateRHS) const; 63 | 64 | // energy function for testing: 65 | Real energy(const Vector& x, const Vector& uv) const; 66 | 67 | // vector containing all the constraint functions for testing: 68 | void C(const Vector& x, const Vector& uv, Vector& c) const; 69 | 70 | private: 71 | 72 | // positions: 73 | Vector m_x; 74 | 75 | // velocities: 76 | Vector m_v; 77 | 78 | // masses: 79 | Vector m_m; 80 | 81 | // reference pose: 82 | Vector m_uv; 83 | 84 | SparseMatrix m_implicitUpdateMatrix; 85 | SparseMatrix m_dfdx; 86 | SparseMatrix m_dfdv; 87 | 88 | const std::vector m_triangleIndices; 89 | 90 | // material properties: 91 | Real m_kBend; 92 | Real m_kStretch; 93 | Real m_kShear; 94 | 95 | Real m_dBend; 96 | Real m_dStretch; 97 | Real m_dShear; 98 | 99 | // energy terms: 100 | std::vector< BendCondition > m_bendConditions; 101 | std::vector< ShearCondition > m_shearConditions; 102 | std::vector< StretchCondition > m_stretchConditions; 103 | 104 | }; 105 | 106 | } //namespace ClothSim 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /simLib.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.40629.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "simLib", "simLib.vcxproj", "{681412B0-F197-4A2F-9263-DEA2E8690146}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTest1", "UnitTest1.vcxproj", "{A6920408-6FDD-4CFB-9BF4-21F5F277358F}" 9 | ProjectSection(ProjectDependencies) = postProject 10 | {681412B0-F197-4A2F-9263-DEA2E8690146} = {681412B0-F197-4A2F-9263-DEA2E8690146} 11 | EndProjectSection 12 | EndProject 13 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "particleNeighbourCompare", "mayaPlugin.vcxproj", "{E57E653C-6070-429A-B64A-B7C6FDC3002D}" 14 | ProjectSection(ProjectDependencies) = postProject 15 | {681412B0-F197-4A2F-9263-DEA2E8690146} = {681412B0-F197-4A2F-9263-DEA2E8690146} 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Mixed Platforms = Debug|Mixed Platforms 21 | Debug|Win32 = Debug|Win32 22 | Debug|x64 = Debug|x64 23 | Release|Mixed Platforms = Release|Mixed Platforms 24 | Release|Win32 = Release|Win32 25 | Release|x64 = Release|x64 26 | ReleaseDebug|Mixed Platforms = ReleaseDebug|Mixed Platforms 27 | ReleaseDebug|Win32 = ReleaseDebug|Win32 28 | ReleaseDebug|x64 = ReleaseDebug|x64 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 32 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Debug|Mixed Platforms.Build.0 = Debug|x64 33 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Debug|Win32.ActiveCfg = Debug|x64 34 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Debug|x64.ActiveCfg = Debug|x64 35 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Debug|x64.Build.0 = Debug|x64 36 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Release|Mixed Platforms.ActiveCfg = Release|x64 37 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Release|Mixed Platforms.Build.0 = Release|x64 38 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Release|Win32.ActiveCfg = Release|x64 39 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Release|x64.ActiveCfg = Release|x64 40 | {681412B0-F197-4A2F-9263-DEA2E8690146}.Release|x64.Build.0 = Release|x64 41 | {681412B0-F197-4A2F-9263-DEA2E8690146}.ReleaseDebug|Mixed Platforms.ActiveCfg = ReleaseDebug|x64 42 | {681412B0-F197-4A2F-9263-DEA2E8690146}.ReleaseDebug|Mixed Platforms.Build.0 = ReleaseDebug|x64 43 | {681412B0-F197-4A2F-9263-DEA2E8690146}.ReleaseDebug|Win32.ActiveCfg = ReleaseDebug|x64 44 | {681412B0-F197-4A2F-9263-DEA2E8690146}.ReleaseDebug|x64.ActiveCfg = ReleaseDebug|x64 45 | {681412B0-F197-4A2F-9263-DEA2E8690146}.ReleaseDebug|x64.Build.0 = ReleaseDebug|x64 46 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 47 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Debug|Mixed Platforms.Build.0 = Debug|x64 48 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Debug|Win32.ActiveCfg = Debug|x64 49 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Debug|x64.ActiveCfg = Debug|x64 50 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Debug|x64.Build.0 = Debug|x64 51 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Release|Mixed Platforms.ActiveCfg = Release|x64 52 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Release|Mixed Platforms.Build.0 = Release|x64 53 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Release|Win32.ActiveCfg = Release|x64 54 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Release|x64.ActiveCfg = Release|x64 55 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.Release|x64.Build.0 = Release|x64 56 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.ReleaseDebug|Mixed Platforms.ActiveCfg = Release|x64 57 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.ReleaseDebug|Mixed Platforms.Build.0 = Release|x64 58 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.ReleaseDebug|Win32.ActiveCfg = Release|x64 59 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.ReleaseDebug|x64.ActiveCfg = Release|x64 60 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F}.ReleaseDebug|x64.Build.0 = Release|x64 61 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 62 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Debug|Mixed Platforms.Build.0 = Debug|x64 63 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Debug|Win32.ActiveCfg = Debug|x64 64 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Debug|x64.ActiveCfg = Debug|x64 65 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Debug|x64.Build.0 = Debug|x64 66 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Release|Mixed Platforms.ActiveCfg = Release|x64 67 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Release|Mixed Platforms.Build.0 = Release|x64 68 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Release|Win32.ActiveCfg = Release|x64 69 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Release|x64.ActiveCfg = Release|x64 70 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.Release|x64.Build.0 = Release|x64 71 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.ReleaseDebug|Mixed Platforms.ActiveCfg = ReleaseDebug|x64 72 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.ReleaseDebug|Mixed Platforms.Build.0 = ReleaseDebug|x64 73 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.ReleaseDebug|Win32.ActiveCfg = ReleaseDebug|x64 74 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.ReleaseDebug|x64.ActiveCfg = ReleaseDebug|x64 75 | {E57E653C-6070-429A-B64A-B7C6FDC3002D}.ReleaseDebug|x64.Build.0 = ReleaseDebug|x64 76 | EndGlobalSection 77 | GlobalSection(SolutionProperties) = preSolution 78 | HideSolutionNode = FALSE 79 | EndGlobalSection 80 | EndGlobal 81 | -------------------------------------------------------------------------------- /UnitTest1.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {A6920408-6FDD-4CFB-9BF4-21F5F277358F} 25 | Win32Proj 26 | UnitTest1 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v120 33 | Unicode 34 | false 35 | 36 | 37 | DynamicLibrary 38 | false 39 | v120 40 | true 41 | Unicode 42 | false 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | true 56 | C:\projects\oculusMusicVis\src;$(IncludePath) 57 | $(SolutionDir)$(Platform)\test\$(Configuration)\ 58 | $(Platform)\test\$(Configuration)\ 59 | 60 | 61 | true 62 | C:\projects\oculusMusicVis\src;$(IncludePath) 63 | $(SolutionDir)$(Platform)\test\$(Configuration)\ 64 | $(Platform)\test\$(Configuration)\ 65 | 66 | 67 | 68 | NotUsing 69 | Level3 70 | Disabled 71 | C:\projects\clothSim\include;$(VCInstallDir)UnitTest\include;C:\Software Libraries\eigen-3.2.7;%(AdditionalIncludeDirectories) 72 | WIN32;_DEBUG;%(PreprocessorDefinitions) 73 | true 74 | 75 | 76 | Windows 77 | true 78 | $(SolutionDir)$(Platform)\$(Configuration)\;$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 79 | simLib.lib;%(AdditionalDependencies) 80 | 81 | 82 | 83 | 84 | Level3 85 | Use 86 | MaxSpeed 87 | true 88 | true 89 | C:\projects\clothSim\include;$(VCInstallDir)UnitTest\include;C:\Software Libraries\eigen-3.2.7;%(AdditionalIncludeDirectories) 90 | WIN32;NDEBUG;%(PreprocessorDefinitions) 91 | true 92 | 93 | 94 | Windows 95 | true 96 | true 97 | true 98 | $(SolutionDir)$(Platform)\$(Configuration)\;$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 99 | simLib.lib;%(AdditionalDependencies) 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/test/EqualityTests.cpp: -------------------------------------------------------------------------------- 1 | #include "test\EqualityTests.h" 2 | 3 | #include "CppUnitTest.h" 4 | 5 | using namespace ClothSim; 6 | 7 | void checkVectorEquality(Eigen::Vector3d v0, Eigen::Vector3d v1, double tol) 8 | { 9 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(v0[0], v1[0], tol); 10 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(v0[1], v1[1], tol); 11 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(v0[2], v1[2], tol); 12 | } 13 | 14 | void checkVectorEquality(Eigen::VectorXd v0, Eigen::VectorXd v1, double tol, bool relative) 15 | { 16 | if (relative) 17 | { 18 | for (int i = 0; i < v0.size(); ++i) 19 | { 20 | if (fabs(v0[i]) > 0.01) 21 | { 22 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(v0[i], v1[i], tol*v0[i]); 23 | } 24 | else 25 | { 26 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(v0[i], v1[i], tol); 27 | } 28 | } 29 | } 30 | else 31 | { 32 | for (int i = 0; i < v0.size(); ++i) 33 | { 34 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(v0[i], v1[i], tol); 35 | } 36 | } 37 | } 38 | 39 | void checkMatrixEquality(Eigen::Matrix3d m0, Eigen::Matrix3d m1, double tol) 40 | { 41 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(0, 0), m1(0, 0), tol); 42 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(0, 1), m1(0, 1), tol); 43 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(0, 2), m1(0, 2), tol); 44 | 45 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(1, 0), m1(1, 0), tol); 46 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(1, 1), m1(1, 1), tol); 47 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(1, 2), m1(1, 2), tol); 48 | 49 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(2, 0), m1(2, 0), tol); 50 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(2, 1), m1(2, 1), tol); 51 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(m0(2, 2), m1(2, 2), tol); 52 | } 53 | 54 | double numericalForce(const EnergyCondition &c, const Eigen::VectorXd &uv, const Eigen::VectorXd &x, double k, int i, double dx) 55 | { 56 | Eigen::VectorXd xtest = x; 57 | xtest[i] = x[i] + dx; 58 | double ePlus = 0.5 * c.C(xtest, uv).squaredNorm(); 59 | xtest[i] = x[i] - dx; 60 | double eMinus = 0.5 * c.C(xtest, uv).squaredNorm(); 61 | 62 | // f = -k * dE/dx 63 | return -k * (ePlus - eMinus) / (2 * dx); 64 | } 65 | 66 | static Eigen::VectorXd numericalCTimeDerivative(const EnergyCondition &c, const Eigen::VectorXd &x, const Eigen::VectorXd &v, const Eigen::VectorXd &uv, double dx) 67 | { 68 | Eigen::VectorXd xtest; 69 | xtest = x + dx * v; 70 | Eigen::VectorXd cPlus = c.C(xtest, uv); 71 | xtest = x - dx * v; 72 | Eigen::VectorXd cMinus = c.C(xtest, uv); 73 | 74 | return (cPlus - cMinus) / (2 * dx); 75 | } 76 | 77 | static Eigen::VectorXd numericalFirstCDerivative(const EnergyCondition &c, const Eigen::VectorXd &x, const Eigen::VectorXd &uv, int i, double dx) 78 | { 79 | Eigen::VectorXd xtest = x; 80 | 81 | xtest[i] = x[i] + dx; 82 | Eigen::VectorXd cPlus = c.C(xtest, uv); 83 | xtest[i] = x[i] - dx; 84 | Eigen::VectorXd cMinus = c.C(xtest, uv); 85 | 86 | return (cPlus - cMinus) / (2 * dx); 87 | } 88 | 89 | static Eigen::VectorXd numericalSecondCDerivative(const EnergyCondition &c, const Eigen::VectorXd &x, const Eigen::VectorXd &uv, int i, int j, double dx) 90 | { 91 | Eigen::VectorXd xtest = x; 92 | 93 | xtest[j] = x[j] + dx; 94 | Eigen::VectorXd dCdxPlus = numericalFirstCDerivative(c, xtest, uv, i, dx); 95 | xtest[j] = x[j] - dx; 96 | Eigen::VectorXd dCdxcMinus = numericalFirstCDerivative(c, xtest, uv, i, dx); 97 | 98 | return (dCdxPlus - dCdxcMinus) / (2 * dx); 99 | } 100 | 101 | double numericalDampingForce(const EnergyCondition &c, const Eigen::VectorXd &uv, const Eigen::VectorXd &x, const Eigen::VectorXd &v, double d, int i, double dx) 102 | { 103 | // find dC/dt 104 | Eigen::VectorXd dCdt = numericalCTimeDerivative(c, x, v, uv, dx); 105 | 106 | // find dC/dx 107 | Eigen::VectorXd dCdx = numericalFirstCDerivative(c, x, uv, i, dx); 108 | 109 | // fd = -d * sum( i, dC_i/dx * dC_i/dt ) 110 | return -d * (dCdx.array() * dCdt.array()).sum(); 111 | } 112 | 113 | Eigen::VectorXd numericalForceDerivative(const EnergyCondition &c, const Eigen::VectorXd &uv, Eigen::VectorXd &x, Eigen::VectorXd &v, double k, double d, int i, double dx) 114 | { 115 | Eigen::SparseMatrix dfdx((int)x.size(), (int)x.size()); 116 | Eigen::SparseMatrix dfdv((int)x.size(), (int)x.size()); 117 | Eigen::VectorXd dampingForces((int)x.size()); 118 | Eigen::SparseMatrix dampingPseudoDerivatives((int)x.size(), (int)x.size()); 119 | 120 | double xOrig = x[i]; 121 | 122 | x[i] = xOrig + dx; 123 | 124 | Eigen::VectorXd fPlus(x.size()); 125 | fPlus.setConstant(0); 126 | c.computeForces(x, uv, k, fPlus, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 127 | 128 | x[i] = xOrig - dx; 129 | 130 | Eigen::VectorXd fMinus(x.size()); 131 | fMinus.setConstant(0); 132 | c.computeForces(x, uv, k, fMinus, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 133 | 134 | x[i] = xOrig; 135 | 136 | // df/dx 137 | return (fPlus - fMinus) / (2 * dx); 138 | } 139 | 140 | Eigen::VectorXd numericalDampingForceDerivative(const EnergyCondition &c, const Eigen::VectorXd &uv, Eigen::VectorXd &x, Eigen::VectorXd &v, double k, double d, int i, double dx) 141 | { 142 | Eigen::SparseMatrix dfdx((int)x.size(), (int)x.size()); 143 | Eigen::SparseMatrix dfdv((int)x.size(), (int)x.size()); 144 | Eigen::VectorXd f((int)x.size()); 145 | Eigen::SparseMatrix dampingPseudoDerivatives((int)x.size(), (int)x.size()); 146 | 147 | double vOrig = v[i]; 148 | 149 | v[i] = vOrig + dx; 150 | 151 | Eigen::VectorXd dampingForcePlus(x.size()); 152 | dampingForcePlus.setConstant(0); 153 | c.computeForces(x, uv, k, f, dfdx, v, d, dampingForcePlus, dampingPseudoDerivatives, dfdv); 154 | 155 | v[i] = vOrig - dx; 156 | 157 | Eigen::VectorXd dampingForceMinus(x.size()); 158 | dampingForceMinus.setConstant(0); 159 | c.computeForces(x, uv, k, f, dfdx, v, d, dampingForceMinus, dampingPseudoDerivatives, dfdv); 160 | 161 | v[i] = vOrig; 162 | 163 | // df/dx 164 | return (dampingForcePlus - dampingForceMinus) / (2 * dx); 165 | } 166 | 167 | void checkPseudoDerivatives(const Eigen::SparseMatrix &dampingPseudoDerivatives, const EnergyCondition &c, const Eigen::VectorXd &uv, Eigen::VectorXd &x, Eigen::VectorXd &v, double k, double d, double dx, double tol) 168 | { 169 | Eigen::VectorXd dCdt = numericalCTimeDerivative(c, x, v, uv, dx); 170 | 171 | for (int i = 0; i < x.size(); ++i) 172 | { 173 | for (int j = 0; j < x.size(); ++j) 174 | { 175 | Eigen::VectorXd d2Cdidj = numericalSecondCDerivative(c, x, uv, i, j, dx); 176 | double expectedCoeff = -d * (d2Cdidj.array() * dCdt.array()).sum(); 177 | double actualCoeff = dampingPseudoDerivatives.coeff(i, j); 178 | Microsoft::VisualStudio::CppUnitTestFramework::Assert::AreEqual(actualCoeff, expectedCoeff, tol); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/mayaPlugin/ClothSimMayaPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "mayaPlugin\ClothSimMayaPlugin.h" 2 | 3 | #include "simLib\BasicCGSolver.h" 4 | #include "simLib\DirectSolver.h" 5 | #include "simLib\ConstrainedCGSolver.h" 6 | #include "simLib\GravityField.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | using namespace ClothSim; 37 | 38 | MTypeId ClothSimMayaPlugin::id(0x8f17c); 39 | 40 | MObject ClothSimMayaPlugin::g_aTime; 41 | MObject ClothSimMayaPlugin::g_aOutputMesh; 42 | 43 | 44 | #define McheckErr(stat,msg) \ 45 | if ( MS::kSuccess != stat ) { \ 46 | cerr << msg; \ 47 | return MS::kFailure; \ 48 | } 49 | 50 | ClothSimMayaPlugin::ClothSimMayaPlugin() : m_prevTime( 1.0/24 ) 51 | { 52 | } 53 | 54 | ClothSimMayaPlugin::~ClothSimMayaPlugin() 55 | { 56 | } 57 | 58 | 59 | MStatus ClothSimMayaPlugin::initialize() 60 | { 61 | MFnUnitAttribute unitAttr; 62 | MFnTypedAttribute typedAttr; 63 | 64 | MStatus returnStatus; 65 | 66 | g_aTime = unitAttr.create("time", "tm", MFnUnitAttribute::kTime, 0.0, &returnStatus); 67 | McheckErr(returnStatus, "ERROR creating time attribute\n"); 68 | 69 | g_aOutputMesh = typedAttr.create("outputMesh", "out", MFnData::kMesh, &returnStatus); 70 | McheckErr(returnStatus, "ERROR creating output attribute\n"); 71 | typedAttr.setStorable(false); 72 | 73 | returnStatus = addAttribute(g_aTime); 74 | McheckErr(returnStatus, "ERROR adding time attribute\n"); 75 | 76 | returnStatus = addAttribute(g_aOutputMesh); 77 | McheckErr(returnStatus, "ERROR adding outputMesh attribute\n"); 78 | 79 | returnStatus = attributeAffects(g_aTime, g_aOutputMesh); 80 | McheckErr(returnStatus, "ERROR in attributeAffects\n"); 81 | 82 | return MS::kSuccess; 83 | } 84 | 85 | MStatus ClothSimMayaPlugin::compute(const MPlug& plug, MDataBlock& data) 86 | { 87 | MStatus returnStatus; 88 | 89 | if (plug == g_aOutputMesh) 90 | { 91 | MDataHandle timeData = data.inputValue(g_aTime, &returnStatus); 92 | McheckErr(returnStatus, "Error getting time data handle\n"); 93 | MTime time = timeData.asTime(); 94 | 95 | /* Get output object */ 96 | 97 | MDataHandle outputHandle = data.outputValue(g_aOutputMesh, &returnStatus); 98 | McheckErr(returnStatus, "ERROR getting polygon data handle\n"); 99 | 100 | MFnMeshData dataCreator; 101 | MObject newOutputData = dataCreator.create(&returnStatus); 102 | McheckErr(returnStatus, "ERROR creating outputData"); 103 | 104 | createMesh(time, newOutputData, returnStatus); 105 | if (!returnStatus) 106 | { 107 | std::cerr << "ERROR creating new Cube: " << returnStatus.errorString() << std::endl; 108 | return returnStatus; 109 | } 110 | 111 | outputHandle.set(newOutputData); 112 | data.setClean(plug); 113 | } 114 | else 115 | return MS::kUnknownParameter; 116 | 117 | return MS::kSuccess; 118 | } 119 | 120 | void* ClothSimMayaPlugin::creator() 121 | { 122 | return new ClothSimMayaPlugin; 123 | } 124 | 125 | MObject ClothSimMayaPlugin::createMesh(const MTime& time, 126 | MObject& outData, 127 | MStatus& stat) 128 | 129 | { 130 | double t = time.as(MTime::kSeconds); 131 | if (t <= 1.0 / 24 && m_prevTime > 1.0/24) 132 | { 133 | m_simMesh.reset(0); 134 | } 135 | 136 | int nx = 60; 137 | int ny = 60; 138 | 139 | if (!m_simMesh.get()) 140 | { 141 | 142 | Eigen::VectorXf v((nx+1) * (ny+1) * 3); 143 | Eigen::VectorXf x((nx + 1) * (ny + 1) * 3); 144 | Eigen::VectorXf uv((nx + 1) * (ny + 1) * 2); 145 | 146 | for (int i = 0; i <= nx; ++i) 147 | { 148 | for (int j = 0; j <= ny; ++j) 149 | { 150 | int base = i + (nx+1) * j; 151 | uv[2 * base + 0] = (float)i / nx - 0.5f; 152 | uv[2 * base + 1] = (float)j / ny - 0.5f; 153 | 154 | x[3 * base + 0] = uv[2 * base + 0]; 155 | x[3 * base + 1] = 0; 156 | x[3 * base + 2] = uv[2 * base + 1]; 157 | 158 | v[3 * base + 0] = v[3 * base + 1] = v[3 * base + 2] = 0; 159 | } 160 | } 161 | 162 | std::vector triangleInds; 163 | for (int i = 0; i < nx; ++i) 164 | { 165 | for (int j = 0; j < ny; ++j) 166 | { 167 | int base = i + (nx + 1) * j; 168 | 169 | triangleInds.push_back(base + 0); 170 | triangleInds.push_back(base + 1); 171 | triangleInds.push_back(base + (nx + 1)); 172 | 173 | triangleInds.push_back(base + 1); 174 | triangleInds.push_back(base + (nx + 2)); 175 | triangleInds.push_back(base + (nx + 1)); 176 | 177 | } 178 | } 179 | 180 | m_simMesh.reset( 181 | new ClothMesh( 182 | x, v, uv, triangleInds, 183 | 0.01f, 1000000.0f, 1000000.0f, 184 | 0.01f, 1000.0f, 1000.0f, 185 | 1.0f 186 | ) 187 | ); 188 | } 189 | 190 | std::vector constraintIndices; 191 | std::vector< Eigen::Matrix3f > constraintMatrices; 192 | 193 | 194 | Eigen::VectorXf constraintVelocityDeltas(m_simMesh->x().size()); 195 | constraintVelocityDeltas.setConstant(0); 196 | 197 | for (int i = 0; i <= nx; ++i) 198 | { 199 | for (int j = 0; j <= ny; ++j) 200 | { 201 | int idx = i + (nx + 1) * j; 202 | float x = (float)i / nx - 0.5f; 203 | float y = (float)j / ny - 0.5f; 204 | 205 | if (x * x + y * y < 0.3 * 0.3) 206 | { 207 | constraintIndices.push_back(idx); 208 | constraintMatrices.push_back(Eigen::Matrix3f::Zero()); 209 | } 210 | } 211 | } 212 | 213 | if (t > m_prevTime) 214 | { 215 | ConstrainedCGSolver solver( 216 | constraintIndices, 217 | constraintMatrices, 218 | constraintVelocityDeltas, 219 | 0.01f, 220 | 400 221 | ); 222 | 223 | GravityField g( m_simMesh->m(), Eigen::Vector3f( 0,-9.8f, 0 ) ); 224 | std::vector< ForceField* > forceFields; 225 | forceFields.push_back( &g ); 226 | 227 | try 228 | { 229 | std::cerr << "advance" << std::endl; 230 | m_simMesh->advance(forceFields, float(t - m_prevTime)*0.5f, solver); 231 | m_simMesh->advance(forceFields, float(t - m_prevTime)*0.5f, solver); 232 | std::cerr << "done" << std::endl; 233 | } 234 | catch (const std::exception &e) 235 | { 236 | std::cerr << e.what() << std::endl; 237 | stat = MStatus::kFailure; 238 | return MObject(); 239 | } 240 | catch (...) 241 | { 242 | std::cerr << "unknown exception" << std::endl; 243 | stat = MStatus::kFailure; 244 | return MObject(); 245 | } 246 | } 247 | 248 | m_prevTime = t; 249 | 250 | MFloatPointArray points; 251 | for (int i = 0; i < m_simMesh->x().size(); i += 3) 252 | { 253 | MFloatPoint p(m_simMesh->x()[i], m_simMesh->x()[i + 1], m_simMesh->x()[i + 2]); 254 | points.append(p); 255 | } 256 | 257 | MFnMesh meshFS; 258 | MIntArray faceCounts((int)m_simMesh->triangleIndices().size()/3, 3); 259 | MIntArray faceConnects; 260 | for (unsigned i = 0; i < m_simMesh->triangleIndices().size(); ++i) 261 | { 262 | faceConnects.append(m_simMesh->triangleIndices()[i]); 263 | } 264 | 265 | MObject newMesh = meshFS.create((int)m_simMesh->x().size() / 3, (int)m_simMesh->triangleIndices().size() / 3, points, faceCounts, faceConnects, outData, &stat); 266 | 267 | return newMesh; 268 | } 269 | 270 | MStatus initializePlugin(MObject obj) 271 | { 272 | MStatus status; 273 | MFnPlugin plugin(obj, PLUGIN_COMPANY, "6.0", "Any"); 274 | 275 | status = plugin.registerNode("ClothSimMayaPlugin", ClothSimMayaPlugin::id, ClothSimMayaPlugin::creator, ClothSimMayaPlugin::initialize); 276 | if (!status) 277 | { 278 | status.perror("registerNode"); 279 | return(status); 280 | } 281 | 282 | return(status); 283 | } 284 | 285 | MStatus uninitializePlugin(MObject obj) 286 | { 287 | MStatus status; 288 | MFnPlugin plugin(obj); 289 | 290 | status = plugin.deregisterNode(ClothSimMayaPlugin::id); 291 | if (!status) 292 | { 293 | status.perror("deregisterNode"); 294 | return(status); 295 | } 296 | 297 | return(status); 298 | } 299 | -------------------------------------------------------------------------------- /src/simLib/ShearCondition.cpp: -------------------------------------------------------------------------------- 1 | #include "simLib\ShearCondition.h" 2 | 3 | using namespace ClothSim; 4 | 5 | template 6 | ShearCondition::ShearCondition(int i0, int i1, int i2) 7 | { 8 | m_inds[0] = i0; 9 | m_inds[1] = i1; 10 | m_inds[2] = i2; 11 | } 12 | 13 | template 14 | ShearCondition::TriangleQuantities::TriangleQuantities() : TangentTriangleQuantities() 15 | { 16 | } 17 | 18 | template 19 | ShearCondition::TriangleQuantities::TriangleQuantities( 20 | const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, 21 | const Vector2 &uv0, const Vector2 &uv1, const Vector2 &uv2, 22 | const Vector3 &v0, const Vector3 &v1, const Vector3 &v2 23 | ) : TangentTriangleQuantities(p0, p1, p2, uv0, uv1, uv2) 24 | { 25 | 26 | Real wuNorm = wu.norm(); 27 | Real wvNorm = wv.norm(); 28 | 29 | wuHat = wu / wuNorm; 30 | wvHat = wv / wvNorm; 31 | 32 | // energy condition: 33 | C = a * wu.dot(wv); 34 | 35 | // first derivatives of condition quantity: 36 | dCdP0 = a * (dwudP0Scalar * wv + dwvdP0Scalar * wu); 37 | dCdP1 = a * (dwudP1Scalar * wv + dwvdP1Scalar * wu); 38 | dCdP2 = a * (dwudP2Scalar * wv + dwvdP2Scalar * wu); 39 | 40 | // d/dP1 a * (dwudP0Scalar * wv + dwvdP0Scalar * wu) 41 | // == a * (dwudP0Scalar * dwvdP1 + dwvdP0Scalar * dwudP1) 42 | 43 | // time derivative of energy condition: 44 | dCdt = dCdP0.dot(v0) + dCdP1.dot(v1) + dCdP2.dot(v2); 45 | 46 | d2CdP0dP0 = 2 * a * dwudP0Scalar * dwvdP0Scalar * Matrix3::Identity(); 47 | d2CdP0dP1 = a * (dwudP0Scalar * dwvdP1Scalar + dwvdP0Scalar * dwudP1Scalar) * Matrix3::Identity(); 48 | d2CdP0dP2 = a * (dwudP0Scalar * dwvdP2Scalar + dwvdP0Scalar * dwudP2Scalar) * Matrix3::Identity(); 49 | 50 | d2CdP1dP0 = a * (dwudP1Scalar * dwvdP0Scalar + dwvdP1Scalar * dwudP0Scalar) * Matrix3::Identity(); 51 | d2CdP1dP1 = 2 * a * dwvdP1Scalar * dwudP1Scalar * Matrix3::Identity(); 52 | d2CdP1dP2 = a * (dwudP1Scalar * dwvdP2Scalar + dwvdP1Scalar * dwudP2Scalar) * Matrix3::Identity(); 53 | 54 | d2CdP2dP0 = a * (dwudP2Scalar * dwvdP0Scalar + dwvdP2Scalar * dwudP0Scalar) * Matrix3::Identity(); 55 | d2CdP2dP1 = a * (dwudP2Scalar * dwvdP1Scalar + dwvdP2Scalar * dwudP1Scalar) * Matrix3::Identity(); 56 | d2CdP2dP2 = 2 * a * dwvdP2Scalar * dwudP2Scalar * Matrix3::Identity(); 57 | 58 | } 59 | 60 | template 61 | typename EnergyCondition::Vector ShearCondition::C(const Vector& x, const Vector& uv) const 62 | { 63 | TriangleQuantities q( 64 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]), 65 | uv.segment<2>(2 * m_inds[0]), uv.segment<2>(2 * m_inds[1]), uv.segment<2>(2 * m_inds[2]), 66 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]) 67 | ); 68 | 69 | Vector ret(1); 70 | ret[0] = q.C; 71 | return ret; 72 | } 73 | 74 | template 75 | void ShearCondition::computeForces(const Vector& x, const Vector& uv, Real k, Vector& forces, SparseMatrix &dfdx, const Vector& v, Real d, Vector &dampingForces, SparseMatrix &dampingPseudoXDerivatives, SparseMatrix &dddv) const 76 | { 77 | // I can't be bothered doing C = a * wuHat . wvHat, so I'm doing C = a * wu * wv instead. 78 | // I guess this term will have the effect of trying to shrink the cloth. Hopefully that'll 79 | // get counteracted by the stretch condition. 80 | 81 | // Actually, it won't try and shrink it at all if the tangents are orthogonal will it... 82 | // probably won't be a promlem then. 83 | 84 | TriangleQuantities q( 85 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]), 86 | uv.segment<2>(2 * m_inds[0]), uv.segment<2>(2 * m_inds[1]), uv.segment<2>(2 * m_inds[2]), 87 | v.segment<3>(3 * m_inds[0]), v.segment<3>(3 * m_inds[1]), v.segment<3>(3 * m_inds[2]) 88 | ); 89 | 90 | // E = 0.5 * C * C 91 | // f = -dE/dx = C dC/dx 92 | forces.segment<3>(3 * m_inds[0]) -= k * q.C * q.dCdP0; 93 | forces.segment<3>(3 * m_inds[1]) -= k * q.C * q.dCdP1; 94 | forces.segment<3>(3 * m_inds[2]) -= k * q.C * q.dCdP2; 95 | 96 | 97 | // compute force derivatives and insert them into the sparse matrix: 98 | Matrix3 df0dP0 = -k * (q.dCdP0 * q.dCdP0.transpose() + q.C * q.d2CdP0dP0); 99 | Matrix3 df0dP1 = -k * (q.dCdP0 * q.dCdP1.transpose() + q.C * q.d2CdP0dP1); 100 | Matrix3 df0dP2 = -k * (q.dCdP0 * q.dCdP2.transpose() + q.C * q.d2CdP0dP2); 101 | 102 | Matrix3 df1dP0 = -k * (q.dCdP1 * q.dCdP0.transpose() + q.C * q.d2CdP1dP0); 103 | Matrix3 df1dP1 = -k * (q.dCdP1 * q.dCdP1.transpose() + q.C * q.d2CdP1dP1); 104 | Matrix3 df1dP2 = -k * (q.dCdP1 * q.dCdP2.transpose() + q.C * q.d2CdP1dP2); 105 | 106 | Matrix3 df2dP0 = -k * (q.dCdP2 * q.dCdP0.transpose() + q.C * q.d2CdP2dP0); 107 | Matrix3 df2dP1 = -k * (q.dCdP2 * q.dCdP1.transpose() + q.C * q.d2CdP2dP1); 108 | Matrix3 df2dP2 = -k * (q.dCdP2 * q.dCdP2.transpose() + q.C * q.d2CdP2dP2); 109 | 110 | 111 | for (int i = 0; i < 3; ++i) 112 | { 113 | for (int j = 0; j < 3; ++j) 114 | { 115 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += df0dP0(i, j); 116 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += df0dP1(i, j); 117 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += df0dP2(i, j); 118 | 119 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += df1dP0(i, j); 120 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += df1dP1(i, j); 121 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += df1dP2(i, j); 122 | 123 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += df2dP0(i, j); 124 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += df2dP1(i, j); 125 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += df2dP2(i, j); 126 | } 127 | } 128 | 129 | // compute damping forces and v derivatives: 130 | 131 | // fd = -d * dC/dt * dC/dx: 132 | 133 | dampingForces.segment<3>(3 * m_inds[0]) -= d * q.dCdt * q.dCdP0; 134 | dampingForces.segment<3>(3 * m_inds[1]) -= d * q.dCdt * q.dCdP1; 135 | dampingForces.segment<3>(3 * m_inds[2]) -= d * q.dCdt * q.dCdP2; 136 | 137 | Matrix3 dfd0dV0 = -d * (q.dCdP0 * q.dCdP0.transpose()); 138 | Matrix3 dfd0dV1 = -d * (q.dCdP0 * q.dCdP1.transpose()); 139 | Matrix3 dfd0dV2 = -d * (q.dCdP0 * q.dCdP2.transpose()); 140 | 141 | Matrix3 dfd1dV0 = -d * (q.dCdP1 * q.dCdP0.transpose()); 142 | Matrix3 dfd1dV1 = -d * (q.dCdP1 * q.dCdP1.transpose()); 143 | Matrix3 dfd1dV2 = -d * (q.dCdP1 * q.dCdP2.transpose()); 144 | 145 | Matrix3 dfd2dV0 = -d * (q.dCdP2 * q.dCdP0.transpose()); 146 | Matrix3 dfd2dV1 = -d * (q.dCdP2 * q.dCdP1.transpose()); 147 | Matrix3 dfd2dV2 = -d * (q.dCdP2 * q.dCdP2.transpose()); 148 | 149 | for (int i = 0; i < 3; ++i) 150 | { 151 | for (int j = 0; j < 3; ++j) 152 | { 153 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += dfd0dV0(i, j); 154 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += dfd0dV1(i, j); 155 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += dfd0dV2(i, j); 156 | 157 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += dfd1dV0(i, j); 158 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += dfd1dV1(i, j); 159 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += dfd1dV2(i, j); 160 | 161 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += dfd2dV0(i, j); 162 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += dfd2dV1(i, j); 163 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += dfd2dV2(i, j); 164 | } 165 | } 166 | 167 | // Damping force x derivatives are given by: 168 | 169 | // -kd ( dc/dpi pc/dpi^T + d2c/dpidpj dc/dt ) 170 | 171 | // Unfortunately, the first term isn't symmetric, so it messes up the 172 | // linear solver. However, apparently it's quite small in practice so 173 | // you can leave it out, hence the name dampingForcePseudoXDerivatives. 174 | // see page 6 of http://www.cs.cmu.edu/~baraff/papers/sig98.pdf for 175 | // details. 176 | 177 | // Therefore, lets compute -kd * d2c/dpidpj dc/dt: 178 | Matrix3 dD0dP0Pseudo = -d * (q.d2CdP0dP0 * q.dCdt); 179 | Matrix3 dD1dP0Pseudo = -d * (q.d2CdP1dP0 * q.dCdt); 180 | Matrix3 dD2dP0Pseudo = -d * (q.d2CdP2dP0 * q.dCdt); 181 | 182 | Matrix3 dD0dP1Pseudo = -d * (q.d2CdP0dP1 * q.dCdt); 183 | Matrix3 dD1dP1Pseudo = -d * (q.d2CdP1dP1 * q.dCdt); 184 | Matrix3 dD2dP1Pseudo = -d * (q.d2CdP2dP1 * q.dCdt); 185 | 186 | Matrix3 dD0dP2Pseudo = -d * (q.d2CdP0dP2 * q.dCdt); 187 | Matrix3 dD1dP2Pseudo = -d * (q.d2CdP1dP2 * q.dCdt); 188 | Matrix3 dD2dP2Pseudo = -d * (q.d2CdP2dP2 * q.dCdt); 189 | 190 | for (int i = 0; i < 3; ++i) 191 | { 192 | for (int j = 0; j < 3; ++j) 193 | { 194 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += dD0dP0Pseudo(i, j); 195 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += dD1dP0Pseudo(i, j); 196 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += dD2dP0Pseudo(i, j); 197 | 198 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += dD0dP1Pseudo(i, j); 199 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += dD1dP1Pseudo(i, j); 200 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += dD2dP1Pseudo(i, j); 201 | 202 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += dD0dP2Pseudo(i, j); 203 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += dD1dP2Pseudo(i, j); 204 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += dD2dP2Pseudo(i, j); 205 | } 206 | } 207 | 208 | } 209 | 210 | template class ShearCondition; 211 | template class ShearCondition; 212 | -------------------------------------------------------------------------------- /src/simLib/ClothMesh.cpp: -------------------------------------------------------------------------------- 1 | #include "simLib\ClothMesh.h" 2 | 3 | using namespace ClothSim; 4 | 5 | static std::vector sharedVertices(const std::vector &triangleIndices, size_t i, size_t j) 6 | { 7 | std::vector ret; 8 | if (triangleIndices[i + 0] == triangleIndices[j + 0]) ret.push_back(triangleIndices[i + 0]); 9 | if (triangleIndices[i + 0] == triangleIndices[j + 1]) ret.push_back(triangleIndices[i + 0]); 10 | if (triangleIndices[i + 0] == triangleIndices[j + 2]) ret.push_back(triangleIndices[i + 0]); 11 | if (triangleIndices[i + 1] == triangleIndices[j + 0]) ret.push_back(triangleIndices[i + 1]); 12 | if (triangleIndices[i + 1] == triangleIndices[j + 1]) ret.push_back(triangleIndices[i + 1]); 13 | if (triangleIndices[i + 1] == triangleIndices[j + 2]) ret.push_back(triangleIndices[i + 1]); 14 | if (triangleIndices[i + 2] == triangleIndices[j + 0]) ret.push_back(triangleIndices[i + 2]); 15 | if (triangleIndices[i + 2] == triangleIndices[j + 1]) ret.push_back(triangleIndices[i + 2]); 16 | if (triangleIndices[i + 2] == triangleIndices[j + 2]) ret.push_back(triangleIndices[i + 2]); 17 | return ret; 18 | } 19 | 20 | template 21 | ClothMesh::ClothMesh( 22 | const Vector &x, 23 | const Vector &v, 24 | const Vector &uv, 25 | const std::vector &triangleIndices, 26 | Real kBend, Real kStretch, Real kShear, 27 | Real dBend, Real dStretch, Real dShear, 28 | Real density 29 | ) : 30 | m_kBend(kBend), m_kStretch(kStretch), m_kShear(kShear), 31 | m_dBend(dBend), m_dStretch(dStretch), m_dShear(dShear), 32 | m_x((int)x.size()), 33 | m_v((int)v.size()), 34 | m_uv((int)uv.size()), 35 | m_implicitUpdateMatrix((int)x.size(), (int)x.size()), 36 | m_dfdx((int)x.size(), (int)x.size()), 37 | m_dfdv((int)x.size(), (int)x.size()), 38 | m_triangleIndices( triangleIndices ), 39 | m_m((int)uv.size() / 2) 40 | { 41 | for (int i = 0; i < x.size(); ++i) 42 | { 43 | m_x[i] = x[i]; 44 | } 45 | for (int i = 0; i < v.size(); ++i) 46 | { 47 | m_v[i] = v[i]; 48 | } 49 | for (int i = 0; i < uv.size(); ++i) 50 | { 51 | m_uv[i] = uv[i]; 52 | } 53 | 54 | m_m.setConstant(0); 55 | for(size_t i = 0; i < triangleIndices.size(); i += 3 ) 56 | { 57 | // add stretch and shear terms to the equations of motion: 58 | m_shearConditions.push_back(ShearCondition(triangleIndices[i + 0], triangleIndices[i + 1], triangleIndices[i + 2])); 59 | m_stretchConditions.push_back(StretchCondition(triangleIndices[i + 0], triangleIndices[i + 1], triangleIndices[i + 2], 1, 1)); 60 | 61 | // find triangle rest area: 62 | const Vector2 &uv0 = m_uv.segment<2>(2 * triangleIndices[i+0]); 63 | const Vector2 &uv1 = m_uv.segment<2>(2 * triangleIndices[i + 1]); 64 | const Vector2 &uv2 = m_uv.segment<2>(2 * triangleIndices[i + 2]); 65 | 66 | Vector2 duv1 = uv1 - uv0; 67 | Vector2 duv2 = uv2 - uv0; 68 | 69 | Real du1 = duv1[0]; 70 | Real dv1 = duv1[1]; 71 | Real du2 = duv2[0]; 72 | Real dv2 = duv2[1]; 73 | 74 | // triangle area in reference pose: 75 | Real area = Real(0.5) * abs(du1 * dv2 - du2 * dv1); 76 | 77 | // calculate triangle mass and distribute it equally over the 3 vertices: 78 | Real triangleMass = area * density; 79 | m_m[triangleIndices[i + 0]] += triangleMass / 3; 80 | m_m[triangleIndices[i + 1]] += triangleMass / 3; 81 | m_m[triangleIndices[i + 2]] += triangleMass / 3; 82 | } 83 | 84 | // find shared edges and instantiate BendConditions for them. 85 | // Doing it in a dumb slow way right now just so it works: 86 | for(size_t i = 0; i < triangleIndices.size(); i += 3) 87 | { 88 | for(size_t j = i + 3; j < triangleIndices.size(); j += 3) 89 | { 90 | std::vector shared = sharedVertices(triangleIndices, i, j); 91 | if(shared.size() == 2) 92 | { 93 | // sharing an edge! 94 | int i1 = shared[0]; 95 | int i2 = shared[1]; 96 | int i0; 97 | for(size_t n = 0; n < 3; ++n) 98 | { 99 | if (triangleIndices[i + n] != i1 && triangleIndices[i + n] != i2) 100 | { 101 | i0 = triangleIndices[i + n]; 102 | break; 103 | } 104 | } 105 | 106 | int i3; 107 | for (size_t n = 0; n < 3; ++n) 108 | { 109 | if (triangleIndices[j + n] != i1 && triangleIndices[j + n] != i2) 110 | { 111 | i3 = triangleIndices[j + n]; 112 | break; 113 | } 114 | } 115 | m_bendConditions.push_back( BendCondition( i0, i1, i2, i3 ) ); 116 | } 117 | } 118 | } 119 | } 120 | 121 | template 122 | const typename ClothMesh::Vector &ClothMesh::x() const 123 | { 124 | return m_x; 125 | } 126 | 127 | // accessor for velocities: 128 | template 129 | const typename ClothMesh::Vector &ClothMesh::v() const 130 | { 131 | return m_v; 132 | } 133 | 134 | // accessor for masses: 135 | template 136 | const typename ClothMesh::Vector &ClothMesh::m() const 137 | { 138 | return m_m; 139 | } 140 | template 141 | const std::vector< BendCondition > &ClothMesh::bendConditions() const 142 | { 143 | return m_bendConditions; 144 | } 145 | 146 | template 147 | const std::vector< ShearCondition > &ClothMesh::shearConditions() const 148 | { 149 | return m_shearConditions; 150 | } 151 | 152 | template 153 | const std::vector< StretchCondition > &ClothMesh::stretchConditions() const 154 | { 155 | return m_stretchConditions; 156 | } 157 | 158 | template 159 | const std::vector &ClothMesh::triangleIndices() 160 | { 161 | return m_triangleIndices; 162 | } 163 | 164 | // advance the simulation: 165 | template 166 | void ClothMesh::advance(std::vector< ForceField* > &forceFields, Real dt, const LinearSolver &solver) 167 | { 168 | Vector forces((int)m_x.size()); 169 | forcesAndDerivatives(m_x, m_uv, m_v, forces, forces, m_dfdx, m_dfdx, m_dfdv); 170 | for (size_t i = 0; i < forceFields.size(); ++i) 171 | { 172 | forceFields[i]->forcesAndDerivatives(forces, m_dfdx); 173 | } 174 | 175 | Vector implicitUpdateRHS((int)m_x.size()); 176 | assembleImplicitUpdateEquations(dt, forces, m_v, m_dfdx, m_dfdv, m_implicitUpdateMatrix, implicitUpdateRHS); 177 | 178 | // solve the linear system: 179 | Vector dv((int)m_x.size()); 180 | solver.solve(m_implicitUpdateMatrix, implicitUpdateRHS, dv); 181 | 182 | // update: 183 | m_v += dv; 184 | m_x += dt * m_v; 185 | } 186 | 187 | template 188 | void ClothMesh::forcesAndDerivatives(const Vector &x, const Vector &uv, const Vector &v, Vector &f, Vector &d, SparseMatrix &dfdx, SparseMatrix &dddx, SparseMatrix &dddv) const 189 | { 190 | d.setConstant(0); 191 | f.setConstant(0); 192 | for (int i = 0; i < dfdx.outerSize(); ++i) 193 | { 194 | for (SparseMatrix::InnerIterator it(dfdx, i); it; ++it) 195 | { 196 | it.valueRef() = 0; 197 | } 198 | } 199 | for (int i = 0; i < dddx.outerSize(); ++i) 200 | { 201 | for (SparseMatrix::InnerIterator it(dddx, i); it; ++it) 202 | { 203 | it.valueRef() = 0; 204 | } 205 | } 206 | for (int i = 0; i < dddv.outerSize(); ++i) 207 | { 208 | for (SparseMatrix::InnerIterator it(dddv, i); it; ++it) 209 | { 210 | it.valueRef() = 0; 211 | } 212 | } 213 | 214 | // add the bend terms: 215 | for (size_t i = 0; i < m_bendConditions.size(); ++i) 216 | { 217 | m_bendConditions[i].computeForces(x, uv, m_kBend, f, dfdx, v, m_dBend, d, dddx, dddv); 218 | } 219 | 220 | // add the stretch terms: 221 | for (size_t i = 0; i < m_stretchConditions.size(); ++i) 222 | { 223 | m_stretchConditions[i].computeForces(x, uv, m_kStretch, f, dfdx, v, m_dStretch, d, dddx, dddv); 224 | } 225 | 226 | // add the shear terms: 227 | for (size_t i = 0; i < m_shearConditions.size(); ++i) 228 | { 229 | m_shearConditions[i].computeForces(x, uv, m_kShear, f, dfdx, v, m_dShear, d, dddx, dddv); 230 | } 231 | } 232 | 233 | 234 | template 235 | void ClothMesh::assembleImplicitUpdateEquations(Real dt, const Vector &f, const Vector &v, const SparseMatrix &dfdx, const SparseMatrix &dfdv, SparseMatrix &implicitUpdateMatrix, Vector &implicitUpdateRHS) const 236 | { 237 | 238 | // reset existing sim data to zero: 239 | for (int i = 0; i < implicitUpdateMatrix.outerSize(); ++i) 240 | { 241 | for (SparseMatrix::InnerIterator it(implicitUpdateMatrix, i); it; ++it) 242 | { 243 | it.valueRef() = 0; 244 | } 245 | } 246 | 247 | // right hand side: 248 | implicitUpdateRHS = f * dt + dfdx * v * dt * dt; 249 | 250 | // matrix: 251 | implicitUpdateMatrix = -dfdx * dt * dt - dfdv * dt; 252 | 253 | // add masses onto the diagonal: 254 | for (int i = 0; i < f.size(); ++i) 255 | { 256 | // unit masses for now... 257 | implicitUpdateMatrix.coeffRef(i, i) += m_m[i / 3]; 258 | } 259 | implicitUpdateMatrix.makeCompressed(); 260 | } 261 | 262 | template 263 | Real ClothMesh::energy(const Vector& x, const Vector& uv) const 264 | { 265 | Real e = 0; 266 | for (size_t i = 0; i < m_bendConditions.size(); ++i) 267 | { 268 | Vector c = m_bendConditions[i].C(x, uv); 269 | e += m_kBend * Real(0.5) * c.dot(c); 270 | } 271 | for (size_t i = 0; i < m_shearConditions.size(); ++i) 272 | { 273 | Vector c = m_shearConditions[i].C(x, uv); 274 | e += m_kShear * Real(0.5) * c.dot(c); 275 | } 276 | for (size_t i = 0; i < m_stretchConditions.size(); ++i) 277 | { 278 | Vector c = m_stretchConditions[i].C(x, uv); 279 | e += m_kStretch * Real(0.5) * c.dot(c); 280 | } 281 | return e; 282 | } 283 | 284 | template 285 | void ClothMesh::C(const Vector& x, const Vector& uv, Vector& c) const 286 | { 287 | c.resize(m_bendConditions.size() + m_shearConditions.size() + 2 * m_stretchConditions.size()); 288 | c.setConstant(0); 289 | 290 | size_t n = 0; 291 | for (size_t i = 0; i < m_bendConditions.size(); ++i) 292 | { 293 | c[n++] = m_bendConditions[i].C(x, uv)[0]; 294 | } 295 | for (size_t i = 0; i < m_shearConditions.size(); ++i) 296 | { 297 | c[n++] = m_shearConditions[i].C(x, uv)[0]; 298 | } 299 | for (size_t i = 0; i < m_stretchConditions.size(); ++i) 300 | { 301 | Vector cc = m_stretchConditions[i].C(x, uv); 302 | c[n++] = cc[0]; 303 | c[n++] = cc[1]; 304 | } 305 | } 306 | 307 | template class ClothMesh; 308 | template class ClothMesh; 309 | -------------------------------------------------------------------------------- /mayaPlugin.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | ReleaseDebug 10 | x64 11 | 12 | 13 | Release 14 | x64 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Win32Proj 25 | {E57E653C-6070-429A-B64A-B7C6FDC3002D} 26 | mayaPlugin 27 | 28 | 29 | 30 | DynamicLibrary 31 | true 32 | v120 33 | 34 | 35 | DynamicLibrary 36 | false 37 | v120 38 | 39 | 40 | DynamicLibrary 41 | false 42 | v120 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | true 62 | $(Platform)\$(Configuration)\ 63 | .mll 64 | AllRules.ruleset 65 | Link 66 | 67 | 68 | 69 | _DEBUG;WIN32;_WINDOWS;_USRDLL;NT_PLUGIN;_SECURE_SCL=0;_SECURE_SCL_THROWS=0;_SECURE_SCL_DEPRECATE=0;_CRT_SECURE_NO_DEPRECATE;TBB_USE_DEBUG=0;__TBB_LIB_NAME=tbb.lib;REQUIRE_IOSTREAM;AW_NEW_IOSTREAMS;Bits64_;%(PreprocessorDefinitions) 70 | C:\Software Libraries\eigen-3.2.7;.\include;C:\Program Files\Autodesk\Maya2014\include;%(AdditionalIncludeDirectories) 71 | MultiThreadedDebugDLL 72 | Level3 73 | ProgramDatabase 74 | true 75 | $(IntDir)$(ProjectName).pdb 76 | Disabled 77 | 78 | 79 | true 80 | Windows 81 | C:\Program Files\Autodesk\Maya2014\lib;$(SolutionDir)$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories) 82 | simLib.lib;OpenMaya.lib;Foundation.lib;%(AdditionalDependencies) 83 | /export:initializePlugin /export:uninitializePlugin %(AdditionalOptions) 84 | false 85 | $(OutDir)$(TargetName).lib 86 | 87 | 88 | C:\freelance\Jordan Hara\ParticleAnalyser\git\test\launchMayaTest.bat 89 | 90 | 91 | 92 | false 93 | $(Platform)\$(Configuration)\ 94 | .mll 95 | AllRules.ruleset 96 | Link 97 | 98 | 99 | 100 | NDEBUG;WIN32;_WINDOWS;_USRDLL;NT_PLUGIN;_HAS_ITERATOR_DEBUGGING=0;_SECURE_SCL=0;_SECURE_SCL_THROWS=0;_SECURE_SCL_DEPRECATE=0;_CRT_SECURE_NO_DEPRECATE;TBB_USE_DEBUG=0;__TBB_LIB_NAME=tbb.lib;REQUIRE_IOSTREAM;AW_NEW_IOSTREAMS;Bits64_;%(PreprocessorDefinitions) 101 | C:\Software Libraries\eigen-3.2.7;.\include;C:\Program Files\Autodesk\Maya2014\include;%(AdditionalIncludeDirectories) 102 | MultiThreadedDLL 103 | Level3 104 | ProgramDatabase 105 | true 106 | $(IntDir)$(ProjectName).pdb 107 | OnlyExplicitInline 108 | true 109 | true 110 | 111 | 112 | true 113 | Windows 114 | C:\Program Files\Autodesk\Maya2014\lib;$(SolutionDir)$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories) 115 | simLib.lib;OpenMaya.lib;Foundation.lib;%(AdditionalDependencies) 116 | /export:initializePlugin /export:uninitializePlugin %(AdditionalOptions) 117 | false 118 | $(OutDir)$(TargetName).lib 119 | true 120 | true 121 | 122 | 123 | C:\freelance\Jordan Hara\ParticleAnalyser\git\test\launchMayaTest.bat 124 | 125 | 126 | 127 | false 128 | $(Platform)\$(Configuration)\ 129 | .mll 130 | AllRules.ruleset 131 | Link 132 | 133 | 134 | 135 | NDEBUG;WIN32;_WINDOWS;_USRDLL;NT_PLUGIN;_HAS_ITERATOR_DEBUGGING=0;_SECURE_SCL=0;_SECURE_SCL_THROWS=0;_SECURE_SCL_DEPRECATE=0;_CRT_SECURE_NO_DEPRECATE;TBB_USE_DEBUG=0;__TBB_LIB_NAME=tbb.lib;REQUIRE_IOSTREAM;AW_NEW_IOSTREAMS;Bits64_;%(PreprocessorDefinitions) 136 | C:\Software Libraries\eigen-3.2.7;.\include;C:\Program Files\Autodesk\Maya2014\include;%(AdditionalIncludeDirectories) 137 | MultiThreaded 138 | Level3 139 | ProgramDatabase 140 | true 141 | $(IntDir)$(ProjectName).pdb 142 | OnlyExplicitInline 143 | true 144 | true 145 | 146 | 147 | false 148 | Windows 149 | C:\Program Files\Autodesk\Maya2014\lib;$(SolutionDir)$(Platform)\$(Configuration)\;%(AdditionalLibraryDirectories) 150 | simLib.lib;OpenMaya.lib;Foundation.lib; 151 | /export:initializePlugin /export:uninitializePlugin %(AdditionalOptions) 152 | false 153 | $(OutDir)$(TargetName).lib 154 | true 155 | true 156 | 157 | 158 | C:\freelance\Jordan Hara\ParticleAnalyser\git\test\launchMayaTest.bat 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/simLib/StretchCondition.cpp: -------------------------------------------------------------------------------- 1 | #include "simLib\StretchCondition.h" 2 | 3 | using namespace ClothSim; 4 | 5 | template 6 | StretchCondition::StretchCondition(int i0, int i1, int i2, Real restU, Real restV) : m_restU(restU), m_restV(restV) 7 | { 8 | m_inds[0] = i0; 9 | m_inds[1] = i1; 10 | m_inds[2] = i2; 11 | } 12 | 13 | template 14 | StretchCondition::TriangleQuantities::TriangleQuantities() : TangentTriangleQuantities() 15 | { 16 | } 17 | 18 | template 19 | StretchCondition::TriangleQuantities::TriangleQuantities( 20 | const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, 21 | const Vector2 &uv0, const Vector2 &uv1, const Vector2 &uv2, 22 | const Vector3 &v0, const Vector3 &v1, const Vector3 &v2, 23 | Real bu, Real bv 24 | ) : TangentTriangleQuantities( p0, p1, p2, uv0, uv1, uv2 ) 25 | { 26 | 27 | // first derivatives of condition quantities: 28 | Real wuNorm = wu.norm(); 29 | dC0dP0 = a * dwudP0 * wu / wuNorm; 30 | dC0dP1 = a * dwudP1 * wu / wuNorm; 31 | dC0dP2 = a * dwudP2 * wu / wuNorm; 32 | 33 | Real wvNorm = wv.norm(); 34 | dC1dP0 = a * dwvdP0 * wv / wvNorm; 35 | dC1dP1 = a * dwvdP1 * wv / wvNorm; 36 | dC1dP2 = a * dwvdP2 * wv / wvNorm; 37 | 38 | // condition quantities: 39 | C0 = a * (wuNorm - bu); 40 | C1 = a * (wvNorm - bv); 41 | 42 | // time derivative of condition quantities: 43 | dC0dt = dC0dP0.dot(v0) + dC0dP1.dot(v1) + dC0dP2.dot(v2); 44 | dC1dt = dC1dP0.dot(v0) + dC1dP1.dot(v1) + dC1dP2.dot(v2); 45 | 46 | // second derivatives of condition quantities: 47 | Matrix3 wuMatrix = (Matrix3::Identity() - wu * wu.transpose() / (wuNorm * wuNorm)); 48 | d2C0dP0dP0 = (a / wuNorm) * dwudP0 * dwudP0 * wuMatrix; 49 | d2C0dP0dP1 = (a / wuNorm) * dwudP0 * dwudP1 * wuMatrix; 50 | d2C0dP0dP2 = (a / wuNorm) * dwudP0 * dwudP2 * wuMatrix; 51 | 52 | d2C0dP1dP0 = (a / wuNorm) * dwudP1 * dwudP0 * wuMatrix; 53 | d2C0dP1dP1 = (a / wuNorm) * dwudP1 * dwudP1 * wuMatrix; 54 | d2C0dP1dP2 = (a / wuNorm) * dwudP1 * dwudP2 * wuMatrix; 55 | 56 | d2C0dP2dP0 = (a / wuNorm) * dwudP2 * dwudP0 * wuMatrix; 57 | d2C0dP2dP1 = (a / wuNorm) * dwudP2 * dwudP1 * wuMatrix; 58 | d2C0dP2dP2 = (a / wuNorm) * dwudP2 * dwudP2 * wuMatrix; 59 | 60 | Matrix3 wvMatrix = (Matrix3::Identity() - wv * wv.transpose() / (wvNorm * wvNorm)); 61 | d2C1dP0dP0 = (a / wvNorm) * dwvdP0 * dwvdP0 * wvMatrix; 62 | d2C1dP0dP1 = (a / wvNorm) * dwvdP0 * dwvdP1 * wvMatrix; 63 | d2C1dP0dP2 = (a / wvNorm) * dwvdP0 * dwvdP2 * wvMatrix; 64 | 65 | d2C1dP1dP0 = (a / wvNorm) * dwvdP1 * dwvdP0 * wvMatrix; 66 | d2C1dP1dP1 = (a / wvNorm) * dwvdP1 * dwvdP1 * wvMatrix; 67 | d2C1dP1dP2 = (a / wvNorm) * dwvdP1 * dwvdP2 * wvMatrix; 68 | 69 | d2C1dP2dP0 = (a / wvNorm) * dwvdP2 * dwvdP0 * wvMatrix; 70 | d2C1dP2dP1 = (a / wvNorm) * dwvdP2 * dwvdP1 * wvMatrix; 71 | d2C1dP2dP2 = (a / wvNorm) * dwvdP2 * dwvdP2 * wvMatrix; 72 | 73 | } 74 | 75 | template 76 | typename EnergyCondition::Vector StretchCondition::C(const Vector& x, const Vector& uv) const 77 | { 78 | TriangleQuantities q( 79 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]), 80 | uv.segment<2>(2 * m_inds[0]), uv.segment<2>(2 * m_inds[1]), uv.segment<2>(2 * m_inds[2]), 81 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]), 82 | m_restU, m_restV 83 | ); 84 | 85 | Vector ret(2); 86 | ret[0] = q.C0; 87 | ret[1] = q.C1; 88 | return ret; 89 | } 90 | 91 | template 92 | void StretchCondition::computeForces(const Vector& x, const Vector& uv, Real k, Vector& forces, SparseMatrix &dfdx, const Vector& v, Real d, Vector &dampingForces, SparseMatrix &dampingPseudoXDerivatives, SparseMatrix &dddv) const 93 | { 94 | TriangleQuantities q( 95 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]), 96 | uv.segment<2>(2 * m_inds[0]), uv.segment<2>(2 * m_inds[1]), uv.segment<2>(2 * m_inds[2]), 97 | v.segment<3>(3 * m_inds[0]), v.segment<3>(3 * m_inds[1]), v.segment<3>(3 * m_inds[2]), 98 | m_restU, m_restV 99 | ); 100 | 101 | // E = 0.5 * ( C0*C0 + C1*C1 ) 102 | // f = -dE/dx = C0 dC0/dx + C1 dC1/dx 103 | forces.segment<3>(3 * m_inds[0]) -= k * (q.C0 * q.dC0dP0 + q.C1 * q.dC1dP0); 104 | forces.segment<3>(3 * m_inds[1]) -= k * (q.C0 * q.dC0dP1 + q.C1 * q.dC1dP1); 105 | forces.segment<3>(3 * m_inds[2]) -= k * (q.C0 * q.dC0dP2 + q.C1 * q.dC1dP2); 106 | 107 | // compute force derivatives and insert them into the sparse matrix: 108 | Matrix3 df0dP0 = -k * (q.dC0dP0 * q.dC0dP0.transpose() + q.C0 * q.d2C0dP0dP0 + q.dC1dP0 * q.dC1dP0.transpose() + q.C1 * q.d2C1dP0dP0); 109 | Matrix3 df0dP1 = -k * (q.dC0dP0 * q.dC0dP1.transpose() + q.C0 * q.d2C0dP0dP1 + q.dC1dP0 * q.dC1dP1.transpose() + q.C1 * q.d2C1dP0dP1); 110 | Matrix3 df0dP2 = -k * (q.dC0dP0 * q.dC0dP2.transpose() + q.C0 * q.d2C0dP0dP2 + q.dC1dP0 * q.dC1dP2.transpose() + q.C1 * q.d2C1dP0dP2); 111 | 112 | Matrix3 df1dP0 = -k * (q.dC0dP1 * q.dC0dP0.transpose() + q.C0 * q.d2C0dP1dP0 + q.dC1dP1 * q.dC1dP0.transpose() + q.C1 * q.d2C1dP1dP0); 113 | Matrix3 df1dP1 = -k * (q.dC0dP1 * q.dC0dP1.transpose() + q.C0 * q.d2C0dP1dP1 + q.dC1dP1 * q.dC1dP1.transpose() + q.C1 * q.d2C1dP1dP1); 114 | Matrix3 df1dP2 = -k * (q.dC0dP1 * q.dC0dP2.transpose() + q.C0 * q.d2C0dP1dP2 + q.dC1dP1 * q.dC1dP2.transpose() + q.C1 * q.d2C1dP1dP2); 115 | 116 | Matrix3 df2dP0 = -k * (q.dC0dP2 * q.dC0dP0.transpose() + q.C0 * q.d2C0dP2dP0 + q.dC1dP2 * q.dC1dP0.transpose() + q.C1 * q.d2C1dP2dP0); 117 | Matrix3 df2dP1 = -k * (q.dC0dP2 * q.dC0dP1.transpose() + q.C0 * q.d2C0dP2dP1 + q.dC1dP2 * q.dC1dP1.transpose() + q.C1 * q.d2C1dP2dP1); 118 | Matrix3 df2dP2 = -k * (q.dC0dP2 * q.dC0dP2.transpose() + q.C0 * q.d2C0dP2dP2 + q.dC1dP2 * q.dC1dP2.transpose() + q.C1 * q.d2C1dP2dP2); 119 | 120 | for (int i = 0; i < 3; ++i) 121 | { 122 | for (int j = 0; j < 3; ++j) 123 | { 124 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += df0dP0(i, j); 125 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += df0dP1(i, j); 126 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += df0dP2(i, j); 127 | 128 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += df1dP0(i, j); 129 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += df1dP1(i, j); 130 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += df1dP2(i, j); 131 | 132 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += df2dP0(i, j); 133 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += df2dP1(i, j); 134 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += df2dP2(i, j); 135 | } 136 | } 137 | 138 | // compute damping forces and v derivatives: 139 | 140 | // fd = -d * ( dC0/dt * dC0/dx + dC1/dt * dC1/dx ): 141 | 142 | dampingForces.segment<3>(3 * m_inds[0]) -= d * ( q.dC0dt * q.dC0dP0 + q.dC1dt * q.dC1dP0 ); 143 | dampingForces.segment<3>(3 * m_inds[1]) -= d * ( q.dC0dt * q.dC0dP1 + q.dC1dt * q.dC1dP1 ); 144 | dampingForces.segment<3>(3 * m_inds[2]) -= d * ( q.dC0dt * q.dC0dP2 + q.dC1dt * q.dC1dP2 ); 145 | 146 | Matrix3 dfd0dV0 = -d * (q.dC0dP0 * q.dC0dP0.transpose() + q.dC1dP0 * q.dC1dP0.transpose()); 147 | Matrix3 dfd0dV1 = -d * (q.dC0dP0 * q.dC0dP1.transpose() + q.dC1dP0 * q.dC1dP1.transpose()); 148 | Matrix3 dfd0dV2 = -d * (q.dC0dP0 * q.dC0dP2.transpose() + q.dC1dP0 * q.dC1dP2.transpose()); 149 | 150 | Matrix3 dfd1dV0 = -d * (q.dC0dP1 * q.dC0dP0.transpose() + q.dC1dP1 * q.dC1dP0.transpose()); 151 | Matrix3 dfd1dV1 = -d * (q.dC0dP1 * q.dC0dP1.transpose() + q.dC1dP1 * q.dC1dP1.transpose()); 152 | Matrix3 dfd1dV2 = -d * (q.dC0dP1 * q.dC0dP2.transpose() + q.dC1dP1 * q.dC1dP2.transpose()); 153 | 154 | Matrix3 dfd2dV0 = -d * (q.dC0dP2 * q.dC0dP0.transpose() + q.dC1dP2 * q.dC1dP0.transpose()); 155 | Matrix3 dfd2dV1 = -d * (q.dC0dP2 * q.dC0dP1.transpose() + q.dC1dP2 * q.dC1dP1.transpose()); 156 | Matrix3 dfd2dV2 = -d * (q.dC0dP2 * q.dC0dP2.transpose() + q.dC1dP2 * q.dC1dP2.transpose()); 157 | 158 | for (int i = 0; i < 3; ++i) 159 | { 160 | for (int j = 0; j < 3; ++j) 161 | { 162 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += dfd0dV0(i, j); 163 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += dfd0dV1(i, j); 164 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += dfd0dV2(i, j); 165 | 166 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += dfd1dV0(i, j); 167 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += dfd1dV1(i, j); 168 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += dfd1dV2(i, j); 169 | 170 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += dfd2dV0(i, j); 171 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += dfd2dV1(i, j); 172 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += dfd2dV2(i, j); 173 | } 174 | } 175 | 176 | // Damping force x derivatives are given by: 177 | 178 | // -kd ( dc/dpi pc/dpi^T + d2c/dpidpj dc/dt ) 179 | 180 | // Unfortunately, the first term isn't symmetric, so it messes up the 181 | // linear solver. However, apparently it's quite small in practice so 182 | // you can leave it out, hence the name dampingForcePseudoXDerivatives. 183 | // see page 6 of http://www.cs.cmu.edu/~baraff/papers/sig98.pdf for 184 | // details. 185 | 186 | // Therefore, lets compute -kd * d2c/dpidpj dc/dt: 187 | Matrix3 dD0dP0Pseudo = -d * (q.d2C0dP0dP0 * q.dC0dt + q.d2C1dP0dP0 * q.dC1dt); 188 | Matrix3 dD1dP0Pseudo = -d * (q.d2C0dP1dP0 * q.dC0dt + q.d2C1dP1dP0 * q.dC1dt); 189 | Matrix3 dD2dP0Pseudo = -d * (q.d2C0dP2dP0 * q.dC0dt + q.d2C1dP2dP0 * q.dC1dt); 190 | 191 | Matrix3 dD0dP1Pseudo = -d * (q.d2C0dP0dP1 * q.dC0dt + q.d2C1dP0dP1 * q.dC1dt); 192 | Matrix3 dD1dP1Pseudo = -d * (q.d2C0dP1dP1 * q.dC0dt + q.d2C1dP1dP1 * q.dC1dt); 193 | Matrix3 dD2dP1Pseudo = -d * (q.d2C0dP2dP1 * q.dC0dt + q.d2C1dP2dP1 * q.dC1dt); 194 | 195 | Matrix3 dD0dP2Pseudo = -d * (q.d2C0dP0dP2 * q.dC0dt + q.d2C1dP0dP2 * q.dC1dt); 196 | Matrix3 dD1dP2Pseudo = -d * (q.d2C0dP1dP2 * q.dC0dt + q.d2C1dP1dP2 * q.dC1dt); 197 | Matrix3 dD2dP2Pseudo = -d * (q.d2C0dP2dP2 * q.dC0dt + q.d2C1dP2dP2 * q.dC1dt); 198 | 199 | for (int i = 0; i < 3; ++i) 200 | { 201 | for (int j = 0; j < 3; ++j) 202 | { 203 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += dD0dP0Pseudo(i, j); 204 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += dD1dP0Pseudo(i, j); 205 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += dD2dP0Pseudo(i, j); 206 | 207 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += dD0dP1Pseudo(i, j); 208 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += dD1dP1Pseudo(i, j); 209 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += dD2dP1Pseudo(i, j); 210 | 211 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += dD0dP2Pseudo(i, j); 212 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += dD1dP2Pseudo(i, j); 213 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += dD2dP2Pseudo(i, j); 214 | } 215 | } 216 | 217 | 218 | } 219 | 220 | template class StretchCondition; 221 | template class StretchCondition; 222 | -------------------------------------------------------------------------------- /simLib.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | ReleaseDebug 10 | x64 11 | 12 | 13 | Release 14 | x64 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Win32Proj 43 | {681412B0-F197-4A2F-9263-DEA2E8690146} 44 | simLib 45 | 46 | 47 | 48 | StaticLibrary 49 | true 50 | v120 51 | 52 | 53 | StaticLibrary 54 | false 55 | v120 56 | 57 | 58 | StaticLibrary 59 | false 60 | v120 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | true 80 | $(Platform)\$(Configuration)\ 81 | .lib 82 | AllRules.ruleset 83 | Link 84 | 85 | 86 | 87 | _DEBUG;WIN32;_WINDOWS;_USRDLL;NT_PLUGIN;_SECURE_SCL=0;_SECURE_SCL_THROWS=0;_SECURE_SCL_DEPRECATE=0;_CRT_SECURE_NO_DEPRECATE;TBB_USE_DEBUG=0;__TBB_LIB_NAME=tbb.lib;REQUIRE_IOSTREAM;AW_NEW_IOSTREAMS;Bits64_;%(PreprocessorDefinitions) 88 | C:\Software Libraries\eigen-3.2.7;.\include;%(AdditionalIncludeDirectories) 89 | MultiThreadedDebugDLL 90 | Level3 91 | ProgramDatabase 92 | true 93 | $(IntDir)$(ProjectName).pdb 94 | Disabled 95 | 96 | 97 | true 98 | Windows 99 | %(AdditionalLibraryDirectories) 100 | 101 | 102 | /export:initializePlugin /export:uninitializePlugin %(AdditionalOptions) 103 | false 104 | $(OutDir)$(TargetName).lib 105 | 106 | 107 | C:\freelance\Jordan Hara\ParticleAnalyser\git\test\launchMayaTest.bat 108 | 109 | 110 | 111 | false 112 | $(Platform)\$(Configuration)\ 113 | .lib 114 | AllRules.ruleset 115 | Link 116 | 117 | 118 | 119 | NDEBUG;WIN32;_WINDOWS;_USRDLL;NT_PLUGIN;_SECURE_SCL=0;_SECURE_SCL_THROWS=0;_SECURE_SCL_DEPRECATE=0;_CRT_SECURE_NO_DEPRECATE;TBB_USE_DEBUG=0;__TBB_LIB_NAME=tbb.lib;REQUIRE_IOSTREAM;AW_NEW_IOSTREAMS;Bits64_;%(PreprocessorDefinitions) 120 | C:\Software Libraries\eigen-3.2.7;.\include;%(AdditionalIncludeDirectories) 121 | MultiThreadedDLL 122 | Level3 123 | ProgramDatabase 124 | true 125 | $(IntDir)$(ProjectName).pdb 126 | OnlyExplicitInline 127 | true 128 | true 129 | Disabled 130 | 131 | 132 | true 133 | Windows 134 | %(AdditionalLibraryDirectories) 135 | 136 | 137 | /export:initializePlugin /export:uninitializePlugin %(AdditionalOptions) 138 | false 139 | $(OutDir)$(TargetName).lib 140 | true 141 | true 142 | 143 | 144 | C:\freelance\Jordan Hara\ParticleAnalyser\git\test\launchMayaTest.bat 145 | 146 | 147 | 148 | false 149 | $(Platform)\$(Configuration)\ 150 | .lib 151 | AllRules.ruleset 152 | Link 153 | 154 | 155 | 156 | NDEBUG;WIN32;_WINDOWS;_USRDLL;NT_PLUGIN;_SECURE_SCL=0;_SECURE_SCL_THROWS=0;_SECURE_SCL_DEPRECATE=0;_CRT_SECURE_NO_DEPRECATE;TBB_USE_DEBUG=0;__TBB_LIB_NAME=tbb.lib;REQUIRE_IOSTREAM;AW_NEW_IOSTREAMS;Bits64_;%(PreprocessorDefinitions) 157 | C:\Software Libraries\eigen-3.2.7;.\include;%(AdditionalIncludeDirectories) 158 | MultiThreaded 159 | Level3 160 | ProgramDatabase 161 | true 162 | $(IntDir)$(ProjectName).pdb 163 | OnlyExplicitInline 164 | true 165 | true 166 | Disabled 167 | 168 | 169 | false 170 | Windows 171 | %(AdditionalLibraryDirectories) 172 | 173 | 174 | /export:initializePlugin /export:uninitializePlugin %(AdditionalOptions) 175 | false 176 | $(OutDir)$(TargetName).lib 177 | true 178 | true 179 | 180 | 181 | C:\freelance\Jordan Hara\ParticleAnalyser\git\test\launchMayaTest.bat 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /src/test/ShearConditionTest.cpp: -------------------------------------------------------------------------------- 1 | #include "CppUnitTest.h" 2 | 3 | #include "test\EqualityTests.h" 4 | 5 | #include "simLib\ShearCondition.h" 6 | 7 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 8 | using namespace ClothSim; 9 | 10 | namespace UnitTest1 11 | { 12 | TEST_CLASS(ShearConditionTest) 13 | { 14 | public: 15 | 16 | void shearTriangleQuantitiesForDerivative(Eigen::VectorXd x, Eigen::VectorXd uv, Eigen::VectorXd v, int idx, double dx, ShearCondition::TriangleQuantities &qPlus, ShearCondition::TriangleQuantities &qMinus) const 17 | { 18 | double component = x[idx]; 19 | x[idx] = component + dx; 20 | qPlus = ShearCondition::TriangleQuantities( 21 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), 22 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 23 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2) 24 | ); 25 | x[idx] = component - dx; 26 | qMinus = ShearCondition::TriangleQuantities( 27 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), 28 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 29 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2) 30 | ); 31 | x[idx] = component; 32 | } 33 | 34 | void checkShearTriangleQuantities(const Eigen::VectorXd &x, const Eigen::VectorXd &uv, const Eigen::VectorXd &v, double dx, double tol) 35 | { 36 | ShearCondition::TriangleQuantities q( 37 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), 38 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 39 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2) 40 | ); 41 | 42 | Eigen::VectorXd xtest; 43 | xtest = x + v * dx; 44 | ShearCondition::TriangleQuantities qtPlus( 45 | xtest.segment<3>(3 * 0), xtest.segment<3>(3 * 1), xtest.segment<3>(3 * 2), 46 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 47 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2) 48 | ); 49 | xtest = x - v * dx; 50 | ShearCondition::TriangleQuantities qtMinus( 51 | xtest.segment<3>(3 * 0), xtest.segment<3>(3 * 1), xtest.segment<3>(3 * 2), 52 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 53 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2) 54 | ); 55 | 56 | double dCdt = (qtPlus.C - qtMinus.C) / (2 * dx); 57 | Assert::AreEqual(dCdt, q.dCdt, tol); 58 | 59 | 60 | ShearCondition::TriangleQuantities q0xPlus, q0xMinus, q0yPlus, q0yMinus, q0zPlus, q0zMinus; 61 | ShearCondition::TriangleQuantities q1xPlus, q1xMinus, q1yPlus, q1yMinus, q1zPlus, q1zMinus; 62 | ShearCondition::TriangleQuantities q2xPlus, q2xMinus, q2yPlus, q2yMinus, q2zPlus, q2zMinus; 63 | 64 | shearTriangleQuantitiesForDerivative(x, uv, v, 0, dx, q0xPlus, q0xMinus); 65 | shearTriangleQuantitiesForDerivative(x, uv, v, 1, dx, q0yPlus, q0yMinus); 66 | shearTriangleQuantitiesForDerivative(x, uv, v, 2, dx, q0zPlus, q0zMinus); 67 | 68 | shearTriangleQuantitiesForDerivative(x, uv, v, 3, dx, q1xPlus, q1xMinus); 69 | shearTriangleQuantitiesForDerivative(x, uv, v, 4, dx, q1yPlus, q1yMinus); 70 | shearTriangleQuantitiesForDerivative(x, uv, v, 5, dx, q1zPlus, q1zMinus); 71 | 72 | shearTriangleQuantitiesForDerivative(x, uv, v, 6, dx, q2xPlus, q2xMinus); 73 | shearTriangleQuantitiesForDerivative(x, uv, v, 7, dx, q2yPlus, q2yMinus); 74 | shearTriangleQuantitiesForDerivative(x, uv, v, 8, dx, q2zPlus, q2zMinus); 75 | 76 | 77 | // test first normalized wu and wv derivatives: 78 | Eigen::Matrix3d dwudP0; 79 | dwudP0.col(0) = (q0xPlus.wu - q0xMinus.wu) / (2 * dx); 80 | dwudP0.col(1) = (q0yPlus.wu - q0yMinus.wu) / (2 * dx); 81 | dwudP0.col(2) = (q0zPlus.wu - q0zMinus.wu) / (2 * dx); 82 | checkMatrixEquality(dwudP0, q.dwudP0, tol); 83 | 84 | Eigen::Matrix3d dwudP1; 85 | dwudP1.col(0) = (q1xPlus.wu - q1xMinus.wu) / (2 * dx); 86 | dwudP1.col(1) = (q1yPlus.wu - q1yMinus.wu) / (2 * dx); 87 | dwudP1.col(2) = (q1zPlus.wu - q1zMinus.wu) / (2 * dx); 88 | checkMatrixEquality(dwudP1, q.dwudP1, tol); 89 | 90 | Eigen::Matrix3d dwudP2; 91 | dwudP2.col(0) = (q2xPlus.wu - q2xMinus.wu) / (2 * dx); 92 | dwudP2.col(1) = (q2yPlus.wu - q2yMinus.wu) / (2 * dx); 93 | dwudP2.col(2) = (q2zPlus.wu - q2zMinus.wu) / (2 * dx); 94 | checkMatrixEquality(dwudP2, q.dwudP2, tol); 95 | 96 | Eigen::Matrix3d dwvdP0; 97 | dwvdP0.col(0) = (q0xPlus.wv - q0xMinus.wv) / (2 * dx); 98 | dwvdP0.col(1) = (q0yPlus.wv - q0yMinus.wv) / (2 * dx); 99 | dwvdP0.col(2) = (q0zPlus.wv - q0zMinus.wv) / (2 * dx); 100 | checkMatrixEquality(dwvdP0, q.dwvdP0, tol); 101 | 102 | Eigen::Matrix3d dwvdP1; 103 | dwvdP1.col(0) = (q1xPlus.wv - q1xMinus.wv) / (2 * dx); 104 | dwvdP1.col(1) = (q1yPlus.wv - q1yMinus.wv) / (2 * dx); 105 | dwvdP1.col(2) = (q1zPlus.wv - q1zMinus.wv) / (2 * dx); 106 | checkMatrixEquality(dwvdP1, q.dwvdP1, tol); 107 | 108 | Eigen::Matrix3d dwvdP2; 109 | dwvdP2.col(0) = (q2xPlus.wv - q2xMinus.wv) / (2 * dx); 110 | dwvdP2.col(1) = (q2yPlus.wv - q2yMinus.wv) / (2 * dx); 111 | dwvdP2.col(2) = (q2zPlus.wv - q2zMinus.wv) / (2 * dx); 112 | checkMatrixEquality(dwvdP2, q.dwvdP2, tol); 113 | 114 | 115 | // test first C derivatives: 116 | Eigen::Vector3d dCdP0; 117 | dCdP0[0] = (q0xPlus.C - q0xMinus.C) / (2 * dx); 118 | dCdP0[1] = (q0yPlus.C - q0yMinus.C) / (2 * dx); 119 | dCdP0[2] = (q0zPlus.C - q0zMinus.C) / (2 * dx); 120 | checkVectorEquality(dCdP0, q.dCdP0, tol); 121 | 122 | Eigen::Vector3d dCdP1; 123 | dCdP1[0] = (q1xPlus.C - q1xMinus.C) / (2 * dx); 124 | dCdP1[1] = (q1yPlus.C - q1yMinus.C) / (2 * dx); 125 | dCdP1[2] = (q1zPlus.C - q1zMinus.C) / (2 * dx); 126 | checkVectorEquality(dCdP1, q.dCdP1, tol); 127 | 128 | Eigen::Vector3d dCdP2; 129 | dCdP2[0] = (q2xPlus.C - q2xMinus.C) / (2 * dx); 130 | dCdP2[1] = (q2yPlus.C - q2yMinus.C) / (2 * dx); 131 | dCdP2[2] = (q2zPlus.C - q2zMinus.C) / (2 * dx); 132 | checkVectorEquality(dCdP2, q.dCdP2, tol); 133 | 134 | 135 | // test second C derivatives: 136 | Eigen::Matrix3d d2CdP0dP0; 137 | d2CdP0dP0.col(0) = (q0xPlus.dCdP0 - q0xMinus.dCdP0) / (2 * dx); 138 | d2CdP0dP0.col(1) = (q0yPlus.dCdP0 - q0yMinus.dCdP0) / (2 * dx); 139 | d2CdP0dP0.col(2) = (q0zPlus.dCdP0 - q0zMinus.dCdP0) / (2 * dx); 140 | checkMatrixEquality(d2CdP0dP0, q.d2CdP0dP0, tol); 141 | 142 | Eigen::Matrix3d d2CdP0dP1; 143 | d2CdP0dP1.col(0) = (q1xPlus.dCdP0 - q1xMinus.dCdP0) / (2 * dx); 144 | d2CdP0dP1.col(1) = (q1yPlus.dCdP0 - q1yMinus.dCdP0) / (2 * dx); 145 | d2CdP0dP1.col(2) = (q1zPlus.dCdP0 - q1zMinus.dCdP0) / (2 * dx); 146 | checkMatrixEquality(d2CdP0dP1, q.d2CdP0dP1, tol); 147 | 148 | Eigen::Matrix3d d2CdP0dP2; 149 | d2CdP0dP2.col(0) = (q2xPlus.dCdP0 - q2xMinus.dCdP0) / (2 * dx); 150 | d2CdP0dP2.col(1) = (q2yPlus.dCdP0 - q2yMinus.dCdP0) / (2 * dx); 151 | d2CdP0dP2.col(2) = (q2zPlus.dCdP0 - q2zMinus.dCdP0) / (2 * dx); 152 | checkMatrixEquality(d2CdP0dP2, q.d2CdP0dP2, tol); 153 | 154 | 155 | 156 | Eigen::Matrix3d d2CdP1dP0; 157 | d2CdP1dP0.col(0) = (q0xPlus.dCdP1 - q0xMinus.dCdP1) / (2 * dx); 158 | d2CdP1dP0.col(1) = (q0yPlus.dCdP1 - q0yMinus.dCdP1) / (2 * dx); 159 | d2CdP1dP0.col(2) = (q0zPlus.dCdP1 - q0zMinus.dCdP1) / (2 * dx); 160 | checkMatrixEquality(d2CdP1dP0, q.d2CdP1dP0, tol); 161 | 162 | Eigen::Matrix3d d2CdP1dP1; 163 | d2CdP1dP1.col(0) = (q1xPlus.dCdP1 - q1xMinus.dCdP1) / (2 * dx); 164 | d2CdP1dP1.col(1) = (q1yPlus.dCdP1 - q1yMinus.dCdP1) / (2 * dx); 165 | d2CdP1dP1.col(2) = (q1zPlus.dCdP1 - q1zMinus.dCdP1) / (2 * dx); 166 | checkMatrixEquality(d2CdP1dP1, q.d2CdP1dP1, tol); 167 | 168 | Eigen::Matrix3d d2CdP1dP2; 169 | d2CdP1dP2.col(0) = (q2xPlus.dCdP1 - q2xMinus.dCdP1) / (2 * dx); 170 | d2CdP1dP2.col(1) = (q2yPlus.dCdP1 - q2yMinus.dCdP1) / (2 * dx); 171 | d2CdP1dP2.col(2) = (q2zPlus.dCdP1 - q2zMinus.dCdP1) / (2 * dx); 172 | checkMatrixEquality(d2CdP1dP2, q.d2CdP1dP2, tol); 173 | 174 | 175 | Eigen::Matrix3d d2CdP2dP0; 176 | d2CdP2dP0.col(0) = (q0xPlus.dCdP2 - q0xMinus.dCdP2) / (2 * dx); 177 | d2CdP2dP0.col(1) = (q0yPlus.dCdP2 - q0yMinus.dCdP2) / (2 * dx); 178 | d2CdP2dP0.col(2) = (q0zPlus.dCdP2 - q0zMinus.dCdP2) / (2 * dx); 179 | checkMatrixEquality(d2CdP2dP0, q.d2CdP2dP0, tol); 180 | 181 | Eigen::Matrix3d d2CdP2dP1; 182 | d2CdP2dP1.col(0) = (q1xPlus.dCdP2 - q1xMinus.dCdP2) / (2 * dx); 183 | d2CdP2dP1.col(1) = (q1yPlus.dCdP2 - q1yMinus.dCdP2) / (2 * dx); 184 | d2CdP2dP1.col(2) = (q1zPlus.dCdP2 - q1zMinus.dCdP2) / (2 * dx); 185 | checkMatrixEquality(d2CdP2dP1, q.d2CdP2dP1, tol); 186 | 187 | Eigen::Matrix3d d2CdP2dP2; 188 | d2CdP2dP2.col(0) = (q2xPlus.dCdP2 - q2xMinus.dCdP2) / (2 * dx); 189 | d2CdP2dP2.col(1) = (q2yPlus.dCdP2 - q2yMinus.dCdP2) / (2 * dx); 190 | d2CdP2dP2.col(2) = (q2zPlus.dCdP2 - q2zMinus.dCdP2) / (2 * dx); 191 | checkMatrixEquality(d2CdP2dP2, q.d2CdP2dP2, tol); 192 | 193 | } 194 | 195 | 196 | TEST_METHOD(TestShearConditionTriangleQuantities) 197 | { 198 | Eigen::VectorXd uv(3 * 2); 199 | 200 | uv[2 * 0] = 1.0; 201 | uv[2 * 0 + 1] = 2.0; 202 | 203 | uv[2 * 1] = 6.0; 204 | uv[2 * 1 + 1] = 3.0; 205 | 206 | uv[2 * 2] = 2.0; 207 | uv[2 * 2 + 1] = 5.0; 208 | 209 | Eigen::Vector3d p0(1, 2, 3); 210 | Eigen::Vector3d wu(1.3, 0.2, -0.1); 211 | Eigen::Vector3d wv(-0.5, 1.1, 0.3); 212 | 213 | Eigen::VectorXd x(3 * 3); 214 | x.segment<3>(3 * 0) = p0 + wu * uv[2 * 0 + 0] + wv * uv[2 * 0 + 1]; 215 | x.segment<3>(3 * 1) = p0 + wu * uv[2 * 1 + 0] + wv * uv[2 * 1 + 1]; 216 | x.segment<3>(3 * 2) = p0 + wu * uv[2 * 2 + 0] + wv * uv[2 * 2 + 1]; 217 | 218 | Eigen::VectorXd v = Eigen::VectorXd::Random(3 * 3); 219 | 220 | ShearCondition::TriangleQuantities q( 221 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), 222 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 223 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2) 224 | ); 225 | 226 | double dx = 0.0001; 227 | double tol = 1.e-5; 228 | 229 | // make sure we're calculating wu, wv and a as expected: 230 | checkVectorEquality(q.wu, wu, tol); 231 | checkVectorEquality(q.wv, wv, tol); 232 | Eigen::Vector3d uv0(uv[2 * 0], uv[2 * 0 + 1], 0); 233 | Eigen::Vector3d uv1(uv[2 * 1], uv[2 * 1 + 1], 0); 234 | Eigen::Vector3d uv2(uv[2 * 2], uv[2 * 2 + 1], 0); 235 | Assert::AreEqual(q.a, 0.5 * ((uv1 - uv0).cross(uv2 - uv0)).norm(), tol); 236 | 237 | checkShearTriangleQuantities(x, uv, v, dx, tol); 238 | 239 | // test for 10 random configurations: 240 | for (int i = 0; i < 10; ++i) 241 | { 242 | // randomize the positions: 243 | x = Eigen::VectorXd::Random(3 * 3); 244 | uv = Eigen::VectorXd::Random(3 * 2); 245 | v = Eigen::VectorXd::Random(3 * 3); 246 | checkShearTriangleQuantities(x, uv, v, dx, tol); 247 | } 248 | 249 | } 250 | 251 | TEST_METHOD(TestShearCondition) 252 | { 253 | Eigen::VectorXd uv(3 * 2); 254 | 255 | uv[2 * 0] = 1.0; 256 | uv[2 * 0 + 1] = 2.0; 257 | 258 | uv[2 * 1] = 6.0; 259 | uv[2 * 1 + 1] = 3.0; 260 | 261 | uv[2 * 2] = 2.0; 262 | uv[2 * 2 + 1] = 5.0; 263 | 264 | Eigen::Vector3d p0(1, 2, 3); 265 | Eigen::Vector3d wu(1.3, 0.2, -0.1); 266 | Eigen::Vector3d wv(-0.5, 1.1, 0.3); 267 | 268 | Eigen::VectorXd x(3 * 3); 269 | x.segment<3>(3 * 0) = p0 + wu * uv[2 * 0 + 0] + wv * uv[2 * 0 + 1]; 270 | x.segment<3>(3 * 1) = p0 + wu * uv[2 * 1 + 0] + wv * uv[2 * 1 + 1]; 271 | x.segment<3>(3 * 2) = p0 + wu * uv[2 * 2 + 0] + wv * uv[2 * 2 + 1]; 272 | 273 | Eigen::VectorXd v(3 * 3); 274 | 275 | v[3 * 0] = 0.1; 276 | v[3 * 0 + 1] = 0.2; 277 | v[3 * 0 + 2] = 0.3; 278 | 279 | v[3 * 1] = -.1; 280 | v[3 * 1 + 1] = -1; 281 | v[3 * 1 + 2] = 0.4; 282 | 283 | v[3 * 2] = 0; 284 | v[3 * 2 + 1] = -0.2; 285 | v[3 * 2 + 2] = 0.1; 286 | 287 | double bu = 0.9; 288 | double bv = 1.1; 289 | 290 | Eigen::VectorXd f(3 * 3); 291 | f.setConstant(0); 292 | Eigen::VectorXd dampingForces(3 * 3); 293 | dampingForces.setConstant(0); 294 | ShearCondition sc(0, 1, 2); 295 | 296 | double k = 1.5; 297 | Eigen::SparseMatrix dfdx(3 * 3, 3 * 3); 298 | double d = 1.0; 299 | Eigen::SparseMatrix dampingPseudoDerivatives(3 * 3, 3 * 3); 300 | Eigen::SparseMatrix dfdv(3 * 3, 3 * 3); 301 | 302 | // compute forces analytically: 303 | sc.computeForces(x, uv, k, f, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 304 | 305 | // compare to numerically computed forces: 306 | double dx = 0.0001; 307 | double tol = 1.e-4; 308 | 309 | for (int i = 0; i < x.size(); ++i) 310 | { 311 | Assert::AreEqual(numericalForce(sc, uv, x, k, i, dx), f[i], tol); 312 | Assert::AreEqual(numericalDampingForce(sc, uv, x, v, d, i, dx), dampingForces[i], tol); 313 | } 314 | 315 | 316 | for (int i = 0; i < x.size(); ++i) 317 | { 318 | Eigen::VectorXd fdN = dfdx.row(i); 319 | checkVectorEquality(fdN, numericalForceDerivative(sc, uv, x, v, k, d, i, dx), tol, true); 320 | fdN = dfdv.row(i); 321 | checkVectorEquality(fdN, numericalDampingForceDerivative(sc, uv, x, v, k, d, i, dx), tol, true); 322 | } 323 | 324 | // check the terms that are almost dfd/dx but not quite: 325 | checkPseudoDerivatives(dampingPseudoDerivatives, sc, uv, x, v, k, d, dx, tol); 326 | 327 | // test on 10 randomized configurations: 328 | for (size_t n = 0; n < 10; ++n) 329 | { 330 | // randomize positions: 331 | x = Eigen::VectorXd::Random(3 * 3); 332 | f.setConstant(0); 333 | for (int i = 0; i < dfdx.outerSize(); ++i) 334 | { 335 | for (Eigen::SparseMatrix::InnerIterator it(dfdx, i); it; ++it) 336 | { 337 | it.valueRef() = 0; 338 | } 339 | } 340 | dampingForces.setConstant(0); 341 | for (int i = 0; i < dampingPseudoDerivatives.outerSize(); ++i) 342 | { 343 | for (Eigen::SparseMatrix::InnerIterator it(dampingPseudoDerivatives, i); it; ++it) 344 | { 345 | it.valueRef() = 0; 346 | } 347 | } 348 | for (int i = 0; i < dfdv.outerSize(); ++i) 349 | { 350 | for (Eigen::SparseMatrix::InnerIterator it(dfdv, i); it; ++it) 351 | { 352 | it.valueRef() = 0; 353 | } 354 | } 355 | sc.computeForces(x, uv, k, f, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 356 | 357 | for (int i = 0; i < x.size(); ++i) 358 | { 359 | Assert::AreEqual(numericalForce(sc, uv, x, k, i, dx), f[i], tol); 360 | Assert::AreEqual(numericalDampingForce(sc, uv, x, v, d, i, dx), dampingForces[i], tol); 361 | } 362 | 363 | for (int i = 0; i < x.size(); ++i) 364 | { 365 | Eigen::VectorXd fdN = dfdx.row(i); 366 | checkVectorEquality(fdN, numericalForceDerivative(sc, uv, x, v, k, d, i, dx), tol, true); 367 | fdN = dfdv.row(i); 368 | checkVectorEquality(fdN, numericalDampingForceDerivative(sc, uv, x, v, k, d, i, dx), tol, true); 369 | } 370 | 371 | // check the terms that are almost dfd/dx but not quite: 372 | checkPseudoDerivatives(dampingPseudoDerivatives, sc, uv, x, v, k, d, dx, tol); 373 | } 374 | } 375 | 376 | }; 377 | } 378 | -------------------------------------------------------------------------------- /src/simLib/BendCondition.cpp: -------------------------------------------------------------------------------- 1 | #include "simLib\BendCondition.h" 2 | 3 | using namespace ClothSim; 4 | 5 | template 6 | BendCondition::BendCondition(int i0, int i1, int i2, int i3) 7 | { 8 | m_inds[0] = i0; 9 | m_inds[1] = i1; 10 | m_inds[2] = i2; 11 | m_inds[3] = i3; 12 | } 13 | 14 | template 15 | BendCondition::TriangleQuantities::TriangleQuantities() 16 | { 17 | } 18 | 19 | template 20 | BendCondition::TriangleQuantities::TriangleQuantities( 21 | const Vector3 &p0, const Vector3 &p1, const Vector3 &p2, const Vector3 &p3, 22 | const Vector3 &v0, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3 23 | ) 24 | { 25 | // triangles are laid out like this: 26 | // 0 27 | // / \ 28 | // / 0 \ 29 | // 1-----2 30 | // \ 1 / 31 | // \ / 32 | // 3 33 | 34 | // edge vectors: 35 | v01 = p1 - p0; 36 | v02 = p2 - p0; 37 | v32 = p2 - p3; 38 | v31 = p1 - p3; 39 | v21 = p1 - p2; 40 | 41 | // normalized edge vectors: 42 | e01 = v01.normalized(); 43 | e02 = v02.normalized(); 44 | e32 = v32.normalized(); 45 | e31 = v31.normalized(); 46 | e21 = v21.normalized(); 47 | 48 | // cosines of the angles at each of the points for each of the triangles: 49 | c00 = e01.dot(e02); 50 | c01 = e01.dot(e21); 51 | c02 = -e02.dot(e21); 52 | 53 | c13 = e31.dot(e32); 54 | c11 = e21.dot(e31); 55 | c12 = -e32.dot(e21); 56 | 57 | // normalized triangle normals: 58 | n0 = (e21.cross(e01)).normalized(); 59 | n1 = (e32.cross(e21)).normalized(); 60 | 61 | // normalized binormals for each triangle, pointing from a vertex to its opposite edge: 62 | b00 = (e01 - e21 * (e21.dot(e01))).normalized(); 63 | b01 = (-e01 - e02 * (e02.dot(-e01))).normalized(); 64 | b02 = (-e02 - e01 * (e01.dot(-e02))).normalized(); 65 | 66 | b13 = (e32 - e21 * (e21.dot(e32))).normalized(); 67 | b12 = (-e32 - e31 * (e31.dot(-e32))).normalized(); 68 | b11 = (-e31 - e32 * (e32.dot(-e31))).normalized(); 69 | 70 | // vertex distances to opposite edges: 71 | d00 = (v01 - v01.dot(v21) / v21.dot(v21) * v21).norm();//b00.dot(v01); 72 | d01 = b01.dot(-v21); 73 | d02 = b02.dot(-v02); 74 | 75 | d11 = b11.dot(-v31); 76 | d12 = b12.dot(v21); 77 | d13 = b13.dot(v31); 78 | 79 | // compute angle between triangles, using a sin as that can give us negative answers as well as positive ones: 80 | Real cosTheta = n0.dot(n1); 81 | Real sinTheta = n1.dot(b00); 82 | theta = atan2(sinTheta, cosTheta); 83 | 84 | // derivatives of theta with respect to the different vertex positions: 85 | dThetadP0 = -n0 / d00; 86 | dThetadP1 = c02 * n0 / d01 + c12 * n1 / d11; 87 | dThetadP2 = c01 * n0 / d02 + c11 * n1 / d12; 88 | dThetadP3 = -n1 / d13; 89 | 90 | // time derivative of theta: 91 | dThetadt = dThetadP0.dot(v0) + dThetadP1.dot(v1) + dThetadP2.dot(v2) + dThetadP3.dot(v3); 92 | 93 | // now compute the derivatives of some terms in those formulae, so we can get second derivatives: 94 | 95 | // derivatives of the normals: 96 | dn0dP0 = b00 * n0.transpose() / d00; 97 | dn0dP1 = b01 * n0.transpose() / d01; 98 | dn0dP2 = b02 * n0.transpose() / d02; 99 | dn0dP3 = Matrix3::Zero(); 100 | 101 | dn1dP0 = Matrix3::Zero(); 102 | dn1dP1 = b11 * n1.transpose() / d11; 103 | dn1dP2 = b12 * n1.transpose() / d12; 104 | dn1dP3 = b13 * n1.transpose() / d13; 105 | 106 | // derivatives of the cosines: 107 | dc01dP0 = -b02 * b00.dot(v01) / v01.dot(v01); 108 | dc01dP2 = -b00 * b02.dot(v21) / v21.dot(v21); 109 | dc01dP1 = -dc01dP0 - dc01dP2; 110 | dc01dP3 = Vector3::Zero(); 111 | 112 | dc02dP0 = -b01 * b00.dot(v02) / v02.dot(v02); 113 | dc02dP1 = b00 * b01.dot(v21) / v21.dot(v21); 114 | dc02dP2 = -dc02dP0 - dc02dP1; 115 | dc02dP3 = Vector3::Zero(); 116 | 117 | dc11dP0 = Vector3::Zero(); 118 | dc11dP2 = -b13 * b12.dot(v21) / v21.dot(v21); 119 | dc11dP3 = -b12 * b13.dot(v31) / v31.dot(v31); 120 | dc11dP1 = -dc11dP2 - dc11dP3; 121 | 122 | dc12dP0 = Vector3::Zero(); 123 | dc12dP1 = b13 * b11.dot(v21) / v21.dot(v21); 124 | dc12dP3 = -b11 * b13.dot(v32) / v32.dot(v32); 125 | dc12dP2 = -dc12dP1 - dc12dP3; 126 | 127 | // derivatives of the perpendicular distances: 128 | dd00dP0 = -b00; 129 | dd00dP1 = b00 * -v21.dot(v02) / v21.dot(v21); 130 | dd00dP2 = b00 * v21.dot(v01) / v21.dot(v21); 131 | dd00dP3 = Vector3::Zero(); 132 | 133 | dd01dP0 = b01 * v02.dot(-v21) / v02.dot(v02); 134 | dd01dP1 = -b01; 135 | dd01dP2 = b01 * v02.dot(v01) / v02.dot(v02); 136 | dd01dP3 = Vector3::Zero(); 137 | 138 | dd02dP0 = b02 * v01.dot(v21) / v01.dot(v01); 139 | dd02dP1 = b02 * v01.dot(v02) / v01.dot(v01); 140 | dd02dP2 = -b02; 141 | dd02dP3 = Vector3::Zero(); 142 | 143 | dd11dP0 = Vector3::Zero(); 144 | dd11dP1 = -b11; 145 | dd11dP2 = b11 * v32.dot(v31) / v32.dot(v32); 146 | dd11dP3 = b11 * v32.dot(-v21) / v32.dot(v32); 147 | 148 | dd12dP0 = Vector3::Zero(); 149 | dd12dP1 = b12 * v31.dot(v32) / v31.dot(v31); 150 | dd12dP2 = -b12; 151 | dd12dP3 = b12 * v31.dot(v21) / v31.dot(v31); 152 | 153 | dd13dP0 = Vector3::Zero(); 154 | dd13dP1 = b13 * v21.dot(-v32) / v21.dot(v21); 155 | dd13dP2 = b13 * v21.dot(v31) / v21.dot(v21); 156 | dd13dP3 = -b13; 157 | 158 | 159 | // second derivatives of theta: 160 | d2ThetadP0dP0 = -dn0dP0 / d00 + n0 * dd00dP0.transpose() / (d00 * d00); 161 | d2ThetadP0dP1 = -dn0dP1 / d00 + n0 * dd00dP1.transpose() / (d00 * d00); 162 | d2ThetadP0dP2 = -dn0dP2 / d00 + n0 * dd00dP2.transpose() / (d00 * d00); 163 | d2ThetadP0dP3 = -dn0dP3 / d00 + n0 * dd00dP3.transpose() / (d00 * d00); 164 | 165 | d2ThetadP1dP0 = ((c02 / d01) * dn0dP0 + n0 * (d01 * dc02dP0 - c02 * dd01dP0).transpose() / (d01 * d01)) + ((c12 / d11) * dn1dP0 + n1 * (d11 * dc12dP0 - c12 * dd11dP0).transpose() / (d11 * d11)); 166 | d2ThetadP1dP1 = ((c02 / d01) * dn0dP1 + n0 * (d01 * dc02dP1 - c02 * dd01dP1).transpose() / (d01 * d01)) + ((c12 / d11) * dn1dP1 + n1 * (d11 * dc12dP1 - c12 * dd11dP1).transpose() / (d11 * d11)); 167 | d2ThetadP1dP2 = ((c02 / d01) * dn0dP2 + n0 * (d01 * dc02dP2 - c02 * dd01dP2).transpose() / (d01 * d01)) + ((c12 / d11) * dn1dP2 + n1 * (d11 * dc12dP2 - c12 * dd11dP2).transpose() / (d11 * d11)); 168 | d2ThetadP1dP3 = ((c02 / d01) * dn0dP3 + n0 * (d01 * dc02dP3 - c02 * dd01dP3).transpose() / (d01 * d01)) + ((c12 / d11) * dn1dP3 + n1 * (d11 * dc12dP3 - c12 * dd11dP3).transpose() / (d11 * d11)); 169 | 170 | d2ThetadP2dP0 = ((c01 / d02) * dn0dP0 + n0 * (d02 * dc01dP0 - c01 * dd02dP0).transpose() / (d02 * d02)) + ((c11 / d12) * dn1dP0 + n1 * (d12 * dc11dP0 - c11 * dd12dP0).transpose() / (d12 * d12)); 171 | d2ThetadP2dP1 = ((c01 / d02) * dn0dP1 + n0 * (d02 * dc01dP1 - c01 * dd02dP1).transpose() / (d02 * d02)) + ((c11 / d12) * dn1dP1 + n1 * (d12 * dc11dP1 - c11 * dd12dP1).transpose() / (d12 * d12)); 172 | d2ThetadP2dP2 = ((c01 / d02) * dn0dP2 + n0 * (d02 * dc01dP2 - c01 * dd02dP2).transpose() / (d02 * d02)) + ((c11 / d12) * dn1dP2 + n1 * (d12 * dc11dP2 - c11 * dd12dP2).transpose() / (d12 * d12)); 173 | d2ThetadP2dP3 = ((c01 / d02) * dn0dP3 + n0 * (d02 * dc01dP3 - c01 * dd02dP3).transpose() / (d02 * d02)) + ((c11 / d12) * dn1dP3 + n1 * (d12 * dc11dP3 - c11 * dd12dP3).transpose() / (d12 * d12)); 174 | 175 | d2ThetadP3dP0 = -dn1dP0 / d13 + n1 * dd13dP0.transpose() / (d13 * d13); 176 | d2ThetadP3dP1 = -dn1dP1 / d13 + n1 * dd13dP1.transpose() / (d13 * d13); 177 | d2ThetadP3dP2 = -dn1dP2 / d13 + n1 * dd13dP2.transpose() / (d13 * d13); 178 | d2ThetadP3dP3 = -dn1dP3 / d13 + n1 * dd13dP3.transpose() / (d13 * d13); 179 | 180 | 181 | } 182 | 183 | template 184 | typename EnergyCondition::Vector BendCondition::C(const Vector& x, const Vector& uv) const 185 | { 186 | TriangleQuantities q( 187 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]), x.segment<3>(3 * m_inds[3]), 188 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]), x.segment<3>(3 * m_inds[3]) 189 | ); 190 | 191 | Vector ret(1); 192 | Real l = (uv.segment<2>(2 * m_inds[1]) - uv.segment<2>(2 * m_inds[2])).norm(); 193 | ret[0] = q.theta * sqrt(l); 194 | 195 | return ret; 196 | } 197 | 198 | template 199 | void BendCondition::computeForces( 200 | const Vector& x, 201 | const Vector& uv, 202 | Real k, 203 | Vector& forces, 204 | SparseMatrix &dfdx, 205 | const Vector& v, 206 | Real d, 207 | Vector &dampingForces, 208 | SparseMatrix &dampingPseudoXDerivatives, 209 | SparseMatrix &dddv 210 | ) const 211 | { 212 | 213 | TriangleQuantities q( 214 | x.segment<3>(3 * m_inds[0]), x.segment<3>(3 * m_inds[1]), x.segment<3>(3 * m_inds[2]), x.segment<3>(3 * m_inds[3]), 215 | v.segment<3>(3 * m_inds[0]), v.segment<3>(3 * m_inds[1]), v.segment<3>(3 * m_inds[2]), v.segment<3>(3 * m_inds[3]) 216 | ); 217 | 218 | Real l = (uv.segment<2>(2 * m_inds[1]) - uv.segment<2>(2 * m_inds[2])).norm(); 219 | 220 | // Compute forces: 221 | 222 | // E = 1/2 C^t C 223 | // dE/dx = theta * dThetadX 224 | // f = -dE/dx 225 | // f = - theta * dThetadX 226 | 227 | forces.segment<3>(3 * m_inds[0]) -= k * l * q.theta * q.dThetadP0; 228 | forces.segment<3>(3 * m_inds[1]) -= k * l * q.theta * q.dThetadP1; 229 | forces.segment<3>(3 * m_inds[2]) -= k * l * q.theta * q.dThetadP2; 230 | forces.segment<3>(3 * m_inds[3]) -= k * l * q.theta * q.dThetadP3; 231 | 232 | // d/dP0( - q.theta * q.dThetadP0 ) 233 | // = - q.dThetadP0 * q.dThetadP0.transpose() - q.theta * q.d2ThetadP0dP0 234 | 235 | // compute force derivatives and insert them into the sparse matrix: 236 | Matrix3 df0dP0 = -k * l * (q.dThetadP0 * q.dThetadP0.transpose() + q.theta * q.d2ThetadP0dP0); 237 | Matrix3 df0dP1 = -k * l * (q.dThetadP0 * q.dThetadP1.transpose() + q.theta * q.d2ThetadP0dP1); 238 | Matrix3 df0dP2 = -k * l * (q.dThetadP0 * q.dThetadP2.transpose() + q.theta * q.d2ThetadP0dP2); 239 | Matrix3 df0dP3 = -k * l * (q.dThetadP0 * q.dThetadP3.transpose() + q.theta * q.d2ThetadP0dP3); 240 | 241 | Matrix3 df1dP0 = -k * l * (q.dThetadP1 * q.dThetadP0.transpose() + q.theta * q.d2ThetadP1dP0); 242 | Matrix3 df1dP1 = -k * l * (q.dThetadP1 * q.dThetadP1.transpose() + q.theta * q.d2ThetadP1dP1); 243 | Matrix3 df1dP2 = -k * l * (q.dThetadP1 * q.dThetadP2.transpose() + q.theta * q.d2ThetadP1dP2); 244 | Matrix3 df1dP3 = -k * l * (q.dThetadP1 * q.dThetadP3.transpose() + q.theta * q.d2ThetadP1dP3); 245 | 246 | Matrix3 df2dP0 = -k * l * (q.dThetadP2 * q.dThetadP0.transpose() + q.theta * q.d2ThetadP2dP0); 247 | Matrix3 df2dP1 = -k * l * (q.dThetadP2 * q.dThetadP1.transpose() + q.theta * q.d2ThetadP2dP1); 248 | Matrix3 df2dP2 = -k * l * (q.dThetadP2 * q.dThetadP2.transpose() + q.theta * q.d2ThetadP2dP2); 249 | Matrix3 df2dP3 = -k * l * (q.dThetadP2 * q.dThetadP3.transpose() + q.theta * q.d2ThetadP2dP3); 250 | 251 | Matrix3 df3dP0 = -k * l * (q.dThetadP3 * q.dThetadP0.transpose() + q.theta * q.d2ThetadP3dP0); 252 | Matrix3 df3dP1 = -k * l * (q.dThetadP3 * q.dThetadP1.transpose() + q.theta * q.d2ThetadP3dP1); 253 | Matrix3 df3dP2 = -k * l * (q.dThetadP3 * q.dThetadP2.transpose() + q.theta * q.d2ThetadP3dP2); 254 | Matrix3 df3dP3 = -k * l * (q.dThetadP3 * q.dThetadP3.transpose() + q.theta * q.d2ThetadP3dP3); 255 | 256 | for (int i = 0; i < 3; ++i) 257 | { 258 | for (int j = 0; j < 3; ++j) 259 | { 260 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += df0dP0(i, j); 261 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += df0dP1(i, j); 262 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += df0dP2(i, j); 263 | dfdx.coeffRef(3 * m_inds[0] + i, 3 * m_inds[3] + j) += df0dP3(i, j); 264 | 265 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += df1dP0(i, j); 266 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += df1dP1(i, j); 267 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += df1dP2(i, j); 268 | dfdx.coeffRef(3 * m_inds[1] + i, 3 * m_inds[3] + j) += df1dP3(i, j); 269 | 270 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += df2dP0(i, j); 271 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += df2dP1(i, j); 272 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += df2dP2(i, j); 273 | dfdx.coeffRef(3 * m_inds[2] + i, 3 * m_inds[3] + j) += df2dP3(i, j); 274 | 275 | dfdx.coeffRef(3 * m_inds[3] + i, 3 * m_inds[0] + j) += df3dP0(i, j); 276 | dfdx.coeffRef(3 * m_inds[3] + i, 3 * m_inds[1] + j) += df3dP1(i, j); 277 | dfdx.coeffRef(3 * m_inds[3] + i, 3 * m_inds[2] + j) += df3dP2(i, j); 278 | dfdx.coeffRef(3 * m_inds[3] + i, 3 * m_inds[3] + j) += df3dP3(i, j); 279 | } 280 | } 281 | 282 | // compute damping forces and v derivatives: 283 | 284 | // fd = -d * dTheta/dt * dTheta/dx: 285 | 286 | dampingForces.segment<3>(3 * m_inds[0]) -= d * l * q.dThetadt * q.dThetadP0; 287 | dampingForces.segment<3>(3 * m_inds[1]) -= d * l * q.dThetadt * q.dThetadP1; 288 | dampingForces.segment<3>(3 * m_inds[2]) -= d * l * q.dThetadt * q.dThetadP2; 289 | dampingForces.segment<3>(3 * m_inds[3]) -= d * l * q.dThetadt * q.dThetadP3; 290 | 291 | Matrix3 dfd0dV0 = -d * l * (q.dThetadP0 * q.dThetadP0.transpose()); 292 | Matrix3 dfd0dV1 = -d * l * (q.dThetadP0 * q.dThetadP1.transpose()); 293 | Matrix3 dfd0dV2 = -d * l * (q.dThetadP0 * q.dThetadP2.transpose()); 294 | Matrix3 dfd0dV3 = -d * l * (q.dThetadP0 * q.dThetadP3.transpose()); 295 | 296 | Matrix3 dfd1dV0 = -d * l * (q.dThetadP1 * q.dThetadP0.transpose()); 297 | Matrix3 dfd1dV1 = -d * l * (q.dThetadP1 * q.dThetadP1.transpose()); 298 | Matrix3 dfd1dV2 = -d * l * (q.dThetadP1 * q.dThetadP2.transpose()); 299 | Matrix3 dfd1dV3 = -d * l * (q.dThetadP1 * q.dThetadP3.transpose()); 300 | 301 | Matrix3 dfd2dV0 = -d * l * (q.dThetadP2 * q.dThetadP0.transpose()); 302 | Matrix3 dfd2dV1 = -d * l * (q.dThetadP2 * q.dThetadP1.transpose()); 303 | Matrix3 dfd2dV2 = -d * l * (q.dThetadP2 * q.dThetadP2.transpose()); 304 | Matrix3 dfd2dV3 = -d * l * (q.dThetadP2 * q.dThetadP3.transpose()); 305 | 306 | Matrix3 dfd3dV0 = -d * l * (q.dThetadP3 * q.dThetadP0.transpose()); 307 | Matrix3 dfd3dV1 = -d * l * (q.dThetadP3 * q.dThetadP1.transpose()); 308 | Matrix3 dfd3dV2 = -d * l * (q.dThetadP3 * q.dThetadP2.transpose()); 309 | Matrix3 dfd3dV3 = -d * l * (q.dThetadP3 * q.dThetadP3.transpose()); 310 | 311 | for (int i = 0; i < 3; ++i) 312 | { 313 | for (int j = 0; j < 3; ++j) 314 | { 315 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += dfd0dV0(i, j); 316 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += dfd0dV1(i, j); 317 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += dfd0dV2(i, j); 318 | dddv.coeffRef(3 * m_inds[0] + i, 3 * m_inds[3] + j) += dfd0dV3(i, j); 319 | 320 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += dfd1dV0(i, j); 321 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += dfd1dV1(i, j); 322 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += dfd1dV2(i, j); 323 | dddv.coeffRef(3 * m_inds[1] + i, 3 * m_inds[3] + j) += dfd1dV3(i, j); 324 | 325 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += dfd2dV0(i, j); 326 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += dfd2dV1(i, j); 327 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += dfd2dV2(i, j); 328 | dddv.coeffRef(3 * m_inds[2] + i, 3 * m_inds[3] + j) += dfd2dV3(i, j); 329 | 330 | dddv.coeffRef(3 * m_inds[3] + i, 3 * m_inds[0] + j) += dfd3dV0(i, j); 331 | dddv.coeffRef(3 * m_inds[3] + i, 3 * m_inds[1] + j) += dfd3dV1(i, j); 332 | dddv.coeffRef(3 * m_inds[3] + i, 3 * m_inds[2] + j) += dfd3dV2(i, j); 333 | dddv.coeffRef(3 * m_inds[3] + i, 3 * m_inds[3] + j) += dfd3dV3(i, j); 334 | } 335 | } 336 | 337 | // Damping force x derivatives are given by: 338 | 339 | // -kd ( dc/dpi pc/dpi^T + d2c/dpidpj dc/dt ) 340 | 341 | // Unfortunately, the first term isn't symmetric, so it messes up the 342 | // linear solver. However, apparently it's quite small in practice so 343 | // you can leave it out, hence the name dampingForcePseudoXDerivatives. 344 | // see page 6 of http://www.cs.cmu.edu/~baraff/papers/sig98.pdf for 345 | // details. 346 | 347 | // Therefore, lets compute -kd * d2c/dpidpj dc/dt: 348 | Matrix3 dD0dP0Pseudo = -d * l * (q.d2ThetadP0dP0 * q.dThetadt); 349 | Matrix3 dD1dP0Pseudo = -d * l * (q.d2ThetadP1dP0 * q.dThetadt); 350 | Matrix3 dD2dP0Pseudo = -d * l * (q.d2ThetadP2dP0 * q.dThetadt); 351 | Matrix3 dD3dP0Pseudo = -d * l * (q.d2ThetadP3dP0 * q.dThetadt); 352 | 353 | Matrix3 dD0dP1Pseudo = -d * l * (q.d2ThetadP0dP1 * q.dThetadt); 354 | Matrix3 dD1dP1Pseudo = -d * l * (q.d2ThetadP1dP1 * q.dThetadt); 355 | Matrix3 dD2dP1Pseudo = -d * l * (q.d2ThetadP2dP1 * q.dThetadt); 356 | Matrix3 dD3dP1Pseudo = -d * l * (q.d2ThetadP3dP1 * q.dThetadt); 357 | 358 | Matrix3 dD0dP2Pseudo = -d * l * (q.d2ThetadP0dP2 * q.dThetadt); 359 | Matrix3 dD1dP2Pseudo = -d * l * (q.d2ThetadP1dP2 * q.dThetadt); 360 | Matrix3 dD2dP2Pseudo = -d * l * (q.d2ThetadP2dP2 * q.dThetadt); 361 | Matrix3 dD3dP2Pseudo = -d * l * (q.d2ThetadP3dP2 * q.dThetadt); 362 | 363 | Matrix3 dD0dP3Pseudo = -d * l * (q.d2ThetadP0dP3 * q.dThetadt); 364 | Matrix3 dD1dP3Pseudo = -d * l * (q.d2ThetadP1dP3 * q.dThetadt); 365 | Matrix3 dD2dP3Pseudo = -d * l * (q.d2ThetadP2dP3 * q.dThetadt); 366 | Matrix3 dD3dP3Pseudo = -d * l * (q.d2ThetadP3dP3 * q.dThetadt); 367 | 368 | for (int i = 0; i < 3; ++i) 369 | { 370 | for (int j = 0; j < 3; ++j) 371 | { 372 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[0] + j) += dD0dP0Pseudo(i, j); 373 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[1] + j) += dD0dP1Pseudo(i, j); 374 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[2] + j) += dD0dP2Pseudo(i, j); 375 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[0] + i, 3 * m_inds[3] + j) += dD0dP3Pseudo(i, j); 376 | 377 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[0] + j) += dD1dP0Pseudo(i, j); 378 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[1] + j) += dD1dP1Pseudo(i, j); 379 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[2] + j) += dD1dP2Pseudo(i, j); 380 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[1] + i, 3 * m_inds[3] + j) += dD1dP3Pseudo(i, j); 381 | 382 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[0] + j) += dD2dP0Pseudo(i, j); 383 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[1] + j) += dD2dP1Pseudo(i, j); 384 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[2] + j) += dD2dP2Pseudo(i, j); 385 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[2] + i, 3 * m_inds[3] + j) += dD2dP3Pseudo(i, j); 386 | 387 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[3] + i, 3 * m_inds[0] + j) += dD3dP0Pseudo(i, j); 388 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[3] + i, 3 * m_inds[1] + j) += dD3dP1Pseudo(i, j); 389 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[3] + i, 3 * m_inds[2] + j) += dD3dP2Pseudo(i, j); 390 | dampingPseudoXDerivatives.coeffRef(3 * m_inds[3] + i, 3 * m_inds[3] + j) += dD3dP3Pseudo(i, j); 391 | } 392 | } 393 | 394 | } 395 | 396 | template class BendCondition; 397 | template class BendCondition; 398 | -------------------------------------------------------------------------------- /src/test/StretchConditionTest.cpp: -------------------------------------------------------------------------------- 1 | #include "CppUnitTest.h" 2 | 3 | #include "test\EqualityTests.h" 4 | 5 | #include "simLib\StretchCondition.h" 6 | 7 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 8 | using namespace ClothSim; 9 | 10 | namespace UnitTest1 11 | { 12 | TEST_CLASS(StretchConditionTest) 13 | { 14 | public: 15 | 16 | void stretchTriangleQuantitiesForDerivative(Eigen::VectorXd x, Eigen::VectorXd uv, Eigen::VectorXd v, double bu, double bv, int idx, double dx, StretchCondition::TriangleQuantities &qPlus, StretchCondition::TriangleQuantities &qMinus) const 17 | { 18 | double component = x[idx]; 19 | x[idx] = component + dx; 20 | qPlus = StretchCondition::TriangleQuantities( 21 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), 22 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 23 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), 24 | bu, bv 25 | ); 26 | x[idx] = component - dx; 27 | qMinus = StretchCondition::TriangleQuantities( 28 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), 29 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 30 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), 31 | bu, bv 32 | ); 33 | x[idx] = component; 34 | } 35 | 36 | void checkStretchTriangleQuantities(const Eigen::VectorXd &x, const Eigen::VectorXd &uv, const Eigen::VectorXd &v, double bu, double bv, double dx, double tol) 37 | { 38 | StretchCondition::TriangleQuantities q( 39 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), 40 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 41 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), 42 | bu, bv 43 | ); 44 | 45 | Eigen::VectorXd xtest; 46 | xtest = x + v * dx; 47 | StretchCondition::TriangleQuantities qtPlus( 48 | xtest.segment<3>(3 * 0), xtest.segment<3>(3 * 1), xtest.segment<3>(3 * 2), 49 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 50 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), 51 | bu, bv 52 | ); 53 | xtest = x - v * dx; 54 | StretchCondition::TriangleQuantities qtMinus( 55 | xtest.segment<3>(3 * 0), xtest.segment<3>(3 * 1), xtest.segment<3>(3 * 2), 56 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 57 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), 58 | bu, bv 59 | ); 60 | 61 | double dC0dt = (qtPlus.C0 - qtMinus.C0) / (2 * dx); 62 | double dC1dt = (qtPlus.C1 - qtMinus.C1) / (2 * dx); 63 | Assert::AreEqual(dC0dt, q.dC0dt, tol); 64 | Assert::AreEqual(dC1dt, q.dC1dt, tol); 65 | 66 | 67 | StretchCondition::TriangleQuantities q0xPlus, q0xMinus, q0yPlus, q0yMinus, q0zPlus, q0zMinus; 68 | StretchCondition::TriangleQuantities q1xPlus, q1xMinus, q1yPlus, q1yMinus, q1zPlus, q1zMinus; 69 | StretchCondition::TriangleQuantities q2xPlus, q2xMinus, q2yPlus, q2yMinus, q2zPlus, q2zMinus; 70 | 71 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 0, dx, q0xPlus, q0xMinus); 72 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 1, dx, q0yPlus, q0yMinus); 73 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 2, dx, q0zPlus, q0zMinus); 74 | 75 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 3, dx, q1xPlus, q1xMinus); 76 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 4, dx, q1yPlus, q1yMinus); 77 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 5, dx, q1zPlus, q1zMinus); 78 | 79 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 6, dx, q2xPlus, q2xMinus); 80 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 7, dx, q2yPlus, q2yMinus); 81 | stretchTriangleQuantitiesForDerivative(x, uv, v, bu, bv, 8, dx, q2zPlus, q2zMinus); 82 | 83 | // test first wu and wv derivatives: 84 | Eigen::Matrix3d dwudP0; 85 | dwudP0.col(0) = (q0xPlus.wu - q0xMinus.wu) / (2 * dx); 86 | dwudP0.col(1) = (q0yPlus.wu - q0yMinus.wu) / (2 * dx); 87 | dwudP0.col(2) = (q0zPlus.wu - q0zMinus.wu) / (2 * dx); 88 | checkMatrixEquality(dwudP0, q.dwudP0, tol); 89 | 90 | Eigen::Matrix3d dwudP1; 91 | dwudP1.col(0) = (q1xPlus.wu - q1xMinus.wu) / (2 * dx); 92 | dwudP1.col(1) = (q1yPlus.wu - q1yMinus.wu) / (2 * dx); 93 | dwudP1.col(2) = (q1zPlus.wu - q1zMinus.wu) / (2 * dx); 94 | checkMatrixEquality(dwudP1, q.dwudP1, tol); 95 | 96 | Eigen::Matrix3d dwudP2; 97 | dwudP2.col(0) = (q2xPlus.wu - q2xMinus.wu) / (2 * dx); 98 | dwudP2.col(1) = (q2yPlus.wu - q2yMinus.wu) / (2 * dx); 99 | dwudP2.col(2) = (q2zPlus.wu - q2zMinus.wu) / (2 * dx); 100 | checkMatrixEquality(dwudP2, q.dwudP2, tol); 101 | 102 | Eigen::Matrix3d dwvdP0; 103 | dwvdP0.col(0) = (q0xPlus.wv - q0xMinus.wv) / (2 * dx); 104 | dwvdP0.col(1) = (q0yPlus.wv - q0yMinus.wv) / (2 * dx); 105 | dwvdP0.col(2) = (q0zPlus.wv - q0zMinus.wv) / (2 * dx); 106 | checkMatrixEquality(dwvdP0, q.dwvdP0, tol); 107 | 108 | Eigen::Matrix3d dwvdP1; 109 | dwvdP1.col(0) = (q1xPlus.wv - q1xMinus.wv) / (2 * dx); 110 | dwvdP1.col(1) = (q1yPlus.wv - q1yMinus.wv) / (2 * dx); 111 | dwvdP1.col(2) = (q1zPlus.wv - q1zMinus.wv) / (2 * dx); 112 | checkMatrixEquality(dwvdP1, q.dwvdP1, tol); 113 | 114 | Eigen::Matrix3d dwvdP2; 115 | dwvdP2.col(0) = (q2xPlus.wv - q2xMinus.wv) / (2 * dx); 116 | dwvdP2.col(1) = (q2yPlus.wv - q2yMinus.wv) / (2 * dx); 117 | dwvdP2.col(2) = (q2zPlus.wv - q2zMinus.wv) / (2 * dx); 118 | checkMatrixEquality(dwvdP2, q.dwvdP2, tol); 119 | 120 | // test first C0 and C1 derivatives: 121 | Eigen::Vector3d dC0dP0; 122 | dC0dP0[0] = (q0xPlus.C0 - q0xMinus.C0) / (2 * dx); 123 | dC0dP0[1] = (q0yPlus.C0 - q0yMinus.C0) / (2 * dx); 124 | dC0dP0[2] = (q0zPlus.C0 - q0zMinus.C0) / (2 * dx); 125 | checkVectorEquality(dC0dP0, q.dC0dP0, tol); 126 | 127 | Eigen::Vector3d dC0dP1; 128 | dC0dP1[0] = (q1xPlus.C0 - q1xMinus.C0) / (2 * dx); 129 | dC0dP1[1] = (q1yPlus.C0 - q1yMinus.C0) / (2 * dx); 130 | dC0dP1[2] = (q1zPlus.C0 - q1zMinus.C0) / (2 * dx); 131 | checkVectorEquality(dC0dP1, q.dC0dP1, tol); 132 | 133 | Eigen::Vector3d dC0dP2; 134 | dC0dP2[0] = (q2xPlus.C0 - q2xMinus.C0) / (2 * dx); 135 | dC0dP2[1] = (q2yPlus.C0 - q2yMinus.C0) / (2 * dx); 136 | dC0dP2[2] = (q2zPlus.C0 - q2zMinus.C0) / (2 * dx); 137 | checkVectorEquality(dC0dP2, q.dC0dP2, tol); 138 | 139 | 140 | Eigen::Vector3d dC1dP0; 141 | dC1dP0[0] = (q0xPlus.C1 - q0xMinus.C1) / (2 * dx); 142 | dC1dP0[1] = (q0yPlus.C1 - q0yMinus.C1) / (2 * dx); 143 | dC1dP0[2] = (q0zPlus.C1 - q0zMinus.C1) / (2 * dx); 144 | checkVectorEquality(dC1dP0, q.dC1dP0, tol); 145 | 146 | Eigen::Vector3d dC1dP1; 147 | dC1dP1[0] = (q1xPlus.C1 - q1xMinus.C1) / (2 * dx); 148 | dC1dP1[1] = (q1yPlus.C1 - q1yMinus.C1) / (2 * dx); 149 | dC1dP1[2] = (q1zPlus.C1 - q1zMinus.C1) / (2 * dx); 150 | checkVectorEquality(dC1dP1, q.dC1dP1, tol); 151 | 152 | Eigen::Vector3d dC1dP2; 153 | dC1dP2[0] = (q2xPlus.C1 - q2xMinus.C1) / (2 * dx); 154 | dC1dP2[1] = (q2yPlus.C1 - q2yMinus.C1) / (2 * dx); 155 | dC1dP2[2] = (q2zPlus.C1 - q2zMinus.C1) / (2 * dx); 156 | checkVectorEquality(dC1dP2, q.dC1dP2, tol); 157 | 158 | 159 | // test second C0 and C1 derivatives: 160 | Eigen::Matrix3d d2C0dP0dP0; 161 | d2C0dP0dP0.col(0) = (q0xPlus.dC0dP0 - q0xMinus.dC0dP0) / (2 * dx); 162 | d2C0dP0dP0.col(1) = (q0yPlus.dC0dP0 - q0yMinus.dC0dP0) / (2 * dx); 163 | d2C0dP0dP0.col(2) = (q0zPlus.dC0dP0 - q0zMinus.dC0dP0) / (2 * dx); 164 | checkMatrixEquality(d2C0dP0dP0, q.d2C0dP0dP0, tol); 165 | 166 | Eigen::Matrix3d d2C0dP0dP1; 167 | d2C0dP0dP1.col(0) = (q1xPlus.dC0dP0 - q1xMinus.dC0dP0) / (2 * dx); 168 | d2C0dP0dP1.col(1) = (q1yPlus.dC0dP0 - q1yMinus.dC0dP0) / (2 * dx); 169 | d2C0dP0dP1.col(2) = (q1zPlus.dC0dP0 - q1zMinus.dC0dP0) / (2 * dx); 170 | checkMatrixEquality(d2C0dP0dP1, q.d2C0dP0dP1, tol); 171 | 172 | Eigen::Matrix3d d2C0dP0dP2; 173 | d2C0dP0dP2.col(0) = (q2xPlus.dC0dP0 - q2xMinus.dC0dP0) / (2 * dx); 174 | d2C0dP0dP2.col(1) = (q2yPlus.dC0dP0 - q2yMinus.dC0dP0) / (2 * dx); 175 | d2C0dP0dP2.col(2) = (q2zPlus.dC0dP0 - q2zMinus.dC0dP0) / (2 * dx); 176 | checkMatrixEquality(d2C0dP0dP2, q.d2C0dP0dP2, tol); 177 | 178 | 179 | Eigen::Matrix3d d2C0dP1dP0; 180 | d2C0dP1dP0.col(0) = (q0xPlus.dC0dP1 - q0xMinus.dC0dP1) / (2 * dx); 181 | d2C0dP1dP0.col(1) = (q0yPlus.dC0dP1 - q0yMinus.dC0dP1) / (2 * dx); 182 | d2C0dP1dP0.col(2) = (q0zPlus.dC0dP1 - q0zMinus.dC0dP1) / (2 * dx); 183 | checkMatrixEquality(d2C0dP1dP0, q.d2C0dP1dP0, tol); 184 | 185 | Eigen::Matrix3d d2C0dP1dP1; 186 | d2C0dP1dP1.col(0) = (q1xPlus.dC0dP1 - q1xMinus.dC0dP1) / (2 * dx); 187 | d2C0dP1dP1.col(1) = (q1yPlus.dC0dP1 - q1yMinus.dC0dP1) / (2 * dx); 188 | d2C0dP1dP1.col(2) = (q1zPlus.dC0dP1 - q1zMinus.dC0dP1) / (2 * dx); 189 | checkMatrixEquality(d2C0dP1dP1, q.d2C0dP1dP1, tol); 190 | 191 | Eigen::Matrix3d d2C0dP1dP2; 192 | d2C0dP1dP2.col(0) = (q2xPlus.dC0dP1 - q2xMinus.dC0dP1) / (2 * dx); 193 | d2C0dP1dP2.col(1) = (q2yPlus.dC0dP1 - q2yMinus.dC0dP1) / (2 * dx); 194 | d2C0dP1dP2.col(2) = (q2zPlus.dC0dP1 - q2zMinus.dC0dP1) / (2 * dx); 195 | checkMatrixEquality(d2C0dP1dP2, q.d2C0dP1dP2, tol); 196 | 197 | 198 | Eigen::Matrix3d d2C0dP2dP0; 199 | d2C0dP2dP0.col(0) = (q0xPlus.dC0dP2 - q0xMinus.dC0dP2) / (2 * dx); 200 | d2C0dP2dP0.col(1) = (q0yPlus.dC0dP2 - q0yMinus.dC0dP2) / (2 * dx); 201 | d2C0dP2dP0.col(2) = (q0zPlus.dC0dP2 - q0zMinus.dC0dP2) / (2 * dx); 202 | checkMatrixEquality(d2C0dP2dP0, q.d2C0dP2dP0, tol); 203 | 204 | Eigen::Matrix3d d2C0dP2dP1; 205 | d2C0dP2dP1.col(0) = (q1xPlus.dC0dP2 - q1xMinus.dC0dP2) / (2 * dx); 206 | d2C0dP2dP1.col(1) = (q1yPlus.dC0dP2 - q1yMinus.dC0dP2) / (2 * dx); 207 | d2C0dP2dP1.col(2) = (q1zPlus.dC0dP2 - q1zMinus.dC0dP2) / (2 * dx); 208 | checkMatrixEquality(d2C0dP2dP1, q.d2C0dP2dP1, tol); 209 | 210 | Eigen::Matrix3d d2C0dP2dP2; 211 | d2C0dP2dP2.col(0) = (q2xPlus.dC0dP2 - q2xMinus.dC0dP2) / (2 * dx); 212 | d2C0dP2dP2.col(1) = (q2yPlus.dC0dP2 - q2yMinus.dC0dP2) / (2 * dx); 213 | d2C0dP2dP2.col(2) = (q2zPlus.dC0dP2 - q2zMinus.dC0dP2) / (2 * dx); 214 | checkMatrixEquality(d2C0dP2dP2, q.d2C0dP2dP2, tol); 215 | 216 | 217 | 218 | 219 | Eigen::Matrix3d d2C1dP0dP0; 220 | d2C1dP0dP0.col(0) = (q0xPlus.dC1dP0 - q0xMinus.dC1dP0) / (2 * dx); 221 | d2C1dP0dP0.col(1) = (q0yPlus.dC1dP0 - q0yMinus.dC1dP0) / (2 * dx); 222 | d2C1dP0dP0.col(2) = (q0zPlus.dC1dP0 - q0zMinus.dC1dP0) / (2 * dx); 223 | checkMatrixEquality(d2C1dP0dP0, q.d2C1dP0dP0, tol); 224 | 225 | Eigen::Matrix3d d2C1dP0dP1; 226 | d2C1dP0dP1.col(0) = (q1xPlus.dC1dP0 - q1xMinus.dC1dP0) / (2 * dx); 227 | d2C1dP0dP1.col(1) = (q1yPlus.dC1dP0 - q1yMinus.dC1dP0) / (2 * dx); 228 | d2C1dP0dP1.col(2) = (q1zPlus.dC1dP0 - q1zMinus.dC1dP0) / (2 * dx); 229 | checkMatrixEquality(d2C1dP0dP1, q.d2C1dP0dP1, tol); 230 | 231 | Eigen::Matrix3d d2C1dP0dP2; 232 | d2C1dP0dP2.col(0) = (q2xPlus.dC1dP0 - q2xMinus.dC1dP0) / (2 * dx); 233 | d2C1dP0dP2.col(1) = (q2yPlus.dC1dP0 - q2yMinus.dC1dP0) / (2 * dx); 234 | d2C1dP0dP2.col(2) = (q2zPlus.dC1dP0 - q2zMinus.dC1dP0) / (2 * dx); 235 | checkMatrixEquality(d2C1dP0dP2, q.d2C1dP0dP2, tol); 236 | 237 | 238 | Eigen::Matrix3d d2C1dP1dP0; 239 | d2C1dP1dP0.col(0) = (q0xPlus.dC1dP1 - q0xMinus.dC1dP1) / (2 * dx); 240 | d2C1dP1dP0.col(1) = (q0yPlus.dC1dP1 - q0yMinus.dC1dP1) / (2 * dx); 241 | d2C1dP1dP0.col(2) = (q0zPlus.dC1dP1 - q0zMinus.dC1dP1) / (2 * dx); 242 | checkMatrixEquality(d2C1dP1dP0, q.d2C1dP1dP0, tol); 243 | 244 | Eigen::Matrix3d d2C1dP1dP1; 245 | d2C1dP1dP1.col(0) = (q1xPlus.dC1dP1 - q1xMinus.dC1dP1) / (2 * dx); 246 | d2C1dP1dP1.col(1) = (q1yPlus.dC1dP1 - q1yMinus.dC1dP1) / (2 * dx); 247 | d2C1dP1dP1.col(2) = (q1zPlus.dC1dP1 - q1zMinus.dC1dP1) / (2 * dx); 248 | checkMatrixEquality(d2C1dP1dP1, q.d2C1dP1dP1, tol); 249 | 250 | Eigen::Matrix3d d2C1dP1dP2; 251 | d2C1dP1dP2.col(0) = (q2xPlus.dC1dP1 - q2xMinus.dC1dP1) / (2 * dx); 252 | d2C1dP1dP2.col(1) = (q2yPlus.dC1dP1 - q2yMinus.dC1dP1) / (2 * dx); 253 | d2C1dP1dP2.col(2) = (q2zPlus.dC1dP1 - q2zMinus.dC1dP1) / (2 * dx); 254 | checkMatrixEquality(d2C1dP1dP2, q.d2C1dP1dP2, tol); 255 | 256 | 257 | Eigen::Matrix3d d2C1dP2dP0; 258 | d2C1dP2dP0.col(0) = (q0xPlus.dC1dP2 - q0xMinus.dC1dP2) / (2 * dx); 259 | d2C1dP2dP0.col(1) = (q0yPlus.dC1dP2 - q0yMinus.dC1dP2) / (2 * dx); 260 | d2C1dP2dP0.col(2) = (q0zPlus.dC1dP2 - q0zMinus.dC1dP2) / (2 * dx); 261 | checkMatrixEquality(d2C1dP2dP0, q.d2C1dP2dP0, tol); 262 | 263 | Eigen::Matrix3d d2C1dP2dP1; 264 | d2C1dP2dP1.col(0) = (q1xPlus.dC1dP2 - q1xMinus.dC1dP2) / (2 * dx); 265 | d2C1dP2dP1.col(1) = (q1yPlus.dC1dP2 - q1yMinus.dC1dP2) / (2 * dx); 266 | d2C1dP2dP1.col(2) = (q1zPlus.dC1dP2 - q1zMinus.dC1dP2) / (2 * dx); 267 | checkMatrixEquality(d2C1dP2dP1, q.d2C1dP2dP1, tol); 268 | 269 | Eigen::Matrix3d d2C1dP2dP2; 270 | d2C1dP2dP2.col(0) = (q2xPlus.dC1dP2 - q2xMinus.dC1dP2) / (2 * dx); 271 | d2C1dP2dP2.col(1) = (q2yPlus.dC1dP2 - q2yMinus.dC1dP2) / (2 * dx); 272 | d2C1dP2dP2.col(2) = (q2zPlus.dC1dP2 - q2zMinus.dC1dP2) / (2 * dx); 273 | checkMatrixEquality(d2C1dP2dP2, q.d2C1dP2dP2, tol); 274 | 275 | 276 | } 277 | 278 | 279 | TEST_METHOD(TestStretchConditionTriangleQuantities) 280 | { 281 | Eigen::VectorXd uv(3 * 2); 282 | 283 | uv[2 * 0] = 1.0; 284 | uv[2 * 0 + 1] = 2.0; 285 | 286 | uv[2 * 1] = 6.0; 287 | uv[2 * 1 + 1] = 3.0; 288 | 289 | uv[2 * 2] = 2.0; 290 | uv[2 * 2 + 1] = 5.0; 291 | 292 | Eigen::Vector3d p0(1, 2, 3); 293 | Eigen::Vector3d wu(1.3, 0.2, -0.1); 294 | Eigen::Vector3d wv(-0.5, 1.1, 0.3); 295 | 296 | Eigen::VectorXd x(3 * 3); 297 | x.segment<3>(3 * 0) = p0 + wu * uv[2 * 0 + 0] + wv * uv[2 * 0 + 1]; 298 | x.segment<3>(3 * 1) = p0 + wu * uv[2 * 1 + 0] + wv * uv[2 * 1 + 1]; 299 | x.segment<3>(3 * 2) = p0 + wu * uv[2 * 2 + 0] + wv * uv[2 * 2 + 1]; 300 | 301 | Eigen::VectorXd v = Eigen::VectorXd::Random(3 * 3); 302 | 303 | double bu = 0.9; 304 | double bv = 1.1; 305 | 306 | StretchCondition::TriangleQuantities q( 307 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), 308 | uv.segment<2>(2 * 0), uv.segment<2>(2 * 1), uv.segment<2>(2 * 2), 309 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), 310 | bu, bv 311 | ); 312 | 313 | double dx = 0.0001; 314 | double tol = 1.e-5; 315 | 316 | // make sure we're calculating wu, wv and a as expected: 317 | checkVectorEquality(q.wu, wu, tol); 318 | checkVectorEquality(q.wv, wv, tol); 319 | Eigen::Vector3d uv0(uv[2 * 0], uv[2 * 0 + 1], 0); 320 | Eigen::Vector3d uv1(uv[2 * 1], uv[2 * 1 + 1], 0); 321 | Eigen::Vector3d uv2(uv[2 * 2], uv[2 * 2 + 1], 0); 322 | Assert::AreEqual(q.a, 0.5 * ((uv1 - uv0).cross(uv2 - uv0)).norm(), tol); 323 | 324 | checkStretchTriangleQuantities(x, uv, v, bu, bv, dx, tol); 325 | 326 | // test for 10 random configurations: 327 | for (int i = 0; i < 10; ++i) 328 | { 329 | // randomize the positions: 330 | x = Eigen::VectorXd::Random(3 * 3); 331 | uv = Eigen::VectorXd::Random(3 * 2); 332 | v = Eigen::VectorXd::Random(3 * 3); 333 | checkStretchTriangleQuantities(x, uv, v, bu, bv, dx, tol); 334 | } 335 | 336 | } 337 | 338 | 339 | TEST_METHOD(TestStretchCondition) 340 | { 341 | Eigen::VectorXd uv(3 * 2); 342 | 343 | uv[2 * 0] = 1.0; 344 | uv[2 * 0 + 1] = 2.0; 345 | 346 | uv[2 * 1] = 6.0; 347 | uv[2 * 1 + 1] = 3.0; 348 | 349 | uv[2 * 2] = 2.0; 350 | uv[2 * 2 + 1] = 5.0; 351 | 352 | Eigen::Vector3d p0(1, 2, 3); 353 | Eigen::Vector3d wu(1.3, 0.2, -0.1); 354 | Eigen::Vector3d wv(-0.5, 1.1, 0.3); 355 | 356 | Eigen::VectorXd x(3 * 3); 357 | x.segment<3>(3 * 0) = p0 + wu * uv[2 * 0 + 0] + wv * uv[2 * 0 + 1]; 358 | x.segment<3>(3 * 1) = p0 + wu * uv[2 * 1 + 0] + wv * uv[2 * 1 + 1]; 359 | x.segment<3>(3 * 2) = p0 + wu * uv[2 * 2 + 0] + wv * uv[2 * 2 + 1]; 360 | 361 | Eigen::VectorXd v(3 * 3); 362 | 363 | v[3 * 0] = 0.1; 364 | v[3 * 0 + 1] = 0.2; 365 | v[3 * 0 + 2] = 0.3; 366 | 367 | v[3 * 1] = -.1; 368 | v[3 * 1 + 1] = -1; 369 | v[3 * 1 + 2] = 0.4; 370 | 371 | v[3 * 2] = 0; 372 | v[3 * 2 + 1] = -0.2; 373 | v[3 * 2 + 2] = 0.1; 374 | 375 | double bu = 0.9; 376 | double bv = 1.1; 377 | 378 | Eigen::VectorXd f(3 * 3); 379 | f.setConstant(0); 380 | Eigen::VectorXd dampingForces(3 * 3); 381 | dampingForces.setConstant(0); 382 | StretchCondition sc(0, 1, 2, bu, bv); 383 | 384 | double k = 1.5; 385 | Eigen::SparseMatrix dfdx(3 * 3, 3 * 3); 386 | double d = 1.0; 387 | Eigen::SparseMatrix dampingPseudoDerivatives(3 * 3, 3 * 3); 388 | Eigen::SparseMatrix dfdv(3 * 3, 3 * 3); 389 | 390 | // compute forces analytically: 391 | sc.computeForces(x, uv, k, f, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 392 | 393 | // compare to numerically computed forces: 394 | double dx = 0.0001; 395 | double tol = 1.e-4; 396 | for (int i = 0; i < x.size(); ++i) 397 | { 398 | Assert::AreEqual(numericalForce(sc, uv, x, k, i, dx), f[i], tol); 399 | Assert::AreEqual(numericalDampingForce(sc, uv, x, v, d, i, dx), dampingForces[i], tol); 400 | } 401 | 402 | // check force derivatives numerically: 403 | for (int i = 0; i < x.size(); ++i) 404 | { 405 | Eigen::VectorXd fdN = dfdx.row(i); 406 | checkVectorEquality(fdN, numericalForceDerivative(sc, uv, x, v, k, d, i, dx), tol, true); 407 | fdN = dfdv.row(i); 408 | checkVectorEquality(fdN, numericalDampingForceDerivative(sc, uv, x, v, k, d, i, dx), tol, true); 409 | } 410 | 411 | // check the terms that are almost dfd/dx but not quite: 412 | checkPseudoDerivatives(dampingPseudoDerivatives, sc, uv, x, v, k, d, dx, tol); 413 | 414 | // test on 10 randomized configurations: 415 | for (size_t n = 0; n < 10; ++n) 416 | { 417 | // randomize positions: 418 | x = Eigen::VectorXd::Random(3 * 3); 419 | f.setConstant(0); 420 | for (int i = 0; i < dfdx.outerSize(); ++i) 421 | { 422 | for (Eigen::SparseMatrix::InnerIterator it(dfdx, i); it; ++it) 423 | { 424 | it.valueRef() = 0; 425 | } 426 | } 427 | dampingForces.setConstant(0); 428 | for (int i = 0; i < dampingPseudoDerivatives.outerSize(); ++i) 429 | { 430 | for (Eigen::SparseMatrix::InnerIterator it(dampingPseudoDerivatives, i); it; ++it) 431 | { 432 | it.valueRef() = 0; 433 | } 434 | } 435 | for (int i = 0; i < dfdv.outerSize(); ++i) 436 | { 437 | for (Eigen::SparseMatrix::InnerIterator it(dfdv, i); it; ++it) 438 | { 439 | it.valueRef() = 0; 440 | } 441 | } 442 | sc.computeForces(x, uv, k, f, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 443 | 444 | for (int i = 0; i < x.size(); ++i) 445 | { 446 | Assert::AreEqual(numericalForce(sc, uv, x, k, i, dx), f[i], tol); 447 | Assert::AreEqual(numericalDampingForce(sc, uv, x, v, d, i, dx), dampingForces[i], tol); 448 | } 449 | 450 | for (int i = 0; i < x.size(); ++i) 451 | { 452 | Eigen::VectorXd fdN = dfdx.row(i); 453 | checkVectorEquality(fdN, numericalForceDerivative(sc, uv, x, v, k, d, i, dx), tol, true); 454 | fdN = dfdv.row(i); 455 | checkVectorEquality(fdN, numericalDampingForceDerivative(sc, uv, x, v, k, d, i, dx), tol, true); 456 | } 457 | 458 | // check the terms that are almost dfd/dx but not quite: 459 | checkPseudoDerivatives(dampingPseudoDerivatives, sc, uv, x, v, k, d, dx, tol); 460 | } 461 | 462 | } 463 | 464 | }; 465 | } 466 | -------------------------------------------------------------------------------- /src/test/ClothMeshTest.cpp: -------------------------------------------------------------------------------- 1 | #include "CppUnitTest.h" 2 | 3 | #include "test\EqualityTests.h" 4 | 5 | #include "simLib\ClothMesh.h" 6 | #include "simLib\DirectSolver.h" 7 | 8 | #include 9 | 10 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 11 | using namespace ClothSim; 12 | 13 | namespace UnitTest1 14 | { 15 | TEST_CLASS(ClothMeshTest) 16 | { 17 | public: 18 | 19 | TEST_METHOD(TestSmallClothMesh) 20 | { 21 | Eigen::VectorXd v(4 * 3); 22 | 23 | v[3 * 0] = 0; 24 | v[3 * 0 + 1] = 0; 25 | v[3 * 0 + 2] = -0.4; 26 | 27 | v[3 * 1] = 0; 28 | v[3 * 1 + 1] = 0; 29 | v[3 * 1 + 2] = 0.1; 30 | 31 | v[3 * 2] = 0; 32 | v[3 * 2 + 1] = 0; 33 | v[3 * 2 + 2] = 0.1; 34 | 35 | v[3 * 3] = 1.0; 36 | v[3 * 3 + 1] = 0; 37 | v[3 * 3 + 2] = -0.2; 38 | 39 | Eigen::VectorXd x(4 * 3); 40 | 41 | x[3 * 0] = -2.0; 42 | x[3 * 0 + 1] = 0; 43 | x[3 * 0 + 2] = 0.4; 44 | 45 | x[3 * 1] = 0; 46 | x[3 * 1 + 1] = 1.0; 47 | x[3 * 1 + 2] = 0; 48 | 49 | x[3 * 2] = 0; 50 | x[3 * 2 + 1] = -1.0; 51 | x[3 * 2 + 2] = 0; 52 | 53 | x[3 * 3] = 1.0; 54 | x[3 * 3 + 1] = 0; 55 | x[3 * 3 + 2] = 0.2; 56 | 57 | Eigen::VectorXd uv(4 * 2); 58 | uv[2 * 0 + 0] = -2.0; 59 | uv[2 * 0 + 1] = 0.0; 60 | 61 | uv[2 * 1 + 0] = 0.0; 62 | uv[2 * 1 + 1] = 1.0; 63 | 64 | uv[2 * 2 + 0] = 0.0; 65 | uv[2 * 2 + 1] = -1.0; 66 | 67 | uv[2 * 3 + 0] = 1.0; 68 | uv[2 * 3 + 1] = 0.0; 69 | 70 | std::vector triangleIndices; 71 | triangleIndices.push_back(0); 72 | triangleIndices.push_back(1); 73 | triangleIndices.push_back(2); 74 | 75 | triangleIndices.push_back(2); 76 | triangleIndices.push_back(1); 77 | triangleIndices.push_back(3); 78 | 79 | ClothMesh m( 80 | x, v, uv, triangleIndices, 81 | 1.0, 1.0, 1.0, 82 | 1.0, 1.0, 1.0, 83 | 1.0 84 | ); 85 | 86 | Assert::IsTrue(m.x() == x); 87 | Assert::IsTrue(m.v() == v); 88 | 89 | Assert::AreEqual((int)m.m().size(), 4); 90 | 91 | Assert::AreEqual(m.m()[0], 2.0 / 3.0); 92 | Assert::AreEqual(m.m()[1], 1.0); 93 | Assert::AreEqual(m.m()[2], 1.0); 94 | Assert::AreEqual(m.m()[3], 1.0 / 3.0); 95 | 96 | Assert::AreEqual((int)m.bendConditions().size(), 1); 97 | Assert::AreEqual((int)m.shearConditions().size(), 2); 98 | Assert::AreEqual((int)m.stretchConditions().size(), 2); 99 | 100 | Assert::AreEqual(m.shearConditions()[0].inds()[0], 0); 101 | Assert::AreEqual(m.shearConditions()[0].inds()[1], 1); 102 | Assert::AreEqual(m.shearConditions()[0].inds()[2], 2); 103 | 104 | Assert::AreEqual(m.shearConditions()[1].inds()[0], 2); 105 | Assert::AreEqual(m.shearConditions()[1].inds()[1], 1); 106 | Assert::AreEqual(m.shearConditions()[1].inds()[2], 3); 107 | 108 | Assert::AreEqual(m.stretchConditions()[0].inds()[0], 0); 109 | Assert::AreEqual(m.stretchConditions()[0].inds()[1], 1); 110 | Assert::AreEqual(m.stretchConditions()[0].inds()[2], 2); 111 | 112 | Assert::AreEqual(m.stretchConditions()[1].inds()[0], 2); 113 | Assert::AreEqual(m.stretchConditions()[1].inds()[1], 1); 114 | Assert::AreEqual(m.stretchConditions()[1].inds()[2], 3); 115 | 116 | Assert::AreEqual(m.bendConditions()[0].inds()[0], 0); 117 | Assert::AreEqual(m.bendConditions()[0].inds()[1], 1); 118 | Assert::AreEqual(m.bendConditions()[0].inds()[2], 2); 119 | Assert::AreEqual(m.bendConditions()[0].inds()[3], 3); 120 | 121 | } 122 | 123 | TEST_METHOD(TestLargerClothMesh) 124 | { 125 | Eigen::VectorXd v(16 * 3); 126 | Eigen::VectorXd x(16 * 3); 127 | Eigen::VectorXd uv(16 * 2); 128 | 129 | for (int i = 0; i <= 3; ++i) 130 | { 131 | for (int j = 0; j <= 3; ++j) 132 | { 133 | x[3 * i + 0] = uv[2 * i + 0] = i; 134 | x[3 * i + 1] = uv[2 * i + 1] = j; 135 | x[3 * i + 2] = 0; 136 | 137 | v[3 * i + 0] = v[3 * i + 1] = v[3 * i + 2] = 0; 138 | } 139 | } 140 | 141 | std::vector triangleInds; 142 | int shuffledI[] = { 2, 0, 1 }; 143 | int shuffledJ[] = { 1, 2, 0 }; 144 | for (int i = 0; i < 3; ++i) 145 | { 146 | int si = shuffledI[i]; 147 | for (int j = 0; j < 3; ++j) 148 | { 149 | int base = shuffledI[i] + 4 * shuffledJ[j]; 150 | 151 | triangleInds.push_back(base + 0); 152 | triangleInds.push_back(base + 1); 153 | triangleInds.push_back(base + 4); 154 | 155 | triangleInds.push_back(base + 1); 156 | triangleInds.push_back(base + 5); 157 | triangleInds.push_back(base + 4); 158 | 159 | } 160 | } 161 | 162 | ClothMesh m( 163 | x, v, uv, triangleInds, 164 | 1.0, 1.0, 1.0, 165 | 1.0, 1.0, 1.0, 166 | 1.0 167 | ); 168 | 169 | Assert::AreEqual((int)m.stretchConditions().size(), 18); 170 | Assert::AreEqual((int)m.shearConditions().size(), 18); 171 | Assert::AreEqual((int)m.bendConditions().size(), 9 + 6 + 6); 172 | 173 | for (size_t i = 0; i < m.stretchConditions().size(); ++i) 174 | { 175 | Assert::AreEqual(m.stretchConditions()[i].inds()[0], triangleInds[3 * i + 0]); 176 | Assert::AreEqual(m.stretchConditions()[i].inds()[1], triangleInds[3 * i + 1]); 177 | Assert::AreEqual(m.stretchConditions()[i].inds()[2], triangleInds[3 * i + 2]); 178 | 179 | Assert::AreEqual(m.shearConditions()[i].inds()[0], triangleInds[3 * i + 0]); 180 | Assert::AreEqual(m.shearConditions()[i].inds()[1], triangleInds[3 * i + 1]); 181 | Assert::AreEqual(m.shearConditions()[i].inds()[2], triangleInds[3 * i + 2]); 182 | } 183 | 184 | std::set bendConditionsFound; 185 | 186 | // check all the shared edges and make sure there's a corresponding bend condition: 187 | for (size_t i = 0; i < triangleInds.size(); i += 3) 188 | { 189 | // find triangles sharing an edge with triangle i: 190 | for (size_t j = i + 3; j < triangleInds.size(); j += 3) 191 | { 192 | int iverts[] = { triangleInds[i + 0], triangleInds[i + 1], triangleInds[i + 2] }; 193 | int jverts[] = { triangleInds[j + 0], triangleInds[j + 1], triangleInds[j + 2] }; 194 | 195 | std::vector sharedVerts; 196 | if (triangleInds[i + 0] == triangleInds[j + 0]) sharedVerts.push_back(triangleInds[i + 0]); 197 | if (triangleInds[i + 1] == triangleInds[j + 0]) sharedVerts.push_back(triangleInds[i + 1]); 198 | if (triangleInds[i + 2] == triangleInds[j + 0]) sharedVerts.push_back(triangleInds[i + 2]); 199 | if (triangleInds[i + 0] == triangleInds[j + 1]) sharedVerts.push_back(triangleInds[i + 0]); 200 | if (triangleInds[i + 1] == triangleInds[j + 1]) sharedVerts.push_back(triangleInds[i + 1]); 201 | if (triangleInds[i + 2] == triangleInds[j + 1]) sharedVerts.push_back(triangleInds[i + 2]); 202 | if (triangleInds[i + 0] == triangleInds[j + 2]) sharedVerts.push_back(triangleInds[i + 0]); 203 | if (triangleInds[i + 1] == triangleInds[j + 2]) sharedVerts.push_back(triangleInds[i + 1]); 204 | if (triangleInds[i + 2] == triangleInds[j + 2]) sharedVerts.push_back(triangleInds[i + 2]); 205 | 206 | // found a shared edge! 207 | if (sharedVerts.size() == 2) 208 | { 209 | // search for it in the bend conditions: 210 | bool found(false); 211 | for (size_t k = 0; k < m.bendConditions().size(); ++k) 212 | { 213 | if (m.bendConditions()[k].inds()[1] == sharedVerts[0] && 214 | m.bendConditions()[k].inds()[2] == sharedVerts[1]) 215 | { 216 | found = true; 217 | } 218 | else if (m.bendConditions()[k].inds()[2] == sharedVerts[0] && 219 | m.bendConditions()[k].inds()[1] == sharedVerts[1]) 220 | { 221 | found = true; 222 | } 223 | 224 | if (found) 225 | { 226 | bendConditionsFound.insert((int)k); 227 | std::set trivertset; 228 | trivertset.insert(triangleInds[i + 0]); 229 | trivertset.insert(triangleInds[i + 1]); 230 | trivertset.insert(triangleInds[i + 2]); 231 | trivertset.insert(triangleInds[j + 0]); 232 | trivertset.insert(triangleInds[j + 1]); 233 | trivertset.insert(triangleInds[j + 2]); 234 | 235 | std::set bcvertset; 236 | bcvertset.insert(m.bendConditions()[k].inds()[0]); 237 | bcvertset.insert(m.bendConditions()[k].inds()[1]); 238 | bcvertset.insert(m.bendConditions()[k].inds()[2]); 239 | bcvertset.insert(m.bendConditions()[k].inds()[3]); 240 | 241 | Assert::IsTrue( trivertset == bcvertset ); 242 | 243 | break; 244 | } 245 | } 246 | Assert::IsTrue( found ); 247 | } 248 | } 249 | } 250 | 251 | Assert::AreEqual(bendConditionsFound.size(), m.bendConditions().size()); 252 | } 253 | 254 | double numericalForce(const ClothMesh &m, const Eigen::VectorXd x, const Eigen::VectorXd &uv, int index, double dx) 255 | { 256 | Eigen::VectorXd xTest(x.size()); 257 | xTest.setConstant(0); 258 | xTest += x; 259 | 260 | xTest[index] = x[index] - dx; 261 | double eMinus = m.energy(xTest, uv); 262 | 263 | xTest[index] = x[index] + dx; 264 | double ePlus = m.energy(xTest, uv); 265 | 266 | // f = -dE/dx 267 | return -(ePlus - eMinus) / (2 * dx); 268 | } 269 | 270 | Eigen::VectorXd numericalForceDerivative(const ClothMesh &m, const Eigen::VectorXd &uv, Eigen::VectorXd &x, Eigen::VectorXd &v, int i, double dx) 271 | { 272 | Eigen::SparseMatrix dfdx((int)x.size(), (int)x.size()); 273 | Eigen::SparseMatrix dddx((int)x.size(), (int)x.size()); 274 | Eigen::SparseMatrix dddv((int)x.size(), (int)x.size()); 275 | Eigen::VectorXd dampingForces((int)x.size()); 276 | Eigen::SparseMatrix dampingPseudoDerivatives((int)x.size(), (int)x.size()); 277 | 278 | double xOrig = x[i]; 279 | 280 | x[i] = xOrig + dx; 281 | 282 | Eigen::VectorXd fPlus(x.size()); 283 | fPlus.setConstant(0); 284 | m.forcesAndDerivatives(x, uv, v, fPlus, dampingForces, dfdx, dddx, dddv); 285 | 286 | x[i] = xOrig - dx; 287 | 288 | Eigen::VectorXd fMinus(x.size()); 289 | fMinus.setConstant(0); 290 | m.forcesAndDerivatives(x, uv, v, fMinus, dampingForces, dfdx, dddx, dddv); 291 | 292 | x[i] = xOrig; 293 | 294 | // df/dx 295 | return (fPlus - fMinus) / (2 * dx); 296 | } 297 | 298 | Eigen::VectorXd numericalDampingDerivative(const ClothMesh &m, const Eigen::VectorXd &uv, Eigen::VectorXd &x, Eigen::VectorXd &v, int i, double dx) 299 | { 300 | Eigen::SparseMatrix dfdx((int)x.size(), (int)x.size()); 301 | Eigen::SparseMatrix dddx((int)x.size(), (int)x.size()); 302 | Eigen::SparseMatrix dddv((int)x.size(), (int)x.size()); 303 | Eigen::VectorXd forces((int)x.size()); 304 | Eigen::SparseMatrix dampingPseudoDerivatives((int)x.size(), (int)x.size()); 305 | 306 | double vOrig = v[i]; 307 | 308 | v[i] = vOrig + dx; 309 | 310 | Eigen::VectorXd fPlus(v.size()); 311 | fPlus.setConstant(0); 312 | m.forcesAndDerivatives(x, uv, v, forces, fPlus, dfdx, dddx, dddv); 313 | 314 | v[i] = vOrig - dx; 315 | 316 | Eigen::VectorXd fMinus(x.size()); 317 | fMinus.setConstant(0); 318 | m.forcesAndDerivatives(x, uv, v, forces, fMinus, dfdx, dddx, dddv); 319 | 320 | v[i] = vOrig; 321 | 322 | // df/dx 323 | return (fPlus - fMinus) / (2 * dx); 324 | } 325 | 326 | TEST_METHOD(TestClothMeshForcesAndDerivatives) 327 | { 328 | int nx = 4; 329 | int ny = 4; 330 | 331 | Eigen::VectorXd v((nx + 1) * (ny + 1) * 3); 332 | Eigen::VectorXd x((nx + 1) * (ny + 1) * 3); 333 | Eigen::VectorXd uv((nx + 1) * (ny + 1) * 2); 334 | 335 | for (int i = 0; i <= nx; ++i) 336 | { 337 | for (int j = 0; j <= nx; ++j) 338 | { 339 | int base = i + (nx + 1) * j; 340 | x[3 * base + 0] = uv[2 * base + 0] = (float)i / nx; 341 | x[3 * base + 1] = uv[2 * base + 1] = (float)j / ny; 342 | x[3 * base + 2] = 0.1f * i * j / (nx * ny); 343 | 344 | v[3 * base + 0] = v[3 * base + 1] = 0; 345 | v[3 * base + 2] = -x[3 * base + 2]; 346 | } 347 | } 348 | 349 | std::vector triangleInds; 350 | for (int i = 0; i < nx; ++i) 351 | { 352 | for (int j = 0; j < ny; ++j) 353 | { 354 | int base = i + (nx + 1) * j; 355 | 356 | triangleInds.push_back(base + 0); 357 | triangleInds.push_back(base + 1); 358 | triangleInds.push_back(base + (nx + 1)); 359 | 360 | triangleInds.push_back(base + 1); 361 | triangleInds.push_back(base + (nx + 2)); 362 | triangleInds.push_back(base + (nx + 1)); 363 | 364 | } 365 | } 366 | 367 | double kBend(1.5), kStretch(200.0), kShear(100.0); 368 | double dBend(2.0), dStretch(20.0), dShear(10.0); 369 | 370 | ClothMesh m( 371 | x, v, uv, triangleInds, 372 | kBend, kStretch, kShear, 373 | dBend, dStretch, dShear, 374 | 1.0 375 | ); 376 | 377 | Eigen::VectorXd forces((nx + 1) * (ny + 1) * 3); 378 | Eigen::VectorXd dampingforces((nx + 1) * (ny + 1) * 3); 379 | Eigen::SparseMatrix dfdx((int)x.size(), (int)x.size()); 380 | Eigen::SparseMatrix dddx((int)x.size(), (int)x.size()); 381 | Eigen::SparseMatrix dddv((int)x.size(), (int)x.size()); 382 | m.forcesAndDerivatives(x, uv, v, forces, dampingforces, dfdx, dddx, dddv); 383 | 384 | double dx = 0.00001; 385 | double tol = 1.e-4; 386 | 387 | // check forces are correct: 388 | for( int i=0; i < forces.size(); ++i ) 389 | { 390 | Assert::AreEqual( forces[i], numericalForce( m, x, uv, i, dx ), tol ); 391 | } 392 | 393 | // check dfdx is correct: 394 | for (int i = 0; i < forces.size(); ++i) 395 | { 396 | Eigen::VectorXd fdN = dfdx.row(i); 397 | checkVectorEquality(fdN, numericalForceDerivative(m, uv, x, v, i, dx), tol, true); 398 | } 399 | 400 | // check damping forces are correct: 401 | 402 | // work out time derivative of c 403 | Eigen::VectorXd xTest; 404 | xTest = x + dx * v; 405 | Eigen::VectorXd cPlus; 406 | m.C(x + dx * v, uv, cPlus); 407 | Eigen::VectorXd cMinus; 408 | m.C(x - dx * v, uv, cMinus); 409 | 410 | Eigen::VectorXd dCdt = (cPlus - cMinus) / (2 * dx); 411 | 412 | Eigen::VectorXd dCdxi; 413 | for (int i = 0; i < dampingforces.size(); ++i) 414 | { 415 | 416 | // work out dCdxi: 417 | double xOrig = x[i]; 418 | x[i] = xOrig + dx; 419 | m.C(x, uv, cPlus); 420 | 421 | x[i] = xOrig - dx; 422 | m.C(x, uv, cMinus); 423 | 424 | x[i] = xOrig; 425 | dCdxi = (cPlus - cMinus) / ( 2 * dx ); 426 | 427 | double dampingForce(0); 428 | size_t n = 0; 429 | for (size_t j = 0; j < m.bendConditions().size(); ++j, ++n) 430 | { 431 | dampingForce -= dBend * dCdxi[n] * dCdt[n]; 432 | } 433 | for (size_t j = 0; j < m.shearConditions().size(); ++j, ++n) 434 | { 435 | dampingForce -= dShear * dCdxi[n] * dCdt[n]; 436 | } 437 | for (size_t j = 0; j < m.stretchConditions().size(); ++j, n += 2) 438 | { 439 | dampingForce -= dStretch * dCdxi[n] * dCdt[n]; 440 | dampingForce -= dStretch * dCdxi[n+1] * dCdt[n+1]; 441 | } 442 | 443 | Assert::AreEqual(dampingforces[i], dampingForce, tol); 444 | } 445 | 446 | // check dddv is correct: 447 | for (int i = 0; i < forces.size(); ++i) 448 | { 449 | Eigen::VectorXd fdN = dddv.row(i); 450 | checkVectorEquality(fdN, numericalDampingDerivative(m, uv, x, v, i, dx), tol, true); 451 | } 452 | 453 | 454 | 455 | 456 | // \todo: check dddx works... even though it doesn't really 457 | 458 | 459 | 460 | 461 | 462 | // check doubling up the arguments adds the results: 463 | Eigen::VectorXd f2((nx + 1) * (ny + 1) * 3); 464 | Eigen::SparseMatrix dfdx2((int)x.size(), (int)x.size()); 465 | Eigen::SparseMatrix dfdv((int)x.size(), (int)x.size()); 466 | 467 | m.forcesAndDerivatives(x, uv, v, f2, f2, dfdx2, dfdx2, dfdv); 468 | checkVectorEquality(f2, forces + dampingforces, 1.e-6, true); 469 | 470 | for (int i = 0; i < dfdx2.outerSize(); ++i) 471 | { 472 | for (Eigen::SparseMatrix::InnerIterator it(dfdx2, i); it; ++it) 473 | { 474 | Assert::AreEqual( it.value(), dfdx.coeff(it.row(),it.col()) + dddx.coeff( it.row(), it.col() ), 1.e-6 ); 475 | } 476 | } 477 | 478 | for (int i = 0; i < dfdv.outerSize(); ++i) 479 | { 480 | for (Eigen::SparseMatrix::InnerIterator it(dfdv, i); it; ++it) 481 | { 482 | Assert::AreEqual(it.value(), dddv.coeff(it.row(), it.col()), 1.e-6); 483 | } 484 | } 485 | 486 | } 487 | 488 | TEST_METHOD(TestClothMeshImplicitUpdate) 489 | { 490 | int nx = 4; 491 | int ny = 4; 492 | 493 | Eigen::VectorXd v((nx + 1) * (ny + 1) * 3); 494 | Eigen::VectorXd x((nx + 1) * (ny + 1) * 3); 495 | Eigen::VectorXd uv((nx + 1) * (ny + 1) * 2); 496 | 497 | for (int i = 0; i <= nx; ++i) 498 | { 499 | for (int j = 0; j <= nx; ++j) 500 | { 501 | int base = i + (nx + 1) * j; 502 | x[3 * base + 0] = uv[2 * base + 0] = (float)i / nx; 503 | x[3 * base + 1] = uv[2 * base + 1] = (float)j / ny; 504 | x[3 * base + 2] = 0.1f * i * j / (nx * ny); 505 | 506 | v[3 * base + 0] = v[3 * base + 1] = 0; 507 | v[3 * base + 2] = -x[3 * base + 2]; 508 | } 509 | } 510 | 511 | std::vector triangleInds; 512 | for (int i = 0; i < nx; ++i) 513 | { 514 | for (int j = 0; j < ny; ++j) 515 | { 516 | int base = i + (nx + 1) * j; 517 | 518 | triangleInds.push_back(base + 0); 519 | triangleInds.push_back(base + 1); 520 | triangleInds.push_back(base + (nx + 1)); 521 | 522 | triangleInds.push_back(base + 1); 523 | triangleInds.push_back(base + (nx + 2)); 524 | triangleInds.push_back(base + (nx + 1)); 525 | 526 | } 527 | } 528 | 529 | double kBend(1.5), kStretch(200.0), kShear(100.0); 530 | double dBend(2.0), dStretch(20.0), dShear(10.0); 531 | 532 | ClothMesh m( 533 | x, v, uv, triangleInds, 534 | kBend, kStretch, kShear, 535 | dBend, dStretch, dShear, 536 | 1.0 537 | ); 538 | 539 | 540 | 541 | Eigen::VectorXd forces((nx + 1) * (ny + 1) * 3); 542 | Eigen::SparseMatrix dfdx((int)x.size(), (int)x.size()); 543 | Eigen::SparseMatrix dfdv((int)x.size(), (int)x.size()); 544 | 545 | Eigen::SparseMatrix A((int)x.size(), (int)x.size()); 546 | Eigen::VectorXd rhs((int)x.size()); 547 | 548 | for (int i = 0; i < 20; ++i) 549 | { 550 | m.forcesAndDerivatives(m.x(), uv, m.v(), forces, forces, dfdx, dfdx, dfdv); 551 | 552 | // assemble equation: 553 | double dt(0.1); 554 | 555 | m.assembleImplicitUpdateEquations(dt, forces, m.v(), dfdx, dfdv, A, rhs); 556 | 557 | // solve to find changes in velocity and position: 558 | Eigen::SparseLU< Eigen::SparseMatrix > s; 559 | s.analyzePattern(A); 560 | s.factorize(A); 561 | 562 | Eigen::VectorXd dv = s.solve(rhs); 563 | Eigen::VectorXd dx = (m.v() + dv) * dt; 564 | 565 | // now check the following: 566 | 567 | // df = dfdx * dx + dfdv * dv 568 | // fNext = forces + df 569 | // aNext = M^-1 fNext 570 | // dv = aNext * dt 571 | 572 | Eigen::VectorXd df = dfdx * dx + dfdv * dv; 573 | Eigen::VectorXd fNext = forces + df; 574 | Eigen::VectorXd aNext(fNext.size()); 575 | for (int i = 0; i < m.m().size(); ++i) 576 | { 577 | aNext.segment<3>(3 * i) = fNext.segment<3>(3 * i) / m.m()[i]; 578 | } 579 | 580 | checkVectorEquality(dv, aNext * dt, 1.e-6, true); 581 | 582 | Eigen::VectorXd vOrig = m.v(); 583 | Eigen::VectorXd xOrig = m.x(); 584 | 585 | DirectSolver solver; 586 | std::vector< ForceField* > forceFields; 587 | m.advance(forceFields, dt, solver); 588 | 589 | checkVectorEquality(m.v() - vOrig, dv, 1.e-6, true); 590 | checkVectorEquality(m.x() - xOrig, dx, 1.e-6, true); 591 | } 592 | 593 | } 594 | }; 595 | } 596 | -------------------------------------------------------------------------------- /src/test/BendConditionTest.cpp: -------------------------------------------------------------------------------- 1 | #include "CppUnitTest.h" 2 | 3 | #include "test\EqualityTests.h" 4 | 5 | #include "simLib\BendCondition.h" 6 | 7 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 8 | using namespace ClothSim; 9 | 10 | namespace UnitTest1 11 | { 12 | TEST_CLASS(BendConditionTest) 13 | { 14 | public: 15 | 16 | void bendTriangleQuantitiesForDerivative(Eigen::VectorXd x, Eigen::VectorXd v, int idx, double dx, BendCondition::TriangleQuantities &qPlus, BendCondition::TriangleQuantities &qMinus) const 17 | { 18 | double component = x[idx]; 19 | x[idx] = component + dx; 20 | qPlus = BendCondition::TriangleQuantities( 21 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), x.segment<3>(3 * 3), 22 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), v.segment<3>(3 * 3) 23 | ); 24 | x[idx] = component - dx; 25 | qMinus = BendCondition::TriangleQuantities( 26 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), x.segment<3>(3 * 3), 27 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), v.segment<3>(3 * 3) 28 | ); 29 | x[idx] = component; 30 | } 31 | 32 | void checkBendTriangleQuantities(const Eigen::VectorXd &x, const Eigen::VectorXd &v, double dx, double tol) 33 | { 34 | BendCondition::TriangleQuantities q( 35 | x.segment<3>(3 * 0), x.segment<3>(3 * 1), x.segment<3>(3 * 2), x.segment<3>(3 * 3), 36 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), v.segment<3>(3 * 3) 37 | ); 38 | 39 | Eigen::VectorXd xtest; 40 | xtest = x + v * dx; 41 | BendCondition::TriangleQuantities qtPlus( 42 | xtest.segment<3>(3 * 0), xtest.segment<3>(3 * 1), xtest.segment<3>(3 * 2), xtest.segment<3>(3 * 3), 43 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), v.segment<3>(3 * 3) 44 | ); 45 | xtest = x - v * dx; 46 | BendCondition::TriangleQuantities qtMinus( 47 | xtest.segment<3>(3 * 0), xtest.segment<3>(3 * 1), xtest.segment<3>(3 * 2), xtest.segment<3>(3 * 3), 48 | v.segment<3>(3 * 0), v.segment<3>(3 * 1), v.segment<3>(3 * 2), v.segment<3>(3 * 3) 49 | ); 50 | 51 | double dThetadt = (qtPlus.theta - qtMinus.theta) / (2 * dx); 52 | Assert::AreEqual(dThetadt, q.dThetadt, tol); 53 | 54 | BendCondition::TriangleQuantities q0xPlus, q0xMinus, q0yPlus, q0yMinus, q0zPlus, q0zMinus; 55 | BendCondition::TriangleQuantities q1xPlus, q1xMinus, q1yPlus, q1yMinus, q1zPlus, q1zMinus; 56 | BendCondition::TriangleQuantities q2xPlus, q2xMinus, q2yPlus, q2yMinus, q2zPlus, q2zMinus; 57 | BendCondition::TriangleQuantities q3xPlus, q3xMinus, q3yPlus, q3yMinus, q3zPlus, q3zMinus; 58 | 59 | bendTriangleQuantitiesForDerivative(x, v, 0, dx, q0xPlus, q0xMinus); 60 | bendTriangleQuantitiesForDerivative(x, v, 1, dx, q0yPlus, q0yMinus); 61 | bendTriangleQuantitiesForDerivative(x, v, 2, dx, q0zPlus, q0zMinus); 62 | 63 | bendTriangleQuantitiesForDerivative(x, v, 3, dx, q1xPlus, q1xMinus); 64 | bendTriangleQuantitiesForDerivative(x, v, 4, dx, q1yPlus, q1yMinus); 65 | bendTriangleQuantitiesForDerivative(x, v, 5, dx, q1zPlus, q1zMinus); 66 | 67 | bendTriangleQuantitiesForDerivative(x, v, 6, dx, q2xPlus, q2xMinus); 68 | bendTriangleQuantitiesForDerivative(x, v, 7, dx, q2yPlus, q2yMinus); 69 | bendTriangleQuantitiesForDerivative(x, v, 8, dx, q2zPlus, q2zMinus); 70 | 71 | bendTriangleQuantitiesForDerivative(x, v, 9, dx, q3xPlus, q3xMinus); 72 | bendTriangleQuantitiesForDerivative(x, v, 10, dx, q3yPlus, q3yMinus); 73 | bendTriangleQuantitiesForDerivative(x, v, 11, dx, q3zPlus, q3zMinus); 74 | 75 | 76 | // test theta derivatives: 77 | BendCondition::Vector3 dThetadP0; 78 | dThetadP0[0] = (q0xPlus.theta - q0xMinus.theta) / (2 * dx); 79 | dThetadP0[1] = (q0yPlus.theta - q0yMinus.theta) / (2 * dx); 80 | dThetadP0[2] = (q0zPlus.theta - q0zMinus.theta) / (2 * dx); 81 | checkVectorEquality(dThetadP0, q.dThetadP0, tol); 82 | 83 | BendCondition::Vector3 dThetadP1; 84 | dThetadP1[0] = (q1xPlus.theta - q1xMinus.theta) / (2 * dx); 85 | dThetadP1[1] = (q1yPlus.theta - q1yMinus.theta) / (2 * dx); 86 | dThetadP1[2] = (q1zPlus.theta - q1zMinus.theta) / (2 * dx); 87 | checkVectorEquality(dThetadP1, q.dThetadP1, tol); 88 | 89 | BendCondition::Vector3 dThetadP2; 90 | dThetadP2[0] = (q2xPlus.theta - q2xMinus.theta) / (2 * dx); 91 | dThetadP2[1] = (q2yPlus.theta - q2yMinus.theta) / (2 * dx); 92 | dThetadP2[2] = (q2zPlus.theta - q2zMinus.theta) / (2 * dx); 93 | checkVectorEquality(dThetadP2, q.dThetadP2, tol); 94 | 95 | BendCondition::Vector3 dThetadP3; 96 | dThetadP3[0] = (q3xPlus.theta - q3xMinus.theta) / (2 * dx); 97 | dThetadP3[1] = (q3yPlus.theta - q3yMinus.theta) / (2 * dx); 98 | dThetadP3[2] = (q3zPlus.theta - q3zMinus.theta) / (2 * dx); 99 | checkVectorEquality(dThetadP3, q.dThetadP3, tol); 100 | 101 | 102 | // test normal derivatives: 103 | BendCondition::Matrix3 dn0dP0N; 104 | dn0dP0N.col(0) = (q0xPlus.n0 - q0xMinus.n0) / (2 * dx); 105 | dn0dP0N.col(1) = (q0yPlus.n0 - q0yMinus.n0) / (2 * dx); 106 | dn0dP0N.col(2) = (q0zPlus.n0 - q0zMinus.n0) / (2 * dx); 107 | checkMatrixEquality(dn0dP0N, q.dn0dP0, tol); 108 | 109 | BendCondition::Matrix3 dn0dP1N; 110 | dn0dP1N.col(0) = (q1xPlus.n0 - q1xMinus.n0) / (2 * dx); 111 | dn0dP1N.col(1) = (q1yPlus.n0 - q1yMinus.n0) / (2 * dx); 112 | dn0dP1N.col(2) = (q1zPlus.n0 - q1zMinus.n0) / (2 * dx); 113 | checkMatrixEquality(dn0dP1N, q.dn0dP1, tol); 114 | 115 | BendCondition::Matrix3 dn0dP2N; 116 | dn0dP2N.col(0) = (q2xPlus.n0 - q2xMinus.n0) / (2 * dx); 117 | dn0dP2N.col(1) = (q2yPlus.n0 - q2yMinus.n0) / (2 * dx); 118 | dn0dP2N.col(2) = (q2zPlus.n0 - q2zMinus.n0) / (2 * dx); 119 | checkMatrixEquality(dn0dP2N, q.dn0dP2, tol); 120 | 121 | BendCondition::Matrix3 dn0dP3N; 122 | dn0dP3N.col(0) = (q3xPlus.n0 - q3xMinus.n0) / (2 * dx); 123 | dn0dP3N.col(1) = (q3yPlus.n0 - q3yMinus.n0) / (2 * dx); 124 | dn0dP3N.col(2) = (q3zPlus.n0 - q3zMinus.n0) / (2 * dx); 125 | checkMatrixEquality(dn0dP3N, q.dn0dP3, tol); 126 | 127 | 128 | // test perpendicular distance derivatives: 129 | BendCondition::Vector3 dd00dP0; 130 | dd00dP0[0] = (q0xPlus.d00 - q0xMinus.d00) / (2 * dx); 131 | dd00dP0[1] = (q0yPlus.d00 - q0yMinus.d00) / (2 * dx); 132 | dd00dP0[2] = (q0zPlus.d00 - q0zMinus.d00) / (2 * dx); 133 | checkVectorEquality(dd00dP0, q.dd00dP0, tol); 134 | 135 | BendCondition::Vector3 dd00dP1; 136 | dd00dP1[0] = (q1xPlus.d00 - q1xMinus.d00) / (2 * dx); 137 | dd00dP1[1] = (q1yPlus.d00 - q1yMinus.d00) / (2 * dx); 138 | dd00dP1[2] = (q1zPlus.d00 - q1zMinus.d00) / (2 * dx); 139 | checkVectorEquality(dd00dP1, q.dd00dP1, tol); 140 | 141 | BendCondition::Vector3 dd00dP2; 142 | dd00dP2[0] = (q2xPlus.d00 - q2xMinus.d00) / (2 * dx); 143 | dd00dP2[1] = (q2yPlus.d00 - q2yMinus.d00) / (2 * dx); 144 | dd00dP2[2] = (q2zPlus.d00 - q2zMinus.d00) / (2 * dx); 145 | checkVectorEquality(dd00dP2, q.dd00dP2, tol); 146 | 147 | BendCondition::Vector3 dd00dP3; 148 | dd00dP3[0] = (q3xPlus.d00 - q3xMinus.d00) / (2 * dx); 149 | dd00dP3[1] = (q3yPlus.d00 - q3yMinus.d00) / (2 * dx); 150 | dd00dP3[2] = (q3zPlus.d00 - q3zMinus.d00) / (2 * dx); 151 | checkVectorEquality(dd00dP3, q.dd00dP3, tol); 152 | 153 | BendCondition::Vector3 dd01dP0; 154 | dd01dP0[0] = (q0xPlus.d01 - q0xMinus.d01) / (2 * dx); 155 | dd01dP0[1] = (q0yPlus.d01 - q0yMinus.d01) / (2 * dx); 156 | dd01dP0[2] = (q0zPlus.d01 - q0zMinus.d01) / (2 * dx); 157 | checkVectorEquality(dd01dP0, q.dd01dP0, tol); 158 | 159 | BendCondition::Vector3 dd01dP1; 160 | dd01dP1[0] = (q1xPlus.d01 - q1xMinus.d01) / (2 * dx); 161 | dd01dP1[1] = (q1yPlus.d01 - q1yMinus.d01) / (2 * dx); 162 | dd01dP1[2] = (q1zPlus.d01 - q1zMinus.d01) / (2 * dx); 163 | checkVectorEquality(dd01dP1, q.dd01dP1, tol); 164 | 165 | BendCondition::Vector3 dd01dP2; 166 | dd01dP2[0] = (q2xPlus.d01 - q2xMinus.d01) / (2 * dx); 167 | dd01dP2[1] = (q2yPlus.d01 - q2yMinus.d01) / (2 * dx); 168 | dd01dP2[2] = (q2zPlus.d01 - q2zMinus.d01) / (2 * dx); 169 | checkVectorEquality(dd01dP2, q.dd01dP2, tol); 170 | 171 | BendCondition::Vector3 dd01dP3; 172 | dd01dP3[0] = (q3xPlus.d01 - q3xMinus.d01) / (2 * dx); 173 | dd01dP3[1] = (q3yPlus.d01 - q3yMinus.d01) / (2 * dx); 174 | dd01dP3[2] = (q3zPlus.d01 - q3zMinus.d01) / (2 * dx); 175 | checkVectorEquality(dd01dP3, q.dd01dP3, tol); 176 | 177 | BendCondition::Vector3 dd02dP0; 178 | dd02dP0[0] = (q0xPlus.d02 - q0xMinus.d02) / (2 * dx); 179 | dd02dP0[1] = (q0yPlus.d02 - q0yMinus.d02) / (2 * dx); 180 | dd02dP0[2] = (q0zPlus.d02 - q0zMinus.d02) / (2 * dx); 181 | checkVectorEquality(dd02dP0, q.dd02dP0, tol); 182 | 183 | BendCondition::Vector3 dd02dP1; 184 | dd02dP1[0] = (q1xPlus.d02 - q1xMinus.d02) / (2 * dx); 185 | dd02dP1[1] = (q1yPlus.d02 - q1yMinus.d02) / (2 * dx); 186 | dd02dP1[2] = (q1zPlus.d02 - q1zMinus.d02) / (2 * dx); 187 | checkVectorEquality(dd02dP1, q.dd02dP1, tol); 188 | 189 | BendCondition::Vector3 dd02dP2; 190 | dd02dP2[0] = (q2xPlus.d02 - q2xMinus.d02) / (2 * dx); 191 | dd02dP2[1] = (q2yPlus.d02 - q2yMinus.d02) / (2 * dx); 192 | dd02dP2[2] = (q2zPlus.d02 - q2zMinus.d02) / (2 * dx); 193 | checkVectorEquality(dd02dP2, q.dd02dP2, tol); 194 | 195 | BendCondition::Vector3 dd02dP3; 196 | dd02dP3[0] = (q3xPlus.d02 - q3xMinus.d02) / (2 * dx); 197 | dd02dP3[1] = (q3yPlus.d02 - q3yMinus.d02) / (2 * dx); 198 | dd02dP3[2] = (q3zPlus.d02 - q3zMinus.d02) / (2 * dx); 199 | checkVectorEquality(dd02dP3, q.dd02dP3, tol); 200 | 201 | BendCondition::Vector3 dd11dP0; 202 | dd11dP0[0] = (q0xPlus.d11 - q0xMinus.d11) / (2 * dx); 203 | dd11dP0[1] = (q0yPlus.d11 - q0yMinus.d11) / (2 * dx); 204 | dd11dP0[2] = (q0zPlus.d11 - q0zMinus.d11) / (2 * dx); 205 | checkVectorEquality(dd11dP0, q.dd11dP0, tol); 206 | 207 | BendCondition::Vector3 dd11dP1; 208 | dd11dP1[0] = (q1xPlus.d11 - q1xMinus.d11) / (2 * dx); 209 | dd11dP1[1] = (q1yPlus.d11 - q1yMinus.d11) / (2 * dx); 210 | dd11dP1[2] = (q1zPlus.d11 - q1zMinus.d11) / (2 * dx); 211 | checkVectorEquality(dd11dP1, q.dd11dP1, tol); 212 | 213 | BendCondition::Vector3 dd11dP2; 214 | dd11dP2[0] = (q2xPlus.d11 - q2xMinus.d11) / (2 * dx); 215 | dd11dP2[1] = (q2yPlus.d11 - q2yMinus.d11) / (2 * dx); 216 | dd11dP2[2] = (q2zPlus.d11 - q2zMinus.d11) / (2 * dx); 217 | checkVectorEquality(dd11dP2, q.dd11dP2, tol); 218 | 219 | BendCondition::Vector3 dd11dP3; 220 | dd11dP3[0] = (q3xPlus.d11 - q3xMinus.d11) / (2 * dx); 221 | dd11dP3[1] = (q3yPlus.d11 - q3yMinus.d11) / (2 * dx); 222 | dd11dP3[2] = (q3zPlus.d11 - q3zMinus.d11) / (2 * dx); 223 | checkVectorEquality(dd11dP3, q.dd11dP3, tol); 224 | 225 | BendCondition::Vector3 dd12dP0; 226 | dd12dP0[0] = (q0xPlus.d12 - q0xMinus.d12) / (2 * dx); 227 | dd12dP0[1] = (q0yPlus.d12 - q0yMinus.d12) / (2 * dx); 228 | dd12dP0[2] = (q0zPlus.d12 - q0zMinus.d12) / (2 * dx); 229 | checkVectorEquality(dd12dP0, q.dd12dP0, tol); 230 | 231 | BendCondition::Vector3 dd12dP1; 232 | dd12dP1[0] = (q1xPlus.d12 - q1xMinus.d12) / (2 * dx); 233 | dd12dP1[1] = (q1yPlus.d12 - q1yMinus.d12) / (2 * dx); 234 | dd12dP1[2] = (q1zPlus.d12 - q1zMinus.d12) / (2 * dx); 235 | checkVectorEquality(dd12dP1, q.dd12dP1, tol); 236 | 237 | BendCondition::Vector3 dd12dP2; 238 | dd12dP2[0] = (q2xPlus.d12 - q2xMinus.d12) / (2 * dx); 239 | dd12dP2[1] = (q2yPlus.d12 - q2yMinus.d12) / (2 * dx); 240 | dd12dP2[2] = (q2zPlus.d12 - q2zMinus.d12) / (2 * dx); 241 | checkVectorEquality(dd12dP2, q.dd12dP2, tol); 242 | 243 | BendCondition::Vector3 dd12dP3; 244 | dd12dP3[0] = (q3xPlus.d12 - q3xMinus.d12) / (2 * dx); 245 | dd12dP3[1] = (q3yPlus.d12 - q3yMinus.d12) / (2 * dx); 246 | dd12dP3[2] = (q3zPlus.d12 - q3zMinus.d12) / (2 * dx); 247 | checkVectorEquality(dd12dP3, q.dd12dP3, tol); 248 | 249 | BendCondition::Vector3 dd13dP0; 250 | dd13dP0[0] = (q0xPlus.d13 - q0xMinus.d13) / (2 * dx); 251 | dd13dP0[1] = (q0yPlus.d13 - q0yMinus.d13) / (2 * dx); 252 | dd13dP0[2] = (q0zPlus.d13 - q0zMinus.d13) / (2 * dx); 253 | checkVectorEquality(dd13dP0, q.dd13dP0, tol); 254 | 255 | BendCondition::Vector3 dd13dP1; 256 | dd13dP1[0] = (q1xPlus.d13 - q1xMinus.d13) / (2 * dx); 257 | dd13dP1[1] = (q1yPlus.d13 - q1yMinus.d13) / (2 * dx); 258 | dd13dP1[2] = (q1zPlus.d13 - q1zMinus.d13) / (2 * dx); 259 | checkVectorEquality(dd13dP1, q.dd13dP1, tol); 260 | 261 | BendCondition::Vector3 dd13dP2; 262 | dd13dP2[0] = (q2xPlus.d13 - q2xMinus.d13) / (2 * dx); 263 | dd13dP2[1] = (q2yPlus.d13 - q2yMinus.d13) / (2 * dx); 264 | dd13dP2[2] = (q2zPlus.d13 - q2zMinus.d13) / (2 * dx); 265 | checkVectorEquality(dd13dP2, q.dd13dP2, tol); 266 | 267 | BendCondition::Vector3 dd13dP3; 268 | dd13dP3[0] = (q3xPlus.d13 - q3xMinus.d13) / (2 * dx); 269 | dd13dP3[1] = (q3yPlus.d13 - q3yMinus.d13) / (2 * dx); 270 | dd13dP3[2] = (q3zPlus.d13 - q3zMinus.d13) / (2 * dx); 271 | checkVectorEquality(dd13dP3, q.dd13dP3, tol); 272 | 273 | 274 | // test cosine derivatives: 275 | BendCondition::Vector3 dc01dP0; 276 | dc01dP0[0] = (q0xPlus.c01 - q0xMinus.c01) / (2 * dx); 277 | dc01dP0[1] = (q0yPlus.c01 - q0yMinus.c01) / (2 * dx); 278 | dc01dP0[2] = (q0zPlus.c01 - q0zMinus.c01) / (2 * dx); 279 | checkVectorEquality(dc01dP0, q.dc01dP0, tol); 280 | 281 | BendCondition::Vector3 dc01dP1; 282 | dc01dP1[0] = (q1xPlus.c01 - q1xMinus.c01) / (2 * dx); 283 | dc01dP1[1] = (q1yPlus.c01 - q1yMinus.c01) / (2 * dx); 284 | dc01dP1[2] = (q1zPlus.c01 - q1zMinus.c01) / (2 * dx); 285 | checkVectorEquality(dc01dP1, q.dc01dP1, tol); 286 | 287 | BendCondition::Vector3 dc01dP2; 288 | dc01dP2[0] = (q2xPlus.c01 - q2xMinus.c01) / (2 * dx); 289 | dc01dP2[1] = (q2yPlus.c01 - q2yMinus.c01) / (2 * dx); 290 | dc01dP2[2] = (q2zPlus.c01 - q2zMinus.c01) / (2 * dx); 291 | checkVectorEquality(dc01dP2, q.dc01dP2, tol); 292 | 293 | BendCondition::Vector3 dc01dP3; 294 | dc01dP3[0] = (q3xPlus.c01 - q3xMinus.c01) / (2 * dx); 295 | dc01dP3[1] = (q3yPlus.c01 - q3yMinus.c01) / (2 * dx); 296 | dc01dP3[2] = (q3zPlus.c01 - q3zMinus.c01) / (2 * dx); 297 | checkVectorEquality(dc01dP3, q.dc01dP3, tol); 298 | 299 | BendCondition::Vector3 dc02dP0; 300 | dc02dP0[0] = (q0xPlus.c02 - q0xMinus.c02) / (2 * dx); 301 | dc02dP0[1] = (q0yPlus.c02 - q0yMinus.c02) / (2 * dx); 302 | dc02dP0[2] = (q0zPlus.c02 - q0zMinus.c02) / (2 * dx); 303 | checkVectorEquality(dc02dP0, q.dc02dP0, tol); 304 | 305 | BendCondition::Vector3 dc02dP1; 306 | dc02dP1[0] = (q1xPlus.c02 - q1xMinus.c02) / (2 * dx); 307 | dc02dP1[1] = (q1yPlus.c02 - q1yMinus.c02) / (2 * dx); 308 | dc02dP1[2] = (q1zPlus.c02 - q1zMinus.c02) / (2 * dx); 309 | checkVectorEquality(dc02dP1, q.dc02dP1, tol); 310 | 311 | BendCondition::Vector3 dc02dP2; 312 | dc02dP2[0] = (q2xPlus.c02 - q2xMinus.c02) / (2 * dx); 313 | dc02dP2[1] = (q2yPlus.c02 - q2yMinus.c02) / (2 * dx); 314 | dc02dP2[2] = (q2zPlus.c02 - q2zMinus.c02) / (2 * dx); 315 | checkVectorEquality(dc02dP2, q.dc02dP2, tol); 316 | 317 | BendCondition::Vector3 dc02dP3; 318 | dc02dP3[0] = (q3xPlus.c02 - q3xMinus.c02) / (2 * dx); 319 | dc02dP3[1] = (q3yPlus.c02 - q3yMinus.c02) / (2 * dx); 320 | dc02dP3[2] = (q3zPlus.c02 - q3zMinus.c02) / (2 * dx); 321 | checkVectorEquality(dc02dP3, q.dc02dP3, tol); 322 | 323 | BendCondition::Vector3 dc11dP0; 324 | dc11dP0[0] = (q0xPlus.c11 - q0xMinus.c11) / (2 * dx); 325 | dc11dP0[1] = (q0yPlus.c11 - q0yMinus.c11) / (2 * dx); 326 | dc11dP0[2] = (q0zPlus.c11 - q0zMinus.c11) / (2 * dx); 327 | checkVectorEquality(dc11dP0, q.dc11dP0, tol); 328 | 329 | BendCondition::Vector3 dc11dP1; 330 | dc11dP1[0] = (q1xPlus.c11 - q1xMinus.c11) / (2 * dx); 331 | dc11dP1[1] = (q1yPlus.c11 - q1yMinus.c11) / (2 * dx); 332 | dc11dP1[2] = (q1zPlus.c11 - q1zMinus.c11) / (2 * dx); 333 | checkVectorEquality(dc11dP1, q.dc11dP1, tol); 334 | 335 | BendCondition::Vector3 dc11dP2; 336 | dc11dP2[0] = (q2xPlus.c11 - q2xMinus.c11) / (2 * dx); 337 | dc11dP2[1] = (q2yPlus.c11 - q2yMinus.c11) / (2 * dx); 338 | dc11dP2[2] = (q2zPlus.c11 - q2zMinus.c11) / (2 * dx); 339 | checkVectorEquality(dc11dP2, q.dc11dP2, tol); 340 | 341 | BendCondition::Vector3 dc11dP3; 342 | dc11dP3[0] = (q3xPlus.c11 - q3xMinus.c11) / (2 * dx); 343 | dc11dP3[1] = (q3yPlus.c11 - q3yMinus.c11) / (2 * dx); 344 | dc11dP3[2] = (q3zPlus.c11 - q3zMinus.c11) / (2 * dx); 345 | checkVectorEquality(dc11dP3, q.dc11dP3, tol); 346 | 347 | BendCondition::Vector3 dc12dP0; 348 | dc12dP0[0] = (q0xPlus.c12 - q0xMinus.c12) / (2 * dx); 349 | dc12dP0[1] = (q0yPlus.c12 - q0yMinus.c12) / (2 * dx); 350 | dc12dP0[2] = (q0zPlus.c12 - q0zMinus.c12) / (2 * dx); 351 | checkVectorEquality(dc12dP0, q.dc12dP0, tol); 352 | 353 | BendCondition::Vector3 dc12dP1; 354 | dc12dP1[0] = (q1xPlus.c12 - q1xMinus.c12) / (2 * dx); 355 | dc12dP1[1] = (q1yPlus.c12 - q1yMinus.c12) / (2 * dx); 356 | dc12dP1[2] = (q1zPlus.c12 - q1zMinus.c12) / (2 * dx); 357 | checkVectorEquality(dc12dP1, q.dc12dP1, tol); 358 | 359 | BendCondition::Vector3 dc12dP2; 360 | dc12dP2[0] = (q2xPlus.c12 - q2xMinus.c12) / (2 * dx); 361 | dc12dP2[1] = (q2yPlus.c12 - q2yMinus.c12) / (2 * dx); 362 | dc12dP2[2] = (q2zPlus.c12 - q2zMinus.c12) / (2 * dx); 363 | checkVectorEquality(dc12dP2, q.dc12dP2, tol); 364 | 365 | BendCondition::Vector3 dc12dP3; 366 | dc12dP3[0] = (q3xPlus.c12 - q3xMinus.c12) / (2 * dx); 367 | dc12dP3[1] = (q3yPlus.c12 - q3yMinus.c12) / (2 * dx); 368 | dc12dP3[2] = (q3zPlus.c12 - q3zMinus.c12) / (2 * dx); 369 | checkVectorEquality(dc12dP3, q.dc12dP3, tol); 370 | 371 | 372 | // second derivatives of theta: 373 | BendCondition::Matrix3 d2ThetadP0dP0; 374 | d2ThetadP0dP0.col(0) = (q0xPlus.dThetadP0 - q0xMinus.dThetadP0) / (2 * dx); 375 | d2ThetadP0dP0.col(1) = (q0yPlus.dThetadP0 - q0yMinus.dThetadP0) / (2 * dx); 376 | d2ThetadP0dP0.col(2) = (q0zPlus.dThetadP0 - q0zMinus.dThetadP0) / (2 * dx); 377 | checkMatrixEquality(d2ThetadP0dP0, q.d2ThetadP0dP0, tol); 378 | 379 | BendCondition::Matrix3 d2ThetadP0dP1; 380 | d2ThetadP0dP1.col(0) = (q1xPlus.dThetadP0 - q1xMinus.dThetadP0) / (2 * dx); 381 | d2ThetadP0dP1.col(1) = (q1yPlus.dThetadP0 - q1yMinus.dThetadP0) / (2 * dx); 382 | d2ThetadP0dP1.col(2) = (q1zPlus.dThetadP0 - q1zMinus.dThetadP0) / (2 * dx); 383 | checkMatrixEquality(d2ThetadP0dP1, q.d2ThetadP0dP1, tol); 384 | 385 | BendCondition::Matrix3 d2ThetadP0dP2; 386 | d2ThetadP0dP2.col(0) = (q2xPlus.dThetadP0 - q2xMinus.dThetadP0) / (2 * dx); 387 | d2ThetadP0dP2.col(1) = (q2yPlus.dThetadP0 - q2yMinus.dThetadP0) / (2 * dx); 388 | d2ThetadP0dP2.col(2) = (q2zPlus.dThetadP0 - q2zMinus.dThetadP0) / (2 * dx); 389 | checkMatrixEquality(d2ThetadP0dP2, q.d2ThetadP0dP2, tol); 390 | 391 | BendCondition::Matrix3 d2ThetadP0dP3; 392 | d2ThetadP0dP3.col(0) = (q3xPlus.dThetadP0 - q3xMinus.dThetadP0) / (2 * dx); 393 | d2ThetadP0dP3.col(1) = (q3yPlus.dThetadP0 - q3yMinus.dThetadP0) / (2 * dx); 394 | d2ThetadP0dP3.col(2) = (q3zPlus.dThetadP0 - q3zMinus.dThetadP0) / (2 * dx); 395 | checkMatrixEquality(d2ThetadP0dP3, q.d2ThetadP0dP3, tol); 396 | 397 | BendCondition::Matrix3 d2ThetadP1dP0; 398 | d2ThetadP1dP0.col(0) = (q0xPlus.dThetadP1 - q0xMinus.dThetadP1) / (2 * dx); 399 | d2ThetadP1dP0.col(1) = (q0yPlus.dThetadP1 - q0yMinus.dThetadP1) / (2 * dx); 400 | d2ThetadP1dP0.col(2) = (q0zPlus.dThetadP1 - q0zMinus.dThetadP1) / (2 * dx); 401 | checkMatrixEquality(d2ThetadP1dP0, q.d2ThetadP1dP0, tol); 402 | 403 | BendCondition::Matrix3 d2ThetadP1dP1; 404 | d2ThetadP1dP1.col(0) = (q1xPlus.dThetadP1 - q1xMinus.dThetadP1) / (2 * dx); 405 | d2ThetadP1dP1.col(1) = (q1yPlus.dThetadP1 - q1yMinus.dThetadP1) / (2 * dx); 406 | d2ThetadP1dP1.col(2) = (q1zPlus.dThetadP1 - q1zMinus.dThetadP1) / (2 * dx); 407 | checkMatrixEquality(d2ThetadP1dP1, q.d2ThetadP1dP1, tol); 408 | 409 | BendCondition::Matrix3 d2ThetadP1dP2; 410 | d2ThetadP1dP2.col(0) = (q2xPlus.dThetadP1 - q2xMinus.dThetadP1) / (2 * dx); 411 | d2ThetadP1dP2.col(1) = (q2yPlus.dThetadP1 - q2yMinus.dThetadP1) / (2 * dx); 412 | d2ThetadP1dP2.col(2) = (q2zPlus.dThetadP1 - q2zMinus.dThetadP1) / (2 * dx); 413 | checkMatrixEquality(d2ThetadP1dP2, q.d2ThetadP1dP2, tol); 414 | 415 | BendCondition::Matrix3 d2ThetadP1dP3; 416 | d2ThetadP1dP3.col(0) = (q3xPlus.dThetadP1 - q3xMinus.dThetadP1) / (2 * dx); 417 | d2ThetadP1dP3.col(1) = (q3yPlus.dThetadP1 - q3yMinus.dThetadP1) / (2 * dx); 418 | d2ThetadP1dP3.col(2) = (q3zPlus.dThetadP1 - q3zMinus.dThetadP1) / (2 * dx); 419 | checkMatrixEquality(d2ThetadP1dP3, q.d2ThetadP1dP3, tol); 420 | 421 | BendCondition::Matrix3 d2ThetadP2dP0; 422 | d2ThetadP2dP0.col(0) = (q0xPlus.dThetadP2 - q0xMinus.dThetadP2) / (2 * dx); 423 | d2ThetadP2dP0.col(1) = (q0yPlus.dThetadP2 - q0yMinus.dThetadP2) / (2 * dx); 424 | d2ThetadP2dP0.col(2) = (q0zPlus.dThetadP2 - q0zMinus.dThetadP2) / (2 * dx); 425 | checkMatrixEquality(d2ThetadP2dP0, q.d2ThetadP2dP0, tol); 426 | 427 | BendCondition::Matrix3 d2ThetadP2dP1; 428 | d2ThetadP2dP1.col(0) = (q1xPlus.dThetadP2 - q1xMinus.dThetadP2) / (2 * dx); 429 | d2ThetadP2dP1.col(1) = (q1yPlus.dThetadP2 - q1yMinus.dThetadP2) / (2 * dx); 430 | d2ThetadP2dP1.col(2) = (q1zPlus.dThetadP2 - q1zMinus.dThetadP2) / (2 * dx); 431 | checkMatrixEquality(d2ThetadP2dP1, q.d2ThetadP2dP1, tol); 432 | 433 | BendCondition::Matrix3 d2ThetadP2dP2; 434 | d2ThetadP2dP2.col(0) = (q2xPlus.dThetadP2 - q2xMinus.dThetadP2) / (2 * dx); 435 | d2ThetadP2dP2.col(1) = (q2yPlus.dThetadP2 - q2yMinus.dThetadP2) / (2 * dx); 436 | d2ThetadP2dP2.col(2) = (q2zPlus.dThetadP2 - q2zMinus.dThetadP2) / (2 * dx); 437 | checkMatrixEquality(d2ThetadP2dP2, q.d2ThetadP2dP2, tol); 438 | 439 | BendCondition::Matrix3 d2ThetadP2dP3; 440 | d2ThetadP2dP3.col(0) = (q3xPlus.dThetadP2 - q3xMinus.dThetadP2) / (2 * dx); 441 | d2ThetadP2dP3.col(1) = (q3yPlus.dThetadP2 - q3yMinus.dThetadP2) / (2 * dx); 442 | d2ThetadP2dP3.col(2) = (q3zPlus.dThetadP2 - q3zMinus.dThetadP2) / (2 * dx); 443 | checkMatrixEquality(d2ThetadP2dP3, q.d2ThetadP2dP3, tol); 444 | 445 | BendCondition::Matrix3 d2ThetadP3dP0; 446 | d2ThetadP3dP0.col(0) = (q0xPlus.dThetadP3 - q0xMinus.dThetadP3) / (2 * dx); 447 | d2ThetadP3dP0.col(1) = (q0yPlus.dThetadP3 - q0yMinus.dThetadP3) / (2 * dx); 448 | d2ThetadP3dP0.col(2) = (q0zPlus.dThetadP3 - q0zMinus.dThetadP3) / (2 * dx); 449 | checkMatrixEquality(d2ThetadP3dP0, q.d2ThetadP3dP0, tol); 450 | 451 | BendCondition::Matrix3 d2ThetadP3dP1; 452 | d2ThetadP3dP1.col(0) = (q1xPlus.dThetadP3 - q1xMinus.dThetadP3) / (2 * dx); 453 | d2ThetadP3dP1.col(1) = (q1yPlus.dThetadP3 - q1yMinus.dThetadP3) / (2 * dx); 454 | d2ThetadP3dP1.col(2) = (q1zPlus.dThetadP3 - q1zMinus.dThetadP3) / (2 * dx); 455 | checkMatrixEquality(d2ThetadP3dP1, q.d2ThetadP3dP1, tol); 456 | 457 | BendCondition::Matrix3 d2ThetadP3dP2; 458 | d2ThetadP3dP2.col(0) = (q2xPlus.dThetadP3 - q2xMinus.dThetadP3) / (2 * dx); 459 | d2ThetadP3dP2.col(1) = (q2yPlus.dThetadP3 - q2yMinus.dThetadP3) / (2 * dx); 460 | d2ThetadP3dP2.col(2) = (q2zPlus.dThetadP3 - q2zMinus.dThetadP3) / (2 * dx); 461 | checkMatrixEquality(d2ThetadP3dP2, q.d2ThetadP3dP2, tol); 462 | 463 | BendCondition::Matrix3 d2ThetadP3dP3; 464 | d2ThetadP3dP3.col(0) = (q3xPlus.dThetadP3 - q3xMinus.dThetadP3) / (2 * dx); 465 | d2ThetadP3dP3.col(1) = (q3yPlus.dThetadP3 - q3yMinus.dThetadP3) / (2 * dx); 466 | d2ThetadP3dP3.col(2) = (q3zPlus.dThetadP3 - q3zMinus.dThetadP3) / (2 * dx); 467 | checkMatrixEquality(d2ThetadP3dP3, q.d2ThetadP3dP3, tol); 468 | } 469 | 470 | TEST_METHOD(TestBendConditionTriangleQuantities) 471 | { 472 | Eigen::VectorXd v(5 * 3); 473 | 474 | v[3 * 0] = 0; 475 | v[3 * 0 + 1] = 0; 476 | v[3 * 0 + 2] = -0.4; 477 | 478 | v[3 * 1] = 0; 479 | v[3 * 1 + 1] = 0; 480 | v[3 * 1 + 2] = 0.1; 481 | 482 | v[3 * 2] = 0; 483 | v[3 * 2 + 1] = 0; 484 | v[3 * 2 + 2] = 0.1; 485 | 486 | v[3 * 3] = 1.0; 487 | v[3 * 3 + 1] = 0; 488 | v[3 * 3 + 2] = -0.2; 489 | 490 | v[3 * 4] = 0.0; 491 | v[3 * 4 + 1] = 0; 492 | v[3 * 4 + 2] = 0; 493 | 494 | Eigen::VectorXd x(5 * 3); 495 | 496 | x[3 * 0] = -2.0; 497 | x[3 * 0 + 1] = 0; 498 | x[3 * 0 + 2] = 0.4; 499 | 500 | x[3 * 1] = 0; 501 | x[3 * 1 + 1] = 1.0; 502 | x[3 * 1 + 2] = 0; 503 | 504 | x[3 * 2] = 0; 505 | x[3 * 2 + 1] = -0.5; 506 | x[3 * 2 + 2] = 0; 507 | 508 | x[3 * 3] = 1.0; 509 | x[3 * 3 + 1] = 0; 510 | x[3 * 3 + 2] = 0.2; 511 | 512 | x[3 * 4] = 0.0; 513 | x[3 * 4 + 1] = 0; 514 | x[3 * 4 + 2] = 0; 515 | 516 | double dx = 0.0001; 517 | double tol = 1.e-5; 518 | 519 | // test with positive angle: 520 | checkBendTriangleQuantities(x, v, dx, tol); 521 | 522 | // test with negative angle: 523 | x[3 * 0 + 2] = -0.4; 524 | x[3 * 3 + 2] = -0.2; 525 | checkBendTriangleQuantities(x, v, dx, tol); 526 | 527 | // test for 10 random configurations: 528 | for (int i = 0; i < 10; ++i) 529 | { 530 | // randomize the positions: 531 | x = Eigen::VectorXd::Random(5 * 3); 532 | v = Eigen::VectorXd::Random(5 * 3); 533 | checkBendTriangleQuantities(x, v, dx, tol); 534 | } 535 | } 536 | 537 | TEST_METHOD(TestBendCondition) 538 | { 539 | 540 | Eigen::VectorXd v(5 * 3); 541 | 542 | v[3 * 0] = 0; 543 | v[3 * 0 + 1] = 0; 544 | v[3 * 0 + 2] = -0.4; 545 | 546 | v[3 * 1] = 0; 547 | v[3 * 1 + 1] = 0; 548 | v[3 * 1 + 2] = 0.1; 549 | 550 | v[3 * 2] = 0; 551 | v[3 * 2 + 1] = 0; 552 | v[3 * 2 + 2] = 0.1; 553 | 554 | v[3 * 3] = 1.0; 555 | v[3 * 3 + 1] = 0; 556 | v[3 * 3 + 2] = -0.2; 557 | 558 | v[3 * 4] = 0.0; 559 | v[3 * 4 + 1] = 0; 560 | v[3 * 4 + 2] = 0; 561 | 562 | Eigen::VectorXd x(5 * 3); 563 | 564 | x[3 * 0] = -2.0; 565 | x[3 * 0 + 1] = 0; 566 | x[3 * 0 + 2] = 0.4; 567 | 568 | x[3 * 1] = 0; 569 | x[3 * 1 + 1] = 1.0; 570 | x[3 * 1 + 2] = 0; 571 | 572 | x[3 * 2] = 0; 573 | x[3 * 2 + 1] = -1.0; 574 | x[3 * 2 + 2] = 0; 575 | 576 | x[3 * 3] = 1.0; 577 | x[3 * 3 + 1] = 0; 578 | x[3 * 3 + 2] = 0.2; 579 | 580 | x[3 * 4] = 0.0; 581 | x[3 * 4 + 1] = 0; 582 | x[3 * 4 + 2] = 0; 583 | 584 | Eigen::VectorXd uv(5 * 2); 585 | 586 | uv[2 * 0] = -2.0; 587 | uv[2 * 0 + 1] = 0; 588 | 589 | uv[2 * 1] = 0; 590 | uv[2 * 1 + 1] = 1.0; 591 | 592 | uv[2 * 2] = 0; 593 | uv[2 * 2 + 1] = -1.0; 594 | 595 | uv[2 * 3] = 1.0; 596 | uv[2 * 3 + 1] = 0; 597 | 598 | uv[2 * 4] = 0.0; 599 | uv[2 * 4 + 1] = 0; 600 | 601 | Eigen::VectorXd f(5 * 3); 602 | f.setConstant(0); 603 | Eigen::VectorXd dampingForces(5 * 3); 604 | dampingForces.setConstant(0); 605 | BendCondition bc(0, 1, 2, 3); 606 | 607 | double k = 1.5; 608 | Eigen::SparseMatrix dfdx(5 * 3, 5 * 3); 609 | double d = 1.0; 610 | Eigen::SparseMatrix dampingPseudoDerivatives(5 * 3, 5 * 3); 611 | Eigen::SparseMatrix dfdv(5 * 3, 5 * 3); 612 | 613 | // check we've got the energy condition right, and we're measuring the angle between the normals: 614 | Eigen::VectorXd c; 615 | c = bc.C(x, uv); 616 | 617 | // sqrt(2) comes from the length of the bend edge: 618 | Assert::AreEqual(c[0], sqrt(2.0) * 2 * atan(0.2), 1.e-4); 619 | 620 | // compute forces analytically: 621 | bc.computeForces(x, uv, k, f, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 622 | 623 | // sanity check: forces should be opposing the bend, so the ones on x0,x3 should be pointing down, 624 | // the ones on x1,x2 should be pointing up: 625 | Assert::IsTrue(f[3 * 0 + 2] < 0); 626 | Assert::IsTrue(f[3 * 3 + 2] < 0); 627 | Assert::IsTrue(f[3 * 1 + 2] > 0); 628 | Assert::IsTrue(f[3 * 2 + 2] > 0); 629 | 630 | // you'd expect the damping forces to oppose the motion: 631 | Assert::IsTrue(dampingForces[3 * 0 + 2] > 0); 632 | Assert::IsTrue(dampingForces[3 * 3 + 2] > 0); 633 | Assert::IsTrue(dampingForces[3 * 1 + 2] < 0); 634 | Assert::IsTrue(dampingForces[3 * 2 + 2] < 0); 635 | 636 | // compare to numerically computed forces: 637 | double dx = 0.0001; 638 | double tol = 1.e-3; 639 | for (int i = 0; i < x.size(); ++i) 640 | { 641 | Assert::AreEqual(numericalForce(bc, uv, x, k, i, dx), f[i], tol); 642 | Assert::AreEqual(numericalDampingForce(bc, uv, x, v, d, i, dx), dampingForces[i], tol); 643 | } 644 | 645 | for (int i = 0; i < x.size(); ++i) 646 | { 647 | Eigen::VectorXd fdN = dfdx.row(i); 648 | checkVectorEquality(fdN, numericalForceDerivative(bc, uv, x, v, k, d, i, dx), tol, true); 649 | } 650 | 651 | checkPseudoDerivatives(dampingPseudoDerivatives, bc, uv, x, v, k, d, dx*0.1, tol); 652 | 653 | // turn the angle the other way to make sure we're handling bends in both directions: 654 | x[3 * 0 + 2] = -0.4; 655 | x[3 * 3 + 2] = -0.2; 656 | 657 | // test we're measuring a negative angle: 658 | c = bc.C(x, uv); 659 | Assert::AreEqual(c[0], -sqrt(2) * 2 * atan(0.2), 1.e-4); 660 | 661 | // recompute the forces and check the derivatives: 662 | f.setConstant(0); 663 | for (int i = 0; i < dfdx.outerSize(); ++i) 664 | { 665 | for (Eigen::SparseMatrix::InnerIterator it(dfdx, i); it; ++it) 666 | { 667 | it.valueRef() = 0; 668 | } 669 | } 670 | dampingForces.setConstant(0); 671 | for (int i = 0; i < dampingPseudoDerivatives.outerSize(); ++i) 672 | { 673 | for (Eigen::SparseMatrix::InnerIterator it(dampingPseudoDerivatives, i); it; ++it) 674 | { 675 | it.valueRef() = 0; 676 | } 677 | } 678 | for (int i = 0; i < dfdv.outerSize(); ++i) 679 | { 680 | for (Eigen::SparseMatrix::InnerIterator it(dfdv, i); it; ++it) 681 | { 682 | it.valueRef() = 0; 683 | } 684 | } 685 | bc.computeForces(x, uv, k, f, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 686 | 687 | // sanity check: forces should be opposing the bend, so now the ones on x0,x3 should be pointing up, 688 | // and the ones on x1,x2 should be pointing down: 689 | Assert::IsTrue(f[3 * 0 + 2] > 0); 690 | Assert::IsTrue(f[3 * 3 + 2] > 0); 691 | Assert::IsTrue(f[3 * 1 + 2] < 0); 692 | Assert::IsTrue(f[3 * 2 + 2] < 0); 693 | 694 | // you'd expect the damping forces to oppose the motion: 695 | Assert::IsTrue(dampingForces[3 * 0 + 2] > 0); 696 | Assert::IsTrue(dampingForces[3 * 3 + 2] > 0); 697 | Assert::IsTrue(dampingForces[3 * 1 + 2] < 0); 698 | Assert::IsTrue(dampingForces[3 * 2 + 2] < 0); 699 | 700 | for (int i = 0; i < x.size(); ++i) 701 | { 702 | Assert::AreEqual(numericalForce(bc, uv, x, k, i, dx), f[i], tol); 703 | Assert::AreEqual(numericalDampingForce(bc, uv, x, v, d, i, dx), dampingForces[i], tol); 704 | } 705 | 706 | 707 | for (int i = 0; i < x.size(); ++i) 708 | { 709 | Eigen::VectorXd fdN = dfdx.row(i); 710 | checkVectorEquality(fdN, numericalForceDerivative(bc, uv, x, v, k, d, i, dx), tol, true); 711 | fdN = dfdv.row(i); 712 | checkVectorEquality(fdN, numericalDampingForceDerivative(bc, uv, x, v, k, d, i, dx), tol, true); 713 | } 714 | 715 | // check the terms that are almost dfd/dx but not quite: 716 | checkPseudoDerivatives(dampingPseudoDerivatives, bc, uv, x, v, k, d, dx*0.1, tol); 717 | 718 | // test on 10 randomized configurations: 719 | for (size_t n = 0; n < 10; ++n) 720 | { 721 | // randomize positions: 722 | x = Eigen::VectorXd::Random(5 * 3); 723 | v = Eigen::VectorXd::Random(5 * 3); 724 | f.setConstant(0); 725 | for (int i = 0; i < dfdx.outerSize(); ++i) 726 | { 727 | for (Eigen::SparseMatrix::InnerIterator it(dfdx, i); it; ++it) 728 | { 729 | it.valueRef() = 0; 730 | } 731 | } 732 | dampingForces.setConstant(0); 733 | for (int i = 0; i < dampingPseudoDerivatives.outerSize(); ++i) 734 | { 735 | for (Eigen::SparseMatrix::InnerIterator it(dampingPseudoDerivatives, i); it; ++it) 736 | { 737 | it.valueRef() = 0; 738 | } 739 | } 740 | for (int i = 0; i < dfdv.outerSize(); ++i) 741 | { 742 | for (Eigen::SparseMatrix::InnerIterator it(dfdv, i); it; ++it) 743 | { 744 | it.valueRef() = 0; 745 | } 746 | } 747 | bc.computeForces(x, uv, k, f, dfdx, v, d, dampingForces, dampingPseudoDerivatives, dfdv); 748 | 749 | for (int i = 0; i < x.size(); ++i) 750 | { 751 | Assert::AreEqual(numericalForce(bc, uv, x, k, i, dx), f[i], tol); 752 | Assert::AreEqual(numericalDampingForce(bc, uv, x, v, d, i, dx), dampingForces[i], tol); 753 | } 754 | 755 | for (int i = 0; i < x.size(); ++i) 756 | { 757 | Eigen::VectorXd fdN = dfdx.row(i); 758 | checkVectorEquality(fdN, numericalForceDerivative(bc, uv, x, v, k, d, i, dx), tol, true); 759 | fdN = dfdv.row(i); 760 | checkVectorEquality(fdN, numericalDampingForceDerivative(bc, uv, x, v, k, d, i, dx), tol, true); 761 | } 762 | 763 | // check the terms that are almost dfd/dx but not quite: 764 | checkPseudoDerivatives(dampingPseudoDerivatives, bc, uv, x, v, k, d, dx*0.1, tol); 765 | } 766 | } 767 | 768 | }; 769 | } 770 | --------------------------------------------------------------------------------