diff options
Diffstat (limited to 'extern/softbody/src/admmpd_collision.cpp')
-rw-r--r-- | extern/softbody/src/admmpd_collision.cpp | 282 |
1 files changed, 178 insertions, 104 deletions
diff --git a/extern/softbody/src/admmpd_collision.cpp b/extern/softbody/src/admmpd_collision.cpp index 78889b810b8..c6751b3c279 100644 --- a/extern/softbody/src/admmpd_collision.cpp +++ b/extern/softbody/src/admmpd_collision.cpp @@ -26,83 +26,110 @@ VFCollisionPair::VFCollisionPair() : q_bary(0,0,0) {} -void Collision::ObstacleData::clear() { - V = MatrixXd(); - F = MatrixXi(); - sdf = Discregrid::CubicLagrangeDiscreteGrid(); -} - - -bool Collision::set_obstacles( - const float *v0, - const float *v1, - int nv, - const unsigned int *faces, - int nf, - std::string *err) +bool Collision::ObstacleData::compute_sdf(int idx) { - (void)(v0); - if (nv==0 || nf==0) - { - if (err) { *err = "Collision obstacle has no verts or faces"; } - obsdata.clear(); + if (idx < 0 || idx >x1.size()) { return false; } - std::vector<double> v1_dbl(nv*3); - Eigen::AlignedBox<double,3> domain; - - if (obsdata.V.rows() != nv) - obsdata.V.resize(nv,3); - for (int i=0; i<nv; ++i) - { - for (int j=0; j<3; ++j) - { - obsdata.V(i,j) = v1[i*3+j]; - v1_dbl[i*3+j] = v1[i*3+j]; - } - domain.extend(obsdata.V.row(i).transpose()); - } - - if (obsdata.F.rows() != nf) - obsdata.F.resize(nf,3); - for (int i=0; i<nf; ++i) - { - for (int j=0; j<3; ++j) - obsdata.F(i,j) = faces[i*3+j]; + // There was an error in init + if (box[idx].isEmpty()) { + return false; } - // Is the mesh closed? - Discregrid::TriangleMesh tm(v1_dbl.data(), faces, nv, nf); + // Test that the mesh is closed + Discregrid::TriangleMesh tm( + (double const*)x1[idx].data(), + (unsigned int const*)F[idx].data(), + x1[idx].rows(), F[idx].rows()); if (!tm.is_closed()) { - if (err) { *err = "Collision obstacle not a closed mesh - ignoring"; } - obsdata.clear(); return false; } // Generate signed distance field - { - Discregrid::MeshDistance md(tm); - domain.max() += 1e-3 * domain.diagonal().norm() * Eigen::Vector3d::Ones(); - domain.min() -= 1e-3 * domain.diagonal().norm() * Eigen::Vector3d::Ones(); - std::array<unsigned int, 3> resolution; - resolution[0] = 30; resolution[1] = 30; resolution[2] = 30; - obsdata.sdf = Discregrid::CubicLagrangeDiscreteGrid(domain, resolution); - auto func = Discregrid::DiscreteGrid::ContinuousFunction{}; - std::vector<std::thread::id> thread_map; - md.set_thread_map(&thread_map); - func = [&md](Eigen::Vector3d const& xi) { - return md.signedDistanceCached(xi); - }; - obsdata.sdf.addFunction(func, &thread_map, false); + Discregrid::MeshDistance md(tm); + std::array<unsigned int, 3> resolution; + resolution[0] = 30; resolution[1] = 30; resolution[2] = 30; + sdf[idx] = Discregrid::CubicLagrangeDiscreteGrid(box[idx], resolution); + auto func = Discregrid::DiscreteGrid::ContinuousFunction{}; + std::vector<std::thread::id> thread_map; + md.set_thread_map(&thread_map); + func = [&md](Eigen::Vector3d const& xi) { + return md.signedDistanceCached(xi); + }; + sdf[idx].addFunction(func, &thread_map, false); + + if (sdf[idx].nCells()==0) { + return false; } + return true; +} - if (obsdata.sdf.nCells()==0) { - if (err) { *err = "SDF gen failed for collision obstacle"; } - obsdata.clear(); +bool Collision::set_obstacles( + std::vector<Eigen::MatrixXd> &v0, + std::vector<Eigen::MatrixXd> &v1, + std::vector<Eigen::MatrixXi> &F, + std::string *err) +{ + if (v0.size() != v1.size() || v0.size() != F.size()) { + if (err) { *err = "Bad dimensions on obstacle input"; } return false; } + // Copy the obstacle data from the input to the stored + // data container. If the vertex locations have changed, + // we need to recompute the SDF. Otherwise, leave it as is. + int n_obs_new = v0.size(); + int n_obs_old = obsdata.x0.size(); + obsdata.sdf.resize(n_obs_new); + obsdata.x0.resize(n_obs_new); + obsdata.x1.resize(n_obs_new); + obsdata.F.resize(n_obs_new); + obsdata.box.resize(n_obs_new); + + // We can use isApprox for testing if the obstacle has + // moved from the last call to set_obstacles. The SDF + // has limited accuracy anyway... + double approx_eps = 1e-6; + for (int i=0; i<n_obs_new; ++i) { + + bool reset_obs = false; + if (i >= n_obs_old) { + reset_obs=true; // is new obs + } + else if (!obsdata.x1[i].isApprox(v1[i],approx_eps) || + !obsdata.x0[i].isApprox(v0[i],approx_eps)) { + reset_obs = true; // is different than before + } + + if (reset_obs) { + + obsdata.box[i].setEmpty(); + int nv = v1[i].rows(); + for (int j=0; j<nv; ++j) { + obsdata.box[i].extend(v1[i].row(j).transpose()); + } + obsdata.box[i].max() += 1e-3 * obsdata.box[i].diagonal().norm() * Eigen::Vector3d::Ones(); + obsdata.box[i].min() -= 1e-3 * obsdata.box[i].diagonal().norm() * Eigen::Vector3d::Ones(); + + obsdata.sdf[i] = SDFType(); // clear old sdf + obsdata.x0[i] = v0[i]; + obsdata.x1[i] = v1[i]; + obsdata.F[i] = F[i].cast<unsigned int>(); + + // Determine if the triangle mesh is closed or not. + // We want to provide a warning if it is. + Discregrid::TriangleMesh tm( + (double const*)obsdata.x1[i].data(), + (unsigned int const*)obsdata.F[i].data(), + obsdata.x1[i].rows(), obsdata.F[i].rows()); + if (!tm.is_closed()) { + obsdata.box[i].setEmpty(); + if (err) { *err = "Collision obstacle not a closed mesh - ignoring"; } + } + } + } + return true; } // end add obstacle @@ -121,29 +148,31 @@ Collision::detect_against_obs( (void)(data); (void)(pt_t0); - std::pair<bool,VFCollisionPair> ret = - std::make_pair(false, VFCollisionPair()); - - if (!obs->has_obs()) - return ret; + int n_obs = obs->num_obs(); + if (n_obs==0) { + return std::make_pair(false, VFCollisionPair()); + } - // So I feel bad because we're using the SDF only - // for inside/outside query. Unfortunately this implementation - // doesn't store the face indices within the grid cells, so - // the interpolate function won't return the nearest - // face at the gradient + distance. - Vector3d n; - double dist = obs->sdf.interpolate(0, pt_t1, &n); - if (dist > 0) + for (int i=0; i<n_obs; ++i) + { + if (obs->sdf[i].nCells()==0) { + continue; // not initialized + } + Vector3d n; + double dist = obs->sdf[i].interpolate(0, pt_t1, &n); + if (dist > 0) { continue; } // not colliding + + std::pair<bool,VFCollisionPair> ret = + std::make_pair(true, VFCollisionPair()); + ret.first = true; + ret.second.q_idx = -1; + ret.second.q_is_obs = true; + ret.second.q_bary.setZero(); + ret.second.q_pt = pt_t1 - dist*n; + ret.second.q_n = n.normalized(); return ret; - - ret.first = true; - ret.second.q_idx = -1; - ret.second.q_is_obs = true; - ret.second.q_bary.setZero(); - ret.second.q_pt = pt_t1 - dist*n; - ret.second.q_n = n.normalized(); - return ret; + } + return std::make_pair(false, VFCollisionPair()); } int EmbeddedMeshCollision::detect( @@ -153,17 +182,35 @@ int EmbeddedMeshCollision::detect( const Eigen::MatrixXd *x0, const Eigen::MatrixXd *x1) { - if (!mesh) + if (!mesh) { return 0; + } - if (mesh->type() != MESHTYPE_EMBEDDED) + if (mesh->type() != MESHTYPE_EMBEDDED) { return 0; + } - // Do we even need to process collisions? - if (!this->obsdata.has_obs() && !options->self_collision) - { - if (x1->col(1).minCoeff() > options->floor) - { + // Compute SDFs if the mesh is intersecting + // the associated obstacle. The sdf generation is internally threaded, + // but it might be faster to thread the different SDFs. + bool has_obs_intersection = false; + int n_obs = obsdata.num_obs(); + AlignedBox<double,3> mesh_box = data->col.prim_tree.bounds(); + for (int i=0; i<n_obs; ++i) { + AlignedBox<double,3> &box = obsdata.box[i]; + if (box.isEmpty()) { continue; } + if (!box.intersects(mesh_box)) { continue; } + has_obs_intersection = true; + // Do we need to generate a new SDF? + if (obsdata.sdf[i].nCells()==0) { + obsdata.compute_sdf(i); + } + } + + // Do we even need to process collisions and launch + // the per-vertex threads? + if (!has_obs_intersection && !options->self_collision) { + if (x1->col(2).minCoeff() > options->floor) { return 0; } } @@ -171,8 +218,9 @@ int EmbeddedMeshCollision::detect( // We store the results of the collisions in a per-vertex buffer. // This is a workaround so we can create them in threads. int nev = mesh->rest_facet_verts()->rows(); - if ((int)per_vertex_pairs.size() != nev) + if ((int)per_vertex_pairs.size() != nev) { per_vertex_pairs.resize(nev, std::vector<VFCollisionPair>()); + } // // Thread data for detection @@ -215,6 +263,10 @@ int EmbeddedMeshCollision::detect( Vector3d pt_t0 = td->mesh->get_mapped_facet_vertex(td->x0,vi); Vector3d pt_t1 = td->mesh->get_mapped_facet_vertex(td->x1,vi); +// if (td->options->log_level >= LOGLEVEL_DEBUG) { +// printf("\tDetecting collisions for emb vertex %d: %f %f %f\n", vi, pt_t1[0], pt_t1[1], pt_t1[2]); +// } + // Special case, check if we are below the floor if (pt_t1[2] < td->options->floor) { @@ -231,7 +283,8 @@ int EmbeddedMeshCollision::detect( } // Detect against obstacles - if (td->obsdata->has_obs()) + bool had_obstacle_collision = false; + if (td->obsdata->num_obs()>0) { std::pair<bool,VFCollisionPair> pt_hit_obs = td->collision->detect_against_obs( @@ -247,13 +300,14 @@ int EmbeddedMeshCollision::detect( pt_hit_obs.second.p_is_obs = false; pt_res.emplace_back(vi,vi_pairs.size()); vi_pairs.emplace_back(pt_hit_obs.second); + had_obstacle_collision = true; } } - // We perform self collision if the self_collision flag is true and either: - // a) the set of vertices (vertex group) to do self collision is empty - // b) the vertex is in the set of self collision vertices - bool do_self_collision = td->options->self_collision; + // We perform self collision if the self_collision flag is true and: + // a) there was no obstacle collision + // b) the vertex is in the set of self collision vertices (or its empty) + bool do_self_collision = !had_obstacle_collision && td->options->self_collision; if (do_self_collision) { if (td->data->col.selfcollision_verts.size()>0) { do_self_collision = td->data->col.selfcollision_verts.count(vi)>0; @@ -304,15 +358,24 @@ int EmbeddedMeshCollision::detect( // all of the BVH traversals and the other threads do none. // I haven't actually profiled this, so maybe I'm wrong. int max_threads = std::max(1, std::min(nev, admmpd::get_max_threads(options))); + if (options->log_level >= LOGLEVEL_DEBUG) { + max_threads = 1; + } const auto & per_thread_function = [&per_embedded_vertex_detect,&max_threads,&nev] (DetectThreadData *td, int thread_idx) { - int slice = std::max((int)std::round((nev+1)/double(max_threads)),1); + float slice_f = float(nev+1) / float(max_threads); + int slice = std::max((int)std::ceil(slice_f),1); for (int i=0; i<slice; ++i) { int vi = i*max_threads + thread_idx; - if (vi >= nev) + + // Yeah okay I know this is dumb and I can just do a better job + // of calculating the slice. We can save thread optimization + // for the future, since this will be written different anyway. + if (vi >= nev) { break; + } per_embedded_vertex_detect(td,thread_idx,vi); } @@ -321,8 +384,12 @@ int EmbeddedMeshCollision::detect( // Launch threads std::vector<std::thread> pool; per_thread_results.resize(max_threads, std::vector<Vector2i>()); - for (int i=0; i<max_threads; ++i) + for (int i=0; i<max_threads; ++i) { + per_thread_results[i].reserve(std::max(1,nev/max_threads)); + } + for (int i=0; i<max_threads; ++i) { pool.emplace_back(per_thread_function,&thread_data,i); + } // Combine parallel results vf_pairs.clear(); @@ -439,8 +506,9 @@ EmbeddedMeshCollision::detect_against_self( x1->row(tet[1]), x1->row(tet[2]), x1->row(tet[3])); - if (barys.minCoeff()<-1e-8 || barys.sum() > 1+1e-8) + if (barys.minCoeff()<-1e-8 || barys.sum() > 1+1e-8) { throw std::runtime_error("EmbeddedMeshCollision: Bad tet barys"); + } const MatrixXd *rest_V0 = mesh->rest_prim_verts(); Vector3d rest_pt = @@ -454,8 +522,9 @@ EmbeddedMeshCollision::detect_against_self( if (rest_emb_sdf) { double dist = rest_emb_sdf->interpolate(0, rest_pt); - if (dist > 0) + if (dist > 0) { return ret; // nope + } } // Find triangle surface projection that doesn't @@ -469,23 +538,28 @@ EmbeddedMeshCollision::detect_against_self( skip_tri_inds); mesh->emb_rest_tree()->traverse(nearest_tri); - if (nearest_tri.output.prim<0) + if (nearest_tri.output.prim<0) { throw std::runtime_error("EmbeddedMeshCollision: Failed to find triangle"); + } ret.first = true; ret.second.p_idx = pt_idx; ret.second.p_is_obs = false; ret.second.q_idx = nearest_tri.output.prim; ret.second.q_is_obs = false; - ret.second.q_pt = nearest_tri.output.pt_on_tri; // Compute barycoords of projection RowVector3i f = mesh->facets()->row(nearest_tri.output.prim); Vector3d v3[3] = { emb_V0->row(f[0]), emb_V0->row(f[1]), emb_V0->row(f[2]) }; ret.second.q_bary = geom::point_triangle_barys<double>( nearest_tri.output.pt_on_tri, v3[0], v3[1], v3[2]); - if (ret.second.q_bary.minCoeff()<-1e-8 || ret.second.q_bary.sum() > 1+1e-8) + if (ret.second.q_bary.minCoeff()<-1e-8 || ret.second.q_bary.sum() > 1+1e-8) { throw std::runtime_error("EmbeddedMeshCollision: Bad triangle barys"); + } + + // q_pt is not used for self collisions, but we'll use it + // to define the tet constraint stencil. + ret.second.q_pt = pt_t1; return ret; } @@ -581,7 +655,7 @@ void EmbeddedMeshCollision::linearize( //int nx = x->rows(); d->reserve((int)d->size() + np); trips->reserve((int)trips->size() + np*3*4); - double eta = std::max(0.0,options->collision_thickness); + double eta = 0;//std::max(0.0,options->collision_thickness); for (int i=0; i<np; ++i) { |