diff options
author | over0219 <over0219@umn.edu> | 2020-06-03 07:26:39 +0300 |
---|---|---|
committer | over0219 <over0219@umn.edu> | 2020-06-03 07:26:39 +0300 |
commit | 9ceb298156044e616bcea97b3c82f1c416ec4385 (patch) | |
tree | 3fdbfc815f87334f3e545201566aaf325582db2f | |
parent | 9ba5c8b90aa6245c569f7e66b0cd472beb59b5a5 (diff) |
still pretty buggy. The solver should be working, but lattice is too lousy to see. The interface between blender and solver needs work too, doesn't seem to reset as often as I think it should be?
-rw-r--r-- | extern/softbody/CMakeLists.txt | 2 | ||||
-rw-r--r-- | extern/softbody/src/admmpd_collision.cpp | 33 | ||||
-rw-r--r-- | extern/softbody/src/admmpd_collision.h | 42 | ||||
-rw-r--r-- | extern/softbody/src/admmpd_energy.cpp | 2 | ||||
-rw-r--r-- | extern/softbody/src/admmpd_lattice.cpp | 1 | ||||
-rw-r--r-- | extern/softbody/src/admmpd_math.cpp | 40 | ||||
-rw-r--r-- | extern/softbody/src/admmpd_math.h | 22 | ||||
-rw-r--r-- | extern/softbody/src/admmpd_solver.cpp | 220 | ||||
-rw-r--r-- | extern/softbody/src/admmpd_solver.h | 37 | ||||
-rw-r--r-- | intern/softbody/admmpd_api.cpp | 2 |
10 files changed, 312 insertions, 89 deletions
diff --git a/extern/softbody/CMakeLists.txt b/extern/softbody/CMakeLists.txt index 08a36882f28..8f6ec97d7dd 100644 --- a/extern/softbody/CMakeLists.txt +++ b/extern/softbody/CMakeLists.txt @@ -35,6 +35,8 @@ set(SRC src/admmpd_lattice.cpp src/admmpd_solver.h src/admmpd_solver.cpp + src/admmpd_collision.h + src/admmpd_collision.cpp ) set(LIB diff --git a/extern/softbody/src/admmpd_collision.cpp b/extern/softbody/src/admmpd_collision.cpp new file mode 100644 index 00000000000..2bce56fa9f0 --- /dev/null +++ b/extern/softbody/src/admmpd_collision.cpp @@ -0,0 +1,33 @@ + + +#include "admmpd_collision.h" + +namespace admmpd { + +void FloorCollider::detect(const Eigen::MatrixXd *x) +{ + (void)(x); + // Can just do detection in jacobian I guess. +} + +void FloorCollider::jacobian( + const Eigen::MatrixXd *x, + std::vector<Eigen::Triplet<double> > *trips_x, + std::vector<Eigen::Triplet<double> > *trips_y, + std::vector<Eigen::Triplet<double> > *trips_z, + std::vector<double> *l) +{ + int nx = x->rows(); + for (int i=0; i<nx; ++i) + { + Eigen::Vector3d p = x->row(i); + if (p[2]>0) + continue; + + int c_idx = l->size(); + trips_z->emplace_back(c_idx,i,1.0); + l->emplace_back(0); + } +} // end floor collider Jacobian + +} // namespace admmpd
\ No newline at end of file diff --git a/extern/softbody/src/admmpd_collision.h b/extern/softbody/src/admmpd_collision.h new file mode 100644 index 00000000000..274be91be6d --- /dev/null +++ b/extern/softbody/src/admmpd_collision.h @@ -0,0 +1,42 @@ + + + +#ifndef _ADMMPD_COLLISION_H +#define _ADMMPD_COLLISION_H + +#include <Eigen/Sparse> +#include <vector> + +namespace admmpd { + +// I'll update this class/structure another day. +// For now let's get something in place to do floor collisions. +// Probably will work better to use uber-collision class for +// all self and obstacle collisions, reducing the amount of +// for-all vertices loops. +class Collider { +public: + virtual void detect( + const Eigen::MatrixXd *x) = 0; + virtual void jacobian( + const Eigen::MatrixXd *x, + std::vector<Eigen::Triplet<double> > *trips_x, + std::vector<Eigen::Triplet<double> > *trips_y, + std::vector<Eigen::Triplet<double> > *trips_z, + std::vector<double> *l) = 0; +}; + +class FloorCollider : public Collider { +public: + void detect(const Eigen::MatrixXd *x); + void jacobian( + const Eigen::MatrixXd *x, + std::vector<Eigen::Triplet<double> > *trips_x, + std::vector<Eigen::Triplet<double> > *trips_y, + std::vector<Eigen::Triplet<double> > *trips_z, + std::vector<double> *l); +}; + +} // namespace admmpd + +#endif //_ADMMPD_COLLISION_H diff --git a/extern/softbody/src/admmpd_energy.cpp b/extern/softbody/src/admmpd_energy.cpp index 978fd565aa4..ea7c68b6dca 100644 --- a/extern/softbody/src/admmpd_energy.cpp +++ b/extern/softbody/src/admmpd_energy.cpp @@ -8,7 +8,7 @@ using namespace Eigen; Lame::Lame() : m_model(0) { - set_from_youngs_poisson(10000000,0.299); + set_from_youngs_poisson(10000000,0.399); } void Lame::set_from_youngs_poisson(double youngs, double poisson) diff --git a/extern/softbody/src/admmpd_lattice.cpp b/extern/softbody/src/admmpd_lattice.cpp index 4323f8fce33..be7a5bc63d2 100644 --- a/extern/softbody/src/admmpd_lattice.cpp +++ b/extern/softbody/src/admmpd_lattice.cpp @@ -3,6 +3,7 @@ #include "admmpd_lattice.h" #include "admmpd_math.h" +#include <iostream> //#include "vdb.h" diff --git a/extern/softbody/src/admmpd_math.cpp b/extern/softbody/src/admmpd_math.cpp index 98b1fcc2520..7c76dfb8b3a 100644 --- a/extern/softbody/src/admmpd_math.cpp +++ b/extern/softbody/src/admmpd_math.cpp @@ -6,26 +6,26 @@ namespace admmpd { namespace barycoords { Eigen::Matrix<double,4,1> point_tet( - const Eigen::Matrix<double,3,1> &p, - const Eigen::Matrix<double,3,1> &a, - const Eigen::Matrix<double,3,1> &b, - const Eigen::Matrix<double,3,1> &c, - const Eigen::Matrix<double,3,1> &d) + const Eigen::Vector3d &p, + const Eigen::Vector3d &a, + const Eigen::Vector3d &b, + const Eigen::Vector3d &c, + const Eigen::Vector3d &d) { auto scalar_triple_product = []( - const Eigen::Matrix<double,3,1> &u, - const Eigen::Matrix<double,3,1> &v, - const Eigen::Matrix<double,3,1> &w ) + const Eigen::Vector3d &u, + const Eigen::Vector3d &v, + const Eigen::Vector3d &w ) { return u.dot(v.cross(w)); }; - Eigen::Matrix<double,3,1> ap = p - a; - Eigen::Matrix<double,3,1> bp = p - b; - Eigen::Matrix<double,3,1> ab = b - a; - Eigen::Matrix<double,3,1> ac = c - a; - Eigen::Matrix<double,3,1> ad = d - a; - Eigen::Matrix<double,3,1> bc = c - b; - Eigen::Matrix<double,3,1> bd = d - b; + Eigen::Vector3d ap = p - a; + Eigen::Vector3d bp = p - b; + Eigen::Vector3d ab = b - a; + Eigen::Vector3d ac = c - a; + Eigen::Vector3d ad = d - a; + Eigen::Vector3d bc = c - b; + Eigen::Vector3d bd = d - b; double va6 = scalar_triple_product(bp, bd, bc); double vb6 = scalar_triple_product(ap, ac, ad); double vc6 = scalar_triple_product(ap, ad, ab); @@ -37,11 +37,11 @@ namespace barycoords { // Checks that it's on the "correct" side of the normal // for each face of the tet. Assumes winding points inward. bool point_in_tet( - const Eigen::Matrix<double,3,1> &p, - const Eigen::Matrix<double,3,1> &a, - const Eigen::Matrix<double,3,1> &b, - const Eigen::Matrix<double,3,1> &c, - const Eigen::Matrix<double,3,1> &d) + const Eigen::Vector3d &p, + const Eigen::Vector3d &a, + const Eigen::Vector3d &b, + const Eigen::Vector3d &c, + const Eigen::Vector3d &d) { using namespace Eigen; auto check_face = []( diff --git a/extern/softbody/src/admmpd_math.h b/extern/softbody/src/admmpd_math.h index d777df5ce2e..25ff2d3114f 100644 --- a/extern/softbody/src/admmpd_math.h +++ b/extern/softbody/src/admmpd_math.h @@ -9,19 +9,19 @@ namespace admmpd { namespace barycoords { -Eigen::Matrix<double,4,1> point_tet( - const Eigen::Matrix<double,3,1> &p, - const Eigen::Matrix<double,3,1> &a, - const Eigen::Matrix<double,3,1> &b, - const Eigen::Matrix<double,3,1> &c, - const Eigen::Matrix<double,3,1> &d); +Eigen::Vector4d point_tet( + const Eigen::Vector3d &p, + const Eigen::Vector3d &a, + const Eigen::Vector3d &b, + const Eigen::Vector3d &c, + const Eigen::Vector3d &d); bool point_in_tet( - const Eigen::Matrix<double,3,1> &p, - const Eigen::Matrix<double,3,1> &a, - const Eigen::Matrix<double,3,1> &b, - const Eigen::Matrix<double,3,1> &c, - const Eigen::Matrix<double,3,1> &d); + const Eigen::Vector3d &p, + const Eigen::Vector3d &a, + const Eigen::Vector3d &b, + const Eigen::Vector3d &c, + const Eigen::Vector3d &d); } // namespace barycoords diff --git a/extern/softbody/src/admmpd_solver.cpp b/extern/softbody/src/admmpd_solver.cpp index eb2222ef3bb..052008121b9 100644 --- a/extern/softbody/src/admmpd_solver.cpp +++ b/extern/softbody/src/admmpd_solver.cpp @@ -4,11 +4,13 @@ #include "admmpd_solver.h" #include "admmpd_lattice.h" #include "admmpd_energy.h" +#include "admmpd_collision.h" #include <Eigen/Geometry> #include <Eigen/Sparse> #include <stdio.h> +#include <iostream> namespace admmpd { using namespace Eigen; @@ -29,34 +31,6 @@ bool Solver::init( return true; } // end init -void Solver::init_solve( - const ADMMPD_Options *options, - ADMMPD_Data *data) -{ - double dt = std::max(0.0, options->timestep_s); - int nx = data->x.rows(); - if (data->M_xbar.rows() != nx) - data->M_xbar.resize(nx,3); - - // velocity and position - data->x_start = data->x; - for( int i=0; i<nx; ++i ) - { - data->v.row(i) += options->grav; - data->M_xbar.row(i) = - data->m[i] * data->x.row(i) + - dt*data->m[i]*data->v.row(i); - } - - // ADMM variables - data->Dx.noalias() = data->D * data->x; - data->z = data->Dx; - data->z_prev = data->z; - data->u.setZero(); - data->u_prev.setZero(); - -} // end init solve - int Solver::solve( const ADMMPD_Options *options, ADMMPD_Data *data) @@ -67,17 +41,14 @@ int Solver::solve( init_solve(options,data); int ne = data->rest_volumes.size(); - Lame lame; + Lame lame; // TODO lame params as input // Begin solver loop int iters = 0; - int max_iters = options->max_iters < 0 ? 10000 : options->max_iters; - for (; iters < max_iters; ++iters) + for (; iters < options->max_admm_iters; ++iters) { // Local step data->Dx.noalias() = data->D * data->x; - data->z_prev.noalias() = data->z; - data->u_prev.noalias() = data->u; for(int i=0; i<ne; ++i) { EnergyTerm().update( @@ -92,18 +63,162 @@ int Solver::solve( } // Global step + update_constraints(options,data); data->b.noalias() = data->M_xbar + data->DtW2*(data->z-data->u); - data->x.noalias() = data->ldltA.solve(data->b); + solve_conjugate_gradients(options,data); } // end solver iters - double dt = std::max(0.0, options->timestep_s); + double dt = options->timestep_s; if (dt > 0.0) data->v.noalias() = (data->x-data->x_start)*(1.0/dt); return iters; } // end solve +void Solver::init_solve( + const ADMMPD_Options *options, + ADMMPD_Data *data) +{ + int nx = data->x.rows(); + if (data->M_xbar.rows() != nx) + data->M_xbar.resize(nx,3); + + // velocity and position + double dt = std::max(0.0, options->timestep_s); + data->x_start = data->x; + for( int i=0; i<nx; ++i ) + { + data->v.row(i) += options->grav; + data->M_xbar.row(i) = + data->m[i] * data->x.row(i) + + dt*data->m[i]*data->v.row(i); + } + + // ADMM variables + data->Dx.noalias() = data->D * data->x; + data->z = data->Dx; + data->u.setZero(); + +} // end init solve + +void Solver::update_constraints( + const ADMMPD_Options *options, + ADMMPD_Data *data) +{ + + std::vector<double> l_coeffs; + std::vector<Eigen::Triplet<double> > trips_x; + std::vector<Eigen::Triplet<double> > trips_y; + std::vector<Eigen::Triplet<double> > trips_z; + + // TODO collision detection + FloorCollider().jacobian( + &data->x, + &trips_x, + &trips_y, + &trips_z, + &l_coeffs); + + // Check number of constraints. + // If no constraints, clear Jacobian. + int nx = data->x.rows(); + int nc = l_coeffs.size(); + if (nc==0) + { + data->l.setZero(); + for (int i=0; i<3; ++i) + data->K[i].setZero(); + + return; + } + + // Otherwise update the data. + data->l = Map<VectorXd>(l_coeffs.data(),nc); + data->K[0].resize(nc,nx); + data->K[0].setFromTriplets(trips_x.begin(),trips_x.end()); + data->K[1].resize(nc,nx); + data->K[1].setFromTriplets(trips_y.begin(),trips_y.end()); + data->K[2].resize(nc,nx); + data->K[2].setFromTriplets(trips_z.begin(),trips_z.end()); + +} // end update constraints + +void Solver::solve_conjugate_gradients( + const ADMMPD_Options *options, + ADMMPD_Data *data) +{ + // If we don't have any constraints, + // we don't need to perform CG + int nnz = std::max(std::max( + data->K[0].nonZeros(), + data->K[1].nonZeros()), + data->K[2].nonZeros()); + if (nnz==0) + { + data->x.noalias() = data->ldltA.solve(data->b); + return; + } + + // Inner product of matrices interpreted + // if they were instead vectorized + auto mat_inner = []( + const MatrixXd &A, + const MatrixXd &B ) + { + double dot = 0.0; + int nr = std::min(A.rows(), B.rows()); + for( int i=0; i<nr; ++i ) + for(int j=0; j<3; ++j) + dot += A(i,j)*B(i,j); + + return dot; + }; + + double eps = options->min_res; + MatrixXd b = data->b; + int nv = b.rows(); + RowSparseMatrix<double> A[3]; + MatrixXd r(b.rows(),3); + MatrixXd z(nv,3); + MatrixXd p(nv,3); + MatrixXd Ap(nv,3); + + for (int i=0; i<3; ++i) + { + RowSparseMatrix<double> Kt = data->K[i].transpose(); + A[i] = data->A + data->spring_k*RowSparseMatrix<double>(Kt*data->K[i]); + b.col(i) += data->spring_k*Kt*data->l; + r.col(i) = b.col(i) - A[i]*data->x.col(i); + } + z = data->ldltA.solve(r); + p = z; + + for (int iter=0; iter<options->max_cg_iters; ++iter) + { + for( int i=0; i<3; ++i ) + Ap.col(i) = A[i]*p.col(i); + + double p_dot_Ap = mat_inner(p,Ap); + if( p_dot_Ap==0.0 ) + break; + + double zk_dot_rk = mat_inner(z,r); + if( zk_dot_rk==0.0 ) + break; + + double alpha = zk_dot_rk / p_dot_Ap; + data->x += alpha * p; + r -= alpha * Ap; + if( r.lpNorm<Infinity>() < eps ) + break; + + z = data->ldltA.solve(r); + double beta = mat_inner(z,r) / zk_dot_rk; + p = z + beta*p; + } +} // end solve conjugate gradients + void Solver::compute_matrices( const ADMMPD_Options *options, ADMMPD_Data *data) @@ -124,33 +239,45 @@ void Solver::compute_matrices( { // TODO get from BodyPoint data->m.resize(nx); data->m.setOnes(); + data->m *= 0.01; } // Add per-element energies to data std::vector< Triplet<double> > trips; append_energies(options,data,trips); int nw = trips.back().row()+1; + double dt2 = std::max(0.0, options->timestep_s * options->timestep_s); // Global matrix data->D.resize(nw,nx); data->D.setFromTriplets(trips.begin(), trips.end()); data->Dt = data->D.transpose(); - VectorXd w_diag = Map<VectorXd>(data->weights.data(), data->weights.size()); - data->DtW2 = data->Dt * w_diag.asDiagonal() * w_diag.asDiagonal(); + VectorXd wd = Map<VectorXd>(data->weights.data(), data->weights.size()); + RowSparseMatrix<double> W2(nw,nw); + VectorXi W_nnz = VectorXi::Ones(nw); + W2.reserve(W_nnz); + for(int i=0; i<nw; ++i) + W2.coeffRef(i,i) = data->weights[i]*data->weights[i]; + + data->DtW2 = dt2 * data->Dt * W2; data->A = data->DtW2 * data->D; - data->A.diagonal() += data->m; + for (int i=0; i<nx; ++i) + data->A.coeffRef(i,i) += data->m[i]; + data->ldltA.compute(data->A); data->b.resize(nx,3); data->b.setZero(); + data->spring_k = options->mult_k*data->A.diagonal().maxCoeff(); + data->l = VectorXd::Zero(1); + for (int i=0; i<3; ++i) + data->K[i].resize(1,nx); + + // ADMM variables data->z.resize(nw,3); data->z.setZero(); - data->z_prev.resize(nw,3); - data->z_prev.setZero(); data->u.resize(nw,3); data->u.setZero(); - data->u_prev.resize(nw,3); - data->u_prev.setZero(); } // end compute matrices @@ -171,12 +298,11 @@ void Solver::append_energies( int energy_index = 0; for (int i=0; i<nt; ++i) { + RowVector4i ele = data->tets.row(i); + data->rest_volumes.emplace_back(); data->weights.emplace_back(); - int energy_dim = 0; - - RowVector4i ele = data->tets.row(i); - energy_dim = EnergyTerm().init_tet( + int energy_dim = EnergyTerm().init_tet( energy_index, lame, ele, @@ -197,4 +323,4 @@ void Solver::append_energies( } } // end append energies -} // namespace admmpd +} // namespace admmpd
\ No newline at end of file diff --git a/extern/softbody/src/admmpd_solver.h b/extern/softbody/src/admmpd_solver.h index 10dc103719c..ec836d3c466 100644 --- a/extern/softbody/src/admmpd_solver.h +++ b/extern/softbody/src/admmpd_solver.h @@ -13,25 +13,31 @@ namespace admmpd { struct ADMMPD_Options { double timestep_s; - int max_iters; + int max_admm_iters; + int max_cg_iters; + double mult_k; // stiffness multiplier for constraints + double min_res; // min residual for CG solver Eigen::Vector3d grav; ADMMPD_Options() : - timestep_s(1.0/100.0), // TODO: Figure out delta time from blender api! - max_iters(20), + timestep_s(1.0/100.0), // TODO: Figure out delta time from blender api + max_admm_iters(20), + max_cg_iters(10), + mult_k(3.0), + min_res(1e-4), grav(0,0,-9.8) {} }; struct ADMMPD_Data { // Input: - Eigen::MatrixXi tets; // elements lattice, t x 4 - Eigen::MatrixXd x; // vertices of lattice, n x 3 + Eigen::MatrixXi tets; // elements t x 4 + Eigen::MatrixXd x; // vertices, n x 3 + Eigen::MatrixXd v; // velocity, n x 3 TODO: from cache // Set in compute_matrices: Eigen::MatrixXd x_start; // x at beginning of timestep, n x 3 - Eigen::MatrixXd v; // velocity of lattice mesh, n x 3 - Eigen::VectorXd m; // masses of lattice verts, n x 1 - Eigen::MatrixXd z, z_prev; // ADMM z variable - Eigen::MatrixXd u, u_prev; // ADMM u aug lag with W inv + Eigen::VectorXd m; // masses, n x 1 TODO: from BodyPoint + Eigen::MatrixXd z; // ADMM z variable + Eigen::MatrixXd u; // ADMM u aug lag with W inv Eigen::MatrixXd M_xbar; // M*(x + dt v) Eigen::MatrixXd Dx; // D * x Eigen::MatrixXd b; // M xbar + DtW2(z-u) @@ -40,7 +46,10 @@ struct ADMMPD_Data { RowSparseMatrix<double> Dt; // transpose reduction matrix RowSparseMatrix<double> DtW2; // D'W^2 RowSparseMatrix<double> A; // M + D'W^2D + RowSparseMatrix<double> K[3]; // constraint Jacobian + Eigen::VectorXd l; // constraint rhs (Kx=l) Eigen::SimplicialLDLT<Eigen::SparseMatrix<double> > ldltA; + double spring_k; // Set in append_energies: std::vector<Eigen::Vector2i> indices; // per-energy index into D (row, num rows) std::vector<double> rest_volumes; // per-energy rest volume @@ -67,10 +76,20 @@ public: protected: + void update_constraints( + const ADMMPD_Options *options, + ADMMPD_Data *data); + void init_solve( const ADMMPD_Options *options, ADMMPD_Data *data); + // Global step with CG: + // 1/2||Ax-b||^2 + k/2||Kx-l||^2 + void solve_conjugate_gradients( + const ADMMPD_Options *options, + ADMMPD_Data *data); + void compute_lattice( const ADMMPD_Options *options, ADMMPD_Data *data); diff --git a/intern/softbody/admmpd_api.cpp b/intern/softbody/admmpd_api.cpp index 7f5a238214e..5963c4f71c8 100644 --- a/intern/softbody/admmpd_api.cpp +++ b/intern/softbody/admmpd_api.cpp @@ -128,7 +128,7 @@ void admmpd_to_bodypoint( bpi.pos[0] = xi[0]; bpi.pos[1] = xi[1]; bpi.pos[2] = xi[2]; - bpi.vec[0] = vi[0]; + bpi.vec[0] = vi[0]; // vec is velocity? bpi.vec[1] = vi[1]; bpi.vec[2] = vi[2]; } |