Working on improved interconnection strategy

This commit is contained in:
tamasmeszaros 2019-03-04 18:32:28 +01:00
parent f2f513dd3e
commit c2d5a8d03b
2 changed files with 277 additions and 108 deletions

View file

@ -594,6 +594,10 @@ inline ClusteredPoints cluster(
return cluster(points, indices, pred, max_points);
}
ClusteredPoints cluster_nearest2d(
const PointSet& points, const std::vector<unsigned>& indices,
double dist,
unsigned max_points = 0);
// This class will hold the support tree meshes with some additional bookkeeping
// as well. Various parts of the support geometry are stored separately and are
@ -818,6 +822,9 @@ long cluster_centroid(const ClusterEl& clust,
// distance for the two points and add the distance to the averages.
// The point with the smallest average than wins.
// The complexity should be O(n^2) but we will mostly apply this function
// for small clusters only (cca 3 elements)
std::vector<bool> sel(clust.size(), false); // create full zero bitmask
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
@ -1217,68 +1224,109 @@ class SLASupportTree::Algorithm {
}
// Helper function for interconnecting two pillars with zig-zag bridges.
// This is not an individual step.
bool interconnect(const Pillar& pillar, const Pillar& nextpillar)
{
// Get the starting heads corresponding to the given pillars
const Head& phead = m_result.pillar_head(pillar.id);
const Head& nextphead = m_result.pillar_head(nextpillar.id);
Vec3d sj = phead.junction_point();
sj(Z) = std::min(sj(Z), nextphead.junction_point()(Z));
Vec3d ej = nextpillar.endpoint;
double pillar_dist = distance(Vec2d{sj(X), sj(Y)},
Vec2d{ej(X), ej(Y)});
double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope);
ej(Z) = sj(Z) + zstep;
double chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r);
double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope);
// We need to get the starting point of the zig-zag pattern. We have to
// be aware that the two head junctions are at different heights. We
// may start from the lowest junction and call it a day but this
// strategy would leave unconnected a lot of pillar duos where the
// shorter pillar is too short to start a new bridge but the taller
// pillar could still be bridged with the shorter one.
bool was_connected = false;
// If the pillars are so close that they touch each other,
// there is no need to bridge them together.
if(pillar_dist > 2*m_cfg.head_back_radius_mm &&
bridge_distance < m_cfg.max_bridge_length_mm) {
while(sj(Z) > pillar.endpoint(Z) + m_cfg.base_radius_mm &&
ej(Z) > nextpillar.endpoint(Z) + m_cfg.base_radius_mm)
Vec3d supper = phead.junction_point();
Vec3d slower = nextphead.junction_point();
Vec3d eupper = pillar.endpoint;
Vec3d elower = nextpillar.endpoint;
double zmin = m_result.ground_level + m_cfg.base_height_mm;
eupper(Z) = std::max(eupper(Z), zmin);
elower(Z) = std::max(elower(Z), zmin);
// The usable length of both pillars should be positive
if(slower(Z) - elower(Z) < 0) return false;
if(supper(Z) - eupper(Z) < 0) return false;
double pillar_dist = distance(Vec2d{slower(X), slower(Y)},
Vec2d{supper(X), supper(Y)});
double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope);
double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope);
if(pillar_dist < 2*m_cfg.head_back_radius_mm) return false;
if(bridge_distance > m_cfg.max_bridge_length_mm) return false;
if(supper(Z) < slower(Z)) supper.swap(slower);
if(eupper(Z) < elower(Z)) eupper.swap(elower);
double startz = 0, endz = 0;
startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z);
endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z);
if(slower(Z) - eupper(Z) < std::abs(zstep)) { // no space for even one cross
// Get max available space
startz = std::min(supper(Z), slower(Z) - zstep);
endz = std::max(eupper(Z) + zstep, elower(Z));
// Align to center
double available_dist = (startz - endz);
double rounds = std::floor(available_dist / std::abs(zstep));
startz -= 0.5 * (available_dist - rounds * std::abs(zstep));;
}
auto pcm = m_cfg.pillar_connection_mode;
bool docrosses =
pcm == PillarConnectionMode::cross ||
(pcm == PillarConnectionMode::dynamic &&
pillar_dist > 2*m_cfg.base_radius_mm);
// 'sj' means starting junction, 'ej' is the end junction of a bridge.
// They will be swapped in every iteration thus the zig-zag pattern.
// According to a config parameter, a second bridge may be added which
// results in a cross connection between the pillars.
Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep;
while(ej(Z) >= endz) {
if(bridge_mesh_intersect(sj,
dirv(sj, ej),
pillar.r) >= bridge_distance)
{
if(chkd >= bridge_distance) {
m_result.add_bridge(sj, ej, pillar.r);
was_connected = true;
auto pcm = m_cfg.pillar_connection_mode;
// double bridging: (crosses)
if( pcm == PillarConnectionMode::cross ||
(pcm == PillarConnectionMode::dynamic &&
pillar_dist > 2*m_cfg.base_radius_mm))
{
// If the columns are close together, no need to
// double bridge them
Vec3d bsj(ej(X), ej(Y), sj(Z));
Vec3d bej(sj(X), sj(Y), ej(Z));
// need to check collision for the cross stick
double backchkd = bridge_mesh_intersect(
bsj, dirv(bsj, bej), pillar.r);
if(backchkd >= bridge_distance) {
m_result.add_bridge(bsj, bej, pillar.r);
}
}
}
sj.swap(ej);
ej(Z) = sj(Z) + zstep;
chkd = bridge_mesh_intersect(sj, dirv(sj, ej), pillar.r);
m_result.add_bridge(sj, ej, pillar.r);
was_connected = true;
}
// double bridging: (crosses)
if(docrosses) {
Vec3d sjback(ej(X), ej(Y), sj(Z));
Vec3d ejback(sj(X), sj(Y), ej(Z));
if(sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) &&
bridge_mesh_intersect(sjback,
dirv(sjback, ejback),
pillar.r) >= bridge_distance)
{
// need to check collision for the cross stick
m_result.add_bridge(sjback, ejback, pillar.r);
was_connected = true;
}
}
sj.swap(ej);
ej(Z) = sj(Z) + zstep;
}
return was_connected;
}
long search_nearest(const Vec3d& querypoint)
// Search for the nearest pillar to the given query point which is not
// further than max_dist. The result is the pillar ID or -1 if nothing was
// found. The pillar search is carried out using the m_pillar_index
// structure.
long search_nearest(const Vec3d& querypoint, double max_dist)
{
SpatIndex spindex = m_pillar_index;
@ -1318,7 +1366,7 @@ class SLASupportTree::Algorithm {
// WARNING: previously, max_bridge_length was divided by two.
// I don't remember if this was intentional or by accident. There
// is no logical reason why it shouldn't be used directly.
if(bridge_ep(Z) <= minz || d > m_cfg.max_bridge_length_mm) break;
if(bridge_ep(Z) <= minz || d > max_dist) break;
double chkd = bridge_mesh_intersect(querypoint,
dirv(querypoint, bridge_ep),
@ -1331,6 +1379,10 @@ class SLASupportTree::Algorithm {
return nearest_id;
}
inline long search_nearest(const Vec3d& qp) {
return search_nearest(qp, m_cfg.max_bridge_length_mm);
}
void connect_to_nearhead(const Head& head, const Head& nearhead) {
Vec3d hjp = head.junction_point();
Vec3d headjp = hjp;
@ -1579,7 +1631,10 @@ public:
auto hit = bridge_mesh_intersect(headjp, n, r);
if(std::isinf(hit)) ground_head_indices.emplace_back(i);
else m_iheads_onmodel.emplace_back(std::make_pair(i, hit));
else {
if(m_cfg.ground_facing_only) head.invalidate();
m_iheads_onmodel.emplace_back(std::make_pair(i, hit));
}
}
// We want to search for clusters of points that are far enough
@ -1675,7 +1730,8 @@ public:
sidehead.transform();
Vec3d sidehead_jp = sidehead.junction_point();
long nearest_id = search_nearest(sidehead_jp);
long nearest_id = search_nearest(sidehead_jp,
m_cfg.max_bridge_length_mm/2);
// at this point we either have our pillar index or we have
// to connect the sidehead to the ground
@ -1714,59 +1770,15 @@ public:
// points spatially and create the bridge stick from one endpoint to
// another.
ClusterEl rem = cl_centroids;
ClusterEl ring;
while(!rem.empty()) { // loop until all the points belong to some ring
m_thr();
std::sort(rem.begin(), rem.end());
auto& points = m_points;
auto newring = pts_convex_hull(rem,
[&points](unsigned i) {
auto&& p = points.row(i);
return Vec2d(p(X), p(Y)); // project to 2D in along Z axis
});
if(!ring.empty()) {
// inner ring is now in 'newring' and outer ring is in 'ring'
SpatIndex innerring;
for(unsigned i : newring) { m_thr();
const Pillar& pill = m_result.head_pillar(i);
assert(pill.id >= 0);
innerring.insert(pill.endpoint, unsigned(pill.id));
}
// For all pillars in the outer ring find the closest in the
// inner ring and connect them. This will create the spider web
// fashioned connections between pillars
for(unsigned i : ring) { m_thr();
const Pillar& outerpill = m_result.head_pillar(i);
auto res = innerring.nearest(outerpill.endpoint,
unsigned(innerring.size()));
for(auto& ne : res) {
const Pillar& innerpill = m_result.pillars()[ne.second];
if(interconnect(outerpill, innerpill)) break;
}
}
}
// no need for newring anymore in the current iteration
ring.swap(newring);
/*std::cout << "ring: \n";
for(auto ri : ring) {
std::cout << ri << " " << " X = " << gnd_head_pt(ri)(X)
<< " Y = " << gnd_head_pt(ri)(Y) << std::endl;
}
std::cout << std::endl;*/
double d = std::cos(m_cfg.bridge_slope) * m_cfg.max_bridge_length_mm;
auto pclusters = cluster_nearest2d(m_points, cl_centroids, d, 3);
for(auto& ring : pclusters) {
// now the ring has to be connected with bridge sticks
for(auto it = ring.begin(), next = std::next(it);
next != ring.end();
++it, ++next)
if(ring.size() > 1)
for(auto it = ring.begin(), next = std::next(it);
next != ring.end();
++it, ++next)
{
m_thr();
const Pillar& pillar = m_result.head_pillar(*it);
@ -1774,13 +1786,81 @@ public:
interconnect(pillar, nextpillar);
}
auto sring = ring; ClusterEl tmp;
std::sort(sring.begin(), sring.end());
std::set_difference(rem.begin(), rem.end(),
sring.begin(), sring.end(),
std::back_inserter(tmp));
rem.swap(tmp);
if(ring.size() > 2) {
const Pillar& firstpillar = m_result.head_pillar(ring.front());
const Pillar& lastpillar = m_result.head_pillar(ring.back());
interconnect(firstpillar, lastpillar);
}
}
// ClusterEl rem = cl_centroids;
// ClusterEl ring;
// while(!rem.empty()) { // loop until all the points belong to some ring
// m_thr();
// std::sort(rem.begin(), rem.end());
// auto& points = m_points;
// auto newring = pts_convex_hull(rem,
// [&points](unsigned i) {
// auto&& p = points.row(i);
// return Vec2d(p(X), p(Y)); // project to 2D in along Z axis
// });
// if(!ring.empty()) {
// // inner ring is now in 'newring' and outer ring is in 'ring'
// SpatIndex innerring;
// for(unsigned i : newring) { m_thr();
// const Pillar& pill = m_result.head_pillar(i);
// assert(pill.id >= 0);
// innerring.insert(pill.endpoint, unsigned(pill.id));
// }
// // For all pillars in the outer ring find the closest in the
// // inner ring and connect them. This will create the spider web
// // fashioned connections between pillars
// for(unsigned i : ring) { m_thr();
// const Pillar& outerpill = m_result.head_pillar(i);
// auto res = innerring.nearest(outerpill.endpoint,
// unsigned(innerring.size()));
// for(auto& ne : res) {
// const Pillar& innerpill = m_result.pillars()[ne.second];
// if(interconnect(outerpill, innerpill)) break;
// }
// }
// }
// // no need for newring anymore in the current iteration
// ring.swap(newring);
// /*std::cout << "ring: \n";
// for(auto ri : ring) {
// std::cout << ri << " " << " X = " << gnd_head_pt(ri)(X)
// << " Y = " << gnd_head_pt(ri)(Y) << std::endl;
// }
// std::cout << std::endl;*/
// // now the ring has to be connected with bridge sticks
// for(auto it = ring.begin(), next = std::next(it);
// next != ring.end();
// ++it, ++next)
// {
// m_thr();
// const Pillar& pillar = m_result.head_pillar(*it);
// const Pillar& nextpillar = m_result.head_pillar(*next);
// interconnect(pillar, nextpillar);
// }
// auto sring = ring; ClusterEl tmp;
// std::sort(sring.begin(), sring.end());
// std::set_difference(rem.begin(), rem.end(),
// sring.begin(), sring.end(),
// std::back_inserter(tmp));
// rem.swap(tmp);
// }
}
// Step: routing the pinheads that would connect to the model surface

View file

@ -347,6 +347,95 @@ PointSet normals(const PointSet& points,
return ret;
}
//template<class Vec> double distance(const Vec& p) {
// return std::sqrt(p.transpose() * p);
//}
//template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
// auto p = pp2 - pp1;
// return distance(p);
//}
// Clustering a set of points by the given criteria
ClusteredPoints cluster_nearest2d(
const sla::PointSet& points, const std::vector<unsigned>& indices,
double dist,
unsigned max_points = 0)
{
namespace bgi = boost::geometry::index;
using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >;
// A spatial index for querying the nearest points
Index3D sindex;
// Build the index
for(unsigned idx : indices) {
Vec3d p = points.row(idx); p(Z) = 0;
sindex.insert( std::make_pair(points.row(idx), idx));
}
using Elems = std::vector<SpatElement>;
// Recursive function for visiting all the points in a given distance to
// each other
std::function<void(Elems&, Elems&)> group =
[&sindex, &group, max_points, dist](Elems& pts, Elems& cluster)
{
for(auto& p : pts) {
std::vector<SpatElement> tmp;
sindex.query(
bgi::nearest(p.first, max_points),
std::back_inserter(tmp)
);
for(auto it = tmp.begin(); it < tmp.end(); ++it)
if(distance(p.first, it->first) > dist) it = tmp.erase(it);
auto cmp = [](const SpatElement& e1, const SpatElement& e2){
return e1.second < e2.second;
};
std::sort(tmp.begin(), tmp.end(), cmp);
Elems newpts;
std::set_difference(tmp.begin(), tmp.end(),
cluster.begin(), cluster.end(),
std::back_inserter(newpts), cmp);
int c = max_points && newpts.size() + cluster.size() > max_points?
int(max_points - cluster.size()) : int(newpts.size());
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
std::sort(cluster.begin(), cluster.end(), cmp);
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
group(newpts, cluster);
}
};
std::vector<Elems> clusters;
for(auto it = sindex.begin(); it != sindex.end();) {
Elems cluster = {};
Elems pts = {*it};
group(pts, cluster);
for(auto& c : cluster) sindex.remove(c);
it = sindex.begin();
clusters.emplace_back(cluster);
}
ClusteredPoints result;
for(auto& cluster : clusters) {
result.emplace_back();
for(auto c : cluster) result.back().emplace_back(c.second);
}
return result;
}
// Clustering a set of points by the given criteria
ClusteredPoints cluster(
const sla::PointSet& points, const std::vector<unsigned>& indices,