1 #include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
2 
3 #include <libslic3r/SLA/SpatIndex.hpp>
4 #include <libslic3r/Optimize/NLoptOptimizer.hpp>
5 #include <boost/log/trivial.hpp>
6 
7 namespace Slic3r {
8 namespace sla {
9 
10 using Slic3r::opt::initvals;
11 using Slic3r::opt::bounds;
12 using Slic3r::opt::StopCriteria;
13 using Slic3r::opt::Optimizer;
14 using Slic3r::opt::AlgNLoptSubplex;
15 using Slic3r::opt::AlgNLoptGenetic;
16 
get_criteria(const SupportTreeConfig & cfg)17 StopCriteria get_criteria(const SupportTreeConfig &cfg)
18 {
19     return StopCriteria{}
20         .rel_score_diff(cfg.optimizer_rel_score_diff)
21         .max_iterations(cfg.optimizer_max_iterations);
22 }
23 
24 template<class C, class Hit = IndexedMesh::hit_result>
min_hit(const C & hits)25 static Hit min_hit(const C &hits)
26 {
27     auto mit = std::min_element(hits.begin(), hits.end(),
28                                 [](const Hit &h1, const Hit &h2) {
29                                     return h1.distance() < h2.distance();
30                                 });
31 
32     return *mit;
33 }
34 
SupportTreeBuildsteps(SupportTreeBuilder & builder,const SupportableMesh & sm)35 SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder &   builder,
36                                              const SupportableMesh &sm)
37     : m_cfg(sm.cfg)
38     , m_mesh(sm.emesh)
39     , m_support_pts(sm.pts)
40     , m_support_nmls(sm.pts.size(), 3)
41     , m_builder(builder)
42     , m_points(sm.pts.size(), 3)
43     , m_thr(builder.ctl().cancelfn)
44 {
45     // Prepare the support points in Eigen/IGL format as well, we will use
46     // it mostly in this form.
47 
48     long i = 0;
49     for (const SupportPoint &sp : m_support_pts) {
50         m_points.row(i)(X) = double(sp.pos(X));
51         m_points.row(i)(Y) = double(sp.pos(Y));
52         m_points.row(i)(Z) = double(sp.pos(Z));
53         ++i;
54     }
55 }
56 
execute(SupportTreeBuilder & builder,const SupportableMesh & sm)57 bool SupportTreeBuildsteps::execute(SupportTreeBuilder &   builder,
58                                     const SupportableMesh &sm)
59 {
60     if(sm.pts.empty()) return false;
61 
62     builder.ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
63 
64     SupportTreeBuildsteps alg(builder, sm);
65 
66     // Let's define the individual steps of the processing. We can experiment
67     // later with the ordering and the dependencies between them.
68     enum Steps {
69         BEGIN,
70         FILTER,
71         PINHEADS,
72         CLASSIFY,
73         ROUTING_GROUND,
74         ROUTING_NONGROUND,
75         CASCADE_PILLARS,
76         MERGE_RESULT,
77         DONE,
78         ABORT,
79         NUM_STEPS
80         //...
81     };
82 
83     // Collect the algorithm steps into a nice sequence
84     std::array<std::function<void()>, NUM_STEPS> program = {
85         [] () {
86             // Begin...
87             // Potentially clear up the shared data (not needed for now)
88         },
89 
90         std::bind(&SupportTreeBuildsteps::filter, &alg),
91 
92         std::bind(&SupportTreeBuildsteps::add_pinheads, &alg),
93 
94         std::bind(&SupportTreeBuildsteps::classify, &alg),
95 
96         std::bind(&SupportTreeBuildsteps::routing_to_ground, &alg),
97 
98         std::bind(&SupportTreeBuildsteps::routing_to_model, &alg),
99 
100         std::bind(&SupportTreeBuildsteps::interconnect_pillars, &alg),
101 
102         std::bind(&SupportTreeBuildsteps::merge_result, &alg),
103 
104         [] () {
105             // Done
106         },
107 
108         [] () {
109             // Abort
110         }
111     };
112 
113     Steps pc = BEGIN;
114 
115     if(sm.cfg.ground_facing_only) {
116         program[ROUTING_NONGROUND] = []() {
117             BOOST_LOG_TRIVIAL(info)
118                 << "Skipping model-facing supports as requested.";
119         };
120     }
121 
122     // Let's define a simple automaton that will run our program.
123     auto progress = [&builder, &pc] () {
124         static const std::array<std::string, NUM_STEPS> stepstr {
125             "Starting",
126             "Filtering",
127             "Generate pinheads",
128             "Classification",
129             "Routing to ground",
130             "Routing supports to model surface",
131             "Interconnecting pillars",
132             "Merging support mesh",
133             "Done",
134             "Abort"
135         };
136 
137         static const std::array<unsigned, NUM_STEPS> stepstate {
138             0,
139             10,
140             30,
141             50,
142             60,
143             70,
144             80,
145             99,
146             100,
147             0
148         };
149 
150         if(builder.ctl().stopcondition()) pc = ABORT;
151 
152         switch(pc) {
153         case BEGIN: pc = FILTER; break;
154         case FILTER: pc = PINHEADS; break;
155         case PINHEADS: pc = CLASSIFY; break;
156         case CLASSIFY: pc = ROUTING_GROUND; break;
157         case ROUTING_GROUND: pc = ROUTING_NONGROUND; break;
158         case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break;
159         case CASCADE_PILLARS: pc = MERGE_RESULT; break;
160         case MERGE_RESULT: pc = DONE; break;
161         case DONE:
162         case ABORT: break;
163         default: ;
164         }
165 
166         builder.ctl().statuscb(stepstate[pc], stepstr[pc]);
167     };
168 
169     // Just here we run the computation...
170     while(pc < DONE) {
171         progress();
172         program[pc]();
173     }
174 
175     return pc == ABORT;
176 }
177 
pinhead_mesh_intersect(const Vec3d & s,const Vec3d & dir,double r_pin,double r_back,double width,double sd)178 IndexedMesh::hit_result SupportTreeBuildsteps::pinhead_mesh_intersect(
179     const Vec3d &s,
180     const Vec3d &dir,
181     double       r_pin,
182     double       r_back,
183     double       width,
184     double       sd)
185 {
186     static const size_t SAMPLES = 8;
187 
188     // Move away slightly from the touching point to avoid raycasting on the
189     // inner surface of the mesh.
190 
191     auto& m = m_mesh;
192     using HitResult = IndexedMesh::hit_result;
193 
194     // Hit results
195     std::array<HitResult, SAMPLES> hits;
196 
197     struct Rings {
198         double rpin;
199         double rback;
200         Vec3d  spin;
201         Vec3d  sback;
202         PointRing<SAMPLES> ring;
203 
204         Vec3d backring(size_t idx) { return ring.get(idx, sback, rback); }
205         Vec3d pinring(size_t idx) { return ring.get(idx, spin, rpin); }
206     } rings {r_pin + sd, r_back + sd, s, s + width * dir, dir};
207 
208     // We will shoot multiple rays from the head pinpoint in the direction
209     // of the pinhead robe (side) surface. The result will be the smallest
210     // hit distance.
211 
212     ccr::for_each(size_t(0), hits.size(),
213                   [&m, &rings, sd, &hits](size_t i) {
214 
215        // Point on the circle on the pin sphere
216        Vec3d ps = rings.pinring(i);
217        // This is the point on the circle on the back sphere
218        Vec3d p = rings.backring(i);
219 
220        auto &hit = hits[i];
221 
222        // Point ps is not on mesh but can be inside or
223        // outside as well. This would cause many problems
224        // with ray-casting. To detect the position we will
225        // use the ray-casting result (which has an is_inside
226        // predicate).
227 
228        Vec3d n = (p - ps).normalized();
229        auto  q = m.query_ray_hit(ps + sd * n, n);
230 
231        if (q.is_inside()) { // the hit is inside the model
232            if (q.distance() > rings.rpin) {
233                // If we are inside the model and the hit
234                // distance is bigger than our pin circle
235                // diameter, it probably indicates that the
236                // support point was already inside the
237                // model, or there is really no space
238                // around the point. We will assign a zero
239                // hit distance to these cases which will
240                // enforce the function return value to be
241                // an invalid ray with zero hit distance.
242                // (see min_element at the end)
243                hit = HitResult(0.0);
244            } else {
245                // re-cast the ray from the outside of the
246                // object. The starting point has an offset
247                // of 2*safety_distance because the
248                // original ray has also had an offset
249                auto q2 = m.query_ray_hit(ps + (q.distance() + 2 * sd) * n, n);
250                hit = q2;
251            }
252        } else
253            hit = q;
254     });
255 
256     return min_hit(hits);
257 }
258 
bridge_mesh_intersect(const Vec3d & src,const Vec3d & dir,double r,double sd)259 IndexedMesh::hit_result SupportTreeBuildsteps::bridge_mesh_intersect(
260     const Vec3d &src, const Vec3d &dir, double r, double sd)
261 {
262     static const size_t SAMPLES = 8;
263     PointRing<SAMPLES> ring{dir};
264 
265     using Hit = IndexedMesh::hit_result;
266 
267     // Hit results
268     std::array<Hit, SAMPLES> hits;
269 
270     ccr::for_each(size_t(0), hits.size(),
271                  [this, r, src, /*ins_check,*/ &ring, dir, sd, &hits] (size_t i)
272     {
273         Hit &hit = hits[i];
274 
275         // Point on the circle on the pin sphere
276         Vec3d p = ring.get(i, src, r + sd);
277 
278         auto hr = m_mesh.query_ray_hit(p + r * dir, dir);
279 
280         if(/*ins_check && */hr.is_inside()) {
281             if(hr.distance() > 2 * r + sd) hit = Hit(0.0);
282             else {
283                 // re-cast the ray from the outside of the object
284                 hit = m_mesh.query_ray_hit(p + (hr.distance() + EPSILON) * dir, dir);
285             }
286         } else hit = hr;
287     });
288 
289     return min_hit(hits);
290 }
291 
interconnect(const Pillar & pillar,const Pillar & nextpillar)292 bool SupportTreeBuildsteps::interconnect(const Pillar &pillar,
293                                          const Pillar &nextpillar)
294 {
295     // We need to get the starting point of the zig-zag pattern. We have to
296     // be aware that the two head junctions are at different heights. We
297     // may start from the lowest junction and call it a day but this
298     // strategy would leave unconnected a lot of pillar duos where the
299     // shorter pillar is too short to start a new bridge but the taller
300     // pillar could still be bridged with the shorter one.
301     bool was_connected = false;
302 
303     Vec3d supper = pillar.startpoint();
304     Vec3d slower = nextpillar.startpoint();
305     Vec3d eupper = pillar.endpoint();
306     Vec3d elower = nextpillar.endpoint();
307 
308     double zmin = m_builder.ground_level + m_cfg.base_height_mm;
309     eupper(Z) = std::max(eupper(Z), zmin);
310     elower(Z) = std::max(elower(Z), zmin);
311 
312     // The usable length of both pillars should be positive
313     if(slower(Z) - elower(Z) < 0) return false;
314     if(supper(Z) - eupper(Z) < 0) return false;
315 
316     double pillar_dist = distance(Vec2d{slower(X), slower(Y)},
317                                   Vec2d{supper(X), supper(Y)});
318     double bridge_distance = pillar_dist / std::cos(-m_cfg.bridge_slope);
319     double zstep = pillar_dist * std::tan(-m_cfg.bridge_slope);
320 
321     if(pillar_dist < 2 * m_cfg.head_back_radius_mm ||
322         pillar_dist > m_cfg.max_pillar_link_distance_mm) return false;
323 
324     if(supper(Z) < slower(Z)) supper.swap(slower);
325     if(eupper(Z) < elower(Z)) eupper.swap(elower);
326 
327     double startz = 0, endz = 0;
328 
329     startz = slower(Z) - zstep < supper(Z) ? slower(Z) - zstep : slower(Z);
330     endz = eupper(Z) + zstep > elower(Z) ? eupper(Z) + zstep : eupper(Z);
331 
332     if(slower(Z) - eupper(Z) < std::abs(zstep)) {
333         // no space for even one cross
334 
335         // Get max available space
336         startz = std::min(supper(Z), slower(Z) - zstep);
337         endz = std::max(eupper(Z) + zstep, elower(Z));
338 
339         // Align to center
340         double available_dist = (startz - endz);
341         double rounds = std::floor(available_dist / std::abs(zstep));
342         startz -= 0.5 * (available_dist - rounds * std::abs(zstep));
343     }
344 
345     auto pcm = m_cfg.pillar_connection_mode;
346     bool docrosses =
347         pcm == PillarConnectionMode::cross ||
348         (pcm == PillarConnectionMode::dynamic &&
349          pillar_dist > 2*m_cfg.base_radius_mm);
350 
351     // 'sj' means starting junction, 'ej' is the end junction of a bridge.
352     // They will be swapped in every iteration thus the zig-zag pattern.
353     // According to a config parameter, a second bridge may be added which
354     // results in a cross connection between the pillars.
355     Vec3d sj = supper, ej = slower; sj(Z) = startz; ej(Z) = sj(Z) + zstep;
356 
357     // TODO: This is a workaround to not have a faulty last bridge
358     while(ej(Z) >= eupper(Z) /*endz*/) {
359         if(bridge_mesh_distance(sj, dirv(sj, ej), pillar.r) >= bridge_distance)
360         {
361             m_builder.add_crossbridge(sj, ej, pillar.r);
362             was_connected = true;
363         }
364 
365         // double bridging: (crosses)
366         if(docrosses) {
367             Vec3d sjback(ej(X), ej(Y), sj(Z));
368             Vec3d ejback(sj(X), sj(Y), ej(Z));
369             if (sjback(Z) <= slower(Z) && ejback(Z) >= eupper(Z) &&
370                 bridge_mesh_distance(sjback, dirv(sjback, ejback),
371                                       pillar.r) >= bridge_distance) {
372                 // need to check collision for the cross stick
373                 m_builder.add_crossbridge(sjback, ejback, pillar.r);
374                 was_connected = true;
375             }
376         }
377 
378         sj.swap(ej);
379         ej(Z) = sj(Z) + zstep;
380     }
381 
382     return was_connected;
383 }
384 
connect_to_nearpillar(const Head & head,long nearpillar_id)385 bool SupportTreeBuildsteps::connect_to_nearpillar(const Head &head,
386                                                   long        nearpillar_id)
387 {
388     auto nearpillar = [this, nearpillar_id]() -> const Pillar& {
389         return m_builder.pillar(nearpillar_id);
390     };
391 
392     if (m_builder.bridgecount(nearpillar()) > m_cfg.max_bridges_on_pillar)
393         return false;
394 
395     Vec3d headjp = head.junction_point();
396     Vec3d nearjp_u = nearpillar().startpoint();
397     Vec3d nearjp_l = nearpillar().endpoint();
398 
399     double r = head.r_back_mm;
400     double d2d = distance(to_2d(headjp), to_2d(nearjp_u));
401     double d3d = distance(headjp, nearjp_u);
402 
403     double hdiff = nearjp_u(Z) - headjp(Z);
404     double slope = std::atan2(hdiff, d2d);
405 
406     Vec3d bridgestart = headjp;
407     Vec3d bridgeend = nearjp_u;
408     double max_len = r * m_cfg.max_bridge_length_mm / m_cfg.head_back_radius_mm;
409     double max_slope = m_cfg.bridge_slope;
410     double zdiff = 0.0;
411 
412     // check the default situation if feasible for a bridge
413     if(d3d > max_len || slope > -max_slope) {
414         // not feasible to connect the two head junctions. We have to search
415         // for a suitable touch point.
416 
417         double Zdown = headjp(Z) + d2d * std::tan(-max_slope);
418         Vec3d touchjp = bridgeend; touchjp(Z) = Zdown;
419         double D = distance(headjp, touchjp);
420         zdiff = Zdown - nearjp_u(Z);
421 
422         if(zdiff > 0) {
423             Zdown -= zdiff;
424             bridgestart(Z) -= zdiff;
425             touchjp(Z) = Zdown;
426 
427             double t = bridge_mesh_distance(headjp, DOWN, r);
428 
429             // We can't insert a pillar under the source head to connect
430             // with the nearby pillar's starting junction
431             if(t < zdiff) return false;
432         }
433 
434         if(Zdown <= nearjp_u(Z) && Zdown >= nearjp_l(Z) && D < max_len)
435             bridgeend(Z) = Zdown;
436         else
437             return false;
438     }
439 
440     // There will be a minimum distance from the ground where the
441     // bridge is allowed to connect. This is an empiric value.
442     double minz = m_builder.ground_level + 4 * head.r_back_mm;
443     if(bridgeend(Z) < minz) return false;
444 
445     double t = bridge_mesh_distance(bridgestart, dirv(bridgestart, bridgeend), r);
446 
447     // Cannot insert the bridge. (further search might not worth the hassle)
448     if(t < distance(bridgestart, bridgeend)) return false;
449 
450     std::lock_guard<ccr::BlockingMutex> lk(m_bridge_mutex);
451 
452     if (m_builder.bridgecount(nearpillar()) < m_cfg.max_bridges_on_pillar) {
453         // A partial pillar is needed under the starting head.
454         if(zdiff > 0) {
455             m_builder.add_pillar(head.id, headjp.z() - bridgestart.z());
456             m_builder.add_junction(bridgestart, r);
457             m_builder.add_bridge(bridgestart, bridgeend, r);
458         } else {
459             m_builder.add_bridge(head.id, bridgeend);
460         }
461 
462         m_builder.increment_bridges(nearpillar());
463     } else return false;
464 
465     return true;
466 }
467 
create_ground_pillar(const Vec3d & hjp,const Vec3d & sourcedir,double radius,long head_id)468 bool SupportTreeBuildsteps::create_ground_pillar(const Vec3d &hjp,
469                                                  const Vec3d &sourcedir,
470                                                  double       radius,
471                                                  long         head_id)
472 {
473     Vec3d  jp           = hjp, endp = jp, dir = sourcedir;
474     long   pillar_id    = SupportTreeNode::ID_UNSET;
475     bool   can_add_base = false, non_head = false;
476 
477     double gndlvl = 0.; // The Z level where pedestals should be
478     double jp_gnd = 0.; // The lowest Z where a junction center can be
479     double gap_dist = 0.; // The gap distance between the model and the pad
480 
481     auto to_floor = [&gndlvl](const Vec3d &p) { return Vec3d{p.x(), p.y(), gndlvl}; };
482 
483     auto eval_limits = [this, &radius, &can_add_base, &gndlvl, &gap_dist, &jp_gnd]
484         (bool base_en = true)
485     {
486         can_add_base  = base_en && radius >= m_cfg.head_back_radius_mm;
487         double base_r = can_add_base ? m_cfg.base_radius_mm : 0.;
488         gndlvl        = m_builder.ground_level;
489         if (!can_add_base) gndlvl -= m_mesh.ground_level_offset();
490         jp_gnd   = gndlvl + (can_add_base ? 0. : m_cfg.head_back_radius_mm);
491         gap_dist = m_cfg.pillar_base_safety_distance_mm + base_r + EPSILON;
492     };
493 
494     eval_limits();
495 
496     // We are dealing with a mini pillar that's potentially too long
497     if (radius < m_cfg.head_back_radius_mm && jp.z() - gndlvl > 20 * radius)
498     {
499         std::optional<DiffBridge> diffbr =
500             search_widening_path(jp, dir, radius, m_cfg.head_back_radius_mm);
501 
502         if (diffbr && diffbr->endp.z() > jp_gnd) {
503             auto &br = m_builder.add_diffbridge(*diffbr);
504             if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id;
505             endp = diffbr->endp;
506             radius = diffbr->end_r;
507             m_builder.add_junction(endp, radius);
508             non_head = true;
509             dir = diffbr->get_dir();
510             eval_limits();
511         } else return false;
512     }
513 
514     if (m_cfg.object_elevation_mm < EPSILON)
515     {
516         // get a suitable direction for the corrector bridge. It is the
517         // original sourcedir's azimuth but the polar angle is saturated to the
518         // configured bridge slope.
519         auto [polar, azimuth] = dir_to_spheric(dir);
520         polar = PI - m_cfg.bridge_slope;
521         Vec3d d = spheric_to_dir(polar, azimuth).normalized();
522         double t = bridge_mesh_distance(endp, dir, radius);
523         double tmax = std::min(m_cfg.max_bridge_length_mm, t);
524         t = 0.;
525 
526         double zd = endp.z() - jp_gnd;
527         double tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope);
528         tmax = std::min(tmax, tmax2);
529 
530         Vec3d nexp = endp;
531         double dlast = 0.;
532         while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist ||
533                 !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) {
534             t += radius;
535             nexp = endp + t * d;
536         }
537 
538         if (dlast < gap_dist && can_add_base) {
539             nexp         = endp;
540             t            = 0.;
541             can_add_base = false;
542             eval_limits(can_add_base);
543 
544             zd = endp.z() - jp_gnd;
545             tmax2 = zd / std::sqrt(1 - m_cfg.bridge_slope * m_cfg.bridge_slope);
546             tmax = std::min(tmax, tmax2);
547 
548             while (((dlast = std::sqrt(m_mesh.squared_distance(to_floor(nexp)))) < gap_dist ||
549                     !std::isinf(bridge_mesh_distance(nexp, DOWN, radius))) && t < tmax) {
550                 t += radius;
551                 nexp = endp + t * d;
552             }
553         }
554 
555         // Could not find a path to avoid the pad gap
556         if (dlast < gap_dist) return false;
557 
558         if (t > 0.) { // Need to make additional bridge
559             const Bridge& br = m_builder.add_bridge(endp, nexp, radius);
560             if (head_id >= 0) m_builder.head(head_id).bridge_id = br.id;
561 
562             m_builder.add_junction(nexp, radius);
563             endp = nexp;
564             non_head = true;
565         }
566     }
567 
568     Vec3d gp = to_floor(endp);
569     double h = endp.z() - gp.z();
570 
571     pillar_id = head_id >= 0 && !non_head ? m_builder.add_pillar(head_id, h) :
572                                             m_builder.add_pillar(gp, h, radius);
573 
574     if (can_add_base)
575         add_pillar_base(pillar_id);
576 
577     if(pillar_id >= 0) // Save the pillar endpoint in the spatial index
578         m_pillar_index.guarded_insert(m_builder.pillar(pillar_id).endpt,
579                                       unsigned(pillar_id));
580 
581     return true;
582 }
583 
search_widening_path(const Vec3d & jp,const Vec3d & dir,double radius,double new_radius)584 std::optional<DiffBridge> SupportTreeBuildsteps::search_widening_path(
585     const Vec3d &jp, const Vec3d &dir, double radius, double new_radius)
586 {
587     double w = radius + 2 * m_cfg.head_back_radius_mm;
588     double stopval = w + jp.z() - m_builder.ground_level;
589     Optimizer<AlgNLoptSubplex> solver(get_criteria(m_cfg).stop_score(stopval));
590 
591     auto [polar, azimuth] = dir_to_spheric(dir);
592 
593     double fallback_ratio = radius / m_cfg.head_back_radius_mm;
594 
595     auto oresult = solver.to_max().optimize(
596         [this, jp, radius, new_radius](const opt::Input<3> &input) {
597             auto &[plr, azm, t] = input;
598 
599             auto   d   = spheric_to_dir(plr, azm).normalized();
600             double ret = pinhead_mesh_intersect(jp, d, radius, new_radius, t)
601                              .distance();
602             double down = bridge_mesh_distance(jp + t * d, d, new_radius);
603 
604             if (ret > t && std::isinf(down))
605                 ret += jp.z() - m_builder.ground_level;
606 
607             return ret;
608         },
609         initvals({polar, azimuth, w}), // start with what we have
610         bounds({
611             {PI - m_cfg.bridge_slope, PI}, // Must not exceed the slope limit
612             {-PI, PI}, // azimuth can be a full search
613             {radius + m_cfg.head_back_radius_mm,
614                   fallback_ratio * m_cfg.max_bridge_length_mm}
615         }));
616 
617     if (oresult.score >= stopval) {
618         polar       = std::get<0>(oresult.optimum);
619         azimuth     = std::get<1>(oresult.optimum);
620         double t    = std::get<2>(oresult.optimum);
621         Vec3d  endp = jp + t * spheric_to_dir(polar, azimuth);
622 
623         return DiffBridge(jp, endp, radius, m_cfg.head_back_radius_mm);
624     }
625 
626     return {};
627 }
628 
filter()629 void SupportTreeBuildsteps::filter()
630 {
631     // Get the points that are too close to each other and keep only the
632     // first one
633     auto aliases = cluster(m_points, D_SP, 2);
634 
635     PtIndices filtered_indices;
636     filtered_indices.reserve(aliases.size());
637     m_iheads.reserve(aliases.size());
638     m_iheadless.reserve(aliases.size());
639     for(auto& a : aliases) {
640         // Here we keep only the front point of the cluster.
641         filtered_indices.emplace_back(a.front());
642     }
643 
644     // calculate the normals to the triangles for filtered points
645     auto nmls = sla::normals(m_points, m_mesh, m_cfg.head_front_radius_mm,
646                              m_thr, filtered_indices);
647 
648     // Not all of the support points have to be a valid position for
649     // support creation. The angle may be inappropriate or there may
650     // not be enough space for the pinhead. Filtering is applied for
651     // these reasons.
652 
653     std::vector<Head> heads; heads.reserve(m_support_pts.size());
654     for (const SupportPoint &sp : m_support_pts) {
655         m_thr();
656         heads.emplace_back(
657             std::nan(""),
658             sp.head_front_radius,
659             0.,
660             m_cfg.head_penetration_mm,
661             Vec3d::Zero(),         // dir
662             sp.pos.cast<double>() // displacement
663             );
664     }
665 
666     std::function<void(unsigned, size_t, double)> filterfn;
667     filterfn = [this, &nmls, &heads, &filterfn](unsigned fidx, size_t i, double back_r) {
668         m_thr();
669 
670         auto n = nmls.row(Eigen::Index(i));
671 
672         // for all normals we generate the spherical coordinates and
673         // saturate the polar angle to 45 degrees from the bottom then
674         // convert back to standard coordinates to get the new normal.
675         // Then we just create a quaternion from the two normals
676         // (Quaternion::FromTwoVectors) and apply the rotation to the
677         // arrow head.
678 
679         auto [polar, azimuth] = dir_to_spheric(n);
680 
681         // skip if the tilt is not sane
682         if (polar < PI - m_cfg.normal_cutoff_angle) return;
683 
684         // We saturate the polar angle to 3pi/4
685         polar = std::max(polar, PI - m_cfg.bridge_slope);
686 
687         // save the head (pinpoint) position
688         Vec3d hp = m_points.row(fidx);
689 
690         double lmin = m_cfg.head_width_mm, lmax = lmin;
691 
692         if (back_r < m_cfg.head_back_radius_mm) {
693             lmin = 0., lmax = m_cfg.head_penetration_mm;
694         }
695 
696         // The distance needed for a pinhead to not collide with model.
697         double w = lmin + 2 * back_r + 2 * m_cfg.head_front_radius_mm -
698                    m_cfg.head_penetration_mm;
699 
700         double pin_r = double(m_support_pts[fidx].head_front_radius);
701 
702         // Reassemble the now corrected normal
703         auto nn = spheric_to_dir(polar, azimuth).normalized();
704 
705         // check available distance
706         IndexedMesh::hit_result t = pinhead_mesh_intersect(hp, nn, pin_r,
707                                                            back_r, w);
708 
709         if (t.distance() < w) {
710             // Let's try to optimize this angle, there might be a
711             // viable normal that doesn't collide with the model
712             // geometry and its very close to the default.
713 
714             Optimizer<AlgNLoptGenetic> solver(get_criteria(m_cfg));
715             solver.seed(0); // we want deterministic behavior
716 
717             auto oresult = solver.to_max().optimize(
718                 [this, pin_r, back_r, hp](const opt::Input<3> &input)
719                 {
720                     auto &[plr, azm, l] = input;
721 
722                     auto dir = spheric_to_dir(plr, azm).normalized();
723 
724                     return pinhead_mesh_intersect(
725                         hp, dir, pin_r, back_r, l).distance();
726                 },
727                 initvals({polar, azimuth, (lmin + lmax) / 2.}), // start with what we have
728                 bounds({
729                     {PI - m_cfg.bridge_slope, PI},    // Must not exceed the slope limit
730                     {-PI, PI}, // azimuth can be a full search
731                     {lmin, lmax}
732                 }));
733 
734             if(oresult.score > w) {
735                 polar = std::get<0>(oresult.optimum);
736                 azimuth = std::get<1>(oresult.optimum);
737                 nn = spheric_to_dir(polar, azimuth).normalized();
738                 lmin = std::get<2>(oresult.optimum);
739                 t = IndexedMesh::hit_result(oresult.score);
740             }
741         }
742 
743         if (t.distance() > w && hp(Z) + w * nn(Z) >= m_builder.ground_level) {
744             Head &h = heads[fidx];
745             h.id = fidx; h.dir = nn; h.width_mm = lmin; h.r_back_mm = back_r;
746         } else if (back_r > m_cfg.head_fallback_radius_mm) {
747             filterfn(fidx, i, m_cfg.head_fallback_radius_mm);
748         }
749     };
750 
751     ccr::for_each(size_t(0), filtered_indices.size(),
752                   [this, &filterfn, &filtered_indices] (size_t i) {
753                       filterfn(filtered_indices[i], i, m_cfg.head_back_radius_mm);
754                   });
755 
756     for (size_t i = 0; i < heads.size(); ++i)
757         if (heads[i].is_valid()) {
758             m_builder.add_head(i, heads[i]);
759             m_iheads.emplace_back(i);
760         }
761 
762     m_thr();
763 }
764 
add_pinheads()765 void SupportTreeBuildsteps::add_pinheads()
766 {
767 }
768 
classify()769 void SupportTreeBuildsteps::classify()
770 {
771     // We should first get the heads that reach the ground directly
772     PtIndices ground_head_indices;
773     ground_head_indices.reserve(m_iheads.size());
774     m_iheads_onmodel.reserve(m_iheads.size());
775 
776     // First we decide which heads reach the ground and can be full
777     // pillars and which shall be connected to the model surface (or
778     // search a suitable path around the surface that leads to the
779     // ground -- TODO)
780     for(unsigned i : m_iheads) {
781         m_thr();
782 
783         Head &head = m_builder.head(i);
784         double r = head.r_back_mm;
785         Vec3d headjp = head.junction_point();
786 
787         // collision check
788         auto hit = bridge_mesh_intersect(headjp, DOWN, r);
789 
790         if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
791         else if(m_cfg.ground_facing_only)  head.invalidate();
792         else m_iheads_onmodel.emplace_back(i);
793 
794         m_head_to_ground_scans[i] = hit;
795     }
796 
797     // We want to search for clusters of points that are far enough
798     // from each other in the XY plane to not cross their pillar bases
799     // These clusters of support points will join in one pillar,
800     // possibly in their centroid support point.
801 
802     auto pointfn = [this](unsigned i) {
803         return m_builder.head(i).junction_point();
804     };
805 
806     auto predicate = [this](const PointIndexEl &e1,
807                             const PointIndexEl &e2) {
808         double d2d = distance(to_2d(e1.first), to_2d(e2.first));
809         double d3d = distance(e1.first, e2.first);
810         return d2d < 2 * m_cfg.base_radius_mm
811                && d3d < m_cfg.max_bridge_length_mm;
812     };
813 
814     m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate,
815                                 m_cfg.max_bridges_on_pillar);
816 }
817 
routing_to_ground()818 void SupportTreeBuildsteps::routing_to_ground()
819 {
820     ClusterEl cl_centroids;
821     cl_centroids.reserve(m_pillar_clusters.size());
822 
823     for (auto &cl : m_pillar_clusters) {
824         m_thr();
825 
826         // place all the centroid head positions into the index. We
827         // will query for alternative pillar positions. If a sidehead
828         // cannot connect to the cluster centroid, we have to search
829         // for another head with a full pillar. Also when there are two
830         // elements in the cluster, the centroid is arbitrary and the
831         // sidehead is allowed to connect to a nearby pillar to
832         // increase structural stability.
833 
834         if (cl.empty()) continue;
835 
836         // get the current cluster centroid
837         auto &      thr    = m_thr;
838         const auto &points = m_points;
839 
840         long lcid = cluster_centroid(
841             cl, [&points](size_t idx) { return points.row(long(idx)); },
842             [thr](const Vec3d &p1, const Vec3d &p2) {
843                 thr();
844                 return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y)));
845             });
846 
847         assert(lcid >= 0);
848         unsigned hid = cl[size_t(lcid)]; // Head ID
849 
850         cl_centroids.emplace_back(hid);
851 
852         Head &h = m_builder.head(hid);
853 
854         if (!create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id)) {
855             BOOST_LOG_TRIVIAL(warning)
856                 << "Pillar cannot be created for support point id: " << hid;
857             m_iheads_onmodel.emplace_back(h.id);
858             continue;
859         }
860     }
861 
862     // now we will go through the clusters ones again and connect the
863     // sidepoints with the cluster centroid (which is a ground pillar)
864     // or a nearby pillar if the centroid is unreachable.
865     size_t ci = 0;
866     for (auto cl : m_pillar_clusters) {
867         m_thr();
868 
869         auto cidx = cl_centroids[ci++];
870 
871         auto q = m_pillar_index.query(m_builder.head(cidx).junction_point(), 1);
872         if (!q.empty()) {
873             long centerpillarID = q.front().second;
874             for (auto c : cl) {
875                 m_thr();
876                 if (c == cidx) continue;
877 
878                 auto &sidehead = m_builder.head(c);
879 
880                 if (!connect_to_nearpillar(sidehead, centerpillarID) &&
881                     !search_pillar_and_connect(sidehead)) {
882                     Vec3d pstart = sidehead.junction_point();
883                     // Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl};
884                     // Could not find a pillar, create one
885                     create_ground_pillar(pstart, sidehead.dir, sidehead.r_back_mm, sidehead.id);
886                 }
887             }
888         }
889     }
890 }
891 
connect_to_ground(Head & head,const Vec3d & dir)892 bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
893 {
894     auto hjp = head.junction_point();
895     double r = head.r_back_mm;
896     double t = bridge_mesh_distance(hjp, dir, head.r_back_mm);
897     double d = 0, tdown = 0;
898     t = std::min(t, m_cfg.max_bridge_length_mm * r / m_cfg.head_back_radius_mm);
899 
900     while (d < t && !std::isinf(tdown = bridge_mesh_distance(hjp + d * dir, DOWN, r)))
901         d += r;
902 
903     if(!std::isinf(tdown)) return false;
904 
905     Vec3d endp = hjp + d * dir;
906     bool ret = false;
907 
908     if ((ret = create_ground_pillar(endp, dir, head.r_back_mm))) {
909         m_builder.add_bridge(head.id, endp);
910         m_builder.add_junction(endp, head.r_back_mm);
911     }
912 
913     return ret;
914 }
915 
connect_to_ground(Head & head)916 bool SupportTreeBuildsteps::connect_to_ground(Head &head)
917 {
918     if (connect_to_ground(head, head.dir)) return true;
919 
920     // Optimize bridge direction:
921     // Straight path failed so we will try to search for a suitable
922     // direction out of the cavity.
923     auto [polar, azimuth] = dir_to_spheric(head.dir);
924 
925     Optimizer<AlgNLoptGenetic> solver(get_criteria(m_cfg).stop_score(1e6));
926     solver.seed(0); // we want deterministic behavior
927 
928     double r_back = head.r_back_mm;
929     Vec3d hjp = head.junction_point();
930     auto oresult = solver.to_max().optimize(
931         [this, hjp, r_back](const opt::Input<2> &input) {
932             auto &[plr, azm] = input;
933             Vec3d n = spheric_to_dir(plr, azm).normalized();
934             return bridge_mesh_distance(hjp, n, r_back);
935         },
936         initvals({polar, azimuth}),  // let's start with what we have
937         bounds({ {PI - m_cfg.bridge_slope, PI}, {-PI, PI} })
938     );
939 
940     Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
941     return connect_to_ground(head, bridgedir);
942 }
943 
connect_to_model_body(Head & head)944 bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
945 {
946     if (head.id <= SupportTreeNode::ID_UNSET) return false;
947 
948     auto it = m_head_to_ground_scans.find(unsigned(head.id));
949     if (it == m_head_to_ground_scans.end()) return false;
950 
951     auto &hit = it->second;
952 
953     if (!hit.is_hit()) {
954         // TODO scan for potential anchor points on model surface
955         return false;
956     }
957 
958     Vec3d hjp = head.junction_point();
959     double zangle = std::asin(hit.direction()(Z));
960     zangle = std::max(zangle, PI/4);
961     double h = std::sin(zangle) * head.fullwidth();
962 
963     // The width of the tail head that we would like to have...
964     h = std::min(hit.distance() - head.r_back_mm, h);
965 
966     // If this is a mini pillar dont bother with the tail width, can be 0.
967     if (head.r_back_mm < m_cfg.head_back_radius_mm) h = std::max(h, 0.);
968     else if (h <= 0.) return false;
969 
970     Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
971     auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
972 
973     double hitdiff = center_hit.distance() - hit.distance();
974     Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
975                      center_hit.position() : hit.position();
976 
977     long pillar_id = m_builder.add_pillar(head.id, hjp.z() - endp.z());
978     Pillar &pill = m_builder.pillar(pillar_id);
979 
980     Vec3d taildir = endp - hitp;
981     double dist = (hitp - endp).norm() + m_cfg.head_penetration_mm;
982     double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
983 
984     if (w < 0.) {
985         BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
986         w = 0.;
987     }
988 
989     m_builder.add_anchor(head.r_back_mm, head.r_pin_mm, w,
990                          m_cfg.head_penetration_mm, taildir, hitp);
991 
992     m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
993 
994     return true;
995 }
996 
search_pillar_and_connect(const Head & source)997 bool SupportTreeBuildsteps::search_pillar_and_connect(const Head &source)
998 {
999     // Hope that a local copy takes less time than the whole search loop.
1000     // We also need to remove elements progressively from the copied index.
1001     PointIndex spindex = m_pillar_index.guarded_clone();
1002 
1003     long nearest_id = SupportTreeNode::ID_UNSET;
1004 
1005     Vec3d querypt = source.junction_point();
1006 
1007     while(nearest_id < 0 && !spindex.empty()) { m_thr();
1008         // loop until a suitable head is not found
1009         // if there is a pillar closer than the cluster center
1010         // (this may happen as the clustering is not perfect)
1011         // than we will bridge to this closer pillar
1012 
1013         Vec3d qp(querypt(X), querypt(Y), m_builder.ground_level);
1014         auto qres = spindex.nearest(qp, 1);
1015         if(qres.empty()) break;
1016 
1017         auto ne = qres.front();
1018         nearest_id = ne.second;
1019 
1020         if(nearest_id >= 0) {
1021             if (size_t(nearest_id) < m_builder.pillarcount()) {
1022                 if(!connect_to_nearpillar(source, nearest_id) ||
1023                     m_builder.pillar(nearest_id).r < source.r_back_mm) {
1024                     nearest_id = SupportTreeNode::ID_UNSET;    // continue searching
1025                     spindex.remove(ne);       // without the current pillar
1026                 }
1027             }
1028         }
1029     }
1030 
1031     return nearest_id >= 0;
1032 }
1033 
routing_to_model()1034 void SupportTreeBuildsteps::routing_to_model()
1035 {
1036     // We need to check if there is an easy way out to the bed surface.
1037     // If it can be routed there with a bridge shorter than
1038     // min_bridge_distance.
1039 
1040     ccr::for_each(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
1041                   [this] (const unsigned idx) {
1042         m_thr();
1043 
1044         auto& head = m_builder.head(idx);
1045 
1046         // Search nearby pillar
1047         if (search_pillar_and_connect(head)) { return; }
1048 
1049         // Cannot connect to nearby pillar. We will try to search for
1050         // a route to the ground.
1051         if (connect_to_ground(head)) { return; }
1052 
1053         // No route to the ground, so connect to the model body as a last resort
1054         if (connect_to_model_body(head)) { return; }
1055 
1056         // We have failed to route this head.
1057         BOOST_LOG_TRIVIAL(warning)
1058                 << "Failed to route model facing support point. ID: " << idx;
1059 
1060         head.invalidate();
1061     });
1062 }
1063 
interconnect_pillars()1064 void SupportTreeBuildsteps::interconnect_pillars()
1065 {
1066     // Now comes the algorithm that connects pillars with each other.
1067     // Ideally every pillar should be connected with at least one of its
1068     // neighbors if that neighbor is within max_pillar_link_distance
1069 
1070     // Pillars with height exceeding H1 will require at least one neighbor
1071     // to connect with. Height exceeding H2 require two neighbors.
1072     double H1 = m_cfg.max_solo_pillar_height_mm;
1073     double H2 = m_cfg.max_dual_pillar_height_mm;
1074     double d = m_cfg.max_pillar_link_distance_mm;
1075 
1076     //A connection between two pillars only counts if the height ratio is
1077     // bigger than 50%
1078     double min_height_ratio = 0.5;
1079 
1080     std::set<unsigned long> pairs;
1081 
1082     // A function to connect one pillar with its neighbors. THe number of
1083     // neighbors is given in the configuration. This function if called
1084     // for every pillar in the pillar index. A pair of pillar will not
1085     // be connected multiple times this is ensured by the 'pairs' set which
1086     // remembers the processed pillar pairs
1087     auto cascadefn =
1088         [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el)
1089     {
1090         Vec3d qp = el.first;    // endpoint of the pillar
1091 
1092         const Pillar& pillar = m_builder.pillar(el.second); // actual pillar
1093 
1094         // Get the max number of neighbors a pillar should connect to
1095         unsigned neighbors = m_cfg.pillar_cascade_neighbors;
1096 
1097         // connections are already enough for the pillar
1098         if(pillar.links >= neighbors) return;
1099 
1100         double max_d = d * pillar.r / m_cfg.head_back_radius_mm;
1101         // Query all remaining points within reach
1102         auto qres = m_pillar_index.query([qp, max_d](const PointIndexEl& e){
1103             return distance(e.first, qp) < max_d;
1104         });
1105 
1106         // sort the result by distance (have to check if this is needed)
1107         std::sort(qres.begin(), qres.end(),
1108                   [qp](const PointIndexEl& e1, const PointIndexEl& e2){
1109                       return distance(e1.first, qp) < distance(e2.first, qp);
1110                   });
1111 
1112         for(auto& re : qres) { // process the queried neighbors
1113 
1114             if(re.second == el.second) continue; // Skip self
1115 
1116             auto a = el.second, b = re.second;
1117 
1118             // Get unique hash for the given pair (order doesn't matter)
1119             auto hashval = pairhash(a, b);
1120 
1121             // Search for the pair amongst the remembered pairs
1122             if(pairs.find(hashval) != pairs.end()) continue;
1123 
1124             const Pillar& neighborpillar = m_builder.pillar(re.second);
1125 
1126             // this neighbor is occupied, skip
1127             if (neighborpillar.links >= neighbors) continue;
1128             if (neighborpillar.r < pillar.r) continue;
1129 
1130             if(interconnect(pillar, neighborpillar)) {
1131                 pairs.insert(hashval);
1132 
1133                 // If the interconnection length between the two pillars is
1134                 // less than 50% of the longer pillar's height, don't count
1135                 if(pillar.height < H1 ||
1136                     neighborpillar.height / pillar.height > min_height_ratio)
1137                     m_builder.increment_links(pillar);
1138 
1139                 if(neighborpillar.height < H1 ||
1140                     pillar.height / neighborpillar.height > min_height_ratio)
1141                     m_builder.increment_links(neighborpillar);
1142 
1143             }
1144 
1145             // connections are enough for one pillar
1146             if(pillar.links >= neighbors) break;
1147         }
1148     };
1149 
1150     // Run the cascade for the pillars in the index
1151     m_pillar_index.foreach(cascadefn);
1152 
1153     // We would be done here if we could allow some pillars to not be
1154     // connected with any neighbors. But this might leave the support tree
1155     // unprintable.
1156     //
1157     // The current solution is to insert additional pillars next to these
1158     // lonely pillars. One or even two additional pillar might get inserted
1159     // depending on the length of the lonely pillar.
1160 
1161     size_t pillarcount = m_builder.pillarcount();
1162 
1163     // Again, go through all pillars, this time in the whole support tree
1164     // not just the index.
1165     for(size_t pid = 0; pid < pillarcount; pid++) {
1166         auto pillar = [this, pid]() { return m_builder.pillar(pid); };
1167 
1168         // Decide how many additional pillars will be needed:
1169 
1170         unsigned needpillars = 0;
1171         if (pillar().bridges > m_cfg.max_bridges_on_pillar)
1172             needpillars = 3;
1173         else if (pillar().links < 2 && pillar().height > H2) {
1174             // Not enough neighbors to support this pillar
1175             needpillars = 2;
1176         } else if (pillar().links < 1 && pillar().height > H1) {
1177             // No neighbors could be found and the pillar is too long.
1178             needpillars = 1;
1179         }
1180 
1181         needpillars = std::max(pillar().links, needpillars) - pillar().links;
1182         if (needpillars == 0) continue;
1183 
1184         // Search for new pillar locations:
1185 
1186         bool   found    = false;
1187         double alpha    = 0; // goes to 2Pi
1188         double r        = 2 * m_cfg.base_radius_mm;
1189         Vec3d  pillarsp = pillar().startpoint();
1190 
1191         // temp value for starting point detection
1192         Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r);
1193 
1194         // A vector of bool for placement feasbility
1195         std::vector<bool>  canplace(needpillars, false);
1196         std::vector<Vec3d> spts(needpillars); // vector of starting points
1197 
1198         double gnd      = m_builder.ground_level;
1199         double min_dist = m_cfg.pillar_base_safety_distance_mm +
1200                           m_cfg.base_radius_mm + EPSILON;
1201 
1202         while(!found && alpha < 2*PI) {
1203             for (unsigned n = 0;
1204                  n < needpillars && (!n || canplace[n - 1]);
1205                  n++)
1206             {
1207                 double a = alpha + n * PI / 3;
1208                 Vec3d  s = sp;
1209                 s(X) += std::cos(a) * r;
1210                 s(Y) += std::sin(a) * r;
1211                 spts[n] = s;
1212 
1213                 // Check the path vertically down
1214                 Vec3d check_from = s + Vec3d{0., 0., pillar().r};
1215                 auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r);
1216                 Vec3d gndsp{s(X), s(Y), gnd};
1217 
1218                 // If the path is clear, check for pillar base collisions
1219                 canplace[n] = std::isinf(hr.distance()) &&
1220                               std::sqrt(m_mesh.squared_distance(gndsp)) >
1221                                   min_dist;
1222             }
1223 
1224             found = std::all_of(canplace.begin(), canplace.end(),
1225                                 [](bool v) { return v; });
1226 
1227             // 20 angles will be tried...
1228             alpha += 0.1 * PI;
1229         }
1230 
1231         std::vector<long> newpills;
1232         newpills.reserve(needpillars);
1233 
1234         if (found)
1235             for (unsigned n = 0; n < needpillars; n++) {
1236                 Vec3d s = spts[n];
1237                 Pillar p(Vec3d{s.x(), s.y(), gnd}, s.z() - gnd, pillar().r);
1238 
1239                 if (interconnect(pillar(), p)) {
1240                     Pillar &pp = m_builder.pillar(m_builder.add_pillar(p));
1241 
1242                     add_pillar_base(pp.id);
1243 
1244                     m_pillar_index.insert(pp.endpoint(), unsigned(pp.id));
1245 
1246                     m_builder.add_junction(s, pillar().r);
1247                     double t = bridge_mesh_distance(pillarsp, dirv(pillarsp, s),
1248                                                     pillar().r);
1249                     if (distance(pillarsp, s) < t)
1250                         m_builder.add_bridge(pillarsp, s, pillar().r);
1251 
1252                     if (pillar().endpoint()(Z) > m_builder.ground_level + pillar().r)
1253                         m_builder.add_junction(pillar().endpoint(), pillar().r);
1254 
1255                     newpills.emplace_back(pp.id);
1256                     m_builder.increment_links(pillar());
1257                     m_builder.increment_links(pp);
1258                 }
1259             }
1260 
1261         if(!newpills.empty()) {
1262             for(auto it = newpills.begin(), nx = std::next(it);
1263                  nx != newpills.end(); ++it, ++nx) {
1264                 const Pillar& itpll = m_builder.pillar(*it);
1265                 const Pillar& nxpll = m_builder.pillar(*nx);
1266                 if(interconnect(itpll, nxpll)) {
1267                     m_builder.increment_links(itpll);
1268                     m_builder.increment_links(nxpll);
1269                 }
1270             }
1271 
1272             m_pillar_index.foreach(cascadefn);
1273         }
1274     }
1275 }
1276 
1277 }} // namespace Slic3r::sla
1278